aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/bun.js/api')
-rw-r--r--src/bun.js/api/FFI.h252
-rw-r--r--src/bun.js/api/bun.zig3007
-rw-r--r--src/bun.js/api/ffi.zig1436
-rw-r--r--src/bun.js/api/html_rewriter.zig1886
-rw-r--r--src/bun.js/api/libtcc1.a.macos-aarch64bin0 -> 29988 bytes
-rw-r--r--src/bun.js/api/libtcc1.c606
-rw-r--r--src/bun.js/api/router.zig541
-rw-r--r--src/bun.js/api/server.zig1844
-rw-r--r--src/bun.js/api/transpiler.zig1304
9 files changed, 10876 insertions, 0 deletions
diff --git a/src/bun.js/api/FFI.h b/src/bun.js/api/FFI.h
new file mode 100644
index 000000000..e17e15d30
--- /dev/null
+++ b/src/bun.js/api/FFI.h
@@ -0,0 +1,252 @@
+// This file is part of Bun!
+// You can find the original source:
+// https://github.com/Jarred-Sumner/bun/blob/main/src/bun.js/api/FFI.h#L2
+//
+// clang-format off
+// This file is only compatible with 64 bit CPUs
+// It must be kept in sync with JSCJSValue.h
+// https://github.com/Jarred-Sumner/WebKit/blob/72c2052b781cbfd4af867ae79ac9de460e392fba/Source/JavaScriptCore/runtime/JSCJSValue.h#L455-L458
+#ifdef IS_CALLBACK
+#define INJECT_BEFORE int c = 500; // This is a callback, so we need to inject code before the call
+#endif
+#define IS_BIG_ENDIAN 0
+#define USE_JSVALUE64 1
+#define USE_JSVALUE32_64 0
+
+
+// /* 7.18.1.1 Exact-width integer types */
+typedef unsigned char uint8_t;
+typedef signed char int8_t;
+typedef short int16_t;
+typedef unsigned short uint16_t;
+typedef int int32_t;
+typedef unsigned int uint32_t;
+typedef long long int64_t;
+typedef unsigned long long uint64_t;
+typedef unsigned long long size_t;
+typedef long intptr_t;
+typedef uint64_t uintptr_t;
+typedef _Bool bool;
+
+#define true 1
+#define false 0
+
+
+#ifdef INJECT_BEFORE
+// #include <stdint.h>
+#endif
+// #include <tcclib.h>
+
+// This value is 2^49, used to encode doubles such that the encoded value will
+// begin with a 15-bit pattern within the range 0x0002..0xFFFC.
+#define DoubleEncodeOffsetBit 49
+#define DoubleEncodeOffset (1ll << DoubleEncodeOffsetBit)
+#define OtherTag 0x2
+#define BoolTag 0x4
+#define UndefinedTag 0x8
+#define TagValueFalse (OtherTag | BoolTag | false)
+#define TagValueTrue (OtherTag | BoolTag | true)
+#define TagValueUndefined (OtherTag | UndefinedTag)
+#define TagValueNull (OtherTag)
+#define NotCellMask NumberTag | OtherTag
+
+#define MAX_INT32 2147483648
+#define MAX_INT52 9007199254740991
+
+// If all bits in the mask are set, this indicates an integer number,
+// if any but not all are set this value is a double precision number.
+#define NumberTag 0xfffe000000000000ll
+
+typedef void* JSCell;
+
+typedef union EncodedJSValue {
+ int64_t asInt64;
+
+#if USE_JSVALUE64
+ JSCell *ptr;
+#endif
+
+#if IS_BIG_ENDIAN
+ struct {
+ int32_t tag;
+ int32_t payload;
+ } asBits;
+#else
+ struct {
+ int32_t payload;
+ int32_t tag;
+ } asBits;
+#endif
+
+ void* asPtr;
+ double asDouble;
+} EncodedJSValue;
+
+EncodedJSValue ValueUndefined = { TagValueUndefined };
+EncodedJSValue ValueTrue = { TagValueTrue };
+
+typedef void* JSContext;
+
+// Bun_FFI_PointerOffsetToArgumentsList is injected into the build
+// The value is generated in `make sizegen`
+// The value is 6.
+// On ARM64_32, the value is something else but it really doesn't matter for our case
+// However, I don't want this to subtly break amidst future upgrades to JavaScriptCore
+#define LOAD_ARGUMENTS_FROM_CALL_FRAME \
+ int64_t *argsPtr = (int64_t*)((size_t*)callFrame + Bun_FFI_PointerOffsetToArgumentsList)
+
+
+#ifdef IS_CALLBACK
+extern int64_t bun_call(JSContext, void* func, void* thisValue, size_t len, const EncodedJSValue args[], void* exception);
+JSContext cachedJSContext;
+void* cachedCallbackFunction;
+#endif
+
+static bool JSVALUE_IS_CELL(EncodedJSValue val) __attribute__((__always_inline__));
+static bool JSVALUE_IS_INT32(EncodedJSValue val) __attribute__((__always_inline__));
+static bool JSVALUE_IS_NUMBER(EncodedJSValue val) __attribute__((__always_inline__));
+
+static uint64_t JSVALUE_TO_UINT64(void* globalObject, EncodedJSValue value) __attribute__((__always_inline__));
+static int64_t JSVALUE_TO_INT64(EncodedJSValue value) __attribute__((__always_inline__));
+uint64_t JSVALUE_TO_UINT64_SLOW(void* globalObject, EncodedJSValue value);
+int64_t JSVALUE_TO_INT64_SLOW(EncodedJSValue value);
+
+EncodedJSValue UINT64_TO_JSVALUE_SLOW(void* globalObject, uint64_t val);
+EncodedJSValue INT64_TO_JSVALUE_SLOW(void* globalObject, int64_t val);
+static EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val) __attribute__((__always_inline__));
+static EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val) __attribute__((__always_inline__));
+
+
+static EncodedJSValue INT32_TO_JSVALUE(int32_t val) __attribute__((__always_inline__));
+static EncodedJSValue DOUBLE_TO_JSVALUE(double val) __attribute__((__always_inline__));
+static EncodedJSValue FLOAT_TO_JSVALUE(float val) __attribute__((__always_inline__));
+static EncodedJSValue BOOLEAN_TO_JSVALUE(bool val) __attribute__((__always_inline__));
+static EncodedJSValue PTR_TO_JSVALUE(void* ptr) __attribute__((__always_inline__));
+
+static void* JSVALUE_TO_PTR(EncodedJSValue val) __attribute__((__always_inline__));
+static int32_t JSVALUE_TO_INT32(EncodedJSValue val) __attribute__((__always_inline__));
+static float JSVALUE_TO_FLOAT(EncodedJSValue val) __attribute__((__always_inline__));
+static double JSVALUE_TO_DOUBLE(EncodedJSValue val) __attribute__((__always_inline__));
+static bool JSVALUE_TO_BOOL(EncodedJSValue val) __attribute__((__always_inline__));
+
+static bool JSVALUE_IS_CELL(EncodedJSValue val) {
+ return !(val.asInt64 & NotCellMask);
+}
+
+static bool JSVALUE_IS_INT32(EncodedJSValue val) {
+ return (val.asInt64 & NumberTag) == NumberTag;
+}
+
+static bool JSVALUE_IS_NUMBER(EncodedJSValue val) {
+ return val.asInt64 & NumberTag;
+}
+
+
+static void* JSVALUE_TO_PTR(EncodedJSValue val) {
+ // must be a double
+ return (void*)(val.asInt64 - DoubleEncodeOffset);
+}
+
+static EncodedJSValue PTR_TO_JSVALUE(void* ptr) {
+ EncodedJSValue val;
+ val.asInt64 = (int64_t)ptr + DoubleEncodeOffset;
+ return val;
+}
+
+static int32_t JSVALUE_TO_INT32(EncodedJSValue val) {
+ return val.asInt64;
+}
+
+static EncodedJSValue INT32_TO_JSVALUE(int32_t val) {
+ EncodedJSValue res;
+ res.asInt64 = NumberTag | (uint32_t)val;
+ return res;
+}
+
+
+static EncodedJSValue DOUBLE_TO_JSVALUE(double val) {
+ EncodedJSValue res;
+ res.asDouble = val;
+ res.asInt64 += DoubleEncodeOffset;
+ return res;
+}
+
+static EncodedJSValue FLOAT_TO_JSVALUE(float val) {
+ return DOUBLE_TO_JSVALUE((double)val);
+}
+
+static EncodedJSValue BOOLEAN_TO_JSVALUE(bool val) {
+ EncodedJSValue res;
+ res.asInt64 = val ? TagValueTrue : TagValueFalse;
+ return res;
+}
+
+
+static double JSVALUE_TO_DOUBLE(EncodedJSValue val) {
+ val.asInt64 -= DoubleEncodeOffset;
+ return val.asDouble;
+}
+
+static float JSVALUE_TO_FLOAT(EncodedJSValue val) {
+ return (float)JSVALUE_TO_DOUBLE(val);
+}
+
+static bool JSVALUE_TO_BOOL(EncodedJSValue val) {
+ return val.asInt64 == TagValueTrue;
+}
+
+
+static uint64_t JSVALUE_TO_UINT64(void* globalObject, EncodedJSValue value) {
+ if (JSVALUE_IS_INT32(value)) {
+ return (uint64_t)JSVALUE_TO_INT32(value);
+ }
+
+ if (JSVALUE_IS_NUMBER(value)) {
+ return (uint64_t)JSVALUE_TO_DOUBLE(value);
+ }
+
+ return JSVALUE_TO_UINT64_SLOW(globalObject, value);
+}
+static int64_t JSVALUE_TO_INT64(EncodedJSValue value) {
+ if (JSVALUE_IS_INT32(value)) {
+ return (int64_t)JSVALUE_TO_INT32(value);
+ }
+
+ if (JSVALUE_IS_NUMBER(value)) {
+ return (int64_t)JSVALUE_TO_DOUBLE(value);
+ }
+
+ return JSVALUE_TO_INT64_SLOW(value);
+}
+
+static EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val) {
+ if (val < MAX_INT32) {
+ return INT32_TO_JSVALUE((int32_t)val);
+ }
+
+ if (val < MAX_INT52) {
+ return DOUBLE_TO_JSVALUE((double)val);
+ }
+
+ return UINT64_TO_JSVALUE_SLOW(globalObject, val);
+}
+
+static EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val) {
+ if (val >= -MAX_INT32 && val <= MAX_INT32) {
+ return INT32_TO_JSVALUE((int32_t)val);
+ }
+
+ if (val >= -MAX_INT52 && val <= MAX_INT52) {
+ return DOUBLE_TO_JSVALUE((double)val);
+ }
+
+ return INT64_TO_JSVALUE_SLOW(globalObject, val);
+}
+
+#ifndef IS_CALLBACK
+void* JSFunctionCall(void* globalObject, void* callFrame);
+
+#endif
+
+
+// --- Generated Code ---
diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig
new file mode 100644
index 000000000..c5831ed59
--- /dev/null
+++ b/src/bun.js/api/bun.zig
@@ -0,0 +1,3007 @@
+const Bun = @This();
+const default_allocator = @import("../../global.zig").default_allocator;
+const bun = @import("../../global.zig");
+const Environment = bun.Environment;
+const NetworkThread = @import("http").NetworkThread;
+const Global = bun.Global;
+const strings = bun.strings;
+const string = bun.string;
+const Output = @import("../../global.zig").Output;
+const MutableString = @import("../../global.zig").MutableString;
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const IdentityContext = @import("../../identity_context.zig").IdentityContext;
+const Fs = @import("../../fs.zig");
+const Resolver = @import("../../resolver/resolver.zig");
+const ast = @import("../../import_record.zig");
+const NodeModuleBundle = @import("../../node_module_bundle.zig").NodeModuleBundle;
+const MacroEntryPoint = @import("../../bundler.zig").MacroEntryPoint;
+const logger = @import("../../logger.zig");
+const Api = @import("../../api/schema.zig").Api;
+const options = @import("../../options.zig");
+const Bundler = @import("../../bundler.zig").Bundler;
+const ServerEntryPoint = @import("../../bundler.zig").ServerEntryPoint;
+const js_printer = @import("../../js_printer.zig");
+const js_parser = @import("../../js_parser.zig");
+const js_ast = @import("../../js_ast.zig");
+const hash_map = @import("../../hash_map.zig");
+const http = @import("../../http.zig");
+const NodeFallbackModules = @import("../../node_fallbacks.zig");
+const ImportKind = ast.ImportKind;
+const Analytics = @import("../../analytics/analytics_thread.zig");
+const ZigString = @import("../../jsc.zig").ZigString;
+const Runtime = @import("../../runtime.zig");
+const Router = @import("./router.zig");
+const ImportRecord = ast.ImportRecord;
+const DotEnv = @import("../../env_loader.zig");
+const ParseResult = @import("../../bundler.zig").ParseResult;
+const PackageJSON = @import("../../resolver/package_json.zig").PackageJSON;
+const MacroRemap = @import("../../resolver/package_json.zig").MacroMap;
+const WebCore = @import("../../jsc.zig").WebCore;
+const Request = WebCore.Request;
+const Response = WebCore.Response;
+const Headers = WebCore.Headers;
+const Fetch = WebCore.Fetch;
+const FetchEvent = WebCore.FetchEvent;
+const js = @import("../../jsc.zig").C;
+const JSC = @import("../../jsc.zig");
+const JSError = @import("../base.zig").JSError;
+const d = @import("../base.zig").d;
+const MarkedArrayBuffer = @import("../base.zig").MarkedArrayBuffer;
+const getAllocator = @import("../base.zig").getAllocator;
+const JSValue = @import("../../jsc.zig").JSValue;
+const NewClass = @import("../base.zig").NewClass;
+const Microtask = @import("../../jsc.zig").Microtask;
+const JSGlobalObject = @import("../../jsc.zig").JSGlobalObject;
+const ExceptionValueRef = @import("../../jsc.zig").ExceptionValueRef;
+const JSPrivateDataPtr = @import("../../jsc.zig").JSPrivateDataPtr;
+const ZigConsoleClient = @import("../../jsc.zig").ZigConsoleClient;
+const Node = @import("../../jsc.zig").Node;
+const ZigException = @import("../../jsc.zig").ZigException;
+const ZigStackTrace = @import("../../jsc.zig").ZigStackTrace;
+const ErrorableResolvedSource = @import("../../jsc.zig").ErrorableResolvedSource;
+const ResolvedSource = @import("../../jsc.zig").ResolvedSource;
+const JSPromise = @import("../../jsc.zig").JSPromise;
+const JSInternalPromise = @import("../../jsc.zig").JSInternalPromise;
+const JSModuleLoader = @import("../../jsc.zig").JSModuleLoader;
+const JSPromiseRejectionOperation = @import("../../jsc.zig").JSPromiseRejectionOperation;
+const Exception = @import("../../jsc.zig").Exception;
+const ErrorableZigString = @import("../../jsc.zig").ErrorableZigString;
+const ZigGlobalObject = @import("../../jsc.zig").ZigGlobalObject;
+const VM = @import("../../jsc.zig").VM;
+const JSFunction = @import("../../jsc.zig").JSFunction;
+const Config = @import("../config.zig");
+const URL = @import("../../url.zig").URL;
+const Transpiler = @import("./transpiler.zig");
+const VirtualMachine = @import("../javascript.zig").VirtualMachine;
+const IOTask = JSC.IOTask;
+const zlib = @import("../../zlib.zig");
+
+const is_bindgen = JSC.is_bindgen;
+const max_addressible_memory = std.math.maxInt(u56);
+
+threadlocal var css_imports_list_strings: [512]ZigString = undefined;
+threadlocal var css_imports_list: [512]Api.StringPointer = undefined;
+threadlocal var css_imports_list_tail: u16 = 0;
+threadlocal var css_imports_buf: std.ArrayList(u8) = undefined;
+threadlocal var css_imports_buf_loaded: bool = false;
+
+threadlocal var routes_list_strings: [1024]ZigString = undefined;
+
+pub fn onImportCSS(
+ resolve_result: *const Resolver.Result,
+ import_record: *ImportRecord,
+ origin: URL,
+) void {
+ if (!css_imports_buf_loaded) {
+ css_imports_buf = std.ArrayList(u8).initCapacity(
+ VirtualMachine.vm.allocator,
+ import_record.path.text.len,
+ ) catch unreachable;
+ css_imports_buf_loaded = true;
+ }
+
+ var writer = css_imports_buf.writer();
+ const offset = css_imports_buf.items.len;
+ css_imports_list[css_imports_list_tail] = .{
+ .offset = @truncate(u32, offset),
+ .length = 0,
+ };
+ getPublicPath(resolve_result.path_pair.primary.text, origin, @TypeOf(writer), writer);
+ const length = css_imports_buf.items.len - offset;
+ css_imports_list[css_imports_list_tail].length = @truncate(u32, length);
+ css_imports_list_tail += 1;
+}
+
+pub fn flushCSSImports() void {
+ if (css_imports_buf_loaded) {
+ css_imports_buf.clearRetainingCapacity();
+ css_imports_list_tail = 0;
+ }
+}
+
+pub fn getCSSImports() []ZigString {
+ var i: u16 = 0;
+ const tail = css_imports_list_tail;
+ while (i < tail) : (i += 1) {
+ ZigString.fromStringPointer(css_imports_list[i], css_imports_buf.items, &css_imports_list_strings[i]);
+ }
+ return css_imports_list_strings[0..tail];
+}
+
+pub fn inspect(
+ // this
+ _: void,
+ ctx: js.JSContextRef,
+ // function
+ _: js.JSObjectRef,
+ // thisObject
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ if (arguments.len == 0)
+ return ZigString.Empty.toValue(ctx.ptr()).asObjectRef();
+
+ for (arguments) |arg| {
+ JSC.C.JSValueProtect(ctx, arg);
+ }
+ defer {
+ for (arguments) |arg| {
+ JSC.C.JSValueUnprotect(ctx, arg);
+ }
+ }
+
+ // very stable memory address
+ var array = MutableString.init(getAllocator(ctx), 0) catch unreachable;
+ var buffered_writer_ = MutableString.BufferedWriter{ .context = &array };
+ var buffered_writer = &buffered_writer_;
+
+ var writer = buffered_writer.writer();
+ const Writer = @TypeOf(writer);
+ // we buffer this because it'll almost always be < 4096
+ // when it's under 4096, we want to avoid the dynamic allocation
+ ZigConsoleClient.format(
+ .Debug,
+ ctx.ptr(),
+ @ptrCast([*]const JSValue, arguments.ptr),
+ arguments.len,
+ Writer,
+ Writer,
+ writer,
+ false,
+ false,
+ false,
+ );
+ buffered_writer.flush() catch {
+ return JSC.C.JSValueMakeUndefined(ctx);
+ };
+
+ // we are going to always clone to keep things simple for now
+ // the common case here will be stack-allocated, so it should be fine
+ var out = ZigString.init(array.toOwnedSliceLeaky()).withEncoding();
+ const ret = out.toValueGC(ctx);
+ array.deinit();
+ return ret.asObjectRef();
+
+ // // when it's a small thing, rely on GC to manage the memory
+ // if (writer.context.pos < 2048 and array.list.items.len == 0) {
+ // var slice = writer.context.buffer[0..writer.context.pos];
+ // if (slice.len == 0) {
+ // return ZigString.Empty.toValue(ctx.ptr()).asObjectRef();
+ // }
+
+ // var zig_str =
+ // return zig_str.toValueGC(ctx.ptr()).asObjectRef();
+ // }
+
+ // // when it's a big thing, we will manage it
+ // {
+ // writer.context.flush() catch {};
+ // var slice = writer.context.context.toOwnedSlice();
+
+ // var zig_str = ZigString.init(slice).withEncoding();
+ // if (!zig_str.isUTF8()) {
+ // return zig_str.toExternalValue(ctx.ptr()).asObjectRef();
+ // } else {
+ // return zig_str.toValueGC(ctx.ptr()).asObjectRef();
+ // }
+ // }
+}
+
+pub fn registerMacro(
+ // this
+ _: void,
+ ctx: js.JSContextRef,
+ // function
+ _: js.JSObjectRef,
+ // thisObject
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+) js.JSValueRef {
+ if (arguments.len != 2 or !js.JSValueIsNumber(ctx, arguments[0])) {
+ JSError(getAllocator(ctx), "Internal error registering macros: invalid args", .{}, ctx, exception);
+ return js.JSValueMakeUndefined(ctx);
+ }
+ // TODO: make this faster
+ const id = @truncate(i32, @floatToInt(i64, js.JSValueToNumber(ctx, arguments[0], exception)));
+ if (id == -1 or id == 0) {
+ JSError(getAllocator(ctx), "Internal error registering macros: invalid id", .{}, ctx, exception);
+ return js.JSValueMakeUndefined(ctx);
+ }
+
+ if (!js.JSValueIsObject(ctx, arguments[1]) or !js.JSObjectIsFunction(ctx, arguments[1])) {
+ JSError(getAllocator(ctx), "Macro must be a function. Received: {s}", .{@tagName(js.JSValueGetType(ctx, arguments[1]))}, ctx, exception);
+ return js.JSValueMakeUndefined(ctx);
+ }
+
+ var get_or_put_result = VirtualMachine.vm.macros.getOrPut(id) catch unreachable;
+ if (get_or_put_result.found_existing) {
+ js.JSValueUnprotect(ctx, get_or_put_result.value_ptr.*);
+ }
+
+ js.JSValueProtect(ctx, arguments[1]);
+ get_or_put_result.value_ptr.* = arguments[1];
+
+ return js.JSValueMakeUndefined(ctx);
+}
+
+pub fn getCWD(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ return ZigString.init(VirtualMachine.vm.bundler.fs.top_level_dir).toValue(ctx.ptr()).asRef();
+}
+
+pub fn getOrigin(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ return ZigString.init(VirtualMachine.vm.origin.origin).toValue(ctx.ptr()).asRef();
+}
+
+pub fn getStdin(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ var existing = ctx.ptr().getCachedObject(&ZigString.init("BunSTDIN"));
+ if (existing.isEmpty()) {
+ var rare_data = JSC.VirtualMachine.vm.rareData();
+ var store = rare_data.stdin();
+ var blob = bun.default_allocator.create(JSC.WebCore.Blob) catch unreachable;
+ blob.* = JSC.WebCore.Blob.initWithStore(store, ctx.ptr());
+
+ return ctx.ptr().putCachedObject(
+ &ZigString.init("BunSTDIN"),
+ JSC.JSValue.fromRef(JSC.WebCore.Blob.Class.make(ctx, blob)),
+ ).asObjectRef();
+ }
+
+ return existing.asObjectRef();
+}
+
+pub fn getStderr(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ var existing = ctx.ptr().getCachedObject(&ZigString.init("BunSTDERR"));
+ if (existing.isEmpty()) {
+ var rare_data = JSC.VirtualMachine.vm.rareData();
+ var store = rare_data.stderr();
+ var blob = bun.default_allocator.create(JSC.WebCore.Blob) catch unreachable;
+ blob.* = JSC.WebCore.Blob.initWithStore(store, ctx.ptr());
+
+ return ctx.ptr().putCachedObject(
+ &ZigString.init("BunSTDERR"),
+ JSC.JSValue.fromRef(JSC.WebCore.Blob.Class.make(ctx, blob)),
+ ).asObjectRef();
+ }
+
+ return existing.asObjectRef();
+}
+
+pub fn getStdout(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ var existing = ctx.ptr().getCachedObject(&ZigString.init("BunSTDOUT"));
+ if (existing.isEmpty()) {
+ var rare_data = JSC.VirtualMachine.vm.rareData();
+ var store = rare_data.stdout();
+ var blob = bun.default_allocator.create(JSC.WebCore.Blob) catch unreachable;
+ blob.* = JSC.WebCore.Blob.initWithStore(store, ctx.ptr());
+
+ return ctx.ptr().putCachedObject(
+ &ZigString.init("BunSTDOUT"),
+ JSC.JSValue.fromRef(JSC.WebCore.Blob.Class.make(ctx, blob)),
+ ).asObjectRef();
+ }
+
+ return existing.asObjectRef();
+}
+
+pub fn enableANSIColors(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ return js.JSValueMakeBoolean(ctx, Output.enable_ansi_colors);
+}
+pub fn getMain(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ return ZigString.init(VirtualMachine.vm.main).toValue(ctx.ptr()).asRef();
+}
+
+pub fn getAssetPrefix(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ return ZigString.init(VirtualMachine.vm.bundler.options.routes.asset_prefix_path).toValue(ctx.ptr()).asRef();
+}
+
+pub fn getArgv(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ if (comptime Environment.isWindows) {
+ @compileError("argv not supported on windows");
+ }
+
+ var argv_list = std.heap.stackFallback(128, getAllocator(ctx));
+ var allocator = argv_list.get();
+ var argv = allocator.alloc(ZigString, std.os.argv.len) catch unreachable;
+ defer if (argv.len > 128) allocator.free(argv);
+ for (std.os.argv) |arg, i| {
+ argv[i] = ZigString.init(std.mem.span(arg));
+ }
+
+ return JSValue.createStringArray(ctx.ptr(), argv.ptr, argv.len, true).asObjectRef();
+}
+
+pub fn getRoutesDir(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ if (!VirtualMachine.vm.bundler.options.routes.routes_enabled or VirtualMachine.vm.bundler.options.routes.dir.len == 0) {
+ return js.JSValueMakeUndefined(ctx);
+ }
+
+ return ZigString.init(VirtualMachine.vm.bundler.options.routes.dir).toValue(ctx.ptr()).asRef();
+}
+
+pub fn getFilePath(ctx: js.JSContextRef, arguments: []const js.JSValueRef, buf: []u8, exception: js.ExceptionRef) ?string {
+ if (arguments.len != 1) {
+ JSError(getAllocator(ctx), "Expected a file path as a string or an array of strings to be part of a file path.", .{}, ctx, exception);
+ return null;
+ }
+
+ const value = arguments[0];
+ if (js.JSValueIsString(ctx, value)) {
+ var out = ZigString.Empty;
+ JSValue.toZigString(JSValue.fromRef(value), &out, ctx.ptr());
+ var out_slice = out.slice();
+
+ // The dots are kind of unnecessary. They'll be normalized.
+ if (out.len == 0 or @ptrToInt(out.ptr) == 0 or std.mem.eql(u8, out_slice, ".") or std.mem.eql(u8, out_slice, "..") or std.mem.eql(u8, out_slice, "../")) {
+ JSError(getAllocator(ctx), "Expected a file path as a string or an array of strings to be part of a file path.", .{}, ctx, exception);
+ return null;
+ }
+
+ var parts = [_]string{out_slice};
+ // This does the equivalent of Node's path.normalize(path.join(cwd, out_slice))
+ var res = VirtualMachine.vm.bundler.fs.absBuf(&parts, buf);
+
+ return res;
+ } else if (js.JSValueIsArray(ctx, value)) {
+ var temp_strings_list: [32]string = undefined;
+ var temp_strings_list_len: u8 = 0;
+ defer {
+ for (temp_strings_list[0..temp_strings_list_len]) |_, i| {
+ temp_strings_list[i] = "";
+ }
+ }
+
+ var iter = JSValue.fromRef(value).arrayIterator(ctx.ptr());
+ while (iter.next()) |item| {
+ if (temp_strings_list_len >= temp_strings_list.len) {
+ break;
+ }
+
+ if (!item.isString()) {
+ JSError(getAllocator(ctx), "Expected a file path as a string or an array of strings to be part of a file path.", .{}, ctx, exception);
+ return null;
+ }
+
+ var out = ZigString.Empty;
+ JSValue.toZigString(item, &out, ctx.ptr());
+ const out_slice = out.slice();
+
+ temp_strings_list[temp_strings_list_len] = out_slice;
+ // The dots are kind of unnecessary. They'll be normalized.
+ if (out.len == 0 or @ptrToInt(out.ptr) == 0 or std.mem.eql(u8, out_slice, ".") or std.mem.eql(u8, out_slice, "..") or std.mem.eql(u8, out_slice, "../")) {
+ JSError(getAllocator(ctx), "Expected a file path as a string or an array of strings to be part of a file path.", .{}, ctx, exception);
+ return null;
+ }
+ temp_strings_list_len += 1;
+ }
+
+ if (temp_strings_list_len == 0) {
+ JSError(getAllocator(ctx), "Expected a file path as a string or an array of strings to be part of a file path.", .{}, ctx, exception);
+ return null;
+ }
+
+ return VirtualMachine.vm.bundler.fs.absBuf(temp_strings_list[0..temp_strings_list_len], buf);
+ } else {
+ JSError(getAllocator(ctx), "Expected a file path as a string or an array of strings to be part of a file path.", .{}, ctx, exception);
+ return null;
+ }
+}
+
+pub fn getImportedStyles(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ _: []const js.JSValueRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ defer flushCSSImports();
+ const styles = getCSSImports();
+ if (styles.len == 0) {
+ return js.JSObjectMakeArray(ctx, 0, null, null);
+ }
+
+ return JSValue.createStringArray(ctx.ptr(), styles.ptr, styles.len, true).asRef();
+}
+
+pub fn newPath(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ args: []const js.JSValueRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ const is_windows = args.len == 1 and JSValue.fromRef(args[0]).toBoolean();
+ return Node.Path.create(ctx.ptr(), is_windows).asObjectRef();
+}
+
+pub fn readFileAsStringCallback(
+ ctx: js.JSContextRef,
+ buf_z: [:0]const u8,
+ exception: js.ExceptionRef,
+) js.JSValueRef {
+ const path = buf_z.ptr[0..buf_z.len];
+ var file = std.fs.cwd().openFileZ(buf_z, .{ .mode = .read_only }) catch |err| {
+ JSError(getAllocator(ctx), "Opening file {s} for path: \"{s}\"", .{ @errorName(err), path }, ctx, exception);
+ return js.JSValueMakeUndefined(ctx);
+ };
+
+ defer file.close();
+
+ const stat = file.stat() catch |err| {
+ JSError(getAllocator(ctx), "Getting file size {s} for \"{s}\"", .{ @errorName(err), path }, ctx, exception);
+ return js.JSValueMakeUndefined(ctx);
+ };
+
+ if (stat.kind != .File) {
+ JSError(getAllocator(ctx), "Can't read a {s} as a string (\"{s}\")", .{ @tagName(stat.kind), path }, ctx, exception);
+ return js.JSValueMakeUndefined(ctx);
+ }
+
+ var contents_buf = VirtualMachine.vm.allocator.alloc(u8, stat.size + 2) catch unreachable; // OOM
+ defer VirtualMachine.vm.allocator.free(contents_buf);
+ const contents_len = file.readAll(contents_buf) catch |err| {
+ JSError(getAllocator(ctx), "{s} reading file (\"{s}\")", .{ @errorName(err), path }, ctx, exception);
+ return js.JSValueMakeUndefined(ctx);
+ };
+
+ contents_buf[contents_len] = 0;
+
+ // Very slow to do it this way. We're copying the string twice.
+ // But it's important that this string is garbage collected instead of manually managed.
+ // We can't really recycle this one.
+ // TODO: use external string
+ return js.JSValueMakeString(ctx, js.JSStringCreateWithUTF8CString(contents_buf.ptr));
+}
+
+pub fn readFileAsBytesCallback(
+ ctx: js.JSContextRef,
+ buf_z: [:0]const u8,
+ exception: js.ExceptionRef,
+) js.JSValueRef {
+ const path = buf_z.ptr[0..buf_z.len];
+
+ var file = std.fs.cwd().openFileZ(buf_z, .{ .mode = .read_only }) catch |err| {
+ JSError(getAllocator(ctx), "Opening file {s} for path: \"{s}\"", .{ @errorName(err), path }, ctx, exception);
+ return js.JSValueMakeUndefined(ctx);
+ };
+
+ defer file.close();
+
+ const stat = file.stat() catch |err| {
+ JSError(getAllocator(ctx), "Getting file size {s} for \"{s}\"", .{ @errorName(err), path }, ctx, exception);
+ return js.JSValueMakeUndefined(ctx);
+ };
+
+ if (stat.kind != .File) {
+ JSError(getAllocator(ctx), "Can't read a {s} as a string (\"{s}\")", .{ @tagName(stat.kind), path }, ctx, exception);
+ return js.JSValueMakeUndefined(ctx);
+ }
+
+ var contents_buf = VirtualMachine.vm.allocator.alloc(u8, stat.size + 2) catch unreachable; // OOM
+ errdefer VirtualMachine.vm.allocator.free(contents_buf);
+ const contents_len = file.readAll(contents_buf) catch |err| {
+ JSError(getAllocator(ctx), "{s} reading file (\"{s}\")", .{ @errorName(err), path }, ctx, exception);
+ return js.JSValueMakeUndefined(ctx);
+ };
+
+ contents_buf[contents_len] = 0;
+
+ var marked_array_buffer = VirtualMachine.vm.allocator.create(MarkedArrayBuffer) catch unreachable;
+ marked_array_buffer.* = MarkedArrayBuffer.fromBytes(
+ contents_buf[0..contents_len],
+ VirtualMachine.vm.allocator,
+ .Uint8Array,
+ );
+
+ return marked_array_buffer.toJSObjectRef(ctx, exception);
+}
+
+pub fn getRouteFiles(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ _: []const js.JSValueRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ if (VirtualMachine.vm.bundler.router == null) return js.JSObjectMakeArray(ctx, 0, null, null);
+
+ const router = &VirtualMachine.vm.bundler.router.?;
+ const list = router.getPublicPaths() catch unreachable;
+
+ for (routes_list_strings[0..@minimum(list.len, routes_list_strings.len)]) |_, i| {
+ routes_list_strings[i] = ZigString.init(list[i]);
+ }
+
+ const ref = JSValue.createStringArray(ctx.ptr(), &routes_list_strings, list.len, true).asRef();
+ return ref;
+}
+
+pub fn getRouteNames(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ _: []const js.JSValueRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ if (VirtualMachine.vm.bundler.router == null) return js.JSObjectMakeArray(ctx, 0, null, null);
+
+ const router = &VirtualMachine.vm.bundler.router.?;
+ const list = router.getNames() catch unreachable;
+
+ for (routes_list_strings[0..@minimum(list.len, routes_list_strings.len)]) |_, i| {
+ routes_list_strings[i] = ZigString.init(list[i]);
+ }
+
+ const ref = JSValue.createStringArray(ctx.ptr(), &routes_list_strings, list.len, true).asRef();
+ return ref;
+}
+
+const Editor = @import("../../open.zig").Editor;
+pub fn openInEditor(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ args: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+) js.JSValueRef {
+ var edit = &VirtualMachine.vm.rareData().editor_context;
+
+ var arguments = JSC.Node.ArgumentsSlice.from(ctx.bunVM(), args);
+ defer arguments.deinit();
+ var path: string = "";
+ var editor_choice: ?Editor = null;
+ var line: ?string = null;
+ var column: ?string = null;
+
+ if (arguments.nextEat()) |file_path_| {
+ path = file_path_.toSlice(ctx.ptr(), bun.default_allocator).slice();
+ }
+
+ if (arguments.nextEat()) |opts| {
+ if (!opts.isUndefinedOrNull()) {
+ if (opts.getTruthy(ctx.ptr(), "editor")) |editor_val| {
+ var sliced = editor_val.toSlice(ctx.ptr(), bun.default_allocator);
+ var prev_name = edit.name;
+
+ if (!strings.eqlLong(prev_name, sliced.slice(), true)) {
+ var prev = edit.*;
+ edit.name = sliced.slice();
+ edit.detectEditor(VirtualMachine.vm.bundler.env);
+ editor_choice = edit.editor;
+ if (editor_choice == null) {
+ edit.* = prev;
+ JSError(getAllocator(ctx), "Could not find editor \"{s}\"", .{sliced.slice()}, ctx, exception);
+ return js.JSValueMakeUndefined(ctx);
+ } else if (edit.name.ptr == edit.path.ptr) {
+ edit.name = bun.default_allocator.dupe(u8, edit.path) catch unreachable;
+ edit.path = edit.path;
+ }
+ }
+ }
+
+ if (opts.getTruthy(ctx.ptr(), "line")) |line_| {
+ line = line_.toSlice(ctx.ptr(), bun.default_allocator).slice();
+ }
+
+ if (opts.getTruthy(ctx.ptr(), "column")) |column_| {
+ column = column_.toSlice(ctx.ptr(), bun.default_allocator).slice();
+ }
+ }
+ }
+
+ const editor = editor_choice orelse edit.editor orelse brk: {
+ edit.autoDetectEditor(VirtualMachine.vm.bundler.env);
+ if (edit.editor == null) {
+ JSC.JSError(bun.default_allocator, "Failed to auto-detect editor", .{}, ctx, exception);
+ return null;
+ }
+
+ break :brk edit.editor.?;
+ };
+
+ if (path.len == 0) {
+ JSError(getAllocator(ctx), "No file path specified", .{}, ctx, exception);
+ return js.JSValueMakeUndefined(ctx);
+ }
+
+ editor.open(edit.path, path, line, column, bun.default_allocator) catch |err| {
+ JSC.JSError(bun.default_allocator, "Opening editor failed {s}", .{@errorName(err)}, ctx, exception);
+ return null;
+ };
+
+ return JSC.JSValue.jsUndefined().asObjectRef();
+}
+
+pub fn readFileAsBytes(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+) js.JSValueRef {
+ var buf: [bun.MAX_PATH_BYTES]u8 = undefined;
+ const path = getFilePath(ctx, arguments, &buf, exception) orelse return null;
+ buf[path.len] = 0;
+
+ const buf_z: [:0]const u8 = buf[0..path.len :0];
+ const result = readFileAsBytesCallback(ctx, buf_z, exception);
+ return result;
+}
+
+pub fn readFileAsString(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+) js.JSValueRef {
+ var buf: [bun.MAX_PATH_BYTES]u8 = undefined;
+ const path = getFilePath(ctx, arguments, &buf, exception) orelse return null;
+ buf[path.len] = 0;
+
+ const buf_z: [:0]const u8 = buf[0..path.len :0];
+ const result = readFileAsStringCallback(ctx, buf_z, exception);
+ return result;
+}
+
+pub fn getPublicPath(to: string, origin: URL, comptime Writer: type, writer: Writer) void {
+ const relative_path = VirtualMachine.vm.bundler.fs.relativeTo(to);
+ if (origin.isAbsolute()) {
+ if (strings.hasPrefix(relative_path, "..") or strings.hasPrefix(relative_path, "./")) {
+ writer.writeAll(origin.origin) catch return;
+ writer.writeAll("/abs:") catch return;
+ if (std.fs.path.isAbsolute(to)) {
+ writer.writeAll(to) catch return;
+ } else {
+ writer.writeAll(VirtualMachine.vm.bundler.fs.abs(&[_]string{to})) catch return;
+ }
+ } else {
+ origin.joinWrite(
+ Writer,
+ writer,
+ VirtualMachine.vm.bundler.options.routes.asset_prefix_path,
+ "",
+ relative_path,
+ "",
+ ) catch return;
+ }
+ } else {
+ writer.writeAll(std.mem.trimLeft(u8, relative_path, "/")) catch unreachable;
+ }
+}
+
+pub fn sleepSync(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ if (js.JSValueIsNumber(ctx, arguments[0])) {
+ const seconds = JSValue.fromRef(arguments[0]).asNumber();
+ if (seconds > 0 and std.math.isFinite(seconds)) std.time.sleep(@floatToInt(u64, seconds * 1000) * std.time.ns_per_ms);
+ }
+
+ return js.JSValueMakeUndefined(ctx);
+}
+
+pub fn createNodeFS(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ _: []const js.JSValueRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ return Node.NodeFSBindings.make(
+ ctx,
+ VirtualMachine.vm.nodeFS(),
+ );
+}
+
+pub fn generateHeapSnapshot(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ _: []const js.JSValueRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ return ctx.ptr().generateHeapSnapshot().asObjectRef();
+}
+
+pub fn runGC(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ // it should only force cleanup on thread exit
+
+ Global.mimalloc_cleanup(false);
+
+ return ctx.ptr().vm().runGC(arguments.len > 0 and JSValue.fromRef(arguments[0]).toBoolean()).asRef();
+}
+
+pub fn shrink(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ _: []const js.JSValueRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ ctx.ptr().vm().shrinkFootprint();
+ return JSValue.jsUndefined().asRef();
+}
+
+fn doResolve(
+ ctx: js.JSContextRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+) ?JSC.JSValue {
+ var args = JSC.Node.ArgumentsSlice.from(ctx.bunVM(), arguments);
+ defer args.deinit();
+ const specifier = args.protectEatNext() orelse {
+ JSC.throwInvalidArguments("Expected a specifier and a from path", .{}, ctx, exception);
+ return null;
+ };
+
+ if (specifier.isUndefinedOrNull()) {
+ JSC.throwInvalidArguments("specifier must be a string", .{}, ctx, exception);
+ return null;
+ }
+
+ const from = args.protectEatNext() orelse {
+ JSC.throwInvalidArguments("Expected a from path", .{}, ctx, exception);
+ return null;
+ };
+
+ if (from.isUndefinedOrNull()) {
+ JSC.throwInvalidArguments("from must be a string", .{}, ctx, exception);
+ return null;
+ }
+
+ return doResolveWithArgs(ctx, specifier.getZigString(ctx.ptr()), from.getZigString(ctx.ptr()), exception, false);
+}
+
+fn doResolveWithArgs(
+ ctx: js.JSContextRef,
+ specifier: ZigString,
+ from: ZigString,
+ exception: js.ExceptionRef,
+ comptime is_file_path: bool,
+) ?JSC.JSValue {
+ var errorable: ErrorableZigString = undefined;
+
+ if (comptime is_file_path) {
+ VirtualMachine.resolveFilePathForAPI(
+ &errorable,
+ ctx.ptr(),
+ specifier,
+ from,
+ );
+ } else {
+ VirtualMachine.resolveForAPI(
+ &errorable,
+ ctx.ptr(),
+ specifier,
+ from,
+ );
+ }
+
+ if (!errorable.success) {
+ exception.* = bun.cast(JSC.JSValueRef, errorable.result.err.ptr.?);
+ return null;
+ }
+
+ return errorable.result.value.toValue(ctx.ptr());
+}
+
+pub fn resolveSync(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+) js.JSValueRef {
+ const value = doResolve(ctx, arguments, exception) orelse return null;
+ return value.asObjectRef();
+}
+
+pub fn resolve(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+) js.JSValueRef {
+ const value = doResolve(ctx, arguments, exception) orelse {
+ var exception_value = exception.*.?;
+ exception.* = null;
+ return JSC.JSPromise.rejectedPromiseValue(ctx.ptr(), JSC.JSValue.fromRef(exception_value)).asObjectRef();
+ };
+ return JSC.JSPromise.resolvedPromiseValue(ctx.ptr(), value).asObjectRef();
+}
+
+export fn Bun__resolve(
+ global: *JSGlobalObject,
+ specifier: JSValue,
+ source: JSValue,
+) JSC.JSValue {
+ var exception_ = [1]JSC.JSValueRef{null};
+ var exception = &exception_;
+ const value = doResolveWithArgs(global.ref(), specifier.getZigString(global), source.getZigString(global), exception, true) orelse {
+ return JSC.JSPromise.rejectedPromiseValue(global, JSC.JSValue.fromRef(exception[0]));
+ };
+ return JSC.JSPromise.resolvedPromiseValue(global, value);
+}
+
+export fn Bun__resolveSync(
+ global: *JSGlobalObject,
+ specifier: JSValue,
+ source: JSValue,
+) JSC.JSValue {
+ var exception_ = [1]JSC.JSValueRef{null};
+ var exception = &exception_;
+ return doResolveWithArgs(global.ref(), specifier.getZigString(global), source.getZigString(global), exception, true) orelse {
+ return JSC.JSValue.fromRef(exception[0]);
+ };
+}
+
+comptime {
+ if (!is_bindgen) {
+ _ = Bun__resolve;
+ _ = Bun__resolveSync;
+ }
+}
+
+pub fn readAllStdinSync(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ _: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+) js.JSValueRef {
+ var stack = std.heap.stackFallback(2048, getAllocator(ctx));
+ var allocator = stack.get();
+
+ var stdin = std.io.getStdIn();
+ var result = stdin.readToEndAlloc(allocator, std.math.maxInt(u32)) catch |err| {
+ JSError(undefined, "{s} reading stdin", .{@errorName(err)}, ctx, exception);
+ return null;
+ };
+ var out = ZigString.init(result);
+ out.detectEncoding();
+ return out.toValueGC(ctx.ptr()).asObjectRef();
+}
+
+var public_path_temp_str: [bun.MAX_PATH_BYTES]u8 = undefined;
+
+pub fn getPublicPathJS(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ var zig_str: ZigString = ZigString.Empty;
+ JSValue.toZigString(JSValue.fromRef(arguments[0]), &zig_str, ctx.ptr());
+
+ const to = zig_str.slice();
+
+ var stream = std.io.fixedBufferStream(&public_path_temp_str);
+ var writer = stream.writer();
+ getPublicPath(to, VirtualMachine.vm.origin, @TypeOf(&writer), &writer);
+
+ return ZigString.init(stream.buffer[0..stream.pos]).toValueGC(ctx.ptr()).asObjectRef();
+}
+
+pub const Class = NewClass(
+ void,
+ .{
+ .name = "Bun",
+ .read_only = true,
+ },
+ .{
+ .match = .{
+ .rfn = Router.match,
+ .ts = Router.match_type_definition,
+ },
+ .sleepSync = .{
+ .rfn = sleepSync,
+ },
+ .fetch = .{
+ .rfn = Fetch.call,
+ .ts = d.ts{},
+ },
+ .getImportedStyles = .{
+ .rfn = Bun.getImportedStyles,
+ .ts = d.ts{
+ .name = "getImportedStyles",
+ .@"return" = "string[]",
+ },
+ },
+ .inspect = .{
+ .rfn = Bun.inspect,
+ .ts = d.ts{
+ .name = "inspect",
+ .@"return" = "string",
+ },
+ },
+ .getRouteFiles = .{
+ .rfn = Bun.getRouteFiles,
+ .ts = d.ts{
+ .name = "getRouteFiles",
+ .@"return" = "string[]",
+ },
+ },
+ ._Path = .{
+ .rfn = Bun.newPath,
+ .ts = d.ts{},
+ },
+ .getRouteNames = .{
+ .rfn = Bun.getRouteNames,
+ .ts = d.ts{
+ .name = "getRouteNames",
+ .@"return" = "string[]",
+ },
+ },
+ .readFile = .{
+ .rfn = Bun.readFileAsString,
+ .ts = d.ts{
+ .name = "readFile",
+ .@"return" = "string",
+ },
+ },
+ .resolveSync = .{
+ .rfn = Bun.resolveSync,
+ .ts = d.ts{
+ .name = "resolveSync",
+ .@"return" = "string",
+ },
+ },
+ .resolve = .{
+ .rfn = Bun.resolve,
+ .ts = d.ts{
+ .name = "resolve",
+ .@"return" = "string",
+ },
+ },
+ .readFileBytes = .{
+ .rfn = Bun.readFileAsBytes,
+ .ts = d.ts{
+ .name = "readFile",
+ .@"return" = "Uint8Array",
+ },
+ },
+ .getPublicPath = .{
+ .rfn = Bun.getPublicPathJS,
+ .ts = d.ts{
+ .name = "getPublicPath",
+ .@"return" = "string",
+ },
+ },
+ .registerMacro = .{
+ .rfn = Bun.registerMacro,
+ .ts = d.ts{
+ .name = "registerMacro",
+ .@"return" = "undefined",
+ },
+ .enumerable = false,
+ },
+ .fs = .{
+ .rfn = Bun.createNodeFS,
+ .ts = d.ts{},
+ .enumerable = false,
+ },
+ .jest = .{
+ .rfn = @import("../test/jest.zig").Jest.call,
+ .ts = d.ts{},
+ .enumerable = false,
+ },
+ .gc = .{
+ .rfn = Bun.runGC,
+ .ts = d.ts{},
+ },
+ .allocUnsafe = .{
+ .rfn = Bun.allocUnsafe,
+ .ts = .{},
+ },
+ .mmap = .{
+ .rfn = Bun.mmapFile,
+ .ts = .{},
+ },
+ .generateHeapSnapshot = .{
+ .rfn = Bun.generateHeapSnapshot,
+ .ts = d.ts{},
+ },
+ .shrink = .{
+ .rfn = Bun.shrink,
+ .ts = d.ts{},
+ },
+ .openInEditor = .{
+ .rfn = Bun.openInEditor,
+ .ts = d.ts{},
+ },
+ .readAllStdinSync = .{
+ .rfn = Bun.readAllStdinSync,
+ .ts = d.ts{},
+ },
+ .serve = .{
+ .rfn = Bun.serve,
+ .ts = d.ts{},
+ },
+ .file = .{
+ .rfn = JSC.WebCore.Blob.constructFile,
+ .ts = d.ts{},
+ },
+ .write = .{
+ .rfn = JSC.WebCore.Blob.writeFile,
+ .ts = d.ts{},
+ },
+ .sha = .{
+ .rfn = JSC.wrapWithHasContainer(Crypto.SHA512_256, "hash", false, false, true),
+ },
+ .nanoseconds = .{
+ .rfn = nanoseconds,
+ },
+ .gzipSync = .{
+ .rfn = JSC.wrapWithHasContainer(JSZlib, "gzipSync", false, false, true),
+ },
+ .deflateSync = .{
+ .rfn = JSC.wrapWithHasContainer(JSZlib, "deflateSync", false, false, true),
+ },
+ .gunzipSync = .{
+ .rfn = JSC.wrapWithHasContainer(JSZlib, "gunzipSync", false, false, true),
+ },
+ .inflateSync = .{
+ .rfn = JSC.wrapWithHasContainer(JSZlib, "inflateSync", false, false, true),
+ },
+ },
+ .{
+ .main = .{
+ .get = getMain,
+ .ts = d.ts{ .name = "main", .@"return" = "string" },
+ },
+ .cwd = .{
+ .get = getCWD,
+ .ts = d.ts{ .name = "cwd", .@"return" = "string" },
+ },
+ .origin = .{
+ .get = getOrigin,
+ .ts = d.ts{ .name = "origin", .@"return" = "string" },
+ },
+ .stdin = .{
+ .get = getStdin,
+ },
+ .stdout = .{
+ .get = getStdout,
+ },
+ .stderr = .{
+ .get = getStderr,
+ },
+ .routesDir = .{
+ .get = getRoutesDir,
+ .ts = d.ts{ .name = "routesDir", .@"return" = "string" },
+ },
+ .assetPrefix = .{
+ .get = getAssetPrefix,
+ .ts = d.ts{ .name = "assetPrefix", .@"return" = "string" },
+ },
+ .argv = .{
+ .get = getArgv,
+ .ts = d.ts{ .name = "argv", .@"return" = "string[]" },
+ },
+ .env = .{
+ .get = EnvironmentVariables.getter,
+ },
+
+ .enableANSIColors = .{
+ .get = enableANSIColors,
+ },
+ .Transpiler = .{
+ .get = getTranspilerConstructor,
+ .ts = d.ts{ .name = "Transpiler", .@"return" = "Transpiler.prototype" },
+ },
+ .hash = .{
+ .get = getHashObject,
+ },
+ .TOML = .{
+ .get = getTOMLObject,
+ .ts = d.ts{ .name = "TOML", .@"return" = "TOML.prototype" },
+ },
+ .unsafe = .{
+ .get = getUnsafe,
+ },
+
+ .SHA1 = .{
+ .get = Crypto.SHA1.getter,
+ },
+ .MD5 = .{
+ .get = Crypto.MD5.getter,
+ },
+ .MD4 = .{
+ .get = Crypto.MD4.getter,
+ },
+ .SHA224 = .{
+ .get = Crypto.SHA224.getter,
+ },
+ .SHA512 = .{
+ .get = Crypto.SHA512.getter,
+ },
+ .SHA384 = .{
+ .get = Crypto.SHA384.getter,
+ },
+ .SHA256 = .{
+ .get = Crypto.SHA256.getter,
+ },
+ .SHA512_256 = .{
+ .get = Crypto.SHA512_256.getter,
+ },
+ .FFI = .{
+ .get = FFI.getter,
+ },
+ },
+);
+
+pub const Crypto = struct {
+ const Hashers = @import("../../sha.zig");
+
+ fn CryptoHasher(comptime Hasher: type, comptime name: [:0]const u8, cached_constructor_name: []const u8) type {
+ return struct {
+ hashing: Hasher = Hasher{},
+
+ pub fn byteLength(
+ _: void,
+ _: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+ ) js.JSValueRef {
+ return JSC.JSValue.jsNumber(@as(u16, Hasher.digest)).asObjectRef();
+ }
+
+ pub fn byteLength2(
+ _: *@This(),
+ _: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+ ) js.JSValueRef {
+ return JSC.JSValue.jsNumber(@as(u16, Hasher.digest)).asObjectRef();
+ }
+
+ pub const Constructor = JSC.NewConstructor(
+ @This(),
+ .{
+ .hash = .{
+ .rfn = JSC.wrapWithHasContainer(@This(), "hash", false, false, true),
+ },
+ .constructor = .{ .rfn = constructor },
+ },
+ .{
+ .byteLength = .{
+ .get = byteLength,
+ },
+ },
+ );
+
+ pub const Class = JSC.NewClass(
+ @This(),
+ .{
+ .name = name,
+ },
+ .{
+ .update = .{
+ .rfn = JSC.wrapSync(@This(), "update"),
+ },
+ .digest = .{
+ .rfn = JSC.wrapSync(@This(), "digest"),
+ },
+ .finalize = finalize,
+ },
+ .{
+ .byteLength = .{
+ .get = byteLength2,
+ },
+ },
+ );
+
+ fn hashToEncoding(
+ globalThis: *JSGlobalObject,
+ input: JSC.Node.StringOrBuffer,
+ encoding: JSC.Node.Encoding,
+ exception: JSC.C.ExceptionRef,
+ ) JSC.JSValue {
+ var output_digest_buf: Hasher.Digest = undefined;
+
+ Hasher.hash(input.slice(), &output_digest_buf, JSC.VirtualMachine.vm.rareData().boringEngine());
+
+ return encoding.encodeWithSize(globalThis, Hasher.digest, &output_digest_buf, exception);
+ }
+
+ fn hashToBytes(
+ globalThis: *JSGlobalObject,
+ input: JSC.Node.StringOrBuffer,
+ output: ?JSC.ArrayBuffer,
+ exception: JSC.C.ExceptionRef,
+ ) JSC.JSValue {
+ var output_digest_buf: Hasher.Digest = undefined;
+ var output_digest_slice: *Hasher.Digest = &output_digest_buf;
+ if (output) |output_buf| {
+ var bytes = output_buf.byteSlice();
+ if (bytes.len < Hasher.digest) {
+ JSC.JSError(
+ bun.default_allocator,
+ comptime std.fmt.comptimePrint("TypedArray must be at least {d} bytes", .{Hasher.digest}),
+ .{},
+ globalThis.ref(),
+ exception,
+ );
+ return JSC.JSValue.zero;
+ }
+ output_digest_slice = bytes[0..Hasher.digest];
+ }
+
+ Hasher.hash(input.slice(), output_digest_slice, JSC.VirtualMachine.vm.rareData().boringEngine());
+
+ if (output) |output_buf| {
+ return output_buf.value;
+ } else {
+ var array_buffer_out = JSC.ArrayBuffer.fromBytes(bun.default_allocator.dupe(u8, output_digest_slice) catch unreachable, .Uint8Array);
+ return array_buffer_out.toJSUnchecked(globalThis.ref(), exception);
+ }
+ }
+
+ pub fn hash(
+ globalThis: *JSGlobalObject,
+ input: JSC.Node.StringOrBuffer,
+ output: ?JSC.Node.StringOrBuffer,
+ exception: JSC.C.ExceptionRef,
+ ) JSC.JSValue {
+ if (output) |string_or_buffer| {
+ switch (string_or_buffer) {
+ .string => |str| {
+ const encoding = JSC.Node.Encoding.from(str) orelse {
+ JSC.JSError(
+ bun.default_allocator,
+ "Unknown encoding",
+ .{},
+ globalThis.ref(),
+ exception,
+ );
+ return JSC.JSValue.zero;
+ };
+
+ return hashToEncoding(globalThis, input, encoding, exception);
+ },
+ .buffer => |buffer| {
+ return hashToBytes(globalThis, input, buffer.buffer, exception);
+ },
+ }
+ } else {
+ return hashToBytes(globalThis, input, null, exception);
+ }
+ }
+
+ pub fn constructor(
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) js.JSObjectRef {
+ var this = bun.default_allocator.create(@This()) catch {
+ JSC.JSError(bun.default_allocator, "Failed to create new object", .{}, ctx, exception);
+ return null;
+ };
+
+ this.* = .{ .hashing = Hasher.init() };
+ return @This().Class.make(ctx, this);
+ }
+
+ pub fn getter(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+ ) js.JSValueRef {
+ var existing = ctx.ptr().getCachedObject(&ZigString.init(cached_constructor_name));
+ if (existing.isEmpty()) {
+ return ctx.ptr().putCachedObject(
+ &ZigString.init(cached_constructor_name),
+ JSC.JSValue.fromRef(@This().Constructor.constructor(ctx)),
+ ).asObjectRef();
+ }
+
+ return existing.asObjectRef();
+ }
+
+ pub fn update(this: *@This(), thisObj: JSC.C.JSObjectRef, buffer: JSC.Node.StringOrBuffer) JSC.JSValue {
+ this.hashing.update(buffer.slice());
+ return JSC.JSValue.c(thisObj);
+ }
+
+ pub fn digest(
+ this: *@This(),
+ globalThis: *JSGlobalObject,
+ output: ?JSC.Node.StringOrBuffer,
+ exception: JSC.C.ExceptionRef,
+ ) JSC.JSValue {
+ if (output) |string_or_buffer| {
+ switch (string_or_buffer) {
+ .string => |str| {
+ const encoding = JSC.Node.Encoding.from(str) orelse {
+ JSC.JSError(
+ bun.default_allocator,
+ "Unknown encoding",
+ .{},
+ globalThis.ref(),
+ exception,
+ );
+ return JSC.JSValue.zero;
+ };
+
+ return this.digestToEncoding(globalThis, exception, encoding);
+ },
+ .buffer => |buffer| {
+ return this.digestToBytes(
+ globalThis,
+ exception,
+ buffer.buffer,
+ );
+ },
+ }
+ } else {
+ return this.digestToBytes(globalThis, exception, null);
+ }
+ }
+
+ fn digestToBytes(this: *@This(), globalThis: *JSGlobalObject, exception: JSC.C.ExceptionRef, output: ?JSC.ArrayBuffer) JSC.JSValue {
+ var output_digest_buf: Hasher.Digest = undefined;
+ var output_digest_slice: *Hasher.Digest = &output_digest_buf;
+ if (output) |output_buf| {
+ var bytes = output_buf.byteSlice();
+ if (bytes.len < Hasher.digest) {
+ JSC.JSError(
+ bun.default_allocator,
+ comptime std.fmt.comptimePrint("TypedArray must be at least {d} bytes", .{@as(usize, Hasher.digest)}),
+ .{},
+ globalThis.ref(),
+ exception,
+ );
+ return JSC.JSValue.zero;
+ }
+ output_digest_slice = bytes[0..Hasher.digest];
+ } else {
+ output_digest_buf = comptime brk: {
+ var bytes: Hasher.Digest = undefined;
+ var i: usize = 0;
+ while (i < Hasher.digest) {
+ bytes[i] = 0;
+ i += 1;
+ }
+ break :brk bytes;
+ };
+ }
+
+ this.hashing.final(output_digest_slice);
+
+ if (output) |output_buf| {
+ return output_buf.value;
+ } else {
+ var array_buffer_out = JSC.ArrayBuffer.fromBytes(bun.default_allocator.dupe(u8, &output_digest_buf) catch unreachable, .Uint8Array);
+ return array_buffer_out.toJSUnchecked(globalThis.ref(), exception);
+ }
+ }
+
+ fn digestToEncoding(this: *@This(), globalThis: *JSGlobalObject, exception: JSC.C.ExceptionRef, encoding: JSC.Node.Encoding) JSC.JSValue {
+ var output_digest_buf: Hasher.Digest = comptime brk: {
+ var bytes: Hasher.Digest = undefined;
+ var i: usize = 0;
+ while (i < Hasher.digest) {
+ bytes[i] = 0;
+ i += 1;
+ }
+ break :brk bytes;
+ };
+
+ var output_digest_slice: *Hasher.Digest = &output_digest_buf;
+
+ this.hashing.final(output_digest_slice);
+
+ return encoding.encodeWithSize(globalThis, Hasher.digest, output_digest_slice, exception);
+ }
+
+ pub fn finalize(this: *@This()) void {
+ VirtualMachine.vm.allocator.destroy(this);
+ }
+ };
+ }
+
+ pub const SHA1 = CryptoHasher(Hashers.SHA1, "SHA1", "Bun_Crypto_SHA1");
+ pub const MD5 = CryptoHasher(Hashers.MD5, "MD5", "Bun_Crypto_MD5");
+ pub const MD4 = CryptoHasher(Hashers.MD4, "MD4", "Bun_Crypto_MD4");
+ pub const SHA224 = CryptoHasher(Hashers.SHA224, "SHA224", "Bun_Crypto_SHA224");
+ pub const SHA512 = CryptoHasher(Hashers.SHA512, "SHA512", "Bun_Crypto_SHA512");
+ pub const SHA384 = CryptoHasher(Hashers.SHA384, "SHA384", "Bun_Crypto_SHA384");
+ pub const SHA256 = CryptoHasher(Hashers.SHA256, "SHA256", "Bun_Crypto_SHA256");
+ pub const SHA512_256 = CryptoHasher(Hashers.SHA512_256, "SHA512_256", "Bun_Crypto_SHA512_256");
+ pub const MD5_SHA1 = CryptoHasher(Hashers.MD5_SHA1, "MD5_SHA1", "Bun_Crypto_MD5_SHA1");
+};
+
+pub fn nanoseconds(
+ _: void,
+ _: JSC.C.JSContextRef,
+ _: JSC.C.JSObjectRef,
+ _: JSC.C.JSObjectRef,
+ _: []const JSC.C.JSValueRef,
+ _: JSC.C.ExceptionRef,
+) JSC.C.JSValueRef {
+ const ns = JSC.VirtualMachine.vm.origin_timer.read();
+ return JSC.JSValue.jsNumberFromUint64(ns).asObjectRef();
+}
+
+pub fn serve(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+) js.JSValueRef {
+ var args = JSC.Node.ArgumentsSlice.from(ctx.bunVM(), arguments);
+ var config = JSC.API.ServerConfig.fromJS(ctx.ptr(), &args, exception);
+ if (exception.* != null) {
+ return null;
+ }
+
+ // Listen happens on the next tick!
+ // This is so we can return a Server object
+ if (config.ssl_config != null) {
+ if (config.development) {
+ var server = JSC.API.DebugSSLServer.init(config, ctx.ptr());
+ server.listen();
+ if (!server.thisObject.isEmpty()) {
+ exception.* = server.thisObject.asObjectRef();
+ server.thisObject = JSC.JSValue.zero;
+ server.deinit();
+ return null;
+ }
+ var obj = JSC.API.DebugSSLServer.Class.make(ctx, server);
+ JSC.C.JSValueProtect(ctx, obj);
+ server.thisObject = JSValue.c(obj);
+ return obj;
+ } else {
+ var server = JSC.API.SSLServer.init(config, ctx.ptr());
+ server.listen();
+ if (!server.thisObject.isEmpty()) {
+ exception.* = server.thisObject.asObjectRef();
+ server.thisObject = JSC.JSValue.zero;
+ server.deinit();
+ return null;
+ }
+ var obj = JSC.API.SSLServer.Class.make(ctx, server);
+ JSC.C.JSValueProtect(ctx, obj);
+ server.thisObject = JSValue.c(obj);
+ return obj;
+ }
+ } else {
+ if (config.development) {
+ var server = JSC.API.DebugServer.init(config, ctx.ptr());
+ server.listen();
+ if (!server.thisObject.isEmpty()) {
+ exception.* = server.thisObject.asObjectRef();
+ server.thisObject = JSC.JSValue.zero;
+ server.deinit();
+ return null;
+ }
+ var obj = JSC.API.DebugServer.Class.make(ctx, server);
+ JSC.C.JSValueProtect(ctx, obj);
+ server.thisObject = JSValue.c(obj);
+ return obj;
+ } else {
+ var server = JSC.API.Server.init(config, ctx.ptr());
+ server.listen();
+ if (!server.thisObject.isEmpty()) {
+ exception.* = server.thisObject.asObjectRef();
+ server.thisObject = JSC.JSValue.zero;
+ server.deinit();
+ return null;
+ }
+ var obj = JSC.API.Server.Class.make(ctx, server);
+ JSC.C.JSValueProtect(ctx, obj);
+ server.thisObject = JSValue.c(obj);
+ return obj;
+ }
+ }
+
+ unreachable;
+}
+
+pub export fn Bun__escapeHTML(
+ globalObject: *JSGlobalObject,
+ callframe: *JSC.CallFrame,
+) JSC.JSValue {
+ const arguments = callframe.arguments();
+ if (arguments.len < 1) {
+ return ZigString.Empty.toValue(globalObject);
+ }
+
+ const input_value = arguments[0];
+ const zig_str = input_value.getZigString(globalObject);
+ if (zig_str.len == 0)
+ return ZigString.Empty.toValue(globalObject);
+
+ if (zig_str.is16Bit()) {
+ const input_slice = zig_str.utf16SliceAligned();
+ const escaped = strings.escapeHTMLForUTF16Input(globalObject.bunVM().allocator, input_slice) catch {
+ globalObject.vm().throwError(globalObject, ZigString.init("Out of memory").toValue(globalObject));
+ return JSC.JSValue.jsUndefined();
+ };
+
+ switch (escaped) {
+ .static => |val| {
+ return ZigString.init(val).toValue(globalObject);
+ },
+ .original => return input_value,
+ .allocated => |escaped_html| {
+ if (comptime Environment.allow_assert) {
+ // assert that re-encoding the string produces the same result
+ std.debug.assert(
+ std.mem.eql(
+ u16,
+ (strings.toUTF16Alloc(bun.default_allocator, strings.toUTF8Alloc(bun.default_allocator, escaped_html) catch unreachable, false) catch unreachable).?,
+ escaped_html,
+ ),
+ );
+
+ // assert we do not allocate a new string unnecessarily
+ std.debug.assert(
+ !std.mem.eql(
+ u16,
+ input_slice,
+ escaped_html,
+ ),
+ );
+
+ // the output should always be longer than the input
+ std.debug.assert(escaped_html.len > input_slice.len);
+ }
+
+ return ZigString.from16(escaped_html.ptr, escaped_html.len).toExternalValue(globalObject);
+ },
+ }
+ } else {
+ const input_slice = zig_str.slice();
+ const escaped = strings.escapeHTMLForLatin1Input(globalObject.bunVM().allocator, input_slice) catch {
+ globalObject.vm().throwError(globalObject, ZigString.init("Out of memory").toValue(globalObject));
+ return JSC.JSValue.jsUndefined();
+ };
+
+ switch (escaped) {
+ .static => |val| {
+ return ZigString.init(val).toValue(globalObject);
+ },
+ .original => return input_value,
+ .allocated => |escaped_html| {
+ if (comptime Environment.allow_assert) {
+ // the output should always be longer than the input
+ std.debug.assert(escaped_html.len > input_slice.len);
+
+ // assert we do not allocate a new string unnecessarily
+ std.debug.assert(
+ !std.mem.eql(
+ u8,
+ input_slice,
+ escaped_html,
+ ),
+ );
+ }
+
+ return ZigString.init(escaped_html).toExternalValue(globalObject);
+ },
+ }
+ }
+}
+
+comptime {
+ if (!JSC.is_bindgen) {
+ _ = Bun__escapeHTML;
+ }
+}
+
+pub fn allocUnsafe(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+) js.JSValueRef {
+ var args = JSC.Node.ArgumentsSlice.from(ctx.bunVM(), arguments);
+
+ const length = @intCast(
+ usize,
+ @minimum(
+ @maximum(1, (args.nextEat() orelse JSC.JSValue.jsNumber(@as(i32, 1))).toInt32()),
+ std.math.maxInt(i32),
+ ),
+ );
+ var bytes = bun.default_allocator.alloc(u8, length) catch {
+ JSC.JSError(bun.default_allocator, "OOM! Out of memory", .{}, ctx, exception);
+ return null;
+ };
+
+ return JSC.MarkedArrayBuffer.fromBytes(
+ bytes,
+ bun.default_allocator,
+ .Uint8Array,
+ ).toJSObjectRef(ctx, null);
+}
+
+pub fn mmapFile(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+) js.JSValueRef {
+ var args = JSC.Node.ArgumentsSlice.from(ctx.bunVM(), arguments);
+
+ var buf: [bun.MAX_PATH_BYTES]u8 = undefined;
+ const path = getFilePath(ctx, arguments[0..@minimum(1, arguments.len)], &buf, exception) orelse return null;
+ args.eat();
+
+ buf[path.len] = 0;
+
+ const buf_z: [:0]const u8 = buf[0..path.len :0];
+
+ const sync_flags: u32 = if (@hasDecl(std.os.MAP, "SYNC")) std.os.MAP.SYNC | std.os.MAP.SHARED_VALIDATE else 0;
+ const file_flags: u32 = if (@hasDecl(std.os.MAP, "FILE")) std.os.MAP.FILE else 0;
+
+ // Conforming applications must specify either MAP_PRIVATE or MAP_SHARED.
+ var offset: usize = 0;
+ var flags = file_flags;
+ var map_size: ?usize = null;
+
+ if (args.nextEat()) |opts| {
+ const sync = opts.get(ctx.ptr(), "sync") orelse JSC.JSValue.jsBoolean(false);
+ const shared = opts.get(ctx.ptr(), "shared") orelse JSC.JSValue.jsBoolean(true);
+ flags |= @as(u32, if (sync.toBoolean()) sync_flags else 0);
+ flags |= @as(u32, if (shared.toBoolean()) std.os.MAP.SHARED else std.os.MAP.PRIVATE);
+
+ if (opts.get(ctx.ptr(), "size")) |value| {
+ map_size = @intCast(usize, value.toInt64());
+ }
+
+ if (opts.get(ctx.ptr(), "offset")) |value| {
+ offset = @intCast(usize, value.toInt64());
+ offset = std.mem.alignBackwardAnyAlign(offset, std.mem.page_size);
+ }
+ } else {
+ flags |= std.os.MAP.SHARED;
+ }
+
+ const map = switch (JSC.Node.Syscall.mmapFile(buf_z, flags, map_size, offset)) {
+ .result => |map| map,
+
+ .err => |err| {
+ exception.* = err.toJS(ctx);
+ return null;
+ },
+ };
+
+ return JSC.C.JSObjectMakeTypedArrayWithBytesNoCopy(ctx, JSC.C.JSTypedArrayType.kJSTypedArrayTypeUint8Array, @ptrCast(?*anyopaque, map.ptr), map.len, struct {
+ pub fn x(ptr: ?*anyopaque, size: ?*anyopaque) callconv(.C) void {
+ _ = JSC.Node.Syscall.munmap(@ptrCast([*]align(std.mem.page_size) u8, @alignCast(std.mem.page_size, ptr))[0..@ptrToInt(size)]);
+ }
+ }.x, @intToPtr(?*anyopaque, map.len), exception);
+}
+
+pub fn getTranspilerConstructor(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ var existing = ctx.ptr().getCachedObject(&ZigString.init("BunTranspiler"));
+ if (existing.isEmpty()) {
+ return ctx.ptr().putCachedObject(
+ &ZigString.init("BunTranspiler"),
+ JSC.JSValue.fromRef(Transpiler.Constructor.constructor(ctx)),
+ ).asObjectRef();
+ }
+
+ return existing.asObjectRef();
+}
+
+pub fn getHashObject(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ var existing = ctx.ptr().getCachedObject(&ZigString.init("BunHash"));
+ if (existing.isEmpty()) {
+ return ctx.ptr().putCachedObject(
+ &ZigString.init("BunHash"),
+ JSC.JSValue.fromRef(JSC.C.JSObjectMake(ctx, Hash.Class.get().*, null)),
+ ).asObjectRef();
+ }
+
+ return existing.asObjectRef();
+}
+
+pub const Hash = struct {
+ pub const Class = NewClass(
+ void,
+ .{
+ .name = "Hash",
+ },
+ .{
+ .call = .{
+ .rfn = call,
+ },
+ .wyhash = .{
+ .rfn = hashWrap(std.hash.Wyhash).hash,
+ },
+ .adler32 = .{
+ .rfn = hashWrap(std.hash.Adler32).hash,
+ },
+ .crc32 = .{
+ .rfn = hashWrap(std.hash.Crc32).hash,
+ },
+ .cityHash32 = .{
+ .rfn = hashWrap(std.hash.CityHash32).hash,
+ },
+ .cityHash64 = .{
+ .rfn = hashWrap(std.hash.CityHash64).hash,
+ },
+ .murmur32v2 = .{
+ .rfn = hashWrap(std.hash.murmur.Murmur2_32).hash,
+ },
+ .murmur32v3 = .{
+ .rfn = hashWrap(std.hash.murmur.Murmur3_32).hash,
+ },
+ .murmur64v2 = .{
+ .rfn = hashWrap(std.hash.murmur.Murmur2_64).hash,
+ },
+ },
+ .{},
+ );
+
+ pub fn call(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) js.JSObjectRef {
+ return hashWrap(std.hash.Wyhash).hash(void{}, ctx, null, null, arguments, exception);
+ }
+ fn hashWrap(comptime Hasher: anytype) type {
+ return struct {
+ pub fn hash(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ var args = JSC.Node.ArgumentsSlice.from(ctx.bunVM(), arguments);
+ var input: []const u8 = "";
+ var input_slice = ZigString.Slice.empty;
+ defer input_slice.deinit();
+ if (args.nextEat()) |arg| {
+ if (arg.as(JSC.WebCore.Blob)) |blob| {
+ // TODO: files
+ input = blob.sharedView();
+ } else {
+ switch (arg.jsTypeLoose()) {
+ .ArrayBuffer, .Int8Array, .Uint8Array, .Uint8ClampedArray, .Int16Array, .Uint16Array, .Int32Array, .Uint32Array, .Float32Array, .Float64Array, .BigInt64Array, .BigUint64Array, .DataView => {
+ var array_buffer = arg.asArrayBuffer(ctx.ptr()) orelse {
+ JSC.throwInvalidArguments("ArrayBuffer conversion error", .{}, ctx, exception);
+ return null;
+ };
+ input = array_buffer.slice();
+ },
+ else => {
+ input_slice = arg.toSlice(ctx.ptr(), bun.default_allocator);
+ input = input_slice.slice();
+ },
+ }
+ }
+ }
+
+ // std.hash has inconsistent interfaces
+ //
+ const Function = if (@hasDecl(Hasher, "hashWithSeed")) Hasher.hashWithSeed else Hasher.hash;
+ var function_args: std.meta.ArgsTuple(@TypeOf(Function)) = undefined;
+ if (comptime std.meta.fields(std.meta.ArgsTuple(@TypeOf(Function))).len == 1) {
+ return JSC.JSValue.jsNumber(Function(input)).asObjectRef();
+ } else {
+ var seed: u64 = 0;
+ if (args.nextEat()) |arg| {
+ if (arg.isNumber()) {
+ seed = arg.toU32();
+ }
+ }
+ if (comptime std.meta.trait.isNumber(@TypeOf(function_args[0]))) {
+ function_args[0] = @intCast(@TypeOf(function_args[0]), seed);
+ function_args[1] = input;
+ } else {
+ function_args[1] = @intCast(@TypeOf(function_args[1]), seed);
+ function_args[0] = input;
+ }
+
+ const value = @call(.{}, Function, function_args);
+
+ if (@TypeOf(value) == u32) {
+ return JSC.JSValue.jsNumber(@bitCast(i32, value)).asObjectRef();
+ }
+ return JSC.JSValue.jsNumber(value).asObjectRef();
+ }
+ }
+ };
+ }
+};
+
+pub fn getTOMLObject(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ var existing = ctx.ptr().getCachedObject(&ZigString.init("TOML"));
+ if (existing.isEmpty()) {
+ return ctx.ptr().putCachedObject(
+ &ZigString.init("TOML"),
+ JSValue.fromRef(js.JSObjectMake(ctx, TOML.Class.get().?[0], null)),
+ ).asObjectRef();
+ }
+
+ return existing.asObjectRef();
+}
+
+pub fn getUnsafe(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ var existing = ctx.ptr().getCachedObject(&ZigString.init("Unsafe"));
+ if (existing.isEmpty()) {
+ return ctx.ptr().putCachedObject(
+ &ZigString.init("Unsafe"),
+ JSValue.fromRef(js.JSObjectMake(ctx, Unsafe.Class.get().?[0], null)),
+ ).asObjectRef();
+ }
+
+ return existing.asObjectRef();
+}
+
+pub const Unsafe = struct {
+ pub const Class = NewClass(
+ void,
+ .{ .name = "Unsafe", .read_only = true },
+ .{
+ .segfault = .{
+ .rfn = __debug__doSegfault,
+ },
+ .arrayBufferToString = .{
+ .rfn = arrayBufferToString,
+ },
+ },
+ .{},
+ );
+
+ // For testing the segfault handler
+ pub fn __debug__doSegfault(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ _: []const js.JSValueRef,
+ _: js.ExceptionRef,
+ ) js.JSValueRef {
+ _ = ctx;
+ const Reporter = @import("../../report.zig");
+ Reporter.globalError(error.SegfaultTest);
+ }
+
+ pub fn arrayBufferToString(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ args: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ const array_buffer = JSC.ArrayBuffer.fromTypedArray(ctx, JSC.JSValue.fromRef(args[0]), exception);
+ switch (array_buffer.typed_array_type) {
+ .Uint16Array, .Int16Array => {
+ var zig_str = ZigString.init("");
+ zig_str.ptr = @ptrCast([*]const u8, @alignCast(@alignOf([*]align(1) const u16), array_buffer.ptr));
+ zig_str.len = array_buffer.len;
+ zig_str.markUTF16();
+ // the deinitializer for string causes segfaults
+ // if we don't clone it
+ return ZigString.toValueGC(&zig_str, ctx.ptr()).asObjectRef();
+ },
+ else => {
+ // the deinitializer for string causes segfaults
+ // if we don't clone it
+ return ZigString.init(array_buffer.slice()).toValueGC(ctx.ptr()).asObjectRef();
+ },
+ }
+ }
+};
+
+// pub const Lockfile = struct {
+// const BunLockfile = @import("../../install/install.zig").Lockfile;
+// lockfile: *BunLockfile,
+
+// pub const RefCountedLockfile = bun.RefCount(Lockfile, true);
+
+// pub const StaticClass = NewClass(
+// void,
+// .{
+// .name = "Lockfile",
+// .read_only = true,
+// },
+// .{
+// .load = .{
+// .rfn = BunLockfile.load,
+// },
+// },
+// .{},
+// );
+
+// pub const Class = NewClass(
+// RefCountedLockfile,
+// .{
+// .name = "Lockfile",
+// .read_only = true,
+// },
+// .{
+// .findPackagesByName = .{
+// .rfn = BunLockfile.load,
+// },
+// .dependencies = .{
+// .rfn = BunLockfile.load,
+// },
+// },
+// .{},
+// );
+
+// pub fn deinit(this: *Lockfile) void {
+// this.lockfile.deinit();
+// }
+
+// pub fn load(
+// // this
+// _: void,
+// ctx: js.JSContextRef,
+// // function
+// _: js.JSObjectRef,
+// // thisObject
+// _: js.JSObjectRef,
+// arguments: []const js.JSValueRef,
+// exception: js.ExceptionRef,
+// ) js.JSValueRef {
+// if (arguments.len == 0) {
+// JSError(undefined, "Expected file path string or buffer", .{}, ctx, exception);
+// return null;
+// }
+
+// var lockfile: *BunLockfile = getAllocator(ctx).create(BunLockfile) catch return JSValue.jsUndefined().asRef();
+
+// var log = logger.Log.init(default_allocator);
+// var args_slice = @ptrCast([*]const JSValue, arguments.ptr)[0..arguments.len];
+
+// var arguments_slice = Node.ArgumentsSlice.init(args_slice);
+// var path_or_buffer = Node.PathLike.fromJS(ctx, &arguments_slice, exception) orelse {
+// getAllocator(ctx).destroy(lockfile);
+// JSError(undefined, "Expected file path string or buffer", .{}, ctx, exception);
+// return null;
+// };
+
+// const load_from_disk_result = switch (path_or_buffer) {
+// Node.PathLike.Tag.string => lockfile.loadFromDisk(getAllocator(ctx), &log, path_or_buffer.string),
+// Node.PathLike.Tag.buffer => lockfile.loadFromBytes(getAllocator(ctx), path_or_buffer.buffer.slice(), &log),
+// else => {
+// getAllocator(ctx).destroy(lockfile);
+// JSError(undefined, "Expected file path string or buffer", .{}, ctx, exception);
+// return null;
+// },
+// };
+
+// switch (load_from_disk_result) {
+// .err => |cause| {
+// defer getAllocator(ctx).destroy(lockfile);
+// switch (cause.step) {
+// .open_file => {
+// JSError(undefined, "error opening lockfile: {s}", .{
+// @errorName(cause.value),
+// }, ctx, exception);
+// return null;
+// },
+// .parse_file => {
+// JSError(undefined, "error parsing lockfile: {s}", .{
+// @errorName(cause.value),
+// }, ctx, exception);
+// return null;
+// },
+// .read_file => {
+// JSError(undefined, "error reading lockfile: {s}", .{
+// @errorName(cause.value),
+// }, ctx, exception);
+// return null;
+// },
+// }
+// },
+// .ok => {},
+// }
+// }
+// };
+
+pub const TOML = struct {
+ const TOMLParser = @import("../../toml/toml_parser.zig").TOML;
+ pub const Class = NewClass(
+ void,
+ .{
+ .name = "TOML",
+ .read_only = true,
+ },
+ .{
+ .parse = .{
+ .rfn = TOML.parse,
+ },
+ },
+ .{},
+ );
+
+ pub fn parse(
+ // this
+ _: void,
+ ctx: js.JSContextRef,
+ // function
+ _: js.JSObjectRef,
+ // thisObject
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ var arena = std.heap.ArenaAllocator.init(getAllocator(ctx));
+ var allocator = arena.allocator();
+ defer arena.deinit();
+ var log = logger.Log.init(default_allocator);
+ var input_str = ZigString.init("");
+ JSValue.fromRef(arguments[0]).toZigString(&input_str, ctx.ptr());
+ var needs_deinit = false;
+ var input = input_str.slice();
+ if (input_str.is16Bit()) {
+ input = std.fmt.allocPrint(allocator, "{}", .{input_str}) catch unreachable;
+ needs_deinit = true;
+ }
+ var source = logger.Source.initPathString("input.toml", input);
+ var parse_result = TOMLParser.parse(&source, &log, allocator) catch {
+ exception.* = log.toJS(ctx.ptr(), default_allocator, "Failed to parse toml").asObjectRef();
+ return null;
+ };
+
+ // for now...
+ var buffer_writer = try js_printer.BufferWriter.init(allocator);
+ var writer = js_printer.BufferPrinter.init(buffer_writer);
+ _ = js_printer.printJSON(*js_printer.BufferPrinter, &writer, parse_result, &source) catch {
+ exception.* = log.toJS(ctx.ptr(), default_allocator, "Failed to print toml").asObjectRef();
+ return null;
+ };
+
+ var slice = writer.ctx.buffer.toOwnedSliceLeaky();
+ var out = ZigString.init(slice);
+
+ const out_value = js.JSValueMakeFromJSONString(ctx, out.toJSStringRef());
+ return out_value;
+ }
+};
+
+pub const Timer = struct {
+ last_id: i32 = 0,
+ warned: bool = false,
+ active: u32 = 0,
+ timeouts: TimeoutMap = TimeoutMap{},
+
+ const TimeoutMap = std.AutoArrayHashMapUnmanaged(i32, *Timeout);
+
+ pub fn getNextID() callconv(.C) i32 {
+ VirtualMachine.vm.timer.last_id += 1;
+ return VirtualMachine.vm.timer.last_id;
+ }
+
+ pub const Timeout = struct {
+ id: i32 = 0,
+ callback: JSValue,
+ interval: i32 = 0,
+ completion: NetworkThread.Completion = undefined,
+ repeat: bool = false,
+ io_task: ?*TimeoutTask = null,
+ cancelled: bool = false,
+
+ pub const TimeoutTask = IOTask(Timeout);
+
+ pub fn run(this: *Timeout, _task: *TimeoutTask) void {
+ this.io_task = _task;
+ NetworkThread.global.pool.io.?.timeout(
+ *Timeout,
+ this,
+ onCallback,
+ &this.completion,
+ std.time.ns_per_ms * @intCast(
+ u63,
+ @maximum(
+ this.interval,
+ 1,
+ ),
+ ),
+ );
+ }
+
+ pub fn onCallback(this: *Timeout, _: *NetworkThread.Completion, _: NetworkThread.AsyncIO.TimeoutError!void) void {
+ this.io_task.?.onFinish();
+ }
+
+ pub fn then(this: *Timeout, global: *JSGlobalObject) void {
+ if (comptime JSC.is_bindgen)
+ unreachable;
+
+ if (!this.cancelled) {
+ if (this.repeat) {
+ this.io_task.?.deinit();
+ var task = Timeout.TimeoutTask.createOnJSThread(VirtualMachine.vm.allocator, global, this) catch unreachable;
+ this.io_task = task;
+ task.schedule();
+ }
+
+ _ = JSC.C.JSObjectCallAsFunction(global.ref(), this.callback.asObjectRef(), null, 0, null, null);
+
+ if (this.repeat)
+ return;
+ }
+
+ this.clear(global);
+ }
+
+ pub fn clear(this: *Timeout, global: *JSGlobalObject) void {
+ if (comptime JSC.is_bindgen)
+ unreachable;
+
+ this.cancelled = true;
+ JSC.C.JSValueUnprotect(global.ref(), this.callback.asObjectRef());
+ _ = VirtualMachine.vm.timer.timeouts.swapRemove(this.id);
+ if (this.io_task) |task| {
+ task.deinit();
+ }
+ VirtualMachine.vm.allocator.destroy(this);
+ VirtualMachine.vm.timer.active -|= 1;
+ VirtualMachine.vm.active_tasks -|= 1;
+ }
+ };
+
+ fn set(
+ id: i32,
+ globalThis: *JSGlobalObject,
+ callback: JSValue,
+ countdown: JSValue,
+ repeat: bool,
+ ) !void {
+ if (comptime is_bindgen) unreachable;
+ var timeout = try VirtualMachine.vm.allocator.create(Timeout);
+ js.JSValueProtect(globalThis.ref(), callback.asObjectRef());
+ timeout.* = Timeout{ .id = id, .callback = callback, .interval = countdown.toInt32(), .repeat = repeat };
+ var task = try Timeout.TimeoutTask.createOnJSThread(VirtualMachine.vm.allocator, globalThis, timeout);
+ VirtualMachine.vm.timer.timeouts.put(VirtualMachine.vm.allocator, id, timeout) catch unreachable;
+ VirtualMachine.vm.timer.active +|= 1;
+ VirtualMachine.vm.active_tasks +|= 1;
+ task.schedule();
+ }
+
+ pub fn setTimeout(
+ globalThis: *JSGlobalObject,
+ callback: JSValue,
+ countdown: JSValue,
+ ) callconv(.C) JSValue {
+ if (comptime is_bindgen) unreachable;
+ const id = VirtualMachine.vm.timer.last_id;
+ VirtualMachine.vm.timer.last_id +%= 1;
+
+ Timer.set(id, globalThis, callback, countdown, false) catch
+ return JSValue.jsUndefined();
+
+ return JSValue.jsNumberWithType(i32, id);
+ }
+ pub fn setInterval(
+ globalThis: *JSGlobalObject,
+ callback: JSValue,
+ countdown: JSValue,
+ ) callconv(.C) JSValue {
+ if (comptime is_bindgen) unreachable;
+ const id = VirtualMachine.vm.timer.last_id;
+ VirtualMachine.vm.timer.last_id +%= 1;
+
+ Timer.set(id, globalThis, callback, countdown, true) catch
+ return JSValue.jsUndefined();
+
+ return JSValue.jsNumberWithType(i32, id);
+ }
+
+ pub fn clearTimer(id: JSValue, _: *JSGlobalObject) void {
+ if (comptime is_bindgen) unreachable;
+ var timer: *Timeout = VirtualMachine.vm.timer.timeouts.get(id.toInt32()) orelse return;
+ timer.cancelled = true;
+ }
+
+ pub fn clearTimeout(
+ globalThis: *JSGlobalObject,
+ id: JSValue,
+ ) callconv(.C) JSValue {
+ if (comptime is_bindgen) unreachable;
+ Timer.clearTimer(id, globalThis);
+ return JSValue.jsUndefined();
+ }
+ pub fn clearInterval(
+ globalThis: *JSGlobalObject,
+ id: JSValue,
+ ) callconv(.C) JSValue {
+ if (comptime is_bindgen) unreachable;
+ Timer.clearTimer(id, globalThis);
+ return JSValue.jsUndefined();
+ }
+
+ const Shimmer = @import("../bindings/shimmer.zig").Shimmer;
+
+ pub const shim = Shimmer("Bun", "Timer", @This());
+ pub const name = "Bun__Timer";
+ pub const include = "";
+ pub const namespace = shim.namespace;
+
+ pub const Export = shim.exportFunctions(.{
+ .@"setTimeout" = setTimeout,
+ .@"setInterval" = setInterval,
+ .@"clearTimeout" = clearTimeout,
+ .@"clearInterval" = clearInterval,
+ .@"getNextID" = getNextID,
+ });
+
+ comptime {
+ if (!JSC.is_bindgen) {
+ @export(setTimeout, .{ .name = Export[0].symbol_name });
+ @export(setInterval, .{ .name = Export[1].symbol_name });
+ @export(clearTimeout, .{ .name = Export[2].symbol_name });
+ @export(clearInterval, .{ .name = Export[3].symbol_name });
+ @export(getNextID, .{ .name = Export[4].symbol_name });
+ }
+ }
+};
+
+pub const FFI = struct {
+ pub const Class = NewClass(
+ void,
+ .{
+ .name = "FFI",
+ },
+ .{
+ .viewSource = .{
+ .rfn = JSC.wrapWithHasContainer(JSC.FFI, "print", false, false, true),
+ },
+ .dlopen = .{
+ .rfn = JSC.wrapWithHasContainer(JSC.FFI, "open", false, false, true),
+ },
+ .callback = .{
+ .rfn = JSC.wrapWithHasContainer(JSC.FFI, "callback", false, false, false),
+ },
+ .linkSymbols = .{
+ .rfn = JSC.wrapWithHasContainer(JSC.FFI, "linkSymbols", false, false, false),
+ },
+ .ptr = .{
+ .rfn = JSC.wrapWithHasContainer(@This(), "ptr", false, false, true),
+ },
+ .toBuffer = .{
+ .rfn = JSC.wrapWithHasContainer(@This(), "toBuffer", false, false, true),
+ },
+ .toArrayBuffer = .{
+ .rfn = JSC.wrapWithHasContainer(@This(), "toArrayBuffer", false, false, true),
+ },
+ },
+ .{
+ .CString = .{
+ .get = UnsafeCString.getter,
+ },
+ },
+ );
+
+ pub fn ptr(
+ globalThis: *JSGlobalObject,
+ value: JSValue,
+ byteOffset: ?JSValue,
+ ) JSValue {
+ if (value.isEmpty()) {
+ return JSC.JSValue.jsNull();
+ }
+
+ const array_buffer = value.asArrayBuffer(globalThis) orelse {
+ return JSC.toInvalidArguments("Expected ArrayBufferView", .{}, globalThis.ref());
+ };
+
+ if (array_buffer.len == 0) {
+ return JSC.toInvalidArguments("ArrayBufferView must have a length > 0. A pointer to empty memory doesn't work", .{}, globalThis.ref());
+ }
+
+ var addr: usize = @ptrToInt(array_buffer.ptr);
+
+ if (byteOffset) |off| {
+ if (!off.isEmptyOrUndefinedOrNull()) {
+ if (!off.isNumber()) {
+ return JSC.toInvalidArguments("Expected number for byteOffset", .{}, globalThis.ref());
+ }
+ }
+
+ const bytei64 = off.toInt64();
+ if (bytei64 < 0) {
+ addr -|= @intCast(usize, bytei64 * -1);
+ } else {
+ addr += @intCast(usize, bytei64);
+ }
+
+ if (addr > @ptrToInt(array_buffer.ptr) + @as(usize, array_buffer.byte_len)) {
+ return JSC.toInvalidArguments("byteOffset out of bounds", .{}, globalThis.ref());
+ }
+ }
+
+ if (addr > max_addressible_memory) {
+ return JSC.toInvalidArguments("Pointer is outside max addressible memory, which usually means a bug in your program.", .{}, globalThis.ref());
+ }
+
+ if (addr == 0) {
+ return JSC.toInvalidArguments("Pointer must not be 0", .{}, globalThis.ref());
+ }
+
+ if (addr == 0xDEADBEEF or addr == 0xaaaaaaaa or addr == 0xAAAAAAAA) {
+ return JSC.toInvalidArguments("ptr to invalid memory, that would segfault Bun :(", .{}, globalThis.ref());
+ }
+
+ return JSC.JSValue.fromPtrAddress(addr);
+ }
+
+ const ValueOrError = union(enum) {
+ err: JSValue,
+ slice: []u8,
+ };
+
+ pub fn getPtrSlice(globalThis: *JSGlobalObject, value: JSValue, byteOffset: ?JSValue, byteLength: ?JSValue) ValueOrError {
+ if (!value.isNumber()) {
+ return .{ .err = JSC.toInvalidArguments("ptr must be a number.", .{}, globalThis.ref()) };
+ }
+
+ const num = value.asNumber();
+ if (num == 0) {
+ return .{ .err = JSC.toInvalidArguments("ptr cannot be zero, that would segfault Bun :(", .{}, globalThis.ref()) };
+ }
+
+ if (!std.math.isFinite(num)) {
+ return .{ .err = JSC.toInvalidArguments("ptr must be a finite number.", .{}, globalThis.ref()) };
+ }
+
+ var addr = @bitCast(usize, num);
+
+ if (byteOffset) |byte_off| {
+ if (byte_off.isNumber()) {
+ const off = byte_off.toInt64();
+ if (off < 0) {
+ addr -|= @intCast(usize, off * -1);
+ } else {
+ addr +|= @intCast(usize, off);
+ }
+
+ if (addr == 0) {
+ return .{ .err = JSC.toInvalidArguments("ptr cannot be zero, that would segfault Bun :(", .{}, globalThis.ref()) };
+ }
+
+ if (!std.math.isFinite(byte_off.asNumber())) {
+ return .{ .err = JSC.toInvalidArguments("ptr must be a finite number.", .{}, globalThis.ref()) };
+ }
+ } else if (!byte_off.isEmptyOrUndefinedOrNull()) {
+ // do nothing
+ } else {
+ return .{ .err = JSC.toInvalidArguments("Expected number for byteOffset", .{}, globalThis.ref()) };
+ }
+ }
+
+ if (addr == 0xDEADBEEF or addr == 0xaaaaaaaa or addr == 0xAAAAAAAA) {
+ return .{ .err = JSC.toInvalidArguments("ptr to invalid memory, that would segfault Bun :(", .{}, globalThis.ref()) };
+ }
+
+ if (byteLength) |valueLength| {
+ if (!valueLength.isEmptyOrUndefinedOrNull()) {
+ if (!valueLength.isNumber()) {
+ return .{ .err = JSC.toInvalidArguments("length must be a number.", .{}, globalThis.ref()) };
+ }
+
+ if (valueLength.asNumber() == 0.0) {
+ return .{ .err = JSC.toInvalidArguments("length must be > 0. This usually means a bug in your code.", .{}, globalThis.ref()) };
+ }
+
+ const length_i = valueLength.toInt64();
+ if (length_i < 0) {
+ return .{ .err = JSC.toInvalidArguments("length must be > 0. This usually means a bug in your code.", .{}, globalThis.ref()) };
+ }
+
+ if (length_i > max_addressible_memory) {
+ return .{ .err = JSC.toInvalidArguments("length exceeds max addressable memory. This usually means a bug in your code.", .{}, globalThis.ref()) };
+ }
+
+ const length = @intCast(usize, length_i);
+ return .{ .slice = @intToPtr([*]u8, addr)[0..length] };
+ }
+ }
+
+ return .{ .slice = bun.span(@intToPtr([*:0]u8, addr)) };
+ }
+
+ pub fn toArrayBuffer(
+ globalThis: *JSGlobalObject,
+ value: JSValue,
+ byteOffset: ?JSValue,
+ valueLength: ?JSValue,
+ ) JSC.JSValue {
+ switch (getPtrSlice(globalThis, value, byteOffset, valueLength)) {
+ .err => |erro| {
+ return erro;
+ },
+ .slice => |slice| {
+ return JSC.ArrayBuffer.fromBytes(slice, JSC.JSValue.JSType.ArrayBuffer).toJSWithContext(globalThis.ref(), null, null, null);
+ },
+ }
+ }
+
+ pub fn toBuffer(
+ globalThis: *JSGlobalObject,
+ value: JSValue,
+ byteOffset: ?JSValue,
+ valueLength: ?JSValue,
+ ) JSC.JSValue {
+ switch (getPtrSlice(globalThis, value, byteOffset, valueLength)) {
+ .err => |erro| {
+ return erro;
+ },
+ .slice => |slice| {
+ return JSC.JSValue.createBuffer(globalThis, slice, null);
+ },
+ }
+ }
+
+ pub fn toCStringBuffer(
+ globalThis: *JSGlobalObject,
+ value: JSValue,
+ byteOffset: ?JSValue,
+ valueLength: ?JSValue,
+ ) JSC.JSValue {
+ switch (getPtrSlice(globalThis, value, byteOffset, valueLength)) {
+ .err => |erro| {
+ return erro;
+ },
+ .slice => |slice| {
+ return JSC.JSValue.createBuffer(globalThis, slice, null);
+ },
+ }
+ }
+
+ pub fn getter(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+ ) js.JSValueRef {
+ var existing = ctx.ptr().getCachedObject(&ZigString.init("FFI"));
+ if (existing.isEmpty()) {
+ var prototype = JSC.C.JSObjectMake(ctx, FFI.Class.get().?[0], null);
+ var base = JSC.C.JSObjectMake(ctx, null, null);
+ JSC.C.JSObjectSetPrototype(ctx, base, prototype);
+ return ctx.ptr().putCachedObject(
+ &ZigString.init("FFI"),
+ JSValue.fromRef(base),
+ ).asObjectRef();
+ }
+
+ return existing.asObjectRef();
+ }
+};
+
+pub const UnsafeCString = struct {
+ pub fn constructor(
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ len: usize,
+ args: [*c]const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) callconv(.C) js.JSObjectRef {
+ if (len == 0) {
+ JSC.throwInvalidArguments("Expected a ptr", .{}, ctx, exception);
+ return null;
+ }
+
+ return newCString(ctx.ptr(), JSC.JSValue.fromRef(args[0]), if (len > 1) JSC.JSValue.fromRef(args[1]) else null, if (len > 2) JSC.JSValue.fromRef(args[2]) else null).asObjectRef();
+ }
+
+ pub fn newCString(globalThis: *JSGlobalObject, value: JSValue, byteOffset: ?JSValue, lengthValue: ?JSValue) JSC.JSValue {
+ switch (FFI.getPtrSlice(globalThis, value, byteOffset, lengthValue)) {
+ .err => |err| {
+ return err;
+ },
+ .slice => |slice| {
+ return WebCore.Encoder.toString(slice.ptr, slice.len, globalThis, .utf8);
+ },
+ }
+ }
+
+ pub fn getter(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+ ) js.JSValueRef {
+ var existing = ctx.ptr().getCachedObject(&ZigString.init("UnsafeCString"));
+ if (existing.isEmpty()) {
+ return ctx.ptr().putCachedObject(
+ &ZigString.init("UnsafeCString"),
+ JSValue.fromRef(JSC.C.JSObjectMakeConstructor(ctx, null, constructor)),
+ ).asObjectRef();
+ }
+
+ return existing.asObjectRef();
+ }
+};
+
+/// EnvironmentVariables is runtime defined.
+/// Also, you can't iterate over process.env normally since it only exists at build-time otherwise
+// This is aliased to Bun.env
+pub const EnvironmentVariables = struct {
+ pub const Class = NewClass(
+ void,
+ .{
+ .name = "DotEnv",
+ .read_only = true,
+ },
+ .{
+ .getProperty = .{
+ .rfn = getProperty,
+ },
+ .setProperty = .{
+ .rfn = setProperty,
+ },
+ .deleteProperty = .{
+ .rfn = deleteProperty,
+ },
+ .convertToType = .{ .rfn = convertToType },
+ .hasProperty = .{
+ .rfn = hasProperty,
+ },
+ .getPropertyNames = .{
+ .rfn = getPropertyNames,
+ },
+ .toJSON = .{
+ .rfn = toJSON,
+ .name = "toJSON",
+ },
+ },
+ .{},
+ );
+
+ pub fn getter(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+ ) js.JSValueRef {
+ var existing = ctx.ptr().getCachedObject(&ZigString.init("Bun.env"));
+ if (existing.isEmpty()) {
+ return ctx.ptr().putCachedObject(
+ &ZigString.init("Bun.env"),
+ JSValue.fromRef(js.JSObjectMake(ctx, EnvironmentVariables.Class.get().*, null)),
+ ).asObjectRef();
+ }
+
+ return existing.asObjectRef();
+ }
+
+ pub const BooleanString = struct {
+ pub const @"true": string = "true";
+ pub const @"false": string = "false";
+ };
+
+ pub fn getProperty(
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ propertyName: js.JSStringRef,
+ _: js.ExceptionRef,
+ ) callconv(.C) js.JSValueRef {
+ const len = js.JSStringGetLength(propertyName);
+ var ptr = js.JSStringGetCharacters8Ptr(propertyName);
+ var name = ptr[0..len];
+ if (VirtualMachine.vm.bundler.env.map.get(name)) |value| {
+ return ZigString.toRef(value, ctx.ptr());
+ }
+
+ if (Output.enable_ansi_colors) {
+ // https://github.com/chalk/supports-color/blob/main/index.js
+ if (strings.eqlComptime(name, "FORCE_COLOR")) {
+ return ZigString.toRef(BooleanString.@"true", ctx.ptr());
+ }
+ }
+
+ return js.JSValueMakeUndefined(ctx);
+ }
+
+ pub fn toJSON(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ _: []const js.JSValueRef,
+ _: js.ExceptionRef,
+ ) js.JSValueRef {
+ var map = VirtualMachine.vm.bundler.env.map.map;
+ var keys = map.keys();
+ var values = map.values();
+ const StackFallback = std.heap.StackFallbackAllocator(32 * 2 * @sizeOf(ZigString));
+ var stack = StackFallback{
+ .buffer = undefined,
+ .fallback_allocator = bun.default_allocator,
+ .fixed_buffer_allocator = undefined,
+ };
+ var allocator = stack.get();
+ var key_strings_ = allocator.alloc(ZigString, keys.len * 2) catch unreachable;
+ var key_strings = key_strings_[0..keys.len];
+ var value_strings = key_strings_[keys.len..];
+
+ for (keys) |key, i| {
+ key_strings[i] = ZigString.init(key);
+ key_strings[i].detectEncoding();
+ value_strings[i] = ZigString.init(values[i]);
+ value_strings[i].detectEncoding();
+ }
+
+ var result = JSValue.fromEntries(ctx.ptr(), key_strings.ptr, value_strings.ptr, keys.len, false).asObjectRef();
+ allocator.free(key_strings_);
+ return result;
+ // }
+ // ZigConsoleClient.Formatter.format(this: *Formatter, result: Tag.Result, comptime Writer: type, writer: Writer, value: JSValue, globalThis: *JSGlobalObject, comptime enable_ansi_colors: bool)
+ }
+
+ pub fn deleteProperty(
+ _: js.JSContextRef,
+ _: js.JSObjectRef,
+ propertyName: js.JSStringRef,
+ _: js.ExceptionRef,
+ ) callconv(.C) bool {
+ const len = js.JSStringGetLength(propertyName);
+ var ptr = js.JSStringGetCharacters8Ptr(propertyName);
+ var name = ptr[0..len];
+ _ = VirtualMachine.vm.bundler.env.map.map.swapRemove(name);
+ return true;
+ }
+
+ pub fn setProperty(
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ propertyName: js.JSStringRef,
+ value: js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) callconv(.C) bool {
+ const len = js.JSStringGetLength(propertyName);
+ var ptr = js.JSStringGetCharacters8Ptr(propertyName);
+ var name = ptr[0..len];
+ var val = ZigString.init("");
+ JSValue.fromRef(value).toZigString(&val, ctx.ptr());
+ if (exception.* != null) return false;
+ var result = std.fmt.allocPrint(VirtualMachine.vm.allocator, "{}", .{val}) catch unreachable;
+ VirtualMachine.vm.bundler.env.map.put(name, result) catch unreachable;
+
+ return true;
+ }
+
+ pub fn hasProperty(
+ _: js.JSContextRef,
+ _: js.JSObjectRef,
+ propertyName: js.JSStringRef,
+ ) callconv(.C) bool {
+ const len = js.JSStringGetLength(propertyName);
+ const ptr = js.JSStringGetCharacters8Ptr(propertyName);
+ const name = ptr[0..len];
+ return VirtualMachine.vm.bundler.env.map.get(name) != null or (Output.enable_ansi_colors and strings.eqlComptime(name, "FORCE_COLOR"));
+ }
+
+ pub fn convertToType(ctx: js.JSContextRef, obj: js.JSObjectRef, kind: js.JSType, exception: js.ExceptionRef) callconv(.C) js.JSValueRef {
+ _ = ctx;
+ _ = obj;
+ _ = kind;
+ _ = exception;
+ return obj;
+ }
+
+ pub fn getPropertyNames(
+ _: js.JSContextRef,
+ _: js.JSObjectRef,
+ props: js.JSPropertyNameAccumulatorRef,
+ ) callconv(.C) void {
+ var iter = VirtualMachine.vm.bundler.env.map.iter();
+
+ while (iter.next()) |item| {
+ const str = item.key_ptr.*;
+ js.JSPropertyNameAccumulatorAddName(props, js.JSStringCreateStatic(str.ptr, str.len));
+ }
+ }
+};
+
+export fn Bun__reportError(_: *JSGlobalObject, err: JSC.JSValue) void {
+ JSC.VirtualMachine.vm.defaultErrorHandler(err, null);
+}
+
+comptime {
+ if (!is_bindgen) {
+ _ = Bun__reportError;
+ }
+}
+
+pub const JSZlib = struct {
+ export fn reader_deallocator(_: ?*anyopaque, ctx: ?*anyopaque) void {
+ var reader: *zlib.ZlibReaderArrayList = bun.cast(*zlib.ZlibReaderArrayList, ctx.?);
+ reader.list.deinit(reader.allocator);
+ reader.deinit();
+ }
+
+ export fn compressor_deallocator(_: ?*anyopaque, ctx: ?*anyopaque) void {
+ var compressor: *zlib.ZlibCompressorArrayList = bun.cast(*zlib.ZlibCompressorArrayList, ctx.?);
+ compressor.list.deinit(compressor.allocator);
+ compressor.deinit();
+ }
+
+ pub fn gzipSync(
+ globalThis: *JSGlobalObject,
+ buffer: JSC.Node.StringOrBuffer,
+ options_val_: ?JSValue,
+ ) JSValue {
+ return gzipOrDeflateSync(globalThis, buffer, options_val_, true);
+ }
+
+ pub fn deflateSync(
+ globalThis: *JSGlobalObject,
+ buffer: JSC.Node.StringOrBuffer,
+ options_val_: ?JSValue,
+ ) JSValue {
+ return gzipOrDeflateSync(globalThis, buffer, options_val_, false);
+ }
+
+ pub fn gzipOrDeflateSync(
+ globalThis: *JSGlobalObject,
+ buffer: JSC.Node.StringOrBuffer,
+ options_val_: ?JSValue,
+ is_gzip: bool,
+ ) JSValue {
+ var opts = zlib.Options{ .gzip = is_gzip };
+ if (options_val_) |options_val| {
+ if (options_val.isObject()) {
+ if (options_val.get(globalThis, "windowBits")) |window| {
+ opts.windowBits = window.toInt32();
+ }
+
+ if (options_val.get(globalThis, "level")) |level| {
+ opts.level = level.toInt32();
+ }
+
+ if (options_val.get(globalThis, "memLevel")) |memLevel| {
+ opts.memLevel = memLevel.toInt32();
+ }
+
+ if (options_val.get(globalThis, "strategy")) |strategy| {
+ opts.strategy = strategy.toInt32();
+ }
+ }
+ }
+
+ var compressed = buffer.slice();
+ const allocator = JSC.VirtualMachine.vm.allocator;
+ var list = std.ArrayListUnmanaged(u8).initCapacity(allocator, if (compressed.len > 512) compressed.len else 32) catch unreachable;
+ var reader = zlib.ZlibCompressorArrayList.init(compressed, &list, allocator, opts) catch |err| {
+ if (err == error.InvalidArgument) {
+ return JSC.toInvalidArguments("Invalid buffer", .{}, globalThis.ref());
+ }
+
+ return JSC.toInvalidArguments("Unexpected", .{}, globalThis.ref());
+ };
+
+ reader.readAll() catch {
+ defer reader.deinit();
+ if (reader.errorMessage()) |msg| {
+ return ZigString.init(msg).toErrorInstance(globalThis);
+ }
+ return ZigString.init("Zlib returned an error").toErrorInstance(globalThis);
+ };
+ reader.list = .{ .items = reader.list.toOwnedSlice(allocator) };
+ reader.list.capacity = reader.list.items.len;
+ reader.list_ptr = &reader.list;
+
+ var array_buffer = JSC.ArrayBuffer.fromBytes(reader.list.items, .Uint8Array);
+ return array_buffer.toJSWithContext(globalThis.ref(), reader, reader_deallocator, null);
+ }
+
+ pub fn inflateSync(
+ globalThis: *JSGlobalObject,
+ buffer: JSC.Node.StringOrBuffer,
+ ) JSValue {
+ var compressed = buffer.slice();
+ const allocator = JSC.VirtualMachine.vm.allocator;
+ var list = std.ArrayListUnmanaged(u8).initCapacity(allocator, if (compressed.len > 512) compressed.len else 32) catch unreachable;
+ var reader = zlib.ZlibReaderArrayList.initWithOptions(compressed, &list, allocator, .{
+ .windowBits = -15,
+ }) catch |err| {
+ if (err == error.InvalidArgument) {
+ return JSC.toInvalidArguments("Invalid buffer", .{}, globalThis.ref());
+ }
+
+ return JSC.toInvalidArguments("Unexpected", .{}, globalThis.ref());
+ };
+
+ reader.readAll() catch {
+ defer reader.deinit();
+ if (reader.errorMessage()) |msg| {
+ return ZigString.init(msg).toErrorInstance(globalThis);
+ }
+ return ZigString.init("Zlib returned an error").toErrorInstance(globalThis);
+ };
+ reader.list = .{ .items = reader.list.toOwnedSlice(allocator) };
+ reader.list.capacity = reader.list.items.len;
+ reader.list_ptr = &reader.list;
+
+ var array_buffer = JSC.ArrayBuffer.fromBytes(reader.list.items, .Uint8Array);
+ return array_buffer.toJSWithContext(globalThis.ref(), reader, reader_deallocator, null);
+ }
+
+ pub fn gunzipSync(
+ globalThis: *JSGlobalObject,
+ buffer: JSC.Node.StringOrBuffer,
+ ) JSValue {
+ var compressed = buffer.slice();
+ const allocator = JSC.VirtualMachine.vm.allocator;
+ var list = std.ArrayListUnmanaged(u8).initCapacity(allocator, if (compressed.len > 512) compressed.len else 32) catch unreachable;
+ var reader = zlib.ZlibReaderArrayList.init(compressed, &list, allocator) catch |err| {
+ if (err == error.InvalidArgument) {
+ return JSC.toInvalidArguments("Invalid buffer", .{}, globalThis.ref());
+ }
+
+ return JSC.toInvalidArguments("Unexpected", .{}, globalThis.ref());
+ };
+
+ reader.readAll() catch {
+ defer reader.deinit();
+ if (reader.errorMessage()) |msg| {
+ return ZigString.init(msg).toErrorInstance(globalThis);
+ }
+ return ZigString.init("Zlib returned an error").toErrorInstance(globalThis);
+ };
+ reader.list = .{ .items = reader.list.toOwnedSlice(allocator) };
+ reader.list.capacity = reader.list.items.len;
+ reader.list_ptr = &reader.list;
+
+ var array_buffer = JSC.ArrayBuffer.fromBytes(reader.list.items, .Uint8Array);
+ return array_buffer.toJSWithContext(globalThis.ref(), reader, reader_deallocator, null);
+ }
+};
diff --git a/src/bun.js/api/ffi.zig b/src/bun.js/api/ffi.zig
new file mode 100644
index 000000000..b4db32e4a
--- /dev/null
+++ b/src/bun.js/api/ffi.zig
@@ -0,0 +1,1436 @@
+const Bun = @This();
+const default_allocator = @import("../../global.zig").default_allocator;
+const bun = @import("../../global.zig");
+const Environment = bun.Environment;
+const NetworkThread = @import("http").NetworkThread;
+const Global = bun.Global;
+const strings = bun.strings;
+const string = bun.string;
+const Output = @import("../../global.zig").Output;
+const MutableString = @import("../../global.zig").MutableString;
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const IdentityContext = @import("../../identity_context.zig").IdentityContext;
+const Fs = @import("../../fs.zig");
+const Resolver = @import("../../resolver/resolver.zig");
+const ast = @import("../../import_record.zig");
+const NodeModuleBundle = @import("../../node_module_bundle.zig").NodeModuleBundle;
+const MacroEntryPoint = @import("../../bundler.zig").MacroEntryPoint;
+const logger = @import("../../logger.zig");
+const Api = @import("../../api/schema.zig").Api;
+const options = @import("../../options.zig");
+const Bundler = @import("../../bundler.zig").Bundler;
+const ServerEntryPoint = @import("../../bundler.zig").ServerEntryPoint;
+const js_printer = @import("../../js_printer.zig");
+const js_parser = @import("../../js_parser.zig");
+const js_ast = @import("../../js_ast.zig");
+const hash_map = @import("../../hash_map.zig");
+const http = @import("../../http.zig");
+const NodeFallbackModules = @import("../../node_fallbacks.zig");
+const ImportKind = ast.ImportKind;
+const Analytics = @import("../../analytics/analytics_thread.zig");
+const ZigString = @import("../../jsc.zig").ZigString;
+const Runtime = @import("../../runtime.zig");
+const Router = @import("./router.zig");
+const ImportRecord = ast.ImportRecord;
+const DotEnv = @import("../../env_loader.zig");
+const ParseResult = @import("../../bundler.zig").ParseResult;
+const PackageJSON = @import("../../resolver/package_json.zig").PackageJSON;
+const MacroRemap = @import("../../resolver/package_json.zig").MacroMap;
+const WebCore = @import("../../jsc.zig").WebCore;
+const Request = WebCore.Request;
+const Response = WebCore.Response;
+const Headers = WebCore.Headers;
+const Fetch = WebCore.Fetch;
+const FetchEvent = WebCore.FetchEvent;
+const js = @import("../../jsc.zig").C;
+const JSC = @import("../../jsc.zig");
+const JSError = @import("../base.zig").JSError;
+const d = @import("../base.zig").d;
+const MarkedArrayBuffer = @import("../base.zig").MarkedArrayBuffer;
+const getAllocator = @import("../base.zig").getAllocator;
+const JSValue = @import("../../jsc.zig").JSValue;
+const NewClass = @import("../base.zig").NewClass;
+const Microtask = @import("../../jsc.zig").Microtask;
+const JSGlobalObject = @import("../../jsc.zig").JSGlobalObject;
+const ExceptionValueRef = @import("../../jsc.zig").ExceptionValueRef;
+const JSPrivateDataPtr = @import("../../jsc.zig").JSPrivateDataPtr;
+const ZigConsoleClient = @import("../../jsc.zig").ZigConsoleClient;
+const Node = @import("../../jsc.zig").Node;
+const ZigException = @import("../../jsc.zig").ZigException;
+const ZigStackTrace = @import("../../jsc.zig").ZigStackTrace;
+const ErrorableResolvedSource = @import("../../jsc.zig").ErrorableResolvedSource;
+const ResolvedSource = @import("../../jsc.zig").ResolvedSource;
+const JSPromise = @import("../../jsc.zig").JSPromise;
+const JSInternalPromise = @import("../../jsc.zig").JSInternalPromise;
+const JSModuleLoader = @import("../../jsc.zig").JSModuleLoader;
+const JSPromiseRejectionOperation = @import("../../jsc.zig").JSPromiseRejectionOperation;
+const Exception = @import("../../jsc.zig").Exception;
+const ErrorableZigString = @import("../../jsc.zig").ErrorableZigString;
+const ZigGlobalObject = @import("../../jsc.zig").ZigGlobalObject;
+const VM = @import("../../jsc.zig").VM;
+const JSFunction = @import("../../jsc.zig").JSFunction;
+const Config = @import("../config.zig");
+const URL = @import("../../url.zig").URL;
+const Transpiler = @import("./transpiler.zig");
+const VirtualMachine = @import("../javascript.zig").VirtualMachine;
+const IOTask = JSC.IOTask;
+const ComptimeStringMap = @import("../../comptime_string_map.zig").ComptimeStringMap;
+
+const TCC = @import("../../tcc.zig");
+
+pub const FFI = struct {
+ dylib: ?std.DynLib = null,
+ functions: std.StringArrayHashMapUnmanaged(Function) = .{},
+ closed: bool = false,
+
+ pub const Class = JSC.NewClass(
+ FFI,
+ .{ .name = "class" },
+ .{ .call = JSC.wrapWithHasContainer(FFI, "close", false, true, true) },
+ .{},
+ );
+
+ pub fn callback(globalThis: *JSGlobalObject, interface: JSC.JSValue, js_callback: JSC.JSValue) JSValue {
+ if (!interface.isObject()) {
+ return JSC.toInvalidArguments("Expected object", .{}, globalThis.ref());
+ }
+
+ if (js_callback.isEmptyOrUndefinedOrNull() or !js_callback.isCallable(globalThis.vm())) {
+ return JSC.toInvalidArguments("Expected callback function", .{}, globalThis.ref());
+ }
+
+ const allocator = VirtualMachine.vm.allocator;
+ var function: Function = .{};
+ var func = &function;
+
+ if (generateSymbolForFunction(globalThis, allocator, interface, func) catch ZigString.init("Out of memory").toErrorInstance(globalThis)) |val| {
+ return val;
+ }
+
+ // TODO: WeakRefHandle that automatically frees it?
+ JSC.C.JSValueProtect(globalThis.ref(), js_callback.asObjectRef());
+ func.base_name = "";
+
+ func.compileCallback(allocator, globalThis, js_callback.asObjectRef().?) catch return ZigString.init("Out of memory").toErrorInstance(globalThis);
+ switch (func.step) {
+ .failed => |err| {
+ JSC.C.JSValueUnprotect(globalThis.ref(), js_callback.asObjectRef());
+ const message = ZigString.init(err.msg).toErrorInstance(globalThis);
+
+ func.deinit(allocator);
+
+ return message;
+ },
+ .pending => {
+ JSC.C.JSValueUnprotect(globalThis.ref(), js_callback.asObjectRef());
+ func.deinit(allocator);
+ return ZigString.init("Failed to compile, but not sure why. Please report this bug").toErrorInstance(globalThis);
+ },
+ .compiled => {
+ var function_ = bun.default_allocator.create(Function) catch unreachable;
+ function_.* = func.*;
+ return JSC.JSValue.jsNumber(@bitCast(f64, @as(usize, @ptrToInt(function_.step.compiled.ptr))));
+ },
+ }
+ }
+
+ pub fn close(this: *FFI) JSValue {
+ if (this.closed) {
+ return JSC.JSValue.jsUndefined();
+ }
+ this.closed = true;
+ if (this.dylib) |*dylib| {
+ dylib.close();
+ this.dylib = null;
+ }
+
+ const allocator = VirtualMachine.vm.allocator;
+
+ for (this.functions.values()) |*val| {
+ val.deinit(allocator);
+ }
+ this.functions.deinit(allocator);
+
+ return JSC.JSValue.jsUndefined();
+ }
+
+ pub fn printCallback(global: *JSGlobalObject, object: JSC.JSValue) JSValue {
+ const allocator = VirtualMachine.vm.allocator;
+
+ if (object.isEmptyOrUndefinedOrNull() or !object.isObject()) {
+ return JSC.toInvalidArguments("Expected an object", .{}, global.ref());
+ }
+
+ var function: Function = .{};
+ if (generateSymbolForFunction(global, allocator, object, &function) catch ZigString.init("Out of memory").toErrorInstance(global)) |val| {
+ return val;
+ }
+
+ var arraylist = std.ArrayList(u8).init(allocator);
+ defer arraylist.deinit();
+ var writer = arraylist.writer();
+
+ function.base_name = "my_callback_function";
+
+ function.printCallbackSourceCode(&writer) catch {
+ return ZigString.init("Error while printing code").toErrorInstance(global);
+ };
+ return ZigString.init(arraylist.items).toValueGC(global);
+ }
+
+ pub fn print(global: *JSGlobalObject, object: JSC.JSValue, is_callback_val: ?JSC.JSValue) JSValue {
+ const allocator = VirtualMachine.vm.allocator;
+ if (is_callback_val) |is_callback| {
+ if (is_callback.toBoolean()) {
+ return printCallback(global, object);
+ }
+ }
+
+ if (object.isEmptyOrUndefinedOrNull() or !object.isObject()) {
+ return JSC.toInvalidArguments("Expected an options object with symbol names", .{}, global.ref());
+ }
+
+ var symbols = std.StringArrayHashMapUnmanaged(Function){};
+ if (generateSymbols(global, &symbols, object) catch JSC.JSValue.zero) |val| {
+ // an error while validating symbols
+ for (symbols.keys()) |key| {
+ allocator.free(bun.constStrToU8(key));
+ }
+ symbols.clearAndFree(allocator);
+ return val;
+ }
+
+ var zig_strings = allocator.alloc(ZigString, symbols.count()) catch unreachable;
+ for (symbols.values()) |*function, i| {
+ var arraylist = std.ArrayList(u8).init(allocator);
+ var writer = arraylist.writer();
+ function.printSourceCode(&writer) catch {
+ // an error while generating source code
+ for (symbols.keys()) |key| {
+ allocator.free(bun.constStrToU8(key));
+ }
+ for (zig_strings) |zig_string| {
+ allocator.free(bun.constStrToU8(zig_string.slice()));
+ }
+ for (symbols.values()) |*function_| {
+ function_.arg_types.deinit(allocator);
+ }
+
+ symbols.clearAndFree(allocator);
+ return ZigString.init("Error while printing code").toErrorInstance(global);
+ };
+ zig_strings[i] = ZigString.init(arraylist.items);
+ }
+
+ const ret = JSC.JSValue.createStringArray(global, zig_strings.ptr, zig_strings.len, true);
+
+ for (symbols.keys()) |key| {
+ allocator.free(bun.constStrToU8(key));
+ }
+ for (zig_strings) |zig_string| {
+ allocator.free(bun.constStrToU8(zig_string.slice()));
+ }
+ for (symbols.values()) |*function_| {
+ function_.arg_types.deinit(allocator);
+ if (function_.step == .compiled) {
+ allocator.free(function_.step.compiled.buf);
+ }
+ }
+ symbols.clearAndFree(allocator);
+
+ return ret;
+ }
+
+ // pub fn dlcompile(global: *JSGlobalObject, object: JSC.JSValue) JSValue {
+ // const allocator = VirtualMachine.vm.allocator;
+
+ // if (object.isEmptyOrUndefinedOrNull() or !object.isObject()) {
+ // return JSC.toInvalidArguments("Expected an options object with symbol names", .{}, global.ref());
+ // }
+
+ // var symbols = std.StringArrayHashMapUnmanaged(Function){};
+ // if (generateSymbols(global, &symbols, object) catch JSC.JSValue.zero) |val| {
+ // // an error while validating symbols
+ // for (symbols.keys()) |key| {
+ // allocator.free(bun.constStrToU8(key));
+ // }
+ // symbols.clearAndFree(allocator);
+ // return val;
+ // }
+
+ // }
+
+ pub fn open(global: *JSGlobalObject, name_str: ZigString, object: JSC.JSValue) JSC.JSValue {
+ const allocator = VirtualMachine.vm.allocator;
+ var name_slice = name_str.toSlice(allocator);
+ defer name_slice.deinit();
+
+ if (name_slice.len == 0) {
+ return JSC.toInvalidArguments("Invalid library name", .{}, global.ref());
+ }
+
+ if (object.isEmptyOrUndefinedOrNull() or !object.isObject()) {
+ return JSC.toInvalidArguments("Expected an options object with symbol names", .{}, global.ref());
+ }
+
+ const name = name_slice.sliceZ();
+ var symbols = std.StringArrayHashMapUnmanaged(Function){};
+ if (generateSymbols(global, &symbols, object) catch JSC.JSValue.zero) |val| {
+ // an error while validating symbols
+ for (symbols.keys()) |key| {
+ allocator.free(bun.constStrToU8(key));
+ }
+ symbols.clearAndFree(allocator);
+ return val;
+ }
+ if (symbols.count() == 0) {
+ return JSC.toInvalidArguments("Expected at least one symbol", .{}, global.ref());
+ }
+
+ var dylib = std.DynLib.open(name) catch {
+ return JSC.toInvalidArguments("Failed to open library", .{}, global.ref());
+ };
+
+ var obj = JSC.JSValue.c(JSC.C.JSObjectMake(global.ref(), null, null));
+ JSC.C.JSValueProtect(global.ref(), obj.asObjectRef());
+ defer JSC.C.JSValueUnprotect(global.ref(), obj.asObjectRef());
+ for (symbols.values()) |*function| {
+ const function_name = function.base_name.?;
+
+ // optional if the user passed "ptr"
+ if (function.symbol_from_dynamic_library == null) {
+ var resolved_symbol = dylib.lookup(*anyopaque, function_name) orelse {
+ const ret = JSC.toInvalidArguments("Symbol \"{s}\" not found in \"{s}\"", .{ std.mem.span(function_name), name_slice.slice() }, global.ref());
+ for (symbols.values()) |*value| {
+ allocator.free(bun.constStrToU8(std.mem.span(value.base_name.?)));
+ value.arg_types.clearAndFree(allocator);
+ }
+ symbols.clearAndFree(allocator);
+ dylib.close();
+ return ret;
+ };
+
+ function.symbol_from_dynamic_library = resolved_symbol;
+ }
+
+ function.compile(allocator) catch |err| {
+ const ret = JSC.toInvalidArguments("{s} when compiling symbol \"{s}\" in \"{s}\"", .{
+ std.mem.span(@errorName(err)),
+ std.mem.span(function_name),
+ name_slice.slice(),
+ }, global.ref());
+ for (symbols.values()) |*value| {
+ allocator.free(bun.constStrToU8(std.mem.span(value.base_name.?)));
+ value.arg_types.clearAndFree(allocator);
+ }
+ symbols.clearAndFree(allocator);
+ dylib.close();
+ return ret;
+ };
+ switch (function.step) {
+ .failed => |err| {
+ for (symbols.values()) |*value| {
+ allocator.free(bun.constStrToU8(std.mem.span(value.base_name.?)));
+ value.arg_types.clearAndFree(allocator);
+ }
+
+ const res = ZigString.init(err.msg).toErrorInstance(global);
+ function.deinit(allocator);
+ symbols.clearAndFree(allocator);
+ dylib.close();
+ return res;
+ },
+ .pending => {
+ for (symbols.values()) |*value| {
+ allocator.free(bun.constStrToU8(std.mem.span(value.base_name.?)));
+ value.arg_types.clearAndFree(allocator);
+ }
+ symbols.clearAndFree(allocator);
+ dylib.close();
+ return ZigString.init("Failed to compile (nothing happend!)").toErrorInstance(global);
+ },
+ .compiled => |compiled| {
+ var cb = JSC.NewFunctionPtr(
+ global,
+ &ZigString.init(std.mem.span(function_name)),
+ @intCast(u32, function.arg_types.items.len),
+ compiled.ptr,
+ );
+
+ obj.put(global, &ZigString.init(std.mem.span(function_name)), JSC.JSValue.cast(cb));
+ },
+ }
+ }
+
+ var lib = allocator.create(FFI) catch unreachable;
+ lib.* = .{
+ .dylib = dylib,
+ .functions = symbols,
+ };
+
+ var close_object = JSC.JSValue.c(Class.make(global.ref(), lib));
+
+ return JSC.JSValue.createObject2(global, &ZigString.init("close"), &ZigString.init("symbols"), close_object, obj);
+ }
+
+ pub fn linkSymbols(global: *JSGlobalObject, object: JSC.JSValue) JSC.JSValue {
+ const allocator = VirtualMachine.vm.allocator;
+
+ if (object.isEmptyOrUndefinedOrNull() or !object.isObject()) {
+ return JSC.toInvalidArguments("Expected an options object with symbol names", .{}, global.ref());
+ }
+
+ var symbols = std.StringArrayHashMapUnmanaged(Function){};
+ if (generateSymbols(global, &symbols, object) catch JSC.JSValue.zero) |val| {
+ // an error while validating symbols
+ for (symbols.keys()) |key| {
+ allocator.free(bun.constStrToU8(key));
+ }
+ symbols.clearAndFree(allocator);
+ return val;
+ }
+ if (symbols.count() == 0) {
+ return JSC.toInvalidArguments("Expected at least one symbol", .{}, global.ref());
+ }
+
+ var obj = JSC.JSValue.c(JSC.C.JSObjectMake(global.ref(), null, null));
+ JSC.C.JSValueProtect(global.ref(), obj.asObjectRef());
+ defer JSC.C.JSValueUnprotect(global.ref(), obj.asObjectRef());
+ for (symbols.values()) |*function| {
+ const function_name = function.base_name.?;
+
+ if (function.symbol_from_dynamic_library == null) {
+ const ret = JSC.toInvalidArguments("Symbol for \"{s}\" not found", .{std.mem.span(function_name)}, global.ref());
+ for (symbols.values()) |*value| {
+ allocator.free(bun.constStrToU8(std.mem.span(value.base_name.?)));
+ value.arg_types.clearAndFree(allocator);
+ }
+ symbols.clearAndFree(allocator);
+ return ret;
+ }
+
+ function.compile(allocator) catch |err| {
+ const ret = JSC.toInvalidArguments("{s} when compiling symbol \"{s}\"", .{
+ std.mem.span(@errorName(err)),
+ std.mem.span(function_name),
+ }, global.ref());
+ for (symbols.values()) |*value| {
+ allocator.free(bun.constStrToU8(std.mem.span(value.base_name.?)));
+ value.arg_types.clearAndFree(allocator);
+ }
+ symbols.clearAndFree(allocator);
+ return ret;
+ };
+ switch (function.step) {
+ .failed => |err| {
+ for (symbols.values()) |*value| {
+ allocator.free(bun.constStrToU8(std.mem.span(value.base_name.?)));
+ value.arg_types.clearAndFree(allocator);
+ }
+
+ const res = ZigString.init(err.msg).toErrorInstance(global);
+ function.deinit(allocator);
+ symbols.clearAndFree(allocator);
+ return res;
+ },
+ .pending => {
+ for (symbols.values()) |*value| {
+ allocator.free(bun.constStrToU8(std.mem.span(value.base_name.?)));
+ value.arg_types.clearAndFree(allocator);
+ }
+ symbols.clearAndFree(allocator);
+ return ZigString.init("Failed to compile (nothing happend!)").toErrorInstance(global);
+ },
+ .compiled => |compiled| {
+ var cb = JSC.NewFunctionPtr(
+ global,
+ &ZigString.init(std.mem.span(function_name)),
+ @intCast(u32, function.arg_types.items.len),
+ compiled.ptr,
+ );
+
+ obj.put(global, &ZigString.init(std.mem.span(function_name)), JSC.JSValue.cast(cb));
+ },
+ }
+ }
+
+ var lib = allocator.create(FFI) catch unreachable;
+ lib.* = .{
+ .dylib = null,
+ .functions = symbols,
+ };
+
+ var close_object = JSC.JSValue.c(Class.make(global.ref(), lib));
+
+ return JSC.JSValue.createObject2(global, &ZigString.init("close"), &ZigString.init("symbols"), close_object, obj);
+ }
+ pub fn generateSymbolForFunction(global: *JSGlobalObject, allocator: std.mem.Allocator, value: JSC.JSValue, function: *Function) !?JSValue {
+ var abi_types = std.ArrayListUnmanaged(ABIType){};
+
+ if (value.get(global, "args")) |args| {
+ if (args.isEmptyOrUndefinedOrNull() or !args.jsType().isArray()) {
+ return ZigString.init("Expected an object with \"args\" as an array").toErrorInstance(global);
+ }
+
+ var array = args.arrayIterator(global);
+
+ try abi_types.ensureTotalCapacityPrecise(allocator, array.len);
+ while (array.next()) |val| {
+ if (val.isEmptyOrUndefinedOrNull()) {
+ abi_types.clearAndFree(allocator);
+ return ZigString.init("param must be a string (type name) or number").toErrorInstance(global);
+ }
+
+ if (val.isAnyInt()) {
+ const int = val.toInt32();
+ switch (int) {
+ 0...14 => {
+ abi_types.appendAssumeCapacity(@intToEnum(ABIType, int));
+ continue;
+ },
+ else => {
+ abi_types.clearAndFree(allocator);
+ return ZigString.init("invalid ABI type").toErrorInstance(global);
+ },
+ }
+ }
+
+ if (!val.jsType().isStringLike()) {
+ abi_types.clearAndFree(allocator);
+ return ZigString.init("param must be a string (type name) or number").toErrorInstance(global);
+ }
+
+ var type_name = val.toSlice(global, allocator);
+ defer type_name.deinit();
+ abi_types.appendAssumeCapacity(ABIType.label.get(type_name.slice()) orelse {
+ abi_types.clearAndFree(allocator);
+ return JSC.toTypeError(JSC.Node.ErrorCode.ERR_INVALID_ARG_VALUE, "Unknown type {s}", .{type_name.slice()}, global.ref());
+ });
+ }
+ }
+ // var function
+ var return_type = ABIType.@"void";
+
+ if (value.get(global, "returns")) |ret_value| brk: {
+ if (ret_value.isAnyInt()) {
+ const int = ret_value.toInt32();
+ switch (int) {
+ 0...14 => {
+ return_type = @intToEnum(ABIType, int);
+ break :brk;
+ },
+ else => {
+ abi_types.clearAndFree(allocator);
+ return ZigString.init("invalid ABI type").toErrorInstance(global);
+ },
+ }
+ }
+
+ var ret_slice = ret_value.toSlice(global, allocator);
+ defer ret_slice.deinit();
+ return_type = ABIType.label.get(ret_slice.slice()) orelse {
+ abi_types.clearAndFree(allocator);
+ return JSC.toTypeError(JSC.Node.ErrorCode.ERR_INVALID_ARG_VALUE, "Unknown return type {s}", .{ret_slice.slice()}, global.ref());
+ };
+ }
+
+ function.* = Function{
+ .base_name = null,
+ .arg_types = abi_types,
+ .return_type = return_type,
+ };
+
+ if (value.get(global, "ptr")) |ptr| {
+ if (ptr.isNumber()) {
+ const num = @bitCast(usize, ptr.asNumber());
+ if (num > 0)
+ function.symbol_from_dynamic_library = @intToPtr(*anyopaque, num);
+ } else {
+ const num = ptr.toUInt64NoTruncate();
+ if (num > 0) {
+ function.symbol_from_dynamic_library = @intToPtr(*anyopaque, num);
+ }
+ }
+ }
+
+ return null;
+ }
+ pub fn generateSymbols(global: *JSGlobalObject, symbols: *std.StringArrayHashMapUnmanaged(Function), object: JSC.JSValue) !?JSValue {
+ const allocator = VirtualMachine.vm.allocator;
+
+ var keys = JSC.C.JSObjectCopyPropertyNames(global.ref(), object.asObjectRef());
+ defer JSC.C.JSPropertyNameArrayRelease(keys);
+ const count = JSC.C.JSPropertyNameArrayGetCount(keys);
+
+ try symbols.ensureTotalCapacity(allocator, count);
+
+ var i: usize = 0;
+ while (i < count) : (i += 1) {
+ var property_name_ref = JSC.C.JSPropertyNameArrayGetNameAtIndex(keys, i);
+ defer JSC.C.JSStringRelease(property_name_ref);
+ const len = JSC.C.JSStringGetLength(property_name_ref);
+ if (len == 0) continue;
+ var prop = JSC.C.JSStringGetCharacters8Ptr(property_name_ref)[0..len];
+
+ var value = JSC.JSValue.c(JSC.C.JSObjectGetProperty(global.ref(), object.asObjectRef(), property_name_ref, null));
+ if (value.isEmptyOrUndefinedOrNull()) {
+ return JSC.toTypeError(JSC.Node.ErrorCode.ERR_INVALID_ARG_VALUE, "Expected an object for key \"{s}\"", .{prop}, global.ref());
+ }
+
+ var function: Function = .{};
+ if (try generateSymbolForFunction(global, allocator, value, &function)) |val| {
+ return val;
+ }
+ function.base_name = try allocator.dupeZ(u8, prop);
+
+ symbols.putAssumeCapacity(std.mem.span(function.base_name.?), function);
+ }
+
+ return null;
+ }
+
+ pub const Function = struct {
+ symbol_from_dynamic_library: ?*anyopaque = null,
+ base_name: ?[:0]const u8 = null,
+ state: ?*TCC.TCCState = null,
+
+ return_type: ABIType = ABIType.@"void",
+ arg_types: std.ArrayListUnmanaged(ABIType) = .{},
+ step: Step = Step{ .pending = {} },
+
+ pub var lib_dirZ: [*:0]const u8 = "";
+
+ pub fn deinit(val: *Function, allocator: std.mem.Allocator) void {
+ if (val.base_name) |base_name| {
+ if (std.mem.span(base_name).len > 0) {
+ allocator.free(bun.constStrToU8(std.mem.span(base_name)));
+ }
+ }
+
+ val.arg_types.clearAndFree(allocator);
+
+ if (val.state) |state| {
+ TCC.tcc_delete(state);
+ val.state = null;
+ }
+
+ if (val.step == .compiled) {
+ // allocator.free(val.step.compiled.buf);
+ if (val.step.compiled.js_function) |js_function| {
+ JSC.C.JSValueUnprotect(@ptrCast(JSC.C.JSContextRef, val.step.compiled.js_context.?), @ptrCast(JSC.C.JSObjectRef, js_function));
+ }
+ }
+
+ if (val.step == .failed and val.step.failed.allocated) {
+ allocator.free(val.step.failed.msg);
+ }
+ }
+
+ pub const Step = union(enum) {
+ pending: void,
+ compiled: struct {
+ ptr: *anyopaque,
+ buf: []u8,
+ js_function: ?*anyopaque = null,
+ js_context: ?*anyopaque = null,
+ },
+ failed: struct {
+ msg: []const u8,
+ allocated: bool = false,
+ },
+ };
+
+ const FFI_HEADER: string = @embedFile("./FFI.h");
+ pub inline fn ffiHeader() string {
+ if (comptime Environment.isDebug) {
+ var dirpath = std.fs.path.dirname(@src().file).?;
+ var env = std.process.getEnvMap(default_allocator) catch unreachable;
+
+ const dir = std.mem.replaceOwned(
+ u8,
+ default_allocator,
+ dirpath,
+ "jarred",
+ env.get("USER").?,
+ ) catch unreachable;
+ var runtime_path = std.fs.path.join(default_allocator, &[_]string{ dir, "FFI.h" }) catch unreachable;
+ const file = std.fs.openFileAbsolute(runtime_path, .{}) catch @panic("Missing bun/src/bun.js/api/FFI.h.");
+ defer file.close();
+ return file.readToEndAlloc(default_allocator, (file.stat() catch unreachable).size) catch unreachable;
+ } else {
+ return FFI_HEADER;
+ }
+ }
+
+ pub fn handleTCCError(ctx: ?*anyopaque, message: [*c]const u8) callconv(.C) void {
+ var this = bun.cast(*Function, ctx.?);
+ var msg = std.mem.span(message);
+ if (msg.len > 0) {
+ var offset: usize = 0;
+ // the message we get from TCC sometimes has garbage in it
+ // i think because we're doing in-memory compilation
+ while (offset < msg.len) : (offset += 1) {
+ if (msg[offset] > 0x20 and msg[offset] < 0x7f) break;
+ }
+ msg = msg[offset..];
+ }
+
+ this.step = .{ .failed = .{ .msg = VirtualMachine.vm.allocator.dupe(u8, msg) catch unreachable, .allocated = true } };
+ }
+
+ extern fn pthread_jit_write_protect_np(enable: bool) callconv(.C) void;
+
+ const MyFunctionSStructWorkAround = struct {
+ JSVALUE_TO_INT64: fn (JSValue0: JSC.JSValue) callconv(.C) i64,
+ JSVALUE_TO_UINT64: fn (JSValue0: JSC.JSValue) callconv(.C) u64,
+ INT64_TO_JSVALUE: fn (arg0: [*c]JSC.JSGlobalObject, arg1: i64) callconv(.C) JSC.JSValue,
+ UINT64_TO_JSVALUE: fn (arg0: [*c]JSC.JSGlobalObject, arg1: u64) callconv(.C) JSC.JSValue,
+ bun_call: *const @TypeOf(JSC.C.JSObjectCallAsFunction),
+ };
+ const headers = @import("../bindings/headers.zig");
+
+ var workaround: MyFunctionSStructWorkAround = .{
+ .JSVALUE_TO_INT64 = headers.JSC__JSValue__toInt64,
+ .JSVALUE_TO_UINT64 = headers.JSC__JSValue__toUInt64NoTruncate,
+ .INT64_TO_JSVALUE = headers.JSC__JSValue__fromInt64NoTruncate,
+ .UINT64_TO_JSVALUE = headers.JSC__JSValue__fromUInt64NoTruncate,
+ .bun_call = &JSC.C.JSObjectCallAsFunction,
+ };
+
+ const tcc_options = "-std=c11 -nostdlib -Wl,--export-all-symbols" ++ if (Environment.isDebug) " -g" else "";
+
+ pub fn compile(
+ this: *Function,
+ allocator: std.mem.Allocator,
+ ) !void {
+ var source_code = std.ArrayList(u8).init(allocator);
+ var source_code_writer = source_code.writer();
+ try this.printSourceCode(&source_code_writer);
+
+ try source_code.append(0);
+ defer source_code.deinit();
+
+ var state = TCC.tcc_new() orelse return error.TCCMissing;
+ TCC.tcc_set_options(state, tcc_options);
+ // addSharedLibPaths(state);
+ TCC.tcc_set_error_func(state, this, handleTCCError);
+ this.state = state;
+ defer {
+ if (this.step == .failed) {
+ TCC.tcc_delete(state);
+ this.state = null;
+ }
+ }
+
+ _ = TCC.tcc_set_output_type(state, TCC.TCC_OUTPUT_MEMORY);
+ const Sizes = @import("../bindings/sizes.zig");
+
+ var symbol_buf: [256]u8 = undefined;
+ TCC.tcc_define_symbol(
+ state,
+ "Bun_FFI_PointerOffsetToArgumentsList",
+ std.fmt.bufPrintZ(&symbol_buf, "{d}", .{Sizes.Bun_FFI_PointerOffsetToArgumentsList}) catch unreachable,
+ );
+ CompilerRT.define(state);
+
+ // TCC.tcc_define_symbol(
+ // state,
+ // "Bun_FFI_PointerOffsetToArgumentsCount",
+ // std.fmt.bufPrintZ(symbol_buf[8..], "{d}", .{Bun_FFI_PointerOffsetToArgumentsCount}) catch unreachable,
+ // );
+
+ const compilation_result = TCC.tcc_compile_string(
+ state,
+ source_code.items.ptr,
+ );
+ // did tcc report an error?
+ if (this.step == .failed) {
+ return;
+ }
+
+ // did tcc report failure but never called the error callback?
+ if (compilation_result == -1) {
+ this.step = .{ .failed = .{ .msg = "tcc returned -1, which means it failed" } };
+ return;
+ }
+ CompilerRT.inject(state);
+ _ = TCC.tcc_add_symbol(state, this.base_name.?, this.symbol_from_dynamic_library.?);
+
+ if (this.step == .failed) {
+ return;
+ }
+
+ var relocation_size = TCC.tcc_relocate(state, null);
+ if (this.step == .failed) {
+ return;
+ }
+
+ if (relocation_size < 0) {
+ this.step = .{ .failed = .{ .msg = "tcc_relocate returned a negative value" } };
+ return;
+ }
+
+ var bytes: []u8 = try allocator.rawAlloc(@intCast(usize, relocation_size), 16, 16, 0);
+ defer {
+ if (this.step == .failed) {
+ allocator.free(bytes);
+ }
+ }
+
+ if (comptime Environment.isAarch64 and Environment.isMac) {
+ pthread_jit_write_protect_np(false);
+ }
+ _ = TCC.tcc_relocate(state, bytes.ptr);
+ if (comptime Environment.isAarch64 and Environment.isMac) {
+ pthread_jit_write_protect_np(true);
+ }
+
+ var symbol = TCC.tcc_get_symbol(state, "JSFunctionCall") orelse {
+ this.step = .{ .failed = .{ .msg = "missing generated symbol in source code" } };
+
+ return;
+ };
+
+ this.step = .{
+ .compiled = .{
+ .ptr = symbol,
+ .buf = bytes,
+ },
+ };
+ return;
+ }
+ const CompilerRT = struct {
+ noinline fn memset(
+ dest: [*]u8,
+ c: u8,
+ byte_count: usize,
+ ) callconv(.C) void {
+ @memset(dest, c, byte_count);
+ }
+
+ noinline fn memcpy(
+ noalias dest: [*]u8,
+ noalias source: [*]const u8,
+ byte_count: usize,
+ ) callconv(.C) void {
+ @memcpy(dest, source, byte_count);
+ }
+
+ pub fn define(state: *TCC.TCCState) void {
+ if (comptime Environment.isX64) {
+ _ = TCC.tcc_define_symbol(state, "NEEDS_COMPILER_RT_FUNCTIONS", "1");
+ // there
+ _ = TCC.tcc_compile_string(state, @embedFile(("libtcc1.c")));
+ }
+ }
+
+ pub fn inject(state: *TCC.TCCState) void {
+ _ = TCC.tcc_add_symbol(state, "memset", &memset);
+ _ = TCC.tcc_add_symbol(state, "memcpy", &memcpy);
+
+ _ = TCC.tcc_add_symbol(
+ state,
+ "JSVALUE_TO_INT64_SLOW",
+ workaround.JSVALUE_TO_INT64,
+ );
+ _ = TCC.tcc_add_symbol(
+ state,
+ "JSVALUE_TO_UINT64_SLOW",
+ workaround.JSVALUE_TO_UINT64,
+ );
+ std.mem.doNotOptimizeAway(headers.JSC__JSValue__toUInt64NoTruncate);
+ std.mem.doNotOptimizeAway(headers.JSC__JSValue__toInt64);
+ std.mem.doNotOptimizeAway(headers.JSC__JSValue__fromInt64NoTruncate);
+ std.mem.doNotOptimizeAway(headers.JSC__JSValue__fromUInt64NoTruncate);
+ _ = TCC.tcc_add_symbol(
+ state,
+ "INT64_TO_JSVALUE_SLOW",
+ workaround.INT64_TO_JSVALUE,
+ );
+ _ = TCC.tcc_add_symbol(
+ state,
+ "UINT64_TO_JSVALUE_SLOW",
+ workaround.UINT64_TO_JSVALUE,
+ );
+ }
+ };
+
+ pub fn compileCallback(
+ this: *Function,
+ allocator: std.mem.Allocator,
+ js_context: *anyopaque,
+ js_function: *anyopaque,
+ ) !void {
+ Output.debug("welcome", .{});
+ var source_code = std.ArrayList(u8).init(allocator);
+ var source_code_writer = source_code.writer();
+ try this.printCallbackSourceCode(&source_code_writer);
+ Output.debug("helllooo", .{});
+ try source_code.append(0);
+ // defer source_code.deinit();
+ var state = TCC.tcc_new() orelse return error.TCCMissing;
+ TCC.tcc_set_options(state, tcc_options);
+ TCC.tcc_set_error_func(state, this, handleTCCError);
+ this.state = state;
+ defer {
+ if (this.step == .failed) {
+ TCC.tcc_delete(state);
+ this.state = null;
+ }
+ }
+
+ _ = TCC.tcc_set_output_type(state, TCC.TCC_OUTPUT_MEMORY);
+
+ CompilerRT.define(state);
+
+ const compilation_result = TCC.tcc_compile_string(
+ state,
+ source_code.items.ptr,
+ );
+ Output.debug("compile", .{});
+ // did tcc report an error?
+ if (this.step == .failed) {
+ return;
+ }
+
+ // did tcc report failure but never called the error callback?
+ if (compilation_result == -1) {
+ this.step = .{ .failed = .{ .msg = "tcc returned -1, which means it failed" } };
+
+ return;
+ }
+
+ CompilerRT.inject(state);
+ Output.debug("here", .{});
+ _ = TCC.tcc_add_symbol(state, "bun_call", workaround.bun_call.*);
+ _ = TCC.tcc_add_symbol(state, "cachedJSContext", js_context);
+ _ = TCC.tcc_add_symbol(state, "cachedCallbackFunction", js_function);
+
+ var relocation_size = TCC.tcc_relocate(state, null);
+ if (relocation_size == 0) return;
+ var bytes: []u8 = try allocator.rawAlloc(@intCast(usize, relocation_size), 16, 16, 0);
+ defer {
+ if (this.step == .failed) {
+ allocator.free(bytes);
+ }
+ }
+
+ if (comptime Environment.isAarch64 and Environment.isMac) {
+ pthread_jit_write_protect_np(false);
+ }
+ _ = TCC.tcc_relocate(state, bytes.ptr);
+ if (comptime Environment.isAarch64 and Environment.isMac) {
+ pthread_jit_write_protect_np(true);
+ }
+
+ var symbol = TCC.tcc_get_symbol(state, "my_callback_function") orelse {
+ this.step = .{ .failed = .{ .msg = "missing generated symbol in source code" } };
+
+ return;
+ };
+
+ this.step = .{
+ .compiled = .{
+ .ptr = symbol,
+ .buf = bytes,
+ .js_function = js_function,
+ .js_context = js_context,
+ },
+ };
+ }
+
+ pub fn printSourceCode(
+ this: *Function,
+ writer: anytype,
+ ) !void {
+ if (this.arg_types.items.len > 0) {
+ try writer.writeAll("#define HAS_ARGUMENTS\n");
+ }
+
+ brk: {
+ if (this.return_type.isFloatingPoint()) {
+ try writer.writeAll("#define USES_FLOAT 1\n");
+ break :brk;
+ }
+
+ for (this.arg_types.items) |arg| {
+ // conditionally include math.h
+ if (arg.isFloatingPoint()) {
+ try writer.writeAll("#define USES_FLOAT 1\n");
+ break;
+ }
+ }
+ }
+
+ if (comptime Environment.isRelease) {
+ try writer.writeAll(std.mem.span(FFI_HEADER));
+ } else {
+ try writer.writeAll(ffiHeader());
+ }
+
+ // -- Generate the FFI function symbol
+ try writer.writeAll("/* --- The Function To Call */\n");
+ try this.return_type.typename(writer);
+ try writer.writeAll(" ");
+ try writer.writeAll(std.mem.span(this.base_name.?));
+ try writer.writeAll("(");
+ var first = true;
+ for (this.arg_types.items) |arg, i| {
+ if (!first) {
+ try writer.writeAll(", ");
+ }
+ first = false;
+ try arg.typename(writer);
+ try writer.print(" arg{d}", .{i});
+ }
+ try writer.writeAll(
+ \\);
+ \\
+ \\
+ \\/* ---- Your Wrapper Function ---- */
+ \\void* JSFunctionCall(void* globalObject, void* callFrame) {
+ \\
+ );
+
+ if (this.arg_types.items.len > 0) {
+ try writer.writeAll(
+ \\ LOAD_ARGUMENTS_FROM_CALL_FRAME;
+ \\
+ );
+ for (this.arg_types.items) |arg, i| {
+ if (arg.needsACastInC()) {
+ if (i < this.arg_types.items.len - 1) {
+ try writer.print(
+ \\ EncodedJSValue arg{d};
+ \\ arg{d}.asInt64 = *argsPtr++;
+ \\
+ ,
+ .{
+ i,
+ i,
+ },
+ );
+ } else {
+ try writer.print(
+ \\ EncodedJSValue arg{d};
+ \\ arg{d}.asInt64 = *argsPtr;
+ \\
+ ,
+ .{
+ i,
+ i,
+ },
+ );
+ }
+ } else {
+ if (i < this.arg_types.items.len - 1) {
+ try writer.print(
+ \\ int64_t arg{d} = *argsPtr++;
+ \\
+ ,
+ .{
+ i,
+ },
+ );
+ } else {
+ try writer.print(
+ \\ int64_t arg{d} = *argsPtr;
+ \\
+ ,
+ .{
+ i,
+ },
+ );
+ }
+ }
+ }
+ }
+
+ // try writer.writeAll(
+ // "(JSContext ctx, void* function, void* thisObject, size_t argumentCount, const EncodedJSValue arguments[], void* exception);\n\n",
+ // );
+
+ var arg_buf: [512]u8 = undefined;
+
+ try writer.writeAll(" ");
+ if (!(this.return_type == .void)) {
+ try this.return_type.typename(writer);
+ try writer.writeAll(" return_value = ");
+ }
+ try writer.print("{s}(", .{std.mem.span(this.base_name.?)});
+ first = true;
+ arg_buf[0..3].* = "arg".*;
+ for (this.arg_types.items) |arg, i| {
+ if (!first) {
+ try writer.writeAll(", ");
+ }
+ first = false;
+
+ try writer.writeAll(" ");
+ const lengthBuf = std.fmt.bufPrintIntToSlice(arg_buf["arg".len..], i, 10, .lower, .{});
+ const argName = arg_buf[0 .. 3 + lengthBuf.len];
+ if (arg.needsACastInC()) {
+ try writer.print("{}", .{arg.toC(argName)});
+ } else {
+ try writer.writeAll(argName);
+ }
+ }
+ try writer.writeAll(");\n");
+
+ if (!first) try writer.writeAll("\n");
+
+ try writer.writeAll(" ");
+
+ try writer.writeAll("return ");
+
+ if (!(this.return_type == .void)) {
+ try writer.print("{}.asPtr", .{this.return_type.toJS("return_value")});
+ } else {
+ try writer.writeAll("ValueUndefined.asPtr");
+ }
+
+ try writer.writeAll(";\n}\n\n");
+ }
+
+ pub fn printCallbackSourceCode(
+ this: *Function,
+ writer: anytype,
+ ) !void {
+ try writer.writeAll("#define IS_CALLBACK 1\n");
+
+ brk: {
+ if (this.return_type.isFloatingPoint()) {
+ try writer.writeAll("#define USES_FLOAT 1\n");
+ break :brk;
+ }
+
+ for (this.arg_types.items) |arg| {
+ // conditionally include math.h
+ if (arg.isFloatingPoint()) {
+ try writer.writeAll("#define USES_FLOAT 1\n");
+ break;
+ }
+ }
+ }
+
+ if (comptime Environment.isRelease) {
+ try writer.writeAll(std.mem.span(FFI_HEADER));
+ } else {
+ try writer.writeAll(ffiHeader());
+ }
+
+ // -- Generate the FFI function symbol
+ try writer.writeAll("\n \n/* --- The Callback Function */\n");
+ try writer.writeAll("/* --- The Callback Function */\n");
+ try this.return_type.typename(writer);
+ try writer.writeAll(" my_callback_function");
+ try writer.writeAll("(");
+ var first = true;
+ for (this.arg_types.items) |arg, i| {
+ if (!first) {
+ try writer.writeAll(", ");
+ }
+ first = false;
+ try arg.typename(writer);
+ try writer.print(" arg{d}", .{i});
+ }
+ try writer.writeAll(");\n\n");
+
+ first = true;
+ try this.return_type.typename(writer);
+
+ try writer.writeAll(" my_callback_function");
+ try writer.writeAll("(");
+ for (this.arg_types.items) |arg, i| {
+ if (!first) {
+ try writer.writeAll(", ");
+ }
+ first = false;
+ try arg.typename(writer);
+ try writer.print(" arg{d}", .{i});
+ }
+ try writer.writeAll(") {\n");
+
+ if (comptime Environment.isDebug) {
+ try writer.writeAll("#ifdef INJECT_BEFORE\n");
+ try writer.writeAll("INJECT_BEFORE;\n");
+ try writer.writeAll("#endif\n");
+ }
+
+ first = true;
+
+ if (this.arg_types.items.len > 0) {
+ try writer.print(" EncodedJSValue arguments[{d}] = {{\n", .{this.arg_types.items.len});
+
+ var arg_buf: [512]u8 = undefined;
+ arg_buf[0.."arg".len].* = "arg".*;
+ for (this.arg_types.items) |arg, i| {
+ const printed = std.fmt.bufPrintIntToSlice(arg_buf["arg".len..], i, 10, .lower, .{});
+ const arg_name = arg_buf[0 .. "arg".len + printed.len];
+ try writer.print(" {}", .{arg.toJS(arg_name)});
+ if (i < this.arg_types.items.len - 1) {
+ try writer.writeAll(",\n");
+ }
+ }
+ try writer.writeAll("\n };\n");
+ } else {
+ try writer.writeAll(" EncodedJSValue arguments[1] = {{0}};\n");
+ }
+
+ try writer.writeAll(" ");
+ if (!(this.return_type == .void)) {
+ try writer.writeAll("EncodedJSValue return_value = {");
+ }
+ // JSC.C.JSObjectCallAsFunction(
+ // ctx,
+ // object,
+ // thisObject,
+ // argumentCount,
+ // arguments,
+ // exception,
+ // );
+ try writer.writeAll("bun_call(cachedJSContext, cachedCallbackFunction, (void*)0, ");
+ if (this.arg_types.items.len > 0) {
+ try writer.print("{d}, &arguments[0], (void*)0)", .{this.arg_types.items.len});
+ } else {
+ try writer.writeAll("0, &arguments[0], (void*)0)");
+ }
+
+ if (this.return_type != .void) {
+ try writer.print("}};\n return {}", .{this.return_type.toC("return_value")});
+ }
+
+ try writer.writeAll(";\n}\n\n");
+ }
+ };
+
+ pub const ABIType = enum(i32) {
+ char = 0,
+
+ int8_t = 1,
+ uint8_t = 2,
+
+ int16_t = 3,
+ uint16_t = 4,
+
+ int32_t = 5,
+ uint32_t = 6,
+
+ int64_t = 7,
+ uint64_t = 8,
+
+ double = 9,
+ float = 10,
+
+ bool = 11,
+
+ ptr = 12,
+
+ @"void" = 13,
+
+ cstring = 14,
+
+ i64_fast = 15,
+ u64_fast = 16,
+
+ /// Types that we can directly pass through as an `int64_t`
+ pub fn needsACastInC(this: ABIType) bool {
+ return switch (this) {
+ .char, .int8_t, .uint8_t, .int16_t, .uint16_t, .int32_t, .uint32_t => false,
+ else => true,
+ };
+ }
+
+ const map = .{
+ .{ "bool", ABIType.bool },
+ .{ "c_int", ABIType.int32_t },
+ .{ "c_uint", ABIType.uint32_t },
+ .{ "char", ABIType.char },
+ .{ "char*", ABIType.ptr },
+ .{ "double", ABIType.double },
+ .{ "f32", ABIType.float },
+ .{ "f64", ABIType.double },
+ .{ "float", ABIType.float },
+ .{ "i16", ABIType.int16_t },
+ .{ "i32", ABIType.int32_t },
+ .{ "i64", ABIType.int64_t },
+ .{ "i8", ABIType.int8_t },
+ .{ "int", ABIType.int32_t },
+ .{ "int16_t", ABIType.int16_t },
+ .{ "int32_t", ABIType.int32_t },
+ .{ "int64_t", ABIType.int64_t },
+ .{ "int8_t", ABIType.int8_t },
+ .{ "isize", ABIType.int64_t },
+ .{ "u16", ABIType.uint16_t },
+ .{ "u32", ABIType.uint32_t },
+ .{ "u64", ABIType.uint64_t },
+ .{ "u8", ABIType.uint8_t },
+ .{ "uint16_t", ABIType.uint16_t },
+ .{ "uint32_t", ABIType.uint32_t },
+ .{ "uint64_t", ABIType.uint64_t },
+ .{ "uint8_t", ABIType.uint8_t },
+ .{ "usize", ABIType.uint64_t },
+ .{ "void*", ABIType.ptr },
+ .{ "ptr", ABIType.ptr },
+ .{ "pointer", ABIType.ptr },
+ .{ "void", ABIType.@"void" },
+ .{ "cstring", ABIType.@"cstring" },
+ .{ "i64_fast", ABIType.i64_fast },
+ .{ "u64_fast", ABIType.u64_fast },
+ };
+ pub const label = ComptimeStringMap(ABIType, map);
+ const EnumMapFormatter = struct {
+ name: []const u8,
+ entry: ABIType,
+ pub fn format(self: EnumMapFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
+ try writer.writeAll("['");
+ // these are not all valid identifiers
+ try writer.writeAll(self.name);
+ try writer.writeAll("']:");
+ try std.fmt.formatInt(@enumToInt(self.entry), 10, .lower, .{}, writer);
+ try writer.writeAll(",'");
+ try std.fmt.formatInt(@enumToInt(self.entry), 10, .lower, .{}, writer);
+ try writer.writeAll("':");
+ try std.fmt.formatInt(@enumToInt(self.entry), 10, .lower, .{}, writer);
+ }
+ };
+ pub const map_to_js_object = brk: {
+ var count: usize = 2;
+ for (map) |item, i| {
+ var fmt = EnumMapFormatter{ .name = item.@"0", .entry = item.@"1" };
+ count += std.fmt.count("{}", .{fmt});
+ count += @boolToInt(i > 0);
+ }
+
+ var buf: [count]u8 = undefined;
+ buf[0] = '{';
+ buf[buf.len - 1] = '}';
+ var end: usize = 1;
+ for (map) |item, i| {
+ var fmt = EnumMapFormatter{ .name = item.@"0", .entry = item.@"1" };
+ if (i > 0) {
+ buf[end] = ',';
+ end += 1;
+ }
+ end += (std.fmt.bufPrint(buf[end..], "{}", .{fmt}) catch unreachable).len;
+ }
+
+ break :brk buf;
+ };
+
+ pub fn isFloatingPoint(this: ABIType) bool {
+ return switch (this) {
+ .double, .float => true,
+ else => false,
+ };
+ }
+
+ const ToCFormatter = struct {
+ symbol: string,
+ tag: ABIType,
+
+ pub fn format(self: ToCFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
+ switch (self.tag) {
+ .void => {},
+ .bool => {
+ try writer.print("JSVALUE_TO_BOOL({s})", .{self.symbol});
+ },
+ .char, .int8_t, .uint8_t, .int16_t, .uint16_t, .int32_t, .uint32_t => {
+ try writer.print("JSVALUE_TO_INT32({s})", .{self.symbol});
+ },
+ .i64_fast, .int64_t => {
+ try writer.print("JSVALUE_TO_INT64({s})", .{self.symbol});
+ },
+ .u64_fast, .uint64_t => {
+ try writer.print("JSVALUE_TO_UINT64(globalObject, {s})", .{self.symbol});
+ },
+ .cstring, .ptr => {
+ try writer.print("JSVALUE_TO_PTR({s})", .{self.symbol});
+ },
+ .double => {
+ try writer.print("JSVALUE_TO_DOUBLE({s})", .{self.symbol});
+ },
+ .float => {
+ try writer.print("JSVALUE_TO_FLOAT({s})", .{self.symbol});
+ },
+ }
+ }
+ };
+
+ const ToJSFormatter = struct {
+ symbol: []const u8,
+ tag: ABIType,
+
+ pub fn format(self: ToJSFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
+ switch (self.tag) {
+ .void => {},
+ .bool => {
+ try writer.print("BOOLEAN_TO_JSVALUE({s})", .{self.symbol});
+ },
+ .char, .int8_t, .uint8_t, .int16_t, .uint16_t, .int32_t, .uint32_t => {
+ try writer.print("INT32_TO_JSVALUE({s})", .{self.symbol});
+ },
+ .i64_fast => {
+ try writer.print("INT64_TO_JSVALUE(globalObject, {s})", .{self.symbol});
+ },
+ .int64_t => {
+ try writer.print("INT64_TO_JSVALUE_SLOW(globalObject, {s})", .{self.symbol});
+ },
+ .u64_fast => {
+ try writer.print("UINT64_TO_JSVALUE(globalObject, {s})", .{self.symbol});
+ },
+ .uint64_t => {
+ try writer.print("UINT64_TO_JSVALUE_SLOW(globalObject, {s})", .{self.symbol});
+ },
+ .cstring, .ptr => {
+ try writer.print("PTR_TO_JSVALUE({s})", .{self.symbol});
+ },
+ .double => {
+ try writer.print("DOUBLE_TO_JSVALUE({s})", .{self.symbol});
+ },
+ .float => {
+ try writer.print("FLOAT_TO_JSVALUE({s})", .{self.symbol});
+ },
+ }
+ }
+ };
+
+ pub fn toC(this: ABIType, symbol: string) ToCFormatter {
+ return ToCFormatter{ .tag = this, .symbol = symbol };
+ }
+
+ pub fn toJS(
+ this: ABIType,
+ symbol: string,
+ ) ToJSFormatter {
+ return ToJSFormatter{
+ .tag = this,
+ .symbol = symbol,
+ };
+ }
+
+ pub fn typename(this: ABIType, writer: anytype) !void {
+ try writer.writeAll(this.typenameLabel());
+ }
+
+ pub fn typenameLabel(this: ABIType) []const u8 {
+ return switch (this) {
+ .cstring, .ptr => "void*",
+ .bool => "bool",
+ .int8_t => "int8_t",
+ .uint8_t => "uint8_t",
+ .int16_t => "int16_t",
+ .uint16_t => "uint16_t",
+ .int32_t => "int32_t",
+ .uint32_t => "uint32_t",
+ .i64_fast, .int64_t => "int64_t",
+ .u64_fast, .uint64_t => "uint64_t",
+ .double => "double",
+ .float => "float",
+ .char => "char",
+ .void => "void",
+ };
+ }
+ };
+};
diff --git a/src/bun.js/api/html_rewriter.zig b/src/bun.js/api/html_rewriter.zig
new file mode 100644
index 000000000..fc91c76ad
--- /dev/null
+++ b/src/bun.js/api/html_rewriter.zig
@@ -0,0 +1,1886 @@
+const std = @import("std");
+const Api = @import("../../api/schema.zig").Api;
+const FilesystemRouter = @import("../../router.zig");
+const http = @import("../../http.zig");
+const JavaScript = @import("../javascript.zig");
+const QueryStringMap = @import("../../url.zig").QueryStringMap;
+const CombinedScanner = @import("../../url.zig").CombinedScanner;
+const bun = @import("../../global.zig");
+const string = bun.string;
+const JSC = @import("../../jsc.zig");
+const js = JSC.C;
+const WebCore = @import("../webcore/response.zig");
+const Router = @This();
+const Bundler = @import("../../bundler.zig");
+const VirtualMachine = JavaScript.VirtualMachine;
+const ScriptSrcStream = std.io.FixedBufferStream([]u8);
+const ZigString = JSC.ZigString;
+const Fs = @import("../../fs.zig");
+const Base = @import("../base.zig");
+const getAllocator = Base.getAllocator;
+const JSObject = JSC.JSObject;
+const JSError = Base.JSError;
+const JSValue = JSC.JSValue;
+const JSGlobalObject = JSC.JSGlobalObject;
+const strings = @import("strings");
+const NewClass = Base.NewClass;
+const To = Base.To;
+const Request = WebCore.Request;
+const d = Base.d;
+const FetchEvent = WebCore.FetchEvent;
+const Response = WebCore.Response;
+const LOLHTML = @import("lolhtml");
+
+const SelectorMap = std.ArrayListUnmanaged(*LOLHTML.HTMLSelector);
+pub const LOLHTMLContext = struct {
+ selectors: SelectorMap = .{},
+ element_handlers: std.ArrayListUnmanaged(*ElementHandler) = .{},
+ document_handlers: std.ArrayListUnmanaged(*DocumentHandler) = .{},
+
+ pub fn deinit(this: *LOLHTMLContext, allocator: std.mem.Allocator) void {
+ for (this.selectors.items) |selector| {
+ selector.deinit();
+ }
+ this.selectors.deinit(allocator);
+ this.selectors = .{};
+
+ for (this.element_handlers.items) |handler| {
+ handler.deinit();
+ }
+ this.element_handlers.deinit(allocator);
+ this.element_handlers = .{};
+
+ for (this.document_handlers.items) |handler| {
+ handler.deinit();
+ }
+ this.document_handlers.deinit(allocator);
+ this.document_handlers = .{};
+ }
+};
+pub const HTMLRewriter = struct {
+ builder: *LOLHTML.HTMLRewriter.Builder,
+ context: LOLHTMLContext,
+
+ pub const Constructor = JSC.NewConstructor(HTMLRewriter, .{ .constructor = constructor }, .{});
+
+ pub const Class = NewClass(
+ HTMLRewriter,
+ .{ .name = "HTMLRewriter" },
+ .{
+ .finalize = finalize,
+ .on = .{
+ .rfn = wrap(HTMLRewriter, "on"),
+ },
+ .onDocument = .{
+ .rfn = wrap(HTMLRewriter, "onDocument"),
+ },
+ .transform = .{
+ .rfn = wrap(HTMLRewriter, "transform"),
+ },
+ },
+ .{},
+ );
+
+ pub fn constructor(
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: []const js.JSValueRef,
+ _: js.ExceptionRef,
+ ) js.JSObjectRef {
+ var rewriter = bun.default_allocator.create(HTMLRewriter) catch unreachable;
+ rewriter.* = HTMLRewriter{
+ .builder = LOLHTML.HTMLRewriter.Builder.init(),
+ .context = .{},
+ };
+ return HTMLRewriter.Class.make(ctx, rewriter);
+ }
+
+ pub fn on(
+ this: *HTMLRewriter,
+ global: *JSGlobalObject,
+ selector_name: ZigString,
+ thisObject: JSC.C.JSObjectRef,
+ listener: JSValue,
+ exception: JSC.C.ExceptionRef,
+ ) JSValue {
+ var selector_slice = std.fmt.allocPrint(bun.default_allocator, "{}", .{selector_name}) catch unreachable;
+
+ var selector = LOLHTML.HTMLSelector.parse(selector_slice) catch
+ return throwLOLHTMLError(global);
+ var handler_ = ElementHandler.init(global, listener, exception);
+ if (exception.* != null) {
+ selector.deinit();
+ return JSValue.fromRef(exception.*);
+ }
+ var handler = getAllocator(global.ref()).create(ElementHandler) catch unreachable;
+ handler.* = handler_;
+
+ this.builder.addElementContentHandlers(
+ selector,
+
+ ElementHandler,
+ ElementHandler.onElement,
+ if (handler.onElementCallback != null)
+ handler
+ else
+ null,
+
+ ElementHandler,
+ ElementHandler.onComment,
+ if (handler.onCommentCallback != null)
+ handler
+ else
+ null,
+
+ ElementHandler,
+ ElementHandler.onText,
+ if (handler.onTextCallback != null)
+ handler
+ else
+ null,
+ ) catch {
+ selector.deinit();
+ return throwLOLHTMLError(global);
+ };
+
+ this.context.selectors.append(bun.default_allocator, selector) catch unreachable;
+ this.context.element_handlers.append(bun.default_allocator, handler) catch unreachable;
+ return JSValue.fromRef(thisObject);
+ }
+
+ pub fn onDocument(
+ this: *HTMLRewriter,
+ global: *JSGlobalObject,
+ listener: JSValue,
+ thisObject: JSC.C.JSObjectRef,
+ exception: JSC.C.ExceptionRef,
+ ) JSValue {
+ var handler_ = DocumentHandler.init(global, listener, exception);
+ if (exception.* != null) {
+ return JSValue.fromRef(exception.*);
+ }
+
+ var handler = getAllocator(global.ref()).create(DocumentHandler) catch unreachable;
+ handler.* = handler_;
+
+ this.builder.addDocumentContentHandlers(
+ DocumentHandler,
+ DocumentHandler.onDocType,
+ if (handler.onDocTypeCallback != null)
+ handler
+ else
+ null,
+
+ DocumentHandler,
+ DocumentHandler.onComment,
+ if (handler.onCommentCallback != null)
+ handler
+ else
+ null,
+
+ DocumentHandler,
+ DocumentHandler.onText,
+ if (handler.onTextCallback != null)
+ handler
+ else
+ null,
+
+ DocumentHandler,
+ DocumentHandler.onEnd,
+ if (handler.onEndCallback != null)
+ handler
+ else
+ null,
+ ) catch {
+ return throwLOLHTMLError(global);
+ };
+
+ this.context.document_handlers.append(bun.default_allocator, handler) catch unreachable;
+ return JSValue.fromRef(thisObject);
+ }
+
+ pub fn finalize(this: *HTMLRewriter) void {
+ this.finalizeWithoutDestroy();
+ bun.default_allocator.destroy(this);
+ }
+
+ pub fn finalizeWithoutDestroy(this: *HTMLRewriter) void {
+ this.context.deinit(bun.default_allocator);
+ }
+
+ pub fn beginTransform(this: *HTMLRewriter, global: *JSGlobalObject, response: *Response) JSValue {
+ const new_context = this.context;
+ this.context = .{};
+ return BufferOutputSink.init(new_context, global, response, this.builder);
+ }
+
+ pub fn returnEmptyResponse(this: *HTMLRewriter, global: *JSGlobalObject, response: *Response) JSValue {
+ var result = bun.default_allocator.create(Response) catch unreachable;
+
+ response.cloneInto(result, getAllocator(global.ref()), global);
+ this.finalizeWithoutDestroy();
+ return JSValue.fromRef(Response.makeMaybePooled(global.ref(), result));
+ }
+
+ pub fn transform(this: *HTMLRewriter, global: *JSGlobalObject, response: *Response) JSValue {
+ var input = response.body.slice();
+
+ if (input.len == 0 and !(response.body.value == .Blob and response.body.value.Blob.needsToReadFile())) {
+ return this.returnEmptyResponse(global, response);
+ }
+
+ return this.beginTransform(global, response);
+ }
+
+ pub const HTMLRewriterLoader = struct {
+ rewriter: *LOLHTML.HTMLRewriter,
+ finalized: bool = false,
+ context: LOLHTMLContext,
+ chunk_size: usize = 0,
+ failed: bool = false,
+ output: JSC.WebCore.Sink,
+ signal: JSC.WebCore.Signal = .{},
+ backpressure: std.fifo.LinearFifo(u8, .Dynamic) = std.fifo.LinearFifo(u8, .Dynamic).init(bun.default_allocator),
+
+ pub fn finalize(this: *HTMLRewriterLoader) void {
+ if (this.finalized) return;
+ this.rewriter.deinit();
+ this.backpressure.deinit();
+ this.backpressure = std.fifo.LinearFifo(u8, .Dynamic).init(bun.default_allocator);
+ this.finalized = true;
+ }
+
+ pub fn fail(this: *HTMLRewriterLoader, err: JSC.Node.Syscall.Error) void {
+ this.signal.close(err);
+ this.output.end(err);
+ this.failed = true;
+ this.finalize();
+ }
+
+ pub fn connect(this: *HTMLRewriterLoader, signal: JSC.WebCore.Signal) void {
+ this.signal = signal;
+ }
+
+ pub fn writeToDestination(this: *HTMLRewriterLoader, bytes: []const u8) void {
+ if (this.backpressure.count > 0) {
+ this.backpressure.write(bytes) catch {
+ this.fail(JSC.Node.Syscall.Error.oom);
+ this.finalize();
+ };
+ return;
+ }
+
+ const write_result = this.output.write(.{ .temporary = bun.ByteList.init(bytes) });
+
+ switch (write_result) {
+ .err => |err| {
+ this.fail(err);
+ },
+ .owned_and_done, .temporary_and_done, .into_array_and_done => {
+ this.done();
+ },
+ .pending => |pending| {
+ pending.applyBackpressure(bun.default_allocator, &this.output, pending, bytes);
+ },
+ .into_array, .owned, .temporary => {
+ this.signal.ready(if (this.chunk_size > 0) this.chunk_size else null, null);
+ },
+ }
+ }
+
+ pub fn done(
+ this: *HTMLRewriterLoader,
+ ) void {
+ this.output.end(null);
+ this.signal.close(null);
+ this.finalize();
+ }
+
+ pub fn setup(
+ this: *HTMLRewriterLoader,
+ builder: *LOLHTML.HTMLRewriter.Builder,
+ context: LOLHTMLContext,
+ size_hint: ?usize,
+ output: JSC.WebCore.Sink,
+ ) ?[]const u8 {
+ for (context.document_handlers.items) |doc| {
+ doc.ctx = this;
+ }
+ for (context.element_handlers.items) |doc| {
+ doc.ctx = this;
+ }
+
+ const chunk_size = @maximum(size_hint orelse 16384, 1024);
+ this.rewriter = builder.build(
+ .UTF8,
+ .{
+ .preallocated_parsing_buffer_size = chunk_size,
+ .max_allowed_memory_usage = std.math.maxInt(u32),
+ },
+ false,
+ HTMLRewriterLoader,
+ this,
+ HTMLRewriterLoader.writeToDestination,
+ HTMLRewriterLoader.done,
+ ) catch {
+ output.end();
+ return LOLHTML.HTMLString.lastError().slice();
+ };
+
+ this.chunk_size = chunk_size;
+ this.context = context;
+ this.output = output;
+
+ return null;
+ }
+
+ pub fn sink(this: *HTMLRewriterLoader) JSC.WebCore.Sink {
+ return JSC.WebCore.Sink.init(this);
+ }
+
+ fn writeBytes(this: *HTMLRewriterLoader, bytes: bun.ByteList, comptime deinit_: bool) ?JSC.Node.Syscall.Error {
+ this.rewriter.write(bytes.slice()) catch {
+ return JSC.Node.Syscall.Error{
+ .errno = 1,
+ // TODO: make this a union
+ .path = bun.default_allocator.dupe(u8, LOLHTML.HTMLString.lastError().slice()) catch unreachable,
+ };
+ };
+ if (comptime deinit_) bytes.listManaged(bun.default_allocator).deinit();
+ return null;
+ }
+
+ pub fn write(this: *HTMLRewriterLoader, data: JSC.WebCore.StreamResult) JSC.WebCore.StreamResult.Writable {
+ switch (data) {
+ .owned => |bytes| {
+ if (this.writeBytes(bytes, true)) |err| {
+ return .{ .err = err };
+ }
+ return .{ .owned = bytes.len };
+ },
+ .owned_and_done => |bytes| {
+ if (this.writeBytes(bytes, true)) |err| {
+ return .{ .err = err };
+ }
+ return .{ .owned_and_done = bytes.len };
+ },
+ .temporary_and_done => |bytes| {
+ if (this.writeBytes(bytes, false)) |err| {
+ return .{ .err = err };
+ }
+ return .{ .temporary_and_done = bytes.len };
+ },
+ .temporary => |bytes| {
+ if (this.writeBytes(bytes, false)) |err| {
+ return .{ .err = err };
+ }
+ return .{ .temporary = bytes.len };
+ },
+ else => unreachable,
+ }
+ }
+
+ pub fn writeUTF16(this: *HTMLRewriterLoader, data: JSC.WebCore.StreamResult) JSC.WebCore.StreamResult.Writable {
+ return JSC.WebCore.Sink.UTF8Fallback.writeUTF16(HTMLRewriterLoader, this, data, write);
+ }
+
+ pub fn writeLatin1(this: *HTMLRewriterLoader, data: JSC.WebCore.StreamResult) JSC.WebCore.StreamResult.Writable {
+ return JSC.WebCore.Sink.UTF8Fallback.writeLatin1(HTMLRewriterLoader, this, data, write);
+ }
+ };
+
+ pub const BufferOutputSink = struct {
+ global: *JSGlobalObject,
+ bytes: bun.MutableString,
+ rewriter: *LOLHTML.HTMLRewriter,
+ context: LOLHTMLContext,
+ response: *Response,
+ input: JSC.WebCore.Blob = undefined,
+ pub fn init(context: LOLHTMLContext, global: *JSGlobalObject, original: *Response, builder: *LOLHTML.HTMLRewriter.Builder) JSValue {
+ var result = bun.default_allocator.create(Response) catch unreachable;
+ var sink = bun.default_allocator.create(BufferOutputSink) catch unreachable;
+ sink.* = BufferOutputSink{
+ .global = global,
+ .bytes = bun.MutableString.initEmpty(bun.default_allocator),
+ .rewriter = undefined,
+ .context = context,
+ .response = result,
+ };
+
+ for (sink.context.document_handlers.items) |doc| {
+ doc.ctx = sink;
+ }
+ for (sink.context.element_handlers.items) |doc| {
+ doc.ctx = sink;
+ }
+
+ sink.rewriter = builder.build(
+ .UTF8,
+ .{
+ .preallocated_parsing_buffer_size = @maximum(original.body.len(), 1024),
+ .max_allowed_memory_usage = std.math.maxInt(u32),
+ },
+ false,
+ BufferOutputSink,
+ sink,
+ BufferOutputSink.write,
+ BufferOutputSink.done,
+ ) catch {
+ sink.deinit();
+ bun.default_allocator.destroy(result);
+
+ return throwLOLHTMLError(global);
+ };
+
+ result.* = Response{
+ .allocator = bun.default_allocator,
+ .body = .{
+ .init = .{
+ .status_code = 200,
+ },
+ .value = .{
+ .Locked = .{
+ .global = global,
+ .task = sink,
+ },
+ },
+ },
+ };
+
+ result.body.init.headers = original.body.init.headers;
+ result.body.init.method = original.body.init.method;
+ result.body.init.status_code = original.body.init.status_code;
+
+ result.url = bun.default_allocator.dupe(u8, original.url) catch unreachable;
+ result.status_text = bun.default_allocator.dupe(u8, original.status_text) catch unreachable;
+
+ var input: JSC.WebCore.Blob = original.body.value.use();
+
+ const is_pending = input.needsToReadFile();
+ defer if (!is_pending) input.detach();
+
+ if (is_pending) {
+ input.doReadFileInternal(*BufferOutputSink, sink, onFinishedLoading, global);
+ } else if (sink.runOutputSink(input.sharedView(), false, false)) |error_value| {
+ return error_value;
+ }
+
+ // Hold off on cloning until we're actually done.
+
+ return JSC.JSValue.fromRef(
+ Response.makeMaybePooled(sink.global.ref(), sink.response),
+ );
+ }
+
+ pub fn onFinishedLoading(sink: *BufferOutputSink, bytes: JSC.WebCore.Blob.Store.ReadFile.ResultType) void {
+ switch (bytes) {
+ .err => |err| {
+ if (sink.response.body.value == .Locked and @ptrToInt(sink.response.body.value.Locked.task) == @ptrToInt(sink) and
+ sink.response.body.value.Locked.promise == null)
+ {
+ sink.response.body.value = .{ .Empty = .{} };
+ // is there a pending promise?
+ // we will need to reject it
+ } else if (sink.response.body.value == .Locked and @ptrToInt(sink.response.body.value.Locked.task) == @ptrToInt(sink) and
+ sink.response.body.value.Locked.promise != null)
+ {
+ sink.response.body.value.Locked.callback = null;
+ sink.response.body.value.Locked.task = null;
+ }
+
+ sink.response.body.value.toErrorInstance(err.toErrorInstance(sink.global), sink.global);
+ sink.rewriter.end() catch {};
+ sink.deinit();
+ return;
+ },
+ .result => |data| {
+ _ = sink.runOutputSink(data.buf, true, data.is_temporary);
+ },
+ }
+ }
+
+ pub fn runOutputSink(
+ sink: *BufferOutputSink,
+ bytes: []const u8,
+ is_async: bool,
+ free_bytes_on_end: bool,
+ ) ?JSValue {
+ defer if (free_bytes_on_end)
+ bun.default_allocator.free(bun.constStrToU8(bytes));
+
+ sink.bytes.growBy(bytes.len) catch unreachable;
+ var global = sink.global;
+ var response = sink.response;
+
+ sink.rewriter.write(bytes) catch {
+ sink.deinit();
+ bun.default_allocator.destroy(sink);
+
+ if (is_async) {
+ response.body.value.toErrorInstance(throwLOLHTMLError(global), global);
+
+ return null;
+ } else {
+ return throwLOLHTMLError(global);
+ }
+ };
+
+ sink.rewriter.end() catch {
+ if (!is_async) response.finalize();
+ sink.response = undefined;
+ sink.deinit();
+
+ if (is_async) {
+ response.body.value.toErrorInstance(throwLOLHTMLError(global), global);
+ return null;
+ } else {
+ return throwLOLHTMLError(global);
+ }
+ };
+
+ return null;
+ }
+
+ pub const Sync = enum { suspended, pending, done };
+
+ pub fn done(this: *BufferOutputSink) void {
+ var prev_value = this.response.body.value;
+ var bytes = this.bytes.toOwnedSliceLeaky();
+ this.response.body.value = .{
+ .Blob = JSC.WebCore.Blob.init(bytes, this.bytes.allocator, this.global),
+ };
+ prev_value.resolve(
+ &this.response.body.value,
+ this.global,
+ );
+ }
+
+ pub fn write(this: *BufferOutputSink, bytes: []const u8) void {
+ this.bytes.append(bytes) catch unreachable;
+ }
+
+ pub fn deinit(this: *BufferOutputSink) void {
+ this.bytes.deinit();
+
+ this.context.deinit(bun.default_allocator);
+ }
+ };
+
+ // pub const StreamOutputSink = struct {
+ // global: *JSGlobalObject,
+ // rewriter: *LOLHTML.HTMLRewriter,
+ // context: LOLHTMLContext,
+ // response: *Response,
+ // input: JSC.WebCore.Blob = undefined,
+ // pub fn init(context: LOLHTMLContext, global: *JSGlobalObject, original: *Response, builder: *LOLHTML.HTMLRewriter.Builder) JSValue {
+ // var result = bun.default_allocator.create(Response) catch unreachable;
+ // var sink = bun.default_allocator.create(StreamOutputSink) catch unreachable;
+ // sink.* = StreamOutputSink{
+ // .global = global,
+ // .rewriter = undefined,
+ // .context = context,
+ // .response = result,
+ // };
+
+ // for (sink.context.document_handlers.items) |doc| {
+ // doc.ctx = sink;
+ // }
+ // for (sink.context.element_handlers.items) |doc| {
+ // doc.ctx = sink;
+ // }
+
+ // sink.rewriter = builder.build(
+ // .UTF8,
+ // .{
+ // .preallocated_parsing_buffer_size = @maximum(original.body.len(), 1024),
+ // .max_allowed_memory_usage = std.math.maxInt(u32),
+ // },
+ // false,
+ // StreamOutputSink,
+ // sink,
+ // StreamOutputSink.write,
+ // StreamOutputSink.done,
+ // ) catch {
+ // sink.deinit();
+ // bun.default_allocator.destroy(result);
+
+ // return throwLOLHTMLError(global);
+ // };
+
+ // result.* = Response{
+ // .allocator = bun.default_allocator,
+ // .body = .{
+ // .init = .{
+ // .status_code = 200,
+ // },
+ // .value = .{
+ // .Locked = .{
+ // .global = global,
+ // .task = sink,
+ // },
+ // },
+ // },
+ // };
+
+ // result.body.init.headers = original.body.init.headers;
+ // result.body.init.method = original.body.init.method;
+ // result.body.init.status_code = original.body.init.status_code;
+
+ // result.url = bun.default_allocator.dupe(u8, original.url) catch unreachable;
+ // result.status_text = bun.default_allocator.dupe(u8, original.status_text) catch unreachable;
+
+ // var input: JSC.WebCore.Blob = original.body.value.use();
+
+ // const is_pending = input.needsToReadFile();
+ // defer if (!is_pending) input.detach();
+
+ // if (is_pending) {
+ // input.doReadFileInternal(*StreamOutputSink, sink, onFinishedLoading, global);
+ // } else if (sink.runOutputSink(input.sharedView(), false, false)) |error_value| {
+ // return error_value;
+ // }
+
+ // // Hold off on cloning until we're actually done.
+
+ // return JSC.JSValue.fromRef(
+ // Response.makeMaybePooled(sink.global.ref(), sink.response),
+ // );
+ // }
+
+ // pub fn runOutputSink(
+ // sink: *StreamOutputSink,
+ // bytes: []const u8,
+ // is_async: bool,
+ // free_bytes_on_end: bool,
+ // ) ?JSValue {
+ // defer if (free_bytes_on_end)
+ // bun.default_allocator.free(bun.constStrToU8(bytes));
+
+ // return null;
+ // }
+
+ // pub const Sync = enum { suspended, pending, done };
+
+ // pub fn done(this: *StreamOutputSink) void {
+ // var prev_value = this.response.body.value;
+ // var bytes = this.bytes.toOwnedSliceLeaky();
+ // this.response.body.value = .{
+ // .Blob = JSC.WebCore.Blob.init(bytes, this.bytes.allocator, this.global),
+ // };
+ // prev_value.resolve(
+ // &this.response.body.value,
+ // this.global,
+ // );
+ // }
+
+ // pub fn write(this: *StreamOutputSink, bytes: []const u8) void {
+ // this.bytes.append(bytes) catch unreachable;
+ // }
+
+ // pub fn deinit(this: *StreamOutputSink) void {
+ // this.bytes.deinit();
+
+ // this.context.deinit(bun.default_allocator);
+ // }
+ // };
+};
+
+const DocumentHandler = struct {
+ onDocTypeCallback: ?JSValue = null,
+ onCommentCallback: ?JSValue = null,
+ onTextCallback: ?JSValue = null,
+ onEndCallback: ?JSValue = null,
+ thisObject: JSValue,
+ global: *JSGlobalObject,
+ ctx: ?*HTMLRewriter.BufferOutputSink = null,
+
+ pub const onDocType = HandlerCallback(
+ DocumentHandler,
+ DocType,
+ LOLHTML.DocType,
+ "doctype",
+ "onDocTypeCallback",
+ );
+ pub const onComment = HandlerCallback(
+ DocumentHandler,
+ Comment,
+ LOLHTML.Comment,
+ "comment",
+ "onCommentCallback",
+ );
+ pub const onText = HandlerCallback(
+ DocumentHandler,
+ TextChunk,
+ LOLHTML.TextChunk,
+ "text_chunk",
+ "onTextCallback",
+ );
+ pub const onEnd = HandlerCallback(
+ DocumentHandler,
+ DocEnd,
+ LOLHTML.DocEnd,
+ "doc_end",
+ "onEndCallback",
+ );
+
+ pub fn init(global: *JSGlobalObject, thisObject: JSValue, exception: JSC.C.ExceptionRef) DocumentHandler {
+ var handler = DocumentHandler{
+ .thisObject = thisObject,
+ .global = global,
+ };
+
+ switch (thisObject.jsType()) {
+ .Object, .ProxyObject, .Cell, .FinalObject => {},
+ else => |kind| {
+ JSC.throwInvalidArguments(
+ "Expected object but received {s}",
+ .{std.mem.span(@tagName(kind))},
+ global.ref(),
+ exception,
+ );
+ return undefined;
+ },
+ }
+
+ if (thisObject.get(global, "doctype")) |val| {
+ if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) {
+ JSC.throwInvalidArguments("doctype must be a function", .{}, global.ref(), exception);
+ return undefined;
+ }
+ JSC.C.JSValueProtect(global.ref(), val.asObjectRef());
+ handler.onDocTypeCallback = val;
+ }
+
+ if (thisObject.get(global, "comments")) |val| {
+ if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) {
+ JSC.throwInvalidArguments("comments must be a function", .{}, global.ref(), exception);
+ return undefined;
+ }
+ JSC.C.JSValueProtect(global.ref(), val.asObjectRef());
+ handler.onCommentCallback = val;
+ }
+
+ if (thisObject.get(global, "text")) |val| {
+ if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) {
+ JSC.throwInvalidArguments("text must be a function", .{}, global.ref(), exception);
+ return undefined;
+ }
+ JSC.C.JSValueProtect(global.ref(), val.asObjectRef());
+ handler.onTextCallback = val;
+ }
+
+ if (thisObject.get(global, "end")) |val| {
+ if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) {
+ JSC.throwInvalidArguments("end must be a function", .{}, global.ref(), exception);
+ return undefined;
+ }
+ JSC.C.JSValueProtect(global.ref(), val.asObjectRef());
+ handler.onEndCallback = val;
+ }
+
+ JSC.C.JSValueProtect(global.ref(), thisObject.asObjectRef());
+ return handler;
+ }
+
+ pub fn deinit(this: *DocumentHandler) void {
+ if (this.onDocTypeCallback) |cb| {
+ JSC.C.JSValueUnprotect(this.global.ref(), cb.asObjectRef());
+ this.onDocTypeCallback = null;
+ }
+
+ if (this.onCommentCallback) |cb| {
+ JSC.C.JSValueUnprotect(this.global.ref(), cb.asObjectRef());
+ this.onCommentCallback = null;
+ }
+
+ if (this.onTextCallback) |cb| {
+ JSC.C.JSValueUnprotect(this.global.ref(), cb.asObjectRef());
+ this.onTextCallback = null;
+ }
+
+ if (this.onEndCallback) |cb| {
+ JSC.C.JSValueUnprotect(this.global.ref(), cb.asObjectRef());
+ this.onEndCallback = null;
+ }
+
+ JSC.C.JSValueUnprotect(this.global.ref(), this.thisObject.asObjectRef());
+ }
+};
+
+fn HandlerCallback(
+ comptime HandlerType: type,
+ comptime ZigType: type,
+ comptime LOLHTMLType: type,
+ comptime field_name: string,
+ comptime callback_name: string,
+) (fn (*HandlerType, *LOLHTMLType) bool) {
+ return struct {
+ pub fn callback(this: *HandlerType, value: *LOLHTMLType) bool {
+ if (comptime JSC.is_bindgen)
+ unreachable;
+ var zig_element = bun.default_allocator.create(ZigType) catch unreachable;
+ @field(zig_element, field_name) = value;
+ // At the end of this scope, the value is no longer valid
+ var args = [1]JSC.C.JSObjectRef{
+ ZigType.Class.make(this.global.ref(), zig_element),
+ };
+ var result = JSC.C.JSObjectCallAsFunctionReturnValue(
+ this.global.ref(),
+ @field(this, callback_name).?.asObjectRef(),
+ if (comptime @hasField(HandlerType, "thisObject"))
+ @field(this, "thisObject").asObjectRef()
+ else
+ null,
+ 1,
+ &args,
+ );
+ var promise_: ?*JSC.JSInternalPromise = null;
+ while (!result.isUndefinedOrNull()) {
+ if (result.isError() or result.isAggregateError(this.global)) {
+ @field(zig_element, field_name) = null;
+ return true;
+ }
+
+ var promise = promise_ orelse JSC.JSInternalPromise.resolvedPromise(this.global, result);
+ promise_ = promise;
+ JavaScript.VirtualMachine.vm.event_loop.waitForPromise(promise);
+
+ switch (promise.status(this.global.vm())) {
+ JSC.JSPromise.Status.Pending => unreachable,
+ JSC.JSPromise.Status.Rejected => {
+ JavaScript.VirtualMachine.vm.defaultErrorHandler(promise.result(this.global.vm()), null);
+ @field(zig_element, field_name) = null;
+ return false;
+ },
+ JSC.JSPromise.Status.Fulfilled => {
+ result = promise.result(this.global.vm());
+ break;
+ },
+ }
+
+ break;
+ }
+ @field(zig_element, field_name) = null;
+ return false;
+ }
+ }.callback;
+}
+
+const ElementHandler = struct {
+ onElementCallback: ?JSValue = null,
+ onCommentCallback: ?JSValue = null,
+ onTextCallback: ?JSValue = null,
+ thisObject: JSValue,
+ global: *JSGlobalObject,
+ ctx: ?*HTMLRewriter.BufferOutputSink = null,
+
+ pub fn init(global: *JSGlobalObject, thisObject: JSValue, exception: JSC.C.ExceptionRef) ElementHandler {
+ var handler = ElementHandler{
+ .thisObject = thisObject,
+ .global = global,
+ };
+
+ switch (thisObject.jsType()) {
+ .Object, .ProxyObject, .Cell, .FinalObject => {},
+ else => |kind| {
+ JSC.throwInvalidArguments(
+ "Expected object but received {s}",
+ .{std.mem.span(@tagName(kind))},
+ global.ref(),
+ exception,
+ );
+ return undefined;
+ },
+ }
+
+ if (thisObject.get(global, "element")) |val| {
+ if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) {
+ JSC.throwInvalidArguments("element must be a function", .{}, global.ref(), exception);
+ return undefined;
+ }
+ JSC.C.JSValueProtect(global.ref(), val.asObjectRef());
+ handler.onElementCallback = val;
+ }
+
+ if (thisObject.get(global, "comments")) |val| {
+ if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) {
+ JSC.throwInvalidArguments("comments must be a function", .{}, global.ref(), exception);
+ return undefined;
+ }
+ JSC.C.JSValueProtect(global.ref(), val.asObjectRef());
+ handler.onCommentCallback = val;
+ }
+
+ if (thisObject.get(global, "text")) |val| {
+ if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) {
+ JSC.throwInvalidArguments("text must be a function", .{}, global.ref(), exception);
+ return undefined;
+ }
+ JSC.C.JSValueProtect(global.ref(), val.asObjectRef());
+ handler.onTextCallback = val;
+ }
+
+ JSC.C.JSValueProtect(global.ref(), thisObject.asObjectRef());
+ return handler;
+ }
+
+ pub fn deinit(this: *ElementHandler) void {
+ if (this.onElementCallback) |cb| {
+ JSC.C.JSValueUnprotect(this.global.ref(), cb.asObjectRef());
+ this.onElementCallback = null;
+ }
+
+ if (this.onCommentCallback) |cb| {
+ JSC.C.JSValueUnprotect(this.global.ref(), cb.asObjectRef());
+ this.onCommentCallback = null;
+ }
+
+ if (this.onTextCallback) |cb| {
+ JSC.C.JSValueUnprotect(this.global.ref(), cb.asObjectRef());
+ this.onTextCallback = null;
+ }
+
+ JSC.C.JSValueUnprotect(this.global.ref(), this.thisObject.asObjectRef());
+ }
+
+ pub fn onElement(this: *ElementHandler, value: *LOLHTML.Element) bool {
+ return HandlerCallback(
+ ElementHandler,
+ Element,
+ LOLHTML.Element,
+ "element",
+ "onElementCallback",
+ )(this, value);
+ }
+
+ pub const onComment = HandlerCallback(
+ ElementHandler,
+ Comment,
+ LOLHTML.Comment,
+ "comment",
+ "onCommentCallback",
+ );
+
+ pub const onText = HandlerCallback(
+ ElementHandler,
+ TextChunk,
+ LOLHTML.TextChunk,
+ "text_chunk",
+ "onTextCallback",
+ );
+};
+
+pub const ContentOptions = struct {
+ html: bool = false,
+};
+
+const getterWrap = JSC.getterWrap;
+const setterWrap = JSC.setterWrap;
+const wrap = JSC.wrapAsync;
+
+pub fn free_html_writer_string(_: ?*anyopaque, ptr: ?*anyopaque, len: usize) callconv(.C) void {
+ var str = LOLHTML.HTMLString{ .ptr = bun.cast([*]const u8, ptr.?), .len = len };
+ str.deinit();
+}
+
+fn throwLOLHTMLError(global: *JSGlobalObject) JSValue {
+ var err = LOLHTML.HTMLString.lastError();
+ return ZigString.init(err.slice()).toErrorInstance(global);
+}
+
+fn htmlStringValue(input: LOLHTML.HTMLString, globalObject: *JSGlobalObject) JSValue {
+ var str = ZigString.init(
+ input.slice(),
+ );
+ str.detectEncoding();
+
+ return str.toExternalValueWithCallback(
+ globalObject,
+ free_html_writer_string,
+ );
+}
+
+pub const TextChunk = struct {
+ text_chunk: ?*LOLHTML.TextChunk = null,
+
+ pub const Class = NewClass(
+ TextChunk,
+ .{ .name = "TextChunk" },
+ .{
+ .before = .{
+ .rfn = wrap(TextChunk, "before"),
+ },
+ .after = .{
+ .rfn = wrap(TextChunk, "after"),
+ },
+
+ .replace = .{
+ .rfn = wrap(TextChunk, "replace"),
+ },
+
+ .remove = .{
+ .rfn = wrap(TextChunk, "remove"),
+ },
+ .finalize = finalize,
+ },
+ .{
+ .removed = .{
+ .get = getterWrap(TextChunk, "removed"),
+ },
+ .text = .{
+ .get = getterWrap(TextChunk, "getText"),
+ },
+ },
+ );
+
+ fn contentHandler(this: *TextChunk, comptime Callback: (fn (*LOLHTML.TextChunk, []const u8, bool) LOLHTML.Error!void), thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
+ if (this.text_chunk == null)
+ return JSC.JSValue.jsUndefined();
+ var content_slice = content.toSlice(bun.default_allocator);
+ defer content_slice.deinit();
+
+ Callback(
+ this.text_chunk.?,
+ content_slice.slice(),
+ contentOptions != null and contentOptions.?.html,
+ ) catch return throwLOLHTMLError(globalObject);
+
+ return JSValue.fromRef(thisObject);
+ }
+
+ pub fn before(
+ this: *TextChunk,
+ thisObject: js.JSObjectRef,
+ globalObject: *JSGlobalObject,
+ content: ZigString,
+ contentOptions: ?ContentOptions,
+ ) JSValue {
+ return this.contentHandler(LOLHTML.TextChunk.before, thisObject, globalObject, content, contentOptions);
+ }
+
+ pub fn after(
+ this: *TextChunk,
+ thisObject: js.JSObjectRef,
+ globalObject: *JSGlobalObject,
+ content: ZigString,
+ contentOptions: ?ContentOptions,
+ ) JSValue {
+ return this.contentHandler(LOLHTML.TextChunk.after, thisObject, globalObject, content, contentOptions);
+ }
+
+ pub fn replace(
+ this: *TextChunk,
+ thisObject: js.JSObjectRef,
+ globalObject: *JSGlobalObject,
+ content: ZigString,
+ contentOptions: ?ContentOptions,
+ ) JSValue {
+ return this.contentHandler(LOLHTML.TextChunk.replace, thisObject, globalObject, content, contentOptions);
+ }
+
+ pub fn remove(this: *TextChunk, thisObject: js.JSObjectRef) JSValue {
+ if (this.text_chunk == null)
+ return JSC.JSValue.jsUndefined();
+ this.text_chunk.?.remove();
+ return JSValue.fromRef(thisObject);
+ }
+
+ pub fn getText(this: *TextChunk, global: *JSGlobalObject) JSValue {
+ if (this.text_chunk == null)
+ return JSC.JSValue.jsUndefined();
+ return ZigString.init(this.text_chunk.?.getContent().slice()).withEncoding().toValue(global);
+ }
+
+ pub fn removed(this: *TextChunk, _: *JSGlobalObject) JSValue {
+ return JSC.JSValue.jsBoolean(this.text_chunk.?.isRemoved());
+ }
+
+ pub fn finalize(this: *TextChunk) void {
+ this.text_chunk = null;
+ bun.default_allocator.destroy(this);
+ }
+};
+
+pub const DocType = struct {
+ doctype: ?*LOLHTML.DocType = null,
+
+ pub fn finalize(this: *DocType) void {
+ this.doctype = null;
+ bun.default_allocator.destroy(this);
+ }
+
+ pub const Class = NewClass(
+ DocType,
+ .{
+ .name = "DocType",
+ },
+ .{
+ .finalize = finalize,
+ },
+ .{
+ .name = .{
+ .get = getterWrap(DocType, "name"),
+ },
+ .systemId = .{
+ .get = getterWrap(DocType, "systemId"),
+ },
+
+ .publicId = .{
+ .get = getterWrap(DocType, "publicId"),
+ },
+ },
+ );
+
+ /// The doctype name.
+ pub fn name(this: *DocType, global: *JSGlobalObject) JSValue {
+ if (this.doctype == null)
+ return JSC.JSValue.jsUndefined();
+ const str = this.doctype.?.getName().slice();
+ if (str.len == 0)
+ return JSValue.jsNull();
+ return ZigString.init(str).toValue(global);
+ }
+
+ pub fn systemId(this: *DocType, global: *JSGlobalObject) JSValue {
+ if (this.doctype == null)
+ return JSC.JSValue.jsUndefined();
+
+ const str = this.doctype.?.getSystemId().slice();
+ if (str.len == 0)
+ return JSValue.jsNull();
+ return ZigString.init(str).toValue(global);
+ }
+
+ pub fn publicId(this: *DocType, global: *JSGlobalObject) JSValue {
+ if (this.doctype == null)
+ return JSC.JSValue.jsUndefined();
+
+ const str = this.doctype.?.getPublicId().slice();
+ if (str.len == 0)
+ return JSValue.jsNull();
+ return ZigString.init(str).toValue(global);
+ }
+};
+
+pub const DocEnd = struct {
+ doc_end: ?*LOLHTML.DocEnd,
+
+ pub fn finalize(this: *DocEnd) void {
+ this.doc_end = null;
+ bun.default_allocator.destroy(this);
+ }
+
+ pub const Class = NewClass(
+ DocEnd,
+ .{ .name = "DocEnd" },
+ .{
+ .finalize = finalize,
+ .append = .{
+ .rfn = wrap(DocEnd, "append"),
+ },
+ },
+ .{},
+ );
+
+ fn contentHandler(this: *DocEnd, comptime Callback: (fn (*LOLHTML.DocEnd, []const u8, bool) LOLHTML.Error!void), thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
+ if (this.doc_end == null)
+ return JSValue.jsNull();
+
+ var content_slice = content.toSlice(bun.default_allocator);
+ defer content_slice.deinit();
+
+ Callback(
+ this.doc_end.?,
+ content_slice.slice(),
+ contentOptions != null and contentOptions.?.html,
+ ) catch return throwLOLHTMLError(globalObject);
+
+ return JSValue.fromRef(thisObject);
+ }
+
+ pub fn append(
+ this: *DocEnd,
+ thisObject: js.JSObjectRef,
+ globalObject: *JSGlobalObject,
+ content: ZigString,
+ contentOptions: ?ContentOptions,
+ ) JSValue {
+ return this.contentHandler(LOLHTML.DocEnd.append, thisObject, globalObject, content, contentOptions);
+ }
+};
+
+pub const Comment = struct {
+ comment: ?*LOLHTML.Comment = null,
+
+ pub fn finalize(this: *Comment) void {
+ this.comment = null;
+ bun.default_allocator.destroy(this);
+ }
+
+ pub const Class = NewClass(
+ Comment,
+ .{ .name = "Comment" },
+ .{
+ .before = .{
+ .rfn = wrap(Comment, "before"),
+ },
+ .after = .{
+ .rfn = wrap(Comment, "after"),
+ },
+
+ .replace = .{
+ .rfn = wrap(Comment, "replace"),
+ },
+
+ .remove = .{
+ .rfn = wrap(Comment, "remove"),
+ },
+ .finalize = finalize,
+ },
+ .{
+ .removed = .{
+ .get = getterWrap(Comment, "removed"),
+ },
+ .text = .{
+ .get = getterWrap(Comment, "getText"),
+ .set = setterWrap(Comment, "setText"),
+ },
+ },
+ );
+
+ fn contentHandler(this: *Comment, comptime Callback: (fn (*LOLHTML.Comment, []const u8, bool) LOLHTML.Error!void), thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
+ if (this.comment == null)
+ return JSValue.jsNull();
+ var content_slice = content.toSlice(bun.default_allocator);
+ defer content_slice.deinit();
+
+ Callback(
+ this.comment.?,
+ content_slice.slice(),
+ contentOptions != null and contentOptions.?.html,
+ ) catch return throwLOLHTMLError(globalObject);
+
+ return JSValue.fromRef(thisObject);
+ }
+
+ pub fn before(
+ this: *Comment,
+ thisObject: js.JSObjectRef,
+ globalObject: *JSGlobalObject,
+ content: ZigString,
+ contentOptions: ?ContentOptions,
+ ) JSValue {
+ return this.contentHandler(LOLHTML.Comment.before, thisObject, globalObject, content, contentOptions);
+ }
+
+ pub fn after(
+ this: *Comment,
+ thisObject: js.JSObjectRef,
+ globalObject: *JSGlobalObject,
+ content: ZigString,
+ contentOptions: ?ContentOptions,
+ ) JSValue {
+ return this.contentHandler(LOLHTML.Comment.after, thisObject, globalObject, content, contentOptions);
+ }
+
+ pub fn replace(
+ this: *Comment,
+ thisObject: js.JSObjectRef,
+ globalObject: *JSGlobalObject,
+ content: ZigString,
+ contentOptions: ?ContentOptions,
+ ) JSValue {
+ return this.contentHandler(LOLHTML.Comment.replace, thisObject, globalObject, content, contentOptions);
+ }
+
+ pub fn remove(this: *Comment, thisObject: js.JSObjectRef) JSValue {
+ if (this.comment == null)
+ return JSValue.jsNull();
+ this.comment.?.remove();
+ return JSValue.fromRef(thisObject);
+ }
+
+ pub fn getText(this: *Comment, global: *JSGlobalObject) JSValue {
+ if (this.comment == null)
+ return JSValue.jsNull();
+ return ZigString.init(this.comment.?.getText().slice()).withEncoding().toValue(global);
+ }
+
+ pub fn setText(
+ this: *Comment,
+ value: JSValue,
+ exception: JSC.C.ExceptionRef,
+ global: *JSGlobalObject,
+ ) void {
+ if (this.comment == null)
+ return;
+ var text = value.toSlice(global, bun.default_allocator);
+ defer text.deinit();
+ this.comment.?.setText(text.slice()) catch {
+ exception.* = throwLOLHTMLError(global).asObjectRef();
+ };
+ }
+
+ pub fn removed(this: *Comment, _: *JSGlobalObject) JSValue {
+ if (this.comment == null)
+ return JSC.JSValue.jsUndefined();
+ return JSC.JSValue.jsBoolean(this.comment.?.isRemoved());
+ }
+};
+
+pub const EndTag = struct {
+ end_tag: ?*LOLHTML.EndTag,
+
+ pub fn finalize(this: *EndTag) void {
+ this.end_tag = null;
+ bun.default_allocator.destroy(this);
+ }
+
+ pub const Handler = struct {
+ callback: ?JSC.JSValue,
+ global: *JSGlobalObject,
+
+ pub const onEndTag = HandlerCallback(
+ Handler,
+ EndTag,
+ LOLHTML.EndTag,
+ "end_tag",
+ "callback",
+ );
+
+ pub const onEndTagHandler = LOLHTML.DirectiveHandler(LOLHTML.EndTag, Handler, onEndTag);
+ };
+
+ pub const Class = NewClass(
+ EndTag,
+ .{ .name = "EndTag" },
+ .{
+ .before = .{
+ .rfn = wrap(EndTag, "before"),
+ },
+ .after = .{
+ .rfn = wrap(EndTag, "after"),
+ },
+
+ .remove = .{
+ .rfn = wrap(EndTag, "remove"),
+ },
+ .finalize = finalize,
+ },
+ .{
+ .name = .{
+ .get = getterWrap(EndTag, "getName"),
+ .set = setterWrap(EndTag, "setName"),
+ },
+ },
+ );
+
+ fn contentHandler(this: *EndTag, comptime Callback: (fn (*LOLHTML.EndTag, []const u8, bool) LOLHTML.Error!void), thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
+ if (this.end_tag == null)
+ return JSValue.jsNull();
+
+ var content_slice = content.toSlice(bun.default_allocator);
+ defer content_slice.deinit();
+
+ Callback(
+ this.end_tag.?,
+ content_slice.slice(),
+ contentOptions != null and contentOptions.?.html,
+ ) catch return throwLOLHTMLError(globalObject);
+
+ return JSValue.fromRef(thisObject);
+ }
+
+ pub fn before(
+ this: *EndTag,
+ thisObject: js.JSObjectRef,
+ globalObject: *JSGlobalObject,
+ content: ZigString,
+ contentOptions: ?ContentOptions,
+ ) JSValue {
+ return this.contentHandler(LOLHTML.EndTag.before, thisObject, globalObject, content, contentOptions);
+ }
+
+ pub fn after(
+ this: *EndTag,
+ thisObject: js.JSObjectRef,
+ globalObject: *JSGlobalObject,
+ content: ZigString,
+ contentOptions: ?ContentOptions,
+ ) JSValue {
+ return this.contentHandler(LOLHTML.EndTag.after, thisObject, globalObject, content, contentOptions);
+ }
+
+ pub fn replace(
+ this: *EndTag,
+ thisObject: js.JSObjectRef,
+ globalObject: *JSGlobalObject,
+ content: ZigString,
+ contentOptions: ?ContentOptions,
+ ) JSValue {
+ return this.contentHandler(LOLHTML.EndTag.replace, thisObject, globalObject, content, contentOptions);
+ }
+
+ pub fn remove(this: *EndTag, thisObject: js.JSObjectRef) JSValue {
+ if (this.end_tag == null)
+ return JSC.JSValue.jsUndefined();
+
+ this.end_tag.?.remove();
+ return JSValue.fromRef(thisObject);
+ }
+
+ pub fn getName(this: *EndTag, global: *JSGlobalObject) JSValue {
+ if (this.end_tag == null)
+ return JSC.JSValue.jsUndefined();
+
+ return ZigString.init(this.end_tag.?.getName().slice()).withEncoding().toValue(global);
+ }
+
+ pub fn setName(
+ this: *EndTag,
+ value: JSValue,
+ exception: JSC.C.ExceptionRef,
+ global: *JSGlobalObject,
+ ) void {
+ if (this.end_tag == null)
+ return;
+ var text = value.toSlice(global, bun.default_allocator);
+ defer text.deinit();
+ this.end_tag.?.setName(text.slice()) catch {
+ exception.* = throwLOLHTMLError(global).asObjectRef();
+ };
+ }
+};
+
+pub const AttributeIterator = struct {
+ iterator: ?*LOLHTML.Attribute.Iterator = null,
+
+ const attribute_iterator_path: string = "file:///bun-vfs/lolhtml/AttributeIterator.js";
+ const attribute_iterator_code: string =
+ \\"use strict";
+ \\
+ \\class AttributeIterator {
+ \\ constructor(internal) {
+ \\ this.#iterator = internal;
+ \\ }
+ \\
+ \\ #iterator;
+ \\
+ \\ [Symbol.iterator]() {
+ \\ return this;
+ \\ }
+ \\
+ \\ next() {
+ \\ if (this.#iterator === null)
+ \\ return {done: true};
+ \\ var value = this.#iterator.next();
+ \\ if (!value) {
+ \\ this.#iterator = null;
+ \\ return {done: true};
+ \\ }
+ \\ return {done: false, value: value};
+ \\ }
+ \\}
+ \\
+ \\return new AttributeIterator(internal1);
+ ;
+ threadlocal var attribute_iterator_class: JSC.C.JSObjectRef = undefined;
+ threadlocal var attribute_iterator_loaded: bool = false;
+
+ pub fn getAttributeIteratorJSClass(global: *JSGlobalObject) JSValue {
+ if (attribute_iterator_loaded)
+ return JSC.JSValue.fromRef(attribute_iterator_class);
+ attribute_iterator_loaded = true;
+ var exception_ptr: ?[*]JSC.JSValueRef = null;
+ var name = JSC.C.JSStringCreateStatic("AttributeIteratorGetter", "AttributeIteratorGetter".len);
+ var param_name = JSC.C.JSStringCreateStatic("internal1", "internal1".len);
+ var attribute_iterator_class_ = JSC.C.JSObjectMakeFunction(
+ global.ref(),
+ name,
+ 1,
+ &[_]JSC.C.JSStringRef{param_name},
+ JSC.C.JSStringCreateStatic(attribute_iterator_code.ptr, attribute_iterator_code.len),
+ JSC.C.JSStringCreateStatic(attribute_iterator_path.ptr, attribute_iterator_path.len),
+ 0,
+ exception_ptr,
+ );
+ JSC.C.JSValueProtect(global.ref(), attribute_iterator_class_);
+ attribute_iterator_class = attribute_iterator_class_;
+ return JSC.JSValue.fromRef(attribute_iterator_class);
+ }
+
+ pub fn finalize(this: *AttributeIterator) void {
+ if (this.iterator) |iter| {
+ iter.deinit();
+ this.iterator = null;
+ }
+ bun.default_allocator.destroy(this);
+ }
+
+ pub const Class = NewClass(
+ AttributeIterator,
+ .{ .name = "AttributeIterator" },
+ .{
+ .next = .{
+ .rfn = wrap(AttributeIterator, "next"),
+ },
+ .finalize = finalize,
+ },
+ .{},
+ );
+
+ const value_ = ZigString.init("value");
+ const done_ = ZigString.init("done");
+ pub fn next(
+ this: *AttributeIterator,
+ globalObject: *JSGlobalObject,
+ ) JSValue {
+ if (this.iterator == null) {
+ return JSC.JSValue.jsNull();
+ }
+
+ var attribute = this.iterator.?.next() orelse {
+ this.iterator.?.deinit();
+ this.iterator = null;
+ return JSC.JSValue.jsNull();
+ };
+
+ // TODO: don't clone here
+ const value = attribute.value();
+ const name = attribute.name();
+ defer name.deinit();
+ defer value.deinit();
+
+ var strs = [2]ZigString{
+ ZigString.init(name.slice()),
+ ZigString.init(value.slice()),
+ };
+
+ var valid_strs: []ZigString = strs[0..2];
+
+ var array = JSC.JSValue.createStringArray(
+ globalObject,
+ valid_strs.ptr,
+ valid_strs.len,
+ true,
+ );
+
+ return array;
+ }
+};
+pub const Element = struct {
+ element: ?*LOLHTML.Element = null,
+
+ pub const Class = NewClass(
+ Element,
+ .{ .name = "Element" },
+ .{
+ .getAttribute = .{
+ .rfn = wrap(Element, "getAttribute"),
+ },
+ .hasAttribute = .{
+ .rfn = wrap(Element, "hasAttribute"),
+ },
+ .setAttribute = .{
+ .rfn = wrap(Element, "setAttribute"),
+ },
+ .removeAttribute = .{
+ .rfn = wrap(Element, "removeAttribute"),
+ },
+ .before = .{
+ .rfn = wrap(Element, "before"),
+ },
+ .after = .{
+ .rfn = wrap(Element, "after"),
+ },
+ .prepend = .{
+ .rfn = wrap(Element, "prepend"),
+ },
+ .append = .{
+ .rfn = wrap(Element, "append"),
+ },
+ .replace = .{
+ .rfn = wrap(Element, "replace"),
+ },
+ .setInnerContent = .{
+ .rfn = wrap(Element, "setInnerContent"),
+ },
+ .remove = .{
+ .rfn = wrap(Element, "remove"),
+ },
+ .removeAndKeepContent = .{
+ .rfn = wrap(Element, "removeAndKeepContent"),
+ },
+ .onEndTag = .{
+ .rfn = wrap(Element, "onEndTag"),
+ },
+ .finalize = finalize,
+ },
+ .{
+ .tagName = .{
+ .get = getterWrap(Element, "getTagName"),
+ .set = setterWrap(Element, "setTagName"),
+ },
+ .removed = .{
+ .get = getterWrap(Element, "getRemoved"),
+ },
+ .namespaceURI = .{
+ .get = getterWrap(Element, "getNamespaceURI"),
+ },
+ .attributes = .{
+ .get = getterWrap(Element, "getAttributes"),
+ },
+ },
+ );
+
+ pub fn finalize(this: *Element) void {
+ this.element = null;
+ bun.default_allocator.destroy(this);
+ }
+
+ pub fn onEndTag(
+ this: *Element,
+ globalObject: *JSGlobalObject,
+ function: JSValue,
+ thisObject: JSC.C.JSObjectRef,
+ ) JSValue {
+ if (this.element == null)
+ return JSValue.jsNull();
+ if (function.isUndefinedOrNull() or !function.isCallable(globalObject.vm())) {
+ return ZigString.init("Expected a function").withEncoding().toValue(globalObject);
+ }
+
+ var end_tag_handler = bun.default_allocator.create(EndTag.Handler) catch unreachable;
+ end_tag_handler.* = .{ .global = globalObject, .callback = function };
+
+ this.element.?.onEndTag(EndTag.Handler.onEndTagHandler, end_tag_handler) catch {
+ bun.default_allocator.destroy(end_tag_handler);
+ return throwLOLHTMLError(globalObject);
+ };
+
+ JSC.C.JSValueProtect(globalObject.ref(), function.asObjectRef());
+ return JSValue.fromRef(thisObject);
+ }
+
+ // // fn wrap(comptime name: string)
+
+ /// Returns the value for a given attribute name: ZigString on the element, or null if it is not found.
+ pub fn getAttribute(this: *Element, globalObject: *JSGlobalObject, name: ZigString) JSValue {
+ if (this.element == null)
+ return JSValue.jsNull();
+
+ var slice = name.toSlice(bun.default_allocator);
+ defer slice.deinit();
+ var attr = this.element.?.getAttribute(slice.slice()).slice();
+
+ if (attr.len == 0)
+ return JSC.JSValue.jsNull();
+
+ var str = ZigString.init(
+ attr,
+ );
+
+ return str.toExternalValueWithCallback(
+ globalObject,
+ free_html_writer_string,
+ );
+ }
+
+ /// Returns a boolean indicating whether an attribute exists on the element.
+ pub fn hasAttribute(this: *Element, global: *JSGlobalObject, name: ZigString) JSValue {
+ if (this.element == null)
+ return JSValue.jsBoolean(false);
+
+ var slice = name.toSlice(bun.default_allocator);
+ defer slice.deinit();
+ return JSValue.jsBoolean(this.element.?.hasAttribute(slice.slice()) catch return throwLOLHTMLError(global));
+ }
+
+ /// Sets an attribute to a provided value, creating the attribute if it does not exist.
+ pub fn setAttribute(this: *Element, thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, name_: ZigString, value_: ZigString) JSValue {
+ if (this.element == null)
+ return JSValue.jsUndefined();
+
+ var name_slice = name_.toSlice(bun.default_allocator);
+ defer name_slice.deinit();
+
+ var value_slice = value_.toSlice(bun.default_allocator);
+ defer value_slice.deinit();
+ this.element.?.setAttribute(name_slice.slice(), value_slice.slice()) catch return throwLOLHTMLError(globalObject);
+ return JSValue.fromRef(thisObject);
+ }
+
+ /// Removes the attribute.
+ pub fn removeAttribute(this: *Element, thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, name: ZigString) JSValue {
+ if (this.element == null)
+ return JSValue.jsUndefined();
+
+ var name_slice = name.toSlice(bun.default_allocator);
+ defer name_slice.deinit();
+
+ this.element.?.removeAttribute(
+ name_slice.slice(),
+ ) catch return throwLOLHTMLError(globalObject);
+ return JSValue.fromRef(thisObject);
+ }
+
+ fn contentHandler(this: *Element, comptime Callback: (fn (*LOLHTML.Element, []const u8, bool) LOLHTML.Error!void), thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
+ if (this.element == null)
+ return JSValue.jsUndefined();
+
+ var content_slice = content.toSlice(bun.default_allocator);
+ defer content_slice.deinit();
+
+ Callback(
+ this.element.?,
+ content_slice.slice(),
+ contentOptions != null and contentOptions.?.html,
+ ) catch return throwLOLHTMLError(globalObject);
+
+ return JSValue.fromRef(thisObject);
+ }
+
+ /// Inserts content before the element.
+ pub fn before(this: *Element, thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
+ return contentHandler(
+ this,
+ LOLHTML.Element.before,
+ thisObject,
+ globalObject,
+ content,
+ contentOptions,
+ );
+ }
+
+ /// Inserts content right after the element.
+ pub fn after(this: *Element, thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
+ return contentHandler(
+ this,
+ LOLHTML.Element.after,
+ thisObject,
+ globalObject,
+ content,
+ contentOptions,
+ );
+ }
+
+ /// Inserts content right after the start tag of the element.
+ pub fn prepend(this: *Element, thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
+ return contentHandler(
+ this,
+ LOLHTML.Element.prepend,
+ thisObject,
+ globalObject,
+ content,
+ contentOptions,
+ );
+ }
+
+ /// Inserts content right before the end tag of the element.
+ pub fn append(this: *Element, thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
+ return contentHandler(
+ this,
+ LOLHTML.Element.append,
+ thisObject,
+ globalObject,
+ content,
+ contentOptions,
+ );
+ }
+
+ /// Removes the element and inserts content in place of it.
+ pub fn replace(this: *Element, thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
+ return contentHandler(
+ this,
+ LOLHTML.Element.replace,
+ thisObject,
+ globalObject,
+ content,
+ contentOptions,
+ );
+ }
+
+ /// Replaces content of the element.
+ pub fn setInnerContent(this: *Element, thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
+ return contentHandler(
+ this,
+ LOLHTML.Element.setInnerContent,
+ thisObject,
+ globalObject,
+ content,
+ contentOptions,
+ );
+ }
+
+ /// Removes the element with all its content.
+ pub fn remove(this: *Element, thisObject: js.JSObjectRef) JSValue {
+ if (this.element == null)
+ return JSValue.jsUndefined();
+
+ this.element.?.remove();
+ return JSValue.fromRef(thisObject);
+ }
+
+ /// Removes the start tag and end tag of the element but keeps its inner content intact.
+ pub fn removeAndKeepContent(this: *Element, thisObject: js.JSObjectRef) JSValue {
+ if (this.element == null)
+ return JSValue.jsUndefined();
+
+ this.element.?.removeAndKeepContent();
+ return JSValue.fromRef(thisObject);
+ }
+
+ pub fn getTagName(this: *Element, globalObject: *JSGlobalObject) JSValue {
+ if (this.element == null)
+ return JSValue.jsUndefined();
+
+ return htmlStringValue(this.element.?.tagName(), globalObject);
+ }
+
+ pub fn setTagName(this: *Element, value: JSValue, exception: JSC.C.ExceptionRef, global: *JSGlobalObject) void {
+ if (this.element == null)
+ return;
+
+ var text = value.toSlice(global, bun.default_allocator);
+ defer text.deinit();
+
+ this.element.?.setTagName(text.slice()) catch {
+ exception.* = throwLOLHTMLError(global).asObjectRef();
+ };
+ }
+
+ pub fn getRemoved(this: *Element, _: *JSGlobalObject) JSValue {
+ if (this.element == null)
+ return JSValue.jsUndefined();
+ return JSC.JSValue.jsBoolean(this.element.?.isRemoved());
+ }
+
+ pub fn getNamespaceURI(this: *Element, globalObject: *JSGlobalObject) JSValue {
+ if (this.element == null)
+ return JSValue.jsUndefined();
+
+ return ZigString.init(std.mem.span(this.element.?.namespaceURI())).toValue(globalObject);
+ }
+
+ pub fn getAttributes(this: *Element, globalObject: *JSGlobalObject) JSValue {
+ if (this.element == null)
+ return JSValue.jsUndefined();
+
+ var iter = this.element.?.attributes() orelse return throwLOLHTMLError(globalObject);
+ var attr_iter = bun.default_allocator.create(AttributeIterator) catch unreachable;
+ attr_iter.* = .{ .iterator = iter };
+ var attr = AttributeIterator.Class.make(globalObject.ref(), attr_iter);
+ JSC.C.JSValueProtect(globalObject.ref(), attr);
+ defer JSC.C.JSValueUnprotect(globalObject.ref(), attr);
+ return JSC.JSValue.fromRef(
+ JSC.C.JSObjectCallAsFunction(
+ globalObject.ref(),
+ AttributeIterator.getAttributeIteratorJSClass(globalObject).asObjectRef(),
+ null,
+ 1,
+ @ptrCast([*]JSC.C.JSObjectRef, &attr),
+ null,
+ ),
+ );
+ }
+};
diff --git a/src/bun.js/api/libtcc1.a.macos-aarch64 b/src/bun.js/api/libtcc1.a.macos-aarch64
new file mode 100644
index 000000000..60696b611
--- /dev/null
+++ b/src/bun.js/api/libtcc1.a.macos-aarch64
Binary files differ
diff --git a/src/bun.js/api/libtcc1.c b/src/bun.js/api/libtcc1.c
new file mode 100644
index 000000000..38750b825
--- /dev/null
+++ b/src/bun.js/api/libtcc1.c
@@ -0,0 +1,606 @@
+/* TCC runtime library.
+ Parts of this code are (c) 2002 Fabrice Bellard
+
+ Copyright (C) 1987, 1988, 1992, 1994, 1995 Free Software Foundation, Inc.
+
+This file is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 2, or (at your option) any
+later version.
+
+In addition to the permissions in the GNU General Public License, the
+Free Software Foundation gives you unlimited permission to link the
+compiled version of this file into combinations with other programs,
+and to distribute those combinations without any restriction coming
+from the use of this file. (The General Public License restrictions
+do apply in other respects; for example, they cover modification of
+the file, and distribution when not linked into a combine
+executable.)
+
+This file is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; see the file COPYING. If not, write to
+the Free Software Foundation, 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#define W_TYPE_SIZE 32
+#define BITS_PER_UNIT 8
+
+typedef int Wtype;
+typedef unsigned int UWtype;
+typedef unsigned int USItype;
+typedef long long DWtype;
+typedef unsigned long long UDWtype;
+
+struct DWstruct {
+ Wtype low, high;
+};
+
+typedef union
+{
+ struct DWstruct s;
+ DWtype ll;
+} DWunion;
+
+typedef long double XFtype;
+#define WORD_SIZE (sizeof (Wtype) * BITS_PER_UNIT)
+#define HIGH_WORD_COEFF (((UDWtype) 1) << WORD_SIZE)
+
+/* the following deal with IEEE single-precision numbers */
+#define EXCESS 126
+#define SIGNBIT 0x80000000
+#define HIDDEN (1 << 23)
+#define SIGN(fp) ((fp) & SIGNBIT)
+#define EXP(fp) (((fp) >> 23) & 0xFF)
+#define MANT(fp) (((fp) & 0x7FFFFF) | HIDDEN)
+#define PACK(s,e,m) ((s) | ((e) << 23) | (m))
+
+/* the following deal with IEEE double-precision numbers */
+#define EXCESSD 1022
+#define HIDDEND (1 << 20)
+#define EXPD(fp) (((fp.l.upper) >> 20) & 0x7FF)
+#define SIGND(fp) ((fp.l.upper) & SIGNBIT)
+#define MANTD(fp) (((((fp.l.upper) & 0xFFFFF) | HIDDEND) << 10) | \
+ (fp.l.lower >> 22))
+#define HIDDEND_LL ((long long)1 << 52)
+#define MANTD_LL(fp) ((fp.ll & (HIDDEND_LL-1)) | HIDDEND_LL)
+#define PACKD_LL(s,e,m) (((long long)((s)+((e)<<20))<<32)|(m))
+
+/* the following deal with x86 long double-precision numbers */
+#define EXCESSLD 16382
+#define EXPLD(fp) (fp.l.upper & 0x7fff)
+#define SIGNLD(fp) ((fp.l.upper) & 0x8000)
+
+/* only for x86 */
+union ldouble_long {
+ long double ld;
+ struct {
+ unsigned long long lower;
+ unsigned short upper;
+ } l;
+};
+
+union double_long {
+ double d;
+#if 1
+ struct {
+ unsigned int lower;
+ int upper;
+ } l;
+#else
+ struct {
+ int upper;
+ unsigned int lower;
+ } l;
+#endif
+ long long ll;
+};
+
+union float_long {
+ float f;
+ long l;
+};
+
+/* XXX: we don't support several builtin supports for now */
+#ifndef __x86_64__
+
+/* XXX: use gcc/tcc intrinsic ? */
+#if defined(__i386__)
+#define sub_ddmmss(sh, sl, ah, al, bh, bl) \
+ __asm__ ("subl %5,%1\n\tsbbl %3,%0" \
+ : "=r" ((USItype) (sh)), \
+ "=&r" ((USItype) (sl)) \
+ : "0" ((USItype) (ah)), \
+ "g" ((USItype) (bh)), \
+ "1" ((USItype) (al)), \
+ "g" ((USItype) (bl)))
+#define umul_ppmm(w1, w0, u, v) \
+ __asm__ ("mull %3" \
+ : "=a" ((USItype) (w0)), \
+ "=d" ((USItype) (w1)) \
+ : "%0" ((USItype) (u)), \
+ "rm" ((USItype) (v)))
+#define udiv_qrnnd(q, r, n1, n0, dv) \
+ __asm__ ("divl %4" \
+ : "=a" ((USItype) (q)), \
+ "=d" ((USItype) (r)) \
+ : "0" ((USItype) (n0)), \
+ "1" ((USItype) (n1)), \
+ "rm" ((USItype) (dv)))
+#define count_leading_zeros(count, x) \
+ do { \
+ USItype __cbtmp; \
+ __asm__ ("bsrl %1,%0" \
+ : "=r" (__cbtmp) : "rm" ((USItype) (x))); \
+ (count) = __cbtmp ^ 31; \
+ } while (0)
+#else
+#error unsupported CPU type
+#endif
+
+/* most of this code is taken from libgcc2.c from gcc */
+
+static UDWtype __udivmoddi4 (UDWtype n, UDWtype d, UDWtype *rp)
+{
+ DWunion ww;
+ DWunion nn, dd;
+ DWunion rr;
+ UWtype d0, d1, n0, n1, n2;
+ UWtype q0, q1;
+ UWtype b, bm;
+
+ nn.ll = n;
+ dd.ll = d;
+
+ d0 = dd.s.low;
+ d1 = dd.s.high;
+ n0 = nn.s.low;
+ n1 = nn.s.high;
+
+#if !UDIV_NEEDS_NORMALIZATION
+ if (d1 == 0)
+ {
+ if (d0 > n1)
+ {
+ /* 0q = nn / 0D */
+
+ udiv_qrnnd (q0, n0, n1, n0, d0);
+ q1 = 0;
+
+ /* Remainder in n0. */
+ }
+ else
+ {
+ /* qq = NN / 0d */
+
+ if (d0 == 0)
+ d0 = 1 / d0; /* Divide intentionally by zero. */
+
+ udiv_qrnnd (q1, n1, 0, n1, d0);
+ udiv_qrnnd (q0, n0, n1, n0, d0);
+
+ /* Remainder in n0. */
+ }
+
+ if (rp != 0)
+ {
+ rr.s.low = n0;
+ rr.s.high = 0;
+ *rp = rr.ll;
+ }
+ }
+
+#else /* UDIV_NEEDS_NORMALIZATION */
+
+ if (d1 == 0)
+ {
+ if (d0 > n1)
+ {
+ /* 0q = nn / 0D */
+
+ count_leading_zeros (bm, d0);
+
+ if (bm != 0)
+ {
+ /* Normalize, i.e. make the most significant bit of the
+ denominator set. */
+
+ d0 = d0 << bm;
+ n1 = (n1 << bm) | (n0 >> (W_TYPE_SIZE - bm));
+ n0 = n0 << bm;
+ }
+
+ udiv_qrnnd (q0, n0, n1, n0, d0);
+ q1 = 0;
+
+ /* Remainder in n0 >> bm. */
+ }
+ else
+ {
+ /* qq = NN / 0d */
+
+ if (d0 == 0)
+ d0 = 1 / d0; /* Divide intentionally by zero. */
+
+ count_leading_zeros (bm, d0);
+
+ if (bm == 0)
+ {
+ /* From (n1 >= d0) /\ (the most significant bit of d0 is set),
+ conclude (the most significant bit of n1 is set) /\ (the
+ leading quotient digit q1 = 1).
+
+ This special case is necessary, not an optimization.
+ (Shifts counts of W_TYPE_SIZE are undefined.) */
+
+ n1 -= d0;
+ q1 = 1;
+ }
+ else
+ {
+ /* Normalize. */
+
+ b = W_TYPE_SIZE - bm;
+
+ d0 = d0 << bm;
+ n2 = n1 >> b;
+ n1 = (n1 << bm) | (n0 >> b);
+ n0 = n0 << bm;
+
+ udiv_qrnnd (q1, n1, n2, n1, d0);
+ }
+
+ /* n1 != d0... */
+
+ udiv_qrnnd (q0, n0, n1, n0, d0);
+
+ /* Remainder in n0 >> bm. */
+ }
+
+ if (rp != 0)
+ {
+ rr.s.low = n0 >> bm;
+ rr.s.high = 0;
+ *rp = rr.ll;
+ }
+ }
+#endif /* UDIV_NEEDS_NORMALIZATION */
+
+ else
+ {
+ if (d1 > n1)
+ {
+ /* 00 = nn / DD */
+
+ q0 = 0;
+ q1 = 0;
+
+ /* Remainder in n1n0. */
+ if (rp != 0)
+ {
+ rr.s.low = n0;
+ rr.s.high = n1;
+ *rp = rr.ll;
+ }
+ }
+ else
+ {
+ /* 0q = NN / dd */
+
+ count_leading_zeros (bm, d1);
+ if (bm == 0)
+ {
+ /* From (n1 >= d1) /\ (the most significant bit of d1 is set),
+ conclude (the most significant bit of n1 is set) /\ (the
+ quotient digit q0 = 0 or 1).
+
+ This special case is necessary, not an optimization. */
+
+ /* The condition on the next line takes advantage of that
+ n1 >= d1 (true due to program flow). */
+ if (n1 > d1 || n0 >= d0)
+ {
+ q0 = 1;
+ sub_ddmmss (n1, n0, n1, n0, d1, d0);
+ }
+ else
+ q0 = 0;
+
+ q1 = 0;
+
+ if (rp != 0)
+ {
+ rr.s.low = n0;
+ rr.s.high = n1;
+ *rp = rr.ll;
+ }
+ }
+ else
+ {
+ UWtype m1, m0;
+ /* Normalize. */
+
+ b = W_TYPE_SIZE - bm;
+
+ d1 = (d1 << bm) | (d0 >> b);
+ d0 = d0 << bm;
+ n2 = n1 >> b;
+ n1 = (n1 << bm) | (n0 >> b);
+ n0 = n0 << bm;
+
+ udiv_qrnnd (q0, n1, n2, n1, d1);
+ umul_ppmm (m1, m0, q0, d0);
+
+ if (m1 > n1 || (m1 == n1 && m0 > n0))
+ {
+ q0--;
+ sub_ddmmss (m1, m0, m1, m0, d1, d0);
+ }
+
+ q1 = 0;
+
+ /* Remainder in (n1n0 - m1m0) >> bm. */
+ if (rp != 0)
+ {
+ sub_ddmmss (n1, n0, n1, n0, m1, m0);
+ rr.s.low = (n1 << b) | (n0 >> bm);
+ rr.s.high = n1 >> bm;
+ *rp = rr.ll;
+ }
+ }
+ }
+ }
+
+ ww.s.low = q0;
+ ww.s.high = q1;
+ return ww.ll;
+}
+
+#define __negdi2(a) (-(a))
+
+long long __divdi3(long long u, long long v)
+{
+ int c = 0;
+ DWunion uu, vv;
+ DWtype w;
+
+ uu.ll = u;
+ vv.ll = v;
+
+ if (uu.s.high < 0) {
+ c = ~c;
+ uu.ll = __negdi2 (uu.ll);
+ }
+ if (vv.s.high < 0) {
+ c = ~c;
+ vv.ll = __negdi2 (vv.ll);
+ }
+ w = __udivmoddi4 (uu.ll, vv.ll, (UDWtype *) 0);
+ if (c)
+ w = __negdi2 (w);
+ return w;
+}
+
+long long __moddi3(long long u, long long v)
+{
+ int c = 0;
+ DWunion uu, vv;
+ DWtype w;
+
+ uu.ll = u;
+ vv.ll = v;
+
+ if (uu.s.high < 0) {
+ c = ~c;
+ uu.ll = __negdi2 (uu.ll);
+ }
+ if (vv.s.high < 0)
+ vv.ll = __negdi2 (vv.ll);
+
+ __udivmoddi4 (uu.ll, vv.ll, (UDWtype *) &w);
+ if (c)
+ w = __negdi2 (w);
+ return w;
+}
+
+unsigned long long __udivdi3(unsigned long long u, unsigned long long v)
+{
+ return __udivmoddi4 (u, v, (UDWtype *) 0);
+}
+
+unsigned long long __umoddi3(unsigned long long u, unsigned long long v)
+{
+ UDWtype w;
+
+ __udivmoddi4 (u, v, &w);
+ return w;
+}
+
+/* XXX: fix tcc's code generator to do this instead */
+long long __ashrdi3(long long a, int b)
+{
+#ifdef __TINYC__
+ DWunion u;
+ u.ll = a;
+ if (b >= 32) {
+ u.s.low = u.s.high >> (b - 32);
+ u.s.high = u.s.high >> 31;
+ } else if (b != 0) {
+ u.s.low = ((unsigned)u.s.low >> b) | (u.s.high << (32 - b));
+ u.s.high = u.s.high >> b;
+ }
+ return u.ll;
+#else
+ return a >> b;
+#endif
+}
+
+/* XXX: fix tcc's code generator to do this instead */
+unsigned long long __lshrdi3(unsigned long long a, int b)
+{
+#ifdef __TINYC__
+ DWunion u;
+ u.ll = a;
+ if (b >= 32) {
+ u.s.low = (unsigned)u.s.high >> (b - 32);
+ u.s.high = 0;
+ } else if (b != 0) {
+ u.s.low = ((unsigned)u.s.low >> b) | (u.s.high << (32 - b));
+ u.s.high = (unsigned)u.s.high >> b;
+ }
+ return u.ll;
+#else
+ return a >> b;
+#endif
+}
+
+/* XXX: fix tcc's code generator to do this instead */
+long long __ashldi3(long long a, int b)
+{
+#ifdef __TINYC__
+ DWunion u;
+ u.ll = a;
+ if (b >= 32) {
+ u.s.high = (unsigned)u.s.low << (b - 32);
+ u.s.low = 0;
+ } else if (b != 0) {
+ u.s.high = ((unsigned)u.s.high << b) | ((unsigned)u.s.low >> (32 - b));
+ u.s.low = (unsigned)u.s.low << b;
+ }
+ return u.ll;
+#else
+ return a << b;
+#endif
+}
+
+#if defined(__i386__)
+/* FPU control word for rounding to nearest mode */
+unsigned short __tcc_fpu_control = 0x137f;
+/* FPU control word for round to zero mode for int conversion */
+unsigned short __tcc_int_fpu_control = 0x137f | 0x0c00;
+#endif
+
+#endif /* !__x86_64__ */
+
+/* XXX: fix tcc's code generator to do this instead */
+float __floatundisf(unsigned long long a)
+{
+ DWunion uu;
+ XFtype r;
+
+ uu.ll = a;
+ if (uu.s.high >= 0) {
+ return (float)uu.ll;
+ } else {
+ r = (XFtype)uu.ll;
+ r += 18446744073709551616.0;
+ return (float)r;
+ }
+}
+
+double __floatundidf(unsigned long long a)
+{
+ DWunion uu;
+ XFtype r;
+
+ uu.ll = a;
+ if (uu.s.high >= 0) {
+ return (double)uu.ll;
+ } else {
+ r = (XFtype)uu.ll;
+ r += 18446744073709551616.0;
+ return (double)r;
+ }
+}
+
+long double __floatundixf(unsigned long long a)
+{
+ DWunion uu;
+ XFtype r;
+
+ uu.ll = a;
+ if (uu.s.high >= 0) {
+ return (long double)uu.ll;
+ } else {
+ r = (XFtype)uu.ll;
+ r += 18446744073709551616.0;
+ return (long double)r;
+ }
+}
+
+unsigned long long __fixunssfdi (float a1)
+{
+ register union float_long fl1;
+ register int exp;
+ register unsigned long l;
+
+ fl1.f = a1;
+
+ if (fl1.l == 0)
+ return (0);
+
+ exp = EXP (fl1.l) - EXCESS - 24;
+
+ l = MANT(fl1.l);
+ if (exp >= 41)
+ return (unsigned long long)-1;
+ else if (exp >= 0)
+ return (unsigned long long)l << exp;
+ else if (exp >= -23)
+ return l >> -exp;
+ else
+ return 0;
+}
+
+unsigned long long __fixunsdfdi (double a1)
+{
+ register union double_long dl1;
+ register int exp;
+ register unsigned long long l;
+
+ dl1.d = a1;
+
+ if (dl1.ll == 0)
+ return (0);
+
+ exp = EXPD (dl1) - EXCESSD - 53;
+
+ l = MANTD_LL(dl1);
+
+ if (exp >= 12)
+ return (unsigned long long)-1;
+ else if (exp >= 0)
+ return l << exp;
+ else if (exp >= -52)
+ return l >> -exp;
+ else
+ return 0;
+}
+
+unsigned long long __fixunsxfdi (long double a1)
+{
+ register union ldouble_long dl1;
+ register int exp;
+ register unsigned long long l;
+
+ dl1.ld = a1;
+
+ if (dl1.l.lower == 0 && dl1.l.upper == 0)
+ return (0);
+
+ exp = EXPLD (dl1) - EXCESSLD - 64;
+
+ l = dl1.l.lower;
+
+ if (exp > 0)
+ return (unsigned long long)-1;
+ else if (exp >= -63)
+ return l >> -exp;
+ else
+ return 0;
+}
diff --git a/src/bun.js/api/router.zig b/src/bun.js/api/router.zig
new file mode 100644
index 000000000..847e7a756
--- /dev/null
+++ b/src/bun.js/api/router.zig
@@ -0,0 +1,541 @@
+const std = @import("std");
+const Api = @import("../../api/schema.zig").Api;
+const FilesystemRouter = @import("../../router.zig");
+const http = @import("../../http.zig");
+const JavaScript = @import("../javascript.zig");
+const QueryStringMap = @import("../../url.zig").QueryStringMap;
+const CombinedScanner = @import("../../url.zig").CombinedScanner;
+const bun = @import("../../global.zig");
+const string = bun.string;
+const JSC = @import("../../jsc.zig");
+const js = JSC.C;
+const WebCore = @import("../webcore/response.zig");
+const Router = @This();
+const Bundler = @import("../../bundler.zig");
+const VirtualMachine = JavaScript.VirtualMachine;
+const ScriptSrcStream = std.io.FixedBufferStream([]u8);
+const ZigString = JSC.ZigString;
+const Fs = @import("../../fs.zig");
+const Base = @import("../base.zig");
+const getAllocator = Base.getAllocator;
+const JSObject = JSC.JSObject;
+const JSError = Base.JSError;
+const JSValue = JSC.JSValue;
+const JSGlobalObject = JSC.JSGlobalObject;
+const strings = @import("strings");
+const NewClass = Base.NewClass;
+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("../../url.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 = .{},
+
+pub fn importRoute(
+ this: *Router,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ _: []const js.JSValueRef,
+ _: js.ExceptionRef,
+) js.JSObjectRef {
+ const prom = JSC.JSModuleLoader.loadAndEvaluateModule(ctx.ptr(), &ZigString.init(this.route.file_path));
+
+ VirtualMachine.vm.tick();
+
+ return prom.result(ctx.ptr().vm()).asRef();
+}
+
+pub fn match(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+) js.JSObjectRef {
+ if (arguments.len == 0) {
+ JSError(getAllocator(ctx), "Expected string, FetchEvent, or Request but there were no arguments", .{}, ctx, exception);
+ return null;
+ }
+
+ const arg: JSC.JSValue = brk: {
+ if (FetchEvent.Class.isLoaded()) {
+ if (JSValue.as(JSValue.fromRef(arguments[0]), FetchEvent)) |fetch_event| {
+ if (fetch_event.request_context != null) {
+ return matchFetchEvent(ctx, fetch_event, exception);
+ }
+
+ // When disconencted, we still have a copy of the request data in here
+ break :brk JSC.JSValue.fromRef(fetch_event.getRequest(ctx, null, null, null));
+ }
+ }
+ break :brk JSC.JSValue.fromRef(arguments[0]);
+ };
+
+ var router = JavaScript.VirtualMachine.vm.bundler.router orelse {
+ JSError(getAllocator(ctx), "Bun.match needs a framework configured with routes", .{}, ctx, exception);
+ return null;
+ };
+
+ 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;
+ };
+
+ 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;
+
+ return Instance.make(ctx, instance);
+ }
+ // router.routes.matchPage
+
+ return JSC.JSValue.jsNull().asObjectRef();
+}
+
+fn matchRequest(
+ ctx: js.JSContextRef,
+ request: *const Request,
+ _: js.ExceptionRef,
+) js.JSObjectRef {
+ return createRouteObject(ctx, request.request_context);
+}
+
+fn matchFetchEvent(
+ ctx: js.JSContextRef,
+ fetch_event: *const FetchEvent,
+ _: js.ExceptionRef,
+) js.JSObjectRef {
+ return createRouteObject(ctx, fetch_event.request_context.?);
+}
+
+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,
+ };
+
+ return Instance.make(ctx, router);
+}
+
+pub const match_type_definition = &[_]d.ts{
+ .{
+ .tsdoc = "Match a {@link https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent FetchEvent} to a `Route` from the local filesystem. Returns `null` if there is no match.",
+ .args = &[_]d.ts.arg{
+ .{
+ .name = "event",
+ .@"return" = "FetchEvent",
+ },
+ },
+ .@"return" = "Route | null",
+ },
+ .{
+ .tsdoc = "Match a `pathname` to a `Route` from the local filesystem. Returns `null` if there is no match.",
+ .args = &[_]d.ts.arg{
+ .{
+ .name = "pathname",
+ .@"return" = "string",
+ },
+ },
+ .@"return" = "Route | null",
+ },
+ .{
+ .tsdoc = "Match a {@link https://developer.mozilla.org/en-US/docs/Web/API/Request Request} to a `Route` from the local filesystem. Returns `null` if there is no match.",
+ .args = &[_]d.ts.arg{
+ .{
+ .name = "request",
+ .@"return" = "Request",
+ },
+ },
+ .@"return" = "Route | null",
+ },
+};
+
+pub const Instance = NewClass(
+ Router,
+ .{
+ .name = "Route",
+ .read_only = true,
+ .ts = .{
+ .class = d.ts.class{
+ .tsdoc =
+ \\Route matched from the filesystem.
+ ,
+ },
+ },
+ },
+ .{
+ .finalize = finalize,
+ .import = .{
+ .rfn = importRoute,
+ .ts = d.ts{
+ .@"return" = "Object",
+ .tsdoc =
+ \\Synchronously load & evaluate the file corresponding to the route. Returns the exports of the route. This is similar to `await import(route.filepath)`, except it's synchronous. It is recommended to use this function instead of `import`.
+ ,
+ },
+ },
+ },
+ .{
+ .pathname = .{
+ .get = getPathname,
+ .ro = true,
+ .ts = d.ts{
+ .@"return" = "string",
+ .@"tsdoc" = "URL path as appears in a web browser's address bar",
+ },
+ },
+
+ .filePath = .{
+ .get = getFilePath,
+ .ro = true,
+ .ts = d.ts{
+ .@"return" = "string",
+ .tsdoc =
+ \\Project-relative filesystem path to the route file.
+ ,
+ },
+ },
+ .scriptSrc = .{
+ .get = getScriptSrc,
+ .ro = true,
+ .ts = d.ts{
+ .@"return" = "string",
+ .tsdoc =
+ \\src attribute of the script tag that loads the route.
+ ,
+ },
+ },
+ .kind = .{
+ .get = getKind,
+ .ro = true,
+ .ts = d.ts{
+ .@"return" = "\"exact\" | \"dynamic\" | \"catch-all\" | \"optional-catch-all\"",
+ },
+ },
+ .name = .{
+ .get = getRoute,
+ .ro = true,
+ .ts = d.ts{
+ .@"return" = "string",
+ .tsdoc =
+ \\Route name
+ \\@example
+ \\`"blog/posts/[id]"`
+ \\`"blog/posts/[id]/[[...slug]]"`
+ \\`"blog"`
+ ,
+ },
+ },
+ .query = .{
+ .get = getQuery,
+ .ro = true,
+ .ts = d.ts{
+ .@"return" = "Record<string, string | string[]>",
+ .tsdoc =
+ \\Route parameters & parsed query string values as a key-value object
+ \\
+ \\@example
+ \\```js
+ \\console.assert(router.query.id === "123");
+ \\console.assert(router.pathname === "/blog/posts/123");
+ \\console.assert(router.route === "blog/posts/[id]");
+ \\```
+ ,
+ },
+ },
+ .params = .{
+ .get = getParams,
+ .ro = true,
+ .ts = d.ts{
+ .@"return" = "Record<string, string | string[]>",
+ .tsdoc =
+ \\Route parameters as a key-value object
+ \\
+ \\@example
+ \\```js
+ \\console.assert(router.query.id === "123");
+ \\console.assert(router.pathname === "/blog/posts/123");
+ \\console.assert(router.route === "blog/posts/[id]");
+ \\```
+ ,
+ },
+ },
+ },
+);
+
+pub fn getFilePath(
+ this: *Router,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ return ZigString.init(this.route.file_path)
+ .withEncoding()
+ .toValueGC(ctx.ptr()).asRef();
+}
+
+pub fn finalize(
+ this: *Router,
+) void {
+ 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(
+ this: *Router,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ return ZigString.init(this.route.pathname)
+ .withEncoding()
+ .toValueGC(ctx.ptr()).asRef();
+}
+
+pub fn getRoute(
+ this: *Router,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ return ZigString.init(this.route.name)
+ .withEncoding()
+ .toValueGC(ctx.ptr()).asRef();
+}
+
+const KindEnum = struct {
+ pub const exact = "exact";
+ pub const catch_all = "catch-all";
+ pub const optional_catch_all = "optional-catch-all";
+ pub const dynamic = "dynamic";
+
+ // this is kinda stupid it should maybe just store it
+ pub fn init(name: string) ZigString {
+ if (strings.contains(name, "[[...")) {
+ return ZigString.init(optional_catch_all);
+ } else if (strings.contains(name, "[...")) {
+ return ZigString.init(catch_all);
+ } else if (strings.contains(name, "[")) {
+ return ZigString.init(dynamic);
+ } else {
+ return ZigString.init(exact);
+ }
+ }
+};
+
+pub fn getKind(
+ this: *Router,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ return KindEnum.init(this.route.name).toValue(ctx.ptr()).asRef();
+}
+
+threadlocal var query_string_values_buf: [256]string = undefined;
+threadlocal var query_string_value_refs_buf: [256]ZigString = undefined;
+pub fn createQueryObject(ctx: js.JSContextRef, map: *QueryStringMap, _: js.ExceptionRef) callconv(.C) js.JSValueRef {
+ const QueryObjectCreator = struct {
+ query: *QueryStringMap,
+ pub fn create(this: *@This(), obj: *JSObject, global: *JSGlobalObject) void {
+ var iter = this.query.iter();
+ var str: ZigString = undefined;
+ while (iter.next(&query_string_values_buf)) |entry| {
+ str = ZigString.init(entry.name);
+
+ std.debug.assert(entry.values.len > 0);
+ if (entry.values.len > 1) {
+ var values = query_string_value_refs_buf[0..entry.values.len];
+ for (entry.values) |value, i| {
+ values[i] = ZigString.init(value);
+ }
+ obj.putRecord(global, &str, values.ptr, values.len);
+ } else {
+ query_string_value_refs_buf[0] = ZigString.init(entry.values[0]);
+
+ obj.putRecord(global, &str, &query_string_value_refs_buf, 1);
+ }
+ }
+ }
+ };
+
+ var creator = QueryObjectCreator{ .query = map };
+
+ var value = JSObject.createWithInitializer(QueryObjectCreator, &creator, ctx.ptr(), map.getNameCount());
+
+ return value.asRef();
+}
+
+pub fn getScriptSrcString(
+ comptime Writer: type,
+ writer: Writer,
+ file_path: string,
+ client_framework_enabled: bool,
+) void {
+ var entry_point_tempbuf: [bun.MAX_PATH_BYTES]u8 = undefined;
+ // We don't store the framework config including the client parts in the server
+ // instead, we just store a boolean saying whether we should generate this whenever the script is requested
+ // this is kind of bad. we should consider instead a way to inline the contents of the script.
+ if (client_framework_enabled) {
+ JSC.API.Bun.getPublicPath(
+ Bundler.ClientEntryPoint.generateEntryPointPath(
+ &entry_point_tempbuf,
+ Fs.PathName.init(file_path),
+ ),
+ VirtualMachine.vm.origin,
+ Writer,
+ writer,
+ );
+ } else {
+ JSC.API.Bun.getPublicPath(file_path, VirtualMachine.vm.origin, Writer, writer);
+ }
+}
+
+pub fn getScriptSrc(
+ this: *Router,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ var script_src_buffer = std.ArrayList(u8).init(bun.default_allocator);
+
+ var writer = script_src_buffer.writer();
+ getScriptSrcString(@TypeOf(&writer), &writer, this.route.file_path, this.route.client_framework_enabled);
+
+ return ZigString.init(script_src_buffer.toOwnedSlice()).toExternalValue(ctx.ptr()).asObjectRef();
+}
+
+pub fn getParams(
+ this: *Router,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSStringRef,
+ exception: js.ExceptionRef,
+) js.JSValueRef {
+ if (this.param_map == null) {
+ if (this.route.params.len > 0) {
+ if (QueryStringMap.initWithScanner(getAllocator(ctx), CombinedScanner.init(
+ "",
+ this.route.pathnameWithoutLeadingSlash(),
+ this.route.name,
+ this.route.params,
+ ))) |map| {
+ this.param_map = map;
+ } else |_| {}
+ }
+ }
+
+ // If it's still null, there are no params
+ if (this.param_map) |*map| {
+ return createQueryObject(ctx, map, exception);
+ } else {
+ return JSValue.createEmptyObject(ctx.ptr(), 0).asRef();
+ }
+}
+
+pub fn getQuery(
+ this: *Router,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSStringRef,
+ exception: js.ExceptionRef,
+) js.JSValueRef {
+ if (this.query_string_map == null) {
+ if (this.route.params.len > 0) {
+ if (QueryStringMap.initWithScanner(getAllocator(ctx), CombinedScanner.init(
+ this.route.query_string,
+ this.route.pathnameWithoutLeadingSlash(),
+ this.route.name,
+
+ this.route.params,
+ ))) |map| {
+ this.query_string_map = map;
+ } else |_| {}
+ } else if (this.route.query_string.len > 0) {
+ if (QueryStringMap.init(getAllocator(ctx), this.route.query_string)) |map| {
+ this.query_string_map = map;
+ } else |_| {}
+ }
+ }
+
+ // If it's still null, the query string has no names.
+ if (this.query_string_map) |*map| {
+ return createQueryObject(ctx, map, exception);
+ } else {
+ return JSValue.createEmptyObject(ctx.ptr(), 0).asRef();
+ }
+}
diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig
new file mode 100644
index 000000000..cb3c4387a
--- /dev/null
+++ b/src/bun.js/api/server.zig
@@ -0,0 +1,1844 @@
+const Bun = @This();
+const default_allocator = @import("../../global.zig").default_allocator;
+const bun = @import("../../global.zig");
+const Environment = bun.Environment;
+const NetworkThread = @import("http").NetworkThread;
+const Global = bun.Global;
+const strings = bun.strings;
+const string = bun.string;
+const Output = @import("../../global.zig").Output;
+const MutableString = @import("../../global.zig").MutableString;
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const IdentityContext = @import("../../identity_context.zig").IdentityContext;
+const Fs = @import("../../fs.zig");
+const Resolver = @import("../../resolver/resolver.zig");
+const ast = @import("../../import_record.zig");
+const NodeModuleBundle = @import("../../node_module_bundle.zig").NodeModuleBundle;
+const MacroEntryPoint = @import("../../bundler.zig").MacroEntryPoint;
+const logger = @import("../../logger.zig");
+const Api = @import("../../api/schema.zig").Api;
+const options = @import("../../options.zig");
+const Bundler = @import("../../bundler.zig").Bundler;
+const ServerEntryPoint = @import("../../bundler.zig").ServerEntryPoint;
+const js_printer = @import("../../js_printer.zig");
+const js_parser = @import("../../js_parser.zig");
+const js_ast = @import("../../js_ast.zig");
+const hash_map = @import("../../hash_map.zig");
+const http = @import("../../http.zig");
+const NodeFallbackModules = @import("../../node_fallbacks.zig");
+const ImportKind = ast.ImportKind;
+const Analytics = @import("../../analytics/analytics_thread.zig");
+const ZigString = @import("../../jsc.zig").ZigString;
+const Runtime = @import("../../runtime.zig");
+const Router = @import("./router.zig");
+const ImportRecord = ast.ImportRecord;
+const DotEnv = @import("../../env_loader.zig");
+const ParseResult = @import("../../bundler.zig").ParseResult;
+const PackageJSON = @import("../../resolver/package_json.zig").PackageJSON;
+const MacroRemap = @import("../../resolver/package_json.zig").MacroMap;
+const WebCore = @import("../../jsc.zig").WebCore;
+const Request = WebCore.Request;
+const Response = WebCore.Response;
+const Headers = WebCore.Headers;
+const Fetch = WebCore.Fetch;
+const HTTP = @import("http");
+const FetchEvent = WebCore.FetchEvent;
+const js = @import("../../jsc.zig").C;
+const JSC = @import("../../jsc.zig");
+const JSError = @import("../base.zig").JSError;
+const MarkedArrayBuffer = @import("../base.zig").MarkedArrayBuffer;
+const getAllocator = @import("../base.zig").getAllocator;
+const JSValue = @import("../../jsc.zig").JSValue;
+const NewClass = @import("../base.zig").NewClass;
+const Microtask = @import("../../jsc.zig").Microtask;
+const JSGlobalObject = @import("../../jsc.zig").JSGlobalObject;
+const ExceptionValueRef = @import("../../jsc.zig").ExceptionValueRef;
+const JSPrivateDataPtr = @import("../../jsc.zig").JSPrivateDataPtr;
+const ZigConsoleClient = @import("../../jsc.zig").ZigConsoleClient;
+const Node = @import("../../jsc.zig").Node;
+const ZigException = @import("../../jsc.zig").ZigException;
+const ZigStackTrace = @import("../../jsc.zig").ZigStackTrace;
+const ErrorableResolvedSource = @import("../../jsc.zig").ErrorableResolvedSource;
+const ResolvedSource = @import("../../jsc.zig").ResolvedSource;
+const JSPromise = @import("../../jsc.zig").JSPromise;
+const JSInternalPromise = @import("../../jsc.zig").JSInternalPromise;
+const JSModuleLoader = @import("../../jsc.zig").JSModuleLoader;
+const JSPromiseRejectionOperation = @import("../../jsc.zig").JSPromiseRejectionOperation;
+const Exception = @import("../../jsc.zig").Exception;
+const ErrorableZigString = @import("../../jsc.zig").ErrorableZigString;
+const ZigGlobalObject = @import("../../jsc.zig").ZigGlobalObject;
+const VM = @import("../../jsc.zig").VM;
+const JSFunction = @import("../../jsc.zig").JSFunction;
+const Config = @import("../config.zig");
+const URL = @import("../../url.zig").URL;
+const Transpiler = @import("./transpiler.zig");
+const VirtualMachine = @import("../javascript.zig").VirtualMachine;
+const IOTask = JSC.IOTask;
+const is_bindgen = JSC.is_bindgen;
+const uws = @import("uws");
+const Fallback = Runtime.Fallback;
+const MimeType = HTTP.MimeType;
+const Blob = JSC.WebCore.Blob;
+const BoringSSL = @import("boringssl");
+const Arena = @import("../../mimalloc_arena.zig").Arena;
+const SendfileContext = struct {
+ fd: i32,
+ socket_fd: i32 = 0,
+ remain: Blob.SizeType = 0,
+ offset: Blob.SizeType = 0,
+ has_listener: bool = false,
+ has_set_on_writable: bool = false,
+ auto_close: bool = false,
+};
+const DateTime = @import("datetime");
+const linux = std.os.linux;
+
+pub const ServerConfig = struct {
+ port: u16 = 0,
+ hostname: [*:0]const u8 = "0.0.0.0",
+
+ // TODO: use webkit URL parser instead of bun's
+ base_url: URL = URL{},
+ base_uri: string = "",
+
+ ssl_config: ?SSLConfig = null,
+ max_request_body_size: usize = 1024 * 1024 * 128,
+ development: bool = false,
+
+ onError: JSC.JSValue = JSC.JSValue.zero,
+ onRequest: JSC.JSValue = JSC.JSValue.zero,
+
+ pub const SSLConfig = struct {
+ server_name: [*c]const u8 = null,
+
+ key_file_name: [*c]const u8 = null,
+ cert_file_name: [*c]const u8 = null,
+
+ ca_file_name: [*c]const u8 = null,
+ dh_params_file_name: [*c]const u8 = null,
+
+ passphrase: [*c]const u8 = null,
+ low_memory_mode: bool = false,
+
+ pub fn deinit(this: *SSLConfig) void {
+ const fields = .{
+ "server_name",
+ "key_file_name",
+ "cert_file_name",
+ "ca_file_name",
+ "dh_params_file_name",
+ "passphrase",
+ };
+
+ inline for (fields) |field| {
+ const slice = std.mem.span(@field(this, field));
+ if (slice.len > 0) {
+ bun.default_allocator.free(slice);
+ }
+ }
+ }
+
+ const zero = SSLConfig{};
+
+ pub fn inJS(global: *JSC.JSGlobalObject, obj: JSC.JSValue, exception: JSC.C.ExceptionRef) ?SSLConfig {
+ var result = zero;
+ var any = false;
+
+ // Required
+ if (obj.getTruthy(global, "keyFile")) |key_file_name| {
+ var sliced = key_file_name.toSlice(global, bun.default_allocator);
+ defer sliced.deinit();
+ if (sliced.len > 0) {
+ result.key_file_name = bun.default_allocator.dupeZ(u8, sliced.slice()) catch unreachable;
+ if (std.os.system.access(result.key_file_name, std.os.F_OK) != 0) {
+ JSC.throwInvalidArguments("Unable to access keyFile path", .{}, global.ref(), exception);
+ result.deinit();
+
+ return null;
+ }
+ any = true;
+ }
+ }
+ if (obj.getTruthy(global, "certFile")) |cert_file_name| {
+ var sliced = cert_file_name.toSlice(global, bun.default_allocator);
+ defer sliced.deinit();
+ if (sliced.len > 0) {
+ result.cert_file_name = bun.default_allocator.dupeZ(u8, sliced.slice()) catch unreachable;
+ if (std.os.system.access(result.cert_file_name, std.os.F_OK) != 0) {
+ JSC.throwInvalidArguments("Unable to access certFile path", .{}, global.ref(), exception);
+ result.deinit();
+ return null;
+ }
+ any = true;
+ }
+ }
+
+ // Optional
+ if (any) {
+ if (obj.getTruthy(global, "serverName")) |key_file_name| {
+ var sliced = key_file_name.toSlice(global, bun.default_allocator);
+ defer sliced.deinit();
+ if (sliced.len > 0) {
+ result.server_name = bun.default_allocator.dupeZ(u8, sliced.slice()) catch unreachable;
+ }
+ }
+
+ if (obj.getTruthy(global, "caFile")) |ca_file_name| {
+ var sliced = ca_file_name.toSlice(global, bun.default_allocator);
+ defer sliced.deinit();
+ if (sliced.len > 0) {
+ result.ca_file_name = bun.default_allocator.dupeZ(u8, sliced.slice()) catch unreachable;
+ if (std.os.system.access(result.ca_file_name, std.os.F_OK) != 0) {
+ JSC.throwInvalidArguments("Invalid caFile path", .{}, global.ref(), exception);
+ result.deinit();
+ return null;
+ }
+ }
+ }
+ if (obj.getTruthy(global, "dhParamsFile")) |dh_params_file_name| {
+ var sliced = dh_params_file_name.toSlice(global, bun.default_allocator);
+ defer sliced.deinit();
+ if (sliced.len > 0) {
+ result.dh_params_file_name = bun.default_allocator.dupeZ(u8, sliced.slice()) catch unreachable;
+ if (std.os.system.access(result.dh_params_file_name, std.os.F_OK) != 0) {
+ JSC.throwInvalidArguments("Invalid dhParamsFile path", .{}, global.ref(), exception);
+ result.deinit();
+ return null;
+ }
+ }
+ }
+
+ if (obj.getTruthy(global, "passphrase")) |passphrase| {
+ var sliced = passphrase.toSlice(global, bun.default_allocator);
+ defer sliced.deinit();
+ if (sliced.len > 0) {
+ result.passphrase = bun.default_allocator.dupeZ(u8, sliced.slice()) catch unreachable;
+ }
+ }
+
+ if (obj.get(global, "lowMemoryMode")) |low_memory_mode| {
+ result.low_memory_mode = low_memory_mode.toBoolean();
+ any = true;
+ }
+ }
+
+ if (!any)
+ return null;
+ return result;
+ }
+
+ pub fn fromJS(global: *JSC.JSGlobalObject, arguments: *JSC.Node.ArgumentsSlice, exception: JSC.C.ExceptionRef) ?SSLConfig {
+ if (arguments.next()) |arg| {
+ return SSLConfig.inJS(global, arg, exception);
+ }
+
+ return null;
+ }
+ };
+
+ pub fn fromJS(global: *JSC.JSGlobalObject, arguments: *JSC.Node.ArgumentsSlice, exception: JSC.C.ExceptionRef) ServerConfig {
+ var env = arguments.vm.bundler.env;
+
+ var args = ServerConfig{
+ .port = 3000,
+ .hostname = "0.0.0.0",
+ .development = true,
+ };
+ var has_hostname = false;
+ if (strings.eqlComptime(env.get("NODE_ENV") orelse "", "production")) {
+ args.development = false;
+ }
+
+ if (arguments.vm.bundler.options.production) {
+ args.development = false;
+ }
+
+ const PORT_ENV = .{ "PORT", "BUN_PORT", "NODE_PORT" };
+
+ inline for (PORT_ENV) |PORT| {
+ if (env.get(PORT)) |port| {
+ if (std.fmt.parseInt(u16, port, 10)) |_port| {
+ args.port = _port;
+ } else |_| {}
+ }
+ }
+
+ if (arguments.vm.bundler.options.transform_options.port) |port| {
+ args.port = port;
+ }
+
+ if (arguments.vm.bundler.options.transform_options.origin) |origin| {
+ args.base_uri = origin;
+ }
+
+ if (arguments.next()) |arg| {
+ if (arg.isUndefinedOrNull() or !arg.isObject()) {
+ JSC.throwInvalidArguments("Bun.serve expects an object", .{}, global.ref(), exception);
+ return args;
+ }
+
+ if (arg.getTruthy(global, "port")) |port_| {
+ args.port = @intCast(u16, @minimum(@maximum(0, port_.toInt32()), std.math.maxInt(u16)));
+ }
+
+ if (arg.getTruthy(global, "baseURI")) |baseURI| {
+ var sliced = baseURI.toSlice(global, bun.default_allocator);
+
+ if (sliced.len > 0) {
+ defer sliced.deinit();
+ args.base_uri = bun.default_allocator.dupe(u8, sliced.slice()) catch unreachable;
+ }
+ }
+
+ if (arg.getTruthy(global, "hostname") orelse arg.getTruthy(global, "host")) |host| {
+ const host_str = host.toSlice(
+ global,
+ bun.default_allocator,
+ );
+ if (host_str.len > 0) {
+ args.hostname = bun.default_allocator.dupeZ(u8, host_str.slice()) catch unreachable;
+ has_hostname = true;
+ }
+ }
+
+ if (arg.get(global, "development")) |dev| {
+ args.development = dev.toBoolean();
+ }
+
+ if (SSLConfig.fromJS(global, arguments, exception)) |ssl_config| {
+ args.ssl_config = ssl_config;
+ }
+
+ if (exception.* != null) {
+ return args;
+ }
+
+ if (arg.getTruthy(global, "maxRequestBodySize")) |max_request_body_size| {
+ args.max_request_body_size = @intCast(u64, @maximum(0, max_request_body_size.toInt64()));
+ }
+
+ if (arg.getTruthy(global, "error")) |onError| {
+ if (!onError.isCallable(global.vm())) {
+ JSC.throwInvalidArguments("Expected error to be a function", .{}, global.ref(), exception);
+ if (args.ssl_config) |*conf| {
+ conf.deinit();
+ }
+ return args;
+ }
+ JSC.C.JSValueProtect(global.ref(), onError.asObjectRef());
+ args.onError = onError;
+ }
+
+ if (arg.getTruthy(global, "fetch")) |onRequest| {
+ if (!onRequest.isCallable(global.vm())) {
+ JSC.throwInvalidArguments("Expected fetch() to be a function", .{}, global.ref(), exception);
+ return args;
+ }
+ JSC.C.JSValueProtect(global.ref(), onRequest.asObjectRef());
+ args.onRequest = onRequest;
+ } else {
+ JSC.throwInvalidArguments("Expected fetch() to be a function", .{}, global.ref(), exception);
+ if (args.ssl_config) |*conf| {
+ conf.deinit();
+ }
+ return args;
+ }
+ }
+
+ if (args.port == 0) {
+ JSC.throwInvalidArguments("Invalid port: must be > 0", .{}, global.ref(), exception);
+ }
+
+ if (args.base_uri.len > 0) {
+ args.base_url = URL.parse(args.base_uri);
+ if (args.base_url.hostname.len == 0) {
+ JSC.throwInvalidArguments("baseURI must have a hostname", .{}, global.ref(), exception);
+ bun.default_allocator.free(bun.constStrToU8(args.base_uri));
+ args.base_uri = "";
+ return args;
+ }
+
+ if (!strings.isAllASCII(args.base_uri)) {
+ JSC.throwInvalidArguments("Unicode baseURI must already be encoded for now.\nnew URL(baseuRI).toString() should do the trick.", .{}, global.ref(), exception);
+ bun.default_allocator.free(bun.constStrToU8(args.base_uri));
+ args.base_uri = "";
+ return args;
+ }
+
+ if (args.base_url.protocol.len == 0) {
+ const protocol: string = if (args.ssl_config != null) "https" else "http";
+
+ args.base_uri = (if ((args.port == 80 and args.ssl_config == null) or (args.port == 443 and args.ssl_config != null))
+ std.fmt.allocPrint(bun.default_allocator, "{s}://{s}/{s}", .{
+ protocol,
+ args.base_url.hostname,
+ strings.trimLeadingChar(args.base_url.pathname, '/'),
+ })
+ else
+ std.fmt.allocPrint(bun.default_allocator, "{s}://{s}:{d}/{s}", .{
+ protocol,
+ args.base_url.hostname,
+ args.port,
+ strings.trimLeadingChar(args.base_url.pathname, '/'),
+ })) catch unreachable;
+
+ args.base_url = URL.parse(args.base_uri);
+ }
+ } else {
+ const hostname: string =
+ if (has_hostname and std.mem.span(args.hostname).len > 0) std.mem.span(args.hostname) else "localhost";
+ const protocol: string = if (args.ssl_config != null) "https" else "http";
+
+ args.base_uri = (if ((args.port == 80 and args.ssl_config == null) or (args.port == 443 and args.ssl_config != null))
+ std.fmt.allocPrint(bun.default_allocator, "{s}://{s}/", .{
+ protocol,
+ hostname,
+ })
+ else
+ std.fmt.allocPrint(bun.default_allocator, "{s}://{s}:{d}/", .{ protocol, hostname, args.port })) catch unreachable;
+
+ if (!strings.isAllASCII(hostname)) {
+ JSC.throwInvalidArguments("Unicode hostnames must already be encoded for now.\nnew URL(input).hostname should do the trick.", .{}, global.ref(), exception);
+ bun.default_allocator.free(bun.constStrToU8(args.base_uri));
+ args.base_uri = "";
+ return args;
+ }
+
+ args.base_url = URL.parse(args.base_uri);
+ }
+
+ // I don't think there's a case where this can happen
+ // but let's check anyway, just in case
+ if (args.base_url.hostname.len == 0) {
+ JSC.throwInvalidArguments("baseURI must have a hostname", .{}, global.ref(), exception);
+ bun.default_allocator.free(bun.constStrToU8(args.base_uri));
+ args.base_uri = "";
+ return args;
+ }
+
+ if (args.base_url.username.len > 0 or args.base_url.password.len > 0) {
+ JSC.throwInvalidArguments("baseURI can't have a username or password", .{}, global.ref(), exception);
+ bun.default_allocator.free(bun.constStrToU8(args.base_uri));
+ args.base_uri = "";
+ return args;
+ }
+
+ return args;
+ }
+};
+
+pub fn NewRequestContextStackAllocator(comptime RequestContext: type, comptime count: usize) type {
+ // Pre-allocate up to 2048 requests
+ // use a bitset to track which ones are used
+ return struct {
+ buf: [count]RequestContext = undefined,
+ unused: Set = undefined,
+ fallback_allocator: std.mem.Allocator = undefined,
+
+ pub const Set = std.bit_set.ArrayBitSet(usize, count);
+
+ pub fn get(this: *@This()) std.mem.Allocator {
+ this.unused = Set.initFull();
+ return std.mem.Allocator.init(this, alloc, resize, free);
+ }
+
+ fn alloc(self: *@This(), a: usize, b: u29, c: u29, d: usize) ![]u8 {
+ if (self.unused.findFirstSet()) |i| {
+ self.unused.unset(i);
+ return std.mem.asBytes(&self.buf[i]);
+ }
+
+ return try self.fallback_allocator.rawAlloc(a, b, c, d);
+ }
+
+ fn resize(
+ _: *@This(),
+ _: []u8,
+ _: u29,
+ _: usize,
+ _: u29,
+ _: usize,
+ ) ?usize {
+ unreachable;
+ }
+
+ fn sliceContainsSlice(container: []u8, slice: []u8) bool {
+ return @ptrToInt(slice.ptr) >= @ptrToInt(container.ptr) and
+ (@ptrToInt(slice.ptr) + slice.len) <= (@ptrToInt(container.ptr) + container.len);
+ }
+
+ fn free(
+ self: *@This(),
+ buf: []u8,
+ buf_align: u29,
+ return_address: usize,
+ ) void {
+ _ = buf_align;
+ _ = return_address;
+ const bytes = std.mem.asBytes(&self.buf);
+ if (sliceContainsSlice(bytes, buf)) {
+ const index = if (bytes[0..buf.len].ptr != buf.ptr)
+ (@ptrToInt(buf.ptr) - @ptrToInt(bytes)) / @sizeOf(RequestContext)
+ else
+ @as(usize, 0);
+
+ if (comptime Environment.allow_assert) {
+ std.debug.assert(@intToPtr(*RequestContext, @ptrToInt(buf.ptr)) == &self.buf[index]);
+ std.debug.assert(!self.unused.isSet(index));
+ }
+
+ self.unused.set(index);
+ } else {
+ self.fallback_allocator.rawFree(buf, buf_align, return_address);
+ }
+ }
+ };
+}
+
+// This is defined separately partially to work-around an LLVM debugger bug.
+fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comptime ThisServer: type) type {
+ return struct {
+ const RequestContext = @This();
+ const App = uws.NewApp(ssl_enabled);
+ pub threadlocal var pool: ?*RequestContext.RequestContextStackAllocator = null;
+ pub threadlocal var pool_allocator: std.mem.Allocator = undefined;
+
+ pub const RequestContextStackAllocator = NewRequestContextStackAllocator(RequestContext, 2048);
+ pub const name = "HTTPRequestContext" ++ (if (debug_mode) "Debug" else "") ++ (if (ThisServer.ssl_enabled) "TLS" else "");
+ pub const shim = JSC.Shimmer("Bun", name, @This());
+
+ server: *ThisServer,
+ resp: *App.Response,
+ /// thread-local default heap allocator
+ /// this prevents an extra pthread_getspecific() call which shows up in profiling
+ allocator: std.mem.Allocator,
+ req: *uws.Request,
+ url: string,
+ method: HTTP.Method,
+ aborted: bool = false,
+ finalized: bun.DebugOnly(bool) = bun.DebugOnlyDefault(false),
+
+ /// We can only safely free once the request body promise is finalized
+ /// and the response is rejected
+ pending_promises_for_abort: u8 = 0,
+
+ has_marked_complete: bool = false,
+ response_jsvalue: JSC.JSValue = JSC.JSValue.zero,
+ response_ptr: ?*JSC.WebCore.Response = null,
+ blob: JSC.WebCore.Blob = JSC.WebCore.Blob{},
+ promise: ?*JSC.JSValue = null,
+ response_headers: ?*JSC.FetchHeaders = null,
+ has_abort_handler: bool = false,
+ has_sendfile_ctx: bool = false,
+ has_called_error_handler: bool = false,
+ needs_content_length: bool = false,
+ sendfile: SendfileContext = undefined,
+ request_js_object: JSC.C.JSObjectRef = null,
+ request_body_buf: std.ArrayListUnmanaged(u8) = .{},
+
+ /// Used either for temporary blob data or fallback
+ /// When the response body is a temporary value
+ response_buf_owned: std.ArrayListUnmanaged(u8) = .{},
+
+ // TODO: support builtin compression
+ const can_sendfile = !ssl_enabled;
+
+ pub const thenables = shim.thenables(.{
+ PromiseHandler,
+ });
+
+ pub const lazy_static_functions = thenables;
+ pub const Export = lazy_static_functions;
+
+ const PromiseHandler = JSC.Thenable(RequestContext, onResolve, onReject);
+
+ pub fn setAbortHandler(this: *RequestContext) void {
+ if (this.has_abort_handler) return;
+ this.has_abort_handler = true;
+ this.resp.onAborted(*RequestContext, RequestContext.onAbort, this);
+ }
+
+ pub fn onResolve(
+ ctx: *RequestContext,
+ _: *JSC.JSGlobalObject,
+ arguments: []const JSC.JSValue,
+ ) void {
+ if (ctx.aborted) {
+ ctx.finalizeForAbort();
+ return;
+ }
+
+ if (arguments.len == 0) {
+ ctx.renderMissing();
+ return;
+ }
+
+ handleResolve(ctx, arguments[0]);
+ }
+
+ fn handleResolve(ctx: *RequestContext, value: JSC.JSValue) void {
+ if (value.isEmptyOrUndefinedOrNull()) {
+ ctx.renderMissing();
+ return;
+ }
+
+ var response = value.as(JSC.WebCore.Response) orelse {
+ Output.prettyErrorln("Expected a Response object", .{});
+ Output.flush();
+ ctx.renderMissing();
+ return;
+ };
+ ctx.response_jsvalue = value;
+ JSC.C.JSValueProtect(ctx.server.globalThis.ref(), value.asObjectRef());
+
+ ctx.render(response);
+ }
+
+ pub fn finalizeForAbort(this: *RequestContext) void {
+ this.pending_promises_for_abort -|= 1;
+ if (this.pending_promises_for_abort == 0) this.finalize();
+ }
+
+ pub fn onReject(
+ ctx: *RequestContext,
+ _: *JSC.JSGlobalObject,
+ arguments: []const JSC.JSValue,
+ ) void {
+ if (ctx.aborted) {
+ ctx.finalizeForAbort();
+ return;
+ }
+ handleReject(ctx, if (arguments.len > 0) arguments[0] else JSC.JSValue.jsUndefined());
+ }
+
+ fn handleReject(ctx: *RequestContext, value: JSC.JSValue) void {
+ ctx.runErrorHandler(
+ value,
+ );
+
+ if (ctx.aborted) {
+ ctx.finalizeForAbort();
+ return;
+ }
+ if (!ctx.resp.hasResponded()) {
+ ctx.renderMissing();
+ }
+ }
+
+ pub fn renderMissing(ctx: *RequestContext) void {
+ if (comptime !debug_mode) {
+ ctx.resp.writeStatus("204 No Content");
+ ctx.resp.endWithoutBody();
+ ctx.finalize();
+ } else {
+ ctx.resp.writeStatus("200 OK");
+ ctx.resp.end("Welcome to Bun! To get started, return a Response object.", false);
+ ctx.finalize();
+ }
+ }
+
+ pub fn renderDefaultError(
+ this: *RequestContext,
+ log: *logger.Log,
+ err: anyerror,
+ exceptions: []Api.JsException,
+ comptime fmt: string,
+ args: anytype,
+ ) void {
+ this.resp.writeStatus("500 Internal Server Error");
+ this.resp.writeHeader("content-type", MimeType.html.value);
+
+ const allocator = this.allocator;
+
+ var fallback_container = allocator.create(Api.FallbackMessageContainer) catch unreachable;
+ defer allocator.destroy(fallback_container);
+ fallback_container.* = Api.FallbackMessageContainer{
+ .message = std.fmt.allocPrint(allocator, comptime Output.prettyFmt(fmt, false), args) catch unreachable,
+ .router = null,
+ .reason = .fetch_event_handler,
+ .cwd = VirtualMachine.vm.bundler.fs.top_level_dir,
+ .problems = Api.Problems{
+ .code = @truncate(u16, @errorToInt(err)),
+ .name = @errorName(err),
+ .exceptions = exceptions,
+ .build = log.toAPI(allocator) catch unreachable,
+ },
+ };
+
+ if (comptime fmt.len > 0) Output.prettyErrorln(fmt, args);
+ Output.flush();
+
+ var bb = std.ArrayList(u8).init(allocator);
+ var bb_writer = bb.writer();
+
+ Fallback.renderBackend(
+ allocator,
+ fallback_container,
+ @TypeOf(bb_writer),
+ bb_writer,
+ ) catch unreachable;
+ if (this.resp.tryEnd(bb.items, bb.items.len)) {
+ bb.clearAndFree();
+ this.finalizeWithoutDeinit();
+ return;
+ }
+
+ this.response_buf_owned = std.ArrayListUnmanaged(u8){ .items = bb.items, .capacity = bb.capacity };
+ this.renderResponseBuffer();
+ }
+
+ pub fn renderResponseBuffer(this: *RequestContext) void {
+ this.resp.onWritable(*RequestContext, onWritableResponseBuffer, this);
+ }
+
+ pub fn onWritableResponseBuffer(this: *RequestContext, write_offset: c_ulong, resp: *App.Response) callconv(.C) bool {
+ std.debug.assert(this.resp == resp);
+ if (this.aborted) {
+ this.finalizeForAbort();
+ return false;
+ }
+ return this.sendWritableBytes(this.response_buf_owned.items, write_offset, resp);
+ }
+
+ pub fn create(this: *RequestContext, server: *ThisServer, req: *uws.Request, resp: *App.Response) void {
+ this.* = .{
+ .allocator = server.allocator,
+ .resp = resp,
+ .req = req,
+ // this memory is owned by the Request object
+ .url = strings.append(this.allocator, server.base_url_string_for_joining, req.url()) catch
+ @panic("Out of memory while joining the URL path?"),
+ .method = HTTP.Method.which(req.method()) orelse .GET,
+ .server = server,
+ };
+ }
+
+ pub fn isDeadRequest(this: *RequestContext) bool {
+ if (this.pending_promises_for_abort > 0) return false;
+
+ if (this.promise != null) {
+ return false;
+ }
+
+ if (this.request_js_object) |obj| {
+ if (obj.value().as(Request)) |req| {
+ if (req.body == .Locked) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ pub fn onAbort(this: *RequestContext, resp: *App.Response) void {
+ std.debug.assert(this.resp == resp);
+ std.debug.assert(!this.aborted);
+ this.aborted = true;
+
+ // if we can, free the request now.
+ if (this.isDeadRequest()) {
+ this.finalizeWithoutDeinit();
+ this.markComplete();
+ this.deinit();
+ } else {
+ this.pending_promises_for_abort = 0;
+
+ // if we cannot, we have to reject pending promises
+ // first, we reject the request body promise
+ if (this.request_js_object != null) {
+ var request_js = this.request_js_object.?.value();
+ request_js.ensureStillAlive();
+
+ this.request_js_object = null;
+ defer request_js.ensureStillAlive();
+ defer JSC.C.JSValueUnprotect(this.server.globalThis.ref(), request_js.asObjectRef());
+ // User called .blob(), .json(), text(), or .arrayBuffer() on the Request object
+ // but we received nothing or the connection was aborted
+ if (request_js.as(Request)) |req| {
+ // the promise is pending
+ if (req.body == .Locked and (req.body.Locked.action != .none or req.body.Locked.promise != null)) {
+ this.pending_promises_for_abort += 1;
+ req.body.toErrorInstance(JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis), this.server.globalThis);
+ }
+ req.uws_request = null;
+ }
+ }
+
+ // then, we reject the response promise
+ if (this.promise) |promise| {
+ this.pending_promises_for_abort += 1;
+ this.promise = null;
+ promise.asPromise().?.reject(this.server.globalThis, JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis));
+ }
+
+ if (this.pending_promises_for_abort > 0) {
+ this.server.vm.tick();
+ }
+ }
+ }
+
+ pub fn markComplete(this: *RequestContext) void {
+ if (!this.has_marked_complete) this.server.onRequestComplete();
+ this.has_marked_complete = true;
+ }
+
+ // This function may be called multiple times
+ // so it's important that we can safely do that
+ pub fn finalizeWithoutDeinit(this: *RequestContext) void {
+ this.blob.detach();
+
+ if (comptime Environment.allow_assert) {
+ std.debug.assert(!this.finalized);
+ this.finalized = true;
+ }
+
+ if (!this.response_jsvalue.isEmpty()) {
+ this.server.response_objects_pool.push(this.server.globalThis, this.response_jsvalue);
+ this.response_jsvalue = JSC.JSValue.zero;
+ }
+
+ if (this.request_js_object != null) {
+ var request_js = this.request_js_object.?.value();
+ request_js.ensureStillAlive();
+
+ this.request_js_object = null;
+ defer request_js.ensureStillAlive();
+ defer JSC.C.JSValueUnprotect(this.server.globalThis.ref(), request_js.asObjectRef());
+ // User called .blob(), .json(), text(), or .arrayBuffer() on the Request object
+ // but we received nothing or the connection was aborted
+ if (request_js.as(Request)) |req| {
+ // the promise is pending
+ if (req.body == .Locked and req.body.Locked.action != .none and req.body.Locked.promise != null) {
+ req.body.toErrorInstance(JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis), this.server.globalThis);
+ }
+ req.uws_request = null;
+ }
+ }
+
+ if (this.promise) |promise| {
+ this.promise = null;
+
+ if (promise.asInternalPromise()) |prom| {
+ prom.rejectAsHandled(this.server.globalThis, (JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis)));
+ } else if (promise.asPromise()) |prom| {
+ prom.rejectAsHandled(this.server.globalThis, (JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis)));
+ }
+ JSC.C.JSValueUnprotect(this.server.globalThis.ref(), promise.asObjectRef());
+ }
+
+ if (this.response_headers != null) {
+ this.response_headers.?.deref();
+ this.response_headers = null;
+ }
+ }
+ pub fn finalize(this: *RequestContext) void {
+ this.finalizeWithoutDeinit();
+ this.markComplete();
+ this.deinit();
+ }
+
+ pub fn deinit(this: *RequestContext) void {
+ if (comptime Environment.allow_assert)
+ std.debug.assert(this.finalized);
+
+ if (comptime Environment.allow_assert)
+ std.debug.assert(this.has_marked_complete);
+
+ var server = this.server;
+ this.request_body_buf.clearAndFree(this.allocator);
+ this.response_buf_owned.clearAndFree(this.allocator);
+
+ server.request_pool_allocator.destroy(this);
+ }
+
+ fn writeHeaders(
+ this: *RequestContext,
+ headers: *JSC.FetchHeaders,
+ ) void {
+ headers.remove(&ZigString.init("content-length"));
+ headers.remove(&ZigString.init("transfer-encoding"));
+ if (!ssl_enabled) headers.remove(&ZigString.init("strict-transport-security"));
+ headers.toUWSResponse(ssl_enabled, this.resp);
+ }
+
+ pub fn writeStatus(this: *RequestContext, status: u16) void {
+ var status_text_buf: [48]u8 = undefined;
+
+ if (status == 302) {
+ this.resp.writeStatus("302 Found");
+ } else {
+ this.resp.writeStatus(std.fmt.bufPrint(&status_text_buf, "{d} HM", .{status}) catch unreachable);
+ }
+ }
+
+ fn cleanupAndFinalizeAfterSendfile(this: *RequestContext) void {
+ this.resp.setWriteOffset(this.sendfile.offset);
+ this.resp.endWithoutBody();
+ // use node syscall so that we don't segfault on BADF
+ if (this.sendfile.auto_close)
+ _ = JSC.Node.Syscall.close(this.sendfile.fd);
+ this.sendfile = undefined;
+ this.finalize();
+ }
+ const separator: string = "\r\n";
+ const separator_iovec = [1]std.os.iovec_const{.{
+ .iov_base = separator.ptr,
+ .iov_len = separator.len,
+ }};
+
+ pub fn onSendfile(this: *RequestContext) bool {
+ if (this.aborted) {
+ this.cleanupAndFinalizeAfterSendfile();
+ return false;
+ }
+
+ const adjusted_count_temporary = @minimum(@as(u64, this.sendfile.remain), @as(u63, std.math.maxInt(u63)));
+ // TODO we should not need this int cast; improve the return type of `@minimum`
+ const adjusted_count = @intCast(u63, adjusted_count_temporary);
+
+ if (Environment.isLinux) {
+ var signed_offset = @intCast(i64, this.sendfile.offset);
+ const start = this.sendfile.offset;
+ const val =
+ // this does the syscall directly, without libc
+ linux.sendfile(this.sendfile.socket_fd, this.sendfile.fd, &signed_offset, this.sendfile.remain);
+ this.sendfile.offset = @intCast(Blob.SizeType, signed_offset);
+
+ const errcode = linux.getErrno(val);
+
+ this.sendfile.remain -= @intCast(Blob.SizeType, this.sendfile.offset - start);
+
+ if (errcode != .SUCCESS or this.aborted or this.sendfile.remain == 0 or val == 0) {
+ if (errcode != .AGAIN and errcode != .SUCCESS and errcode != .PIPE) {
+ Output.prettyErrorln("Error: {s}", .{@tagName(errcode)});
+ Output.flush();
+ }
+ this.cleanupAndFinalizeAfterSendfile();
+ return errcode != .SUCCESS;
+ }
+ } else {
+ var sbytes: std.os.off_t = adjusted_count;
+ const signed_offset = @bitCast(i64, @as(u64, this.sendfile.offset));
+
+ const errcode = std.c.getErrno(std.c.sendfile(
+ this.sendfile.fd,
+ this.sendfile.socket_fd,
+
+ signed_offset,
+ &sbytes,
+ null,
+ 0,
+ ));
+ const wrote = @intCast(Blob.SizeType, sbytes);
+ this.sendfile.offset += wrote;
+ this.sendfile.remain -= wrote;
+ if (errcode != .AGAIN or this.aborted or this.sendfile.remain == 0 or sbytes == 0) {
+ if (errcode != .AGAIN and errcode != .SUCCESS and errcode != .PIPE) {
+ Output.prettyErrorln("Error: {s}", .{@tagName(errcode)});
+ Output.flush();
+ }
+ this.cleanupAndFinalizeAfterSendfile();
+ return errcode == .SUCCESS;
+ }
+ }
+
+ if (!this.sendfile.has_set_on_writable) {
+ this.sendfile.has_set_on_writable = true;
+ this.resp.onWritable(*RequestContext, onWritableSendfile, this);
+ }
+
+ this.setAbortHandler();
+ this.resp.markNeedsMore();
+
+ return true;
+ }
+
+ pub fn onWritableBytes(this: *RequestContext, write_offset: c_ulong, resp: *App.Response) callconv(.C) bool {
+ std.debug.assert(this.resp == resp);
+ if (this.aborted) {
+ this.finalizeForAbort();
+ return false;
+ }
+
+ var bytes = this.blob.sharedView();
+ return this.sendWritableBytes(bytes, write_offset, resp);
+ }
+
+ pub fn sendWritableBytes(this: *RequestContext, bytes_: []const u8, write_offset: c_ulong, resp: *App.Response) bool {
+ std.debug.assert(this.resp == resp);
+
+ var bytes = bytes_[@minimum(bytes_.len, @truncate(usize, write_offset))..];
+ if (resp.tryEnd(bytes, bytes_.len)) {
+ this.finalize();
+ return true;
+ } else {
+ this.resp.onWritable(*RequestContext, onWritableBytes, this);
+ return true;
+ }
+ }
+
+ pub fn onWritableSendfile(this: *RequestContext, _: c_ulong, _: *App.Response) callconv(.C) bool {
+ return this.onSendfile();
+ }
+
+ // We tried open() in another thread for this
+ // it was not faster due to the mountain of syscalls
+ pub fn renderSendFile(this: *RequestContext, blob: JSC.WebCore.Blob) void {
+ this.blob = blob;
+ const file = &this.blob.store.?.data.file;
+ var file_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
+ const auto_close = file.pathlike != .fd;
+ const fd = if (!auto_close)
+ file.pathlike.fd
+ else switch (JSC.Node.Syscall.open(file.pathlike.path.sliceZ(&file_buf), std.os.O.RDONLY | std.os.O.NONBLOCK | std.os.O.CLOEXEC, 0)) {
+ .result => |_fd| _fd,
+ .err => |err| return this.runErrorHandler(err.withPath(file.pathlike.path.slice()).toSystemError().toErrorInstance(
+ this.server.globalThis,
+ )),
+ };
+
+ // stat only blocks if the target is a file descriptor
+ const stat: std.os.Stat = switch (JSC.Node.Syscall.fstat(fd)) {
+ .result => |result| result,
+ .err => |err| {
+ this.runErrorHandler(err.withPath(file.pathlike.path.slice()).toSystemError().toErrorInstance(
+ this.server.globalThis,
+ ));
+ if (auto_close) {
+ _ = JSC.Node.Syscall.close(fd);
+ }
+ return;
+ },
+ };
+
+ if (Environment.isMac) {
+ if (!std.os.S.ISREG(stat.mode)) {
+ if (auto_close) {
+ _ = JSC.Node.Syscall.close(fd);
+ }
+
+ var err = JSC.Node.Syscall.Error{
+ .errno = @intCast(JSC.Node.Syscall.Error.Int, @enumToInt(std.os.E.INVAL)),
+ .path = file.pathlike.path.slice(),
+ .syscall = .sendfile,
+ };
+ var sys = err.toSystemError();
+ sys.message = ZigString.init("MacOS does not support sending non-regular files");
+ this.runErrorHandler(sys.toErrorInstance(
+ this.server.globalThis,
+ ));
+ return;
+ }
+ }
+
+ if (Environment.isLinux) {
+ if (!(std.os.S.ISREG(stat.mode) or std.os.S.ISFIFO(stat.mode))) {
+ if (auto_close) {
+ _ = JSC.Node.Syscall.close(fd);
+ }
+
+ var err = JSC.Node.Syscall.Error{
+ .errno = @intCast(JSC.Node.Syscall.Error.Int, @enumToInt(std.os.E.INVAL)),
+ .path = file.pathlike.path.slice(),
+ .syscall = .sendfile,
+ };
+ var sys = err.toSystemError();
+ sys.message = ZigString.init("File must be regular or FIFO");
+ this.runErrorHandler(sys.toErrorInstance(
+ this.server.globalThis,
+ ));
+ return;
+ }
+ }
+
+ this.blob.size = @intCast(Blob.SizeType, stat.size);
+ this.needs_content_length = true;
+
+ this.sendfile = .{
+ .fd = fd,
+ .remain = this.blob.size,
+ .auto_close = auto_close,
+ .socket_fd = if (!this.aborted) this.resp.getNativeHandle() else -999,
+ };
+
+ this.resp.runCorked(*RequestContext, renderMetadataAndNewline, this);
+
+ if (this.blob.size == 0) {
+ this.cleanupAndFinalizeAfterSendfile();
+ return;
+ }
+
+ _ = this.onSendfile();
+ }
+
+ pub fn renderMetadataAndNewline(this: *RequestContext) void {
+ this.renderMetadata();
+ this.resp.prepareForSendfile();
+ }
+
+ pub fn doSendfile(this: *RequestContext, blob: Blob) void {
+ if (this.aborted) {
+ this.finalizeForAbort();
+ return;
+ }
+
+ if (this.has_sendfile_ctx) return;
+
+ this.has_sendfile_ctx = true;
+
+ if (comptime can_sendfile) {
+ return this.renderSendFile(blob);
+ }
+
+ this.setAbortHandler();
+ this.blob.doReadFileInternal(*RequestContext, this, onReadFile, this.server.globalThis);
+ }
+
+ pub fn onReadFile(this: *RequestContext, result: Blob.Store.ReadFile.ResultType) void {
+ if (this.aborted) {
+ this.finalizeForAbort();
+ return;
+ }
+
+ if (result == .err) {
+ this.runErrorHandler(result.err.toErrorInstance(this.server.globalThis));
+ return;
+ }
+
+ const is_temporary = result.result.is_temporary;
+ if (!is_temporary) {
+ this.blob.resolveSize();
+ this.doRenderBlob();
+ } else {
+ this.blob.size = @truncate(Blob.SizeType, result.result.buf.len);
+ this.response_buf_owned = .{ .items = result.result.buf, .capacity = result.result.buf.len };
+ this.renderResponseBuffer();
+ }
+ }
+
+ pub fn doRenderWithBodyLocked(this: *anyopaque, value: *JSC.WebCore.Body.Value) void {
+ doRenderWithBody(bun.cast(*RequestContext, this), value);
+ }
+
+ pub fn doRenderWithBody(this: *RequestContext, value: *JSC.WebCore.Body.Value) void {
+ switch (value.*) {
+ .Error => {
+ const err = value.Error;
+ _ = value.use();
+ if (this.aborted) {
+ this.finalizeForAbort();
+ return;
+ }
+ this.runErrorHandler(err);
+ return;
+ },
+ .Blob => {
+ this.blob = value.use();
+
+ if (this.aborted) {
+ this.finalizeForAbort();
+ return;
+ }
+
+ if (this.blob.needsToReadFile()) {
+ this.req.setYield(false);
+ if (!this.has_sendfile_ctx)
+ this.doSendfile(this.blob);
+ return;
+ }
+ },
+ // TODO: this needs to support streaming!
+ .Locked => |*lock| {
+ lock.callback = doRenderWithBodyLocked;
+ lock.task = this;
+ return;
+ },
+ else => {},
+ }
+
+ this.doRenderBlob();
+ }
+
+ pub fn doRenderBlob(this: *RequestContext) void {
+ if (this.has_abort_handler)
+ this.resp.runCorked(*RequestContext, renderMetadata, this)
+ else
+ this.renderMetadata();
+
+ this.renderBytes();
+ }
+
+ pub fn doRender(this: *RequestContext) void {
+ if (this.aborted) {
+ this.finalizeForAbort();
+ return;
+ }
+ var response = this.response_ptr.?;
+ this.doRenderWithBody(&response.body.value);
+ }
+
+ pub fn renderProductionError(this: *RequestContext, status: u16) void {
+ switch (status) {
+ 404 => {
+ this.resp.writeStatus("404 Not Found");
+ this.resp.endWithoutBody();
+ },
+ else => {
+ this.resp.writeStatus("500 Internal Server Error");
+ this.resp.writeHeader("content-type", "text/plain");
+ this.resp.end("Something went wrong!", true);
+ },
+ }
+
+ this.finalize();
+ }
+
+ pub fn runErrorHandler(
+ this: *RequestContext,
+ value: JSC.JSValue,
+ ) void {
+ runErrorHandlerWithStatusCode(this, value, 500);
+ }
+
+ pub fn runErrorHandlerWithStatusCode(
+ this: *RequestContext,
+ value: JSC.JSValue,
+ status: u16,
+ ) void {
+ JSC.markBinding();
+ if (this.resp.hasResponded()) return;
+
+ var exception_list: std.ArrayList(Api.JsException) = std.ArrayList(Api.JsException).init(this.allocator);
+ defer exception_list.deinit();
+ if (!this.server.config.onError.isEmpty() and !this.has_called_error_handler) {
+ this.has_called_error_handler = true;
+ var args = [_]JSC.C.JSValueRef{value.asObjectRef()};
+ const result = JSC.C.JSObjectCallAsFunctionReturnValue(this.server.globalThis.ref(), this.server.config.onError.asObjectRef(), this.server.thisObject.asObjectRef(), 1, &args);
+
+ if (!result.isEmptyOrUndefinedOrNull()) {
+ if (result.isError() or result.isAggregateError(this.server.globalThis)) {
+ this.runErrorHandler(result);
+ return;
+ } else if (result.as(Response)) |response| {
+ this.render(response);
+ return;
+ }
+ }
+ }
+
+ if (comptime debug_mode) {
+ JSC.VirtualMachine.vm.defaultErrorHandler(value, &exception_list);
+
+ this.renderDefaultError(
+ JSC.VirtualMachine.vm.log,
+ error.ExceptionOcurred,
+ exception_list.toOwnedSlice(),
+ "<r><red>{s}<r> - <b>{s}<r> failed",
+ .{ std.mem.span(@tagName(this.method)), this.url },
+ );
+ } else {
+ if (status != 404)
+ JSC.VirtualMachine.vm.defaultErrorHandler(value, &exception_list);
+ this.renderProductionError(status);
+ }
+ JSC.VirtualMachine.vm.log.reset();
+ return;
+ }
+
+ pub fn renderMetadata(this: *RequestContext) void {
+ var response: *JSC.WebCore.Response = this.response_ptr.?;
+ var status = response.statusCode();
+ const size = this.blob.size;
+ status = if (status == 200 and size == 0)
+ 204
+ else
+ status;
+
+ this.writeStatus(status);
+ var needs_content_type = true;
+ const content_type: MimeType = brk: {
+ if (response.body.init.headers) |headers_| {
+ if (headers_.get("content-type")) |content| {
+ needs_content_type = false;
+ break :brk MimeType.init(content);
+ }
+ }
+ break :brk if (this.blob.content_type.len > 0)
+ MimeType.init(this.blob.content_type)
+ else if (MimeType.sniff(this.blob.sharedView())) |content|
+ content
+ else if (this.blob.is_all_ascii orelse false)
+ MimeType.text
+ else
+ MimeType.other;
+ };
+
+ var has_content_disposition = false;
+
+ if (response.body.init.headers) |headers_| {
+ this.writeHeaders(headers_);
+ has_content_disposition = headers_.has(&ZigString.init("content-disposition"));
+ response.body.init.headers = null;
+ headers_.deref();
+ }
+
+ if (needs_content_type) {
+ this.resp.writeHeader("content-type", content_type.value);
+ }
+
+ // automatically include the filename when:
+ // 1. Bun.file("foo")
+ // 2. The content-disposition header is not present
+ if (!has_content_disposition and content_type.category.autosetFilename()) {
+ if (this.blob.store) |store| {
+ if (store.data == .file) {
+ if (store.data.file.pathlike == .path) {
+ const basename = std.fs.path.basename(store.data.file.pathlike.path.slice());
+ if (basename.len > 0) {
+ var filename_buf: [1024]u8 = undefined;
+
+ this.resp.writeHeader(
+ "content-disposition",
+ std.fmt.bufPrint(&filename_buf, "filename=\"{s}\"", .{basename[0..@minimum(basename.len, 1024 - 32)]}) catch "",
+ );
+ }
+ }
+ }
+ }
+ }
+
+ if (this.needs_content_length) {
+ this.resp.writeHeaderInt("content-length", size);
+ this.needs_content_length = false;
+ }
+ }
+
+ pub fn renderBytes(this: *RequestContext) void {
+ const bytes = this.blob.sharedView();
+
+ if (!this.resp.tryEnd(
+ bytes,
+ bytes.len,
+ )) {
+ this.resp.onWritable(*RequestContext, onWritableBytes, this);
+ return;
+ }
+
+ this.finalize();
+ }
+
+ pub fn render(this: *RequestContext, response: *JSC.WebCore.Response) void {
+ this.response_ptr = response;
+
+ this.doRender();
+ }
+
+ pub fn resolveRequestBody(this: *RequestContext) void {
+ if (this.aborted) {
+ this.finalizeForAbort();
+ return;
+ }
+
+ if (JSC.JSValue.fromRef(this.request_js_object).as(Request)) |req| {
+ var bytes = this.request_body_buf.toOwnedSlice(this.allocator);
+ var old = req.body;
+ req.body = .{
+ .Blob = if (bytes.len > 0)
+ Blob.init(bytes, this.allocator, this.server.globalThis)
+ else
+ Blob.initEmpty(this.server.globalThis),
+ };
+ old.resolve(&req.body, this.server.globalThis);
+ VirtualMachine.vm.tick();
+ return;
+ }
+ }
+
+ pub fn onBodyChunk(this: *RequestContext, resp: *App.Response, chunk: []const u8, last: bool) void {
+ std.debug.assert(this.resp == resp);
+
+ if (this.aborted) return;
+ this.request_body_buf.appendSlice(this.allocator, chunk) catch @panic("Out of memory while allocating request body");
+ if (last) {
+ if (JSC.JSValue.fromRef(this.request_js_object).as(Request) != null) {
+ uws.Loop.get().?.nextTick(*RequestContext, this, resolveRequestBody);
+ } else {
+ this.request_body_buf.deinit(this.allocator);
+ this.request_body_buf = .{};
+ }
+ }
+ }
+
+ pub fn onPull(this: *RequestContext) void {
+ if (this.req.header("content-length")) |content_length| {
+ const len = std.fmt.parseInt(usize, content_length, 10) catch 0;
+ if (len == 0) {
+ if (JSC.JSValue.fromRef(this.request_js_object).as(Request)) |req| {
+ var old = req.body;
+ old.Locked.callback = null;
+ req.body = .{ .Empty = .{} };
+ old.resolve(&req.body, this.server.globalThis);
+ VirtualMachine.vm.tick();
+ return;
+ }
+ }
+
+ if (len >= this.server.config.max_request_body_size) {
+ if (JSC.JSValue.fromRef(this.request_js_object).as(Request)) |req| {
+ var old = req.body;
+ old.Locked.callback = null;
+ req.body = .{ .Empty = .{} };
+ old.toError(error.RequestBodyTooLarge, this.server.globalThis);
+ VirtualMachine.vm.tick();
+ return;
+ }
+
+ this.resp.writeStatus("413 Request Entity Too Large");
+ this.resp.endWithoutBody();
+ this.finalize();
+ return;
+ }
+
+ this.request_body_buf.ensureTotalCapacityPrecise(this.allocator, len) catch @panic("Out of memory while allocating request body buffer");
+ }
+ this.setAbortHandler();
+
+ this.resp.onData(*RequestContext, onBodyChunk, this);
+ }
+
+ pub fn onPullCallback(this: *anyopaque) void {
+ onPull(bun.cast(*RequestContext, this));
+ }
+
+ comptime {
+ if (!JSC.is_bindgen) {
+ @export(PromiseHandler.resolve, .{
+ .name = Export[0].symbol_name,
+ });
+ @export(PromiseHandler.reject, .{
+ .name = Export[1].symbol_name,
+ });
+ }
+ }
+ };
+}
+
+pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
+ return struct {
+ pub const ssl_enabled = ssl_enabled_;
+ const debug_mode = debug_mode_;
+
+ const ThisServer = @This();
+ pub const RequestContext = NewRequestContext(ssl_enabled, debug_mode, @This());
+
+ pub const App = uws.NewApp(ssl_enabled);
+
+ listener: ?*App.ListenSocket = null,
+ thisObject: JSC.JSValue = JSC.JSValue.zero,
+ app: *App = undefined,
+ vm: *JSC.VirtualMachine = undefined,
+ globalThis: *JSGlobalObject,
+ base_url_string_for_joining: string = "",
+ response_objects_pool: JSC.WebCore.Response.Pool = JSC.WebCore.Response.Pool{},
+ config: ServerConfig = ServerConfig{},
+ pending_requests: usize = 0,
+ request_pool_allocator: std.mem.Allocator = undefined,
+ has_js_deinited: bool = false,
+ listen_callback: JSC.AnyTask = undefined,
+ allocator: std.mem.Allocator,
+
+ pub const Class = JSC.NewClass(
+ ThisServer,
+ .{ .name = "Server" },
+ .{
+ .stop = .{
+ .rfn = JSC.wrapSync(ThisServer, "stopFromJS"),
+ },
+ .finalize = .{
+ .rfn = finalize,
+ },
+ },
+ .{
+ .port = .{
+ .get = JSC.getterWrap(ThisServer, "getPort"),
+ },
+ .hostname = .{
+ .get = JSC.getterWrap(ThisServer, "getHostname"),
+ },
+ .development = .{
+ .get = JSC.getterWrap(ThisServer, "getDevelopment"),
+ },
+ .pendingRequests = .{
+ .get = JSC.getterWrap(ThisServer, "getPendingRequests"),
+ },
+ },
+ );
+
+ pub fn stopFromJS(this: *ThisServer) JSC.JSValue {
+ if (this.listener != null) {
+ JSC.C.JSValueUnprotect(this.globalThis.ref(), this.thisObject.asObjectRef());
+ this.thisObject = JSC.JSValue.jsUndefined();
+ this.stop();
+ }
+
+ return JSC.JSValue.jsUndefined();
+ }
+
+ pub fn getPort(this: *ThisServer) JSC.JSValue {
+ return JSC.JSValue.jsNumber(this.config.port);
+ }
+
+ pub fn getPendingRequests(this: *ThisServer) JSC.JSValue {
+ return JSC.JSValue.jsNumber(@intCast(i32, @truncate(u31, this.pending_requests)));
+ }
+
+ pub fn getHostname(this: *ThisServer, globalThis: *JSGlobalObject) JSC.JSValue {
+ return ZigString.init(this.config.base_uri).toValue(globalThis);
+ }
+
+ pub fn getDevelopment(
+ _: *ThisServer,
+ ) JSC.JSValue {
+ return JSC.JSValue.jsBoolean(debug_mode);
+ }
+
+ pub fn onRequestComplete(this: *ThisServer) void {
+ this.pending_requests -= 1;
+ this.deinitIfWeCan();
+ }
+
+ pub fn finalize(this: *ThisServer) void {
+ this.has_js_deinited = true;
+ this.deinitIfWeCan();
+ }
+
+ pub fn deinitIfWeCan(this: *ThisServer) void {
+ if (this.pending_requests == 0 and this.listener == null and this.has_js_deinited)
+ this.deinit();
+ }
+
+ pub fn stop(this: *ThisServer) void {
+ if (this.listener) |listener| {
+ listener.close();
+ this.listener = null;
+ this.vm.disable_run_us_loop = false;
+ }
+
+ this.deinitIfWeCan();
+ }
+
+ pub fn deinit(this: *ThisServer) void {
+ if (this.vm.response_objects_pool) |pool| {
+ if (pool == &this.response_objects_pool) {
+ this.vm.response_objects_pool = null;
+ }
+ }
+
+ this.app.destroy();
+ const allocator = this.allocator;
+ allocator.destroy(this);
+ }
+
+ pub fn init(config: ServerConfig, globalThis: *JSGlobalObject) *ThisServer {
+ var server = bun.default_allocator.create(ThisServer) catch @panic("Out of memory!");
+ server.* = .{
+ .globalThis = globalThis,
+ .config = config,
+ .base_url_string_for_joining = strings.trim(config.base_url.href, "/"),
+ .vm = JSC.VirtualMachine.vm,
+ .allocator = Arena.getThreadlocalDefault(),
+ };
+ if (RequestContext.pool == null) {
+ RequestContext.pool = server.allocator.create(RequestContext.RequestContextStackAllocator) catch @panic("Out of memory!");
+ RequestContext.pool.?.* = .{
+ .fallback_allocator = server.allocator,
+ };
+ server.request_pool_allocator = RequestContext.pool.?.get();
+ RequestContext.pool_allocator = server.request_pool_allocator;
+ } else {
+ server.request_pool_allocator = RequestContext.pool_allocator;
+ }
+
+ return server;
+ }
+
+ noinline fn onListenFailed(this: *ThisServer) void {
+ var zig_str: ZigString = ZigString.init("Failed to start server");
+ if (comptime ssl_enabled) {
+ var output_buf: [4096]u8 = undefined;
+ output_buf[0] = 0;
+ var written: usize = 0;
+ var ssl_error = BoringSSL.ERR_get_error();
+ while (ssl_error != 0 and written < output_buf.len) : (ssl_error = BoringSSL.ERR_get_error()) {
+ if (written > 0) {
+ output_buf[written] = '\n';
+ written += 1;
+ }
+
+ if (BoringSSL.ERR_reason_error_string(
+ ssl_error,
+ )) |reason_ptr| {
+ const reason = std.mem.span(reason_ptr);
+ if (reason.len == 0) {
+ break;
+ }
+ @memcpy(output_buf[written..].ptr, reason.ptr, reason.len);
+ written += reason.len;
+ }
+
+ if (BoringSSL.ERR_func_error_string(
+ ssl_error,
+ )) |reason_ptr| {
+ const reason = std.mem.span(reason_ptr);
+ if (reason.len > 0) {
+ output_buf[written..][0.." via ".len].* = " via ".*;
+ written += " via ".len;
+ @memcpy(output_buf[written..].ptr, reason.ptr, reason.len);
+ written += reason.len;
+ }
+ }
+
+ if (BoringSSL.ERR_lib_error_string(
+ ssl_error,
+ )) |reason_ptr| {
+ const reason = std.mem.span(reason_ptr);
+ if (reason.len > 0) {
+ output_buf[written..][0] = ' ';
+ written += 1;
+ @memcpy(output_buf[written..].ptr, reason.ptr, reason.len);
+ written += reason.len;
+ }
+ }
+ }
+
+ if (written > 0) {
+ var message = output_buf[0..written];
+ zig_str = ZigString.init(std.fmt.allocPrint(bun.default_allocator, "OpenSSL {s}", .{message}) catch unreachable);
+ zig_str.withEncoding().mark();
+ }
+ }
+ // store the exception in here
+ this.thisObject = zig_str.toErrorInstance(this.globalThis);
+ return;
+ }
+
+ pub fn onListen(this: *ThisServer, socket: ?*App.ListenSocket, _: uws.uws_app_listen_config_t) void {
+ if (socket == null) {
+ return this.onListenFailed();
+ }
+
+ this.listener = socket;
+ const needs_post_handler = this.vm.uws_event_loop == null;
+ this.vm.uws_event_loop = uws.Loop.get();
+ this.vm.response_objects_pool = &this.response_objects_pool;
+ this.listen_callback = JSC.AnyTask.New(ThisServer, run).init(this);
+ this.vm.eventLoop().enqueueTask(JSC.Task.init(&this.listen_callback));
+ if (needs_post_handler) {
+ _ = this.vm.uws_event_loop.?.addPostHandler(*JSC.EventLoop, this.vm.eventLoop(), JSC.EventLoop.tick);
+ }
+ }
+
+ pub fn run(this: *ThisServer) void {
+ // this.app.addServerName(hostname_pattern: [*:0]const u8)
+
+ // we do not increment the reference count here
+ // uWS manages running the loop, so it is unnecessary
+ // this.vm.us_loop_reference_count +|= 1;
+ this.vm.disable_run_us_loop = true;
+
+ this.app.run();
+ }
+
+ pub fn onBunInfoRequest(this: *ThisServer, req: *uws.Request, resp: *App.Response) void {
+ JSC.markBinding();
+ this.pending_requests += 1;
+ defer this.pending_requests -= 1;
+ req.setYield(false);
+ var stack_fallback = std.heap.stackFallback(8096, this.allocator);
+ var allocator = stack_fallback.get();
+
+ var buffer_writer = js_printer.BufferWriter.init(allocator) catch unreachable;
+ var writer = js_printer.BufferPrinter.init(buffer_writer);
+ defer writer.ctx.buffer.deinit();
+ var source = logger.Source.initEmptyFile("info.json");
+ _ = js_printer.printJSON(
+ *js_printer.BufferPrinter,
+ &writer,
+ bun.Global.BunInfo.generate(*Bundler, &JSC.VirtualMachine.vm.bundler, allocator) catch unreachable,
+ &source,
+ ) catch unreachable;
+
+ resp.writeStatus("200 OK");
+ resp.writeHeader("Content-Type", MimeType.json.value);
+ resp.writeHeader("Cache-Control", "public, max-age=3600");
+ resp.writeHeaderInt("Age", 0);
+ const buffer = writer.ctx.written;
+ resp.end(buffer, false);
+ }
+
+ pub fn onSrcRequest(this: *ThisServer, req: *uws.Request, resp: *App.Response) void {
+ JSC.markBinding();
+ this.pending_requests += 1;
+ defer this.pending_requests -= 1;
+ req.setYield(false);
+ if (req.header("open-in-editor") == null) {
+ resp.writeStatus("501 Not Implemented");
+ resp.end("Viewing source without opening in editor is not implemented yet!", false);
+ return;
+ }
+
+ var ctx = &JSC.VirtualMachine.vm.rareData().editor_context;
+ ctx.autoDetectEditor(JSC.VirtualMachine.vm.bundler.env);
+ var line: ?string = req.header("editor-line");
+ var column: ?string = req.header("editor-column");
+
+ if (ctx.editor) |editor| {
+ resp.writeStatus("200 Opened");
+ resp.end("Opened in editor", false);
+ var url = req.url()["/src:".len..];
+ if (strings.indexOfChar(url, ':')) |colon| {
+ url = url[0..colon];
+ }
+ editor.open(ctx.path, url, line, column, this.allocator) catch Output.prettyErrorln("Failed to open editor", .{});
+ } else {
+ resp.writeStatus("500 Missing Editor :(");
+ resp.end("Please set your editor in bunfig.toml", false);
+ }
+ }
+
+ pub fn onRequest(this: *ThisServer, req: *uws.Request, resp: *App.Response) void {
+ JSC.markBinding();
+ this.pending_requests += 1;
+ var vm = this.vm;
+ req.setYield(false);
+ var ctx = this.request_pool_allocator.create(RequestContext) catch @panic("ran out of memory");
+ ctx.create(this, req, resp);
+
+ var request_object = this.allocator.create(JSC.WebCore.Request) catch unreachable;
+ request_object.* = .{
+ .url = JSC.ZigString.init(ctx.url),
+ .method = ctx.method,
+ .uws_request = req,
+ .body = .{
+ .Locked = .{
+ .task = ctx,
+ .global = this.globalThis,
+ .onPull = RequestContext.onPullCallback,
+ },
+ },
+ };
+ request_object.url.mark();
+ // We keep the Request object alive for the duration of the request so that we can remove the pointer to the UWS request object.
+ var args = [_]JSC.C.JSValueRef{JSC.WebCore.Request.Class.make(this.globalThis.ref(), request_object)};
+ ctx.request_js_object = args[0];
+ JSC.C.JSValueProtect(this.globalThis.ref(), args[0]);
+ const response_value = JSC.C.JSObjectCallAsFunctionReturnValue(this.globalThis.ref(), this.config.onRequest.asObjectRef(), this.thisObject.asObjectRef(), 1, &args);
+
+ if (ctx.aborted) {
+ ctx.finalizeForAbort();
+ return;
+ }
+ if (response_value.isEmptyOrUndefinedOrNull() and !ctx.resp.hasResponded()) {
+ ctx.renderMissing();
+ return;
+ }
+
+ if (response_value.isError() or response_value.isAggregateError(this.globalThis) or response_value.isException(this.globalThis.vm())) {
+ ctx.runErrorHandler(response_value);
+ return;
+ }
+
+ if (response_value.as(JSC.WebCore.Response)) |response| {
+ JSC.C.JSValueProtect(this.globalThis.ref(), response_value.asObjectRef());
+ ctx.response_jsvalue = response_value;
+
+ ctx.render(response);
+ return;
+ }
+
+ var wait_for_promise = false;
+
+ if (response_value.asPromise()) |promise| {
+ // If we immediately have the value available, we can skip the extra event loop tick
+ switch (promise.status(vm.global.vm())) {
+ .Pending => {},
+ .Fulfilled => {
+ ctx.handleResolve(promise.result(vm.global.vm()));
+ return;
+ },
+ .Rejected => {
+ ctx.handleReject(promise.result(vm.global.vm()));
+ return;
+ },
+ }
+ wait_for_promise = true;
+ // I don't think this case should happen
+ // But I'm uncertain
+ } else if (response_value.asInternalPromise()) |promise| {
+ switch (promise.status(vm.global.vm())) {
+ .Pending => {},
+ .Fulfilled => {
+ ctx.handleResolve(promise.result(vm.global.vm()));
+ return;
+ },
+ .Rejected => {
+ ctx.handleReject(promise.result(vm.global.vm()));
+ return;
+ },
+ }
+ wait_for_promise = true;
+ }
+
+ if (wait_for_promise) {
+ ctx.setAbortHandler();
+
+ RequestContext.PromiseHandler.then(ctx, response_value, this.globalThis);
+ return;
+ }
+
+ // The user returned something that wasn't a promise or a promise with a response
+ if (!ctx.resp.hasResponded()) ctx.renderMissing();
+ }
+
+ pub fn listen(this: *ThisServer) void {
+ if (ssl_enabled) {
+ BoringSSL.load();
+ const ssl_config = this.config.ssl_config orelse @panic("Assertion failure: ssl_config");
+ this.app = App.create(.{
+ .key_file_name = ssl_config.key_file_name,
+ .cert_file_name = ssl_config.cert_file_name,
+ .passphrase = ssl_config.passphrase,
+ .dh_params_file_name = ssl_config.dh_params_file_name,
+ .ca_file_name = ssl_config.ca_file_name,
+ .ssl_prefer_low_memory_usage = @as(c_int, @boolToInt(ssl_config.low_memory_mode)),
+ });
+
+ if (ssl_config.server_name != null and std.mem.span(ssl_config.server_name).len > 0) {
+ this.app.addServerName(ssl_config.server_name);
+ }
+ } else {
+ this.app = App.create(.{});
+ }
+
+ this.app.any("/*", *ThisServer, this, onRequest);
+
+ if (comptime debug_mode) {
+ this.app.get("/bun:info", *ThisServer, this, onBunInfoRequest);
+ this.app.get("/src:/*", *ThisServer, this, onSrcRequest);
+ }
+
+ this.app.listenWithConfig(*ThisServer, this, onListen, .{
+ .port = this.config.port,
+ .host = this.config.hostname,
+ .options = 0,
+ });
+ }
+ };
+}
+
+pub const Server = NewServer(false, false);
+pub const SSLServer = NewServer(true, false);
+pub const DebugServer = NewServer(false, true);
+pub const DebugSSLServer = NewServer(true, true);
diff --git a/src/bun.js/api/transpiler.zig b/src/bun.js/api/transpiler.zig
new file mode 100644
index 000000000..6d3f3f6fd
--- /dev/null
+++ b/src/bun.js/api/transpiler.zig
@@ -0,0 +1,1304 @@
+const std = @import("std");
+const Api = @import("../../api/schema.zig").Api;
+const FilesystemRouter = @import("../../router.zig");
+const http = @import("../../http.zig");
+const JavaScript = @import("../javascript.zig");
+const QueryStringMap = @import("../../url.zig").QueryStringMap;
+const CombinedScanner = @import("../../url.zig").CombinedScanner;
+const bun = @import("../../global.zig");
+const string = bun.string;
+const JSC = @import("../../jsc.zig");
+const js = JSC.C;
+const WebCore = @import("../webcore/response.zig");
+const Bundler = @import("../../bundler.zig");
+const options = @import("../../options.zig");
+const VirtualMachine = JavaScript.VirtualMachine;
+const ScriptSrcStream = std.io.FixedBufferStream([]u8);
+const ZigString = JSC.ZigString;
+const Fs = @import("../../fs.zig");
+const Base = @import("../base.zig");
+const getAllocator = Base.getAllocator;
+const JSObject = JSC.JSObject;
+const JSError = Base.JSError;
+const JSValue = JSC.JSValue;
+const JSGlobalObject = JSC.JSGlobalObject;
+const strings = @import("strings");
+const NewClass = Base.NewClass;
+const To = Base.To;
+const Request = WebCore.Request;
+const d = Base.d;
+const FetchEvent = WebCore.FetchEvent;
+const MacroMap = @import("../../resolver/package_json.zig").MacroMap;
+const TSConfigJSON = @import("../../resolver/tsconfig_json.zig").TSConfigJSON;
+const PackageJSON = @import("../../resolver/package_json.zig").PackageJSON;
+const logger = @import("../../logger.zig");
+const Loader = options.Loader;
+const Platform = options.Platform;
+const JSAst = @import("../../js_ast.zig");
+const Transpiler = @This();
+const JSParser = @import("../../js_parser.zig");
+const JSPrinter = @import("../../js_printer.zig");
+const ScanPassResult = JSParser.ScanPassResult;
+const Mimalloc = @import("../../mimalloc_arena.zig");
+const Runtime = @import("../../runtime.zig").Runtime;
+const JSLexer = @import("../../js_lexer.zig");
+const Expr = JSAst.Expr;
+
+bundler: Bundler.Bundler,
+arena: std.heap.ArenaAllocator,
+transpiler_options: TranspilerOptions,
+scan_pass_result: ScanPassResult,
+buffer_writer: ?JSPrinter.BufferWriter = null,
+
+pub const Class = NewClass(
+ Transpiler,
+ .{ .name = "Transpiler" },
+ .{
+ .scanImports = .{
+ .rfn = scanImports,
+ },
+ .scan = .{
+ .rfn = scan,
+ },
+ .transform = .{
+ .rfn = transform,
+ },
+ .transformSync = .{
+ .rfn = transformSync,
+ },
+ // .resolve = .{
+ // .rfn = resolve,
+ // },
+ // .buildSync = .{
+ // .rfn = buildSync,
+ // },
+ .finalize = finalize,
+ },
+ .{},
+);
+
+pub const Constructor = JSC.NewConstructor(
+ @This(),
+ .{
+ .constructor = .{ .rfn = constructor },
+ },
+ .{},
+);
+
+const default_transform_options: Api.TransformOptions = brk: {
+ var opts = std.mem.zeroes(Api.TransformOptions);
+ opts.disable_hmr = true;
+ opts.platform = Api.Platform.browser;
+ opts.serve = false;
+ break :brk opts;
+};
+
+const TranspilerOptions = struct {
+ transform: Api.TransformOptions = default_transform_options,
+ default_loader: options.Loader = options.Loader.jsx,
+ macro_map: MacroMap = MacroMap{},
+ tsconfig: ?*TSConfigJSON = null,
+ tsconfig_buf: []const u8 = "",
+ macros_buf: []const u8 = "",
+ log: logger.Log,
+ runtime: Runtime.Features = Runtime.Features{ .top_level_await = true },
+ tree_shaking: bool = false,
+ trim_unused_imports: ?bool = null,
+};
+
+// Mimalloc gets unstable if we try to move this to a different thread
+// threadlocal var transform_buffer: bun.MutableString = undefined;
+// threadlocal var transform_buffer_loaded: bool = false;
+
+// This is going to be hard to not leak
+pub const TransformTask = struct {
+ input_code: ZigString = ZigString.init(""),
+ protected_input_value: JSC.JSValue = @intToEnum(JSC.JSValue, 0),
+ output_code: ZigString = ZigString.init(""),
+ bundler: Bundler.Bundler = undefined,
+ log: logger.Log,
+ err: ?anyerror = null,
+ macro_map: MacroMap = MacroMap{},
+ tsconfig: ?*TSConfigJSON = null,
+ loader: Loader,
+ global: *JSGlobalObject,
+ replace_exports: Runtime.Features.ReplaceableExport.Map = .{},
+
+ pub const AsyncTransformTask = JSC.ConcurrentPromiseTask(TransformTask);
+ pub const AsyncTransformEventLoopTask = AsyncTransformTask.EventLoopTask;
+
+ pub fn create(transpiler: *Transpiler, protected_input_value: JSC.C.JSValueRef, globalThis: *JSGlobalObject, input_code: ZigString, loader: Loader) !*AsyncTransformTask {
+ var transform_task = try bun.default_allocator.create(TransformTask);
+ transform_task.* = .{
+ .input_code = input_code,
+ .protected_input_value = if (protected_input_value != null) JSC.JSValue.fromRef(protected_input_value) else @intToEnum(JSC.JSValue, 0),
+ .bundler = undefined,
+ .global = globalThis,
+ .macro_map = transpiler.transpiler_options.macro_map,
+ .tsconfig = transpiler.transpiler_options.tsconfig,
+ .log = logger.Log.init(bun.default_allocator),
+ .loader = loader,
+ .replace_exports = transpiler.transpiler_options.runtime.replace_exports,
+ };
+ transform_task.bundler = transpiler.bundler;
+ transform_task.bundler.linker.resolver = &transform_task.bundler.resolver;
+
+ transform_task.bundler.setLog(&transform_task.log);
+ transform_task.bundler.setAllocator(bun.default_allocator);
+ return try AsyncTransformTask.createOnJSThread(bun.default_allocator, globalThis, transform_task);
+ }
+
+ pub fn run(this: *TransformTask) void {
+ const name = this.loader.stdinName();
+ const source = logger.Source.initPathString(name, this.input_code.slice());
+
+ JSAst.Stmt.Data.Store.create(bun.default_allocator);
+ JSAst.Expr.Data.Store.create(bun.default_allocator);
+
+ var arena = Mimalloc.Arena.init() catch unreachable;
+
+ const allocator = arena.allocator();
+
+ defer {
+ JSAst.Stmt.Data.Store.reset();
+ JSAst.Expr.Data.Store.reset();
+ arena.deinit();
+ }
+
+ this.bundler.setAllocator(allocator);
+ const jsx = if (this.tsconfig != null)
+ this.tsconfig.?.mergeJSX(this.bundler.options.jsx)
+ else
+ this.bundler.options.jsx;
+
+ const parse_options = Bundler.Bundler.ParseOptions{
+ .allocator = allocator,
+ .macro_remappings = this.macro_map,
+ .dirname_fd = 0,
+ .file_descriptor = null,
+ .loader = this.loader,
+ .jsx = jsx,
+ .path = source.path,
+ .virtual_source = &source,
+ .replace_exports = this.replace_exports,
+ // .allocator = this.
+ };
+
+ const parse_result = this.bundler.parse(parse_options, null) orelse {
+ this.err = error.ParseError;
+ return;
+ };
+
+ if (parse_result.empty) {
+ this.output_code = ZigString.init("");
+ return;
+ }
+
+ var global_allocator = arena.backingAllocator();
+ var buffer_writer = JSPrinter.BufferWriter.init(global_allocator) catch |err| {
+ this.err = err;
+ return;
+ };
+ buffer_writer.buffer.list.ensureTotalCapacity(global_allocator, 512) catch unreachable;
+ buffer_writer.reset();
+
+ // defer {
+ // transform_buffer = buffer_writer.buffer;
+ // }
+
+ var printer = JSPrinter.BufferPrinter.init(buffer_writer);
+ const printed = this.bundler.print(parse_result, @TypeOf(&printer), &printer, .esm_ascii) catch |err| {
+ this.err = err;
+ return;
+ };
+
+ if (printed > 0) {
+ buffer_writer = printer.ctx;
+ buffer_writer.buffer.list.items = buffer_writer.written;
+
+ var output = JSC.ZigString.init(buffer_writer.written);
+ output.mark();
+ this.output_code = output;
+ } else {
+ this.output_code = ZigString.init("");
+ }
+ }
+
+ pub fn then(this: *TransformTask, promise: *JSC.JSInternalPromise) void {
+ if (this.log.hasAny() or this.err != null) {
+ const error_value: JSValue = brk: {
+ if (this.err) |err| {
+ if (!this.log.hasAny()) {
+ break :brk JSC.JSValue.fromRef(JSC.BuildError.create(
+ this.global,
+ bun.default_allocator,
+ logger.Msg{
+ .data = logger.Data{ .text = std.mem.span(@errorName(err)) },
+ },
+ ));
+ }
+ }
+
+ break :brk this.log.toJS(this.global, bun.default_allocator, "Transform failed");
+ };
+
+ promise.reject(this.global, error_value);
+ return;
+ }
+
+ finish(this.output_code, this.global, promise);
+
+ if (@enumToInt(this.protected_input_value) != 0) {
+ this.protected_input_value = @intToEnum(JSC.JSValue, 0);
+ }
+ this.deinit();
+ }
+
+ noinline fn finish(code: ZigString, global: *JSGlobalObject, promise: *JSC.JSInternalPromise) void {
+ promise.resolve(global, code.toValueGC(global));
+ }
+
+ pub fn deinit(this: *TransformTask) void {
+ var should_cleanup = false;
+ defer if (should_cleanup) bun.Global.mimalloc_cleanup(false);
+
+ this.log.deinit();
+ if (this.input_code.isGloballyAllocated()) {
+ this.input_code.deinitGlobal();
+ }
+
+ if (this.output_code.isGloballyAllocated()) {
+ should_cleanup = this.output_code.len > 512_000;
+ this.output_code.deinitGlobal();
+ }
+
+ bun.default_allocator.destroy(this);
+ }
+};
+
+fn exportReplacementValue(value: JSValue, globalThis: *JSGlobalObject) ?JSAst.Expr {
+ if (value.isBoolean()) {
+ return Expr{
+ .data = .{
+ .e_boolean = .{
+ .value = value.toBoolean(),
+ },
+ },
+ .loc = logger.Loc.Empty,
+ };
+ }
+
+ if (value.isNumber()) {
+ return Expr{
+ .data = .{
+ .e_number = .{ .value = value.asNumber() },
+ },
+ .loc = logger.Loc.Empty,
+ };
+ }
+
+ if (value.isNull()) {
+ return Expr{
+ .data = .{
+ .e_null = .{},
+ },
+ .loc = logger.Loc.Empty,
+ };
+ }
+
+ if (value.isUndefined()) {
+ return Expr{
+ .data = .{
+ .e_undefined = .{},
+ },
+ .loc = logger.Loc.Empty,
+ };
+ }
+
+ if (value.isString()) {
+ var str = JSAst.E.String{
+ .data = std.fmt.allocPrint(bun.default_allocator, "{}", .{value.getZigString(globalThis)}) catch unreachable,
+ };
+ var out = bun.default_allocator.create(JSAst.E.String) catch unreachable;
+ out.* = str;
+ return Expr{
+ .data = .{
+ .e_string = out,
+ },
+ .loc = logger.Loc.Empty,
+ };
+ }
+
+ return null;
+}
+
+fn transformOptionsFromJSC(ctx: JSC.C.JSContextRef, temp_allocator: std.mem.Allocator, args: *JSC.Node.ArgumentsSlice, exception: JSC.C.ExceptionRef) !TranspilerOptions {
+ var globalThis = ctx.ptr();
+ const object = args.next() orelse return TranspilerOptions{ .log = logger.Log.init(temp_allocator) };
+ if (object.isUndefinedOrNull()) return TranspilerOptions{ .log = logger.Log.init(temp_allocator) };
+
+ args.eat();
+ var allocator = args.arena.allocator();
+
+ var transpiler = TranspilerOptions{
+ .default_loader = .jsx,
+ .transform = default_transform_options,
+ .log = logger.Log.init(allocator),
+ };
+ transpiler.log.level = .warn;
+
+ if (!object.isObject()) {
+ JSC.throwInvalidArguments("Expected an object", .{}, ctx, exception);
+ return transpiler;
+ }
+
+ if (object.getIfPropertyExists(ctx.ptr(), "define")) |define| {
+ define: {
+ if (define.isUndefinedOrNull()) {
+ break :define;
+ }
+
+ if (!define.isObject()) {
+ JSC.throwInvalidArguments("define must be an object", .{}, ctx, exception);
+ return transpiler;
+ }
+
+ var array = JSC.C.JSObjectCopyPropertyNames(globalThis.ref(), define.asObjectRef());
+ defer JSC.C.JSPropertyNameArrayRelease(array);
+ const count = JSC.C.JSPropertyNameArrayGetCount(array);
+ // cannot be a temporary because it may be loaded on different threads.
+ var map_entries = allocator.alloc([]u8, count * 2) catch unreachable;
+ var names = map_entries[0..count];
+
+ var values = map_entries[count..];
+
+ var i: usize = 0;
+ while (i < count) : (i += 1) {
+ var property_name_ref = JSC.C.JSPropertyNameArrayGetNameAtIndex(
+ array,
+ i,
+ );
+ defer JSC.C.JSStringRelease(property_name_ref);
+ const prop: []const u8 = JSC.C.JSStringGetCharacters8Ptr(property_name_ref)[0..JSC.C.JSStringGetLength(property_name_ref)];
+ const property_value: JSC.JSValue = JSC.JSValue.fromRef(
+ JSC.C.JSObjectGetProperty(
+ globalThis.ref(),
+ define.asObjectRef(),
+ property_name_ref,
+ null,
+ ),
+ );
+ const value_type = property_value.jsType();
+
+ if (!value_type.isStringLike()) {
+ JSC.throwInvalidArguments("define \"{s}\" must be a JSON string", .{prop}, ctx, exception);
+ return transpiler;
+ }
+ names[i] = allocator.dupe(u8, prop) catch unreachable;
+ var val = JSC.ZigString.init("");
+ property_value.toZigString(&val, globalThis);
+ if (val.len == 0) {
+ val = JSC.ZigString.init("\"\"");
+ }
+ values[i] = std.fmt.allocPrint(allocator, "{}", .{val}) catch unreachable;
+ }
+ transpiler.transform.define = Api.StringMap{
+ .keys = names,
+ .values = values,
+ };
+ }
+ }
+
+ if (object.get(globalThis, "external")) |external| {
+ external: {
+ if (external.isUndefinedOrNull()) break :external;
+
+ const toplevel_type = external.jsType();
+ if (toplevel_type.isStringLike()) {
+ var zig_str = JSC.ZigString.init("");
+ external.toZigString(&zig_str, globalThis);
+ if (zig_str.len == 0) break :external;
+ var single_external = allocator.alloc(string, 1) catch unreachable;
+ single_external[0] = std.fmt.allocPrint(allocator, "{}", .{external}) catch unreachable;
+ transpiler.transform.external = single_external;
+ } else if (toplevel_type.isArray()) {
+ const count = external.getLengthOfArray(globalThis);
+ if (count == 0) break :external;
+
+ var externals = allocator.alloc(string, count) catch unreachable;
+ var iter = external.arrayIterator(globalThis);
+ var i: usize = 0;
+ while (iter.next()) |entry| {
+ if (!entry.jsType().isStringLike()) {
+ JSC.throwInvalidArguments("external must be a string or string[]", .{}, ctx, exception);
+ return transpiler;
+ }
+
+ var zig_str = JSC.ZigString.init("");
+ entry.toZigString(&zig_str, globalThis);
+ if (zig_str.len == 0) continue;
+ externals[i] = std.fmt.allocPrint(allocator, "{}", .{external}) catch unreachable;
+ i += 1;
+ }
+
+ transpiler.transform.external = externals[0..i];
+ } else {
+ JSC.throwInvalidArguments("external must be a string or string[]", .{}, ctx, exception);
+ return transpiler;
+ }
+ }
+ }
+
+ if (object.get(globalThis, "loader")) |loader| {
+ if (Loader.fromJS(globalThis, loader, exception)) |resolved| {
+ if (!resolved.isJavaScriptLike()) {
+ JSC.throwInvalidArguments("only JavaScript-like loaders supported for now", .{}, ctx, exception);
+ return transpiler;
+ }
+
+ transpiler.default_loader = resolved;
+ }
+
+ if (exception.* != null) {
+ return transpiler;
+ }
+ }
+
+ if (object.get(globalThis, "platform")) |platform| {
+ if (Platform.fromJS(globalThis, platform, exception)) |resolved| {
+ transpiler.transform.platform = resolved.toAPI();
+ }
+
+ if (exception.* != null) {
+ return transpiler;
+ }
+ }
+
+ if (object.get(globalThis, "tsconfig")) |tsconfig| {
+ tsconfig: {
+ if (tsconfig.isUndefinedOrNull()) break :tsconfig;
+ const kind = tsconfig.jsType();
+ var out = JSC.ZigString.init("");
+
+ if (kind.isArray()) {
+ JSC.throwInvalidArguments("tsconfig must be a string or object", .{}, ctx, exception);
+ return transpiler;
+ }
+
+ if (!kind.isStringLike()) {
+ tsconfig.jsonStringify(globalThis, 0, &out);
+ } else {
+ tsconfig.toZigString(&out, globalThis);
+ }
+
+ if (out.len == 0) break :tsconfig;
+ transpiler.tsconfig_buf = std.fmt.allocPrint(allocator, "{}", .{out}) catch unreachable;
+
+ // TODO: JSC -> Ast conversion
+ if (TSConfigJSON.parse(
+ allocator,
+ &transpiler.log,
+ logger.Source.initPathString("tsconfig.json", transpiler.tsconfig_buf),
+ &VirtualMachine.vm.bundler.resolver.caches.json,
+ true,
+ ) catch null) |parsed_tsconfig| {
+ transpiler.tsconfig = parsed_tsconfig;
+ }
+ }
+ }
+
+ transpiler.runtime.allow_runtime = false;
+
+ if (object.getIfPropertyExists(globalThis, "macro")) |macros| {
+ macros: {
+ if (macros.isUndefinedOrNull()) break :macros;
+ const kind = macros.jsType();
+ const is_object = kind.isObject();
+ if (!(kind.isStringLike() or is_object)) {
+ JSC.throwInvalidArguments("macro must be an object", .{}, ctx, exception);
+ return transpiler;
+ }
+
+ var out: ZigString = ZigString.init("");
+ // TODO: write a converter between JSC types and Bun AST types
+ if (is_object) {
+ macros.jsonStringify(globalThis, 0, &out);
+ } else {
+ macros.toZigString(&out, globalThis);
+ }
+
+ if (out.len == 0) break :macros;
+ transpiler.macros_buf = std.fmt.allocPrint(allocator, "{}", .{out}) catch unreachable;
+ const source = logger.Source.initPathString("macros.json", transpiler.macros_buf);
+ const json = (VirtualMachine.vm.bundler.resolver.caches.json.parseJSON(
+ &transpiler.log,
+ source,
+ allocator,
+ ) catch null) orelse break :macros;
+ transpiler.macro_map = PackageJSON.parseMacrosJSON(allocator, json, &transpiler.log, &source);
+ }
+ }
+
+ if (object.get(globalThis, "autoImportJSX")) |flag| {
+ transpiler.runtime.auto_import_jsx = flag.toBoolean();
+ }
+
+ if (object.get(globalThis, "allowBunRuntime")) |flag| {
+ transpiler.runtime.allow_runtime = flag.toBoolean();
+ }
+
+ if (object.get(globalThis, "jsxOptimizationInline")) |flag| {
+ transpiler.runtime.jsx_optimization_inline = flag.toBoolean();
+ }
+
+ if (object.get(globalThis, "jsxOptimizationHoist")) |flag| {
+ transpiler.runtime.jsx_optimization_hoist = flag.toBoolean();
+
+ if (!transpiler.runtime.jsx_optimization_inline and transpiler.runtime.jsx_optimization_hoist) {
+ JSC.throwInvalidArguments("jsxOptimizationHoist requires jsxOptimizationInline", .{}, ctx, exception);
+ return transpiler;
+ }
+ }
+
+ if (object.get(globalThis, "sourcemap")) |flag| {
+ if (flag.isBoolean() or flag.isUndefinedOrNull()) {
+ if (flag.toBoolean()) {
+ transpiler.transform.source_map = Api.SourceMapMode.external;
+ } else {
+ transpiler.transform.source_map = Api.SourceMapMode.inline_into_file;
+ }
+ } else {
+ var sourcemap = flag.toSlice(globalThis, allocator);
+ if (options.SourceMapOption.map.get(sourcemap.slice())) |source| {
+ transpiler.transform.source_map = source.toAPI();
+ } else {
+ JSC.throwInvalidArguments("sourcemap must be one of \"inline\", \"external\", or \"none\"", .{}, ctx, exception);
+ return transpiler;
+ }
+ }
+ }
+
+ var tree_shaking: ?bool = null;
+ if (object.get(globalThis, "treeShaking")) |treeShaking| {
+ tree_shaking = treeShaking.toBoolean();
+ }
+
+ var trim_unused_imports: ?bool = null;
+ if (object.get(globalThis, "trimUnusedImports")) |trimUnusedImports| {
+ trim_unused_imports = trimUnusedImports.toBoolean();
+ }
+
+ if (object.getTruthy(globalThis, "exports")) |exports| {
+ if (!exports.isObject()) {
+ JSC.throwInvalidArguments("exports must be an object", .{}, ctx, exception);
+ return transpiler;
+ }
+
+ var replacements = Runtime.Features.ReplaceableExport.Map{};
+ errdefer replacements.clearAndFree(bun.default_allocator);
+
+ if (exports.getTruthy(globalThis, "eliminate")) |eliminate| {
+ if (!eliminate.jsType().isArray()) {
+ JSC.throwInvalidArguments("exports.eliminate must be an array", .{}, ctx, exception);
+ return transpiler;
+ }
+
+ var total_name_buf_len: u32 = 0;
+ var string_count: u32 = 0;
+ var iter = JSC.JSArrayIterator.init(eliminate, globalThis);
+ {
+ var length_iter = iter;
+ while (length_iter.next()) |value| {
+ if (value.isString()) {
+ const length = value.getLengthOfArray(globalThis);
+ string_count += @as(u32, @boolToInt(length > 0));
+ total_name_buf_len += length;
+ }
+ }
+ }
+
+ if (total_name_buf_len > 0) {
+ var buf = try std.ArrayListUnmanaged(u8).initCapacity(bun.default_allocator, total_name_buf_len);
+ try replacements.ensureUnusedCapacity(bun.default_allocator, string_count);
+ {
+ var length_iter = iter;
+ while (length_iter.next()) |value| {
+ if (!value.isString()) continue;
+ var str = value.getZigString(globalThis);
+ if (str.len == 0) continue;
+ const name = std.fmt.bufPrint(buf.items.ptr[buf.items.len..buf.capacity], "{}", .{str}) catch {
+ JSC.throwInvalidArguments("Error reading exports.eliminate. TODO: utf-16", .{}, ctx, exception);
+ return transpiler;
+ };
+ buf.items.len += name.len;
+ if (name.len > 0) {
+ replacements.putAssumeCapacity(name, .{ .delete = .{} });
+ }
+ }
+ }
+ }
+ }
+
+ if (exports.getTruthy(globalThis, "replace")) |replace| {
+ if (!replace.isObject()) {
+ JSC.throwInvalidArguments("replace must be an object", .{}, ctx, exception);
+ return transpiler;
+ }
+
+ var total_name_buf_len: usize = 0;
+
+ var array = js.JSObjectCopyPropertyNames(ctx, replace.asObjectRef());
+ defer js.JSPropertyNameArrayRelease(array);
+ const property_names_count = @intCast(u32, js.JSPropertyNameArrayGetCount(array));
+ var iter = JSC.JSPropertyNameIterator{
+ .array = array,
+ .count = @intCast(u32, property_names_count),
+ };
+
+ {
+ var key_iter = iter;
+ while (key_iter.next()) |item| {
+ total_name_buf_len += JSC.C.JSStringGetLength(item);
+ }
+ }
+
+ if (total_name_buf_len > 0) {
+ var total_name_buf = try std.ArrayList(u8).initCapacity(bun.default_allocator, total_name_buf_len);
+ errdefer total_name_buf.clearAndFree();
+
+ try replacements.ensureUnusedCapacity(bun.default_allocator, property_names_count);
+ defer {
+ if (exception.* != null) {
+ total_name_buf.clearAndFree();
+ replacements.clearAndFree(bun.default_allocator);
+ }
+ }
+
+ while (iter.next()) |item| {
+ const start = total_name_buf.items.len;
+ total_name_buf.items.len += @maximum(
+ // this returns a null terminated string
+ JSC.C.JSStringGetUTF8CString(item, total_name_buf.items.ptr + start, total_name_buf.capacity - start),
+ 1,
+ ) - 1;
+ JSC.C.JSStringRelease(item);
+ const key = total_name_buf.items[start..total_name_buf.items.len];
+ // if somehow the string is empty, skip it
+ if (key.len == 0)
+ continue;
+
+ const value = replace.get(globalThis, key).?;
+ if (value.isEmpty()) continue;
+
+ if (!JSLexer.isIdentifier(key)) {
+ JSC.throwInvalidArguments("\"{s}\" is not a valid ECMAScript identifier", .{key}, ctx, exception);
+ total_name_buf.deinit();
+ return transpiler;
+ }
+
+ var entry = replacements.getOrPutAssumeCapacity(key);
+
+ if (exportReplacementValue(value, globalThis)) |expr| {
+ entry.value_ptr.* = .{ .replace = expr };
+ continue;
+ }
+
+ if (value.isObject() and value.getLengthOfArray(ctx.ptr()) == 2) {
+ const replacementValue = JSC.JSObject.getIndex(value, globalThis, 1);
+ if (exportReplacementValue(replacementValue, globalThis)) |to_replace| {
+ const replacementKey = JSC.JSObject.getIndex(value, globalThis, 0);
+ var slice = (try replacementKey.toSlice(globalThis, bun.default_allocator).cloneIfNeeded());
+ var replacement_name = slice.slice();
+
+ if (!JSLexer.isIdentifier(replacement_name)) {
+ JSC.throwInvalidArguments("\"{s}\" is not a valid ECMAScript identifier", .{replacement_name}, ctx, exception);
+ total_name_buf.deinit();
+ slice.deinit();
+ return transpiler;
+ }
+
+ entry.value_ptr.* = .{
+ .inject = .{
+ .name = replacement_name,
+ .value = to_replace,
+ },
+ };
+ continue;
+ }
+ }
+
+ JSC.throwInvalidArguments("exports.replace values can only be string, null, undefined, number or boolean", .{}, ctx, exception);
+ return transpiler;
+ }
+ }
+ }
+
+ tree_shaking = tree_shaking orelse (replacements.count() > 0);
+ transpiler.runtime.replace_exports = replacements;
+ }
+
+ transpiler.tree_shaking = tree_shaking orelse false;
+ transpiler.trim_unused_imports = trim_unused_imports orelse transpiler.tree_shaking;
+
+ return transpiler;
+}
+
+pub fn constructor(
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+) js.JSObjectRef {
+ var temp = std.heap.ArenaAllocator.init(getAllocator(ctx));
+ var args = JSC.Node.ArgumentsSlice.init(ctx.bunVM(), @ptrCast([*]const JSC.JSValue, arguments.ptr)[0..arguments.len]);
+ defer temp.deinit();
+ const transpiler_options: TranspilerOptions = if (arguments.len > 0)
+ transformOptionsFromJSC(ctx, temp.allocator(), &args, exception) catch {
+ JSC.throwInvalidArguments("Failed to create transpiler", .{}, ctx, exception);
+ return null;
+ }
+ else
+ TranspilerOptions{ .log = logger.Log.init(getAllocator(ctx)) };
+
+ if (exception.* != null) {
+ return null;
+ }
+
+ if ((transpiler_options.log.warnings + transpiler_options.log.errors) > 0) {
+ var out_exception = transpiler_options.log.toJS(ctx.ptr(), getAllocator(ctx), "Failed to create transpiler");
+ exception.* = out_exception.asObjectRef();
+ return null;
+ }
+
+ var log = getAllocator(ctx).create(logger.Log) catch unreachable;
+ log.* = transpiler_options.log;
+ var bundler = Bundler.Bundler.init(
+ getAllocator(ctx),
+ log,
+ transpiler_options.transform,
+ null,
+ JavaScript.VirtualMachine.vm.bundler.env,
+ ) catch |err| {
+ if ((log.warnings + log.errors) > 0) {
+ var out_exception = log.toJS(ctx.ptr(), getAllocator(ctx), "Failed to create transpiler");
+ exception.* = out_exception.asObjectRef();
+ return null;
+ }
+
+ JSC.throwInvalidArguments("Error creating transpiler: {s}", .{@errorName(err)}, ctx, exception);
+ return null;
+ };
+
+ bundler.configureLinkerWithAutoJSX(false);
+ bundler.options.env.behavior = .disable;
+ bundler.configureDefines() catch |err| {
+ if ((log.warnings + log.errors) > 0) {
+ var out_exception = log.toJS(ctx.ptr(), getAllocator(ctx), "Failed to load define");
+ exception.* = out_exception.asObjectRef();
+ return null;
+ }
+
+ JSC.throwInvalidArguments("Failed to load define: {s}", .{@errorName(err)}, ctx, exception);
+ return null;
+ };
+
+ if (transpiler_options.macro_map.count() > 0) {
+ bundler.options.macro_remap = transpiler_options.macro_map;
+ }
+
+ bundler.options.tree_shaking = transpiler_options.tree_shaking;
+ bundler.options.trim_unused_imports = transpiler_options.trim_unused_imports;
+ bundler.options.allow_runtime = transpiler_options.runtime.allow_runtime;
+ bundler.options.auto_import_jsx = transpiler_options.runtime.auto_import_jsx;
+ bundler.options.hot_module_reloading = transpiler_options.runtime.hot_module_reloading;
+ bundler.options.jsx.supports_fast_refresh = bundler.options.hot_module_reloading and
+ bundler.options.allow_runtime and transpiler_options.runtime.react_fast_refresh;
+
+ var transpiler = getAllocator(ctx).create(Transpiler) catch unreachable;
+ transpiler.* = Transpiler{
+ .transpiler_options = transpiler_options,
+ .bundler = bundler,
+ .arena = args.arena,
+ .scan_pass_result = ScanPassResult.init(getAllocator(ctx)),
+ };
+
+ return Class.make(ctx, transpiler);
+}
+
+pub fn finalize(
+ this: *Transpiler,
+) void {
+ this.bundler.log.deinit();
+ this.scan_pass_result.named_imports.deinit();
+ this.scan_pass_result.import_records.deinit();
+ this.scan_pass_result.used_symbols.deinit();
+ if (this.buffer_writer != null) {
+ this.buffer_writer.?.buffer.deinit();
+ }
+
+ // bun.default_allocator.free(this.transpiler_options.tsconfig_buf);
+ // bun.default_allocator.free(this.transpiler_options.macros_buf);
+ this.arena.deinit();
+}
+
+fn getParseResult(this: *Transpiler, allocator: std.mem.Allocator, code: []const u8, loader: ?Loader, macro_js_ctx: JSValue) ?Bundler.ParseResult {
+ const name = this.transpiler_options.default_loader.stdinName();
+ const source = logger.Source.initPathString(name, code);
+
+ const jsx = if (this.transpiler_options.tsconfig != null)
+ this.transpiler_options.tsconfig.?.mergeJSX(this.bundler.options.jsx)
+ else
+ this.bundler.options.jsx;
+
+ const parse_options = Bundler.Bundler.ParseOptions{
+ .allocator = allocator,
+ .macro_remappings = this.transpiler_options.macro_map,
+ .dirname_fd = 0,
+ .file_descriptor = null,
+ .loader = loader orelse this.transpiler_options.default_loader,
+ .jsx = jsx,
+ .path = source.path,
+ .virtual_source = &source,
+ .replace_exports = this.transpiler_options.runtime.replace_exports,
+ .macro_js_ctx = macro_js_ctx,
+ // .allocator = this.
+ };
+
+ var parse_result = this.bundler.parse(parse_options, null);
+
+ // necessary because we don't run the linker
+ if (parse_result) |*res| {
+ for (res.ast.import_records) |*import| {
+ if (import.kind.isCommonJS()) {
+ import.wrap_with_to_module = true;
+ import.module_id = @truncate(u32, std.hash.Wyhash.hash(0, import.path.pretty));
+ }
+ }
+ }
+
+ return parse_result;
+}
+
+pub fn scan(
+ this: *Transpiler,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+) JSC.C.JSObjectRef {
+ var args = JSC.Node.ArgumentsSlice.init(ctx.bunVM(), @ptrCast([*]const JSC.JSValue, arguments.ptr)[0..arguments.len]);
+ defer args.arena.deinit();
+ const code_arg = args.next() orelse {
+ JSC.throwInvalidArguments("Expected a string or Uint8Array", .{}, ctx, exception);
+ return null;
+ };
+
+ const code_holder = JSC.Node.StringOrBuffer.fromJS(ctx.ptr(), args.arena.allocator(), code_arg, exception) orelse {
+ if (exception.* == null) JSC.throwInvalidArguments("Expected a string or Uint8Array", .{}, ctx, exception);
+ return null;
+ };
+
+ const code = code_holder.slice();
+ args.eat();
+ const loader: ?Loader = brk: {
+ if (args.next()) |arg| {
+ args.eat();
+ break :brk Loader.fromJS(ctx.ptr(), arg, exception);
+ }
+
+ break :brk null;
+ };
+
+ if (exception.* != null) return null;
+
+ var arena = Mimalloc.Arena.init() catch unreachable;
+ var prev_allocator = this.bundler.allocator;
+ this.bundler.setAllocator(arena.allocator());
+ var log = logger.Log.init(arena.backingAllocator());
+ defer log.deinit();
+ this.bundler.setLog(&log);
+ defer {
+ this.bundler.setLog(&this.transpiler_options.log);
+ this.bundler.setAllocator(prev_allocator);
+ arena.deinit();
+ }
+
+ defer {
+ JSAst.Stmt.Data.Store.reset();
+ JSAst.Expr.Data.Store.reset();
+ }
+
+ const parse_result = getParseResult(this, arena.allocator(), code, loader, JSC.JSValue.zero) orelse {
+ if ((this.bundler.log.warnings + this.bundler.log.errors) > 0) {
+ var out_exception = this.bundler.log.toJS(ctx.ptr(), getAllocator(ctx), "Parse error");
+ exception.* = out_exception.asObjectRef();
+ return null;
+ }
+
+ JSC.throwInvalidArguments("Failed to parse", .{}, ctx, exception);
+ return null;
+ };
+
+ if ((this.bundler.log.warnings + this.bundler.log.errors) > 0) {
+ var out_exception = this.bundler.log.toJS(ctx.ptr(), getAllocator(ctx), "Parse error");
+ exception.* = out_exception.asObjectRef();
+ return null;
+ }
+
+ const exports_label = JSC.ZigString.init("exports");
+ const imports_label = JSC.ZigString.init("imports");
+ const named_imports_value = namedImportsToJS(
+ ctx.ptr(),
+ parse_result.ast.import_records,
+ exception,
+ );
+ if (exception.* != null) return null;
+ var named_exports_value = namedExportsToJS(
+ ctx.ptr(),
+ parse_result.ast.named_exports,
+ );
+ return JSC.JSValue.createObject2(ctx.ptr(), &imports_label, &exports_label, named_imports_value, named_exports_value).asObjectRef();
+}
+
+// pub fn build(
+// this: *Transpiler,
+// ctx: js.JSContextRef,
+// _: js.JSObjectRef,
+// _: js.JSObjectRef,
+// arguments: []const js.JSValueRef,
+// exception: js.ExceptionRef,
+// ) JSC.C.JSObjectRef {}
+
+pub fn transform(
+ this: *Transpiler,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+) JSC.C.JSObjectRef {
+ var args = JSC.Node.ArgumentsSlice.init(ctx.bunVM(), @ptrCast([*]const JSC.JSValue, arguments.ptr)[0..arguments.len]);
+ defer args.arena.deinit();
+ const code_arg = args.next() orelse {
+ JSC.throwInvalidArguments("Expected a string or Uint8Array", .{}, ctx, exception);
+ return null;
+ };
+
+ const code_holder = JSC.Node.StringOrBuffer.fromJS(ctx.ptr(), this.arena.allocator(), code_arg, exception) orelse {
+ if (exception.* == null) JSC.throwInvalidArguments("Expected a string or Uint8Array", .{}, ctx, exception);
+ return null;
+ };
+
+ const code = code_holder.slice();
+ args.eat();
+ const loader: ?Loader = brk: {
+ if (args.next()) |arg| {
+ args.eat();
+ break :brk Loader.fromJS(ctx.ptr(), arg, exception);
+ }
+
+ break :brk null;
+ };
+
+ if (exception.* != null) return null;
+ if (code_holder == .string) {
+ JSC.C.JSValueProtect(ctx, arguments[0]);
+ }
+
+ var task = TransformTask.create(this, if (code_holder == .string) arguments[0] else null, ctx.ptr(), ZigString.init(code), loader orelse this.transpiler_options.default_loader) catch return null;
+ task.schedule();
+ return task.promise.asObjectRef();
+}
+
+pub fn transformSync(
+ this: *Transpiler,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+) JSC.C.JSObjectRef {
+ var args = JSC.Node.ArgumentsSlice.init(ctx.bunVM(), @ptrCast([*]const JSC.JSValue, arguments.ptr)[0..arguments.len]);
+ defer args.arena.deinit();
+ const code_arg = args.next() orelse {
+ JSC.throwInvalidArguments("Expected a string or Uint8Array", .{}, ctx, exception);
+ return null;
+ };
+
+ var arena = Mimalloc.Arena.init() catch unreachable;
+ defer arena.deinit();
+ const code_holder = JSC.Node.StringOrBuffer.fromJS(ctx.ptr(), arena.allocator(), code_arg, exception) orelse {
+ if (exception.* == null) JSC.throwInvalidArguments("Expected a string or Uint8Array", .{}, ctx, exception);
+ return null;
+ };
+
+ const code = code_holder.slice();
+ JSC.JSValue.c(arguments[0]).ensureStillAlive();
+ defer JSC.JSValue.c(arguments[0]).ensureStillAlive();
+
+ args.eat();
+ var js_ctx_value: JSC.JSValue = JSC.JSValue.zero;
+ const loader: ?Loader = brk: {
+ if (args.next()) |arg| {
+ args.eat();
+ if (arg.isNumber() or arg.isString()) {
+ break :brk Loader.fromJS(ctx.ptr(), arg, exception);
+ }
+
+ if (arg.isObject()) {
+ js_ctx_value = arg;
+ break :brk null;
+ }
+ }
+
+ break :brk null;
+ };
+
+ if (args.nextEat()) |arg| {
+ if (arg.isObject()) {
+ js_ctx_value = arg;
+ } else {
+ JSC.throwInvalidArguments("Expected a Loader or object", .{}, ctx, exception);
+ return null;
+ }
+ }
+ if (!js_ctx_value.isEmpty()) {
+ js_ctx_value.ensureStillAlive();
+ }
+
+ defer {
+ if (!js_ctx_value.isEmpty()) {
+ js_ctx_value.ensureStillAlive();
+ }
+ }
+
+ if (exception.* != null) return null;
+
+ JSAst.Stmt.Data.Store.reset();
+ JSAst.Expr.Data.Store.reset();
+ defer {
+ JSAst.Stmt.Data.Store.reset();
+ JSAst.Expr.Data.Store.reset();
+ }
+
+ var prev_bundler = this.bundler;
+ this.bundler.setAllocator(arena.allocator());
+ this.bundler.macro_context = null;
+ var log = logger.Log.init(arena.backingAllocator());
+ this.bundler.setLog(&log);
+
+ defer {
+ this.bundler = prev_bundler;
+ }
+
+ var parse_result = getParseResult(
+ this,
+ arena.allocator(),
+ code,
+ loader,
+ js_ctx_value,
+ ) orelse {
+ if ((this.bundler.log.warnings + this.bundler.log.errors) > 0) {
+ var out_exception = this.bundler.log.toJS(ctx.ptr(), getAllocator(ctx), "Parse error");
+ exception.* = out_exception.asObjectRef();
+ return null;
+ }
+
+ JSC.throwInvalidArguments("Failed to parse", .{}, ctx, exception);
+ return null;
+ };
+
+ if ((this.bundler.log.warnings + this.bundler.log.errors) > 0) {
+ var out_exception = this.bundler.log.toJS(ctx.ptr(), getAllocator(ctx), "Parse error");
+ exception.* = out_exception.asObjectRef();
+ return null;
+ }
+
+ var buffer_writer = this.buffer_writer orelse brk: {
+ var writer = JSPrinter.BufferWriter.init(arena.backingAllocator()) catch {
+ JSC.throwInvalidArguments("Failed to create BufferWriter", .{}, ctx, exception);
+ return null;
+ };
+
+ writer.buffer.growIfNeeded(code.len) catch unreachable;
+ writer.buffer.list.expandToCapacity();
+ break :brk writer;
+ };
+
+ defer {
+ this.buffer_writer = buffer_writer;
+ }
+
+ buffer_writer.reset();
+ var printer = JSPrinter.BufferPrinter.init(buffer_writer);
+ _ = this.bundler.print(parse_result, @TypeOf(&printer), &printer, .esm_ascii) catch |err| {
+ JSC.JSError(bun.default_allocator, "Failed to print code: {s}", .{@errorName(err)}, ctx, exception);
+
+ return null;
+ };
+
+ // TODO: benchmark if pooling this way is faster or moving is faster
+ buffer_writer = printer.ctx;
+ var out = JSC.ZigString.init(buffer_writer.written);
+ out.mark();
+
+ return out.toValueGC(ctx.ptr()).asObjectRef();
+}
+
+fn namedExportsToJS(global: *JSGlobalObject, named_exports: JSAst.Ast.NamedExports) JSC.JSValue {
+ if (named_exports.count() == 0)
+ return JSC.JSValue.fromRef(JSC.C.JSObjectMakeArray(global.ref(), 0, null, null));
+
+ var named_exports_iter = named_exports.iterator();
+ var stack_fallback = std.heap.stackFallback(@sizeOf(JSC.ZigString) * 32, getAllocator(global.ref()));
+ var allocator = stack_fallback.get();
+ var names = allocator.alloc(
+ JSC.ZigString,
+ named_exports.count(),
+ ) catch unreachable;
+ defer allocator.free(names);
+ var i: usize = 0;
+ while (named_exports_iter.next()) |entry| {
+ names[i] = JSC.ZigString.init(entry.key_ptr.*);
+ i += 1;
+ }
+ JSC.ZigString.sortAsc(names[0..i]);
+ return JSC.JSValue.createStringArray(global, names.ptr, names.len, true);
+}
+
+const ImportRecord = @import("../../import_record.zig").ImportRecord;
+
+fn namedImportsToJS(
+ global: *JSGlobalObject,
+ import_records: []const ImportRecord,
+ exception: JSC.C.ExceptionRef,
+) JSC.JSValue {
+ var stack_fallback = std.heap.stackFallback(@sizeOf(JSC.C.JSObjectRef) * 32, getAllocator(global.ref()));
+ var allocator = stack_fallback.get();
+
+ var i: usize = 0;
+ const path_label = JSC.ZigString.init("path");
+ const kind_label = JSC.ZigString.init("kind");
+ var array_items = allocator.alloc(
+ JSC.C.JSValueRef,
+ import_records.len,
+ ) catch unreachable;
+ defer allocator.free(array_items);
+
+ for (import_records) |record| {
+ if (record.is_internal) continue;
+
+ const path = JSC.ZigString.init(record.path.text).toValueGC(global);
+ const kind = JSC.ZigString.init(record.kind.label()).toValue(global);
+ array_items[i] = JSC.JSValue.createObject2(global, &path_label, &kind_label, path, kind).asObjectRef();
+ i += 1;
+ }
+
+ return JSC.JSValue.fromRef(JSC.C.JSObjectMakeArray(global.ref(), i, array_items.ptr, exception));
+}
+
+pub fn scanImports(
+ this: *Transpiler,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+) JSC.C.JSObjectRef {
+ var args = JSC.Node.ArgumentsSlice.init(ctx.bunVM(), @ptrCast([*]const JSC.JSValue, arguments.ptr)[0..arguments.len]);
+ const code_arg = args.next() orelse {
+ JSC.throwInvalidArguments("Expected a string or Uint8Array", .{}, ctx, exception);
+ return null;
+ };
+
+ const code_holder = JSC.Node.StringOrBuffer.fromJS(ctx.ptr(), args.arena.allocator(), code_arg, exception) orelse {
+ if (exception.* == null) JSC.throwInvalidArguments("Expected a string or Uint8Array", .{}, ctx, exception);
+ return null;
+ };
+ args.eat();
+ const code = code_holder.slice();
+
+ var loader: Loader = this.transpiler_options.default_loader;
+ if (args.next()) |arg| {
+ if (Loader.fromJS(ctx.ptr(), arg, exception)) |_loader| {
+ loader = _loader;
+ }
+ args.eat();
+ }
+
+ if (!loader.isJavaScriptLike()) {
+ JSC.throwInvalidArguments("Only JavaScript-like files support this fast path", .{}, ctx, exception);
+ return null;
+ }
+
+ if (exception.* != null) return null;
+
+ var arena = Mimalloc.Arena.init() catch unreachable;
+ var prev_allocator = this.bundler.allocator;
+ this.bundler.setAllocator(arena.allocator());
+ var log = logger.Log.init(arena.backingAllocator());
+ defer log.deinit();
+ this.bundler.setLog(&log);
+ defer {
+ this.bundler.setLog(&this.transpiler_options.log);
+ this.bundler.setAllocator(prev_allocator);
+ arena.deinit();
+ }
+
+ const source = logger.Source.initPathString(loader.stdinName(), code);
+ var bundler = &this.bundler;
+ const jsx = if (this.transpiler_options.tsconfig != null)
+ this.transpiler_options.tsconfig.?.mergeJSX(this.bundler.options.jsx)
+ else
+ this.bundler.options.jsx;
+
+ var opts = JSParser.Parser.Options.init(jsx, loader);
+ if (this.bundler.macro_context == null) {
+ this.bundler.macro_context = JSAst.Macro.MacroContext.init(&this.bundler);
+ }
+ opts.macro_context = &this.bundler.macro_context.?;
+
+ JSAst.Stmt.Data.Store.reset();
+ JSAst.Expr.Data.Store.reset();
+
+ defer {
+ JSAst.Stmt.Data.Store.reset();
+ JSAst.Expr.Data.Store.reset();
+ }
+
+ bundler.resolver.caches.js.scan(
+ bundler.allocator,
+ &this.scan_pass_result,
+ opts,
+ bundler.options.define,
+ &log,
+ &source,
+ ) catch |err| {
+ defer this.scan_pass_result.reset();
+ if ((log.warnings + log.errors) > 0) {
+ var out_exception = log.toJS(ctx.ptr(), getAllocator(ctx), "Failed to scan imports");
+ exception.* = out_exception.asObjectRef();
+ return null;
+ }
+
+ JSC.throwInvalidArguments("Failed to scan imports: {s}", .{@errorName(err)}, ctx, exception);
+ return null;
+ };
+
+ defer this.scan_pass_result.reset();
+
+ if ((log.warnings + log.errors) > 0) {
+ var out_exception = log.toJS(ctx.ptr(), getAllocator(ctx), "Failed to scan imports");
+ exception.* = out_exception.asObjectRef();
+ return null;
+ }
+
+ const named_imports_value = namedImportsToJS(
+ ctx.ptr(),
+ this.scan_pass_result.import_records.items,
+ exception,
+ );
+ if (exception.* != null) return null;
+ return named_imports_value.asObjectRef();
+}