mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
2080 lines
82 KiB
Zig
2080 lines
82 KiB
Zig
/// How to add a new function or property to the Bun global
|
|
///
|
|
/// - Add a callback or property to the below struct
|
|
/// - @export it in the appropriate place
|
|
/// - Update "@begin bunObjectTable" in BunObject.cpp
|
|
/// - Getters use a generated wrapper function `BunObject_getter_wrap_<name>`
|
|
/// - Update "BunObject+exports.h"
|
|
/// - Run `bun run build`
|
|
pub const BunObject = struct {
|
|
// --- Callbacks ---
|
|
pub const allocUnsafe = toJSCallback(Bun.allocUnsafe);
|
|
pub const build = toJSCallback(Bun.JSBundler.buildFn);
|
|
pub const color = toJSCallback(bun.css.CssColor.jsFunctionColor);
|
|
pub const connect = toJSCallback(host_fn.wrapStaticMethod(api.Listener, "connect", false));
|
|
pub const createParsedShellScript = toJSCallback(bun.shell.ParsedShellScript.createParsedShellScript);
|
|
pub const createShellInterpreter = toJSCallback(bun.shell.Interpreter.createShellInterpreter);
|
|
pub const deflateSync = toJSCallback(JSZlib.deflateSync);
|
|
pub const file = toJSCallback(WebCore.Blob.constructBunFile);
|
|
pub const gunzipSync = toJSCallback(JSZlib.gunzipSync);
|
|
pub const gzipSync = toJSCallback(JSZlib.gzipSync);
|
|
pub const indexOfLine = toJSCallback(Bun.indexOfLine);
|
|
pub const inflateSync = toJSCallback(JSZlib.inflateSync);
|
|
pub const jest = toJSCallback(@import("../test/jest.zig").Jest.call);
|
|
pub const listen = toJSCallback(host_fn.wrapStaticMethod(api.Listener, "listen", false));
|
|
pub const mmap = toJSCallback(Bun.mmapFile);
|
|
pub const nanoseconds = toJSCallback(Bun.nanoseconds);
|
|
pub const openInEditor = toJSCallback(Bun.openInEditor);
|
|
pub const registerMacro = toJSCallback(Bun.registerMacro);
|
|
pub const resolve = toJSCallback(Bun.resolve);
|
|
pub const resolveSync = toJSCallback(Bun.resolveSync);
|
|
pub const serve = toJSCallback(Bun.serve);
|
|
pub const sha = toJSCallback(host_fn.wrapStaticMethod(Crypto.SHA512_256, "hash_", true));
|
|
pub const shellEscape = toJSCallback(Bun.shellEscape);
|
|
pub const shrink = toJSCallback(Bun.shrink);
|
|
pub const sleepSync = toJSCallback(Bun.sleepSync);
|
|
pub const spawn = toJSCallback(host_fn.wrapStaticMethod(api.Subprocess, "spawn", false));
|
|
pub const spawnSync = toJSCallback(host_fn.wrapStaticMethod(api.Subprocess, "spawnSync", false));
|
|
pub const udpSocket = toJSCallback(host_fn.wrapStaticMethod(api.UDPSocket, "udpSocket", false));
|
|
pub const which = toJSCallback(Bun.which);
|
|
pub const write = toJSCallback(JSC.WebCore.Blob.writeFile);
|
|
pub const zstdCompressSync = toJSCallback(JSZstd.compressSync);
|
|
pub const zstdDecompressSync = toJSCallback(JSZstd.decompressSync);
|
|
pub const zstdCompress = toJSCallback(JSZstd.compress);
|
|
pub const zstdDecompress = toJSCallback(JSZstd.decompress);
|
|
|
|
// --- Callbacks ---
|
|
|
|
// --- Getters ---
|
|
pub const CryptoHasher = toJSGetter(Crypto.CryptoHasher.getter);
|
|
pub const CSRF = toJSGetter(Bun.getCSRFObject);
|
|
pub const FFI = toJSGetter(Bun.FFIObject.getter);
|
|
pub const FileSystemRouter = toJSGetter(Bun.getFileSystemRouter);
|
|
pub const Glob = toJSGetter(Bun.getGlobConstructor);
|
|
pub const MD4 = toJSGetter(Crypto.MD4.getter);
|
|
pub const MD5 = toJSGetter(Crypto.MD5.getter);
|
|
pub const SHA1 = toJSGetter(Crypto.SHA1.getter);
|
|
pub const SHA224 = toJSGetter(Crypto.SHA224.getter);
|
|
pub const SHA256 = toJSGetter(Crypto.SHA256.getter);
|
|
pub const SHA384 = toJSGetter(Crypto.SHA384.getter);
|
|
pub const SHA512 = toJSGetter(Crypto.SHA512.getter);
|
|
pub const SHA512_256 = toJSGetter(Crypto.SHA512_256.getter);
|
|
pub const TOML = toJSGetter(Bun.getTOMLObject);
|
|
pub const Transpiler = toJSGetter(Bun.getTranspilerConstructor);
|
|
pub const argv = toJSGetter(Bun.getArgv);
|
|
pub const cwd = toJSGetter(Bun.getCWD);
|
|
pub const embeddedFiles = toJSGetter(Bun.getEmbeddedFiles);
|
|
pub const enableANSIColors = toJSGetter(Bun.enableANSIColors);
|
|
pub const hash = toJSGetter(Bun.getHashObject);
|
|
pub const inspect = toJSGetter(Bun.getInspect);
|
|
pub const main = toJSGetter(Bun.getMain);
|
|
pub const origin = toJSGetter(Bun.getOrigin);
|
|
pub const semver = toJSGetter(Bun.getSemver);
|
|
pub const stderr = toJSGetter(Bun.getStderr);
|
|
pub const stdin = toJSGetter(Bun.getStdin);
|
|
pub const stdout = toJSGetter(Bun.getStdout);
|
|
pub const unsafe = toJSGetter(Bun.getUnsafe);
|
|
pub const S3Client = toJSGetter(Bun.getS3ClientConstructor);
|
|
pub const s3 = toJSGetter(Bun.getS3DefaultClient);
|
|
pub const ValkeyClient = toJSGetter(Bun.getValkeyClientConstructor);
|
|
pub const valkey = toJSGetter(Bun.getValkeyDefaultClient);
|
|
// --- Getters ---
|
|
|
|
fn getterName(comptime baseName: anytype) [:0]const u8 {
|
|
return "BunObject_getter_" ++ baseName;
|
|
}
|
|
|
|
fn callbackName(comptime baseName: anytype) [:0]const u8 {
|
|
return "BunObject_callback_" ++ baseName;
|
|
}
|
|
|
|
const toJSCallback = JSC.toJSHostFn;
|
|
|
|
const LazyPropertyCallback = fn (*JSC.JSGlobalObject, *JSC.JSObject) callconv(JSC.conv) JSValue;
|
|
|
|
fn toJSGetter(comptime getter: anytype) LazyPropertyCallback {
|
|
return struct {
|
|
pub fn callback(this: *JSC.JSGlobalObject, object: *JSC.JSObject) callconv(JSC.conv) JSValue {
|
|
return bun.jsc.toJSHostCall(this, @src(), getter, .{ this, object });
|
|
}
|
|
}.callback;
|
|
}
|
|
|
|
pub fn exportAll() void {
|
|
if (!@inComptime()) {
|
|
@compileError("Must be comptime");
|
|
}
|
|
|
|
// --- Getters ---
|
|
@export(&BunObject.CryptoHasher, .{ .name = getterName("CryptoHasher") });
|
|
@export(&BunObject.CSRF, .{ .name = getterName("CSRF") });
|
|
@export(&BunObject.FFI, .{ .name = getterName("FFI") });
|
|
@export(&BunObject.FileSystemRouter, .{ .name = getterName("FileSystemRouter") });
|
|
@export(&BunObject.MD4, .{ .name = getterName("MD4") });
|
|
@export(&BunObject.MD5, .{ .name = getterName("MD5") });
|
|
@export(&BunObject.SHA1, .{ .name = getterName("SHA1") });
|
|
@export(&BunObject.SHA224, .{ .name = getterName("SHA224") });
|
|
@export(&BunObject.SHA256, .{ .name = getterName("SHA256") });
|
|
@export(&BunObject.SHA384, .{ .name = getterName("SHA384") });
|
|
@export(&BunObject.SHA512, .{ .name = getterName("SHA512") });
|
|
@export(&BunObject.SHA512_256, .{ .name = getterName("SHA512_256") });
|
|
|
|
@export(&BunObject.TOML, .{ .name = getterName("TOML") });
|
|
@export(&BunObject.Glob, .{ .name = getterName("Glob") });
|
|
@export(&BunObject.Transpiler, .{ .name = getterName("Transpiler") });
|
|
@export(&BunObject.argv, .{ .name = getterName("argv") });
|
|
@export(&BunObject.cwd, .{ .name = getterName("cwd") });
|
|
@export(&BunObject.enableANSIColors, .{ .name = getterName("enableANSIColors") });
|
|
@export(&BunObject.hash, .{ .name = getterName("hash") });
|
|
@export(&BunObject.inspect, .{ .name = getterName("inspect") });
|
|
@export(&BunObject.main, .{ .name = getterName("main") });
|
|
@export(&BunObject.origin, .{ .name = getterName("origin") });
|
|
@export(&BunObject.stderr, .{ .name = getterName("stderr") });
|
|
@export(&BunObject.stdin, .{ .name = getterName("stdin") });
|
|
@export(&BunObject.stdout, .{ .name = getterName("stdout") });
|
|
@export(&BunObject.unsafe, .{ .name = getterName("unsafe") });
|
|
@export(&BunObject.semver, .{ .name = getterName("semver") });
|
|
@export(&BunObject.embeddedFiles, .{ .name = getterName("embeddedFiles") });
|
|
@export(&BunObject.S3Client, .{ .name = getterName("S3Client") });
|
|
@export(&BunObject.s3, .{ .name = getterName("s3") });
|
|
@export(&BunObject.ValkeyClient, .{ .name = getterName("ValkeyClient") });
|
|
@export(&BunObject.valkey, .{ .name = getterName("valkey") });
|
|
// --- Getters --
|
|
|
|
// -- Callbacks --
|
|
@export(&BunObject.allocUnsafe, .{ .name = callbackName("allocUnsafe") });
|
|
@export(&BunObject.build, .{ .name = callbackName("build") });
|
|
@export(&BunObject.color, .{ .name = callbackName("color") });
|
|
@export(&BunObject.connect, .{ .name = callbackName("connect") });
|
|
@export(&BunObject.createParsedShellScript, .{ .name = callbackName("createParsedShellScript") });
|
|
@export(&BunObject.createShellInterpreter, .{ .name = callbackName("createShellInterpreter") });
|
|
@export(&BunObject.deflateSync, .{ .name = callbackName("deflateSync") });
|
|
@export(&BunObject.file, .{ .name = callbackName("file") });
|
|
@export(&BunObject.gunzipSync, .{ .name = callbackName("gunzipSync") });
|
|
@export(&BunObject.gzipSync, .{ .name = callbackName("gzipSync") });
|
|
@export(&BunObject.indexOfLine, .{ .name = callbackName("indexOfLine") });
|
|
@export(&BunObject.inflateSync, .{ .name = callbackName("inflateSync") });
|
|
@export(&BunObject.jest, .{ .name = callbackName("jest") });
|
|
@export(&BunObject.listen, .{ .name = callbackName("listen") });
|
|
@export(&BunObject.mmap, .{ .name = callbackName("mmap") });
|
|
@export(&BunObject.nanoseconds, .{ .name = callbackName("nanoseconds") });
|
|
@export(&BunObject.openInEditor, .{ .name = callbackName("openInEditor") });
|
|
@export(&BunObject.registerMacro, .{ .name = callbackName("registerMacro") });
|
|
@export(&BunObject.resolve, .{ .name = callbackName("resolve") });
|
|
@export(&BunObject.resolveSync, .{ .name = callbackName("resolveSync") });
|
|
@export(&BunObject.serve, .{ .name = callbackName("serve") });
|
|
@export(&BunObject.sha, .{ .name = callbackName("sha") });
|
|
@export(&BunObject.shellEscape, .{ .name = callbackName("shellEscape") });
|
|
@export(&BunObject.shrink, .{ .name = callbackName("shrink") });
|
|
@export(&BunObject.sleepSync, .{ .name = callbackName("sleepSync") });
|
|
@export(&BunObject.spawn, .{ .name = callbackName("spawn") });
|
|
@export(&BunObject.spawnSync, .{ .name = callbackName("spawnSync") });
|
|
@export(&BunObject.udpSocket, .{ .name = callbackName("udpSocket") });
|
|
@export(&BunObject.which, .{ .name = callbackName("which") });
|
|
@export(&BunObject.write, .{ .name = callbackName("write") });
|
|
@export(&BunObject.zstdCompressSync, .{ .name = callbackName("zstdCompressSync") });
|
|
@export(&BunObject.zstdDecompressSync, .{ .name = callbackName("zstdDecompressSync") });
|
|
@export(&BunObject.zstdCompress, .{ .name = callbackName("zstdCompress") });
|
|
@export(&BunObject.zstdDecompress, .{ .name = callbackName("zstdDecompress") });
|
|
// -- Callbacks --
|
|
}
|
|
};
|
|
|
|
pub fn shellEscape(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
|
const arguments = callframe.arguments_old(1);
|
|
if (arguments.len < 1) {
|
|
return globalThis.throw("shell escape expected at least 1 argument", .{});
|
|
}
|
|
|
|
const jsval = arguments.ptr[0];
|
|
const bunstr = try jsval.toBunString(globalThis);
|
|
if (globalThis.hasException()) return .zero;
|
|
defer bunstr.deref();
|
|
|
|
var outbuf = std.ArrayList(u8).init(bun.default_allocator);
|
|
defer outbuf.deinit();
|
|
|
|
if (bun.shell.needsEscapeBunstr(bunstr)) {
|
|
const result = try bun.shell.escapeBunStr(bunstr, &outbuf, true);
|
|
if (!result) {
|
|
return globalThis.throw("String has invalid utf-16: {s}", .{bunstr.byteSlice()});
|
|
}
|
|
var str = bun.String.createUTF8(outbuf.items[0..]);
|
|
return str.transferToJS(globalThis);
|
|
}
|
|
|
|
return jsval;
|
|
}
|
|
|
|
const gen = bun.gen.BunObject;
|
|
|
|
pub fn braces(global: *JSC.JSGlobalObject, brace_str: bun.String, opts: gen.BracesOptions) bun.JSError!JSC.JSValue {
|
|
const brace_slice = brace_str.toUTF8(bun.default_allocator);
|
|
defer brace_slice.deinit();
|
|
|
|
var arena = std.heap.ArenaAllocator.init(bun.default_allocator);
|
|
defer arena.deinit();
|
|
|
|
var lexer_output = Braces.Lexer.tokenize(arena.allocator(), brace_slice.slice()) catch |err| {
|
|
return global.throwError(err, "failed to tokenize braces");
|
|
};
|
|
|
|
const expansion_count = Braces.calculateExpandedAmount(lexer_output.tokens.items[0..]) catch |err| {
|
|
return global.throwError(err, "failed to calculate brace expansion amount");
|
|
};
|
|
|
|
if (opts.tokenize) {
|
|
const str = try std.json.stringifyAlloc(global.bunVM().allocator, lexer_output.tokens.items[0..], .{});
|
|
defer global.bunVM().allocator.free(str);
|
|
var bun_str = bun.String.fromBytes(str);
|
|
return bun_str.toJS(global);
|
|
}
|
|
if (opts.parse) {
|
|
var parser = Braces.Parser.init(lexer_output.tokens.items[0..], arena.allocator());
|
|
const ast_node = parser.parse() catch |err| {
|
|
return global.throwError(err, "failed to parse braces");
|
|
};
|
|
const str = try std.json.stringifyAlloc(global.bunVM().allocator, ast_node, .{});
|
|
defer global.bunVM().allocator.free(str);
|
|
var bun_str = bun.String.fromBytes(str);
|
|
return bun_str.toJS(global);
|
|
}
|
|
|
|
if (expansion_count == 0) {
|
|
return bun.String.toJSArray(global, &.{brace_str});
|
|
}
|
|
|
|
var expanded_strings = try arena.allocator().alloc(std.ArrayList(u8), expansion_count);
|
|
|
|
for (0..expansion_count) |i| {
|
|
expanded_strings[i] = std.ArrayList(u8).init(arena.allocator());
|
|
}
|
|
|
|
Braces.expand(
|
|
arena.allocator(),
|
|
lexer_output.tokens.items[0..],
|
|
expanded_strings,
|
|
lexer_output.contains_nested,
|
|
) catch |err| switch (err) {
|
|
error.OutOfMemory => |e| return e,
|
|
error.UnexpectedToken => return global.throwPretty("Unexpected token while expanding braces", .{}),
|
|
error.StackFull => return global.throwPretty("Too much nesting while expanding braces", .{}),
|
|
};
|
|
|
|
var out_strings = try arena.allocator().alloc(bun.String, expansion_count);
|
|
for (0..expansion_count) |i| {
|
|
out_strings[i] = bun.String.fromBytes(expanded_strings[i].items[0..]);
|
|
}
|
|
|
|
return bun.String.toJSArray(global, out_strings[0..]);
|
|
}
|
|
|
|
pub fn which(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
|
const arguments_ = callframe.arguments_old(2);
|
|
const path_buf = bun.PathBufferPool.get();
|
|
defer bun.PathBufferPool.put(path_buf);
|
|
var arguments = JSC.CallFrame.ArgumentsSlice.init(globalThis.bunVM(), arguments_.slice());
|
|
defer arguments.deinit();
|
|
const path_arg = arguments.nextEat() orelse {
|
|
return globalThis.throw("which: expected 1 argument, got 0", .{});
|
|
};
|
|
|
|
var path_str: ZigString.Slice = ZigString.Slice.empty;
|
|
var bin_str: ZigString.Slice = ZigString.Slice.empty;
|
|
var cwd_str: ZigString.Slice = ZigString.Slice.empty;
|
|
defer {
|
|
path_str.deinit();
|
|
bin_str.deinit();
|
|
cwd_str.deinit();
|
|
}
|
|
|
|
if (path_arg.isEmptyOrUndefinedOrNull()) {
|
|
return JSC.JSValue.jsNull();
|
|
}
|
|
|
|
bin_str = try path_arg.toSlice(globalThis, globalThis.bunVM().allocator);
|
|
if (globalThis.hasException()) {
|
|
return .zero;
|
|
}
|
|
|
|
if (bin_str.len >= bun.MAX_PATH_BYTES) {
|
|
return globalThis.throw("bin path is too long", .{});
|
|
}
|
|
|
|
if (bin_str.len == 0) {
|
|
return JSC.JSValue.jsNull();
|
|
}
|
|
|
|
path_str = ZigString.Slice.fromUTF8NeverFree(
|
|
globalThis.bunVM().transpiler.env.get("PATH") orelse "",
|
|
);
|
|
cwd_str = ZigString.Slice.fromUTF8NeverFree(
|
|
globalThis.bunVM().transpiler.fs.top_level_dir,
|
|
);
|
|
|
|
if (arguments.nextEat()) |arg| {
|
|
if (!arg.isEmptyOrUndefinedOrNull() and arg.isObject()) {
|
|
if (try arg.get(globalThis, "PATH")) |str_| {
|
|
path_str = try str_.toSlice(globalThis, globalThis.bunVM().allocator);
|
|
}
|
|
|
|
if (try arg.get(globalThis, "cwd")) |str_| {
|
|
cwd_str = try str_.toSlice(globalThis, globalThis.bunVM().allocator);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Which.which(
|
|
path_buf,
|
|
path_str.slice(),
|
|
cwd_str.slice(),
|
|
bin_str.slice(),
|
|
)) |bin_path| {
|
|
return ZigString.init(bin_path).withEncoding().toJS(globalThis);
|
|
}
|
|
|
|
return JSC.JSValue.jsNull();
|
|
}
|
|
|
|
pub fn inspectTable(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
|
var args_buf = callframe.argumentsUndef(5);
|
|
var all_arguments = args_buf.mut();
|
|
if (all_arguments[0].isUndefinedOrNull() or !all_arguments[0].isObject())
|
|
return bun.String.empty.toJS(globalThis);
|
|
|
|
for (all_arguments) |arg| {
|
|
arg.protect();
|
|
}
|
|
defer {
|
|
for (all_arguments) |arg| {
|
|
arg.unprotect();
|
|
}
|
|
}
|
|
|
|
var arguments = all_arguments[0..];
|
|
const value = arguments[0];
|
|
|
|
if (!arguments[1].isArray()) {
|
|
arguments[2] = arguments[1];
|
|
arguments[1] = .js_undefined;
|
|
}
|
|
|
|
var formatOptions = ConsoleObject.FormatOptions{
|
|
.enable_colors = false,
|
|
.add_newline = false,
|
|
.flush = false,
|
|
.max_depth = 5,
|
|
.quote_strings = true,
|
|
.ordered_properties = false,
|
|
.single_line = true,
|
|
};
|
|
if (arguments[2].isObject()) {
|
|
try formatOptions.fromJS(globalThis, arguments[2..]);
|
|
}
|
|
|
|
// very stable memory address
|
|
var array = MutableString.init(bun.default_allocator, 0) catch bun.outOfMemory();
|
|
defer array.deinit();
|
|
var buffered_writer_ = MutableString.BufferedWriter{ .context = &array };
|
|
var buffered_writer = &buffered_writer_;
|
|
|
|
const writer = buffered_writer.writer();
|
|
const Writer = @TypeOf(writer);
|
|
const properties: JSValue = if (arguments[1].jsType().isArray()) arguments[1] else .js_undefined;
|
|
var table_printer = try ConsoleObject.TablePrinter.init(
|
|
globalThis,
|
|
.Log,
|
|
value,
|
|
properties,
|
|
);
|
|
table_printer.value_formatter.depth = formatOptions.max_depth;
|
|
table_printer.value_formatter.ordered_properties = formatOptions.ordered_properties;
|
|
table_printer.value_formatter.single_line = formatOptions.single_line;
|
|
|
|
switch (formatOptions.enable_colors) {
|
|
inline else => |colors| table_printer.printTable(Writer, writer, colors) catch {
|
|
if (!globalThis.hasException())
|
|
return globalThis.throwOutOfMemory();
|
|
return .zero;
|
|
},
|
|
}
|
|
|
|
try buffered_writer.flush();
|
|
|
|
return bun.String.createUTF8ForJS(globalThis, array.slice());
|
|
}
|
|
|
|
pub fn inspect(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
|
const arguments = callframe.arguments_old(4).slice();
|
|
if (arguments.len == 0)
|
|
return bun.String.empty.toJS(globalThis);
|
|
|
|
for (arguments) |arg| {
|
|
arg.protect();
|
|
}
|
|
defer {
|
|
for (arguments) |arg| {
|
|
arg.unprotect();
|
|
}
|
|
}
|
|
|
|
var formatOptions = ConsoleObject.FormatOptions{
|
|
.enable_colors = false,
|
|
.add_newline = false,
|
|
.flush = false,
|
|
.max_depth = 8,
|
|
.quote_strings = true,
|
|
.ordered_properties = false,
|
|
};
|
|
if (arguments.len > 1) {
|
|
try formatOptions.fromJS(globalThis, arguments[1..]);
|
|
}
|
|
|
|
// very stable memory address
|
|
var array = MutableString.init(bun.default_allocator, 0) catch unreachable;
|
|
defer array.deinit();
|
|
var buffered_writer_ = MutableString.BufferedWriter{ .context = &array };
|
|
var buffered_writer = &buffered_writer_;
|
|
|
|
const writer = buffered_writer.writer();
|
|
const Writer = MutableString.BufferedWriter.Writer;
|
|
// we buffer this because it'll almost always be < 4096
|
|
// when it's under 4096, we want to avoid the dynamic allocation
|
|
try ConsoleObject.format2(
|
|
.Debug,
|
|
globalThis,
|
|
arguments.ptr,
|
|
1,
|
|
Writer,
|
|
Writer,
|
|
writer,
|
|
formatOptions,
|
|
);
|
|
if (globalThis.hasException()) return error.JSError;
|
|
buffered_writer.flush() catch return globalThis.throwOutOfMemory();
|
|
|
|
// 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.slice()).withEncoding();
|
|
const ret = out.toJS(globalThis);
|
|
|
|
return ret;
|
|
}
|
|
|
|
export fn Bun__inspect(globalThis: *JSGlobalObject, value: JSValue) bun.String {
|
|
// very stable memory address
|
|
var array = MutableString.init(bun.default_allocator, 0) catch unreachable;
|
|
defer array.deinit();
|
|
var buffered_writer = MutableString.BufferedWriter{ .context = &array };
|
|
const writer = buffered_writer.writer();
|
|
|
|
var formatter = ConsoleObject.Formatter{ .globalThis = globalThis };
|
|
defer formatter.deinit();
|
|
writer.print("{}", .{value.toFmt(&formatter)}) catch return .empty;
|
|
buffered_writer.flush() catch return .empty;
|
|
return bun.String.createUTF8(array.slice());
|
|
}
|
|
|
|
export fn Bun__inspect_singleline(globalThis: *JSGlobalObject, value: JSValue) bun.String {
|
|
var array = MutableString.init(bun.default_allocator, 0) catch unreachable;
|
|
defer array.deinit();
|
|
var buffered_writer = MutableString.BufferedWriter{ .context = &array };
|
|
const writer = buffered_writer.writer();
|
|
const Writer = MutableString.BufferedWriter.Writer;
|
|
ConsoleObject.format2(.Debug, globalThis, (&value)[0..1].ptr, 1, Writer, Writer, writer, .{
|
|
.enable_colors = false,
|
|
.add_newline = false,
|
|
.flush = false,
|
|
.max_depth = std.math.maxInt(u16),
|
|
.quote_strings = true,
|
|
.ordered_properties = false,
|
|
.single_line = true,
|
|
}) catch return .empty;
|
|
if (globalThis.hasException()) return .empty;
|
|
buffered_writer.flush() catch return .empty;
|
|
return bun.String.createUTF8(array.slice());
|
|
}
|
|
|
|
pub fn getInspect(globalObject: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
|
|
const fun = JSC.createCallback(globalObject, ZigString.static("inspect"), 2, inspect);
|
|
var str = ZigString.init("nodejs.util.inspect.custom");
|
|
fun.put(globalObject, ZigString.static("custom"), JSC.JSValue.symbolFor(globalObject, &str));
|
|
fun.put(globalObject, ZigString.static("table"), JSC.createCallback(globalObject, ZigString.static("table"), 3, inspectTable));
|
|
return fun;
|
|
}
|
|
|
|
pub fn registerMacro(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
|
const arguments_ = callframe.arguments_old(2);
|
|
const arguments = arguments_.slice();
|
|
if (arguments.len != 2 or !arguments[0].isNumber()) {
|
|
return globalObject.throwInvalidArguments("Internal error registering macros: invalid args", .{});
|
|
}
|
|
const id = arguments[0].toInt32();
|
|
if (id == -1 or id == 0) {
|
|
return globalObject.throwInvalidArguments("Internal error registering macros: invalid id", .{});
|
|
}
|
|
|
|
if (!arguments[1].isCell() or !arguments[1].isCallable()) {
|
|
// TODO: add "toTypeOf" helper
|
|
return globalObject.throw("Macro must be a function", .{});
|
|
}
|
|
|
|
const get_or_put_result = VirtualMachine.get().macros.getOrPut(id) catch unreachable;
|
|
if (get_or_put_result.found_existing) {
|
|
get_or_put_result.value_ptr.*.?.value().unprotect();
|
|
}
|
|
|
|
arguments[1].protect();
|
|
get_or_put_result.value_ptr.* = arguments[1].asObjectRef();
|
|
|
|
return .js_undefined;
|
|
}
|
|
|
|
pub fn getCWD(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
|
|
return ZigString.init(VirtualMachine.get().transpiler.fs.top_level_dir).toJS(globalThis);
|
|
}
|
|
|
|
pub fn getOrigin(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
|
|
return ZigString.init(VirtualMachine.get().origin.origin).toJS(globalThis);
|
|
}
|
|
|
|
pub fn getStdin(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
|
|
var rare_data = globalThis.bunVM().rareData();
|
|
var store = rare_data.stdin();
|
|
store.ref();
|
|
var blob = JSC.WebCore.Blob.new(
|
|
JSC.WebCore.Blob.initWithStore(store, globalThis),
|
|
);
|
|
blob.allocator = bun.default_allocator;
|
|
return blob.toJS(globalThis);
|
|
}
|
|
|
|
pub fn getStderr(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
|
|
var rare_data = globalThis.bunVM().rareData();
|
|
var store = rare_data.stderr();
|
|
store.ref();
|
|
var blob = JSC.WebCore.Blob.new(
|
|
JSC.WebCore.Blob.initWithStore(store, globalThis),
|
|
);
|
|
blob.allocator = bun.default_allocator;
|
|
return blob.toJS(globalThis);
|
|
}
|
|
|
|
pub fn getStdout(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
|
|
var rare_data = globalThis.bunVM().rareData();
|
|
var store = rare_data.stdout();
|
|
store.ref();
|
|
var blob = JSC.WebCore.Blob.new(
|
|
JSC.WebCore.Blob.initWithStore(store, globalThis),
|
|
);
|
|
blob.allocator = bun.default_allocator;
|
|
return blob.toJS(globalThis);
|
|
}
|
|
|
|
pub fn enableANSIColors(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
|
|
_ = globalThis;
|
|
return JSValue.jsBoolean(Output.enable_ansi_colors);
|
|
}
|
|
|
|
pub fn getMain(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
|
|
const vm = globalThis.bunVM();
|
|
|
|
// Attempt to use the resolved filesystem path
|
|
// This makes `eval('require.main === module')` work when the main module is a symlink.
|
|
// This behavior differs slightly from Node. Node sets the `id` to `.` when the main module is a symlink.
|
|
use_resolved_path: {
|
|
if (vm.main_resolved_path.isEmpty()) {
|
|
// If it's from eval, don't try to resolve it.
|
|
if (strings.hasSuffixComptime(vm.main, "[eval]")) {
|
|
break :use_resolved_path;
|
|
}
|
|
if (strings.hasSuffixComptime(vm.main, "[stdin]")) {
|
|
break :use_resolved_path;
|
|
}
|
|
|
|
const fd = bun.sys.openatA(
|
|
if (comptime Environment.isWindows) bun.invalid_fd else bun.FD.cwd(),
|
|
vm.main,
|
|
|
|
// Open with the minimum permissions necessary for resolving the file path.
|
|
if (comptime Environment.isLinux) bun.O.PATH else bun.O.RDONLY,
|
|
|
|
0,
|
|
).unwrap() catch break :use_resolved_path;
|
|
|
|
defer fd.close();
|
|
if (comptime Environment.isWindows) {
|
|
var wpath: bun.WPathBuffer = undefined;
|
|
const fdpath = bun.getFdPathW(fd, &wpath) catch break :use_resolved_path;
|
|
vm.main_resolved_path = bun.String.createUTF16(fdpath);
|
|
} else {
|
|
var path: bun.PathBuffer = undefined;
|
|
const fdpath = bun.getFdPath(fd, &path) catch break :use_resolved_path;
|
|
|
|
// Bun.main === otherId will be compared many times, so let's try to create an atom string if we can.
|
|
if (bun.String.tryCreateAtom(fdpath)) |atom| {
|
|
vm.main_resolved_path = atom;
|
|
} else {
|
|
vm.main_resolved_path = bun.String.createUTF8(fdpath);
|
|
}
|
|
}
|
|
}
|
|
|
|
return vm.main_resolved_path.toJS(globalThis);
|
|
}
|
|
|
|
return ZigString.init(vm.main).toJS(globalThis);
|
|
}
|
|
|
|
pub fn getArgv(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
|
|
return node.process.getArgv(globalThis);
|
|
}
|
|
|
|
const Editor = @import("../../open.zig").Editor;
|
|
|
|
pub fn openInEditor(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
|
|
var edit = &VirtualMachine.get().rareData().editor_context;
|
|
const args = callframe.arguments_old(4);
|
|
var arguments = JSC.CallFrame.ArgumentsSlice.init(globalThis.bunVM(), args.slice());
|
|
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 = (try file_path_.toSlice(globalThis, arguments.arena.allocator())).slice();
|
|
}
|
|
|
|
if (arguments.nextEat()) |opts| {
|
|
if (!opts.isUndefinedOrNull()) {
|
|
if (try opts.getTruthy(globalThis, "editor")) |editor_val| {
|
|
var sliced = try editor_val.toSlice(globalThis, arguments.arena.allocator());
|
|
const prev_name = edit.name;
|
|
|
|
if (!strings.eqlLong(prev_name, sliced.slice(), true)) {
|
|
const prev = edit.*;
|
|
edit.name = sliced.slice();
|
|
edit.detectEditor(VirtualMachine.get().transpiler.env);
|
|
editor_choice = edit.editor;
|
|
if (editor_choice == null) {
|
|
edit.* = prev;
|
|
return globalThis.throw("Could not find editor \"{s}\"", .{sliced.slice()});
|
|
} else if (edit.name.ptr == edit.path.ptr) {
|
|
edit.name = arguments.arena.allocator().dupe(u8, edit.path) catch unreachable;
|
|
edit.path = edit.path;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (try opts.getTruthy(globalThis, "line")) |line_| {
|
|
line = (try line_.toSlice(globalThis, arguments.arena.allocator())).slice();
|
|
}
|
|
|
|
if (try opts.getTruthy(globalThis, "column")) |column_| {
|
|
column = (try column_.toSlice(globalThis, arguments.arena.allocator())).slice();
|
|
}
|
|
}
|
|
}
|
|
|
|
const editor = editor_choice orelse edit.editor orelse brk: {
|
|
edit.autoDetectEditor(VirtualMachine.get().transpiler.env);
|
|
if (edit.editor == null) {
|
|
return globalThis.throw("Failed to auto-detect editor", .{});
|
|
}
|
|
|
|
break :brk edit.editor.?;
|
|
};
|
|
|
|
if (path.len == 0) {
|
|
return globalThis.throw("No file path specified", .{});
|
|
}
|
|
|
|
editor.open(edit.path, path, line, column, arguments.arena.allocator()) catch |err| {
|
|
return globalThis.throw("Opening editor failed {s}", .{@errorName(err)});
|
|
};
|
|
|
|
return .js_undefined;
|
|
}
|
|
|
|
pub fn getPublicPath(to: string, origin: URL, comptime Writer: type, writer: Writer) void {
|
|
return getPublicPathWithAssetPrefix(
|
|
to,
|
|
VirtualMachine.get().transpiler.fs.top_level_dir,
|
|
origin,
|
|
"",
|
|
comptime Writer,
|
|
writer,
|
|
.loose,
|
|
);
|
|
}
|
|
|
|
pub fn getPublicPathWithAssetPrefix(
|
|
to: string,
|
|
dir: string,
|
|
origin: URL,
|
|
asset_prefix: string,
|
|
comptime Writer: type,
|
|
writer: Writer,
|
|
comptime platform: bun.path.Platform,
|
|
) void {
|
|
const relative_path = if (strings.hasPrefix(to, dir))
|
|
strings.withoutTrailingSlash(to[dir.len..])
|
|
else
|
|
VirtualMachine.get().transpiler.fs.relativePlatform(dir, to, platform);
|
|
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.get().transpiler.fs.abs(&[_]string{to})) catch return;
|
|
}
|
|
} else {
|
|
origin.joinWrite(
|
|
Writer,
|
|
writer,
|
|
asset_prefix,
|
|
"",
|
|
relative_path,
|
|
"",
|
|
) catch return;
|
|
}
|
|
} else {
|
|
writer.writeAll(std.mem.trimLeft(u8, relative_path, "/")) catch unreachable;
|
|
}
|
|
}
|
|
|
|
pub fn sleepSync(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
|
const arguments = callframe.arguments_old(1);
|
|
|
|
// Expect at least one argument. We allow more than one but ignore them; this
|
|
// is useful for supporting things like `[1, 2].map(sleepSync)`
|
|
if (arguments.len < 1) {
|
|
return globalObject.throwNotEnoughArguments("sleepSync", 1, 0);
|
|
}
|
|
const arg = arguments.slice()[0];
|
|
|
|
// The argument must be a number
|
|
if (!arg.isNumber()) {
|
|
return globalObject.throwInvalidArgumentType("sleepSync", "milliseconds", "number");
|
|
}
|
|
|
|
//NOTE: if argument is > max(i32) then it will be truncated
|
|
const milliseconds = arg.coerce(i32, globalObject);
|
|
if (milliseconds < 0) {
|
|
return globalObject.throwInvalidArguments("argument to sleepSync must not be negative, got {d}", .{milliseconds});
|
|
}
|
|
|
|
std.time.sleep(@as(u64, @intCast(milliseconds)) * std.time.ns_per_ms);
|
|
return .js_undefined;
|
|
}
|
|
|
|
pub fn gc(vm: *JSC.VirtualMachine, sync: bool) usize {
|
|
return vm.garbageCollect(sync);
|
|
}
|
|
export fn Bun__gc(vm: *JSC.VirtualMachine, sync: bool) callconv(.C) usize {
|
|
return @call(.always_inline, gc, .{ vm, sync });
|
|
}
|
|
|
|
pub fn shrink(globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
|
globalObject.vm().shrinkFootprint();
|
|
return .js_undefined;
|
|
}
|
|
|
|
fn doResolve(globalThis: *JSC.JSGlobalObject, arguments: []const JSValue) bun.JSError!JSC.JSValue {
|
|
var args = JSC.CallFrame.ArgumentsSlice.init(globalThis.bunVM(), arguments);
|
|
defer args.deinit();
|
|
const specifier = args.protectEatNext() orelse {
|
|
return globalThis.throwInvalidArguments("Expected a specifier and a from path", .{});
|
|
};
|
|
|
|
if (specifier.isUndefinedOrNull()) {
|
|
return globalThis.throwInvalidArguments("specifier must be a string", .{});
|
|
}
|
|
|
|
const from = args.protectEatNext() orelse {
|
|
return globalThis.throwInvalidArguments("Expected a from path", .{});
|
|
};
|
|
|
|
if (from.isUndefinedOrNull()) {
|
|
return globalThis.throwInvalidArguments("from must be a string", .{});
|
|
}
|
|
|
|
var is_esm = true;
|
|
if (args.nextEat()) |next| {
|
|
if (next.isBoolean()) {
|
|
is_esm = next.toBoolean();
|
|
} else {
|
|
return globalThis.throwInvalidArguments("esm must be a boolean", .{});
|
|
}
|
|
}
|
|
|
|
const specifier_str = try specifier.toBunString(globalThis);
|
|
defer specifier_str.deref();
|
|
const from_str = try from.toBunString(globalThis);
|
|
defer from_str.deref();
|
|
return doResolveWithArgs(
|
|
globalThis,
|
|
specifier_str,
|
|
from_str,
|
|
is_esm,
|
|
false,
|
|
false,
|
|
);
|
|
}
|
|
|
|
fn doResolveWithArgs(ctx: *JSC.JSGlobalObject, specifier: bun.String, from: bun.String, is_esm: bool, comptime is_file_path: bool, is_user_require_resolve: bool) bun.JSError!JSC.JSValue {
|
|
var errorable: ErrorableString = undefined;
|
|
var query_string = ZigString.Empty;
|
|
|
|
const specifier_decoded = if (specifier.hasPrefixComptime("file://"))
|
|
bun.JSC.URL.pathFromFileURL(specifier)
|
|
else
|
|
specifier.dupeRef();
|
|
defer specifier_decoded.deref();
|
|
|
|
try VirtualMachine.resolveMaybeNeedsTrailingSlash(
|
|
&errorable,
|
|
ctx,
|
|
specifier_decoded,
|
|
from,
|
|
&query_string,
|
|
is_esm,
|
|
is_file_path,
|
|
is_user_require_resolve,
|
|
);
|
|
|
|
if (!errorable.success) {
|
|
return ctx.throwValue(errorable.result.err.value);
|
|
}
|
|
|
|
if (query_string.len > 0) {
|
|
var stack = std.heap.stackFallback(1024, ctx.allocator());
|
|
const allocator = stack.get();
|
|
var arraylist = std.ArrayList(u8).initCapacity(allocator, 1024) catch unreachable;
|
|
defer arraylist.deinit();
|
|
try arraylist.writer().print("{any}{any}", .{
|
|
errorable.result.value,
|
|
query_string,
|
|
});
|
|
|
|
return ZigString.initUTF8(arraylist.items).toJS(ctx);
|
|
}
|
|
|
|
return errorable.result.value.toJS(ctx);
|
|
}
|
|
|
|
pub fn resolveSync(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
|
return try doResolve(globalObject, callframe.arguments());
|
|
}
|
|
|
|
pub fn resolve(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
|
const arguments = callframe.arguments_old(3);
|
|
const value = doResolve(globalObject, arguments.slice()) catch {
|
|
const err = globalObject.tryTakeException().?;
|
|
return JSC.JSPromise.dangerouslyCreateRejectedPromiseValueWithoutNotifyingVM(globalObject, err);
|
|
};
|
|
return JSC.JSPromise.resolvedPromiseValue(globalObject, value);
|
|
}
|
|
|
|
export fn Bun__resolve(global: *JSGlobalObject, specifier: JSValue, source: JSValue, is_esm: bool) JSC.JSValue {
|
|
const specifier_str = specifier.toBunString(global) catch return .zero;
|
|
defer specifier_str.deref();
|
|
|
|
const source_str = source.toBunString(global) catch return .zero;
|
|
defer source_str.deref();
|
|
|
|
const value = doResolveWithArgs(global, specifier_str, source_str, is_esm, true, false) catch {
|
|
const err = global.tryTakeException().?;
|
|
return JSC.JSPromise.dangerouslyCreateRejectedPromiseValueWithoutNotifyingVM(global, err);
|
|
};
|
|
|
|
return JSC.JSPromise.resolvedPromiseValue(global, value);
|
|
}
|
|
|
|
export fn Bun__resolveSync(global: *JSGlobalObject, specifier: JSValue, source: JSValue, is_esm: bool, is_user_require_resolve: bool) JSC.JSValue {
|
|
const specifier_str = specifier.toBunString(global) catch return .zero;
|
|
defer specifier_str.deref();
|
|
|
|
if (specifier_str.length() == 0) {
|
|
return global.ERR(.INVALID_ARG_VALUE, "The argument 'id' must be a non-empty string. Received ''", .{}).throw() catch .zero;
|
|
}
|
|
|
|
const source_str = source.toBunString(global) catch return .zero;
|
|
defer source_str.deref();
|
|
|
|
return JSC.toJSHostCall(global, @src(), doResolveWithArgs, .{ global, specifier_str, source_str, is_esm, true, is_user_require_resolve });
|
|
}
|
|
|
|
export fn Bun__resolveSyncWithPaths(
|
|
global: *JSGlobalObject,
|
|
specifier: JSValue,
|
|
source: JSValue,
|
|
is_esm: bool,
|
|
is_user_require_resolve: bool,
|
|
paths_ptr: ?[*]const bun.String,
|
|
paths_len: usize,
|
|
) JSC.JSValue {
|
|
const paths: []const bun.String = if (paths_len == 0) &.{} else paths_ptr.?[0..paths_len];
|
|
|
|
const specifier_str = specifier.toBunString(global) catch return .zero;
|
|
defer specifier_str.deref();
|
|
|
|
if (specifier_str.length() == 0) {
|
|
return global.ERR(.INVALID_ARG_VALUE, "The argument 'id' must be a non-empty string. Received ''", .{}).throw() catch .zero;
|
|
}
|
|
|
|
const source_str = source.toBunString(global) catch return .zero;
|
|
defer source_str.deref();
|
|
|
|
const bun_vm = global.bunVM();
|
|
bun.assert(bun_vm.transpiler.resolver.custom_dir_paths == null);
|
|
bun_vm.transpiler.resolver.custom_dir_paths = paths;
|
|
defer bun_vm.transpiler.resolver.custom_dir_paths = null;
|
|
|
|
return JSC.toJSHostCall(global, @src(), doResolveWithArgs, .{ global, specifier_str, source_str, is_esm, true, is_user_require_resolve });
|
|
}
|
|
|
|
export fn Bun__resolveSyncWithStrings(global: *JSGlobalObject, specifier: *bun.String, source: *bun.String, is_esm: bool) JSC.JSValue {
|
|
Output.scoped(.importMetaResolve, false)("source: {s}, specifier: {s}", .{ source.*, specifier.* });
|
|
return JSC.toJSHostCall(global, @src(), doResolveWithArgs, .{ global, specifier.*, source.*, is_esm, true, false });
|
|
}
|
|
|
|
export fn Bun__resolveSyncWithSource(global: *JSGlobalObject, specifier: JSValue, source: *bun.String, is_esm: bool, is_user_require_resolve: bool) JSC.JSValue {
|
|
const specifier_str = specifier.toBunString(global) catch return .zero;
|
|
defer specifier_str.deref();
|
|
if (specifier_str.length() == 0) {
|
|
return global.ERR(.INVALID_ARG_VALUE, "The argument 'id' must be a non-empty string. Received ''", .{}).throw() catch .zero;
|
|
}
|
|
return JSC.toJSHostCall(global, @src(), doResolveWithArgs, .{ global, specifier_str, source.*, is_esm, true, is_user_require_resolve });
|
|
}
|
|
|
|
pub fn indexOfLine(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
|
const arguments_ = callframe.arguments_old(2);
|
|
const arguments = arguments_.slice();
|
|
if (arguments.len == 0) {
|
|
return JSC.JSValue.jsNumberFromInt32(-1);
|
|
}
|
|
|
|
var buffer = arguments[0].asArrayBuffer(globalThis) orelse {
|
|
return JSC.JSValue.jsNumberFromInt32(-1);
|
|
};
|
|
|
|
var offset: usize = 0;
|
|
if (arguments.len > 1) {
|
|
offset = @as(
|
|
usize,
|
|
@intCast(@max(
|
|
arguments[1].to(u32),
|
|
0,
|
|
)),
|
|
);
|
|
}
|
|
|
|
const bytes = buffer.byteSlice();
|
|
var current_offset = offset;
|
|
const end = @as(u32, @truncate(bytes.len));
|
|
|
|
while (current_offset < end) {
|
|
if (strings.indexOfNewlineOrNonASCII(bytes, @as(u32, @truncate(current_offset)))) |i| {
|
|
const byte = bytes[i];
|
|
if (byte > 0x7F) {
|
|
current_offset += @max(strings.wtf8ByteSequenceLength(byte), 1);
|
|
continue;
|
|
}
|
|
|
|
if (byte == '\n') {
|
|
return JSC.JSValue.jsNumber(i);
|
|
}
|
|
|
|
current_offset = i + 1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return JSC.JSValue.jsNumberFromInt32(-1);
|
|
}
|
|
|
|
pub const Crypto = @import("./crypto.zig");
|
|
|
|
pub fn nanoseconds(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
|
const ns = globalThis.bunVM().origin_timer.read();
|
|
return JSC.JSValue.jsNumberFromUint64(ns);
|
|
}
|
|
|
|
pub fn serve(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
|
const arguments = callframe.arguments_old(2).slice();
|
|
var config: JSC.API.ServerConfig = brk: {
|
|
var args = JSC.CallFrame.ArgumentsSlice.init(globalObject.bunVM(), arguments);
|
|
var config: JSC.API.ServerConfig = .{};
|
|
|
|
try JSC.API.ServerConfig.fromJS(
|
|
globalObject,
|
|
&config,
|
|
&args,
|
|
.{
|
|
.allow_bake_config = bun.FeatureFlags.bake() and callframe.isFromBunMain(globalObject.vm()),
|
|
.is_fetch_required = true,
|
|
.has_user_routes = false,
|
|
},
|
|
);
|
|
|
|
if (globalObject.hasException()) {
|
|
config.deinit();
|
|
return .zero;
|
|
}
|
|
|
|
break :brk config;
|
|
};
|
|
|
|
const vm = globalObject.bunVM();
|
|
|
|
if (config.allow_hot) {
|
|
if (vm.hotMap()) |hot| {
|
|
if (config.id.len == 0) {
|
|
config.id = config.computeID(globalObject.allocator());
|
|
}
|
|
|
|
if (hot.getEntry(config.id)) |entry| {
|
|
switch (entry.tag()) {
|
|
@field(@TypeOf(entry.tag()), @typeName(JSC.API.HTTPServer)) => {
|
|
var server: *JSC.API.HTTPServer = entry.as(JSC.API.HTTPServer);
|
|
server.onReloadFromZig(&config, globalObject);
|
|
return server.js_value.get() orelse .js_undefined;
|
|
},
|
|
@field(@TypeOf(entry.tag()), @typeName(JSC.API.DebugHTTPServer)) => {
|
|
var server: *JSC.API.DebugHTTPServer = entry.as(JSC.API.DebugHTTPServer);
|
|
server.onReloadFromZig(&config, globalObject);
|
|
return server.js_value.get() orelse .js_undefined;
|
|
},
|
|
@field(@TypeOf(entry.tag()), @typeName(JSC.API.DebugHTTPSServer)) => {
|
|
var server: *JSC.API.DebugHTTPSServer = entry.as(JSC.API.DebugHTTPSServer);
|
|
server.onReloadFromZig(&config, globalObject);
|
|
return server.js_value.get() orelse .js_undefined;
|
|
},
|
|
@field(@TypeOf(entry.tag()), @typeName(JSC.API.HTTPSServer)) => {
|
|
var server: *JSC.API.HTTPSServer = entry.as(JSC.API.HTTPSServer);
|
|
server.onReloadFromZig(&config, globalObject);
|
|
return server.js_value.get() orelse .js_undefined;
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (config.ssl_config != null) {
|
|
inline else => |has_ssl_config| {
|
|
switch (config.isDevelopment()) {
|
|
inline else => |development| {
|
|
const ServerType = comptime switch (development) {
|
|
true => switch (has_ssl_config) {
|
|
true => JSC.API.DebugHTTPSServer,
|
|
false => JSC.API.DebugHTTPServer,
|
|
},
|
|
false => switch (has_ssl_config) {
|
|
true => JSC.API.HTTPSServer,
|
|
false => JSC.API.HTTPServer,
|
|
},
|
|
};
|
|
|
|
var server = try ServerType.init(&config, globalObject);
|
|
if (globalObject.hasException()) {
|
|
return .zero;
|
|
}
|
|
const route_list_object = server.listen();
|
|
if (globalObject.hasException()) {
|
|
return .zero;
|
|
}
|
|
const obj = server.toJS(globalObject);
|
|
if (route_list_object != .zero) {
|
|
ServerType.js.routeListSetCached(obj, globalObject, route_list_object);
|
|
}
|
|
server.js_value.set(globalObject, obj);
|
|
|
|
if (config.allow_hot) {
|
|
if (globalObject.bunVM().hotMap()) |hot| {
|
|
hot.insert(config.id, server);
|
|
}
|
|
}
|
|
|
|
if (vm.debugger) |*debugger| {
|
|
debugger.http_server_agent.notifyServerStarted(
|
|
JSC.API.AnyServer.from(server),
|
|
);
|
|
debugger.http_server_agent.notifyServerRoutesUpdated(
|
|
JSC.API.AnyServer.from(server),
|
|
) catch bun.outOfMemory();
|
|
}
|
|
|
|
return obj;
|
|
},
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
pub export fn Bun__escapeHTML16(globalObject: *JSC.JSGlobalObject, input_value: JSValue, ptr: [*]const u16, len: usize) JSValue {
|
|
assert(len > 0);
|
|
const input_slice = ptr[0..len];
|
|
const escaped = strings.escapeHTMLForUTF16Input(globalObject.bunVM().allocator, input_slice) catch {
|
|
return globalObject.throwValue(bun.String.static("Out of memory").toJS(globalObject)) catch .zero;
|
|
};
|
|
|
|
return switch (escaped) {
|
|
.static => |val| ZigString.init(val).toJS(globalObject),
|
|
.original => input_value,
|
|
.allocated => |escaped_html| ZigString.from16(escaped_html.ptr, escaped_html.len).toExternalValue(globalObject),
|
|
};
|
|
}
|
|
|
|
pub export fn Bun__escapeHTML8(globalObject: *JSC.JSGlobalObject, input_value: JSValue, ptr: [*]const u8, len: usize) JSValue {
|
|
assert(len > 0);
|
|
|
|
const input_slice = ptr[0..len];
|
|
var stack_allocator = std.heap.stackFallback(256, globalObject.bunVM().allocator);
|
|
const allocator = if (input_slice.len <= 32) stack_allocator.get() else stack_allocator.fallback_allocator;
|
|
|
|
const escaped = strings.escapeHTMLForLatin1Input(allocator, input_slice) catch {
|
|
return globalObject.throwValue(bun.String.static("Out of memory").toJS(globalObject)) catch .zero;
|
|
};
|
|
|
|
switch (escaped) {
|
|
.static => |val| {
|
|
return ZigString.init(val).toJS(globalObject);
|
|
},
|
|
.original => return input_value,
|
|
.allocated => |escaped_html| {
|
|
if (comptime Environment.allow_assert) {
|
|
// the output should always be longer than the input
|
|
assert(escaped_html.len > input_slice.len);
|
|
|
|
// assert we do not allocate a new string unnecessarily
|
|
assert(
|
|
!std.mem.eql(
|
|
u8,
|
|
input_slice,
|
|
escaped_html,
|
|
),
|
|
);
|
|
}
|
|
|
|
if (input_slice.len <= 32) {
|
|
const zig_str = ZigString.init(escaped_html);
|
|
const out = zig_str.toAtomicValue(globalObject);
|
|
return out;
|
|
}
|
|
|
|
return ZigString.init(escaped_html).toExternalValue(globalObject);
|
|
},
|
|
}
|
|
}
|
|
|
|
comptime {
|
|
_ = Bun__escapeHTML8;
|
|
_ = Bun__escapeHTML16;
|
|
}
|
|
|
|
pub fn allocUnsafe(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
|
const arguments = callframe.arguments_old(1);
|
|
const size = arguments.ptr[0];
|
|
if (!size.isUInt32AsAnyInt()) {
|
|
return globalThis.throwInvalidArguments("Expected a positive number", .{});
|
|
}
|
|
|
|
return JSC.JSValue.createUninitializedUint8Array(globalThis, size.toUInt64NoTruncate());
|
|
}
|
|
|
|
pub fn mmapFile(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
|
if (comptime Environment.isWindows) {
|
|
return globalThis.throwTODO("mmapFile is not supported on Windows");
|
|
}
|
|
|
|
const arguments_ = callframe.arguments_old(2);
|
|
var args = JSC.CallFrame.ArgumentsSlice.init(globalThis.bunVM(), arguments_.slice());
|
|
defer args.deinit();
|
|
|
|
var buf: bun.PathBuffer = undefined;
|
|
const path = brk: {
|
|
if (args.nextEat()) |path| {
|
|
if (path.isString()) {
|
|
const path_str = try path.toSlice(globalThis, args.arena.allocator());
|
|
if (path_str.len > bun.MAX_PATH_BYTES) {
|
|
return globalThis.throwInvalidArguments("Path too long", .{});
|
|
}
|
|
const paths = &[_]string{path_str.slice()};
|
|
break :brk bun.path.joinAbsStringBuf(bun.fs.FileSystem.instance.top_level_dir, &buf, paths, .auto);
|
|
}
|
|
}
|
|
return globalThis.throwInvalidArguments("Expected a path", .{});
|
|
};
|
|
|
|
buf[path.len] = 0;
|
|
|
|
const buf_z: [:0]const u8 = buf[0..path.len :0];
|
|
|
|
var flags: std.c.MAP = .{ .TYPE = .SHARED };
|
|
|
|
// Conforming applications must specify either MAP_PRIVATE or MAP_SHARED.
|
|
var offset: usize = 0;
|
|
var map_size: ?usize = null;
|
|
|
|
if (args.nextEat()) |opts| {
|
|
flags.TYPE = if ((try opts.get(globalThis, "shared") orelse JSValue.true).toBoolean())
|
|
.SHARED
|
|
else
|
|
.PRIVATE;
|
|
|
|
if (@hasField(std.c.MAP, "SYNC")) {
|
|
if ((try opts.get(globalThis, "sync") orelse JSValue.false).toBoolean()) {
|
|
flags.TYPE = .SHARED_VALIDATE;
|
|
flags.SYNC = true;
|
|
}
|
|
}
|
|
|
|
if (try opts.get(globalThis, "size")) |value| {
|
|
map_size = @as(usize, @intCast(value.toInt64()));
|
|
}
|
|
|
|
if (try opts.get(globalThis, "offset")) |value| {
|
|
offset = @as(usize, @intCast(value.toInt64()));
|
|
offset = std.mem.alignBackwardAnyAlign(usize, offset, std.heap.pageSize());
|
|
}
|
|
}
|
|
|
|
const map = switch (bun.sys.mmapFile(buf_z, flags, map_size, offset)) {
|
|
.result => |map| map,
|
|
|
|
.err => |err| {
|
|
return globalThis.throwValue(err.toJSC(globalThis));
|
|
},
|
|
};
|
|
|
|
return JSC.C.JSObjectMakeTypedArrayWithBytesNoCopy(globalThis, JSC.C.JSTypedArrayType.kJSTypedArrayTypeUint8Array, @as(?*anyopaque, @ptrCast(map.ptr)), map.len, struct {
|
|
pub fn x(ptr: ?*anyopaque, size: ?*anyopaque) callconv(.C) void {
|
|
_ = bun.sys.munmap(@as([*]align(std.heap.page_size_min) u8, @ptrCast(@alignCast(ptr)))[0..@intFromPtr(size)]);
|
|
}
|
|
}.x, @as(?*anyopaque, @ptrFromInt(map.len)), null).?.value();
|
|
}
|
|
|
|
pub fn getTranspilerConstructor(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
|
|
return JSC.API.JSTranspiler.js.getConstructor(globalThis);
|
|
}
|
|
|
|
pub fn getFileSystemRouter(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
|
|
return JSC.API.FileSystemRouter.js.getConstructor(globalThis);
|
|
}
|
|
|
|
pub fn getHashObject(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
|
|
return HashObject.create(globalThis);
|
|
}
|
|
|
|
pub fn getTOMLObject(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
|
|
return TOMLObject.create(globalThis);
|
|
}
|
|
|
|
pub fn getGlobConstructor(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
|
|
return JSC.API.Glob.js.getConstructor(globalThis);
|
|
}
|
|
pub fn getS3ClientConstructor(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
|
|
return JSC.WebCore.S3Client.js.getConstructor(globalThis);
|
|
}
|
|
|
|
pub fn getS3DefaultClient(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
|
|
return globalThis.bunVM().rareData().s3DefaultClient(globalThis);
|
|
}
|
|
|
|
pub fn getValkeyDefaultClient(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
|
|
const valkey = JSC.API.Valkey.create(globalThis, &.{.js_undefined}) catch |err| {
|
|
if (err != error.JSError) {
|
|
_ = globalThis.throwError(err, "Failed to create Redis client") catch {};
|
|
return .zero;
|
|
}
|
|
return .zero;
|
|
};
|
|
|
|
return valkey.toJS(globalThis);
|
|
}
|
|
|
|
pub fn getValkeyClientConstructor(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
|
|
return JSC.API.Valkey.js.getConstructor(globalThis);
|
|
}
|
|
|
|
pub fn getEmbeddedFiles(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) bun.JSError!JSC.JSValue {
|
|
const vm = globalThis.bunVM();
|
|
const graph = vm.standalone_module_graph orelse return try JSC.JSValue.createEmptyArray(globalThis, 0);
|
|
|
|
const unsorted_files = graph.files.values();
|
|
var sort_indices = std.ArrayList(u32).initCapacity(bun.default_allocator, unsorted_files.len) catch bun.outOfMemory();
|
|
defer sort_indices.deinit();
|
|
for (0..unsorted_files.len) |index| {
|
|
// Some % of people using `bun build --compile` want to obscure the source code
|
|
// We don't really do that right now, but exposing the output source
|
|
// code here as an easily accessible Blob is even worse for them.
|
|
// So let's omit any source code files from the list.
|
|
if (unsorted_files[index].loader.isJavaScriptLike()) continue;
|
|
sort_indices.appendAssumeCapacity(@intCast(index));
|
|
}
|
|
|
|
var i: u32 = 0;
|
|
var array = try JSC.JSValue.createEmptyArray(globalThis, sort_indices.items.len);
|
|
std.mem.sort(u32, sort_indices.items, unsorted_files, bun.StandaloneModuleGraph.File.lessThanByIndex);
|
|
for (sort_indices.items) |index| {
|
|
const file = &unsorted_files[index];
|
|
// We call .dupe() on this to ensure that we don't return a blob that might get freed later.
|
|
const input_blob = file.blob(globalThis);
|
|
const blob = JSC.WebCore.Blob.new(input_blob.dupeWithContentType(true));
|
|
blob.allocator = bun.default_allocator;
|
|
blob.name = input_blob.name.dupeRef();
|
|
array.putIndex(globalThis, i, blob.toJS(globalThis));
|
|
i += 1;
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
pub fn getSemver(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
|
|
return SemverObject.create(globalThis);
|
|
}
|
|
|
|
pub fn getUnsafe(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
|
|
return UnsafeObject.create(globalThis);
|
|
}
|
|
|
|
pub fn stringWidth(str: bun.String, opts: gen.StringWidthOptions) usize {
|
|
if (str.length() == 0)
|
|
return 0;
|
|
|
|
if (opts.count_ansi_escape_codes)
|
|
return str.visibleWidth(!opts.ambiguous_is_narrow);
|
|
|
|
return str.visibleWidthExcludeANSIColors(!opts.ambiguous_is_narrow);
|
|
}
|
|
|
|
/// EnvironmentVariables is runtime defined.
|
|
/// Also, you can't iterate over process.env normally since it only exists at build-time otherwise
|
|
pub fn getCSRFObject(globalObject: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
|
|
return CSRFObject.create(globalObject);
|
|
}
|
|
|
|
const CSRFObject = struct {
|
|
pub fn create(globalThis: *JSC.JSGlobalObject) JSC.JSValue {
|
|
const object = JSValue.createEmptyObject(globalThis, 2);
|
|
|
|
object.put(
|
|
globalThis,
|
|
ZigString.static("generate"),
|
|
JSC.createCallback(globalThis, ZigString.static("generate"), 1, @import("../../csrf.zig").csrf__generate),
|
|
);
|
|
|
|
object.put(
|
|
globalThis,
|
|
ZigString.static("verify"),
|
|
JSC.createCallback(globalThis, ZigString.static("verify"), 1, @import("../../csrf.zig").csrf__verify),
|
|
);
|
|
|
|
return object;
|
|
}
|
|
};
|
|
|
|
// This is aliased to Bun.env
|
|
pub const EnvironmentVariables = struct {
|
|
pub export fn Bun__getEnvCount(globalObject: *JSC.JSGlobalObject, ptr: *[*][]const u8) usize {
|
|
const bunVM = globalObject.bunVM();
|
|
ptr.* = bunVM.transpiler.env.map.map.keys().ptr;
|
|
return bunVM.transpiler.env.map.map.unmanaged.entries.len;
|
|
}
|
|
|
|
pub export fn Bun__getEnvKey(ptr: [*][]const u8, i: usize, data_ptr: *[*]const u8) usize {
|
|
const item = ptr[i];
|
|
data_ptr.* = item.ptr;
|
|
return item.len;
|
|
}
|
|
|
|
pub export fn Bun__getEnvValue(globalObject: *JSC.JSGlobalObject, name: *ZigString, value: *ZigString) bool {
|
|
if (getEnvValue(globalObject, name.*)) |val| {
|
|
value.* = val;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
pub fn getEnvNames(globalObject: *JSC.JSGlobalObject, names: []ZigString) usize {
|
|
var vm = globalObject.bunVM();
|
|
const keys = vm.transpiler.env.map.map.keys();
|
|
const len = @min(names.len, keys.len);
|
|
for (keys[0..len], names[0..len]) |key, *name| {
|
|
name.* = ZigString.initUTF8(key);
|
|
}
|
|
return len;
|
|
}
|
|
|
|
pub fn getEnvValue(globalObject: *JSC.JSGlobalObject, name: ZigString) ?ZigString {
|
|
var vm = globalObject.bunVM();
|
|
var sliced = name.toSlice(vm.allocator);
|
|
defer sliced.deinit();
|
|
const value = vm.transpiler.env.get(sliced.slice()) orelse return null;
|
|
return ZigString.initUTF8(value);
|
|
}
|
|
};
|
|
|
|
export fn Bun__reportError(globalObject: *JSGlobalObject, err: JSC.JSValue) void {
|
|
_ = JSC.VirtualMachine.get().uncaughtException(globalObject, err, false);
|
|
}
|
|
|
|
comptime {
|
|
_ = Bun__reportError;
|
|
_ = EnvironmentVariables.Bun__getEnvCount;
|
|
_ = EnvironmentVariables.Bun__getEnvKey;
|
|
_ = EnvironmentVariables.Bun__getEnvValue;
|
|
}
|
|
|
|
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 global_deallocator(_: ?*anyopaque, ctx: ?*anyopaque) void {
|
|
comptime assert(bun.use_mimalloc);
|
|
bun.Mimalloc.mi_free(ctx);
|
|
}
|
|
export fn compressor_deallocator(_: ?*anyopaque, ctx: ?*anyopaque) void {
|
|
var compressor: *zlib.ZlibCompressorArrayList = bun.cast(*zlib.ZlibCompressorArrayList, ctx.?);
|
|
compressor.list.deinit(compressor.allocator);
|
|
compressor.deinit();
|
|
}
|
|
|
|
const Library = enum {
|
|
zlib,
|
|
libdeflate,
|
|
|
|
pub const map = bun.ComptimeEnumMap(Library);
|
|
};
|
|
|
|
// This has to be `inline` due to the callframe.
|
|
inline fn getOptions(globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!struct { JSC.Node.StringOrBuffer, ?JSValue } {
|
|
const arguments = callframe.arguments_old(2).slice();
|
|
const buffer_value: JSValue = if (arguments.len > 0) arguments[0] else .js_undefined;
|
|
const options_val: ?JSValue =
|
|
if (arguments.len > 1 and arguments[1].isObject())
|
|
arguments[1]
|
|
else if (arguments.len > 1 and !arguments[1].isUndefined()) {
|
|
return globalThis.throwInvalidArguments("Expected options to be an object", .{});
|
|
} else null;
|
|
|
|
if (try JSC.Node.StringOrBuffer.fromJS(globalThis, bun.default_allocator, buffer_value)) |buffer| {
|
|
return .{ buffer, options_val };
|
|
}
|
|
|
|
return globalThis.throwInvalidArguments("Expected buffer to be a string or buffer", .{});
|
|
}
|
|
|
|
pub fn gzipSync(globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
|
|
const buffer, const options_val = try getOptions(globalThis, callframe);
|
|
defer buffer.deinit();
|
|
return gzipOrDeflateSync(globalThis, buffer, options_val, true);
|
|
}
|
|
|
|
pub fn inflateSync(globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
|
|
const buffer, const options_val = try getOptions(globalThis, callframe);
|
|
defer buffer.deinit();
|
|
return gunzipOrInflateSync(globalThis, buffer, options_val, false);
|
|
}
|
|
|
|
pub fn deflateSync(globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
|
|
const buffer, const options_val = try getOptions(globalThis, callframe);
|
|
defer buffer.deinit();
|
|
return gzipOrDeflateSync(globalThis, buffer, options_val, false);
|
|
}
|
|
|
|
pub fn gunzipSync(globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
|
|
const buffer, const options_val = try getOptions(globalThis, callframe);
|
|
defer buffer.deinit();
|
|
return gunzipOrInflateSync(globalThis, buffer, options_val, true);
|
|
}
|
|
|
|
pub fn gunzipOrInflateSync(globalThis: *JSGlobalObject, buffer: JSC.Node.StringOrBuffer, options_val_: ?JSValue, is_gzip: bool) bun.JSError!JSValue {
|
|
var opts = zlib.Options{
|
|
.gzip = is_gzip,
|
|
.windowBits = if (is_gzip) 31 else -15,
|
|
};
|
|
|
|
var library: Library = .zlib;
|
|
if (options_val_) |options_val| {
|
|
if (try options_val.get(globalThis, "windowBits")) |window| {
|
|
opts.windowBits = window.coerce(i32, globalThis);
|
|
library = .zlib;
|
|
}
|
|
|
|
if (try options_val.get(globalThis, "level")) |level| {
|
|
opts.level = level.coerce(i32, globalThis);
|
|
}
|
|
|
|
if (try options_val.get(globalThis, "memLevel")) |memLevel| {
|
|
opts.memLevel = memLevel.coerce(i32, globalThis);
|
|
library = .zlib;
|
|
}
|
|
|
|
if (try options_val.get(globalThis, "strategy")) |strategy| {
|
|
opts.strategy = strategy.coerce(i32, globalThis);
|
|
library = .zlib;
|
|
}
|
|
|
|
if (try options_val.getTruthy(globalThis, "library")) |library_value| {
|
|
if (!library_value.isString()) {
|
|
return globalThis.throwInvalidArguments("Expected library to be a string", .{});
|
|
}
|
|
|
|
library = try Library.map.fromJS(globalThis, library_value) orelse {
|
|
return globalThis.throwInvalidArguments("Expected library to be one of 'zlib' or 'libdeflate'", .{});
|
|
};
|
|
}
|
|
}
|
|
|
|
if (globalThis.hasException()) return .zero;
|
|
|
|
const compressed = buffer.slice();
|
|
const allocator = JSC.VirtualMachine.get().allocator;
|
|
|
|
var list = brk: {
|
|
if (is_gzip and compressed.len > 64) {
|
|
// 0 1 2 3 4 5 6 7
|
|
// +---+---+---+---+---+---+---+---+
|
|
// | CRC32 | ISIZE |
|
|
// +---+---+---+---+---+---+---+---+
|
|
const estimated_size: u32 = @bitCast(compressed[compressed.len - 4 ..][0..4].*);
|
|
// If it's > 256 MB, let's rely on dynamic allocation to minimize the risk of OOM.
|
|
if (estimated_size > 0 and estimated_size < 256 * 1024 * 1024) {
|
|
break :brk try std.ArrayListUnmanaged(u8).initCapacity(allocator, @max(estimated_size, 64));
|
|
}
|
|
}
|
|
|
|
break :brk try std.ArrayListUnmanaged(u8).initCapacity(allocator, if (compressed.len > 512) compressed.len else 32);
|
|
};
|
|
|
|
switch (library) {
|
|
.zlib => {
|
|
var reader = zlib.ZlibReaderArrayList.initWithOptions(compressed, &list, allocator, .{
|
|
.windowBits = opts.windowBits,
|
|
.level = opts.level,
|
|
}) catch |err| {
|
|
list.deinit(allocator);
|
|
if (err == error.InvalidArgument) {
|
|
return globalThis.throw("Zlib error: Invalid argument", .{});
|
|
}
|
|
|
|
return globalThis.throwError(err, "Zlib error") catch return .zero;
|
|
};
|
|
|
|
reader.readAll() catch {
|
|
defer reader.deinit();
|
|
return globalThis.throwValue(ZigString.init(reader.errorMessage() orelse "Zlib returned an error").toErrorInstance(globalThis));
|
|
};
|
|
reader.list = .{ .items = reader.list.items };
|
|
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, reader, reader_deallocator, null);
|
|
},
|
|
.libdeflate => {
|
|
var decompressor: *bun.libdeflate.Decompressor = bun.libdeflate.Decompressor.alloc() orelse {
|
|
list.deinit(allocator);
|
|
return globalThis.throwOutOfMemory();
|
|
};
|
|
defer decompressor.deinit();
|
|
while (true) {
|
|
const result = decompressor.decompress(compressed, list.allocatedSlice(), if (is_gzip) .gzip else .deflate);
|
|
|
|
list.items.len = result.written;
|
|
|
|
if (result.status == .insufficient_space) {
|
|
if (list.capacity > 1024 * 1024 * 1024) {
|
|
list.deinit(allocator);
|
|
return globalThis.throwOutOfMemory();
|
|
}
|
|
|
|
list.ensureTotalCapacity(allocator, list.capacity * 2) catch {
|
|
list.deinit(allocator);
|
|
return globalThis.throwOutOfMemory();
|
|
};
|
|
continue;
|
|
}
|
|
|
|
if (result.status == .success) {
|
|
list.items.len = result.written;
|
|
break;
|
|
}
|
|
|
|
list.deinit(allocator);
|
|
return globalThis.throw("libdeflate returned an error: {s}", .{@tagName(result.status)});
|
|
}
|
|
|
|
var array_buffer = JSC.ArrayBuffer.fromBytes(list.items, .Uint8Array);
|
|
return array_buffer.toJSWithContext(globalThis, list.items.ptr, global_deallocator, null);
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn gzipOrDeflateSync(
|
|
globalThis: *JSGlobalObject,
|
|
buffer: JSC.Node.StringOrBuffer,
|
|
options_val_: ?JSValue,
|
|
is_gzip: bool,
|
|
) bun.JSError!JSValue {
|
|
var level: ?i32 = null;
|
|
var library: Library = .zlib;
|
|
var windowBits: i32 = 0;
|
|
|
|
if (options_val_) |options_val| {
|
|
if (try options_val.get(globalThis, "windowBits")) |window| {
|
|
windowBits = window.coerce(i32, globalThis);
|
|
library = .zlib;
|
|
}
|
|
|
|
if (try options_val.getTruthy(globalThis, "library")) |library_value| {
|
|
if (!library_value.isString()) {
|
|
return globalThis.throwInvalidArguments("Expected library to be a string", .{});
|
|
}
|
|
|
|
library = try Library.map.fromJS(globalThis, library_value) orelse {
|
|
return globalThis.throwInvalidArguments("Expected library to be one of 'zlib' or 'libdeflate'", .{});
|
|
};
|
|
}
|
|
|
|
if (try options_val.get(globalThis, "level")) |level_value| {
|
|
level = level_value.coerce(i32, globalThis);
|
|
if (globalThis.hasException()) return .zero;
|
|
}
|
|
}
|
|
|
|
if (globalThis.hasException()) return .zero;
|
|
|
|
const compressed = buffer.slice();
|
|
const allocator = bun.default_allocator;
|
|
|
|
switch (library) {
|
|
.zlib => {
|
|
var list = try std.ArrayListUnmanaged(u8).initCapacity(
|
|
allocator,
|
|
if (compressed.len > 512) compressed.len else 32,
|
|
);
|
|
|
|
var reader = zlib.ZlibCompressorArrayList.init(compressed, &list, allocator, .{
|
|
.windowBits = 15,
|
|
.gzip = is_gzip,
|
|
.level = level orelse 6,
|
|
}) catch |err| {
|
|
defer list.deinit(allocator);
|
|
if (err == error.InvalidArgument) {
|
|
return globalThis.throw("Zlib error: Invalid argument", .{});
|
|
}
|
|
|
|
return globalThis.throwError(err, "Zlib error");
|
|
};
|
|
|
|
reader.readAll() catch {
|
|
defer reader.deinit();
|
|
return globalThis.throwValue(ZigString.init(reader.errorMessage() orelse "Zlib returned an error").toErrorInstance(globalThis));
|
|
};
|
|
reader.list = .{ .items = reader.list.toOwnedSlice(allocator) catch bun.outOfMemory() };
|
|
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, reader, reader_deallocator, null);
|
|
},
|
|
.libdeflate => {
|
|
var compressor: *bun.libdeflate.Compressor = bun.libdeflate.Compressor.alloc(level orelse 6) orelse {
|
|
return globalThis.throwOutOfMemory();
|
|
};
|
|
const encoding: bun.libdeflate.Encoding = if (is_gzip) .gzip else .deflate;
|
|
defer compressor.deinit();
|
|
|
|
var list = try std.ArrayListUnmanaged(u8).initCapacity(
|
|
allocator,
|
|
// This allocation size is unfortunate, but it's not clear how to avoid it with libdeflate.
|
|
compressor.maxBytesNeeded(compressed, encoding),
|
|
);
|
|
|
|
while (true) {
|
|
const result = compressor.compress(compressed, list.allocatedSlice(), encoding);
|
|
|
|
list.items.len = result.written;
|
|
|
|
if (result.status == .success) {
|
|
list.items.len = result.written;
|
|
break;
|
|
}
|
|
|
|
list.deinit(allocator);
|
|
return globalThis.throw("libdeflate error: {s}", .{@tagName(result.status)});
|
|
}
|
|
|
|
var array_buffer = JSC.ArrayBuffer.fromBytes(list.items, .Uint8Array);
|
|
return array_buffer.toJSWithContext(globalThis, list.items.ptr, global_deallocator, null);
|
|
},
|
|
}
|
|
}
|
|
};
|
|
|
|
pub const JSZstd = struct {
|
|
export fn deallocator(_: ?*anyopaque, ctx: ?*anyopaque) void {
|
|
comptime assert(bun.use_mimalloc);
|
|
bun.Mimalloc.mi_free(ctx);
|
|
}
|
|
|
|
inline fn getOptions(globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!struct { JSC.Node.StringOrBuffer, ?JSValue } {
|
|
const arguments = callframe.arguments();
|
|
const buffer_value: JSValue = if (arguments.len > 0) arguments[0] else .js_undefined;
|
|
const options_val: ?JSValue =
|
|
if (arguments.len > 1 and arguments[1].isObject())
|
|
arguments[1]
|
|
else if (arguments.len > 1 and !arguments[1].isUndefined()) {
|
|
return globalThis.throwInvalidArguments("Expected options to be an object", .{});
|
|
} else null;
|
|
|
|
if (try JSC.Node.StringOrBuffer.fromJS(globalThis, bun.default_allocator, buffer_value)) |buffer| {
|
|
return .{ buffer, options_val };
|
|
}
|
|
|
|
return globalThis.throwInvalidArguments("Expected buffer to be a string or buffer", .{});
|
|
}
|
|
|
|
fn getLevel(globalThis: *JSGlobalObject, options_val: ?JSValue) bun.JSError!i32 {
|
|
if (options_val) |option_obj| {
|
|
if (try option_obj.get(globalThis, "level")) |level_val| {
|
|
const value = level_val.coerce(i32, globalThis);
|
|
if (globalThis.hasException()) return error.JSError;
|
|
|
|
if (value < 1 or value > 22) {
|
|
return globalThis.throwInvalidArguments("Compression level must be between 1 and 22", .{});
|
|
}
|
|
|
|
return value;
|
|
}
|
|
}
|
|
|
|
return 3;
|
|
}
|
|
|
|
inline fn getOptionsAsync(globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!struct { JSC.Node.StringOrBuffer, ?JSValue, i32 } {
|
|
const arguments = callframe.arguments();
|
|
const buffer_value: JSValue = if (arguments.len > 0) arguments[0] else .js_undefined;
|
|
const options_val: ?JSValue =
|
|
if (arguments.len > 1 and arguments[1].isObject())
|
|
arguments[1]
|
|
else if (arguments.len > 1 and !arguments[1].isUndefined()) {
|
|
return globalThis.throwInvalidArguments("Expected options to be an object", .{});
|
|
} else null;
|
|
|
|
const level = try getLevel(globalThis, options_val);
|
|
|
|
const allow_string_object = true;
|
|
if (try JSC.Node.StringOrBuffer.fromJSMaybeAsync(globalThis, bun.default_allocator, buffer_value, true, allow_string_object)) |buffer| {
|
|
return .{ buffer, options_val, level };
|
|
}
|
|
|
|
return globalThis.throwInvalidArguments("Expected buffer to be a string or buffer", .{});
|
|
}
|
|
|
|
pub fn compressSync(globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
|
|
const buffer, const options_val = try getOptions(globalThis, callframe);
|
|
defer buffer.deinit();
|
|
|
|
const level = try getLevel(globalThis, options_val);
|
|
|
|
const input = buffer.slice();
|
|
const allocator = bun.default_allocator;
|
|
|
|
// Calculate max compressed size
|
|
const max_size = bun.zstd.compressBound(input.len);
|
|
var output = try allocator.alloc(u8, max_size);
|
|
|
|
// Perform compression with context
|
|
const compressed_size = switch (bun.zstd.compress(output, input, level)) {
|
|
.success => |size| size,
|
|
.err => |err| {
|
|
allocator.free(output);
|
|
return globalThis.ERR(.ZSTD, "{s}", .{err}).throw();
|
|
},
|
|
};
|
|
|
|
// Resize to actual compressed size
|
|
if (compressed_size < output.len) {
|
|
output = try allocator.realloc(output, compressed_size);
|
|
}
|
|
|
|
return JSC.JSValue.createBuffer(globalThis, output, bun.default_allocator);
|
|
}
|
|
|
|
pub fn decompressSync(globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
|
|
const buffer, _ = try getOptions(globalThis, callframe);
|
|
defer buffer.deinit();
|
|
|
|
const input = buffer.slice();
|
|
const allocator = bun.default_allocator;
|
|
|
|
// Try to get the decompressed size
|
|
const decompressed_size = bun.zstd.getDecompressedSize(input);
|
|
|
|
if (decompressed_size == std.math.maxInt(c_ulonglong) - 1 or decompressed_size == std.math.maxInt(c_ulonglong) - 2) {
|
|
// If size is unknown, we'll need to decompress in chunks
|
|
return globalThis.ERR(.ZSTD, "Decompressed size is unknown. Either the input is not a valid zstd compressed buffer or the decompressed size is too large. If you run into this error with a valid input, please file an issue at https://github.com/oven-sh/bun/issues", .{}).throw();
|
|
}
|
|
|
|
// Allocate output buffer based on decompressed size
|
|
var output = try allocator.alloc(u8, decompressed_size);
|
|
|
|
// Perform decompression
|
|
const actual_size = switch (bun.zstd.decompress(output, input)) {
|
|
.success => |actual_size| actual_size,
|
|
.err => |err| {
|
|
allocator.free(output);
|
|
return globalThis.ERR(.ZSTD, "{s}", .{err}).throw();
|
|
},
|
|
};
|
|
|
|
bun.debugAssert(actual_size <= output.len);
|
|
|
|
// mimalloc doesn't care about the self-reported size of the slice.
|
|
output.len = actual_size;
|
|
|
|
return JSC.JSValue.createBuffer(globalThis, output, bun.default_allocator);
|
|
}
|
|
|
|
// --- Async versions ---
|
|
|
|
pub const ZstdJob = struct {
|
|
buffer: JSC.Node.StringOrBuffer = JSC.Node.StringOrBuffer.empty,
|
|
is_compress: bool = true,
|
|
level: i32 = 3,
|
|
task: JSC.WorkPoolTask = .{ .callback = &runTask },
|
|
promise: JSC.JSPromise.Strong = .{},
|
|
vm: *JSC.VirtualMachine,
|
|
output: []u8 = &[_]u8{},
|
|
error_message: ?[]const u8 = null,
|
|
any_task: JSC.AnyTask = undefined,
|
|
poll: Async.KeepAlive = .{},
|
|
|
|
pub const new = bun.TrivialNew(@This());
|
|
|
|
pub fn runTask(task: *JSC.WorkPoolTask) void {
|
|
const job: *ZstdJob = @fieldParentPtr("task", task);
|
|
defer job.vm.enqueueTaskConcurrent(JSC.ConcurrentTask.create(job.any_task.task()));
|
|
|
|
const input = job.buffer.slice();
|
|
const allocator = bun.default_allocator;
|
|
|
|
if (job.is_compress) {
|
|
// Compression path
|
|
// Calculate max compressed size
|
|
const max_size = bun.zstd.compressBound(input.len);
|
|
job.output = allocator.alloc(u8, max_size) catch {
|
|
job.error_message = "Out of memory";
|
|
return;
|
|
};
|
|
|
|
// Perform compression
|
|
job.output = switch (bun.zstd.compress(job.output, input, job.level)) {
|
|
.success => |size| blk: {
|
|
// Resize to actual compressed size
|
|
if (size < job.output.len) {
|
|
break :blk allocator.realloc(job.output, size) catch {
|
|
job.error_message = "Out of memory";
|
|
return;
|
|
};
|
|
}
|
|
break :blk job.output;
|
|
},
|
|
.err => |err| {
|
|
allocator.free(job.output);
|
|
job.output = &[_]u8{};
|
|
job.error_message = err;
|
|
return;
|
|
},
|
|
};
|
|
} else {
|
|
// Decompression path
|
|
// Try to get the decompressed size
|
|
const decompressed_size = bun.zstd.getDecompressedSize(input);
|
|
|
|
if (decompressed_size == std.math.maxInt(c_ulonglong) - 1 or decompressed_size == std.math.maxInt(c_ulonglong) - 2) {
|
|
job.error_message = "Decompressed size is unknown. Either the input is not a valid zstd compressed buffer or the decompressed size is too large";
|
|
return;
|
|
}
|
|
|
|
// Allocate output buffer based on decompressed size
|
|
job.output = allocator.alloc(u8, decompressed_size) catch {
|
|
job.error_message = "Out of memory";
|
|
return;
|
|
};
|
|
|
|
// Perform decompression
|
|
switch (bun.zstd.decompress(job.output, input)) {
|
|
.success => |actual_size| {
|
|
if (actual_size < job.output.len) {
|
|
job.output.len = actual_size;
|
|
}
|
|
},
|
|
.err => |err| {
|
|
allocator.free(job.output);
|
|
job.output = &[_]u8{};
|
|
job.error_message = err;
|
|
return;
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn runFromJS(this: *ZstdJob) void {
|
|
defer this.deinit();
|
|
if (this.vm.isShuttingDown()) {
|
|
return;
|
|
}
|
|
|
|
const globalThis = this.vm.global;
|
|
const promise = this.promise.swap();
|
|
|
|
if (this.error_message) |err_msg| {
|
|
promise.reject(globalThis, globalThis.ERR(.ZSTD, "{s}", .{err_msg}).toJS());
|
|
return;
|
|
}
|
|
|
|
const output_slice = this.output;
|
|
const buffer_value = JSC.JSValue.createBuffer(globalThis, output_slice, bun.default_allocator);
|
|
if (globalThis.hasException()) {
|
|
promise.reject(globalThis, error.JSError);
|
|
return;
|
|
}
|
|
if (buffer_value == .zero) {
|
|
promise.reject(globalThis, ZigString.init("Failed to create buffer").toErrorInstance(globalThis));
|
|
return;
|
|
}
|
|
|
|
this.output = &[_]u8{};
|
|
promise.resolve(globalThis, buffer_value);
|
|
}
|
|
|
|
pub fn deinit(this: *ZstdJob) void {
|
|
this.poll.unref(this.vm);
|
|
this.buffer.deinitAndUnprotect();
|
|
this.promise.deinit();
|
|
bun.default_allocator.free(this.output);
|
|
bun.destroy(this);
|
|
}
|
|
|
|
pub fn create(vm: *JSC.VirtualMachine, globalThis: *JSC.JSGlobalObject, buffer: JSC.Node.StringOrBuffer, is_compress: bool, level: i32) *ZstdJob {
|
|
var job = ZstdJob.new(.{
|
|
.buffer = buffer,
|
|
.is_compress = is_compress,
|
|
.level = level,
|
|
.vm = vm,
|
|
.any_task = undefined,
|
|
});
|
|
|
|
job.promise = JSC.JSPromise.Strong.init(globalThis);
|
|
job.any_task = JSC.AnyTask.New(@This(), &runFromJS).init(job);
|
|
job.poll.ref(vm);
|
|
JSC.WorkPool.schedule(&job.task);
|
|
|
|
return job;
|
|
}
|
|
};
|
|
|
|
pub fn compress(globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
|
|
const buffer, _, const level = try getOptionsAsync(globalThis, callframe);
|
|
|
|
const vm = globalThis.bunVM();
|
|
var job = ZstdJob.create(vm, globalThis, buffer, true, level);
|
|
return job.promise.value();
|
|
}
|
|
|
|
pub fn decompress(globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
|
|
const buffer, _, _ = try getOptionsAsync(globalThis, callframe);
|
|
|
|
const vm = globalThis.bunVM();
|
|
var job = ZstdJob.create(vm, globalThis, buffer, false, 0); // level is ignored for decompression
|
|
return job.promise.value();
|
|
}
|
|
};
|
|
|
|
// const InternalTestingAPIs = struct {
|
|
// pub fn BunInternalFunction__syntaxHighlighter(globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
|
|
// const args = callframe.arguments_old(1);
|
|
// if (args.len < 1) {
|
|
// globalThis.throwNotEnoughArguments("code", 1, 0);
|
|
// }
|
|
|
|
// const code = args.ptr[0].toSliceOrNull(globalThis) orelse return .zero;
|
|
// defer code.deinit();
|
|
// var buffer = MutableString.initEmpty(bun.default_allocator);
|
|
// defer buffer.deinit();
|
|
// var writer = buffer.bufferedWriter();
|
|
// const formatter = bun.fmt.fmtJavaScript(code.slice(), .{
|
|
// .enable_colors = true,
|
|
// .check_for_unhighlighted_write = false,
|
|
// });
|
|
// std.fmt.format(writer.writer(), "{}", .{formatter}) catch |err| {
|
|
// return globalThis.throwError(err, "Error formatting code");
|
|
// };
|
|
|
|
// writer.flush() catch |err| {
|
|
// return globalThis.throwError(err, "Error formatting code");
|
|
// };
|
|
|
|
// return bun.String.createUTF8ForJS(globalThis, buffer.list.items);
|
|
// }
|
|
// };
|
|
|
|
comptime {
|
|
_ = Crypto.JSPasswordObject.JSPasswordObject__create;
|
|
_ = @import("../../btjs.zig").dumpBtjsTrace;
|
|
BunObject.exportAll();
|
|
}
|
|
|
|
const assert = bun.assert;
|
|
|
|
const conv = std.builtin.CallingConvention.Unspecified;
|
|
const Bun = @This();
|
|
const default_allocator = bun.default_allocator;
|
|
const bun = @import("bun");
|
|
const Environment = bun.Environment;
|
|
const strings = bun.strings;
|
|
const string = bun.string;
|
|
const Output = bun.Output;
|
|
const MutableString = bun.MutableString;
|
|
const std = @import("std");
|
|
const options = @import("../../options.zig");
|
|
const ZigString = bun.JSC.ZigString;
|
|
const WebCore = bun.JSC.WebCore;
|
|
const JSC = bun.JSC;
|
|
const JSValue = bun.JSC.JSValue;
|
|
|
|
const JSGlobalObject = bun.JSC.JSGlobalObject;
|
|
const ConsoleObject = bun.JSC.ConsoleObject;
|
|
const api = bun.api;
|
|
const node = bun.api.node;
|
|
const host_fn = bun.jsc.host_fn;
|
|
const JSPromise = bun.JSC.JSPromise;
|
|
const URL = @import("../../url.zig").URL;
|
|
const Transpiler = bun.JSC.API.JSTranspiler;
|
|
const JSBundler = bun.JSC.API.JSBundler;
|
|
const VirtualMachine = JSC.VirtualMachine;
|
|
const zlib = @import("../../zlib.zig");
|
|
const Which = @import("../../which.zig");
|
|
const ErrorableString = JSC.ErrorableString;
|
|
const Async = bun.Async;
|
|
const SemverObject = bun.Semver.SemverObject;
|
|
const Braces = @import("../../shell/braces.zig");
|
|
|
|
const HashObject = bun.api.HashObject;
|
|
const UnsafeObject = bun.api.UnsafeObject;
|
|
const TOMLObject = bun.api.TOMLObject;
|
|
const FFIObject = bun.api.FFIObject;
|