mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
Merge branch 'zack/ssg-3' of github.com:oven-sh/bun into ali/react
This commit is contained in:
@@ -1098,10 +1098,10 @@ const ReqOrSaved = union(enum) {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn url(this: *const @This(), alloc: Allocator) []const u8 {
|
||||
pub fn url(this: *const @This(), alloc: Allocator) bun.ZigString.Slice {
|
||||
return switch (this.*) {
|
||||
.req => |req| req.url(),
|
||||
.saved => |saved| saved.request.url.toUTF8(alloc).slice(),
|
||||
.req => |req| bun.ZigString.Slice.fromUTF8NeverFree(req.url()),
|
||||
.saved => |saved| saved.request.url.toUTF8(alloc),
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1120,7 +1120,6 @@ fn deferRequest(
|
||||
deferred.data = .{
|
||||
.route_bundle_index = route_bundle_index,
|
||||
.dev = dev,
|
||||
.ref_count = .init(),
|
||||
.handler = switch (kind) {
|
||||
.bundled_html_page => brk: {
|
||||
resp.onAborted(*DeferredRequest, DeferredRequest.onAbort, &deferred.data);
|
||||
@@ -1134,14 +1133,28 @@ fn deferRequest(
|
||||
},
|
||||
.saved => |saved| saved,
|
||||
};
|
||||
server_handler.ctx.setAdditionalOnAbortCallback(.{ .cb = DeferredRequest.onAbortWrapper, .data = &deferred.data });
|
||||
server_handler.ctx.ref();
|
||||
server_handler.ctx.setAdditionalOnAbortCallback(.{
|
||||
.cb = DeferredRequest.onAbortWrapper,
|
||||
.data = &deferred.data,
|
||||
.deref_fn = struct {
|
||||
fn deref_fn(ptr: *anyopaque) void {
|
||||
var self: *DeferredRequest = @ptrCast(@alignCast(ptr));
|
||||
self.weakDeref();
|
||||
}
|
||||
}.deref_fn,
|
||||
});
|
||||
break :brk .{
|
||||
.server_handler = server_handler,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
deferred.data.ref();
|
||||
|
||||
if (deferred.data.handler == .server_handler) {
|
||||
deferred.data.weakRef();
|
||||
}
|
||||
|
||||
requests_array.prepend(deferred);
|
||||
}
|
||||
|
||||
@@ -1589,24 +1602,45 @@ pub const DeferredRequest = struct {
|
||||
pub const List = std.SinglyLinkedList(DeferredRequest);
|
||||
pub const Node = List.Node;
|
||||
|
||||
const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinitImpl, .{
|
||||
.debug_name = "DeferredRequest",
|
||||
});
|
||||
|
||||
const debugLog = bun.Output.Scoped("DlogeferredRequest", .hidden).log;
|
||||
|
||||
route_bundle_index: RouteBundle.Index,
|
||||
handler: Handler,
|
||||
dev: *DevServer,
|
||||
|
||||
/// This struct can have at most 2 references it:
|
||||
/// - The dev server (`dev.current_bundle.requests`)
|
||||
/// - uws.Response as a user data pointer
|
||||
ref_count: RefCount,
|
||||
/// This struct can be referenced by the dev server (`dev.current_bundle.requests`)
|
||||
///
|
||||
/// Simultaneously, RequestContext may refer to it for AdditionalOnAbortCallback,
|
||||
/// but we treat this as a weak reference, otherwise we will have reference
|
||||
/// count cycles as this struct itself may store a reference to
|
||||
/// RequestContext in the `server_handler` variant of `Handler`
|
||||
referenced_by_devserver: bool = true,
|
||||
weakly_referenced_by_requestcontext: bool = false,
|
||||
|
||||
// expose `ref` and `deref` as public methods
|
||||
pub const ref = RefCount.ref;
|
||||
pub const deref = RefCount.deref;
|
||||
pub fn isAlive(this: *DeferredRequest) bool {
|
||||
return this.referenced_by_devserver;
|
||||
}
|
||||
|
||||
pub fn deref(this: *DeferredRequest) void {
|
||||
this.referenced_by_devserver = false;
|
||||
const should_free = !this.weakly_referenced_by_requestcontext;
|
||||
this.__deinit();
|
||||
if (should_free) {
|
||||
this.__free();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn weakRef(this: *DeferredRequest) void {
|
||||
bun.assert(!this.weakly_referenced_by_requestcontext);
|
||||
this.weakly_referenced_by_requestcontext = true;
|
||||
}
|
||||
|
||||
pub fn weakDeref(this: *DeferredRequest) void {
|
||||
this.weakly_referenced_by_requestcontext = false;
|
||||
if (!this.referenced_by_devserver) {
|
||||
this.__free();
|
||||
}
|
||||
}
|
||||
|
||||
const Handler = union(enum) {
|
||||
/// For a .framework route. This says to call and render the page.
|
||||
@@ -1628,6 +1662,7 @@ pub const DeferredRequest = struct {
|
||||
|
||||
fn onAbortWrapper(this: *anyopaque) void {
|
||||
const self: *DeferredRequest = @alignCast(@ptrCast(this));
|
||||
if (!self.isAlive()) return;
|
||||
self.onAbortImpl();
|
||||
}
|
||||
|
||||
@@ -1642,16 +1677,19 @@ pub const DeferredRequest = struct {
|
||||
assert(this.handler == .aborted);
|
||||
}
|
||||
|
||||
/// Actually free the underlying allocation for the node, does not deinitialize children
|
||||
fn __free(this: *DeferredRequest) void {
|
||||
this.dev.deferred_request_pool.put(@fieldParentPtr("data", this));
|
||||
}
|
||||
|
||||
/// *WARNING*: Do not call this directly, instead call `.deref()`
|
||||
///
|
||||
/// Calling this is only required if the desired handler is going to be avoided,
|
||||
/// such as for bundling failures or aborting the server.
|
||||
/// Does not free the underlying `DeferredRequest.Node`
|
||||
fn deinitImpl(this: *DeferredRequest) void {
|
||||
fn __deinit(this: *DeferredRequest) void {
|
||||
debugLog("DeferredRequest(0x{x}) deinitImpl", .{@intFromPtr(this)});
|
||||
this.ref_count.assertNoRefs();
|
||||
|
||||
defer this.dev.deferred_request_pool.put(@fieldParentPtr("data", this));
|
||||
switch (this.handler) {
|
||||
.server_handler => |*saved| saved.deinit(),
|
||||
.bundled_html_page, .aborted => {},
|
||||
|
||||
@@ -465,6 +465,7 @@ pub const Run = struct {
|
||||
}
|
||||
|
||||
bun.api.napi.fixDeadCodeElimination();
|
||||
bun.webcore.BakeResponse.fixDeadCodeElimination();
|
||||
bun.crash_handler.fixDeadCodeElimination();
|
||||
@import("./bun.js/bindings/JSSecrets.zig").fixDeadCodeElimination();
|
||||
vm.globalExit();
|
||||
|
||||
@@ -243,6 +243,28 @@ pub fn onAbort(self: AnyRequestContext, response: uws.AnyResponse) void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ref(self: AnyRequestContext) void {
|
||||
if (self.tagged_pointer.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (self.tagged_pointer.tag()) {
|
||||
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(HTTPServer.RequestContext))) => {
|
||||
self.tagged_pointer.as(HTTPServer.RequestContext).ref();
|
||||
},
|
||||
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(HTTPSServer.RequestContext))) => {
|
||||
self.tagged_pointer.as(HTTPSServer.RequestContext).ref();
|
||||
},
|
||||
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPServer.RequestContext))) => {
|
||||
self.tagged_pointer.as(DebugHTTPServer.RequestContext).ref();
|
||||
},
|
||||
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPSServer.RequestContext))) => {
|
||||
self.tagged_pointer.as(DebugHTTPSServer.RequestContext).ref();
|
||||
},
|
||||
else => @panic("Unexpected AnyRequestContext tag"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deref(self: AnyRequestContext) void {
|
||||
if (self.tagged_pointer.isNull()) {
|
||||
return;
|
||||
|
||||
@@ -11,6 +11,11 @@
|
||||
pub const AdditionalOnAbortCallback = struct {
|
||||
cb: *const fn (this: *anyopaque) void,
|
||||
data: *anyopaque,
|
||||
deref_fn: *const fn (this: *anyopaque) void,
|
||||
|
||||
pub fn deref(this: AdditionalOnAbortCallback) void {
|
||||
this.deref_fn(this.data);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comptime ThisServer: type) type {
|
||||
@@ -258,6 +263,11 @@ pub fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool,
|
||||
this.request_body = null;
|
||||
}
|
||||
|
||||
if (this.additional_on_abort) |cb| {
|
||||
cb.deref();
|
||||
this.additional_on_abort = null;
|
||||
}
|
||||
|
||||
if (this.server) |server| {
|
||||
this.server = null;
|
||||
server.request_pool_allocator.put(this);
|
||||
@@ -266,7 +276,7 @@ pub fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool,
|
||||
}
|
||||
|
||||
pub fn deref(this: *RequestContext) void {
|
||||
streamLog("deref", .{});
|
||||
streamLog("deref {d} -> {d}", .{ this.ref_count, this.ref_count - 1 });
|
||||
assert(this.ref_count > 0);
|
||||
const ref_count = this.ref_count;
|
||||
this.ref_count -= 1;
|
||||
@@ -277,7 +287,7 @@ pub fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool,
|
||||
}
|
||||
|
||||
pub fn ref(this: *RequestContext) void {
|
||||
streamLog("ref", .{});
|
||||
streamLog("ref {d} -> {d}", .{ this.ref_count, this.ref_count + 1 });
|
||||
this.ref_count += 1;
|
||||
}
|
||||
|
||||
@@ -599,6 +609,7 @@ pub fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool,
|
||||
// mark request as aborted
|
||||
this.flags.aborted = true;
|
||||
if (this.additional_on_abort) |abort| {
|
||||
defer abort.deref();
|
||||
this.additional_on_abort = null;
|
||||
abort.cb(abort.data);
|
||||
}
|
||||
|
||||
@@ -33,10 +33,10 @@ static JSC_DECLARE_CUSTOM_GETTER(jsBakeResponsePrototypeGetDebugInfo);
|
||||
static JSC_DECLARE_CUSTOM_GETTER(jsBakeResponsePrototypeGetDebugStack);
|
||||
static JSC_DECLARE_CUSTOM_GETTER(jsBakeResponsePrototypeGetDebugTask);
|
||||
|
||||
extern JSC_CALLCONV void* JSC_HOST_CALL_ATTRIBUTES ResponseClass__constructForSSR(JSC::JSGlobalObject*, JSC::CallFrame*, int*);
|
||||
extern JSC_CALLCONV void* JSC_HOST_CALL_ATTRIBUTES BakeResponseClass__constructForSSR(JSC::JSGlobalObject*, JSC::CallFrame*, int*);
|
||||
extern "C" SYSV_ABI JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES ResponseClass__constructError(JSC::JSGlobalObject*, JSC::CallFrame*);
|
||||
extern "C" SYSV_ABI JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES ResponseClass__constructJSON(JSC::JSGlobalObject*, JSC::CallFrame*);
|
||||
extern "C" SYSV_ABI JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES ResponseClass__constructRender(JSC::JSGlobalObject*, JSC::CallFrame*);
|
||||
extern "C" SYSV_ABI JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES BakeResponseClass__constructRender(JSC::JSGlobalObject*, JSC::CallFrame*);
|
||||
extern "C" SYSV_ABI JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES ResponseClass__constructRedirect(JSC::JSGlobalObject*, JSC::CallFrame*);
|
||||
extern JSC_CALLCONV size_t Response__estimatedSize(void* ptr);
|
||||
|
||||
@@ -76,7 +76,7 @@ extern "C" bool JSC__JSValue__isJSXElement(JSC::EncodedJSValue JSValue0, JSC::JS
|
||||
return isJSXElement(JSValue0, globalObject);
|
||||
}
|
||||
|
||||
extern JSC_CALLCONV JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES Response__createForSSR(Zig::GlobalObject* globalObject, void* ptr, uint8_t kind)
|
||||
extern JSC_CALLCONV JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES BakeResponse__createForSSR(Zig::GlobalObject* globalObject, void* ptr, uint8_t kind)
|
||||
{
|
||||
Structure* structure = globalObject->bakeAdditions().JSBakeResponseStructure(globalObject);
|
||||
|
||||
@@ -102,7 +102,7 @@ static const HashTableValue JSBakeResponseConstructorTableValues[] = {
|
||||
{ "json"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ResponseClass__constructJSON, 0 } },
|
||||
|
||||
{ "redirect"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ResponseClass__constructRedirect, 0 } },
|
||||
{ "render"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ResponseClass__constructRender, 0 } }
|
||||
{ "render"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, BakeResponseClass__constructRender, 0 } }
|
||||
|
||||
};
|
||||
|
||||
@@ -213,7 +213,7 @@ public:
|
||||
JSBakeResponse* instance = JSBakeResponse::create(vm, globalObject, structure, nullptr);
|
||||
|
||||
int arg_was_jsx = 0;
|
||||
void* ptr = ResponseClass__constructForSSR(globalObject, callFrame, &arg_was_jsx);
|
||||
void* ptr = BakeResponseClass__constructForSSR(globalObject, callFrame, &arg_was_jsx);
|
||||
if (scope.exception()) [[unlikely]] {
|
||||
ASSERT_WITH_MESSAGE(!ptr, "Memory leak detected: new Response() allocated memory without checking for exceptions.");
|
||||
return JSValue::encode(JSC::jsUndefined());
|
||||
@@ -243,7 +243,7 @@ public:
|
||||
Structure* structure = globalObject->bakeAdditions().JSBakeResponseStructure(globalObject);
|
||||
JSBakeResponse* instance = JSBakeResponse::create(vm, globalObject, structure, nullptr);
|
||||
|
||||
void* ptr = ResponseClass__constructForSSR(globalObject, callFrame, nullptr);
|
||||
void* ptr = BakeResponseClass__constructForSSR(globalObject, callFrame, nullptr);
|
||||
if (scope.exception()) [[unlikely]] {
|
||||
ASSERT_WITH_MESSAGE(!ptr, "Memory leak detected: new Response() allocated memory without checking for exceptions.");
|
||||
return JSValue::encode(JSC::jsUndefined());
|
||||
|
||||
@@ -19,6 +19,7 @@ pub const AutoFlusher = @import("./webcore/AutoFlusher.zig");
|
||||
pub const EncodingLabel = @import("./webcore/EncodingLabel.zig").EncodingLabel;
|
||||
pub const Fetch = @import("./webcore/fetch.zig");
|
||||
pub const Response = @import("./webcore/Response.zig");
|
||||
pub const BakeResponse = @import("./webcore/BakeResponse.zig");
|
||||
pub const TextDecoder = @import("./webcore/TextDecoder.zig");
|
||||
pub const TextEncoder = @import("./webcore/TextEncoder.zig");
|
||||
pub const TextEncoderStreamEncoder = @import("./webcore/TextEncoderStreamEncoder.zig");
|
||||
|
||||
143
src/bun.js/webcore/BakeResponse.zig
Normal file
143
src/bun.js/webcore/BakeResponse.zig
Normal file
@@ -0,0 +1,143 @@
|
||||
pub fn fixDeadCodeElimination() void {
|
||||
std.mem.doNotOptimizeAway(&BakeResponseClass__constructForSSR);
|
||||
std.mem.doNotOptimizeAway(&BakeResponseClass__constructRender);
|
||||
}
|
||||
|
||||
extern "C" fn BakeResponse__createForSSR(globalObject: *JSGlobalObject, this: *Response, kind: u8) callconv(jsc.conv) jsc.JSValue;
|
||||
|
||||
/// Corresponds to `JSBakeResponseKind` in
|
||||
/// `src/bun.js/bindings/JSBakeResponse.h`
|
||||
const SSRKind = enum(u8) {
|
||||
regular = 0,
|
||||
redirect = 1,
|
||||
render = 2,
|
||||
};
|
||||
|
||||
pub fn toJSForSSR(this: *Response, globalObject: *JSGlobalObject, kind: SSRKind) JSValue {
|
||||
this.calculateEstimatedByteSize();
|
||||
return BakeResponse__createForSSR(globalObject, this, @intFromEnum(kind));
|
||||
}
|
||||
|
||||
pub export fn BakeResponseClass__constructForSSR(globalObject: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame, bake_ssr_has_jsx: ?*c_int) callconv(jsc.conv) ?*anyopaque {
|
||||
return @as(*Response, constructor(globalObject, callFrame, bake_ssr_has_jsx) catch |err| switch (err) {
|
||||
error.JSError => return null,
|
||||
error.OutOfMemory => {
|
||||
globalObject.throwOutOfMemory() catch {};
|
||||
return null;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
pub fn constructor(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame, bake_ssr_has_jsx: ?*c_int) bun.JSError!*Response {
|
||||
var arguments = callframe.argumentsAsArray(2);
|
||||
|
||||
// Allow `return new Response(<jsx> ... </jsx>, { ... }`
|
||||
// inside of a react component
|
||||
if (!arguments[0].isUndefinedOrNull() and arguments[0].isObject()) {
|
||||
bake_ssr_has_jsx.?.* = 0;
|
||||
if (try arguments[0].isJSXElement(globalThis)) {
|
||||
const vm = globalThis.bunVM();
|
||||
if (vm.getDevServerAsyncLocalStorage()) |async_local_storage| {
|
||||
try assertStreamingDisabled(globalThis, async_local_storage, "new Response(<jsx />, { ... })");
|
||||
}
|
||||
bake_ssr_has_jsx.?.* = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return Response.constructor(globalThis, callframe);
|
||||
}
|
||||
|
||||
pub fn constructRedirect(
|
||||
globalThis: *jsc.JSGlobalObject,
|
||||
callframe: *jsc.CallFrame,
|
||||
) bun.JSError!JSValue {
|
||||
const response = try Response.constructRedirectImpl(globalThis, callframe);
|
||||
const ptr = bun.new(Response, response);
|
||||
|
||||
const vm = globalThis.bunVM();
|
||||
// Check if dev_server_async_local_storage is set (indicating we're in Bun dev server)
|
||||
if (vm.getDevServerAsyncLocalStorage()) |async_local_storage| {
|
||||
try assertStreamingDisabled(globalThis, async_local_storage, "Response.redirect");
|
||||
return toJSForSSR(ptr, globalThis, .redirect);
|
||||
}
|
||||
|
||||
return ptr.toJS(globalThis);
|
||||
}
|
||||
|
||||
pub export fn BakeResponseClass__constructRender(globalObject: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) callconv(jsc.conv) jsc.JSValue {
|
||||
return @call(.always_inline, jsc.toJSHostFn(constructRender), .{ globalObject, callFrame });
|
||||
}
|
||||
|
||||
/// This function is only available on JSBakeResponse
|
||||
pub fn constructRender(
|
||||
globalThis: *jsc.JSGlobalObject,
|
||||
callframe: *jsc.CallFrame,
|
||||
) bun.JSError!JSValue {
|
||||
const arguments = callframe.argumentsAsArray(2);
|
||||
const vm = globalThis.bunVM();
|
||||
|
||||
// Check if dev server async local_storage is set
|
||||
const async_local_storage = vm.getDevServerAsyncLocalStorage() orelse {
|
||||
return globalThis.throwInvalidArguments("Response.render() is only available in the Bun dev server", .{});
|
||||
};
|
||||
|
||||
try assertStreamingDisabled(globalThis, async_local_storage, "Response.render");
|
||||
|
||||
// Validate arguments
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("Response.render() requires at least a path argument", .{});
|
||||
}
|
||||
|
||||
const path_arg = arguments[0];
|
||||
if (!path_arg.isString()) {
|
||||
return globalThis.throwInvalidArguments("Response.render() path must be a string", .{});
|
||||
}
|
||||
|
||||
// Get the path string
|
||||
const path_str = try path_arg.toSlice(globalThis, bun.default_allocator);
|
||||
|
||||
// Duplicate the path string so it persists
|
||||
const path_copy = bun.default_allocator.dupe(u8, path_str.slice()) catch {
|
||||
path_str.deinit();
|
||||
return globalThis.throwOutOfMemory();
|
||||
};
|
||||
path_str.deinit();
|
||||
|
||||
// Create a Response with Render body
|
||||
const response = bun.new(Response, Response{
|
||||
.body = Body{
|
||||
.value = .{
|
||||
.Render = .{
|
||||
.path = bun.ptr.Owned([]const u8).fromRaw(path_copy),
|
||||
},
|
||||
},
|
||||
},
|
||||
.init = Response.Init{
|
||||
.status_code = 200,
|
||||
},
|
||||
});
|
||||
|
||||
const response_js = toJSForSSR(response, globalThis, .render);
|
||||
response_js.ensureStillAlive();
|
||||
|
||||
return response_js;
|
||||
}
|
||||
|
||||
fn assertStreamingDisabled(globalThis: *jsc.JSGlobalObject, async_local_storage: JSValue, display_function: []const u8) bun.JSError!void {
|
||||
if (async_local_storage.isEmptyOrUndefinedOrNull() or !async_local_storage.isObject()) return globalThis.throwInvalidArguments("store value must be an object", .{});
|
||||
const getStoreFn = (try async_local_storage.getPropertyValue(globalThis, "getStore")) orelse return globalThis.throwInvalidArguments("store value must have a \"getStore\" field", .{});
|
||||
const store_value = try getStoreFn.call(globalThis, async_local_storage, &.{});
|
||||
const streaming_val = (try store_value.getPropertyValue(globalThis, "streaming")) orelse return globalThis.throwInvalidArguments("store value must have a \"streaming\" field", .{});
|
||||
if (!streaming_val.isBoolean()) return globalThis.throwInvalidArguments("\"streaming\" fied must be a boolean", .{});
|
||||
if (streaming_val.asBoolean()) return globalThis.throwInvalidArguments("\"{s}\" is not available when `export const streaming = true`", .{display_function});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const std = @import("std");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const JSGlobalObject = jsc.JSGlobalObject;
|
||||
const JSValue = jsc.JSValue;
|
||||
|
||||
const Body = bun.webcore.Body;
|
||||
const Response = bun.webcore.Response;
|
||||
@@ -66,21 +66,6 @@ pub fn toJS(this: *Response, globalObject: *JSGlobalObject) JSValue {
|
||||
return js.toJSUnchecked(globalObject, this);
|
||||
}
|
||||
|
||||
/// Corresponds to `JSBakeResponseKind` in
|
||||
/// `src/bun.js/bindings/JSBakeResponse.h`
|
||||
const SSRKind = enum(u8) {
|
||||
regular = 0,
|
||||
redirect = 1,
|
||||
render = 2,
|
||||
};
|
||||
|
||||
extern "C" fn Response__createForSSR(globalObject: *JSGlobalObject, this: *Response, kind: u8) callconv(jsc.conv) jsc.JSValue;
|
||||
|
||||
pub fn toJSForSSR(this: *Response, globalObject: *JSGlobalObject, kind: SSRKind) JSValue {
|
||||
this.calculateEstimatedByteSize();
|
||||
return Response__createForSSR(globalObject, this, @intFromEnum(kind));
|
||||
}
|
||||
|
||||
pub fn getBodyValue(
|
||||
this: *Response,
|
||||
) *Body.Value {
|
||||
@@ -334,7 +319,6 @@ pub fn cloneValue(
|
||||
}
|
||||
|
||||
pub fn clone(this: *Response, globalThis: *JSGlobalObject) bun.JSError!*Response {
|
||||
// TODO: handle clone for jsxElement for bake?
|
||||
return bun.new(Response, try this.cloneValue(globalThis));
|
||||
}
|
||||
|
||||
@@ -473,6 +457,16 @@ pub fn constructRedirect(
|
||||
globalThis: *jsc.JSGlobalObject,
|
||||
callframe: *jsc.CallFrame,
|
||||
) bun.JSError!JSValue {
|
||||
const response = try constructRedirectImpl(globalThis, callframe);
|
||||
const ptr = bun.new(Response, response);
|
||||
const response_js = ptr.toJS(globalThis);
|
||||
return response_js;
|
||||
}
|
||||
|
||||
pub fn constructRedirectImpl(
|
||||
globalThis: *jsc.JSGlobalObject,
|
||||
callframe: *jsc.CallFrame,
|
||||
) bun.JSError!Response {
|
||||
var args_list = callframe.arguments_old(4);
|
||||
// https://github.com/remix-run/remix/blob/db2c31f64affb2095e4286b91306b96435967969/packages/remix-server-runtime/responses.ts#L4
|
||||
var args = jsc.CallFrame.ArgumentsSlice.init(globalThis.bunVM(), args_list.ptr[0..args_list.len]);
|
||||
@@ -519,7 +513,7 @@ pub fn constructRedirect(
|
||||
}
|
||||
|
||||
if (globalThis.hasException()) {
|
||||
return .zero;
|
||||
return error.JSError;
|
||||
}
|
||||
did_succeed = true;
|
||||
break :brk response;
|
||||
@@ -528,77 +522,7 @@ pub fn constructRedirect(
|
||||
response.init.headers = try response.getOrCreateHeaders(globalThis);
|
||||
var headers_ref = response.init.headers.?;
|
||||
try headers_ref.put(.Location, url_string_slice.slice(), globalThis);
|
||||
const ptr = bun.new(Response, response);
|
||||
|
||||
const vm = globalThis.bunVM();
|
||||
// Check if dev_server_async_local_storage is set (indicating we're in Bun dev server)
|
||||
if (vm.getDevServerAsyncLocalStorage()) |async_local_storage| {
|
||||
try assertStreamingDisabled(globalThis, async_local_storage, "Response.redirect");
|
||||
return ptr.toJSForSSR(globalThis, .redirect);
|
||||
}
|
||||
|
||||
const response_js = ptr.toJS(globalThis);
|
||||
|
||||
return response_js;
|
||||
}
|
||||
|
||||
pub export fn ResponseClass__constructRender(globalObject: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) callconv(jsc.conv) jsc.JSValue {
|
||||
return @call(.always_inline, jsc.toJSHostFn(constructRender), .{ globalObject, callFrame });
|
||||
}
|
||||
|
||||
/// This function is only available on JSBakeResponse
|
||||
pub fn constructRender(
|
||||
globalThis: *jsc.JSGlobalObject,
|
||||
callframe: *jsc.CallFrame,
|
||||
) bun.JSError!JSValue {
|
||||
const arguments = callframe.argumentsAsArray(2);
|
||||
const vm = globalThis.bunVM();
|
||||
|
||||
// Check if dev server async local_storage is set
|
||||
const async_local_storage = vm.getDevServerAsyncLocalStorage() orelse {
|
||||
return globalThis.throwInvalidArguments("Response.render() is only available in the Bun dev server", .{});
|
||||
};
|
||||
|
||||
try assertStreamingDisabled(globalThis, async_local_storage, "Response.render");
|
||||
|
||||
// Validate arguments
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("Response.render() requires at least a path argument", .{});
|
||||
}
|
||||
|
||||
const path_arg = arguments[0];
|
||||
if (!path_arg.isString()) {
|
||||
return globalThis.throwInvalidArguments("Response.render() path must be a string", .{});
|
||||
}
|
||||
|
||||
// Get the path string
|
||||
const path_str = try path_arg.toSlice(globalThis, bun.default_allocator);
|
||||
|
||||
// Duplicate the path string so it persists
|
||||
const path_copy = bun.default_allocator.dupe(u8, path_str.slice()) catch {
|
||||
path_str.deinit();
|
||||
return globalThis.throwOutOfMemory();
|
||||
};
|
||||
path_str.deinit();
|
||||
|
||||
// Create a Response with Render body
|
||||
var response = bun.new(Response, Response{
|
||||
.body = Body{
|
||||
.value = .{
|
||||
.Render = .{
|
||||
.path = bun.ptr.Owned([]const u8).fromRaw(path_copy),
|
||||
},
|
||||
},
|
||||
},
|
||||
.init = Response.Init{
|
||||
.status_code = 200,
|
||||
},
|
||||
});
|
||||
|
||||
const response_js = response.toJSForSSR(globalThis, .render);
|
||||
response_js.ensureStillAlive();
|
||||
|
||||
return response_js;
|
||||
return response;
|
||||
}
|
||||
|
||||
pub fn constructError(
|
||||
@@ -621,28 +545,6 @@ pub fn constructError(
|
||||
}
|
||||
|
||||
pub fn constructor(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!*Response {
|
||||
return constructorImpl(globalThis, callframe, null);
|
||||
}
|
||||
|
||||
pub fn ResponseClass__constructForSSR(globalObject: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame, bake_ssr_has_jsx: ?*c_int) callconv(jsc.conv) ?*anyopaque {
|
||||
return @as(*Response, Response.constructorForSSR(globalObject, callFrame, bake_ssr_has_jsx) catch |err| switch (err) {
|
||||
error.JSError => return null,
|
||||
error.OutOfMemory => {
|
||||
globalObject.throwOutOfMemory() catch {};
|
||||
return null;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
comptime {
|
||||
@export(&ResponseClass__constructForSSR, .{ .name = "ResponseClass__constructForSSR" });
|
||||
}
|
||||
|
||||
pub fn constructorForSSR(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame, bake_ssr_has_jsx: ?*c_int) bun.JSError!*Response {
|
||||
return constructorImpl(globalThis, callframe, bake_ssr_has_jsx);
|
||||
}
|
||||
|
||||
pub fn constructorImpl(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame, bake_ssr_has_jsx: ?*c_int) bun.JSError!*Response {
|
||||
var arguments = callframe.argumentsAsArray(2);
|
||||
|
||||
if (!arguments[0].isUndefinedOrNull() and arguments[0].isObject()) {
|
||||
@@ -677,19 +579,6 @@ pub fn constructorImpl(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFram
|
||||
return bun.new(Response, response);
|
||||
}
|
||||
}
|
||||
|
||||
// Special case for bake: allow `return new Response(<jsx> ... </jsx>, { ... }`
|
||||
// inside of a react component
|
||||
if (bake_ssr_has_jsx != null) {
|
||||
bake_ssr_has_jsx.?.* = 0;
|
||||
if (try arguments[0].isJSXElement(globalThis)) {
|
||||
const vm = globalThis.bunVM();
|
||||
if (vm.getDevServerAsyncLocalStorage()) |async_local_storage| {
|
||||
try assertStreamingDisabled(globalThis, async_local_storage, "new Response(<jsx />, { ... })");
|
||||
}
|
||||
bake_ssr_has_jsx.?.* = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
var init: Init = (brk: {
|
||||
if (arguments[1].isUndefinedOrNull()) {
|
||||
@@ -872,15 +761,6 @@ inline fn emptyWithStatus(_: *jsc.JSGlobalObject, status: u16) Response {
|
||||
});
|
||||
}
|
||||
|
||||
fn assertStreamingDisabled(globalThis: *jsc.JSGlobalObject, async_local_storage: JSValue, display_function: []const u8) bun.JSError!void {
|
||||
if (async_local_storage.isEmptyOrUndefinedOrNull() or !async_local_storage.isObject()) return globalThis.throwInvalidArguments("store value must be an object", .{});
|
||||
const getStoreFn = (try async_local_storage.getPropertyValue(globalThis, "getStore")) orelse return globalThis.throwInvalidArguments("store value must have a \"getStore\" field", .{});
|
||||
const store_value = try getStoreFn.call(globalThis, async_local_storage, &.{});
|
||||
const streaming_val = (try store_value.getPropertyValue(globalThis, "streaming")) orelse return globalThis.throwInvalidArguments("store value must have a \"streaming\" field", .{});
|
||||
if (!streaming_val.isBoolean()) return globalThis.throwInvalidArguments("\"streaming\" fied must be a boolean", .{});
|
||||
if (streaming_val.asBoolean()) return globalThis.throwInvalidArguments("\"{s}\" is not available when `export const streaming = true`", .{display_function});
|
||||
}
|
||||
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/API/Headers
|
||||
// TODO: move to http.zig. this has nothing to do with jsc or WebCore
|
||||
|
||||
|
||||
Reference in New Issue
Block a user