aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-03-13 06:08:10 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-03-13 06:08:10 -0700
commitf4504292cfd1e11bc6b4879a8b184aa6b842c8cb (patch)
tree2e0bb0f4596e739b37f76512dbc641f6102d8bcd
parent6d71749c703d0061885230438ce5e46838700c59 (diff)
downloadbun-f4504292cfd1e11bc6b4879a8b184aa6b842c8cb.tar.gz
bun-f4504292cfd1e11bc6b4879a8b184aa6b842c8cb.tar.zst
bun-f4504292cfd1e11bc6b4879a8b184aa6b842c8cb.zip
[bun.js] Implement `Blob`
-rw-r--r--integration/bunjs-only-snippets/fetch.test.js118
-rw-r--r--src/http_client_async.zig1
-rw-r--r--src/javascript/jsc/api/router.zig97
-rw-r--r--src/javascript/jsc/base.zig55
-rw-r--r--src/javascript/jsc/bindings/bindings.cpp22
-rw-r--r--src/javascript/jsc/bindings/bindings.zig42
-rw-r--r--src/javascript/jsc/bindings/headers-cpp.h2
-rw-r--r--src/javascript/jsc/bindings/headers.h4
-rw-r--r--src/javascript/jsc/bindings/headers.zig2
-rw-r--r--src/javascript/jsc/bindings/helpers.h2
-rw-r--r--src/javascript/jsc/javascript.zig16
-rw-r--r--src/javascript/jsc/node/types.zig1
-rw-r--r--src/javascript/jsc/test/jest.zig16
-rw-r--r--src/javascript/jsc/webcore/response.zig1558
-rw-r--r--src/js_ast.zig188
-rw-r--r--src/resolver/resolver.zig2
-rw-r--r--src/string_joiner.zig37
17 files changed, 1537 insertions, 626 deletions
diff --git a/integration/bunjs-only-snippets/fetch.test.js b/integration/bunjs-only-snippets/fetch.test.js
index 76e09bcf8..cfe24d5ac 100644
--- a/integration/bunjs-only-snippets/fetch.test.js
+++ b/integration/bunjs-only-snippets/fetch.test.js
@@ -21,6 +21,82 @@ describe("fetch", () => {
}
});
+function testBlobInterface(blobbyConstructor, hasBlobFn) {
+ it("json", async () => {
+ var response = blobbyConstructor(JSON.stringify({ hello: true }));
+ expect(JSON.stringify(await response.json())).toBe(
+ JSON.stringify({ hello: true })
+ );
+ });
+ it("text", async () => {
+ var response = blobbyConstructor(JSON.stringify({ hello: true }));
+ expect(await response.text()).toBe(JSON.stringify({ hello: true }));
+ });
+ it("arrayBuffer", async () => {
+ var response = blobbyConstructor(JSON.stringify({ hello: true }));
+
+ const bytes = new TextEncoder().encode(JSON.stringify({ hello: true }));
+ const compare = new Uint8Array(await response.arrayBuffer());
+ for (let i = 0; i < compare.length; i++) {
+ expect(compare[i]).toBe(bytes[i]);
+ }
+ });
+ hasBlobFn &&
+ it("blob", async () => {
+ var response = blobbyConstructor(JSON.stringify({ hello: true }));
+ const size = JSON.stringify({ hello: true }).length;
+ const blobed = await response.blob();
+ expect(blobed instanceof Blob).toBe(true);
+ expect(blobed.size).toBe(size);
+ expect(blobed.type).toBe("");
+ blobed.type = "application/json";
+ expect(blobed.type).toBe("application/json");
+ });
+}
+
+describe("Blob", () => {
+ testBlobInterface((data) => new Blob([data]));
+
+ var blobConstructorValues = [
+ ["123", "456"],
+ ["123", 456],
+ ["123", "456", "789"],
+ ["123", 456, 789],
+ [1, 2, 3, 4, 5, 6, 7, 8, 9],
+ [Uint8Array.from([1, 2, 3, 4, 5, 6, 7, 9])],
+ [Uint8Array.from([1, 2, 3, 4]), "5678", 9],
+ [new Blob([Uint8Array.from([1, 2, 3, 4])]), "5678", 9],
+ ];
+ var expected = [
+ "123456",
+ "123456",
+ "123456789",
+ "123456789",
+ "123456789",
+ "\x01\x02\x03\x04\x05\x06\x07\t",
+ "\x01\x02\x03\x0456789",
+ "\x01\x02\x03\x0456789",
+ ];
+
+ it(`blobConstructorValues`, async () => {
+ for (let i = 0; i < blobConstructorValues.length; i++) {
+ var response = new Blob(blobConstructorValues[i]);
+ const res = await response.text();
+ if (res !== expected[i]) {
+ throw new Error(
+ `Failed: ${expected[i]
+ .split("")
+ .map((a) => a.charCodeAt(0))}, received: ${res
+ .split("")
+ .map((a) => a.charCodeAt(0))}`
+ );
+ }
+
+ expect(res).toBe(expected[i]);
+ }
+ });
+});
+
describe("Response", () => {
it("clone", async () => {
var body = new Response("<div>hello</div>", {
@@ -34,4 +110,46 @@ describe("Response", () => {
expect(body.headers.get("content-type")).toBe("text/plain");
expect(await clone.text()).toBe("<div>hello</div>");
});
+ testBlobInterface((data) => new Response(data), true);
+});
+
+describe("Request", () => {
+ it("clone", async () => {
+ var body = new Request("https://hello.com", {
+ headers: {
+ "content-type": "text/html; charset=utf-8",
+ },
+ body: "<div>hello</div>",
+ });
+ expect(body.headers.get("content-type")).toBe("text/html; charset=utf-8");
+
+ var clone = body.clone();
+ body.headers.set("content-type", "text/plain");
+ expect(clone.headers.get("content-type")).toBe("text/html; charset=utf-8");
+ expect(body.headers.get("content-type")).toBe("text/plain");
+ expect(await clone.text()).toBe("<div>hello</div>");
+ });
+
+ testBlobInterface(
+ (data) => new Request("https://hello.com", { body: data }),
+ true
+ );
+});
+
+describe("Headers", () => {
+ it("writes", async () => {
+ var body = new Request("https://hello.com", {
+ headers: {
+ "content-type": "text/html; charset=utf-8",
+ },
+ body: "<div>hello</div>",
+ });
+ expect(body.headers.get("content-type")).toBe("text/html; charset=utf-8");
+
+ var clone = body.clone();
+ body.headers.set("content-type", "text/plain");
+ expect(clone.headers.get("content-type")).toBe("text/html; charset=utf-8");
+ expect(body.headers.get("content-type")).toBe("text/plain");
+ expect(await clone.text()).toBe("<div>hello</div>");
+ });
});
diff --git a/src/http_client_async.zig b/src/http_client_async.zig
index 8512b60cc..a5ca73cf3 100644
--- a/src/http_client_async.zig
+++ b/src/http_client_async.zig
@@ -30,6 +30,7 @@ const AsyncSocket = @import("./http/async_socket.zig");
const ZlibPool = @import("./http/zlib.zig");
const URLBufferPool = ObjectPool([4096]u8, null, false, 10);
pub const MimeType = @import("./http/mime_type.zig");
+pub const URLPath = @import("./http/url_path.zig");
// This becomes Arena.allocator
pub var default_allocator: std.mem.Allocator = undefined;
pub var default_arena: Arena = undefined;
diff --git a/src/javascript/jsc/api/router.zig b/src/javascript/jsc/api/router.zig
index 500ce1853..ed2e0a9af 100644
--- a/src/javascript/jsc/api/router.zig
+++ b/src/javascript/jsc/api/router.zig
@@ -28,10 +28,15 @@ const To = Base.To;
const Request = WebCore.Request;
const d = Base.d;
const FetchEvent = WebCore.FetchEvent;
-
+const URLPath = @import("../../../http/url_path.zig");
+const URL = @import("../../../query_string_map.zig").URL;
route: *const FilesystemRouter.Match,
+route_holder: FilesystemRouter.Match = undefined,
+needs_deinit: bool = false,
query_string_map: ?QueryStringMap = null,
param_map: ?QueryStringMap = null,
+params_list_holder: FilesystemRouter.Param.List = .{},
+
script_src: ?string = null,
script_src_buf: [1024]u8 = undefined,
@@ -65,42 +70,101 @@ pub fn match(
return null;
}
- if (js.JSValueIsObjectOfClass(ctx, arguments[0], FetchEvent.Class.get().*)) {
+ if (FetchEvent.Class.loaded and js.JSValueIsObjectOfClass(ctx, arguments[0], FetchEvent.Class.get().*)) {
return matchFetchEvent(ctx, To.Zig.ptr(FetchEvent, arguments[0]), exception);
}
- // if (js.JSValueIsString(ctx, arguments[0])) {
- // return matchPathName(ctx, arguments[0], exception);
- // }
+ var router = JavaScript.VirtualMachine.vm.bundler.router orelse {
+ JSError(getAllocator(ctx), "Bun.match needs a framework configured with routes", .{}, ctx, exception);
+ return null;
+ };
+ var arg = JSC.JSValue.fromRef(arguments[0]);
+ var path_: ?ZigString.Slice = null;
+ var pathname: string = "";
+ defer {
+ if (path_) |path| {
+ path.deinit();
+ }
+ }
+
+ if (arg.isString()) {
+ var path_string = arg.getZigString(ctx.ptr());
+ path_ = path_string.toSlice(bun.default_allocator);
+ var url = URL.parse(path_.?.slice());
+ pathname = url.pathname;
+ } else if (arg.as(Request)) |req| {
+ var path_string = req.url;
+ path_ = path_string.toSlice(bun.default_allocator);
+ var url = URL.parse(path_.?.slice());
+ pathname = url.pathname;
+ }
+
+ if (path_ == null) {
+ JSError(getAllocator(ctx), "Expected string, FetchEvent, or Request", .{}, ctx, exception);
+ return null;
+ }
+
+ const url_path = URLPath.parse(path_.?.slice()) catch {
+ JSError(getAllocator(ctx), "Could not parse URL path", .{}, ctx, exception);
+ return null;
+ };
- if (js.JSValueIsObjectOfClass(ctx, arguments[0], Request.Class.get().*)) {
- return matchRequest(ctx, To.Zig.ptr(Request, arguments[0]), exception);
+ var match_params_fallback = std.heap.stackFallback(1024, bun.default_allocator);
+ var match_params_allocator = match_params_fallback.get();
+ var match_params = FilesystemRouter.Param.List{};
+ match_params.ensureTotalCapacity(match_params_allocator, 16) catch unreachable;
+ var prev_allocator = router.routes.allocator;
+ router.routes.allocator = match_params_allocator;
+ defer router.routes.allocator = prev_allocator;
+ if (router.routes.matchPage("", url_path, &match_params)) |matched| {
+ var match_ = matched;
+ var params_list = match_.params.clone(bun.default_allocator) catch unreachable;
+ var instance = getAllocator(ctx).create(Router) catch unreachable;
+
+ instance.* = Router{
+ .route_holder = match_,
+ .route = undefined,
+ };
+ instance.params_list_holder = params_list;
+ instance.route = &instance.route_holder;
+ instance.route_holder.params = &instance.params_list_holder;
+ instance.script_src_buf_writer = ScriptSrcStream{ .pos = 0, .buffer = std.mem.span(&instance.script_src_buf) };
+
+ return Instance.make(ctx, instance);
}
+ // router.routes.matchPage
- return null;
+ return JSC.JSValue.jsNull().asObjectRef();
}
fn matchRequest(
ctx: js.JSContextRef,
request: *const Request,
- exception: js.ExceptionRef,
+ _: js.ExceptionRef,
) js.JSObjectRef {
- return createRouteObject(ctx, request.request_context, exception);
+ return createRouteObject(ctx, request.request_context);
}
fn matchFetchEvent(
ctx: js.JSContextRef,
fetch_event: *const FetchEvent,
- exception: js.ExceptionRef,
+ _: js.ExceptionRef,
) js.JSObjectRef {
- return createRouteObject(ctx, fetch_event.request_context, exception);
+ return createRouteObject(ctx, fetch_event.request_context);
}
-fn createRouteObject(ctx: js.JSContextRef, req: *const http.RequestContext, _: js.ExceptionRef) js.JSValueRef {
+fn createRouteObject(ctx: js.JSContextRef, req: *const http.RequestContext) js.JSValueRef {
const route = &(req.matched_route orelse {
return js.JSValueMakeNull(ctx);
});
+ return createRouteObjectFromMatch(ctx, route);
+}
+
+fn createRouteObjectFromMatch(
+ ctx: js.JSContextRef,
+ route: *const FilesystemRouter.Match,
+) js.JSValueRef {
var router = getAllocator(ctx).create(Router) catch unreachable;
router.* = Router{
.route = route,
@@ -276,6 +340,13 @@ pub fn finalize(
if (this.query_string_map) |*map| {
map.deinit();
}
+
+ if (this.needs_deinit) {
+ this.params_list_holder.deinit(bun.default_allocator);
+ this.params_list_holder = .{};
+ this.needs_deinit = false;
+ bun.default_allocator.destroy(this);
+ }
}
pub fn getPathname(
diff --git a/src/javascript/jsc/base.zig b/src/javascript/jsc/base.zig
index cfdc6c110..5d1dbcc31 100644
--- a/src/javascript/jsc/base.zig
+++ b/src/javascript/jsc/base.zig
@@ -1720,6 +1720,17 @@ pub const ArrayBuffer = extern struct {
}
pub fn toJS(this: ArrayBuffer, ctx: JSC.C.JSContextRef, exception: JSC.C.ExceptionRef) JSC.JSValue {
+ if (this.typed_array_type == .ArrayBuffer) {
+ return JSC.JSValue.fromRef(JSC.C.JSObjectMakeArrayBufferWithBytesNoCopy(
+ ctx,
+ this.ptr,
+ this.byte_len,
+ MarkedArrayBuffer_deallocator,
+ @intToPtr(*anyopaque, @ptrToInt(&bun.default_allocator)),
+ exception,
+ ));
+ }
+
return JSC.JSValue.fromRef(JSC.C.JSObjectMakeTypedArrayWithBytesNoCopy(
ctx,
this.typed_array_type.toC(),
@@ -1865,39 +1876,41 @@ const DocType = JSC.Cloudflare.DocType;
const EndTag = JSC.Cloudflare.EndTag;
const DocEnd = JSC.Cloudflare.DocEnd;
const AttributeIterator = JSC.Cloudflare.AttributeIterator;
+const Blob = JSC.WebCore.Blob;
pub const JSPrivateDataPtr = TaggedPointerUnion(.{
- ResolveError,
+ AttributeIterator,
+ BigIntStats,
+ Blob,
+ Body,
BuildError,
- Response,
- Request,
+ Comment,
+ DescribeScope,
+ DirEnt,
+ DocEnd,
+ DocType,
+ Element,
+ EndTag,
+ Expect,
+ ExpectPrototype,
FetchEvent,
+ FetchTaskletContext,
Headers,
- Body,
- Router,
+ HTMLRewriter,
JSNode,
LazyPropertiesObject,
ModuleNamespace,
- FetchTaskletContext,
- DescribeScope,
- Expect,
- ExpectPrototype,
NodeFS,
+ Request,
+ ResolveError,
+ Response,
+ Router,
Stats,
- BigIntStats,
- DirEnt,
- Transpiler,
- TextEncoder,
+ TextChunk,
TextDecoder,
+ TextEncoder,
TimeoutTask,
- HTMLRewriter,
- Element,
- Comment,
- TextChunk,
- DocType,
- EndTag,
- DocEnd,
- AttributeIterator,
+ Transpiler,
});
pub inline fn GetJSPrivateData(comptime Type: type, ref: js.JSObjectRef) ?*Type {
diff --git a/src/javascript/jsc/bindings/bindings.cpp b/src/javascript/jsc/bindings/bindings.cpp
index 40baf6c01..bfdb24600 100644
--- a/src/javascript/jsc/bindings/bindings.cpp
+++ b/src/javascript/jsc/bindings/bindings.cpp
@@ -1,4 +1,5 @@
#include "BunClientData.h"
+#include "GCDefferalContext.h"
#include "ZigGlobalObject.h"
#include "helpers.h"
#include "root.h"
@@ -42,7 +43,6 @@
#include <wtf/text/StringImpl.h>
#include <wtf/text/StringView.h>
#include <wtf/text/WTFString.h>
-
extern "C" {
JSC__JSValue SystemError__toErrorInstance(const SystemError* arg0,
@@ -237,12 +237,20 @@ void JSC__JSValue__jsonStringify(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg
unsigned char JSC__JSValue__jsType(JSC__JSValue JSValue0)
{
JSC::JSValue jsValue = JSC::JSValue::decode(JSValue0);
- if (JSC::JSCell* cell = jsValue.asCell())
- return cell->type();
+ // if the value is NOT a cell
+ // asCell will return an invalid pointer rather than a nullptr
+ if (jsValue.isCell())
+ return jsValue.asCell()->type();
return 0;
}
+JSC__JSValue JSC__JSValue__parseJSON(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1)
+{
+ JSC::JSValue jsValue = JSC::JSValue::decode(JSValue0);
+ return JSC::JSValue::encode(JSC::JSONParse(arg1, jsValue.toWTFString(arg1)));
+}
+
void JSC__JSGlobalObject__deleteModuleRegistryEntry(JSC__JSGlobalObject* global, ZigString* arg1)
{
JSC::JSMap* map = JSC::jsDynamicCast<JSC::JSMap*>(
@@ -2148,6 +2156,14 @@ void JSC__VM__holdAPILock(JSC__VM* arg0, void* ctx, void (*callback)(void* arg0)
callback(ctx);
}
+void JSC__VM__deferGC(JSC__VM* vm, void* ctx, void (*callback)(void* arg0))
+{
+ JSC::GCDeferralContext deferralContext(reinterpret_cast<JSC__VM&>(vm));
+ JSC::DisallowGC disallowGC;
+
+ callback(ctx);
+}
+
void JSC__VM__deleteAllCode(JSC__VM* arg1, JSC__JSGlobalObject* globalObject)
{
JSC::JSLockHolder locker(globalObject->vm());
diff --git a/src/javascript/jsc/bindings/bindings.zig b/src/javascript/jsc/bindings/bindings.zig
index 6215d9e4a..f33d82ad6 100644
--- a/src/javascript/jsc/bindings/bindings.zig
+++ b/src/javascript/jsc/bindings/bindings.zig
@@ -88,6 +88,18 @@ pub const ZigString = extern struct {
ptr: [*]const u8,
len: usize,
+ pub fn clone(this: ZigString, allocator: std.mem.Allocator) !ZigString {
+ var sliced = this.toSlice(allocator);
+ if (!sliced.allocated) {
+ var str = ZigString.init(try allocator.dupe(u8, sliced.slice()));
+ str.mark();
+ str.markUTF8();
+ return str;
+ }
+
+ return this;
+ }
+
pub const shim = Shimmer("", "ZigString", @This());
pub const Slice = struct {
@@ -106,7 +118,7 @@ pub const ZigString = extern struct {
return @intToPtr([*]u8, @ptrToInt(this.ptr))[0..this.len];
}
- pub fn deinit(this: *Slice) void {
+ pub fn deinit(this: *const Slice) void {
if (!this.allocated) {
return;
}
@@ -261,7 +273,7 @@ pub const ZigString = extern struct {
}
pub fn trimmedSlice(this: *const ZigString) []const u8 {
- return std.mem.trim(u8, this.ptr[0..@minimum(this.len, std.math.maxInt(u32))], " \r\n");
+ return strings.trim(this.ptr[0..@minimum(this.len, std.math.maxInt(u32))], " \r\n");
}
pub fn toValueAuto(this: *const ZigString, global: *JSGlobalObject) JSValue {
@@ -1603,6 +1615,7 @@ pub const JSValue = enum(u64) {
GetterSetter,
CustomGetterSetter,
+ /// For 32-bit architectures, this wraps a 64-bit JSValue
APIValueWrapper,
NativeExecutable,
@@ -1824,6 +1837,16 @@ pub const JSValue = enum(u64) {
return cppFn("jsType", .{this});
}
+ pub fn jsTypeLoose(
+ this: JSValue,
+ ) JSType {
+ if (this.isNumber()) {
+ return JSType.NumberObject;
+ }
+
+ return this.jsType();
+ }
+
pub fn createEmptyObject(global: *JSGlobalObject, len: usize) JSValue {
return cppFn("createEmptyObject", .{ global, len });
}
@@ -2230,6 +2253,13 @@ pub const JSValue = enum(u64) {
});
}
+ pub fn parseJSON(this: JSValue, globalObject: *JSGlobalObject) JSValue {
+ return cppFn("parseJSON", .{
+ this,
+ globalObject,
+ });
+ }
+
pub inline fn asRef(this: JSValue) C_API.JSValueRef {
return @intToPtr(C_API.JSValueRef, @intCast(usize, @enumToInt(this)));
}
@@ -2246,7 +2276,7 @@ pub const JSValue = enum(u64) {
return @intToPtr(*anyopaque, @enumToInt(this));
}
- pub const Extern = [_][]const u8{ "symbolKeyFor", "symbolFor", "getSymbolDescription", "createInternalPromise", "asInternalPromise", "asArrayBuffer_", "getReadableStreamState", "getWritableStreamState", "fromEntries", "createTypeError", "createRangeError", "createObject2", "getIfPropertyExistsImpl", "jsType", "jsonStringify", "kind_", "isTerminationException", "isSameValue", "getLengthOfArray", "toZigString", "createStringArray", "createEmptyObject", "putRecord", "asPromise", "isClass", "getNameProperty", "getClassName", "getErrorsProperty", "toInt32", "toBoolean", "isInt32", "isIterable", "forEach", "isAggregateError", "toZigException", "isException", "toWTFString", "hasProperty", "getPropertyNames", "getDirect", "putDirect", "getIfExists", "asString", "asObject", "asNumber", "isError", "jsNull", "jsUndefined", "jsTDZValue", "jsBoolean", "jsDoubleNumber", "jsNumberFromDouble", "jsNumberFromChar", "jsNumberFromU16", "jsNumberFromInt32", "jsNumberFromInt64", "jsNumberFromUint64", "isUndefined", "isNull", "isUndefinedOrNull", "isBoolean", "isAnyInt", "isUInt32AsAnyInt", "isInt32AsAnyInt", "isNumber", "isString", "isBigInt", "isHeapBigInt", "isBigInt32", "isSymbol", "isPrimitive", "isGetterSetter", "isCustomGetterSetter", "isObject", "isCell", "asCell", "toString", "toStringOrNull", "toPropertyKey", "toPropertyKeyValue", "toObject", "toString", "getPrototype", "getPropertyByPropertyName", "eqlValue", "eqlCell", "isCallable" };
+ pub const Extern = [_][]const u8{ "parseJSON", "symbolKeyFor", "symbolFor", "getSymbolDescription", "createInternalPromise", "asInternalPromise", "asArrayBuffer_", "getReadableStreamState", "getWritableStreamState", "fromEntries", "createTypeError", "createRangeError", "createObject2", "getIfPropertyExistsImpl", "jsType", "jsonStringify", "kind_", "isTerminationException", "isSameValue", "getLengthOfArray", "toZigString", "createStringArray", "createEmptyObject", "putRecord", "asPromise", "isClass", "getNameProperty", "getClassName", "getErrorsProperty", "toInt32", "toBoolean", "isInt32", "isIterable", "forEach", "isAggregateError", "toZigException", "isException", "toWTFString", "hasProperty", "getPropertyNames", "getDirect", "putDirect", "getIfExists", "asString", "asObject", "asNumber", "isError", "jsNull", "jsUndefined", "jsTDZValue", "jsBoolean", "jsDoubleNumber", "jsNumberFromDouble", "jsNumberFromChar", "jsNumberFromU16", "jsNumberFromInt32", "jsNumberFromInt64", "jsNumberFromUint64", "isUndefined", "isNull", "isUndefinedOrNull", "isBoolean", "isAnyInt", "isUInt32AsAnyInt", "isInt32AsAnyInt", "isNumber", "isString", "isBigInt", "isHeapBigInt", "isBigInt32", "isSymbol", "isPrimitive", "isGetterSetter", "isCustomGetterSetter", "isObject", "isCell", "asCell", "toString", "toStringOrNull", "toPropertyKey", "toPropertyKeyValue", "toObject", "toString", "getPrototype", "getPropertyByPropertyName", "eqlValue", "eqlCell", "isCallable" };
};
extern "c" fn Microtask__run(*Microtask, *JSGlobalObject) void;
@@ -2386,6 +2416,10 @@ pub const VM = extern struct {
cppFn("holdAPILock", .{ this, ctx, callback });
}
+ pub fn deferGC(this: *VM, ctx: ?*anyopaque, callback: fn (ctx: ?*anyopaque) callconv(.C) void) void {
+ cppFn("deferGC", .{ this, ctx, callback });
+ }
+
pub fn deleteAllCode(
vm: *VM,
global_object: *JSGlobalObject,
@@ -2459,7 +2493,7 @@ pub const VM = extern struct {
});
}
- pub const Extern = [_][]const u8{ "holdAPILock", "runGC", "generateHeapSnapshot", "isJITEnabled", "deleteAllCode", "create", "deinit", "setExecutionForbidden", "executionForbidden", "isEntered", "throwError", "drainMicrotasks", "whenIdle", "shrinkFootprint", "setExecutionTimeLimit", "clearExecutionTimeLimit" };
+ pub const Extern = [_][]const u8{ "deferGC", "holdAPILock", "runGC", "generateHeapSnapshot", "isJITEnabled", "deleteAllCode", "create", "deinit", "setExecutionForbidden", "executionForbidden", "isEntered", "throwError", "drainMicrotasks", "whenIdle", "shrinkFootprint", "setExecutionTimeLimit", "clearExecutionTimeLimit" };
};
pub const ThrowScope = extern struct {
diff --git a/src/javascript/jsc/bindings/headers-cpp.h b/src/javascript/jsc/bindings/headers-cpp.h
index e3c04d000..c620b6e98 100644
--- a/src/javascript/jsc/bindings/headers-cpp.h
+++ b/src/javascript/jsc/bindings/headers-cpp.h
@@ -1,4 +1,4 @@
-//-- AUTOGENERATED FILE -- 1647061500
+//-- AUTOGENERATED FILE -- 1647165586
// clang-format off
#pragma once
diff --git a/src/javascript/jsc/bindings/headers.h b/src/javascript/jsc/bindings/headers.h
index e87cb4377..f63a87319 100644
--- a/src/javascript/jsc/bindings/headers.h
+++ b/src/javascript/jsc/bindings/headers.h
@@ -1,5 +1,5 @@
// clang-format: off
-//-- AUTOGENERATED FILE -- 1647061500
+//-- AUTOGENERATED FILE -- 1647165586
#pragma once
#include <stddef.h>
@@ -490,6 +490,7 @@ CPP_DECL void JSC__JSValue__jsonStringify(JSC__JSValue JSValue0, JSC__JSGlobalOb
CPP_DECL JSC__JSValue JSC__JSValue__jsTDZValue();
CPP_DECL unsigned char JSC__JSValue__jsType(JSC__JSValue JSValue0);
CPP_DECL JSC__JSValue JSC__JSValue__jsUndefined();
+CPP_DECL JSC__JSValue JSC__JSValue__parseJSON(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1);
CPP_DECL void JSC__JSValue__putRecord(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, ZigString* arg2, ZigString* arg3, size_t arg4);
CPP_DECL JSC__JSValue JSC__JSValue__symbolFor(JSC__JSGlobalObject* arg0, ZigString* arg1);
CPP_DECL bool JSC__JSValue__symbolKeyFor(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, ZigString* arg2);
@@ -522,6 +523,7 @@ CPP_DECL JSC__JSValue JSC__Exception__value(JSC__Exception* arg0);
CPP_DECL void JSC__VM__clearExecutionTimeLimit(JSC__VM* arg0);
CPP_DECL JSC__VM* JSC__VM__create(unsigned char HeapType0);
+CPP_DECL void JSC__VM__deferGC(JSC__VM* arg0, void* arg1, void (* ArgFn2)(void* arg0));
CPP_DECL void JSC__VM__deinit(JSC__VM* arg0, JSC__JSGlobalObject* arg1);
CPP_DECL void JSC__VM__deleteAllCode(JSC__VM* arg0, JSC__JSGlobalObject* arg1);
CPP_DECL void JSC__VM__drainMicrotasks(JSC__VM* arg0);
diff --git a/src/javascript/jsc/bindings/headers.zig b/src/javascript/jsc/bindings/headers.zig
index 90a3a46ea..0932fb5f5 100644
--- a/src/javascript/jsc/bindings/headers.zig
+++ b/src/javascript/jsc/bindings/headers.zig
@@ -338,6 +338,7 @@ pub extern fn JSC__JSValue__jsonStringify(JSValue0: JSC__JSValue, arg1: [*c]JSC_
pub extern fn JSC__JSValue__jsTDZValue(...) JSC__JSValue;
pub extern fn JSC__JSValue__jsType(JSValue0: JSC__JSValue) u8;
pub extern fn JSC__JSValue__jsUndefined(...) JSC__JSValue;
+pub extern fn JSC__JSValue__parseJSON(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject) JSC__JSValue;
pub extern fn JSC__JSValue__putRecord(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject, arg2: [*c]ZigString, arg3: [*c]ZigString, arg4: usize) void;
pub extern fn JSC__JSValue__symbolFor(arg0: [*c]JSC__JSGlobalObject, arg1: [*c]ZigString) JSC__JSValue;
pub extern fn JSC__JSValue__symbolKeyFor(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject, arg2: [*c]ZigString) bool;
@@ -360,6 +361,7 @@ pub extern fn JSC__Exception__getStackTrace(arg0: [*c]JSC__Exception, arg1: [*c]
pub extern fn JSC__Exception__value(arg0: [*c]JSC__Exception) JSC__JSValue;
pub extern fn JSC__VM__clearExecutionTimeLimit(arg0: [*c]JSC__VM) void;
pub extern fn JSC__VM__create(HeapType0: u8) [*c]JSC__VM;
+pub extern fn JSC__VM__deferGC(arg0: [*c]JSC__VM, arg1: ?*anyopaque, ArgFn2: ?fn (?*anyopaque) callconv(.C) void) void;
pub extern fn JSC__VM__deinit(arg0: [*c]JSC__VM, arg1: [*c]JSC__JSGlobalObject) void;
pub extern fn JSC__VM__deleteAllCode(arg0: [*c]JSC__VM, arg1: [*c]JSC__JSGlobalObject) void;
pub extern fn JSC__VM__drainMicrotasks(arg0: [*c]JSC__VM) void;
diff --git a/src/javascript/jsc/bindings/helpers.h b/src/javascript/jsc/bindings/helpers.h
index 9e6da5d66..11d728ac5 100644
--- a/src/javascript/jsc/bindings/helpers.h
+++ b/src/javascript/jsc/bindings/helpers.h
@@ -111,7 +111,7 @@ static bool isTaggedExternalPtr(const unsigned char* ptr)
static const WTF::String toString(ZigString str)
{
if (str.len == 0 || str.ptr == nullptr) {
- return WTF::Stzring();
+ return WTF::String();
}
if (UNLIKELY(isTaggedUTF8Ptr(str.ptr))) {
return WTF::String::fromUTF8(untag(str.ptr), str.len);
diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig
index a0198ed65..60ba111de 100644
--- a/src/javascript/jsc/javascript.zig
+++ b/src/javascript/jsc/javascript.zig
@@ -102,6 +102,7 @@ pub const GlobalClasses = [_]type{
WebCore.TextDecoder.Constructor.Class,
JSC.Cloudflare.HTMLRewriter.Class,
+ WebCore.Blob.Class,
// The last item in this array becomes "process.env"
Bun.EnvironmentVariables.Class,
@@ -1951,16 +1952,19 @@ pub const VirtualMachine = struct {
}
}
pub fn tick(this: *EventLoop) void {
- this.tickConcurrent();
+ while (true) {
+ this.tickConcurrent();
+ while (this.tickWithCount() > 0) {}
+ this.tickConcurrent();
- while (this.tickWithCount() > 0) {}
+ if (this.tickWithCount() == 0) break;
+ }
}
pub fn waitForTasks(this: *EventLoop) void {
- this.tickConcurrent();
-
+ this.tick();
while (this.pending_tasks_count.load(.Monotonic) > 0) {
- while (this.tickWithCount() > 0) {}
+ this.tick();
}
}
@@ -3429,7 +3433,7 @@ pub const EventListenerMixin = struct {
fetch_event.* = FetchEvent{
.request_context = request_context,
- .request = Request{ .request_context = request_context },
+ .request = try Request.fromRequestContext(request_context),
.onPromiseRejectionCtx = @as(*anyopaque, ctx),
.onPromiseRejectionHandler = FetchEventRejectionHandler.onRejection,
};
diff --git a/src/javascript/jsc/node/types.zig b/src/javascript/jsc/node/types.zig
index 0879c11be..e4230068a 100644
--- a/src/javascript/jsc/node/types.zig
+++ b/src/javascript/jsc/node/types.zig
@@ -391,6 +391,7 @@ pub const ArgumentsSlice = struct {
remaining: []const JSC.JSValue,
arena: std.heap.ArenaAllocator = std.heap.ArenaAllocator.init(bun.default_allocator),
all: []const JSC.JSValue,
+ threw: bool = false,
pub fn from(arguments: []const JSC.JSValueRef) ArgumentsSlice {
return init(@ptrCast([*]const JSC.JSValue, arguments.ptr)[0..arguments.len]);
diff --git a/src/javascript/jsc/test/jest.zig b/src/javascript/jsc/test/jest.zig
index e2821518e..11518a581 100644
--- a/src/javascript/jsc/test/jest.zig
+++ b/src/javascript/jsc/test/jest.zig
@@ -685,17 +685,20 @@ pub const TestScope = struct {
this.promise = null;
}
- while (this.promise.?.status(vm.global.vm()) == JSC.JSPromise.Status.Pending) {
+ var status = JSC.JSPromise.Status.Pending;
+ var vm_ptr = vm.global.vm();
+ while (this.promise != null and status == JSC.JSPromise.Status.Pending) : (status = this.promise.?.status(vm_ptr)) {
vm.tick();
}
- switch (this.promise.?.status(vm.global.vm())) {
+ switch (status) {
.Rejected => {
vm.defaultErrorHandler(this.promise.?.result(vm.global.vm()), null);
return .{ .fail = this.counter.actual };
},
else => {
- // don't care about the result
- _ = this.promise.?.result(vm.global.vm());
+ if (this.promise != null)
+ // don't care about the result
+ _ = this.promise.?.result(vm.global.vm());
},
}
}
@@ -849,8 +852,11 @@ pub const DescribeScope = struct {
var i: TestRunner.Test.ID = 0;
while (i < end) {
+ // the test array could resize in the middle of this loop
this.current_test_id = i;
- const result = TestScope.run(&tests[i]);
+ var test_ = tests[i];
+ const result = TestScope.run(&test_);
+ tests[i] = test_;
// invalidate it
this.current_test_id = std.math.maxInt(TestRunner.Test.ID);
diff --git a/src/javascript/jsc/webcore/response.zig b/src/javascript/jsc/webcore/response.zig
index f05da9af7..9103c725a 100644
--- a/src/javascript/jsc/webcore/response.zig
+++ b/src/javascript/jsc/webcore/response.zig
@@ -40,7 +40,7 @@ const VirtualMachine = @import("../javascript.zig").VirtualMachine;
const Task = @import("../javascript.zig").Task;
const JSPrinter = @import("../../../js_printer.zig");
const picohttp = @import("picohttp");
-
+const StringJoiner = @import("../../../string_joiner.zig");
pub const Response = struct {
pub const Class = NewClass(
Response,
@@ -48,15 +48,19 @@ pub const Response = struct {
.{
.@"constructor" = constructor,
.@"text" = .{
- .rfn = getText,
+ .rfn = Response.getText,
.ts = d.ts{},
},
.@"json" = .{
- .rfn = getJson,
+ .rfn = Response.getJSON,
.ts = d.ts{},
},
.@"arrayBuffer" = .{
- .rfn = getArrayBuffer,
+ .rfn = Response.getArrayBuffer,
+ .ts = d.ts{},
+ },
+ .@"blob" = .{
+ .rfn = Response.getBlob,
.ts = d.ts{},
},
@@ -87,6 +91,10 @@ pub const Response = struct {
.@"get" = getHeaders,
.ro = true,
},
+ .@"bodyUsed" = .{
+ .@"get" = getBodyUsed,
+ .ro = true,
+ },
},
);
@@ -101,7 +109,7 @@ pub const Response = struct {
pub fn writeFormat(this: *const Response, formatter: *JSC.Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void {
const Writer = @TypeOf(writer);
try formatter.writeIndent(Writer, writer);
- try writer.print("Response ({}) {{\n", .{bun.fmt.size(this.body.len)});
+ try writer.print("Response ({}) {{\n", .{bun.fmt.size(this.body.len())});
{
formatter.indent += 1;
defer formatter.indent -|= 1;
@@ -154,6 +162,16 @@ pub const Response = struct {
return ZigString.init(this.url).withEncoding().toValueGC(ctx.ptr()).asObjectRef();
}
+ pub fn getBodyUsed(
+ this: *Response,
+ _: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+ ) js.JSValueRef {
+ return JSC.JSValue.jsBoolean(this.body.value == .Used).asRef();
+ }
+
pub fn getStatusText(
this: *Response,
ctx: js.JSContextRef,
@@ -219,194 +237,7 @@ pub const Response = struct {
return new_response;
}
- pub fn getText(
- this: *Response,
- ctx: js.JSContextRef,
- _: js.JSObjectRef,
- _: js.JSObjectRef,
- _: []const js.JSValueRef,
- _: js.ExceptionRef,
- ) js.JSValueRef {
-
- // https://developer.mozilla.org/en-US/docs/Web/API/Response/text
- defer this.body.value = .Empty;
- return JSPromise.resolvedPromiseValue(
- ctx.ptr(),
- (brk: {
- switch (this.body.value) {
- .Unconsumed => {
- if (this.body.len > 0) {
- if (this.body.ptr) |_ptr| {
- var zig_string = ZigString.init(_ptr[0..this.body.len]);
- zig_string.detectEncoding();
- if (zig_string.is16Bit()) {
- var value = zig_string.to16BitValue(ctx.ptr());
- this.body.ptr_allocator.?.free(_ptr[0..this.body.len]);
- this.body.ptr_allocator = null;
- this.body.ptr = null;
- break :brk value;
- }
-
- break :brk zig_string.toValue(ctx.ptr());
- }
- }
-
- break :brk ZigString.init("").toValue(ctx.ptr());
- },
- .Empty => {
- break :brk ZigString.init("").toValue(ctx.ptr());
- },
- .String => |str| {
- var zig_string = ZigString.init(str);
-
- zig_string.detectEncoding();
- if (zig_string.is16Bit()) {
- var value = zig_string.to16BitValue(ctx.ptr());
- if (this.body.ptr_allocator) |allocator| this.body.deinit(allocator);
- break :brk value;
- }
-
- break :brk zig_string.toValue(ctx.ptr());
- },
- .ArrayBuffer => |buffer| {
- break :brk ZigString.init(buffer.ptr[buffer.offset..buffer.byte_len]).toValue(ctx.ptr());
- },
- else => unreachable,
- }
- }),
- ).asRef();
- }
-
- var temp_error_buffer: [4096]u8 = undefined;
- var error_arg_list: [1]js.JSObjectRef = undefined;
- pub fn getJson(
- this: *Response,
- ctx: js.JSContextRef,
- _: js.JSObjectRef,
- _: js.JSObjectRef,
- _: []const js.JSValueRef,
- exception: js.ExceptionRef,
- ) js.JSValueRef {
- var zig_string = ZigString.init("");
- var deallocate = false;
- defer {
- if (deallocate) {
- if (this.body.value == .Unconsumed) {
- this.body.ptr_allocator.?.free(this.body.ptr.?[0..this.body.len]);
- this.body.ptr_allocator = null;
- this.body.ptr = null;
- this.body.len = 0;
- }
- }
-
- this.body.value = .Empty;
- }
-
- var json_value = (js.JSValueMakeFromJSONString(
- ctx,
- brk: {
- switch (this.body.value) {
- .Unconsumed => {
- if (this.body.ptr) |_ptr| {
- zig_string = ZigString.init(_ptr[0..this.body.len]);
- deallocate = true;
-
- break :brk zig_string.toJSStringRef();
- }
-
- break :brk zig_string.toJSStringRef();
- },
- .Empty => {
- break :brk zig_string.toJSStringRef();
- },
- .String => |str| {
- zig_string = ZigString.init(str);
- break :brk zig_string.toJSStringRef();
- },
- .ArrayBuffer => |buffer| {
- zig_string = ZigString.init(buffer.ptr[buffer.offset..buffer.byte_len]);
- break :brk zig_string.toJSStringRef();
- },
- else => unreachable,
- }
- },
- ) orelse {
- var out = std.fmt.bufPrint(&temp_error_buffer, "Invalid JSON\n\n \"{s}\"", .{zig_string.slice()[0..std.math.min(zig_string.len, 4000)]}) catch unreachable;
- error_arg_list[0] = ZigString.init(out).toValueGC(ctx.ptr()).asRef();
- return JSPromise.rejectedPromiseValue(
- ctx.ptr(),
- JSValue.fromRef(
- js.JSObjectMakeError(
- ctx,
- 1,
- &error_arg_list,
- exception,
- ),
- ),
- ).asRef();
- });
-
- return JSPromise.resolvedPromiseValue(
- ctx.ptr(),
- JSValue.fromRef(json_value),
- ).asRef();
- }
- pub fn getArrayBuffer(
- this: *Response,
- ctx: js.JSContextRef,
- _: js.JSObjectRef,
- _: js.JSObjectRef,
- _: []const js.JSValueRef,
- exception: js.ExceptionRef,
- ) js.JSValueRef {
- defer this.body.value = .Empty;
- return JSPromise.resolvedPromiseValue(
- ctx.ptr(),
- JSValue.fromRef(
- (brk: {
- switch (this.body.value) {
- .Unconsumed => {
- if (this.body.ptr) |_ptr| {
- break :brk JSC.MarkedArrayBuffer.fromBytes(_ptr[0..this.body.len], default_allocator, .ArrayBuffer).toJSObjectRef(ctx, exception);
- }
- break :brk js.JSObjectMakeTypedArray(
- ctx,
- js.JSTypedArrayType.kJSTypedArrayTypeArrayBuffer,
- 0,
- exception,
- );
- },
- .Empty => {
- break :brk js.JSObjectMakeTypedArray(ctx, js.JSTypedArrayType.kJSTypedArrayTypeArrayBuffer, 0, exception);
- },
- .String => |str| {
- break :brk js.JSObjectMakeTypedArrayWithBytesNoCopy(
- ctx,
- js.JSTypedArrayType.kJSTypedArrayTypeArrayBuffer,
- @intToPtr([*]u8, @ptrToInt(str.ptr)),
- str.len,
- null,
- null,
- exception,
- );
- },
- .ArrayBuffer => |buffer| {
- break :brk js.JSObjectMakeTypedArrayWithBytesNoCopy(
- ctx,
- js.JSTypedArrayType.kJSTypedArrayTypeArrayBuffer,
- buffer.ptr,
- buffer.byte_len,
- null,
- null,
- exception,
- );
- },
- else => unreachable,
- }
- }),
- ),
- ).asRef();
- }
+ pub usingnamespace BlobInterface(@This());
pub fn getStatus(
this: *Response,
@@ -437,7 +268,7 @@ pub const Response = struct {
allocator.destroy(this);
}
- pub fn mimeType(response: *const Response, request_ctx: *const RequestContext) string {
+ pub fn mimeType(response: *const Response, request_ctx_: ?*const RequestContext) string {
if (response.body.init.headers) |headers| {
// Remember, we always lowercase it
// hopefully doesn't matter here tho
@@ -446,25 +277,21 @@ pub const Response = struct {
}
}
- if (request_ctx.url.extname.len > 0) {
- return MimeType.byExtension(request_ctx.url.extname).value;
+ if (request_ctx_) |request_ctx| {
+ if (request_ctx.url.extname.len > 0) {
+ return MimeType.byExtension(request_ctx.url.extname).value;
+ }
}
switch (response.body.value) {
- .Empty => {
- return "text/plain";
- },
- .String => |body| {
- // poor man's mimetype sniffing
- if (body.len > 0 and (body[0] == '{' or body[0] == '[')) {
- return MimeType.json.value;
+ .Blob => |blob| {
+ if (blob.content_type.len > 0) {
+ return blob.content_type;
}
- return MimeType.html.value;
- },
- else => {
- return "application/octet-stream";
+ return MimeType.other.value;
},
+ .Used, .Locked, .Empty => return MimeType.other.value,
}
}
@@ -493,10 +320,6 @@ pub const Response = struct {
unreachable;
};
- // if (exception != null) {
- // return null;
- // }
-
var response = getAllocator(ctx).create(Response) catch unreachable;
response.* = Response{
.body = body,
@@ -722,11 +545,8 @@ pub const Fetch = struct {
.status_code = @truncate(u16, http_response.status_code),
},
.value = .{
- .Unconsumed = 0,
+ .Blob = Blob.init(duped, allocator, this.global_this),
},
- .ptr = duped.ptr,
- .len = duped.len,
- .ptr_allocator = allocator,
},
};
return JSValue.fromRef(Response.Class.make(@ptrCast(js.JSContextRef, this.global_this), response));
@@ -805,92 +625,78 @@ pub const Fetch = struct {
return JSPromise.rejectedPromiseValue(globalThis, ZigString.init(fetch_error).toErrorInstance(globalThis)).asRef();
}
- if (!js.JSValueIsString(ctx, arguments[0])) {
- const fetch_error = fetch_type_error_strings.get(js.JSValueGetType(ctx, arguments[0]));
- return JSPromise.rejectedPromiseValue(globalThis, ZigString.init(fetch_error).toErrorInstance(globalThis)).asRef();
- }
+ var headers: ?Headers = null;
+ var body: MutableString = MutableString.initEmpty(bun.default_allocator);
+ var method = Method.GET;
+ var args = JSC.Node.ArgumentsSlice.from(arguments);
+ var url: ZigURL = undefined;
+ var first_arg = args.nextEat().?;
+ if (first_arg.isString()) {
+ var url_zig_str = ZigString.init("");
+ JSValue.fromRef(arguments[0]).toZigString(&url_zig_str, globalThis);
+ var url_str = url_zig_str.slice();
+
+ if (url_str.len == 0) {
+ const fetch_error = fetch_error_blank_url;
+ return JSPromise.rejectedPromiseValue(globalThis, ZigString.init(fetch_error).toErrorInstance(globalThis)).asRef();
+ }
- var url_zig_str = ZigString.init("");
- JSValue.fromRef(arguments[0]).toZigString(&url_zig_str, globalThis);
- var url_str = url_zig_str.slice();
+ if (url_str[0] == '/') {
+ url_str = strings.append(getAllocator(ctx), VirtualMachine.vm.bundler.options.origin.origin, url_str) catch unreachable;
+ } else {
+ url_str = getAllocator(ctx).dupe(u8, url_str) catch unreachable;
+ }
- if (url_str.len == 0) {
- const fetch_error = fetch_error_blank_url;
- return JSPromise.rejectedPromiseValue(globalThis, ZigString.init(fetch_error).toErrorInstance(globalThis)).asRef();
- }
+ NetworkThread.init() catch @panic("Failed to start network thread");
+ url = ZigURL.parse(url_str);
+
+ if (arguments.len >= 2 and js.JSValueIsObject(ctx, arguments[1])) {
+ var options = JSValue.fromRef(arguments[1]);
+ if (options.get(ctx.ptr(), "method")) |method_| {
+ var slice_ = method_.toSlice(ctx.ptr(), getAllocator(ctx));
+ defer slice_.deinit();
+ method = Method.which(slice_.slice()) orelse .GET;
+ }
+
+ if (options.get(ctx.ptr(), "headers")) |headers_| {
+ var headers2: Headers = undefined;
+ if (headers_.as(Headers)) |headers__| {
+ headers__.clone(&headers2) catch unreachable;
+ headers = headers2;
+ } else if (Headers.JS.headersInit(ctx, headers_.asObjectRef()) catch null) |headers__| {
+ headers__.clone(&headers2) catch unreachable;
+ headers = headers2;
+ }
+ }
- if (url_str[0] == '/') {
- url_str = strings.append(getAllocator(ctx), VirtualMachine.vm.bundler.options.origin.origin, url_str) catch unreachable;
+ if (options.get(ctx.ptr(), "body")) |body__| {
+ if (Blob.fromJS(ctx.ptr(), body__)) |new_blob| {
+ body = MutableString{ .list = new_blob.asArrayList(), .allocator = bun.default_allocator };
+ } else |_| {}
+ }
+ }
+ } else if (Request.Class.loaded and first_arg.as(Request) != null) {
+ var request = first_arg.as(Request).?;
+ url = ZigURL.parse(request.url.slice());
+ method = request.method;
+ if (request.headers) |head| {
+ headers = head.*;
+ }
+ var blob = request.body.use();
+ body = MutableString{
+ .list = blob.asArrayList(),
+ .allocator = blob.allocator orelse bun.default_allocator,
+ };
} else {
- url_str = getAllocator(ctx).dupe(u8, url_str) catch unreachable;
+ const fetch_error = fetch_type_error_strings.get(js.JSValueGetType(ctx, arguments[0]));
+ return JSPromise.rejectedPromiseValue(globalThis, ZigString.init(fetch_error).toErrorInstance(globalThis)).asRef();
}
- NetworkThread.init() catch @panic("Failed to start network thread");
- const url = ZigURL.parse(url_str);
-
if (url.origin.len > 0 and strings.eql(url.origin, VirtualMachine.vm.bundler.options.origin.origin)) {
const fetch_error = fetch_error_cant_fetch_same_origin;
return JSPromise.rejectedPromiseValue(globalThis, ZigString.init(fetch_error).toErrorInstance(globalThis)).asRef();
}
- var headers: ?Headers = null;
- var body: string = "";
- var method = Method.GET;
-
- if (arguments.len >= 2 and js.JSValueIsObject(ctx, arguments[1])) {
- var array = js.JSObjectCopyPropertyNames(ctx, arguments[1]);
- defer js.JSPropertyNameArrayRelease(array);
- const count = js.JSPropertyNameArrayGetCount(array);
- var i: usize = 0;
- while (i < count) : (i += 1) {
- var property_name_ref = js.JSPropertyNameArrayGetNameAtIndex(array, i);
- switch (js.JSStringGetLength(property_name_ref)) {
- "headers".len => {
- if (js.JSStringIsEqualToUTF8CString(property_name_ref, "headers")) {
- if (js.JSObjectGetProperty(ctx, arguments[1], property_name_ref, null)) |value| {
- if (GetJSPrivateData(Headers, value)) |headers_ptr| {
- headers = headers_ptr.*;
- } else if (Headers.JS.headersInit(ctx, value) catch null) |headers_| {
- headers = headers_;
- }
- }
- }
- },
- "body".len => {
- if (js.JSStringIsEqualToUTF8CString(property_name_ref, "body")) {
- if (js.JSObjectGetProperty(ctx, arguments[1], property_name_ref, null)) |value| {
- var body_ = Body.extractBody(ctx, value, false, null, exception);
- if (exception.* != null) return js.JSValueMakeNull(ctx);
- switch (body_.value) {
- .ArrayBuffer => |arraybuffer| {
- body = arraybuffer.ptr[0..arraybuffer.byte_len];
- },
- .String => |str| {
- body = str;
- },
- else => {},
- }
- }
- }
- },
- "method".len => {
- if (js.JSStringIsEqualToUTF8CString(property_name_ref, "method")) {
- if (js.JSObjectGetProperty(ctx, arguments[1], property_name_ref, null)) |value| {
- var string_ref = js.JSValueToStringCopy(ctx, value, exception);
-
- if (exception.* != null) return js.JSValueMakeNull(ctx);
- defer js.JSStringRelease(string_ref);
- var method_name_buf: [16]u8 = undefined;
- var method_name = method_name_buf[0..js.JSStringGetUTF8CString(string_ref, &method_name_buf, method_name_buf.len)];
- method = Method.which(method_name) orelse method;
- }
- }
- },
- else => {},
- }
- }
- }
-
var header_entries: Headers.Entries = .{};
var header_buf: string = "";
@@ -904,6 +710,13 @@ pub const Fetch = struct {
js.JSValueProtect(ctx, resolve);
js.JSValueProtect(ctx, reject);
+ var request_body: ?*MutableString = null;
+ if (body.list.items.len > 0) {
+ var mutable = bun.default_allocator.create(MutableString) catch unreachable;
+ mutable.* = body;
+ request_body = mutable;
+ }
+
// var resolve = FetchTasklet.FetchResolver.Class.make(ctx: js.JSContextRef, ptr: *ZigType)
var queued = FetchTasklet.queue(
default_allocator,
@@ -912,10 +725,10 @@ pub const Fetch = struct {
url,
header_entries,
header_buf,
- null,
+ request_body,
std.time.ns_per_hour,
) catch unreachable;
- queued.data.this_object = js.JSObjectMake(ctx, null, JSPrivateDataPtr.init(&queued.data.context).ptr());
+ queued.data.this_object = js.JSObjectMake(ctx, null, JSPrivateDataPtr.from(&queued.data.context).ptr());
js.JSValueProtect(ctx, queued.data.this_object);
var promise = js.JSObjectMakeDeferredPromise(ctx, &resolve, &reject, exception);
@@ -1326,7 +1139,7 @@ pub const Headers = struct {
headers.entries.items(.value)[header_i] = Api.StringPointer{ .offset = headers.used, .length = end - 1 };
headers.used += end;
// Can we get away with just overwriting in-place?
- } else if (existing_value.length < value.len) {
+ } else if (existing_value.length > value.len) {
std.mem.copy(u8, headers.asStr(existing_value), value);
headers.entries.items(.value)[header_i].length = @truncate(u32, value.len);
headers.asStr(headers.entries.items(.value)[header_i]).ptr[value.len] = 0;
@@ -1523,7 +1336,7 @@ pub const Headers = struct {
}) catch unreachable;
}
- pub fn clone(this: *Headers, to: *Headers) !void {
+ pub fn clone(this: *const Headers, to: *Headers) !void {
to.* = Headers{
.entries = try this.entries.clone(this.allocator),
.buf = try @TypeOf(this.buf).initCapacity(this.allocator, this.buf.items.len),
@@ -1536,50 +1349,694 @@ pub const Headers = struct {
}
};
-// https://developer.mozilla.org/en-US/docs/Web/API/Body
-pub const Body = struct {
- init: Init,
- value: Value,
+pub const Blob = struct {
+ size: usize = 0,
ptr: ?[*]u8 = null,
- len: usize = 0,
+ cap: usize = 0,
ptr_allocator: ?std.mem.Allocator = null,
+ allocator: ?std.mem.Allocator = null,
- pub fn slice(this: *const Body) []const u8 {
- return switch (this.value) {
- .String => this.value.String,
- .ArrayBuffer => this.value.ArrayBuffer.slice(),
- else => "",
+ content_type: string = "",
+ content_type_allocated: bool = false,
+ detached: bool = false,
+
+ globalThis: *JSGlobalObject,
+
+ pub fn asArrayList(this: *const Blob) std.ArrayListUnmanaged(u8) {
+ if (this.ptr == null or this.size == 0)
+ return .{};
+
+ return .{
+ .items = this.ptr.?[0..this.size],
+ .capacity = this.cap,
};
}
- pub fn clone(this: Body, allocator: std.mem.Allocator) Body {
- var value: Value = .{ .Empty = 0 };
- var ptr: ?[*]u8 = null;
- var len: usize = 0;
- switch (this.value) {
- .ArrayBuffer => |buffer| {
- value = .{
- .ArrayBuffer = ArrayBuffer.fromBytes(allocator.dupe(u8, buffer.slice()) catch unreachable, buffer.typed_array_type),
+ pub const Class = NewClass(
+ Blob,
+ .{ .name = "Blob" },
+ .{
+ .constructor = constructor,
+ .finalize = finalize,
+ .text = .{
+ .rfn = getText,
+ },
+ .json = .{
+ .rfn = getJSON,
+ },
+ .arrayBuffer = .{
+ .rfn = getArrayBuffer,
+ },
+ .slice = .{
+ .rfn = getSlice,
+ },
+ },
+ .{
+ .@"type" = .{
+ .get = getType,
+ .set = setType,
+ },
+ .@"size" = .{
+ .get = getSize,
+ .ro = true,
+ },
+ },
+ );
+
+ fn promisified(
+ value: JSC.JSValue,
+ global: *JSGlobalObject,
+ ) JSC.JSValue {
+ return JSC.JSPromise.resolvedPromiseValue(global, value);
+ }
+
+ pub fn getText(
+ this: *Blob,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ _: []const js.JSValueRef,
+ _: js.ExceptionRef,
+ ) JSC.C.JSObjectRef {
+ return promisified(this.toString(ctx.ptr(), .clone), ctx.ptr()).asObjectRef();
+ }
+
+ pub fn getTextTransfer(
+ this: *Blob,
+ ctx: js.JSContextRef,
+ ) JSC.C.JSObjectRef {
+ return promisified(this.toString(ctx.ptr(), .transfer), ctx.ptr()).asObjectRef();
+ }
+
+ pub fn getJSON(
+ this: *Blob,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ _: []const js.JSValueRef,
+ _: js.ExceptionRef,
+ ) JSC.C.JSObjectRef {
+ return promisified(this.toJSON(ctx.ptr()), ctx.ptr()).asObjectRef();
+ }
+
+ pub fn getArrayBufferTransfer(
+ this: *Blob,
+ ctx: js.JSContextRef,
+ ) JSC.C.JSObjectRef {
+ return promisified(this.toArrayBuffer(ctx.ptr(), .transfer), ctx.ptr()).asObjectRef();
+ }
+
+ pub fn getArrayBuffer(
+ this: *Blob,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ _: []const js.JSValueRef,
+ _: js.ExceptionRef,
+ ) JSC.C.JSObjectRef {
+ return promisified(this.toArrayBuffer(ctx.ptr(), .clone), ctx.ptr()).asObjectRef();
+ }
+
+ /// https://w3c.github.io/FileAPI/#slice-method-algo
+ /// The slice() method returns a new Blob object with bytes ranging from the
+ /// optional start parameter up to but not including the optional end
+ /// parameter, and with a type attribute that is the value of the optional
+ /// contentType parameter. It must act as follows:
+ pub fn getSlice(
+ this: *Blob,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ args: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) JSC.C.JSObjectRef {
+ if (this.size == 0) {
+ return constructor(ctx, null, &[_]js.JSValueRef{}, exception);
+ }
+ // If the optional start parameter is not used as a parameter when making this call, let relativeStart be 0.
+ var relativeStart: i32 = 0;
+
+ // If the optional end parameter is not used as a parameter when making this call, let relativeEnd be size.
+ var relativeEnd: i32 = @intCast(i32, this.size);
+
+ var args_iter = JSC.Node.ArgumentsSlice.from(args);
+ if (args_iter.nextEat()) |start_| {
+ const start = start_.toInt32();
+ if (start < 0) {
+ // If the optional start parameter is negative, let relativeStart be start + size.
+ relativeStart = @intCast(i32, @maximum(start + @intCast(i32, this.size), 0));
+ } else {
+ // Otherwise, let relativeStart be start.
+ relativeStart = @minimum(@intCast(i32, start), @intCast(i32, this.size));
+ }
+ }
+
+ if (args_iter.nextEat()) |end_| {
+ const end = end_.toInt32();
+ // If end is negative, let relativeEnd be max((size + end), 0).
+ if (end < 0) {
+ // If the optional start parameter is negative, let relativeStart be start + size.
+ relativeEnd = @intCast(i32, @maximum(end + @intCast(i32, this.size), 0));
+ } else {
+ // Otherwise, let relativeStart be start.
+ relativeEnd = @minimum(@intCast(i32, end), @intCast(i32, this.size));
+ }
+ }
+
+ var content_type: string = "";
+ if (args_iter.nextEat()) |content_type_| {
+ if (content_type_.isString()) {
+ var zig_str = content_type_.getZigString(ctx.ptr());
+ if (!zig_str.is16Bit()) {
+ var slice = zig_str.trimmedSlice();
+ var content_type_buf = getAllocator(ctx).alloc(u8, slice.len) catch unreachable;
+ content_type = strings.copyLowercase(slice, content_type_buf);
+ }
+ }
+ }
+
+ const len = @intCast(u32, @maximum(relativeEnd - relativeStart, 0));
+
+ var blob = Blob.init(
+ getAllocator(ctx).dupe(u8, this.sharedView()[@intCast(usize, relativeStart)..][0..len]) catch unreachable,
+ getAllocator(ctx),
+ ctx.ptr(),
+ );
+ blob.content_type = content_type;
+ blob.content_type_allocated = content_type.len > 0;
+
+ var blob_ = getAllocator(ctx).create(Blob) catch unreachable;
+ blob_.* = blob;
+ blob_.allocator = getAllocator(ctx);
+ return Blob.Class.make(ctx, blob_);
+ }
+
+ pub fn getType(
+ this: *Blob,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+ ) js.JSValueRef {
+ return ZigString.init(this.content_type).toValue(ctx.ptr()).asObjectRef();
+ }
+
+ pub fn setType(
+ this: *Blob,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSStringRef,
+ value: js.JSValueRef,
+ _: js.ExceptionRef,
+ ) bool {
+ var zig_str = JSValue.fromRef(value).getZigString(ctx.ptr());
+ if (zig_str.is16Bit())
+ return false;
+
+ var slice = zig_str.trimmedSlice();
+ if (strings.eql(slice, this.content_type))
+ return true;
+
+ const prev_content_type = this.content_type;
+ defer if (this.content_type_allocated) bun.default_allocator.free(prev_content_type);
+ var content_type_buf = getAllocator(ctx).alloc(u8, slice.len) catch unreachable;
+ this.content_type = strings.copyLowercase(slice, content_type_buf);
+ this.content_type_allocated = true;
+ return true;
+ }
+
+ pub fn getSize(
+ this: *Blob,
+ _: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+ ) js.JSValueRef {
+ return JSValue.jsNumber(@truncate(u32, this.size)).asRef();
+ }
+
+ pub fn constructor(
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ args: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) js.JSObjectRef {
+ var blob: Blob = undefined;
+ switch (args.len) {
+ 0 => {
+ var empty: []u8 = &[_]u8{};
+ blob = Blob.init(empty, getAllocator(ctx), ctx.ptr());
+ },
+ else => {
+ blob = fromJS(ctx.ptr(), JSValue.fromRef(args[0])) catch {
+ JSC.JSError(getAllocator(ctx), "out of memory :(", .{}, ctx, exception);
+ return null;
};
- len = buffer.len;
- ptr = value.ArrayBuffer.ptr;
+
+ if (args.len > 1) {
+ var options = JSValue.fromRef(args[1]);
+ if (options.isCell()) {
+ // type, the ASCII-encoded string in lower case
+ // representing the media type of the Blob.
+ // Normative conditions for this member are provided
+ // in the § 3.1 Constructors.
+ if (options.get(ctx.ptr(), "type")) |content_type| {
+ if (content_type.isString()) {
+ var content_type_str = content_type.getZigString(ctx.ptr());
+ if (!content_type_str.is16Bit()) {
+ var slice = content_type_str.trimmedSlice();
+ var content_type_buf = getAllocator(ctx).alloc(u8, slice.len) catch unreachable;
+ blob.content_type = strings.copyLowercase(slice, content_type_buf);
+ blob.content_type_allocated = true;
+ }
+ }
+ }
+ }
+ }
+
+ if (blob.content_type.len == 0) {
+ blob.content_type = "";
+ }
},
- .String => |str| {
- value = .{
- .String = allocator.dupe(u8, str) catch unreachable,
+ }
+
+ var blob_ = getAllocator(ctx).create(Blob) catch unreachable;
+ blob_.* = blob;
+ blob_.allocator = getAllocator(ctx);
+ return Blob.Class.make(ctx, blob_);
+ }
+
+ pub fn finalize(this: *Blob) void {
+ this.deinit();
+ }
+
+ pub fn init(bytes: []u8, allocator: std.mem.Allocator, globalThis: *JSGlobalObject) Blob {
+ return Blob{
+ .size = bytes.len,
+ .ptr = bytes.ptr,
+ .cap = bytes.len,
+ .ptr_allocator = allocator,
+ .allocator = null,
+ .content_type = "",
+ .detached = false,
+ .globalThis = globalThis,
+ };
+ }
+
+ pub fn initEmpty(globalThis: *JSGlobalObject) Blob {
+ return Blob{
+ .size = 0,
+ .ptr = undefined,
+ .cap = 0,
+ .ptr_allocator = null,
+ .allocator = null,
+ .content_type = "",
+ .detached = false,
+ .globalThis = globalThis,
+ };
+ }
+
+ pub fn detach(this: *Blob) void {
+ var blob = this.*;
+ if (blob.ptr_allocator) |alloc| {
+ alloc.free(blob.sharedView());
+ this.ptr_allocator = null;
+ this.ptr = null;
+ this.size = 0;
+ this.cap = 0;
+ }
+
+ this.detached = true;
+ }
+
+ pub fn dupe(this: *const Blob, allocator: std.mem.Allocator) !Blob {
+ var data = try allocator.dupe(u8, this.sharedView());
+ return Blob{
+ .size = data.len,
+ .ptr = data.ptr,
+ .cap = data.len,
+ .ptr_allocator = allocator,
+ .allocator = allocator,
+ .content_type = this.content_type,
+ .detached = false,
+ .globalThis = this.globalThis,
+ };
+ }
+
+ pub fn deinit(this: *Blob) void {
+ var blob = this.*;
+
+ if (this.allocator) |alloc| {
+ this.allocator = null;
+ alloc.destroy(this);
+ }
+
+ blob.detach();
+ }
+
+ pub fn sharedView(this: *const Blob) []const u8 {
+ if (this.size == 0 or this.ptr == null) return "";
+ return this.ptr.?[0..this.size];
+ }
+
+ pub const Lifetime = enum {
+ clone,
+ transfer,
+ share,
+ };
+
+ pub fn toString(this: *Blob, global: *JSGlobalObject, comptime lifetime: Lifetime) JSValue {
+ var view_ = this.sharedView();
+
+ if (view_.len == 0)
+ return ZigString.Empty.toValue(global);
+
+ // TODO: use the index to make this one pass instead of two passes
+ var buf = view_;
+ if (!strings.isAllASCII(buf)) {
+ var external = (strings.toUTF16Alloc(bun.default_allocator, buf, false) catch null) orelse &[_]u16{};
+ return ZigString.toExternalU16(external.ptr, external.len, global);
+ }
+
+ switch (comptime lifetime) {
+ .clone => {
+ return ZigString.init(buf).toValueGC(global);
+ },
+ .transfer => {
+ this.detached = true;
+ this.ptr_allocator = null;
+ this.ptr = null;
+ this.size = 0;
+ this.cap = 0;
+ return ZigString.init(buf).toExternalValue(global);
+ },
+ .share => {
+ return ZigString.init(buf).toValue(global);
+ },
+ }
+ }
+
+ pub fn toJSON(this: *Blob, global: *JSGlobalObject) JSValue {
+ var view_ = this.sharedView();
+
+ if (view_.len == 0)
+ return ZigString.Empty.toValue(global);
+
+ // TODO: use the index to make this one pass instead of two passes
+ var buf = view_;
+ if (!strings.isAllASCII(buf)) {
+ var external = (strings.toUTF16Alloc(bun.default_allocator, buf, false) catch null) orelse &[_]u16{};
+ defer bun.default_allocator.free(external);
+ return ZigString.toExternalU16(external.ptr, external.len, global).parseJSON(global);
+ }
+
+ return ZigString.init(buf).toValue(global).parseJSON(global);
+ }
+ pub fn toArrayBuffer(this: *Blob, global: *JSGlobalObject, comptime lifetime: Lifetime) JSValue {
+ var view_ = this.sharedView();
+
+ if (view_.len == 0)
+ return JSC.ArrayBuffer.fromBytes(&[_]u8{}, .ArrayBuffer).toJS(global.ref(), null);
+
+ switch (comptime lifetime) {
+ .clone => {
+ var clone = bun.default_allocator.alloc(u8, view_.len) catch unreachable;
+ @memcpy(clone.ptr, view_.ptr, view_.len);
+
+ return JSC.ArrayBuffer.fromBytes(clone, .ArrayBuffer).toJS(global.ref(), null);
+ },
+ .share => {
+ return JSC.ArrayBuffer.fromBytes(view_.ptr, .ArrayBuffer).toJS(global.ref(), null);
+ },
+ .transfer => {
+ this.detached = true;
+ this.ptr_allocator = null;
+ this.ptr = null;
+ this.size = 0;
+ this.cap = 0;
+ return JSC.ArrayBuffer.fromBytes(bun.constStrToU8(view_), .ArrayBuffer).toJS(global.ref(), null);
+ },
+ }
+ }
+
+ pub fn fromJS(global: *JSGlobalObject, arg: JSValue) anyerror!Blob {
+ const DeferCtx = struct {
+ args: std.meta.ArgsTuple(@TypeOf(Blob.fromJSWithoutDeferGC)),
+ ret: anyerror!Blob = undefined,
+
+ pub fn run(ctx: ?*anyopaque) callconv(.C) void {
+ var that = bun.cast(*@This(), ctx.?);
+ that.ret = @call(.{}, Blob.fromJSWithoutDeferGC, that.args);
+ }
+ };
+ var ctx = DeferCtx{
+ .args = .{ global, arg },
+ .ret = undefined,
+ };
+ JSC.VirtualMachine.vm.global.vm().deferGC(&ctx, DeferCtx.run);
+ return ctx.ret;
+ }
+
+ fn fromJSWithoutDeferGC(global: *JSGlobalObject, arg: JSValue) anyerror!Blob {
+ var current = arg;
+ if (current.isUndefinedOrNull()) {
+ return Blob{ .globalThis = global };
+ }
+
+ // Fast path: one item, we don't need to join
+ switch (current.jsTypeLoose()) {
+ .Cell,
+ .NumberObject,
+ JSC.JSValue.JSType.String,
+ JSC.JSValue.JSType.StringObject,
+ JSC.JSValue.JSType.DerivedStringObject,
+ => {
+ var sliced = current.toSlice(global, bun.default_allocator);
+ if (!sliced.allocated) {
+ sliced.ptr = @ptrCast([*]const u8, (try bun.default_allocator.dupe(u8, sliced.slice())).ptr);
+ sliced.allocated = true;
+ }
+
+ return Blob{
+ .size = sliced.len,
+ .ptr = bun.constStrToU8(sliced.ptr[0..sliced.len]).ptr,
+ .cap = sliced.len,
+ .ptr_allocator = bun.default_allocator,
+ .allocator = null,
+ .globalThis = global,
};
- len = str.len;
- ptr = bun.constStrToU8(value.String).ptr;
},
- else => {},
+
+ JSC.JSValue.JSType.ArrayBuffer,
+ JSC.JSValue.JSType.Int8Array,
+ JSC.JSValue.JSType.Uint8Array,
+ JSC.JSValue.JSType.Uint8ClampedArray,
+ JSC.JSValue.JSType.Int16Array,
+ JSC.JSValue.JSType.Uint16Array,
+ JSC.JSValue.JSType.Int32Array,
+ JSC.JSValue.JSType.Uint32Array,
+ JSC.JSValue.JSType.Float32Array,
+ JSC.JSValue.JSType.Float64Array,
+ JSC.JSValue.JSType.BigInt64Array,
+ JSC.JSValue.JSType.BigUint64Array,
+ JSC.JSValue.JSType.DataView,
+ => {
+ var buf = try bun.default_allocator.dupe(u8, current.asArrayBuffer(global).?.slice());
+ return Blob{
+ .size = buf.len,
+ .ptr = buf.ptr,
+ .cap = buf.len,
+ .ptr_allocator = bun.default_allocator,
+ .allocator = null,
+ .globalThis = global,
+ };
+ },
+
+ else => {
+ if (JSC.C.JSObjectGetPrivate(current.asObjectRef())) |priv| {
+ var data = JSC.JSPrivateDataPtr.from(priv);
+ switch (data.tag()) {
+ .Blob => {
+ var blob: *Blob = data.as(Blob);
+ return try blob.dupe(bun.default_allocator);
+ },
+
+ else => return Blob.initEmpty(global),
+ }
+ }
+ },
}
+ var stack_allocator = std.heap.stackFallback(1024, bun.default_allocator);
+ var stack_mem_all = stack_allocator.get();
+ var stack: std.ArrayList(JSValue) = std.ArrayList(JSValue).init(stack_mem_all);
+ var joiner = StringJoiner{ .use_pool = false, .node_allocator = stack_mem_all };
+
+ defer if (stack_allocator.fixed_buffer_allocator.end_index >= 1024) stack.deinit();
+
+ while (true) {
+ switch (current.jsTypeLoose()) {
+ .NumberObject,
+ JSC.JSValue.JSType.String,
+ JSC.JSValue.JSType.StringObject,
+ JSC.JSValue.JSType.DerivedStringObject,
+ => {
+ var sliced = current.toSlice(global, bun.default_allocator);
+ joiner.append(
+ sliced.slice(),
+ 0,
+ if (sliced.allocated) sliced.allocator else null,
+ );
+ },
+
+ .Array, .DerivedArray => {
+ var iter = JSC.JSArrayIterator.init(current, global);
+ try stack.ensureUnusedCapacity(iter.len);
+ var any_arrays = false;
+ while (iter.next()) |item| {
+ if (item.isUndefinedOrNull()) continue;
+
+ // When it's a string or ArrayBuffer inside an array, we can avoid the extra push/pop
+ // we only really want this for nested arrays
+ // However, we must preserve the order
+ // That means if there are any arrays
+ // we have to restart the loop
+ if (!any_arrays) {
+ switch (item.jsTypeLoose()) {
+ .NumberObject,
+ .Cell,
+ JSC.JSValue.JSType.String,
+ JSC.JSValue.JSType.StringObject,
+ JSC.JSValue.JSType.DerivedStringObject,
+ => {
+ var sliced = item.toSlice(global, bun.default_allocator);
+ joiner.append(
+ sliced.slice(),
+ 0,
+ if (sliced.allocated) sliced.allocator else null,
+ );
+ continue;
+ },
+ JSC.JSValue.JSType.ArrayBuffer,
+ JSC.JSValue.JSType.Int8Array,
+ JSC.JSValue.JSType.Uint8Array,
+ JSC.JSValue.JSType.Uint8ClampedArray,
+ JSC.JSValue.JSType.Int16Array,
+ JSC.JSValue.JSType.Uint16Array,
+ JSC.JSValue.JSType.Int32Array,
+ JSC.JSValue.JSType.Uint32Array,
+ JSC.JSValue.JSType.Float32Array,
+ JSC.JSValue.JSType.Float64Array,
+ JSC.JSValue.JSType.BigInt64Array,
+ JSC.JSValue.JSType.BigUint64Array,
+ JSC.JSValue.JSType.DataView,
+ => {
+ var buf = item.asArrayBuffer(global).?;
+ joiner.append(buf.slice(), 0, null);
+ continue;
+ },
+ .Array, .DerivedArray => {
+ any_arrays = true;
+ break;
+ },
+ else => {
+ if (JSC.C.JSObjectGetPrivate(item.asObjectRef())) |priv| {
+ var data = JSC.JSPrivateDataPtr.from(priv);
+ switch (data.tag()) {
+ .Blob => {
+ var blob: *Blob = data.as(Blob);
+ joiner.append(blob.sharedView(), 0, null);
+ continue;
+ },
+ else => {},
+ }
+ }
+ },
+ }
+ }
+
+ stack.appendAssumeCapacity(item);
+ }
+ },
+
+ JSC.JSValue.JSType.ArrayBuffer,
+ JSC.JSValue.JSType.Int8Array,
+ JSC.JSValue.JSType.Uint8Array,
+ JSC.JSValue.JSType.Uint8ClampedArray,
+ JSC.JSValue.JSType.Int16Array,
+ JSC.JSValue.JSType.Uint16Array,
+ JSC.JSValue.JSType.Int32Array,
+ JSC.JSValue.JSType.Uint32Array,
+ JSC.JSValue.JSType.Float32Array,
+ JSC.JSValue.JSType.Float64Array,
+ JSC.JSValue.JSType.BigInt64Array,
+ JSC.JSValue.JSType.BigUint64Array,
+ JSC.JSValue.JSType.DataView,
+ => {
+ var buf = current.asArrayBuffer(global).?;
+ joiner.append(buf.slice(), 0, null);
+ },
+
+ else => {
+ outer: {
+ if (JSC.C.JSObjectGetPrivate(current.asObjectRef())) |priv| {
+ var data = JSC.JSPrivateDataPtr.from(priv);
+ switch (data.tag()) {
+ .Blob => {
+ var blob: *Blob = data.as(Blob);
+ joiner.append(blob.sharedView(), 0, null);
+ break :outer;
+ },
+ else => {},
+ }
+ }
+
+ var sliced = current.toSlice(global, bun.default_allocator);
+ joiner.append(
+ sliced.slice(),
+ 0,
+ if (sliced.allocated) sliced.allocator else null,
+ );
+ }
+ },
+ }
+
+ current = stack.popOrNull() orelse break;
+ }
+
+ var joined = try joiner.done(bun.default_allocator);
+ return Blob{
+ .ptr_allocator = bun.default_allocator,
+ .allocator = null,
+ .size = joined.len,
+ .ptr = joined.ptr,
+ .cap = joined.len,
+ .globalThis = global,
+ };
+ }
+};
+
+// https://developer.mozilla.org/en-US/docs/Web/API/Body
+pub const Body = struct {
+ init: Init = Init{ .headers = null, .status_code = 200 },
+ value: Value = Value.empty,
+
+ pub inline fn len(this: *const Body) usize {
+ return this.slice().len;
+ }
+
+ pub fn slice(this: *const Body) []const u8 {
+ return this.value.slice();
+ }
+
+ pub fn use(this: *Body) Blob {
+ return this.value.use();
+ }
+
+ pub fn clone(this: Body, allocator: std.mem.Allocator) Body {
return Body{
.init = this.init.clone(allocator),
- .value = value,
- .ptr_allocator = if (len > 0) allocator else null,
- .ptr = ptr,
- .len = len,
+ .value = this.value.clone(allocator) catch Value.empty,
};
}
@@ -1588,7 +2045,7 @@ pub const Body = struct {
try formatter.writeIndent(Writer, writer);
try writer.writeAll("bodyUsed: ");
- formatter.printAs(.Boolean, Writer, writer, JSC.JSValue.jsBoolean(!(this.value == .Unconsumed or this.value == .Empty)), .BooleanObject, enable_ansi_colors);
+ formatter.printAs(.Boolean, Writer, writer, JSC.JSValue.jsBoolean(this.value == .Used), .BooleanObject, enable_ansi_colors);
try formatter.printComma(Writer, writer, enable_ansi_colors);
try writer.writeAll("\n");
@@ -1604,25 +2061,18 @@ pub const Body = struct {
formatter.printAs(.Double, Writer, writer, JSC.JSValue.jsNumber(this.init.status_code), .NumberObject, enable_ansi_colors);
}
- pub fn deinit(this: *Body, allocator: std.mem.Allocator) void {
- this.ptr_allocator = null;
+ pub fn deinit(this: *Body, _: std.mem.Allocator) void {
if (this.init.headers) |*headers| {
headers.deinit();
}
- switch (this.value) {
- .ArrayBuffer => {},
- .String => |str| {
- allocator.free(str);
- },
- .Empty => {},
- else => {},
- }
+ this.value.deinit();
}
pub const Init = struct {
headers: ?Headers,
status_code: u16,
+ method: Method = Method.GET,
pub fn clone(this: Init, allocator: std.mem.Allocator) Init {
var that = this;
@@ -1642,6 +2092,7 @@ pub const Body = struct {
var array = js.JSObjectCopyPropertyNames(ctx, init_ref);
defer js.JSPropertyNameArrayRelease(array);
const count = js.JSPropertyNameArrayGetCount(array);
+
var i: usize = 0;
while (i < count) : (i += 1) {
var property_name_ref = js.JSPropertyNameArrayGetNameAtIndex(array, i);
@@ -1668,6 +2119,13 @@ pub const Body = struct {
result.status_code = @truncate(u16, @floatToInt(u64, number));
}
},
+ "method".len => {
+ if (js.JSStringIsEqualToUTF8CString(property_name_ref, "method")) {
+ result.method = Method.which(
+ JSC.JSValue.fromRef(init_ref).get(ctx.ptr(), "method").?.getZigString(ctx.ptr()).slice(),
+ ) orelse Method.GET;
+ }
+ },
else => {},
}
}
@@ -1681,50 +2139,83 @@ pub const Body = struct {
promise: ?JSValue = null,
global: *JSGlobalObject,
task: ?*anyopaque = null,
+ deinit: bool = false,
};
pub const Value = union(Tag) {
- ArrayBuffer: ArrayBuffer,
- String: string,
- Empty: u0,
- Unconsumed: u0,
+ Blob: Blob,
Locked: PendingValue,
+ Used: void,
+ Empty: void,
pub const Tag = enum {
- ArrayBuffer,
- String,
- Empty,
- Unconsumed,
+ Blob,
Locked,
+ Used,
+ Empty,
};
- pub fn length(value: *const Value) usize {
- switch (value.*) {
- .ArrayBuffer => |buf| {
- return buf.ptr[buf.offset..buf.byte_len].len;
- },
- .String => |str| {
- return str.len;
+ pub const empty = Value{ .Empty = .{} };
+ pub fn slice(this: Value) []const u8 {
+ return switch (this) {
+ .Blob => this.Blob.sharedView(),
+ else => "",
+ };
+ }
+
+ pub fn use(this: *Value) Blob {
+ switch (this.*) {
+ .Blob => {
+ var new_blob = this.Blob;
+ this.* = .{ .Used = .{} };
+ return new_blob;
},
else => {
- return 0;
+ return Blob.initEmpty(undefined);
},
}
}
+
+ pub fn deinit(this: *Value) void {
+ const tag = @as(Tag, this.*);
+ if (tag == .Locked) {
+ this.Locked.deinit = true;
+ return;
+ }
+
+ if (tag == .Blob) {
+ this.Blob.deinit();
+ this.* = Value.empty;
+ }
+ }
+
+ pub fn clone(this: Value, allocator: std.mem.Allocator) !Value {
+ if (this == .Blob) {
+ return Value{ .Blob = try this.Blob.dupe(allocator) };
+ }
+
+ return Value{ .Empty = .{} };
+ }
};
pub fn @"404"(_: js.JSContextRef) Body {
- return Body{ .init = Init{
- .headers = null,
- .status_code = 404,
- }, .value = .{ .Empty = 0 } };
+ return Body{
+ .init = Init{
+ .headers = null,
+ .status_code = 404,
+ },
+ .value = Value.empty,
+ };
}
pub fn @"200"(_: js.JSContextRef) Body {
- return Body{ .init = Init{
- .headers = null,
- .status_code = 200,
- }, .value = .{ .Empty = 0 } };
+ return Body{
+ .init = Init{
+ .headers = null,
+ .status_code = 200,
+ },
+ .value = Value.empty,
+ };
}
pub fn extract(ctx: js.JSContextRef, body_ref: js.JSObjectRef, exception: js.ExceptionRef) Body {
@@ -1755,74 +2246,26 @@ pub const Body = struct {
init_ref: js.JSValueRef,
exception: js.ExceptionRef,
) Body {
- var body = Body{ .init = Init{ .headers = null, .status_code = 200 }, .value = .{ .Empty = 0 } };
+ var body = Body{
+ .init = Init{ .headers = null, .status_code = 200 },
+ };
const value = JSC.JSValue.fromRef(body_ref);
- switch (value.jsType()) {
- JSC.JSValue.JSType.String,
- JSC.JSValue.JSType.StringObject,
- JSC.JSValue.JSType.DerivedStringObject,
- => {
- var allocator = getAllocator(ctx);
+ var allocator = getAllocator(ctx);
- if (comptime has_init) {
- if (Init.init(allocator, ctx, init_ref.?)) |maybeInit| {
- if (maybeInit) |init_| {
- body.init = init_;
- }
- } else |_| {}
+ if (comptime has_init) {
+ if (Init.init(allocator, ctx, init_ref.?)) |maybeInit| {
+ if (maybeInit) |init_| {
+ body.init = init_;
}
- var zig_str = JSC.ZigString.init("");
- value.toZigString(&zig_str, ctx.ptr());
-
- if (zig_str.len == 0) {
- body.value = .{ .String = "" };
- return body;
- }
-
- body.value = Value{
- .String = std.fmt.allocPrint(default_allocator, "{}", .{zig_str}) catch unreachable,
- };
- body.ptr_allocator = default_allocator;
- // body.ptr = body.
- // body.len = body.value.String.len;str.characters8()[0..len] };
-
- return body;
- },
-
- JSC.JSValue.JSType.ArrayBuffer,
- JSC.JSValue.JSType.Int8Array,
- JSC.JSValue.JSType.Uint8Array,
- JSC.JSValue.JSType.Uint8ClampedArray,
- JSC.JSValue.JSType.Int16Array,
- JSC.JSValue.JSType.Uint16Array,
- JSC.JSValue.JSType.Int32Array,
- JSC.JSValue.JSType.Uint32Array,
- JSC.JSValue.JSType.Float32Array,
- JSC.JSValue.JSType.Float64Array,
- JSC.JSValue.JSType.BigInt64Array,
- JSC.JSValue.JSType.BigUint64Array,
- JSC.JSValue.JSType.DataView,
- => {
- var allocator = getAllocator(ctx);
+ } else |_| {}
+ }
- if (comptime has_init) {
- if (Init.init(allocator, ctx, init_ref.?)) |maybeInit| {
- if (maybeInit) |init_| {
- body.init = init_;
- }
- } else |_| {}
- }
- body.value = Value{ .ArrayBuffer = value.asArrayBuffer(ctx.ptr()).? };
- body.ptr = body.value.ArrayBuffer.ptr[body.value.ArrayBuffer.offset..body.value.ArrayBuffer.byte_len].ptr;
- body.len = body.value.ArrayBuffer.ptr[body.value.ArrayBuffer.offset..body.value.ArrayBuffer.byte_len].len;
+ body.value = .{
+ .Blob = Blob.fromJS(ctx.ptr(), value) catch {
+ JSC.JSError(allocator, "Out of memory", .{}, ctx, exception);
return body;
},
- else => {},
- }
-
- if (exception == null) {
- JSError(getAllocator(ctx), "Body must be a string or a TypedArray (for now)", .{}, ctx, exception);
- }
+ };
return body;
}
@@ -1830,9 +2273,44 @@ pub const Body = struct {
// https://developer.mozilla.org/en-US/docs/Web/API/Request
pub const Request = struct {
- request_context: *RequestContext,
- url_string_ref: js.JSStringRef = null,
- headers: ?Headers = null,
+ url: ZigString = ZigString.Empty,
+ headers: ?*Headers = null,
+ body: Body.Value = Body.Value{ .Empty = .{} },
+ method: Method = Method.GET,
+
+ pub fn fromRequestContext(ctx: *RequestContext) !Request {
+ var headers_ = bun.default_allocator.create(Headers) catch unreachable;
+ var req = Request{
+ .url = ZigString.init(std.mem.span(ctx.getFullURL())),
+ .body = Body.Value.empty,
+ .method = ctx.method,
+ .headers = headers_,
+ };
+ headers_.* = Headers.fromRequestCtx(bun.default_allocator, ctx) catch unreachable;
+ req.url.mark();
+ return req;
+ }
+
+ pub fn mimeType(this: *const Request) string {
+ if (this.headers) |headers| {
+ // Remember, we always lowercase it
+ // hopefully doesn't matter here tho
+ if (headers.getHeaderIndex("content-type")) |content_type| {
+ return headers.asStr(headers.entries.items(.value)[content_type]);
+ }
+ }
+
+ switch (this.body) {
+ .Blob => |blob| {
+ if (blob.content_type.len > 0) {
+ return blob.content_type;
+ }
+
+ return MimeType.other.value;
+ },
+ .Used, .Locked, .Empty => return MimeType.other.value,
+ }
+ }
pub const Class = NewClass(
Request,
@@ -1840,7 +2318,17 @@ pub const Request = struct {
.name = "Request",
.read_only = true,
},
- .{},
+ .{ .finalize = finalize, .constructor = constructor, .text = .{
+ .rfn = Request.getText,
+ }, .json = .{
+ .rfn = Request.getJSON,
+ }, .arrayBuffer = .{
+ .rfn = Request.getArrayBuffer,
+ }, .blob = .{
+ .rfn = Request.getBlob,
+ }, .clone = .{
+ .rfn = Request.doClone,
+ } },
.{
.@"cache" = .{
.@"get" = getCache,
@@ -1886,6 +2374,10 @@ pub const Request = struct {
.@"get" = getUrl,
.@"ro" = true,
},
+ .@"bodyUsed" = .{
+ .@"get" = getBodyUsed,
+ .@"ro" = true,
+ },
},
);
@@ -1924,10 +2416,12 @@ pub const Request = struct {
_: js.ExceptionRef,
) js.JSValueRef {
if (this.headers == null) {
- this.headers = Headers.fromRequestCtx(getAllocator(ctx), this.request_context) catch unreachable;
+ var headers = getAllocator(ctx).create(Headers) catch unreachable;
+ headers.* = Headers.empty(getAllocator(ctx));
+ this.headers = headers;
}
- return Headers.Class.make(ctx, &this.headers.?);
+ return Headers.Class.make(ctx, this.headers.?);
}
pub fn getIntegrity(
_: *Request,
@@ -1945,7 +2439,7 @@ pub const Request = struct {
_: js.JSStringRef,
_: js.ExceptionRef,
) js.JSValueRef {
- const string_contents: string = switch (this.request_context.method) {
+ const string_contents: string = switch (this.method) {
.GET => Properties.UTF8.GET,
.HEAD => Properties.UTF8.HEAD,
.PATCH => Properties.UTF8.PATCH,
@@ -1955,7 +2449,7 @@ pub const Request = struct {
else => "",
};
- return ZigString.init(string_contents).toValueGC(ctx.ptr()).asRef();
+ return ZigString.init(string_contents).toValue(ctx.ptr()).asRef();
}
pub fn getMode(
@@ -1965,8 +2459,13 @@ pub const Request = struct {
_: js.JSStringRef,
_: js.ExceptionRef,
) js.JSValueRef {
- return ZigString.init(Properties.UTF8.navigate).toValueGC(ctx.ptr()).asRef();
+ return ZigString.init(Properties.UTF8.navigate).toValue(ctx.ptr()).asRef();
}
+
+ pub fn finalize(this: *Request) void {
+ bun.default_allocator.destroy(this);
+ }
+
pub fn getRedirect(
_: *Request,
ctx: js.JSContextRef,
@@ -1983,11 +2482,13 @@ pub const Request = struct {
_: js.JSStringRef,
_: js.ExceptionRef,
) js.JSValueRef {
- if (this.request_context.header("Referrer")) |referrer| {
- return ZigString.init(referrer).toValueGC(ctx.ptr()).asRef();
- } else {
- return ZigString.init("").toValueGC(ctx.ptr()).asRef();
+ if (this.headers) |headers| {
+ if (headers.getHeaderIndex("referrer")) |i| {
+ return ZigString.init(headers.asStr(headers.entries.get(i).value)).toValueGC(ctx.ptr()).asObjectRef();
+ }
}
+
+ return ZigString.init("").toValueGC(ctx.ptr()).asRef();
}
pub fn getReferrerPolicy(
_: *Request,
@@ -2005,14 +2506,146 @@ pub const Request = struct {
_: js.JSStringRef,
_: js.ExceptionRef,
) js.JSValueRef {
- if (this.url_string_ref == null) {
- this.url_string_ref = js.JSStringCreateWithUTF8CString(this.request_context.getFullURL());
+ return this.url.toValueGC(ctx.ptr()).asObjectRef();
+ }
+
+ pub fn constructor(
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ _: js.ExceptionRef,
+ ) js.JSObjectRef {
+ var request = Request{};
+
+ switch (arguments.len) {
+ 0 => {},
+ 1 => {
+ request.url = JSC.JSValue.fromRef(arguments[0]).getZigString(ctx.ptr());
+ },
+ else => {
+ request.url = JSC.JSValue.fromRef(arguments[0]).getZigString(ctx.ptr());
+
+ if (Body.Init.init(getAllocator(ctx), ctx, arguments[1]) catch null) |req_init| {
+ request.headers = getAllocator(ctx).create(Headers) catch unreachable;
+ if (req_init.headers) |headers_| {
+ request.headers.?.* = headers_;
+ }
+
+ request.method = req_init.method;
+ }
+
+ if (JSC.JSValue.fromRef(arguments[1]).get(ctx.ptr(), "body")) |body_| {
+ if (Blob.fromJS(ctx.ptr(), body_) catch null) |blob| {
+ request.body = Body.Value{ .Blob = blob };
+ }
+ }
+ },
+ }
+
+ var request_ = getAllocator(ctx).create(Request) catch unreachable;
+ request_.* = request;
+ return Request.Class.make(
+ ctx,
+ request_,
+ );
+ }
+
+ pub fn getBodyUsed(
+ this: *Request,
+ _: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+ ) js.JSValueRef {
+ return JSC.JSValue.jsBoolean(this.body == .Used).asRef();
+ }
+
+ pub usingnamespace BlobInterface(@This());
+
+ pub fn doClone(
+ this: *Request,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ _: []const js.JSValueRef,
+ _: js.ExceptionRef,
+ ) js.JSValueRef {
+ var cloned = this.clone(getAllocator(ctx));
+ return Request.Class.make(ctx, cloned);
+ }
+
+ pub fn cloneInto(this: *const Request, req: *Request, allocator: std.mem.Allocator) void {
+ req.* = Request{
+ .body = this.body.clone(allocator) catch unreachable,
+ .url = ZigString.init(allocator.dupe(u8, this.url.slice()) catch unreachable),
+ };
+ if (this.headers) |head| {
+ var new_headers = allocator.create(Headers) catch unreachable;
+ head.clone(new_headers) catch unreachable;
+ req.headers = new_headers;
}
+ }
- return js.JSValueMakeString(ctx, this.url_string_ref);
+ pub fn clone(this: *const Request, allocator: std.mem.Allocator) *Request {
+ var req = allocator.create(Request) catch unreachable;
+ this.cloneInto(req, allocator);
+ return req;
}
};
+fn BlobInterface(comptime Type: type) type {
+ return struct {
+ pub fn getText(
+ this: *Type,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ _: []const js.JSValueRef,
+ _: js.ExceptionRef,
+ ) js.JSValueRef {
+ var blob = this.body.use();
+ return blob.getTextTransfer(ctx);
+ }
+
+ pub fn getJSON(
+ this: *Type,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ _: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ var blob = this.body.use();
+ return blob.getJSON(ctx, null, null, &.{}, exception);
+ }
+ pub fn getArrayBuffer(
+ this: *Type,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ _: []const js.JSValueRef,
+ _: js.ExceptionRef,
+ ) js.JSValueRef {
+ var blob = this.body.use();
+ return blob.getArrayBufferTransfer(ctx);
+ }
+
+ pub fn getBlob(
+ this: *Type,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ _: []const js.JSValueRef,
+ _: js.ExceptionRef,
+ ) js.JSValueRef {
+ var blob = this.body.use();
+ var ptr = getAllocator(ctx).create(Blob) catch unreachable;
+ ptr.* = blob;
+ return JSC.JSPromise.resolvedPromiseValue(ctx.ptr(), JSValue.fromRef(Blob.Class.make(ctx, ptr))).asObjectRef();
+ }
+ };
+}
+
// https://github.com/WebKit/WebKit/blob/main/Source/WebCore/workers/service/FetchEvent.h
pub const FetchEvent = struct {
started_waiting_at: u64 = 0,
@@ -2083,7 +2716,12 @@ pub const FetchEvent = struct {
_: js.JSStringRef,
_: js.ExceptionRef,
) js.JSValueRef {
- return Request.Class.make(ctx, &this.request);
+ var req = getAllocator(ctx).create(Request) catch unreachable;
+ req.* = Request.fromRequestContext(this.request_context) catch unreachable;
+ return Request.Class.make(
+ ctx,
+ req,
+ );
}
// https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/respondWith
@@ -2114,7 +2752,7 @@ pub const FetchEvent = struct {
if (this.pending_promise) |promise| {
var status = promise.status(globalThis.vm());
- if (status == .Pending) {
+ while (status == .Pending) {
VirtualMachine.vm.tick();
status = promise.status(globalThis.vm());
}
@@ -2198,7 +2836,10 @@ pub const FetchEvent = struct {
this.request_context.appendHeader("Content-Type", response.mimeType(this.request_context));
}
- const content_length_ = content_length orelse response.body.value.length();
+ var blob = response.body.value.use();
+ // defer blob.deinit();
+
+ const content_length_ = content_length orelse blob.size;
if (content_length_ == 0) {
this.request_context.sendNoContent() catch return js.JSValueMakeUndefined(ctx);
@@ -2206,46 +2847,19 @@ pub const FetchEvent = struct {
}
if (FeatureFlags.strong_etags_for_built_files) {
- switch (response.body.value) {
- .ArrayBuffer => |buf| {
- const did_send = this.request_context.writeETag(buf.ptr[buf.offset..buf.byte_len]) catch false;
- if (did_send) return js.JSValueMakeUndefined(ctx);
- },
- .String => |str| {
- const did_send = this.request_context.writeETag(str) catch false;
- if (did_send) {
- // defer getAllocator(ctx).destroy(str.ptr);
- return js.JSValueMakeUndefined(ctx);
- }
- },
- else => unreachable,
+ const did_send = this.request_context.writeETag(blob.sharedView()) catch false;
+ if (did_send) {
+ // defer getAllocator(ctx).destroy(str.ptr);
+ return js.JSValueMakeUndefined(ctx);
}
}
defer this.request_context.done();
- defer {
- if (response.body.ptr_allocator) |alloc| {
- if (response.body.ptr) |ptr| {
- alloc.free(ptr[0..response.body.len]);
- }
-
- response.body.ptr_allocator = null;
- }
- }
this.request_context.writeStatusSlow(response.body.init.status_code) catch return js.JSValueMakeUndefined(ctx);
this.request_context.prepareToSendBody(content_length_, false) catch return js.JSValueMakeUndefined(ctx);
- switch (response.body.value) {
- .ArrayBuffer => |buf| {
- this.request_context.writeBodyBuf(buf.ptr[buf.offset..buf.byte_len]) catch return js.JSValueMakeUndefined(ctx);
- },
- .String => |str| {
- // defer getAllocator(ctx).destroy(str.ptr);
- this.request_context.writeBodyBuf(str) catch return js.JSValueMakeUndefined(ctx);
- },
- else => unreachable,
- }
+ this.request_context.writeBodyBuf(blob.sharedView()) catch return js.JSValueMakeUndefined(ctx);
return js.JSValueMakeUndefined(ctx);
}
diff --git a/src/js_ast.zig b/src/js_ast.zig
index d203586fb..e2edca77c 100644
--- a/src/js_ast.zig
+++ b/src/js_ast.zig
@@ -27,6 +27,7 @@ const AutoHashMap = _hash_map.AutoHashMap;
const StringHashMapUnmanaged = _hash_map.StringHashMapUnmanaged;
const is_bindgen = std.meta.globalOption("bindgen", bool) orelse false;
const ComptimeStringMap = bun.ComptimeStringMap;
+const JSPrinter = @import("./js_printer.zig");
pub fn NewBaseStore(comptime Union: anytype, comptime count: usize) type {
var max_size = 0;
var max_align = 1;
@@ -2193,6 +2194,57 @@ pub const Expr = struct {
loc: logger.Loc,
data: Data,
+ pub fn fromBlob(
+ blob: *const JSC.WebCore.Blob,
+ allocator: std.mem.Allocator,
+ mime_type_: ?HTTP.MimeType,
+ log: *logger.Log,
+ loc: logger.Loc,
+ ) !Expr {
+ var bytes = blob.sharedView();
+
+ const mime_type = mime_type_ orelse HTTP.MimeType.init(blob.content_type);
+
+ if (mime_type.category == .json) {
+ var source = logger.Source.initPathString("fetch.json", bytes);
+ var out_expr = JSONParser.ParseJSONForMacro(&source, log, allocator) catch {
+ return error.MacroFailed;
+ };
+ out_expr.loc = loc;
+
+ switch (out_expr.data) {
+ .e_object => {
+ out_expr.data.e_object.was_originally_macro = true;
+ },
+ .e_array => {
+ out_expr.data.e_array.was_originally_macro = true;
+ },
+ else => {},
+ }
+
+ return out_expr;
+ }
+
+ if (mime_type.category.isTextLike()) {
+ var output = MutableString.initEmpty(allocator);
+ output = try JSPrinter.quoteForJSON(bytes, output, true);
+ var list = output.toOwnedSlice();
+ // remove the quotes
+ if (list.len > 0) {
+ list = list[1 .. list.len - 1];
+ }
+ return Expr.init(E.String, E.String{ .utf8 = list }, loc);
+ }
+
+ return Expr.init(
+ E.String,
+ E.String{
+ .utf8 = try JSC.ZigString.init(bytes).toBase64DataURL(allocator),
+ },
+ loc,
+ );
+ }
+
pub inline fn initIdentifier(ref: Ref, loc: logger.Loc) Expr {
return Expr{
.loc = loc,
@@ -7713,98 +7765,60 @@ pub const Macro = struct {
if (_entry.found_existing) {
return _entry.value_ptr.*;
}
-
- if (JSCBase.GetJSPrivateData(JSNode, value.asObjectRef())) |node| {
- _entry.value_ptr.* = node.toExpr();
- node.visited = true;
- node.updateSymbolsMap(Visitor, this.visitor);
- return _entry.value_ptr.*;
+ var private_data = JSCBase.JSPrivateDataPtr.from(JSC.C.JSObjectGetPrivate(value.asObjectRef()).?);
+ var blob_: ?JSC.WebCore.Blob = null;
+ var mime_type: ?HTTP.MimeType = null;
+
+ switch (private_data.tag()) {
+ .JSNode => {
+ var node = private_data.as(JSNode);
+ _entry.value_ptr.* = node.toExpr();
+ node.visited = true;
+ node.updateSymbolsMap(Visitor, this.visitor);
+ return _entry.value_ptr.*;
+ },
+ .ResolveError, .BuildError => {
+ this.macro.vm.defaultErrorHandler(value, null);
+ return error.MacroFailed;
+ },
+ .Request => {
+ var req: *JSC.WebCore.Request = private_data.as(JSC.WebCore.Request);
+ blob_ = req.body.use();
+ mime_type = HTTP.MimeType.init(req.mimeType());
+ },
+ .Response => {
+ var res: *JSC.WebCore.Response = private_data.as(JSC.WebCore.Response);
+ blob_ = res.body.use();
+ mime_type =
+ HTTP.MimeType.init(res.mimeType(null));
+ },
+ .Blob => {
+ var blob = private_data.as(JSC.WebCore.Blob);
+ blob_ = blob.*;
+ blob.* = JSC.WebCore.Blob.initEmpty(blob.globalThis);
+ },
+ else => {},
}
- if (JSCBase.GetJSPrivateData(JSC.BuildError, value.asObjectRef()) != null) {
- this.macro.vm.defaultErrorHandler(value, null);
- return error.MacroFailed;
- }
+ if (blob_) |*blob| {
+ const out_expr = Expr.fromBlob(
+ blob,
+ this.allocator,
+ mime_type,
+ this.log,
+ this.caller.loc,
+ ) catch {
+ blob.deinit();
+ return error.MacroFailed;
+ };
+ if (out_expr.data == .e_string) {
+ blob.deinit();
+ }
- if (JSCBase.GetJSPrivateData(JSC.ResolveError, value.asObjectRef()) != null) {
- this.macro.vm.defaultErrorHandler(value, null);
- return error.MacroFailed;
+ return out_expr;
}
- // alright this is insane
- if (JSCBase.GetJSPrivateData(JSC.WebCore.Response, value.asObjectRef())) |response| {
- switch (response.body.value) {
- .Unconsumed => {
- if (response.body.len > 0) {
- var mime_type = HTTP.MimeType.other;
- if (response.body.init.headers) |headers| {
- if (headers.getHeaderIndex("content-type")) |content_type| {
- mime_type = HTTP.MimeType.init(headers.asStr(headers.entries.get(content_type).value));
- }
- }
-
- if (response.body.ptr) |_ptr| {
- var zig_string = JSC.ZigString.init(_ptr[0..response.body.len]);
-
- if (mime_type.category == .json) {
- var source = logger.Source.initPathString("fetch.json", zig_string.slice());
- var out_expr = JSONParser.ParseJSONForMacro(&source, this.log, this.allocator) catch {
- return error.MacroFailed;
- };
- switch (out_expr.data) {
- .e_object => {
- out_expr.data.e_object.was_originally_macro = true;
- },
- .e_array => {
- out_expr.data.e_array.was_originally_macro = true;
- },
- else => {},
- }
-
- return out_expr;
- }
-
- if (mime_type.category.isTextLike()) {
- zig_string.detectEncoding();
- const utf8 = if (zig_string.is16Bit())
- zig_string.toSlice(this.allocator).slice()
- else
- zig_string.slice();
-
- return Expr.init(E.String, E.String{ .utf8 = utf8 }, this.caller.loc);
- }
-
- return Expr.init(E.String, E.String{ .utf8 = zig_string.toBase64DataURL(this.allocator) catch unreachable }, this.caller.loc);
- }
- }
-
- return Expr.init(E.String, E.String.empty, this.caller.loc);
- },
-
- .String => |str| {
- var zig_string = JSC.ZigString.init(str);
-
- zig_string.detectEncoding();
- if (zig_string.is16Bit()) {
- var slice = zig_string.toSlice(this.allocator);
- if (response.body.ptr_allocator) |allocator| response.body.deinit(allocator);
- return Expr.init(E.String, E.String{ .utf8 = slice.slice() }, this.caller.loc);
- }
-
- return Expr.init(E.String, E.String{ .utf8 = zig_string.slice() }, this.caller.loc);
- },
- .ArrayBuffer => |buffer| {
- return Expr.init(
- E.String,
- E.String{ .utf8 = JSC.ZigString.init(buffer.slice()).toBase64DataURL(this.allocator) catch unreachable },
- this.caller.loc,
- );
- },
- else => {
- return Expr.init(E.String, E.String.empty, this.caller.loc);
- },
- }
- }
+ return Expr.init(E.String, E.String.empty, this.caller.loc);
},
.Boolean => {
diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig
index 83b69692a..8d6032b27 100644
--- a/src/resolver/resolver.zig
+++ b/src/resolver/resolver.zig
@@ -41,7 +41,7 @@ const NodeModuleBundle = @import("../node_module_bundle.zig").NodeModuleBundle;
pub fn isPackagePath(path: string) bool {
// this could probably be flattened into something more optimized
- return path[0] != '/' and !strings.startsWith(path, "./") and !strings.startsWith(path, "../") and !strings.eql(path, ".") and !strings.eql(path, "..");
+ return path.len > 0 and path[0] != '/' and !strings.startsWith(path, "./") and !strings.startsWith(path, "../") and !strings.eql(path, ".") and !strings.eql(path, "..");
}
pub const SideEffectsData = struct {
diff --git a/src/string_joiner.zig b/src/string_joiner.zig
index d49c32867..03d63364e 100644
--- a/src/string_joiner.zig
+++ b/src/string_joiner.zig
@@ -21,27 +21,35 @@ const Joinable = struct {
last_byte: u8 = 0,
len: usize = 0,
+use_pool: bool = true,
+node_allocator: std.mem.Allocator = undefined,
head: ?*Joinable.Pool.Node = null,
tail: ?*Joinable.Pool.Node = null,
pub fn done(this: *Joiner, allocator: std.mem.Allocator) ![]u8 {
- var slice = try allocator.alloc(u8, this.cap);
+ if (this.head == null) {
+ var out: []u8 = &[_]u8{};
+ return out;
+ }
+
+ var slice = try allocator.alloc(u8, this.len);
var remaining = slice;
var el_ = this.head;
while (el_) |join| {
- const to_join = join.data.slice[join.offset..];
+ const to_join = join.data.slice[join.data.offset..];
@memcpy(remaining.ptr, to_join.ptr, to_join.len);
- remaining = remaining[to_join.len..];
+ remaining = remaining[@minimum(remaining.len, to_join.len)..];
var prev = join;
el_ = join.next;
if (prev.data.needs_deinit) {
- prev.data.allocator.free(join.data.slice);
+ prev.data.allocator.free(prev.data.slice);
prev.data = Joinable{};
}
- prev.release();
+
+ if (this.use_pool) prev.release();
}
return slice[0 .. slice.len - remaining.len];
@@ -60,12 +68,19 @@ pub fn append(this: *Joiner, slice: string, offset: u32, allocator: ?std.mem.All
const data = slice[offset..];
this.len += @truncate(u32, data.len);
- var new_tail = Joinable.Pool.get(default_allocator);
- new_tail.data = Joinable{
- .offset = offset,
- .allocator = allocator orelse undefined,
- .needs_deinit = allocator != null,
- .slice = slice,
+ var new_tail = if (this.use_pool)
+ Joinable.Pool.get(default_allocator)
+ else
+ (this.node_allocator.create(Joinable.Pool.Node) catch unreachable);
+
+ new_tail.* = .{
+ .allocator = default_allocator,
+ .data = Joinable{
+ .offset = @truncate(u31, offset),
+ .allocator = allocator orelse undefined,
+ .needs_deinit = allocator != null,
+ .slice = slice,
+ },
};
var tail = this.tail orelse {