From 7dae4db52a403396bbe23ec6299f5a06d50e829b Mon Sep 17 00:00:00 2001 From: Dylan Conway <35280289+dylan-conway@users.noreply.github.com> Date: Tue, 5 Sep 2023 06:12:54 -0700 Subject: [PATCH 1/9] fix ipv6 localhost fetch (#4498) * `node` null for localhost getaddrinfo * more test --- src/deps/uws.zig | 13 +++++++++++-- test/js/web/fetch/fetch.test.ts | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/deps/uws.zig b/src/deps/uws.zig index 201544a757..d3228141b3 100644 --- a/src/deps/uws.zig +++ b/src/deps/uws.zig @@ -369,8 +369,17 @@ pub fn NewSocketHandler(comptime is_ssl: bool) type { debug("connect({s}, {d})", .{ host, port }); var stack_fallback = std.heap.stackFallback(1024, bun.default_allocator); var allocator = stack_fallback.get(); - var host_ = allocator.dupeZ(u8, host) catch return null; - defer allocator.free(host_); + + var host_: ?[*:0]u8 = brk: { + // getaddrinfo expects `node` to be null if localhost + if (host.len < 6 and (bun.strings.eqlComptime(host, "[::1]") or bun.strings.eqlComptime(host, "[::]"))) { + break :brk null; + } + + break :brk allocator.dupeZ(u8, host) catch return null; + }; + + defer if (host_) |host__| allocator.free(host__[0..host.len]); var socket = us_socket_context_connect(comptime ssl_int, socket_ctx, host_, port, null, 0, @sizeOf(*anyopaque)) orelse return null; const socket_ = ThisSocket{ .socket = socket }; diff --git a/test/js/web/fetch/fetch.test.ts b/test/js/web/fetch/fetch.test.ts index 59847dde98..aa44ee76a4 100644 --- a/test/js/web/fetch/fetch.test.ts +++ b/test/js/web/fetch/fetch.test.ts @@ -531,6 +531,26 @@ describe("fetch", () => { expect(response2.status).toBe(200); expect(await response2.text()).toBe("0"); }); + + it("should work with ipv6 localhost", async () => { + const server = Bun.serve({ + port: 0, + fetch(req) { + return new Response("Pass!"); + }, + }); + + let res = await fetch(`http://[::1]:${server.port}`); + expect(await res.text()).toBe("Pass!"); + res = await fetch(`http://[::]:${server.port}/`); + expect(await res.text()).toBe("Pass!"); + res = await fetch(`http://[0:0:0:0:0:0:0:1]:${server.port}/`); + expect(await res.text()).toBe("Pass!"); + res = await fetch(`http://[0000:0000:0000:0000:0000:0000:0000:0001]:${server.port}/`); + expect(await res.text()).toBe("Pass!"); + + server.stop(); + }); }); it("simultaneous HTTPS fetch", async () => { From bc2b55fdeee3001b07252d0f250671ea1876a3ed Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 5 Sep 2023 19:16:11 +0300 Subject: [PATCH 2/9] fix checkout/build failure due to `src/deps/uws` (#4505) --- .scripts/write-versions.sh | 3 --- src/bun.js/node/types.zig | 4 ++-- src/deps/uws | 1 - src/generated_versions_list.zig | 8 +++----- test/js/node/process/process.test.js | 10 +++++----- 5 files changed, 10 insertions(+), 16 deletions(-) mode change 100644 => 100755 .scripts/write-versions.sh delete mode 160000 src/deps/uws diff --git a/.scripts/write-versions.sh b/.scripts/write-versions.sh old mode 100644 new mode 100755 index b748b842d2..2b1a7cd243 --- a/.scripts/write-versions.sh +++ b/.scripts/write-versions.sh @@ -7,11 +7,9 @@ LIBARCHIVE_VERSION=$(git rev-parse HEAD:./src/deps/libarchive) PICOHTTPPARSER_VERSION=$(git rev-parse HEAD:./src/deps/picohttpparser) BORINGSSL_VERSION=$(git rev-parse HEAD:./src/deps/boringssl) ZLIB_VERSION=$(git rev-parse HEAD:./src/deps/zlib) -UWS_VERSION=$(git rev-parse HEAD:./src/deps/uws) LOLHTML=$(git rev-parse HEAD:./src/deps/lol-html) TINYCC=$(git rev-parse HEAD:./src/deps/tinycc) C_ARES=$(git rev-parse HEAD:./src/deps/c-ares) -USOCKETS=$(cd src/deps/uws/uSockets && git rev-parse HEAD) rm -rf src/generated_versions_list.zig echo "// AUTO-GENERATED FILE. Created via .scripts/write-versions.sh" >src/generated_versions_list.zig @@ -20,7 +18,6 @@ echo "pub const boringssl = \"$BORINGSSL_VERSION\";" >>src/generated_versions_li echo "pub const libarchive = \"$LIBARCHIVE_VERSION\";" >>src/generated_versions_list.zig echo "pub const mimalloc = \"$MIMALLOC_VERSION\";" >>src/generated_versions_list.zig echo "pub const picohttpparser = \"$PICOHTTPPARSER_VERSION\";" >>src/generated_versions_list.zig -echo "pub const uws = \"$UWS_VERSION\";" >>src/generated_versions_list.zig echo "pub const webkit = \"$WEBKIT_VERSION\";" >>src/generated_versions_list.zig echo "pub const zig = @import(\"std\").fmt.comptimePrint(\"{}\", .{@import(\"builtin\").zig_version});" >>src/generated_versions_list.zig echo "pub const zlib = \"$ZLIB_VERSION\";" >>src/generated_versions_list.zig diff --git a/src/bun.js/node/types.zig b/src/bun.js/node/types.zig index d9f725eaab..328b71b60e 100644 --- a/src/bun.js/node/types.zig +++ b/src/bun.js/node/types.zig @@ -2488,14 +2488,14 @@ pub const Process = struct { pub export const Bun__versions_libarchive: [*:0]const u8 = bun.Global.versions.libarchive; pub export const Bun__versions_mimalloc: [*:0]const u8 = bun.Global.versions.mimalloc; pub export const Bun__versions_picohttpparser: [*:0]const u8 = bun.Global.versions.picohttpparser; - pub export const Bun__versions_uws: [*:0]const u8 = bun.Global.versions.uws; + pub export const Bun__versions_uws: [*:0]const u8 = bun.Environment.git_sha; pub export const Bun__versions_webkit: [*:0]const u8 = bun.Global.versions.webkit; pub export const Bun__versions_zig: [*:0]const u8 = bun.Global.versions.zig; pub export const Bun__versions_zlib: [*:0]const u8 = bun.Global.versions.zlib; pub export const Bun__versions_tinycc: [*:0]const u8 = bun.Global.versions.tinycc; pub export const Bun__versions_lolhtml: [*:0]const u8 = bun.Global.versions.lolhtml; pub export const Bun__versions_c_ares: [*:0]const u8 = bun.Global.versions.c_ares; - pub export const Bun__versions_usockets: [*:0]const u8 = bun.Global.versions.usockets; + pub export const Bun__versions_usockets: [*:0]const u8 = bun.Environment.git_sha; pub export const Bun__version_sha: [*:0]const u8 = bun.Environment.git_sha; }; diff --git a/src/deps/uws b/src/deps/uws deleted file mode 160000 index 8b4206edb9..0000000000 --- a/src/deps/uws +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8b4206edb96799df8c5fc01d43a4e51373064162 diff --git a/src/generated_versions_list.zig b/src/generated_versions_list.zig index c27f47c83d..d0bf1dff90 100644 --- a/src/generated_versions_list.zig +++ b/src/generated_versions_list.zig @@ -2,13 +2,11 @@ pub const boringssl = "b275c5ce1c88bc06f5a967026d3c0ce1df2be815"; pub const libarchive = "dc321febde83dd0f31158e1be61a7aedda65e7a2"; -pub const mimalloc = "3c7079967a269027e438a2aac83197076d9fe09d"; +pub const mimalloc = "7968d4285043401bb36573374710d47a4081a063"; pub const picohttpparser = "066d2b1e9ab820703db0837a7255d92d30f0c9f5"; -pub const uws = "70b1b9fc1341e8b791b42c5447f90505c2abe156"; -pub const webkit = "60d11703a533fd694cd1d6ddda04813eecb5d69f"; +pub const webkit = "a780bdf0255ae1a7ed15e4b3f31c14af705facae"; pub const zig = @import("std").fmt.comptimePrint("{}", .{@import("builtin").zig_version}); pub const zlib = "885674026394870b7e7a05b7bf1ec5eb7bd8a9c0"; pub const tinycc = "2d3ad9e0d32194ad7fd867b66ebe218dcc8cb5cd"; -pub const lolhtml = "2681dcf0b3e6907111565199df8c43cc9aab7fe8"; +pub const lolhtml = "8d4c273ded322193d017042d1f48df2766b0f88b"; pub const c_ares = "0e7a5dee0fbb04080750cf6eabbe89d8bae87faa"; -pub const usockets = "fafc241e8664243fc0c51d69684d5d02b9805134"; diff --git a/test/js/node/process/process.test.js b/test/js/node/process/process.test.js index 04b823825a..bb74bb998f 100644 --- a/test/js/node/process/process.test.js +++ b/test/js/node/process/process.test.js @@ -171,13 +171,11 @@ const versions = existsSync(generated_versions_list); // pub const libarchive = "dc321febde83dd0f31158e1be61a7aedda65e7a2"; // pub const mimalloc = "3c7079967a269027e438a2aac83197076d9fe09d"; // pub const picohttpparser = "066d2b1e9ab820703db0837a7255d92d30f0c9f5"; - // pub const uws = "70b1b9fc1341e8b791b42c5447f90505c2abe156"; // pub const webkit = "60d11703a533fd694cd1d6ddda04813eecb5d69f"; // pub const zlib = "885674026394870b7e7a05b7bf1ec5eb7bd8a9c0"; // pub const tinycc = "2d3ad9e0d32194ad7fd867b66ebe218dcc8cb5cd"; // pub const lolhtml = "2eed349dcdfa4ff5c19fe7c6e501cfd687601033"; // pub const c_ares = "0e7a5dee0fbb04080750cf6eabbe89d8bae87faa"; - // pub const usockets = "fafc241e8664243fc0c51d69684d5d02b9805134"; const versions = Object.fromEntries( readFileSync(generated_versions_list, "utf8") .split("\n") @@ -185,15 +183,17 @@ const versions = existsSync(generated_versions_list); .map(line => line.split(" = ")) .map(([name, hash]) => [name.slice(9).trim(), hash.slice(1, -2)]), ); - versions.uwebsockets = versions.uws; - delete versions.uws; - versions["ares"] = versions.c_ares; + versions.ares = versions.c_ares; delete versions.c_ares; for (const name in versions) { expect(process.versions).toHaveProperty(name); expect(process.versions[name]).toBe(versions[name]); } + + expect(process.versions).toHaveProperty("usockets"); + expect(process.versions).toHaveProperty("uwebsockets"); + expect(process.versions.usockets).toBe(process.versions.uwebsockets); }); it("process.config", () => { From 1e998c1bf2e0c95fb182eb01806bf11eebe6fed3 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Tue, 5 Sep 2023 14:25:19 -0700 Subject: [PATCH 3/9] fix(install): ensure all lockfile structs do not have undefined padding (#4401) * padding sucks * this assertion is already done elsewhere * remove test. will be covered alex's pr i believe? * fix webkit submodule * fix uws submodule --- src/install/bin.zig | 2 + src/install/lockfile.zig | 18 ++++++- src/install/padding_checker.zig | 93 +++++++++++++++++++++++++++++++++ src/install/resolution.zig | 1 + src/install/semver.zig | 1 + 5 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 src/install/padding_checker.zig diff --git a/src/install/bin.zig b/src/install/bin.zig index b559f4f531..0a8d62c8c8 100644 --- a/src/install/bin.zig +++ b/src/install/bin.zig @@ -18,6 +18,8 @@ const bun = @import("root").bun; /// - map where keys are names of the binaries and values are file paths to the binaries pub const Bin = extern struct { tag: Tag = Tag.none, + _padding_tag: [3]u8 = .{0} ** 3, + value: Value = Value{ .none = {} }, pub fn verify(this: *const Bin, extern_strings: []const ExternalString) void { diff --git a/src/install/lockfile.zig b/src/install/lockfile.zig index 1291c648aa..0b5e0d7bce 100644 --- a/src/install/lockfile.zig +++ b/src/install/lockfile.zig @@ -88,6 +88,8 @@ const NameHashMap = std.ArrayHashMapUnmanaged(u32, String, ArrayIdentityContext, const NameHashSet = std.ArrayHashMapUnmanaged(u32, void, ArrayIdentityContext, false); const VersionHashMap = std.ArrayHashMapUnmanaged(u32, Semver.Version, ArrayIdentityContext, false); +const assertNoUninitializedPadding = @import("./padding_checker.zig").assertNoUninitializedPadding; + // Serialized data /// The version of the lockfile format, intended to prevent data corruption for format changes. format: FormatVersion = .v1, @@ -3632,9 +3634,13 @@ pub const Package = extern struct { pub const Meta = extern struct { origin: Origin = Origin.npm, + _padding_origin: u8 = 0, + arch: Npm.Architecture = Npm.Architecture.all, os: Npm.OperatingSystem = Npm.OperatingSystem.all, + _padding_os: u16 = 0, + id: PackageID = invalid_package_id, man_dir: String = String{}, @@ -3731,7 +3737,10 @@ pub const Package = extern struct { var sliced = list.slice(); inline for (FieldsEnum.fields) |field| { - try writer.writeAll(std.mem.sliceAsBytes(sliced.items(@field(Lockfile.Package.List.Field, field.name)))); + const value = sliced.items(@field(Lockfile.Package.List.Field, field.name)); + + comptime assertNoUninitializedPadding(@TypeOf(value)); + try writer.writeAll(std.mem.sliceAsBytes(value)); } const really_end_at = try stream.getPos(); @@ -3782,7 +3791,10 @@ pub const Package = extern struct { var sliced = list.slice(); inline for (FieldsEnum.fields) |field| { - var bytes = std.mem.sliceAsBytes(sliced.items(@field(Lockfile.Package.List.Field, field.name))); + const value = sliced.items(@field(Lockfile.Package.List.Field, field.name)); + + comptime assertNoUninitializedPadding(@TypeOf(value)); + var bytes = std.mem.sliceAsBytes(value); const end_pos = stream.pos + bytes.len; if (end_pos <= end_at) { @memcpy(bytes, stream.buffer[stream.pos..][0..bytes.len]); @@ -3913,7 +3925,9 @@ const Buffers = struct { } pub fn writeArray(comptime StreamType: type, stream: StreamType, comptime Writer: type, writer: Writer, comptime ArrayList: type, array: ArrayList) !void { + comptime assertNoUninitializedPadding(@TypeOf(array)); const bytes = std.mem.sliceAsBytes(array); + const start_pos = try stream.getPos(); try writer.writeIntLittle(u64, 0xDEADBEEF); try writer.writeIntLittle(u64, 0xDEADBEEF); diff --git a/src/install/padding_checker.zig b/src/install/padding_checker.zig new file mode 100644 index 0000000000..1d9405a43d --- /dev/null +++ b/src/install/padding_checker.zig @@ -0,0 +1,93 @@ +const std = @import("std"); + +/// In some parts of lockfile serialization, Bun will use `std.mem.sliceAsBytes` to convert a struct into raw +/// bytes to write. This makes lockfile serialization/deserialization much simpler/faster, at the cost of not +/// having any pointers within these structs. +/// +/// One major caveat of this is that if any of these structs have uninitialized memory, then that can leak +/// garbage memory into the lockfile. See https://github.com/oven-sh/bun/issues/4319 +/// +/// The obvious way to introduce undefined memory into a struct is via `.field = undefined`, but a much more +/// subtle way is to have implicit padding in an extern struct. For example: +/// ```zig +/// const Demo = struct { +/// a: u8, // @sizeOf(Demo, "a") == 1, @offsetOf(Demo, "a") == 0 +/// b: u64, // @sizeOf(Demo, "b") == 8, @offsetOf(Demo, "b") == 8 +/// } +/// ``` +/// +/// `a` is only one byte long, but due to the alignment of `b`, there is 7 bytes of padding between `a` and `b`, +/// which is considered *undefined memory*. +/// +/// The solution is to have it explicitly initialized to zero bytes, like: +/// ```zig +/// const Demo = struct { +/// a: u8, +/// _padding: [7]u8 = .{0} ** 7, +/// b: u64, // same offset as before +/// } +/// ``` +/// +/// There is one other way to introduce undefined memory into a struct, which this does not check for, and that is +/// a union with unequal size fields. +pub fn assertNoUninitializedPadding(comptime T: type) void { + const info_ = @typeInfo(T); + const info = switch (info_) { + .Struct => info_.Struct, + .Union => info_.Union, + .Array => |a| { + assertNoUninitializedPadding(a.child); + return; + }, + .Optional => |a| { + assertNoUninitializedPadding(a.child); + return; + }, + .Pointer => |ptr| { + // Pointers aren't allowed, but this just makes the assertion easier to invoke. + assertNoUninitializedPadding(ptr.child); + return; + }, + else => { + return; + }, + }; + // if (info.layout != .Extern) { + // @compileError("assertNoUninitializedPadding(" ++ @typeName(T) ++ ") expects an extern struct type, got a struct of layout '" ++ @tagName(info.layout) ++ "'"); + // } + var i = 0; + for (info.fields) |field| { + const fieldInfo = @typeInfo(field.type); + switch (fieldInfo) { + .Struct => assertNoUninitializedPadding(field.type), + .Union => assertNoUninitializedPadding(field.type), + .Array => |a| assertNoUninitializedPadding(a.child), + .Optional => |a| assertNoUninitializedPadding(a.child), + .Pointer => { + @compileError("Expected no pointer types in " ++ @typeName(T) ++ ", found field '" ++ field.name ++ "' of type '" ++ @typeName(field.type) ++ "'"); + }, + else => {}, + } + } + if (info_ == .Union) { + return; + } + for (info.fields, 0..) |field, j| { + const offset = @offsetOf(T, field.name); + if (offset != i) { + @compileError(std.fmt.comptimePrint( + \\Expected no possibly uninitialized bytes of memory in '{s}', but found a {d} byte gap between fields '{s}' and '{s}' This can be fixed by adding a padding field to the struct like `padding: [{d}]u8 = .{{0}} ** {d},` between these fields. For more information, look at `padding_checker.zig` + , + .{ + @typeName(T), + offset - i, + info.fields[j - 1].name, + field.name, + offset - i, + offset - i, + }, + )); + } + i = offset + @sizeOf(field.type); + } +} diff --git a/src/install/resolution.zig b/src/install/resolution.zig index b1adb3d80e..38f238bd4b 100644 --- a/src/install/resolution.zig +++ b/src/install/resolution.zig @@ -11,6 +11,7 @@ const VersionedURL = @import("./versioned_url.zig").VersionedURL; pub const Resolution = extern struct { tag: Tag = .uninitialized, + _padding: [7]u8 = .{0} ** 7, value: Value = .{ .uninitialized = {} }, pub fn order( diff --git a/src/install/semver.zig b/src/install/semver.zig index e773f005aa..f76238fa57 100644 --- a/src/install/semver.zig +++ b/src/install/semver.zig @@ -602,6 +602,7 @@ pub const Version = extern struct { major: u32 = 0, minor: u32 = 0, patch: u32 = 0, + _tag_padding: [4]u8 = .{0} ** 4, // [see padding_checker.zig] tag: Tag = .{}, // raw: RawType = RawType{}, From d268097ded4513abe3cff9ca0037f72e90c23a21 Mon Sep 17 00:00:00 2001 From: Ciro Spaciari Date: Tue, 5 Sep 2023 19:21:34 -0300 Subject: [PATCH 4/9] fix SSL proxy tunneling on fetch (#4510) --- src/http_client_async.zig | 55 ++++++++++++++++++++++++---------- test/js/bun/http/proxy.test.js | 38 +++++++++++++++++++++-- 2 files changed, 76 insertions(+), 17 deletions(-) diff --git a/src/http_client_async.zig b/src/http_client_async.zig index 160a35716d..4e477e6bb5 100644 --- a/src/http_client_async.zig +++ b/src/http_client_async.zig @@ -573,14 +573,12 @@ fn NewHTTPContext(comptime ssl: bool) type { client.connected_url = if (client.http_proxy) |proxy| proxy else client.url; client.connected_url.hostname = hostname; - if (comptime FeatureFlags.enable_keepalive) { - if (!client.disable_keepalive) { - if (this.existingSocket(hostname, port)) |sock| { - sock.ext(**anyopaque).?.* = bun.cast(**anyopaque, ActiveSocket.init(client).ptr()); - client.allow_retry = true; - client.onOpen(comptime ssl, sock); - return sock; - } + if (client.isKeepAlivePossible()) { + if (this.existingSocket(hostname, port)) |sock| { + sock.ext(**anyopaque).?.* = bun.cast(**anyopaque, ActiveSocket.init(client).ptr()); + client.allow_retry = true; + client.onOpen(comptime ssl, sock); + return sock; } } @@ -1274,6 +1272,17 @@ pub fn deinit(this: *HTTPClient) void { } } +pub fn isKeepAlivePossible(this: *HTTPClient) bool { + if (comptime FeatureFlags.enable_keepalive) { + // is not possible to reuse Proxy with TSL, so disable keepalive if url is tunneling HTTPS + if (this.http_proxy != null and this.url.isHTTPS()) { + return false; + } + return !this.disable_keepalive; + } + return false; +} + const Stage = enum(u8) { pending, connect, @@ -1508,8 +1517,6 @@ pub const AsyncHTTP = struct { this.timeout = timeout; if (http_proxy) |proxy| { - //TODO: need to understand how is possible to reuse Proxy with TSL, so disable keepalive if url is HTTPS - this.client.disable_keepalive = this.url.isHTTPS(); // Username between 0 and 4096 chars if (proxy.username.len > 0 and proxy.username.len < 4096) { // Password between 0 and 4096 chars @@ -1571,6 +1578,18 @@ pub const AsyncHTTP = struct { return this; } + pub fn isKeepAlivePossible(this: *AsyncHTTP) bool { + if (comptime FeatureFlags.enable_keepalive) { + // is not possible to reuse Proxy with TSL, so disable keepalive if url is tunneling HTTPS + if (this.http_proxy != null and this.url.isHTTPS()) { + return false; + } + // check state + if (this.state.allow_keepalive and !this.disable_keepalive) return true; + } + return false; + } + pub fn initSync(allocator: std.mem.Allocator, method: Method, url: URL, headers: Headers.Entries, headers_buf: string, response_buffer: *MutableString, request_body: []const u8, timeout: usize, http_proxy: ?URL, hostname: ?[]u8, redirect_type: FetchRedirect) AsyncHTTP { return @This().init( allocator, @@ -2018,7 +2037,7 @@ pub fn onWritable(this: *HTTPClient, comptime is_first_call: bool, comptime is_s const headers_len = list.items.len; std.debug.assert(list.items.len == writer.context.items.len); - if (this.state.request_body.len > 0 and list.capacity - list.items.len > 0) { + if (this.state.request_body.len > 0 and list.capacity - list.items.len > 0 and !this.proxy_tunneling) { var remain = list.items.ptr[list.items.len..list.capacity]; const wrote = @min(remain.len, this.state.request_body.len); std.debug.assert(wrote > 0); @@ -2065,7 +2084,11 @@ pub fn onWritable(this: *HTTPClient, comptime is_first_call: bool, comptime is_s } if (has_sent_headers) { - this.state.request_stage = .body; + if (this.proxy_tunneling) { + this.state.request_stage = .proxy_handshake; + } else { + this.state.request_stage = .body; + } std.debug.assert( // we should have leftover data OR we use sendfile() (this.state.original_request_body == .bytes and this.state.request_body.len > 0) or @@ -2122,6 +2145,9 @@ pub fn onWritable(this: *HTTPClient, comptime is_first_call: bool, comptime is_s } }, .proxy_body => { + if (this.state.original_request_body != .bytes) { + @panic("sendfile is only supported without SSL. This code should never have been reached!"); + } var proxy = this.proxy_tunnel orelse return; this.setTimeout(socket, 60); @@ -2257,7 +2283,7 @@ pub fn closeAndFail(this: *HTTPClient, err: anyerror, comptime is_ssl: bool, soc fn startProxySendHeaders(this: *HTTPClient, comptime is_ssl: bool, socket: NewHTTPContext(is_ssl).HTTPSocket) void { this.state.response_stage = .proxy_headers; this.state.request_stage = .proxy_headers; - + this.state.request_sent_len = 0; this.onWritable(true, is_ssl, socket); } @@ -2282,7 +2308,6 @@ fn retryProxyHandshake(this: *HTTPClient, comptime is_ssl: bool, socket: NewHTTP this.startProxySendHeaders(is_ssl, socket); } fn startProxyHandshake(this: *HTTPClient, comptime is_ssl: bool, socket: NewHTTPContext(is_ssl).HTTPSocket) void { - this.state.reset(this.allocator); this.state.response_stage = .proxy_handshake; this.state.request_stage = .proxy_handshake; const proxy = ProxyTunnel.init(is_ssl, this, socket); @@ -2658,7 +2683,7 @@ pub fn progressUpdate(this: *HTTPClient, comptime is_ssl: bool, ctx: *NewHTTPCon if (is_done) { socket.ext(**anyopaque).?.* = bun.cast(**anyopaque, NewHTTPContext(is_ssl).ActiveSocket.init(&dead_socket).ptr()); - if (this.state.allow_keepalive and !this.disable_keepalive and !socket.isClosed() and FeatureFlags.enable_keepalive) { + if (this.isKeepAlivePossible() and !socket.isClosed()) { ctx.releaseSocket( socket, this.connected_url.hostname, diff --git a/test/js/bun/http/proxy.test.js b/test/js/bun/http/proxy.test.js index 85bb7ecf35..48595a2ace 100644 --- a/test/js/bun/http/proxy.test.js +++ b/test/js/bun/http/proxy.test.js @@ -1,10 +1,10 @@ import { afterAll, beforeAll, describe, expect, it } from "bun:test"; import { gc } from "harness"; +import fs from "fs"; +import path from "path"; let proxy, auth_proxy, server; -// TODO: Proxy with TLS requests - beforeAll(() => { proxy = Bun.serve({ port: 0, @@ -76,6 +76,40 @@ afterAll(() => { auth_proxy.stop(); }); +const test = process.env.PROXY_URL ? it : it.skip; + +test("should be able to post on TLS", async () => { + const data = JSON.stringify({ + "name": "bun", + }); + + const result = await fetch("https://httpbin.org/post", { + method: "POST", + proxy: process.env.PROXY_URL, + verbose: true, + headers: { + "Content-Type": "application/json", + }, + body: data, + }).then(res => res.json()); + + expect(result.data).toBe(data); +}); + +test("should be able to post bigger on TLS", async () => { + const data = fs.readFileSync(path.join(import.meta.dir, "fetch.json")).toString("utf8"); + const result = await fetch("https://httpbin.org/post", { + method: "POST", + proxy: process.env.PROXY_URL, + verbose: true, + headers: { + "Content-Type": "application/json", + }, + body: data, + }).then(res => res.json()); + expect(result.data).toBe(data); +}); + it("proxy non-TLS", async () => { const url = `http://localhost:${server.port}`; const auth_proxy_url = `http://squid_user:ASD123%40123asd@localhost:${auth_proxy.port}`; From 6e50dd210fb052a4db4867fa03fe450ce87b4179 Mon Sep 17 00:00:00 2001 From: Ciro Spaciari Date: Tue, 5 Sep 2023 19:22:09 -0300 Subject: [PATCH 5/9] fix(fetch) always use readable stream if it is available (#4503) * always use readable stream if it is available * use bun sleep * fix tests * rm uws dep --- src/bun.js/webcore/response.zig | 57 ++++++++++++++------------ test/js/web/fetch/fetch.stream.test.ts | 52 +++++++++++++++++++++++ 2 files changed, 82 insertions(+), 27 deletions(-) diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig index 8fc282cf07..da16558211 100644 --- a/src/bun.js/webcore/response.zig +++ b/src/bun.js/webcore/response.zig @@ -792,6 +792,36 @@ pub const Fetch = struct { return; } + if (this.readable_stream_ref.get()) |readable| { + if (readable.ptr == .Bytes) { + readable.ptr.Bytes.size_hint = this.getSizeHint(); + // body can be marked as used but we still need to pipe the data + var scheduled_response_buffer = this.scheduled_response_buffer.list; + + const chunk = scheduled_response_buffer.items; + + if (this.result.has_more) { + readable.ptr.Bytes.onData( + .{ + .temporary = bun.ByteList.initConst(chunk), + }, + bun.default_allocator, + ); + + // clean for reuse later + this.scheduled_response_buffer.reset(); + } else { + readable.ptr.Bytes.onData( + .{ + .temporary_and_done = bun.ByteList.initConst(chunk), + }, + bun.default_allocator, + ); + } + return; + } + } + if (this.response.get()) |response_js| { if (response_js.as(Response)) |response| { const body = response.body; @@ -854,33 +884,6 @@ pub const Fetch = struct { old.resolve(&response.body.value, this.global_this); } } - } else if (this.readable_stream_ref.get()) |readable| { - if (readable.ptr == .Bytes) { - readable.ptr.Bytes.size_hint = this.getSizeHint(); - // body can be marked as used but we still need to pipe the data - var scheduled_response_buffer = this.scheduled_response_buffer.list; - - const chunk = scheduled_response_buffer.items; - - if (this.result.has_more) { - readable.ptr.Bytes.onData( - .{ - .temporary = bun.ByteList.initConst(chunk), - }, - bun.default_allocator, - ); - - // clean for reuse later - this.scheduled_response_buffer.reset(); - } else { - readable.ptr.Bytes.onData( - .{ - .temporary_and_done = bun.ByteList.initConst(chunk), - }, - bun.default_allocator, - ); - } - } } } } diff --git a/test/js/web/fetch/fetch.stream.test.ts b/test/js/web/fetch/fetch.stream.test.ts index 49cc0dd6a1..98271ee794 100644 --- a/test/js/web/fetch/fetch.stream.test.ts +++ b/test/js/web/fetch/fetch.stream.test.ts @@ -4,6 +4,17 @@ import { join } from "path"; import { describe, expect, it } from "bun:test"; import { gcTick } from "harness"; import zlib from "zlib"; +import http from "http"; +import { createReadStream } from "fs"; +import { pipeline } from "stream"; +import type { AddressInfo } from "net"; + +const files = [ + join(import.meta.dir, "fixture.html"), + join(import.meta.dir, "fixture.png"), + join(import.meta.dir, "fixture.png.gz"), +]; + const fixtures = { "fixture": readFileSync(join(import.meta.dir, "fixture.html")), "fixture.png": readFileSync(join(import.meta.dir, "fixture.png")), @@ -51,6 +62,47 @@ describe("fetch() with streaming", () => { } }); + for (let file of files) { + it("stream can handle response.body + await response.something() #4500", async () => { + let server: ReturnType | null = null; + try { + const errorHandler = (err: any) => expect(err).toBeUndefined(); + + server = http + .createServer(function (req, res) { + res.writeHead(200, { "Content-Type": "text/plain" }); + + pipeline(createReadStream(file), res, errorHandler); + }) + .listen(0); + + const address = server.address() as AddressInfo; + const url = `http://${address.address}:${address.port}`; + async function getRequestLen(url: string) { + const response = await fetch(url); + const hasBody = response.body; + if (hasBody) { + const res = await response.blob(); + return res.size; + } + return 0; + } + + for (let i = 0; i < 10; i++) { + let len = await getRequestLen(url); + if (len <= 0) { + throw new Error("Request length is 0"); + } + await Bun.sleep(50); + } + + expect(true).toBe(true); + } finally { + server?.close(); + } + }); + } + it("stream still works after response get out of scope", async () => { let server: Server | null = null; try { From 6f8a3934923198cbadae64cda24201e2de2655c1 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Tue, 5 Sep 2023 16:59:40 -0700 Subject: [PATCH 6/9] fix(node:net): emit close event on connection error (#4336) * emit close event on connection error * re-review * add test --- src/js/node/net.js | 8 +++----- src/js/out/InternalModuleRegistryConstants.h | 6 +++--- test/js/node/net/node-net.test.ts | 21 +++++++++++++++++--- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/js/node/net.js b/src/js/node/net.js index 0513f44d3f..f0873ae22d 100644 --- a/src/js/node/net.js +++ b/src/js/node/net.js @@ -89,10 +89,6 @@ const Socket = (function (InternalSocket) { class Socket extends Duplex { static #Handlers = { close: Socket.#Close, - connectError(socket, error) { - const self = socket.data; - self.emit("error", error); - }, data({ data: self }, buffer) { self.bytesRead += buffer.length; const queue = self.#readQueue; @@ -448,8 +444,8 @@ const Socket = (function (InternalSocket) { if (connectListener) this.on("secureConnect", connectListener); } else if (connectListener) this.on("connect", connectListener); - // start using existing connection + // start using existing connection if (connection) { const socket = connection[bunSocketInternal]; @@ -508,6 +504,7 @@ const Socket = (function (InternalSocket) { tls, }).catch(error => { this.emit("error", error); + this.emit("close"); }); } else { // default start @@ -519,6 +516,7 @@ const Socket = (function (InternalSocket) { tls, }).catch(error => { this.emit("error", error); + this.emit("close"); }); } return this; diff --git a/src/js/out/InternalModuleRegistryConstants.h b/src/js/out/InternalModuleRegistryConstants.h index fe872ac304..edcbd8baf4 100644 --- a/src/js/out/InternalModuleRegistryConstants.h +++ b/src/js/out/InternalModuleRegistryConstants.h @@ -106,7 +106,7 @@ static constexpr ASCIILiteral NodeInspectorCode = "(function (){\"use strict\";/ // // -static constexpr ASCIILiteral NodeNetCode = "(function (){\"use strict\";// src/js/out/tmp/node/net.ts\nvar isIPv4 = function(s) {\n return IPv4Reg.test(s);\n}, isIPv6 = function(s) {\n return IPv6Reg.test(s);\n}, isIP = function(s) {\n if (isIPv4(s))\n return 4;\n if (isIPv6(s))\n return 6;\n return 0;\n}, createConnection = function(port, host, connectListener) {\n if (typeof port === \"object\")\n return new Socket(port).connect(port, host, connectListener);\n return new Socket().connect(port, host, connectListener);\n}, emitErrorNextTick = function(self, error) {\n self.emit(\"error\", error);\n}, emitListeningNextTick = function(self, onListen) {\n if (typeof onListen === \"function\")\n try {\n onListen();\n } catch (err) {\n self.emit(\"error\", err);\n }\n self.emit(\"listening\");\n}, createServer = function(options, connectionListener) {\n return new Server(options, connectionListener);\n}, $, { Duplex } = @getInternalField(@internalModuleRegistry, 37) || @createInternalModuleById(37), EventEmitter = @getInternalField(@internalModuleRegistry, 18) || @createInternalModuleById(18);\nvar IPv4Reg = new RegExp(\"^((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$\");\nvar IPv6Reg = new RegExp(\"^((\?:(\?:[0-9a-fA-F]{1,4}):){7}(\?:(\?:[0-9a-fA-F]{1,4})|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){6}(\?:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|:(\?:[0-9a-fA-F]{1,4})|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){5}(\?::((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,2}|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){4}(\?:(:(\?:[0-9a-fA-F]{1,4})){0,1}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,3}|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){3}(\?:(:(\?:[0-9a-fA-F]{1,4})){0,2}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,4}|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){2}(\?:(:(\?:[0-9a-fA-F]{1,4})){0,3}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,5}|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){1}(\?:(:(\?:[0-9a-fA-F]{1,4})){0,4}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,6}|:)|(\?::((\?::(\?:[0-9a-fA-F]{1,4})){0,5}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(\?::(\?:[0-9a-fA-F]{1,4})){1,7}|:)))(%[0-9a-zA-Z-.:]{1,})\?$\"), { connect: bunConnect } = Bun, { setTimeout } = globalThis, bunTlsSymbol = Symbol.for(\"::buntls::\"), bunSocketServerHandlers = Symbol.for(\"::bunsocket_serverhandlers::\"), bunSocketServerConnections = Symbol.for(\"::bunnetserverconnections::\"), bunSocketServerOptions = Symbol.for(\"::bunnetserveroptions::\"), bunSocketInternal = Symbol.for(\"::bunnetsocketinternal::\"), bunTLSConnectOptions = Symbol.for(\"::buntlsconnectoptions::\"), SocketClass, Socket = function(InternalSocket) {\n return SocketClass = InternalSocket, Object.defineProperty(SocketClass.prototype, Symbol.toStringTag, {\n value: \"Socket\",\n enumerable: !1\n }), Object.defineProperty(function Socket(options) {\n return new InternalSocket(options);\n }, Symbol.hasInstance, {\n value(instance) {\n return instance instanceof InternalSocket;\n }\n });\n}(class Socket2 extends Duplex {\n static #Handlers = {\n close: Socket2.#Close,\n connectError(socket, error) {\n socket.data.emit(\"error\", error);\n },\n data({ data: self }, buffer) {\n self.bytesRead += buffer.length;\n const queue = self.#readQueue;\n if (queue.isEmpty()) {\n if (self.push(buffer))\n return;\n }\n queue.push(buffer);\n },\n drain: Socket2.#Drain,\n end: Socket2.#Close,\n error(socket, error) {\n const self = socket.data, callback = self.#writeCallback;\n if (callback)\n self.#writeCallback = null, callback(error);\n self.emit(\"error\", error);\n },\n open(socket) {\n const self = socket.data;\n socket.timeout(self.timeout), socket.ref(), self[bunSocketInternal] = socket, self.connecting = !1;\n const options = self[bunTLSConnectOptions];\n if (options) {\n const { session } = options;\n if (session)\n self.setSession(session);\n }\n if (!self.#upgraded)\n self.emit(\"connect\", self);\n Socket2.#Drain(socket);\n },\n handshake(socket, success, verifyError) {\n const { data: self } = socket;\n self._securePending = !1, self.secureConnecting = !1, self._secureEstablished = !!success, self.emit(\"secure\", self);\n const { checkServerIdentity } = self[bunTLSConnectOptions];\n if (!verifyError && typeof checkServerIdentity === \"function\" && self.servername) {\n const cert = self.getPeerCertificate(!0);\n verifyError = checkServerIdentity(self.servername, cert);\n }\n if (self._requestCert || self._rejectUnauthorized) {\n if (verifyError) {\n if (self.authorized = !1, self.authorizationError = verifyError.code || verifyError.message, self._rejectUnauthorized) {\n self.destroy(verifyError);\n return;\n }\n }\n } else\n self.authorized = !0;\n self.emit(\"secureConnect\", verifyError);\n },\n timeout(socket) {\n const self = socket.data;\n self.emit(\"timeout\", self);\n },\n binaryType: \"buffer\"\n };\n static #Close(socket) {\n const self = socket.data;\n if (self.#closed)\n return;\n self.#closed = !0, self[bunSocketInternal] = null;\n const queue = self.#readQueue;\n if (queue.isEmpty()) {\n if (self.push(null))\n return;\n }\n queue.push(null);\n }\n static #Drain(socket) {\n const self = socket.data, callback = self.#writeCallback;\n if (callback) {\n const chunk = self.#writeChunk, written = socket.write(chunk);\n if (self.bytesWritten += written, written < chunk.length)\n self.#writeChunk = chunk.slice(written);\n else\n self.#writeCallback = null, self.#writeChunk = null, callback(null);\n }\n }\n static [bunSocketServerHandlers] = {\n data: Socket2.#Handlers.data,\n close(socket) {\n Socket2.#Handlers.close(socket), this.data[bunSocketServerConnections]--;\n },\n end(socket) {\n Socket2.#Handlers.end(socket), this.data[bunSocketServerConnections]--;\n },\n open(socket) {\n const self = this.data, options = self[bunSocketServerOptions], { pauseOnConnect, connectionListener, InternalSocketClass, requestCert, rejectUnauthorized } = options, _socket = new InternalSocketClass({});\n if (_socket.isServer = !0, _socket._requestCert = requestCert, _socket._rejectUnauthorized = rejectUnauthorized, _socket.#attach(this.localPort, socket), self.maxConnections && self[bunSocketServerConnections] >= self.maxConnections) {\n const data = {\n localAddress: _socket.localAddress,\n localPort: _socket.localPort,\n localFamily: _socket.localFamily,\n remoteAddress: _socket.remoteAddress,\n remotePort: _socket.remotePort,\n remoteFamily: _socket.remoteFamily || \"IPv4\"\n };\n socket.end(), self.emit(\"drop\", data);\n return;\n }\n if (!pauseOnConnect)\n _socket.resume();\n if (self[bunSocketServerConnections]++, typeof connectionListener == \"function\")\n if (InternalSocketClass.name === \"TLSSocket\")\n self.once(\"secureConnection\", () => connectionListener(_socket));\n else\n connectionListener(_socket);\n self.emit(\"connection\", _socket);\n },\n handshake(socket, success, verifyError) {\n const { data: self } = socket;\n if (self.emit(\"secure\", self), self._securePending = !1, self.secureConnecting = !1, self._secureEstablished = !!success, self._requestCert || self._rejectUnauthorized) {\n if (verifyError) {\n if (self.authorized = !1, self.authorizationError = verifyError.code || verifyError.message, self._rejectUnauthorized) {\n self.destroy(verifyError);\n return;\n }\n }\n } else\n self.authorized = !0;\n self.emit(\"secureConnection\", verifyError);\n },\n error(socket, error) {\n Socket2.#Handlers.error(socket, error), this.data.emit(\"error\", error);\n },\n timeout: Socket2.#Handlers.timeout,\n connectError: Socket2.#Handlers.connectError,\n drain: Socket2.#Handlers.drain,\n binaryType: \"buffer\"\n };\n bytesRead = 0;\n bytesWritten = 0;\n #closed = !1;\n connecting = !1;\n localAddress = \"127.0.0.1\";\n #readQueue = @createFIFO();\n remotePort;\n [bunSocketInternal] = null;\n [bunTLSConnectOptions] = null;\n timeout = 0;\n #writeCallback;\n #writeChunk;\n #pendingRead;\n isServer = !1;\n _handle;\n _parent;\n _parentWrap;\n #socket;\n #upgraded;\n constructor(options) {\n const { socket, signal, write, read, allowHalfOpen = !1, ...opts } = options || {};\n super({\n ...opts,\n allowHalfOpen,\n readable: !0,\n writable: !0\n });\n if (this._handle = this, this._parent = this, this._parentWrap = this, this.#pendingRead = void 0, this.#upgraded = !1, socket instanceof Socket2)\n this.#socket = socket;\n signal\?.once(\"abort\", () => this.destroy()), this.once(\"connect\", () => this.emit(\"ready\"));\n }\n address() {\n return {\n address: this.localAddress,\n family: this.localFamily,\n port: this.localPort\n };\n }\n get bufferSize() {\n return this.writableLength;\n }\n #attach(port, socket) {\n if (this.remotePort = port, socket.data = this, socket.timeout(this.timeout), socket.ref(), this[bunSocketInternal] = socket, this.connecting = !1, !this.#upgraded)\n this.emit(\"connect\", this);\n Socket2.#Drain(socket);\n }\n connect(port, host, connectListener) {\n var path, connection = this.#socket, _checkServerIdentity = void 0;\n if (typeof port === \"string\") {\n if (path = port, port = void 0, typeof host === \"function\")\n connectListener = host, host = void 0;\n } else if (typeof host == \"function\") {\n if (typeof port === \"string\")\n path = port, port = void 0;\n connectListener = host, host = void 0;\n }\n if (typeof port == \"object\") {\n var {\n port,\n host,\n path,\n socket,\n localAddress,\n localPort,\n family,\n hints,\n lookup,\n noDelay,\n keepAlive,\n keepAliveInitialDelay,\n requestCert,\n rejectUnauthorized,\n pauseOnConnect,\n servername,\n checkServerIdentity,\n session\n } = port;\n if (_checkServerIdentity = checkServerIdentity, this.servername = servername, socket)\n connection = socket;\n }\n if (!pauseOnConnect)\n this.resume();\n this.connecting = !0, this.remotePort = port;\n const bunTLS = this[bunTlsSymbol];\n var tls = void 0;\n if (typeof bunTLS === \"function\") {\n if (tls = bunTLS.call(this, port, host, !0), this._requestCert = !0, this._rejectUnauthorized = rejectUnauthorized, tls) {\n if (tls.rejectUnauthorized = rejectUnauthorized, tls.requestCert = !0, tls.session = session || tls.session, this.servername = tls.servername, tls.checkServerIdentity = _checkServerIdentity || tls.checkServerIdentity, this[bunTLSConnectOptions] = tls, !connection && tls.socket)\n connection = tls.socket;\n }\n if (connection) {\n if (typeof connection !== \"object\" || !(connection instanceof Socket2) || typeof connection[bunTlsSymbol] === \"function\")\n @throwTypeError(\"socket must be an instance of net.Socket\");\n }\n if (this.authorized = !1, this.secureConnecting = !0, this._secureEstablished = !1, this._securePending = !0, connectListener)\n this.on(\"secureConnect\", connectListener);\n } else if (connectListener)\n this.on(\"connect\", connectListener);\n if (connection) {\n const socket2 = connection[bunSocketInternal];\n if (socket2) {\n this.connecting = !0, this.#upgraded = !0;\n const result = socket2.upgradeTLS({\n data: this,\n tls,\n socket: Socket2.#Handlers\n });\n if (result) {\n const [raw, tls2] = result;\n connection[bunSocketInternal] = raw, raw.timeout(raw.timeout), raw.connecting = !1, this[bunSocketInternal] = tls2;\n } else\n throw this[bunSocketInternal] = null, new Error(\"Invalid socket\");\n } else\n connection.once(\"connect\", () => {\n const socket3 = connection[bunSocketInternal];\n if (!socket3)\n return;\n this.connecting = !0, this.#upgraded = !0;\n const result = socket3.upgradeTLS({\n data: this,\n tls,\n socket: Socket2.#Handlers\n });\n if (result) {\n const [raw, tls2] = result;\n connection[bunSocketInternal] = raw, raw.timeout(raw.timeout), raw.connecting = !1, this[bunSocketInternal] = tls2;\n } else\n throw this[bunSocketInternal] = null, new Error(\"Invalid socket\");\n });\n } else if (path)\n bunConnect({\n data: this,\n unix: path,\n socket: Socket2.#Handlers,\n tls\n }).catch((error) => {\n this.emit(\"error\", error);\n });\n else\n bunConnect({\n data: this,\n hostname: host || \"localhost\",\n port,\n socket: Socket2.#Handlers,\n tls\n }).catch((error) => {\n this.emit(\"error\", error);\n });\n return this;\n }\n _destroy(err, callback) {\n this[bunSocketInternal]\?.end(), callback(err);\n }\n _final(callback) {\n this[bunSocketInternal]\?.end(), callback();\n }\n get localAddress() {\n return \"127.0.0.1\";\n }\n get localFamily() {\n return \"IPv4\";\n }\n get localPort() {\n return this[bunSocketInternal]\?.localPort;\n }\n get pending() {\n return this.connecting;\n }\n _read(size) {\n const queue = this.#readQueue;\n let chunk;\n while (chunk = queue.peek()) {\n if (!this.push(chunk))\n return;\n queue.shift();\n }\n }\n get readyState() {\n if (this.connecting)\n return \"opening\";\n if (this.readable)\n return this.writable \? \"open\" : \"readOnly\";\n else\n return this.writable \? \"writeOnly\" : \"closed\";\n }\n ref() {\n this[bunSocketInternal]\?.ref();\n }\n get remoteAddress() {\n return this[bunSocketInternal]\?.remoteAddress;\n }\n get remoteFamily() {\n return \"IPv4\";\n }\n resetAndDestroy() {\n this[bunSocketInternal]\?.end();\n }\n setKeepAlive(enable = !1, initialDelay = 0) {\n return this;\n }\n setNoDelay(noDelay = !0) {\n return this;\n }\n setTimeout(timeout, callback) {\n if (this[bunSocketInternal]\?.timeout(timeout), this.timeout = timeout, callback)\n this.once(\"timeout\", callback);\n return this;\n }\n unref() {\n this[bunSocketInternal]\?.unref();\n }\n _write(chunk, encoding, callback) {\n if (typeof chunk == \"string\" && encoding !== \"ascii\")\n chunk = Buffer.from(chunk, encoding);\n var written = this[bunSocketInternal]\?.write(chunk);\n if (written == chunk.length)\n callback();\n else if (this.#writeCallback)\n callback(new Error(\"overlapping _write()\"));\n else {\n if (written > 0)\n if (typeof chunk == \"string\")\n chunk = chunk.slice(written);\n else\n chunk = chunk.subarray(written);\n this.#writeCallback = callback, this.#writeChunk = chunk;\n }\n }\n}), connect = createConnection;\n\nclass Server extends EventEmitter {\n #server;\n #listening = !1;\n [bunSocketServerConnections] = 0;\n [bunSocketServerOptions];\n maxConnections = 0;\n constructor(options, connectionListener) {\n super();\n if (typeof options === \"function\")\n connectionListener = options, options = {};\n else if (options == null || typeof options === \"object\")\n options = { ...options };\n else\n throw new Error(\"bun-net-polyfill: invalid arguments\");\n const { maxConnections } = options;\n this.maxConnections = Number.isSafeInteger(maxConnections) && maxConnections > 0 \? maxConnections : 0, options.connectionListener = connectionListener, this[bunSocketServerOptions] = options;\n }\n ref() {\n return this.#server\?.ref(), this;\n }\n unref() {\n return this.#server\?.unref(), this;\n }\n close(callback) {\n if (this.#server) {\n if (this.#server.stop(!0), this.#server = null, this.#listening = !1, this[bunSocketServerConnections] = 0, this.emit(\"close\"), typeof callback === \"function\")\n callback();\n return this;\n }\n if (typeof callback === \"function\") {\n const error = new Error(\"Server is not running\");\n error.code = \"ERR_SERVER_NOT_RUNNING\", callback(error);\n }\n return this;\n }\n address() {\n const server = this.#server;\n if (server) {\n const unix = server.unix;\n if (unix)\n return unix;\n let address = server.hostname;\n const type = isIP(address), port = server.port;\n if (typeof port === \"number\")\n return {\n port,\n address,\n family: type \? `IPv${type}` : void 0\n };\n if (type)\n return {\n address,\n family: type \? `IPv${type}` : void 0\n };\n return address;\n }\n return null;\n }\n getConnections(callback) {\n if (typeof callback === \"function\")\n callback(null, this.#server \? this[bunSocketServerConnections] : 0);\n return this;\n }\n listen(port, hostname, onListen) {\n let backlog, path, exclusive = !1;\n if (typeof port === \"string\") {\n if (Number.isSafeInteger(hostname)) {\n if (hostname > 0)\n backlog = hostname;\n } else if (typeof hostname === \"function\")\n onListen = hostname;\n path = port, hostname = void 0, port = void 0;\n } else {\n if (typeof hostname === \"function\")\n onListen = hostname, hostname = void 0;\n if (typeof port === \"function\")\n onListen = port, port = 0;\n else if (typeof port === \"object\") {\n const options = port;\n options.signal\?.addEventListener(\"abort\", () => this.close()), hostname = options.host, exclusive = options.exclusive === !0;\n const path2 = options.path;\n if (port = options.port, !Number.isSafeInteger(port) || port < 0)\n if (path2)\n hostname = path2, port = void 0;\n else {\n let message = 'The argument \\'options\\' must have the property \"port\" or \"path\"';\n try {\n message = `${message}. Received ${JSON.stringify(options)}`;\n } catch {\n }\n const error = @makeTypeError(message);\n throw error.code = \"ERR_INVALID_ARG_VALUE\", error;\n }\n else if (!Number.isSafeInteger(port) || port < 0)\n port = 0;\n if (typeof port.callback === \"function\")\n onListen = port\?.callback;\n } else if (!Number.isSafeInteger(port) || port < 0)\n port = 0;\n hostname = hostname || \"::\";\n }\n try {\n var tls = void 0, TLSSocketClass = void 0;\n const bunTLS = this[bunTlsSymbol], options = this[bunSocketServerOptions];\n if (typeof bunTLS === \"function\")\n [tls, TLSSocketClass] = bunTLS.call(this, port, hostname, !1), options.servername = tls.serverName, options.InternalSocketClass = TLSSocketClass;\n else\n options.InternalSocketClass = SocketClass;\n this.#server = Bun.listen(path \? {\n exclusive,\n unix: path,\n tls,\n socket: SocketClass[bunSocketServerHandlers]\n } : {\n exclusive,\n port,\n hostname,\n tls,\n socket: SocketClass[bunSocketServerHandlers]\n }), this.#server.data = this, this.#listening = !0, setTimeout(emitListeningNextTick, 1, this, onListen);\n } catch (err) {\n this.#listening = !1, setTimeout(emitErrorNextTick, 1, this, err);\n }\n return this;\n }\n}\n$ = {\n createServer,\n Server,\n createConnection,\n connect,\n isIP,\n isIPv4,\n isIPv6,\n Socket,\n [Symbol.for(\"::bunternal::\")]: SocketClass\n};\nreturn $})\n"_s; +static constexpr ASCIILiteral NodeNetCode = "(function (){\"use strict\";// src/js/out/tmp/node/net.ts\nvar isIPv4 = function(s) {\n return IPv4Reg.test(s);\n}, isIPv6 = function(s) {\n return IPv6Reg.test(s);\n}, isIP = function(s) {\n if (isIPv4(s))\n return 4;\n if (isIPv6(s))\n return 6;\n return 0;\n}, createConnection = function(port, host, connectListener) {\n if (typeof port === \"object\")\n return new Socket(port).connect(port, host, connectListener);\n return new Socket().connect(port, host, connectListener);\n}, emitErrorNextTick = function(self, error) {\n self.emit(\"error\", error);\n}, emitListeningNextTick = function(self, onListen) {\n if (typeof onListen === \"function\")\n try {\n onListen();\n } catch (err) {\n self.emit(\"error\", err);\n }\n self.emit(\"listening\");\n}, createServer = function(options, connectionListener) {\n return new Server(options, connectionListener);\n}, $, { Duplex } = @getInternalField(@internalModuleRegistry, 37) || @createInternalModuleById(37), EventEmitter = @getInternalField(@internalModuleRegistry, 18) || @createInternalModuleById(18);\nvar IPv4Reg = new RegExp(\"^((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$\");\nvar IPv6Reg = new RegExp(\"^((\?:(\?:[0-9a-fA-F]{1,4}):){7}(\?:(\?:[0-9a-fA-F]{1,4})|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){6}(\?:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|:(\?:[0-9a-fA-F]{1,4})|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){5}(\?::((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,2}|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){4}(\?:(:(\?:[0-9a-fA-F]{1,4})){0,1}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,3}|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){3}(\?:(:(\?:[0-9a-fA-F]{1,4})){0,2}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,4}|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){2}(\?:(:(\?:[0-9a-fA-F]{1,4})){0,3}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,5}|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){1}(\?:(:(\?:[0-9a-fA-F]{1,4})){0,4}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,6}|:)|(\?::((\?::(\?:[0-9a-fA-F]{1,4})){0,5}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(\?::(\?:[0-9a-fA-F]{1,4})){1,7}|:)))(%[0-9a-zA-Z-.:]{1,})\?$\"), { connect: bunConnect } = Bun, { setTimeout } = globalThis, bunTlsSymbol = Symbol.for(\"::buntls::\"), bunSocketServerHandlers = Symbol.for(\"::bunsocket_serverhandlers::\"), bunSocketServerConnections = Symbol.for(\"::bunnetserverconnections::\"), bunSocketServerOptions = Symbol.for(\"::bunnetserveroptions::\"), bunSocketInternal = Symbol.for(\"::bunnetsocketinternal::\"), bunTLSConnectOptions = Symbol.for(\"::buntlsconnectoptions::\"), SocketClass, Socket = function(InternalSocket) {\n return SocketClass = InternalSocket, Object.defineProperty(SocketClass.prototype, Symbol.toStringTag, {\n value: \"Socket\",\n enumerable: !1\n }), Object.defineProperty(function Socket(options) {\n return new InternalSocket(options);\n }, Symbol.hasInstance, {\n value(instance) {\n return instance instanceof InternalSocket;\n }\n });\n}(class Socket2 extends Duplex {\n static #Handlers = {\n close: Socket2.#Close,\n data({ data: self }, buffer) {\n self.bytesRead += buffer.length;\n const queue = self.#readQueue;\n if (queue.isEmpty()) {\n if (self.push(buffer))\n return;\n }\n queue.push(buffer);\n },\n drain: Socket2.#Drain,\n end: Socket2.#Close,\n error(socket, error) {\n const self = socket.data, callback = self.#writeCallback;\n if (callback)\n self.#writeCallback = null, callback(error);\n self.emit(\"error\", error);\n },\n open(socket) {\n const self = socket.data;\n socket.timeout(self.timeout), socket.ref(), self[bunSocketInternal] = socket, self.connecting = !1;\n const options = self[bunTLSConnectOptions];\n if (options) {\n const { session } = options;\n if (session)\n self.setSession(session);\n }\n if (!self.#upgraded)\n self.emit(\"connect\", self);\n Socket2.#Drain(socket);\n },\n handshake(socket, success, verifyError) {\n const { data: self } = socket;\n self._securePending = !1, self.secureConnecting = !1, self._secureEstablished = !!success, self.emit(\"secure\", self);\n const { checkServerIdentity } = self[bunTLSConnectOptions];\n if (!verifyError && typeof checkServerIdentity === \"function\" && self.servername) {\n const cert = self.getPeerCertificate(!0);\n verifyError = checkServerIdentity(self.servername, cert);\n }\n if (self._requestCert || self._rejectUnauthorized) {\n if (verifyError) {\n if (self.authorized = !1, self.authorizationError = verifyError.code || verifyError.message, self._rejectUnauthorized) {\n self.destroy(verifyError);\n return;\n }\n }\n } else\n self.authorized = !0;\n self.emit(\"secureConnect\", verifyError);\n },\n timeout(socket) {\n const self = socket.data;\n self.emit(\"timeout\", self);\n },\n binaryType: \"buffer\"\n };\n static #Close(socket) {\n const self = socket.data;\n if (self.#closed)\n return;\n self.#closed = !0, self[bunSocketInternal] = null;\n const queue = self.#readQueue;\n if (queue.isEmpty()) {\n if (self.push(null))\n return;\n }\n queue.push(null);\n }\n static #Drain(socket) {\n const self = socket.data, callback = self.#writeCallback;\n if (callback) {\n const chunk = self.#writeChunk, written = socket.write(chunk);\n if (self.bytesWritten += written, written < chunk.length)\n self.#writeChunk = chunk.slice(written);\n else\n self.#writeCallback = null, self.#writeChunk = null, callback(null);\n }\n }\n static [bunSocketServerHandlers] = {\n data: Socket2.#Handlers.data,\n close(socket) {\n Socket2.#Handlers.close(socket), this.data[bunSocketServerConnections]--;\n },\n end(socket) {\n Socket2.#Handlers.end(socket), this.data[bunSocketServerConnections]--;\n },\n open(socket) {\n const self = this.data, options = self[bunSocketServerOptions], { pauseOnConnect, connectionListener, InternalSocketClass, requestCert, rejectUnauthorized } = options, _socket = new InternalSocketClass({});\n if (_socket.isServer = !0, _socket._requestCert = requestCert, _socket._rejectUnauthorized = rejectUnauthorized, _socket.#attach(this.localPort, socket), self.maxConnections && self[bunSocketServerConnections] >= self.maxConnections) {\n const data = {\n localAddress: _socket.localAddress,\n localPort: _socket.localPort,\n localFamily: _socket.localFamily,\n remoteAddress: _socket.remoteAddress,\n remotePort: _socket.remotePort,\n remoteFamily: _socket.remoteFamily || \"IPv4\"\n };\n socket.end(), self.emit(\"drop\", data);\n return;\n }\n if (!pauseOnConnect)\n _socket.resume();\n if (self[bunSocketServerConnections]++, typeof connectionListener == \"function\")\n if (InternalSocketClass.name === \"TLSSocket\")\n self.once(\"secureConnection\", () => connectionListener(_socket));\n else\n connectionListener(_socket);\n self.emit(\"connection\", _socket);\n },\n handshake(socket, success, verifyError) {\n const { data: self } = socket;\n if (self.emit(\"secure\", self), self._securePending = !1, self.secureConnecting = !1, self._secureEstablished = !!success, self._requestCert || self._rejectUnauthorized) {\n if (verifyError) {\n if (self.authorized = !1, self.authorizationError = verifyError.code || verifyError.message, self._rejectUnauthorized) {\n self.destroy(verifyError);\n return;\n }\n }\n } else\n self.authorized = !0;\n self.emit(\"secureConnection\", verifyError);\n },\n error(socket, error) {\n Socket2.#Handlers.error(socket, error), this.data.emit(\"error\", error);\n },\n timeout: Socket2.#Handlers.timeout,\n connectError: Socket2.#Handlers.connectError,\n drain: Socket2.#Handlers.drain,\n binaryType: \"buffer\"\n };\n bytesRead = 0;\n bytesWritten = 0;\n #closed = !1;\n connecting = !1;\n localAddress = \"127.0.0.1\";\n #readQueue = @createFIFO();\n remotePort;\n [bunSocketInternal] = null;\n [bunTLSConnectOptions] = null;\n timeout = 0;\n #writeCallback;\n #writeChunk;\n #pendingRead;\n isServer = !1;\n _handle;\n _parent;\n _parentWrap;\n #socket;\n #upgraded;\n constructor(options) {\n const { socket, signal, write, read, allowHalfOpen = !1, ...opts } = options || {};\n super({\n ...opts,\n allowHalfOpen,\n readable: !0,\n writable: !0\n });\n if (this._handle = this, this._parent = this, this._parentWrap = this, this.#pendingRead = void 0, this.#upgraded = !1, socket instanceof Socket2)\n this.#socket = socket;\n signal\?.once(\"abort\", () => this.destroy()), this.once(\"connect\", () => this.emit(\"ready\"));\n }\n address() {\n return {\n address: this.localAddress,\n family: this.localFamily,\n port: this.localPort\n };\n }\n get bufferSize() {\n return this.writableLength;\n }\n #attach(port, socket) {\n if (this.remotePort = port, socket.data = this, socket.timeout(this.timeout), socket.ref(), this[bunSocketInternal] = socket, this.connecting = !1, !this.#upgraded)\n this.emit(\"connect\", this);\n Socket2.#Drain(socket);\n }\n connect(port, host, connectListener) {\n var path, connection = this.#socket, _checkServerIdentity = void 0;\n if (typeof port === \"string\") {\n if (path = port, port = void 0, typeof host === \"function\")\n connectListener = host, host = void 0;\n } else if (typeof host == \"function\") {\n if (typeof port === \"string\")\n path = port, port = void 0;\n connectListener = host, host = void 0;\n }\n if (typeof port == \"object\") {\n var {\n port,\n host,\n path,\n socket,\n localAddress,\n localPort,\n family,\n hints,\n lookup,\n noDelay,\n keepAlive,\n keepAliveInitialDelay,\n requestCert,\n rejectUnauthorized,\n pauseOnConnect,\n servername,\n checkServerIdentity,\n session\n } = port;\n if (_checkServerIdentity = checkServerIdentity, this.servername = servername, socket)\n connection = socket;\n }\n if (!pauseOnConnect)\n this.resume();\n this.connecting = !0, this.remotePort = port;\n const bunTLS = this[bunTlsSymbol];\n var tls = void 0;\n if (typeof bunTLS === \"function\") {\n if (tls = bunTLS.call(this, port, host, !0), this._requestCert = !0, this._rejectUnauthorized = rejectUnauthorized, tls) {\n if (tls.rejectUnauthorized = rejectUnauthorized, tls.requestCert = !0, tls.session = session || tls.session, this.servername = tls.servername, tls.checkServerIdentity = _checkServerIdentity || tls.checkServerIdentity, this[bunTLSConnectOptions] = tls, !connection && tls.socket)\n connection = tls.socket;\n }\n if (connection) {\n if (typeof connection !== \"object\" || !(connection instanceof Socket2) || typeof connection[bunTlsSymbol] === \"function\")\n @throwTypeError(\"socket must be an instance of net.Socket\");\n }\n if (this.authorized = !1, this.secureConnecting = !0, this._secureEstablished = !1, this._securePending = !0, connectListener)\n this.on(\"secureConnect\", connectListener);\n } else if (connectListener)\n this.on(\"connect\", connectListener);\n if (connection) {\n const socket2 = connection[bunSocketInternal];\n if (socket2) {\n this.connecting = !0, this.#upgraded = !0;\n const result = socket2.upgradeTLS({\n data: this,\n tls,\n socket: Socket2.#Handlers\n });\n if (result) {\n const [raw, tls2] = result;\n connection[bunSocketInternal] = raw, raw.timeout(raw.timeout), raw.connecting = !1, this[bunSocketInternal] = tls2;\n } else\n throw this[bunSocketInternal] = null, new Error(\"Invalid socket\");\n } else\n connection.once(\"connect\", () => {\n const socket3 = connection[bunSocketInternal];\n if (!socket3)\n return;\n this.connecting = !0, this.#upgraded = !0;\n const result = socket3.upgradeTLS({\n data: this,\n tls,\n socket: Socket2.#Handlers\n });\n if (result) {\n const [raw, tls2] = result;\n connection[bunSocketInternal] = raw, raw.timeout(raw.timeout), raw.connecting = !1, this[bunSocketInternal] = tls2;\n } else\n throw this[bunSocketInternal] = null, new Error(\"Invalid socket\");\n });\n } else if (path)\n bunConnect({\n data: this,\n unix: path,\n socket: Socket2.#Handlers,\n tls\n }).catch((error) => {\n this.emit(\"error\", error), this.emit(\"close\");\n });\n else\n bunConnect({\n data: this,\n hostname: host || \"localhost\",\n port,\n socket: Socket2.#Handlers,\n tls\n }).catch((error) => {\n this.emit(\"error\", error), this.emit(\"close\");\n });\n return this;\n }\n _destroy(err, callback) {\n this[bunSocketInternal]\?.end(), callback(err);\n }\n _final(callback) {\n this[bunSocketInternal]\?.end(), callback();\n }\n get localAddress() {\n return \"127.0.0.1\";\n }\n get localFamily() {\n return \"IPv4\";\n }\n get localPort() {\n return this[bunSocketInternal]\?.localPort;\n }\n get pending() {\n return this.connecting;\n }\n _read(size) {\n const queue = this.#readQueue;\n let chunk;\n while (chunk = queue.peek()) {\n if (!this.push(chunk))\n return;\n queue.shift();\n }\n }\n get readyState() {\n if (this.connecting)\n return \"opening\";\n if (this.readable)\n return this.writable \? \"open\" : \"readOnly\";\n else\n return this.writable \? \"writeOnly\" : \"closed\";\n }\n ref() {\n this[bunSocketInternal]\?.ref();\n }\n get remoteAddress() {\n return this[bunSocketInternal]\?.remoteAddress;\n }\n get remoteFamily() {\n return \"IPv4\";\n }\n resetAndDestroy() {\n this[bunSocketInternal]\?.end();\n }\n setKeepAlive(enable = !1, initialDelay = 0) {\n return this;\n }\n setNoDelay(noDelay = !0) {\n return this;\n }\n setTimeout(timeout, callback) {\n if (this[bunSocketInternal]\?.timeout(timeout), this.timeout = timeout, callback)\n this.once(\"timeout\", callback);\n return this;\n }\n unref() {\n this[bunSocketInternal]\?.unref();\n }\n _write(chunk, encoding, callback) {\n if (typeof chunk == \"string\" && encoding !== \"ascii\")\n chunk = Buffer.from(chunk, encoding);\n var written = this[bunSocketInternal]\?.write(chunk);\n if (written == chunk.length)\n callback();\n else if (this.#writeCallback)\n callback(new Error(\"overlapping _write()\"));\n else {\n if (written > 0)\n if (typeof chunk == \"string\")\n chunk = chunk.slice(written);\n else\n chunk = chunk.subarray(written);\n this.#writeCallback = callback, this.#writeChunk = chunk;\n }\n }\n}), connect = createConnection;\n\nclass Server extends EventEmitter {\n #server;\n #listening = !1;\n [bunSocketServerConnections] = 0;\n [bunSocketServerOptions];\n maxConnections = 0;\n constructor(options, connectionListener) {\n super();\n if (typeof options === \"function\")\n connectionListener = options, options = {};\n else if (options == null || typeof options === \"object\")\n options = { ...options };\n else\n throw new Error(\"bun-net-polyfill: invalid arguments\");\n const { maxConnections } = options;\n this.maxConnections = Number.isSafeInteger(maxConnections) && maxConnections > 0 \? maxConnections : 0, options.connectionListener = connectionListener, this[bunSocketServerOptions] = options;\n }\n ref() {\n return this.#server\?.ref(), this;\n }\n unref() {\n return this.#server\?.unref(), this;\n }\n close(callback) {\n if (this.#server) {\n if (this.#server.stop(!0), this.#server = null, this.#listening = !1, this[bunSocketServerConnections] = 0, this.emit(\"close\"), typeof callback === \"function\")\n callback();\n return this;\n }\n if (typeof callback === \"function\") {\n const error = new Error(\"Server is not running\");\n error.code = \"ERR_SERVER_NOT_RUNNING\", callback(error);\n }\n return this;\n }\n address() {\n const server = this.#server;\n if (server) {\n const unix = server.unix;\n if (unix)\n return unix;\n let address = server.hostname;\n const type = isIP(address), port = server.port;\n if (typeof port === \"number\")\n return {\n port,\n address,\n family: type \? `IPv${type}` : void 0\n };\n if (type)\n return {\n address,\n family: type \? `IPv${type}` : void 0\n };\n return address;\n }\n return null;\n }\n getConnections(callback) {\n if (typeof callback === \"function\")\n callback(null, this.#server \? this[bunSocketServerConnections] : 0);\n return this;\n }\n listen(port, hostname, onListen) {\n let backlog, path, exclusive = !1;\n if (typeof port === \"string\") {\n if (Number.isSafeInteger(hostname)) {\n if (hostname > 0)\n backlog = hostname;\n } else if (typeof hostname === \"function\")\n onListen = hostname;\n path = port, hostname = void 0, port = void 0;\n } else {\n if (typeof hostname === \"function\")\n onListen = hostname, hostname = void 0;\n if (typeof port === \"function\")\n onListen = port, port = 0;\n else if (typeof port === \"object\") {\n const options = port;\n options.signal\?.addEventListener(\"abort\", () => this.close()), hostname = options.host, exclusive = options.exclusive === !0;\n const path2 = options.path;\n if (port = options.port, !Number.isSafeInteger(port) || port < 0)\n if (path2)\n hostname = path2, port = void 0;\n else {\n let message = 'The argument \\'options\\' must have the property \"port\" or \"path\"';\n try {\n message = `${message}. Received ${JSON.stringify(options)}`;\n } catch {\n }\n const error = @makeTypeError(message);\n throw error.code = \"ERR_INVALID_ARG_VALUE\", error;\n }\n else if (!Number.isSafeInteger(port) || port < 0)\n port = 0;\n if (typeof port.callback === \"function\")\n onListen = port\?.callback;\n } else if (!Number.isSafeInteger(port) || port < 0)\n port = 0;\n hostname = hostname || \"::\";\n }\n try {\n var tls = void 0, TLSSocketClass = void 0;\n const bunTLS = this[bunTlsSymbol], options = this[bunSocketServerOptions];\n if (typeof bunTLS === \"function\")\n [tls, TLSSocketClass] = bunTLS.call(this, port, hostname, !1), options.servername = tls.serverName, options.InternalSocketClass = TLSSocketClass;\n else\n options.InternalSocketClass = SocketClass;\n this.#server = Bun.listen(path \? {\n exclusive,\n unix: path,\n tls,\n socket: SocketClass[bunSocketServerHandlers]\n } : {\n exclusive,\n port,\n hostname,\n tls,\n socket: SocketClass[bunSocketServerHandlers]\n }), this.#server.data = this, this.#listening = !0, setTimeout(emitListeningNextTick, 1, this, onListen);\n } catch (err) {\n this.#listening = !1, setTimeout(emitErrorNextTick, 1, this, err);\n }\n return this;\n }\n}\n$ = {\n createServer,\n Server,\n createConnection,\n connect,\n isIP,\n isIPv4,\n isIPv6,\n Socket,\n [Symbol.for(\"::bunternal::\")]: SocketClass\n};\nreturn $})\n"_s; // // @@ -347,7 +347,7 @@ static constexpr ASCIILiteral NodeInspectorCode = "(function (){\"use strict\";/ // // -static constexpr ASCIILiteral NodeNetCode = "(function (){\"use strict\";// src/js/out/tmp/node/net.ts\nvar isIPv4 = function(s) {\n return IPv4Reg.test(s);\n}, isIPv6 = function(s) {\n return IPv6Reg.test(s);\n}, isIP = function(s) {\n if (isIPv4(s))\n return 4;\n if (isIPv6(s))\n return 6;\n return 0;\n}, createConnection = function(port, host, connectListener) {\n if (typeof port === \"object\")\n return new Socket(port).connect(port, host, connectListener);\n return new Socket().connect(port, host, connectListener);\n}, emitErrorNextTick = function(self, error) {\n self.emit(\"error\", error);\n}, emitListeningNextTick = function(self, onListen) {\n if (typeof onListen === \"function\")\n try {\n onListen();\n } catch (err) {\n self.emit(\"error\", err);\n }\n self.emit(\"listening\");\n}, createServer = function(options, connectionListener) {\n return new Server(options, connectionListener);\n}, $, { Duplex } = @getInternalField(@internalModuleRegistry, 37) || @createInternalModuleById(37), EventEmitter = @getInternalField(@internalModuleRegistry, 18) || @createInternalModuleById(18);\nvar IPv4Reg = new RegExp(\"^((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$\");\nvar IPv6Reg = new RegExp(\"^((\?:(\?:[0-9a-fA-F]{1,4}):){7}(\?:(\?:[0-9a-fA-F]{1,4})|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){6}(\?:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|:(\?:[0-9a-fA-F]{1,4})|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){5}(\?::((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,2}|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){4}(\?:(:(\?:[0-9a-fA-F]{1,4})){0,1}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,3}|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){3}(\?:(:(\?:[0-9a-fA-F]{1,4})){0,2}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,4}|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){2}(\?:(:(\?:[0-9a-fA-F]{1,4})){0,3}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,5}|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){1}(\?:(:(\?:[0-9a-fA-F]{1,4})){0,4}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,6}|:)|(\?::((\?::(\?:[0-9a-fA-F]{1,4})){0,5}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(\?::(\?:[0-9a-fA-F]{1,4})){1,7}|:)))(%[0-9a-zA-Z-.:]{1,})\?$\"), { connect: bunConnect } = Bun, { setTimeout } = globalThis, bunTlsSymbol = Symbol.for(\"::buntls::\"), bunSocketServerHandlers = Symbol.for(\"::bunsocket_serverhandlers::\"), bunSocketServerConnections = Symbol.for(\"::bunnetserverconnections::\"), bunSocketServerOptions = Symbol.for(\"::bunnetserveroptions::\"), bunSocketInternal = Symbol.for(\"::bunnetsocketinternal::\"), bunTLSConnectOptions = Symbol.for(\"::buntlsconnectoptions::\"), SocketClass, Socket = function(InternalSocket) {\n return SocketClass = InternalSocket, Object.defineProperty(SocketClass.prototype, Symbol.toStringTag, {\n value: \"Socket\",\n enumerable: !1\n }), Object.defineProperty(function Socket(options) {\n return new InternalSocket(options);\n }, Symbol.hasInstance, {\n value(instance) {\n return instance instanceof InternalSocket;\n }\n });\n}(class Socket2 extends Duplex {\n static #Handlers = {\n close: Socket2.#Close,\n connectError(socket, error) {\n socket.data.emit(\"error\", error);\n },\n data({ data: self }, buffer) {\n self.bytesRead += buffer.length;\n const queue = self.#readQueue;\n if (queue.isEmpty()) {\n if (self.push(buffer))\n return;\n }\n queue.push(buffer);\n },\n drain: Socket2.#Drain,\n end: Socket2.#Close,\n error(socket, error) {\n const self = socket.data, callback = self.#writeCallback;\n if (callback)\n self.#writeCallback = null, callback(error);\n self.emit(\"error\", error);\n },\n open(socket) {\n const self = socket.data;\n socket.timeout(self.timeout), socket.ref(), self[bunSocketInternal] = socket, self.connecting = !1;\n const options = self[bunTLSConnectOptions];\n if (options) {\n const { session } = options;\n if (session)\n self.setSession(session);\n }\n if (!self.#upgraded)\n self.emit(\"connect\", self);\n Socket2.#Drain(socket);\n },\n handshake(socket, success, verifyError) {\n const { data: self } = socket;\n self._securePending = !1, self.secureConnecting = !1, self._secureEstablished = !!success, self.emit(\"secure\", self);\n const { checkServerIdentity } = self[bunTLSConnectOptions];\n if (!verifyError && typeof checkServerIdentity === \"function\" && self.servername) {\n const cert = self.getPeerCertificate(!0);\n verifyError = checkServerIdentity(self.servername, cert);\n }\n if (self._requestCert || self._rejectUnauthorized) {\n if (verifyError) {\n if (self.authorized = !1, self.authorizationError = verifyError.code || verifyError.message, self._rejectUnauthorized) {\n self.destroy(verifyError);\n return;\n }\n }\n } else\n self.authorized = !0;\n self.emit(\"secureConnect\", verifyError);\n },\n timeout(socket) {\n const self = socket.data;\n self.emit(\"timeout\", self);\n },\n binaryType: \"buffer\"\n };\n static #Close(socket) {\n const self = socket.data;\n if (self.#closed)\n return;\n self.#closed = !0, self[bunSocketInternal] = null;\n const queue = self.#readQueue;\n if (queue.isEmpty()) {\n if (self.push(null))\n return;\n }\n queue.push(null);\n }\n static #Drain(socket) {\n const self = socket.data, callback = self.#writeCallback;\n if (callback) {\n const chunk = self.#writeChunk, written = socket.write(chunk);\n if (self.bytesWritten += written, written < chunk.length)\n self.#writeChunk = chunk.slice(written);\n else\n self.#writeCallback = null, self.#writeChunk = null, callback(null);\n }\n }\n static [bunSocketServerHandlers] = {\n data: Socket2.#Handlers.data,\n close(socket) {\n Socket2.#Handlers.close(socket), this.data[bunSocketServerConnections]--;\n },\n end(socket) {\n Socket2.#Handlers.end(socket), this.data[bunSocketServerConnections]--;\n },\n open(socket) {\n const self = this.data, options = self[bunSocketServerOptions], { pauseOnConnect, connectionListener, InternalSocketClass, requestCert, rejectUnauthorized } = options, _socket = new InternalSocketClass({});\n if (_socket.isServer = !0, _socket._requestCert = requestCert, _socket._rejectUnauthorized = rejectUnauthorized, _socket.#attach(this.localPort, socket), self.maxConnections && self[bunSocketServerConnections] >= self.maxConnections) {\n const data = {\n localAddress: _socket.localAddress,\n localPort: _socket.localPort,\n localFamily: _socket.localFamily,\n remoteAddress: _socket.remoteAddress,\n remotePort: _socket.remotePort,\n remoteFamily: _socket.remoteFamily || \"IPv4\"\n };\n socket.end(), self.emit(\"drop\", data);\n return;\n }\n if (!pauseOnConnect)\n _socket.resume();\n if (self[bunSocketServerConnections]++, typeof connectionListener == \"function\")\n if (InternalSocketClass.name === \"TLSSocket\")\n self.once(\"secureConnection\", () => connectionListener(_socket));\n else\n connectionListener(_socket);\n self.emit(\"connection\", _socket);\n },\n handshake(socket, success, verifyError) {\n const { data: self } = socket;\n if (self.emit(\"secure\", self), self._securePending = !1, self.secureConnecting = !1, self._secureEstablished = !!success, self._requestCert || self._rejectUnauthorized) {\n if (verifyError) {\n if (self.authorized = !1, self.authorizationError = verifyError.code || verifyError.message, self._rejectUnauthorized) {\n self.destroy(verifyError);\n return;\n }\n }\n } else\n self.authorized = !0;\n self.emit(\"secureConnection\", verifyError);\n },\n error(socket, error) {\n Socket2.#Handlers.error(socket, error), this.data.emit(\"error\", error);\n },\n timeout: Socket2.#Handlers.timeout,\n connectError: Socket2.#Handlers.connectError,\n drain: Socket2.#Handlers.drain,\n binaryType: \"buffer\"\n };\n bytesRead = 0;\n bytesWritten = 0;\n #closed = !1;\n connecting = !1;\n localAddress = \"127.0.0.1\";\n #readQueue = @createFIFO();\n remotePort;\n [bunSocketInternal] = null;\n [bunTLSConnectOptions] = null;\n timeout = 0;\n #writeCallback;\n #writeChunk;\n #pendingRead;\n isServer = !1;\n _handle;\n _parent;\n _parentWrap;\n #socket;\n #upgraded;\n constructor(options) {\n const { socket, signal, write, read, allowHalfOpen = !1, ...opts } = options || {};\n super({\n ...opts,\n allowHalfOpen,\n readable: !0,\n writable: !0\n });\n if (this._handle = this, this._parent = this, this._parentWrap = this, this.#pendingRead = void 0, this.#upgraded = !1, socket instanceof Socket2)\n this.#socket = socket;\n signal\?.once(\"abort\", () => this.destroy()), this.once(\"connect\", () => this.emit(\"ready\"));\n }\n address() {\n return {\n address: this.localAddress,\n family: this.localFamily,\n port: this.localPort\n };\n }\n get bufferSize() {\n return this.writableLength;\n }\n #attach(port, socket) {\n if (this.remotePort = port, socket.data = this, socket.timeout(this.timeout), socket.ref(), this[bunSocketInternal] = socket, this.connecting = !1, !this.#upgraded)\n this.emit(\"connect\", this);\n Socket2.#Drain(socket);\n }\n connect(port, host, connectListener) {\n var path, connection = this.#socket, _checkServerIdentity = void 0;\n if (typeof port === \"string\") {\n if (path = port, port = void 0, typeof host === \"function\")\n connectListener = host, host = void 0;\n } else if (typeof host == \"function\") {\n if (typeof port === \"string\")\n path = port, port = void 0;\n connectListener = host, host = void 0;\n }\n if (typeof port == \"object\") {\n var {\n port,\n host,\n path,\n socket,\n localAddress,\n localPort,\n family,\n hints,\n lookup,\n noDelay,\n keepAlive,\n keepAliveInitialDelay,\n requestCert,\n rejectUnauthorized,\n pauseOnConnect,\n servername,\n checkServerIdentity,\n session\n } = port;\n if (_checkServerIdentity = checkServerIdentity, this.servername = servername, socket)\n connection = socket;\n }\n if (!pauseOnConnect)\n this.resume();\n this.connecting = !0, this.remotePort = port;\n const bunTLS = this[bunTlsSymbol];\n var tls = void 0;\n if (typeof bunTLS === \"function\") {\n if (tls = bunTLS.call(this, port, host, !0), this._requestCert = !0, this._rejectUnauthorized = rejectUnauthorized, tls) {\n if (tls.rejectUnauthorized = rejectUnauthorized, tls.requestCert = !0, tls.session = session || tls.session, this.servername = tls.servername, tls.checkServerIdentity = _checkServerIdentity || tls.checkServerIdentity, this[bunTLSConnectOptions] = tls, !connection && tls.socket)\n connection = tls.socket;\n }\n if (connection) {\n if (typeof connection !== \"object\" || !(connection instanceof Socket2) || typeof connection[bunTlsSymbol] === \"function\")\n @throwTypeError(\"socket must be an instance of net.Socket\");\n }\n if (this.authorized = !1, this.secureConnecting = !0, this._secureEstablished = !1, this._securePending = !0, connectListener)\n this.on(\"secureConnect\", connectListener);\n } else if (connectListener)\n this.on(\"connect\", connectListener);\n if (connection) {\n const socket2 = connection[bunSocketInternal];\n if (socket2) {\n this.connecting = !0, this.#upgraded = !0;\n const result = socket2.upgradeTLS({\n data: this,\n tls,\n socket: Socket2.#Handlers\n });\n if (result) {\n const [raw, tls2] = result;\n connection[bunSocketInternal] = raw, raw.timeout(raw.timeout), raw.connecting = !1, this[bunSocketInternal] = tls2;\n } else\n throw this[bunSocketInternal] = null, new Error(\"Invalid socket\");\n } else\n connection.once(\"connect\", () => {\n const socket3 = connection[bunSocketInternal];\n if (!socket3)\n return;\n this.connecting = !0, this.#upgraded = !0;\n const result = socket3.upgradeTLS({\n data: this,\n tls,\n socket: Socket2.#Handlers\n });\n if (result) {\n const [raw, tls2] = result;\n connection[bunSocketInternal] = raw, raw.timeout(raw.timeout), raw.connecting = !1, this[bunSocketInternal] = tls2;\n } else\n throw this[bunSocketInternal] = null, new Error(\"Invalid socket\");\n });\n } else if (path)\n bunConnect({\n data: this,\n unix: path,\n socket: Socket2.#Handlers,\n tls\n }).catch((error) => {\n this.emit(\"error\", error);\n });\n else\n bunConnect({\n data: this,\n hostname: host || \"localhost\",\n port,\n socket: Socket2.#Handlers,\n tls\n }).catch((error) => {\n this.emit(\"error\", error);\n });\n return this;\n }\n _destroy(err, callback) {\n this[bunSocketInternal]\?.end(), callback(err);\n }\n _final(callback) {\n this[bunSocketInternal]\?.end(), callback();\n }\n get localAddress() {\n return \"127.0.0.1\";\n }\n get localFamily() {\n return \"IPv4\";\n }\n get localPort() {\n return this[bunSocketInternal]\?.localPort;\n }\n get pending() {\n return this.connecting;\n }\n _read(size) {\n const queue = this.#readQueue;\n let chunk;\n while (chunk = queue.peek()) {\n if (!this.push(chunk))\n return;\n queue.shift();\n }\n }\n get readyState() {\n if (this.connecting)\n return \"opening\";\n if (this.readable)\n return this.writable \? \"open\" : \"readOnly\";\n else\n return this.writable \? \"writeOnly\" : \"closed\";\n }\n ref() {\n this[bunSocketInternal]\?.ref();\n }\n get remoteAddress() {\n return this[bunSocketInternal]\?.remoteAddress;\n }\n get remoteFamily() {\n return \"IPv4\";\n }\n resetAndDestroy() {\n this[bunSocketInternal]\?.end();\n }\n setKeepAlive(enable = !1, initialDelay = 0) {\n return this;\n }\n setNoDelay(noDelay = !0) {\n return this;\n }\n setTimeout(timeout, callback) {\n if (this[bunSocketInternal]\?.timeout(timeout), this.timeout = timeout, callback)\n this.once(\"timeout\", callback);\n return this;\n }\n unref() {\n this[bunSocketInternal]\?.unref();\n }\n _write(chunk, encoding, callback) {\n if (typeof chunk == \"string\" && encoding !== \"ascii\")\n chunk = Buffer.from(chunk, encoding);\n var written = this[bunSocketInternal]\?.write(chunk);\n if (written == chunk.length)\n callback();\n else if (this.#writeCallback)\n callback(new Error(\"overlapping _write()\"));\n else {\n if (written > 0)\n if (typeof chunk == \"string\")\n chunk = chunk.slice(written);\n else\n chunk = chunk.subarray(written);\n this.#writeCallback = callback, this.#writeChunk = chunk;\n }\n }\n}), connect = createConnection;\n\nclass Server extends EventEmitter {\n #server;\n #listening = !1;\n [bunSocketServerConnections] = 0;\n [bunSocketServerOptions];\n maxConnections = 0;\n constructor(options, connectionListener) {\n super();\n if (typeof options === \"function\")\n connectionListener = options, options = {};\n else if (options == null || typeof options === \"object\")\n options = { ...options };\n else\n throw new Error(\"bun-net-polyfill: invalid arguments\");\n const { maxConnections } = options;\n this.maxConnections = Number.isSafeInteger(maxConnections) && maxConnections > 0 \? maxConnections : 0, options.connectionListener = connectionListener, this[bunSocketServerOptions] = options;\n }\n ref() {\n return this.#server\?.ref(), this;\n }\n unref() {\n return this.#server\?.unref(), this;\n }\n close(callback) {\n if (this.#server) {\n if (this.#server.stop(!0), this.#server = null, this.#listening = !1, this[bunSocketServerConnections] = 0, this.emit(\"close\"), typeof callback === \"function\")\n callback();\n return this;\n }\n if (typeof callback === \"function\") {\n const error = new Error(\"Server is not running\");\n error.code = \"ERR_SERVER_NOT_RUNNING\", callback(error);\n }\n return this;\n }\n address() {\n const server = this.#server;\n if (server) {\n const unix = server.unix;\n if (unix)\n return unix;\n let address = server.hostname;\n const type = isIP(address), port = server.port;\n if (typeof port === \"number\")\n return {\n port,\n address,\n family: type \? `IPv${type}` : void 0\n };\n if (type)\n return {\n address,\n family: type \? `IPv${type}` : void 0\n };\n return address;\n }\n return null;\n }\n getConnections(callback) {\n if (typeof callback === \"function\")\n callback(null, this.#server \? this[bunSocketServerConnections] : 0);\n return this;\n }\n listen(port, hostname, onListen) {\n let backlog, path, exclusive = !1;\n if (typeof port === \"string\") {\n if (Number.isSafeInteger(hostname)) {\n if (hostname > 0)\n backlog = hostname;\n } else if (typeof hostname === \"function\")\n onListen = hostname;\n path = port, hostname = void 0, port = void 0;\n } else {\n if (typeof hostname === \"function\")\n onListen = hostname, hostname = void 0;\n if (typeof port === \"function\")\n onListen = port, port = 0;\n else if (typeof port === \"object\") {\n const options = port;\n options.signal\?.addEventListener(\"abort\", () => this.close()), hostname = options.host, exclusive = options.exclusive === !0;\n const path2 = options.path;\n if (port = options.port, !Number.isSafeInteger(port) || port < 0)\n if (path2)\n hostname = path2, port = void 0;\n else {\n let message = 'The argument \\'options\\' must have the property \"port\" or \"path\"';\n try {\n message = `${message}. Received ${JSON.stringify(options)}`;\n } catch {\n }\n const error = @makeTypeError(message);\n throw error.code = \"ERR_INVALID_ARG_VALUE\", error;\n }\n else if (!Number.isSafeInteger(port) || port < 0)\n port = 0;\n if (typeof port.callback === \"function\")\n onListen = port\?.callback;\n } else if (!Number.isSafeInteger(port) || port < 0)\n port = 0;\n hostname = hostname || \"::\";\n }\n try {\n var tls = void 0, TLSSocketClass = void 0;\n const bunTLS = this[bunTlsSymbol], options = this[bunSocketServerOptions];\n if (typeof bunTLS === \"function\")\n [tls, TLSSocketClass] = bunTLS.call(this, port, hostname, !1), options.servername = tls.serverName, options.InternalSocketClass = TLSSocketClass;\n else\n options.InternalSocketClass = SocketClass;\n this.#server = Bun.listen(path \? {\n exclusive,\n unix: path,\n tls,\n socket: SocketClass[bunSocketServerHandlers]\n } : {\n exclusive,\n port,\n hostname,\n tls,\n socket: SocketClass[bunSocketServerHandlers]\n }), this.#server.data = this, this.#listening = !0, setTimeout(emitListeningNextTick, 1, this, onListen);\n } catch (err) {\n this.#listening = !1, setTimeout(emitErrorNextTick, 1, this, err);\n }\n return this;\n }\n}\n$ = {\n createServer,\n Server,\n createConnection,\n connect,\n isIP,\n isIPv4,\n isIPv6,\n Socket,\n [Symbol.for(\"::bunternal::\")]: SocketClass\n};\nreturn $})\n"_s; +static constexpr ASCIILiteral NodeNetCode = "(function (){\"use strict\";// src/js/out/tmp/node/net.ts\nvar isIPv4 = function(s) {\n return IPv4Reg.test(s);\n}, isIPv6 = function(s) {\n return IPv6Reg.test(s);\n}, isIP = function(s) {\n if (isIPv4(s))\n return 4;\n if (isIPv6(s))\n return 6;\n return 0;\n}, createConnection = function(port, host, connectListener) {\n if (typeof port === \"object\")\n return new Socket(port).connect(port, host, connectListener);\n return new Socket().connect(port, host, connectListener);\n}, emitErrorNextTick = function(self, error) {\n self.emit(\"error\", error);\n}, emitListeningNextTick = function(self, onListen) {\n if (typeof onListen === \"function\")\n try {\n onListen();\n } catch (err) {\n self.emit(\"error\", err);\n }\n self.emit(\"listening\");\n}, createServer = function(options, connectionListener) {\n return new Server(options, connectionListener);\n}, $, { Duplex } = @getInternalField(@internalModuleRegistry, 37) || @createInternalModuleById(37), EventEmitter = @getInternalField(@internalModuleRegistry, 18) || @createInternalModuleById(18);\nvar IPv4Reg = new RegExp(\"^((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$\");\nvar IPv6Reg = new RegExp(\"^((\?:(\?:[0-9a-fA-F]{1,4}):){7}(\?:(\?:[0-9a-fA-F]{1,4})|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){6}(\?:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|:(\?:[0-9a-fA-F]{1,4})|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){5}(\?::((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,2}|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){4}(\?:(:(\?:[0-9a-fA-F]{1,4})){0,1}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,3}|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){3}(\?:(:(\?:[0-9a-fA-F]{1,4})){0,2}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,4}|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){2}(\?:(:(\?:[0-9a-fA-F]{1,4})){0,3}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,5}|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){1}(\?:(:(\?:[0-9a-fA-F]{1,4})){0,4}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,6}|:)|(\?::((\?::(\?:[0-9a-fA-F]{1,4})){0,5}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(\?::(\?:[0-9a-fA-F]{1,4})){1,7}|:)))(%[0-9a-zA-Z-.:]{1,})\?$\"), { connect: bunConnect } = Bun, { setTimeout } = globalThis, bunTlsSymbol = Symbol.for(\"::buntls::\"), bunSocketServerHandlers = Symbol.for(\"::bunsocket_serverhandlers::\"), bunSocketServerConnections = Symbol.for(\"::bunnetserverconnections::\"), bunSocketServerOptions = Symbol.for(\"::bunnetserveroptions::\"), bunSocketInternal = Symbol.for(\"::bunnetsocketinternal::\"), bunTLSConnectOptions = Symbol.for(\"::buntlsconnectoptions::\"), SocketClass, Socket = function(InternalSocket) {\n return SocketClass = InternalSocket, Object.defineProperty(SocketClass.prototype, Symbol.toStringTag, {\n value: \"Socket\",\n enumerable: !1\n }), Object.defineProperty(function Socket(options) {\n return new InternalSocket(options);\n }, Symbol.hasInstance, {\n value(instance) {\n return instance instanceof InternalSocket;\n }\n });\n}(class Socket2 extends Duplex {\n static #Handlers = {\n close: Socket2.#Close,\n data({ data: self }, buffer) {\n self.bytesRead += buffer.length;\n const queue = self.#readQueue;\n if (queue.isEmpty()) {\n if (self.push(buffer))\n return;\n }\n queue.push(buffer);\n },\n drain: Socket2.#Drain,\n end: Socket2.#Close,\n error(socket, error) {\n const self = socket.data, callback = self.#writeCallback;\n if (callback)\n self.#writeCallback = null, callback(error);\n self.emit(\"error\", error);\n },\n open(socket) {\n const self = socket.data;\n socket.timeout(self.timeout), socket.ref(), self[bunSocketInternal] = socket, self.connecting = !1;\n const options = self[bunTLSConnectOptions];\n if (options) {\n const { session } = options;\n if (session)\n self.setSession(session);\n }\n if (!self.#upgraded)\n self.emit(\"connect\", self);\n Socket2.#Drain(socket);\n },\n handshake(socket, success, verifyError) {\n const { data: self } = socket;\n self._securePending = !1, self.secureConnecting = !1, self._secureEstablished = !!success, self.emit(\"secure\", self);\n const { checkServerIdentity } = self[bunTLSConnectOptions];\n if (!verifyError && typeof checkServerIdentity === \"function\" && self.servername) {\n const cert = self.getPeerCertificate(!0);\n verifyError = checkServerIdentity(self.servername, cert);\n }\n if (self._requestCert || self._rejectUnauthorized) {\n if (verifyError) {\n if (self.authorized = !1, self.authorizationError = verifyError.code || verifyError.message, self._rejectUnauthorized) {\n self.destroy(verifyError);\n return;\n }\n }\n } else\n self.authorized = !0;\n self.emit(\"secureConnect\", verifyError);\n },\n timeout(socket) {\n const self = socket.data;\n self.emit(\"timeout\", self);\n },\n binaryType: \"buffer\"\n };\n static #Close(socket) {\n const self = socket.data;\n if (self.#closed)\n return;\n self.#closed = !0, self[bunSocketInternal] = null;\n const queue = self.#readQueue;\n if (queue.isEmpty()) {\n if (self.push(null))\n return;\n }\n queue.push(null);\n }\n static #Drain(socket) {\n const self = socket.data, callback = self.#writeCallback;\n if (callback) {\n const chunk = self.#writeChunk, written = socket.write(chunk);\n if (self.bytesWritten += written, written < chunk.length)\n self.#writeChunk = chunk.slice(written);\n else\n self.#writeCallback = null, self.#writeChunk = null, callback(null);\n }\n }\n static [bunSocketServerHandlers] = {\n data: Socket2.#Handlers.data,\n close(socket) {\n Socket2.#Handlers.close(socket), this.data[bunSocketServerConnections]--;\n },\n end(socket) {\n Socket2.#Handlers.end(socket), this.data[bunSocketServerConnections]--;\n },\n open(socket) {\n const self = this.data, options = self[bunSocketServerOptions], { pauseOnConnect, connectionListener, InternalSocketClass, requestCert, rejectUnauthorized } = options, _socket = new InternalSocketClass({});\n if (_socket.isServer = !0, _socket._requestCert = requestCert, _socket._rejectUnauthorized = rejectUnauthorized, _socket.#attach(this.localPort, socket), self.maxConnections && self[bunSocketServerConnections] >= self.maxConnections) {\n const data = {\n localAddress: _socket.localAddress,\n localPort: _socket.localPort,\n localFamily: _socket.localFamily,\n remoteAddress: _socket.remoteAddress,\n remotePort: _socket.remotePort,\n remoteFamily: _socket.remoteFamily || \"IPv4\"\n };\n socket.end(), self.emit(\"drop\", data);\n return;\n }\n if (!pauseOnConnect)\n _socket.resume();\n if (self[bunSocketServerConnections]++, typeof connectionListener == \"function\")\n if (InternalSocketClass.name === \"TLSSocket\")\n self.once(\"secureConnection\", () => connectionListener(_socket));\n else\n connectionListener(_socket);\n self.emit(\"connection\", _socket);\n },\n handshake(socket, success, verifyError) {\n const { data: self } = socket;\n if (self.emit(\"secure\", self), self._securePending = !1, self.secureConnecting = !1, self._secureEstablished = !!success, self._requestCert || self._rejectUnauthorized) {\n if (verifyError) {\n if (self.authorized = !1, self.authorizationError = verifyError.code || verifyError.message, self._rejectUnauthorized) {\n self.destroy(verifyError);\n return;\n }\n }\n } else\n self.authorized = !0;\n self.emit(\"secureConnection\", verifyError);\n },\n error(socket, error) {\n Socket2.#Handlers.error(socket, error), this.data.emit(\"error\", error);\n },\n timeout: Socket2.#Handlers.timeout,\n connectError: Socket2.#Handlers.connectError,\n drain: Socket2.#Handlers.drain,\n binaryType: \"buffer\"\n };\n bytesRead = 0;\n bytesWritten = 0;\n #closed = !1;\n connecting = !1;\n localAddress = \"127.0.0.1\";\n #readQueue = @createFIFO();\n remotePort;\n [bunSocketInternal] = null;\n [bunTLSConnectOptions] = null;\n timeout = 0;\n #writeCallback;\n #writeChunk;\n #pendingRead;\n isServer = !1;\n _handle;\n _parent;\n _parentWrap;\n #socket;\n #upgraded;\n constructor(options) {\n const { socket, signal, write, read, allowHalfOpen = !1, ...opts } = options || {};\n super({\n ...opts,\n allowHalfOpen,\n readable: !0,\n writable: !0\n });\n if (this._handle = this, this._parent = this, this._parentWrap = this, this.#pendingRead = void 0, this.#upgraded = !1, socket instanceof Socket2)\n this.#socket = socket;\n signal\?.once(\"abort\", () => this.destroy()), this.once(\"connect\", () => this.emit(\"ready\"));\n }\n address() {\n return {\n address: this.localAddress,\n family: this.localFamily,\n port: this.localPort\n };\n }\n get bufferSize() {\n return this.writableLength;\n }\n #attach(port, socket) {\n if (this.remotePort = port, socket.data = this, socket.timeout(this.timeout), socket.ref(), this[bunSocketInternal] = socket, this.connecting = !1, !this.#upgraded)\n this.emit(\"connect\", this);\n Socket2.#Drain(socket);\n }\n connect(port, host, connectListener) {\n var path, connection = this.#socket, _checkServerIdentity = void 0;\n if (typeof port === \"string\") {\n if (path = port, port = void 0, typeof host === \"function\")\n connectListener = host, host = void 0;\n } else if (typeof host == \"function\") {\n if (typeof port === \"string\")\n path = port, port = void 0;\n connectListener = host, host = void 0;\n }\n if (typeof port == \"object\") {\n var {\n port,\n host,\n path,\n socket,\n localAddress,\n localPort,\n family,\n hints,\n lookup,\n noDelay,\n keepAlive,\n keepAliveInitialDelay,\n requestCert,\n rejectUnauthorized,\n pauseOnConnect,\n servername,\n checkServerIdentity,\n session\n } = port;\n if (_checkServerIdentity = checkServerIdentity, this.servername = servername, socket)\n connection = socket;\n }\n if (!pauseOnConnect)\n this.resume();\n this.connecting = !0, this.remotePort = port;\n const bunTLS = this[bunTlsSymbol];\n var tls = void 0;\n if (typeof bunTLS === \"function\") {\n if (tls = bunTLS.call(this, port, host, !0), this._requestCert = !0, this._rejectUnauthorized = rejectUnauthorized, tls) {\n if (tls.rejectUnauthorized = rejectUnauthorized, tls.requestCert = !0, tls.session = session || tls.session, this.servername = tls.servername, tls.checkServerIdentity = _checkServerIdentity || tls.checkServerIdentity, this[bunTLSConnectOptions] = tls, !connection && tls.socket)\n connection = tls.socket;\n }\n if (connection) {\n if (typeof connection !== \"object\" || !(connection instanceof Socket2) || typeof connection[bunTlsSymbol] === \"function\")\n @throwTypeError(\"socket must be an instance of net.Socket\");\n }\n if (this.authorized = !1, this.secureConnecting = !0, this._secureEstablished = !1, this._securePending = !0, connectListener)\n this.on(\"secureConnect\", connectListener);\n } else if (connectListener)\n this.on(\"connect\", connectListener);\n if (connection) {\n const socket2 = connection[bunSocketInternal];\n if (socket2) {\n this.connecting = !0, this.#upgraded = !0;\n const result = socket2.upgradeTLS({\n data: this,\n tls,\n socket: Socket2.#Handlers\n });\n if (result) {\n const [raw, tls2] = result;\n connection[bunSocketInternal] = raw, raw.timeout(raw.timeout), raw.connecting = !1, this[bunSocketInternal] = tls2;\n } else\n throw this[bunSocketInternal] = null, new Error(\"Invalid socket\");\n } else\n connection.once(\"connect\", () => {\n const socket3 = connection[bunSocketInternal];\n if (!socket3)\n return;\n this.connecting = !0, this.#upgraded = !0;\n const result = socket3.upgradeTLS({\n data: this,\n tls,\n socket: Socket2.#Handlers\n });\n if (result) {\n const [raw, tls2] = result;\n connection[bunSocketInternal] = raw, raw.timeout(raw.timeout), raw.connecting = !1, this[bunSocketInternal] = tls2;\n } else\n throw this[bunSocketInternal] = null, new Error(\"Invalid socket\");\n });\n } else if (path)\n bunConnect({\n data: this,\n unix: path,\n socket: Socket2.#Handlers,\n tls\n }).catch((error) => {\n this.emit(\"error\", error), this.emit(\"close\");\n });\n else\n bunConnect({\n data: this,\n hostname: host || \"localhost\",\n port,\n socket: Socket2.#Handlers,\n tls\n }).catch((error) => {\n this.emit(\"error\", error), this.emit(\"close\");\n });\n return this;\n }\n _destroy(err, callback) {\n this[bunSocketInternal]\?.end(), callback(err);\n }\n _final(callback) {\n this[bunSocketInternal]\?.end(), callback();\n }\n get localAddress() {\n return \"127.0.0.1\";\n }\n get localFamily() {\n return \"IPv4\";\n }\n get localPort() {\n return this[bunSocketInternal]\?.localPort;\n }\n get pending() {\n return this.connecting;\n }\n _read(size) {\n const queue = this.#readQueue;\n let chunk;\n while (chunk = queue.peek()) {\n if (!this.push(chunk))\n return;\n queue.shift();\n }\n }\n get readyState() {\n if (this.connecting)\n return \"opening\";\n if (this.readable)\n return this.writable \? \"open\" : \"readOnly\";\n else\n return this.writable \? \"writeOnly\" : \"closed\";\n }\n ref() {\n this[bunSocketInternal]\?.ref();\n }\n get remoteAddress() {\n return this[bunSocketInternal]\?.remoteAddress;\n }\n get remoteFamily() {\n return \"IPv4\";\n }\n resetAndDestroy() {\n this[bunSocketInternal]\?.end();\n }\n setKeepAlive(enable = !1, initialDelay = 0) {\n return this;\n }\n setNoDelay(noDelay = !0) {\n return this;\n }\n setTimeout(timeout, callback) {\n if (this[bunSocketInternal]\?.timeout(timeout), this.timeout = timeout, callback)\n this.once(\"timeout\", callback);\n return this;\n }\n unref() {\n this[bunSocketInternal]\?.unref();\n }\n _write(chunk, encoding, callback) {\n if (typeof chunk == \"string\" && encoding !== \"ascii\")\n chunk = Buffer.from(chunk, encoding);\n var written = this[bunSocketInternal]\?.write(chunk);\n if (written == chunk.length)\n callback();\n else if (this.#writeCallback)\n callback(new Error(\"overlapping _write()\"));\n else {\n if (written > 0)\n if (typeof chunk == \"string\")\n chunk = chunk.slice(written);\n else\n chunk = chunk.subarray(written);\n this.#writeCallback = callback, this.#writeChunk = chunk;\n }\n }\n}), connect = createConnection;\n\nclass Server extends EventEmitter {\n #server;\n #listening = !1;\n [bunSocketServerConnections] = 0;\n [bunSocketServerOptions];\n maxConnections = 0;\n constructor(options, connectionListener) {\n super();\n if (typeof options === \"function\")\n connectionListener = options, options = {};\n else if (options == null || typeof options === \"object\")\n options = { ...options };\n else\n throw new Error(\"bun-net-polyfill: invalid arguments\");\n const { maxConnections } = options;\n this.maxConnections = Number.isSafeInteger(maxConnections) && maxConnections > 0 \? maxConnections : 0, options.connectionListener = connectionListener, this[bunSocketServerOptions] = options;\n }\n ref() {\n return this.#server\?.ref(), this;\n }\n unref() {\n return this.#server\?.unref(), this;\n }\n close(callback) {\n if (this.#server) {\n if (this.#server.stop(!0), this.#server = null, this.#listening = !1, this[bunSocketServerConnections] = 0, this.emit(\"close\"), typeof callback === \"function\")\n callback();\n return this;\n }\n if (typeof callback === \"function\") {\n const error = new Error(\"Server is not running\");\n error.code = \"ERR_SERVER_NOT_RUNNING\", callback(error);\n }\n return this;\n }\n address() {\n const server = this.#server;\n if (server) {\n const unix = server.unix;\n if (unix)\n return unix;\n let address = server.hostname;\n const type = isIP(address), port = server.port;\n if (typeof port === \"number\")\n return {\n port,\n address,\n family: type \? `IPv${type}` : void 0\n };\n if (type)\n return {\n address,\n family: type \? `IPv${type}` : void 0\n };\n return address;\n }\n return null;\n }\n getConnections(callback) {\n if (typeof callback === \"function\")\n callback(null, this.#server \? this[bunSocketServerConnections] : 0);\n return this;\n }\n listen(port, hostname, onListen) {\n let backlog, path, exclusive = !1;\n if (typeof port === \"string\") {\n if (Number.isSafeInteger(hostname)) {\n if (hostname > 0)\n backlog = hostname;\n } else if (typeof hostname === \"function\")\n onListen = hostname;\n path = port, hostname = void 0, port = void 0;\n } else {\n if (typeof hostname === \"function\")\n onListen = hostname, hostname = void 0;\n if (typeof port === \"function\")\n onListen = port, port = 0;\n else if (typeof port === \"object\") {\n const options = port;\n options.signal\?.addEventListener(\"abort\", () => this.close()), hostname = options.host, exclusive = options.exclusive === !0;\n const path2 = options.path;\n if (port = options.port, !Number.isSafeInteger(port) || port < 0)\n if (path2)\n hostname = path2, port = void 0;\n else {\n let message = 'The argument \\'options\\' must have the property \"port\" or \"path\"';\n try {\n message = `${message}. Received ${JSON.stringify(options)}`;\n } catch {\n }\n const error = @makeTypeError(message);\n throw error.code = \"ERR_INVALID_ARG_VALUE\", error;\n }\n else if (!Number.isSafeInteger(port) || port < 0)\n port = 0;\n if (typeof port.callback === \"function\")\n onListen = port\?.callback;\n } else if (!Number.isSafeInteger(port) || port < 0)\n port = 0;\n hostname = hostname || \"::\";\n }\n try {\n var tls = void 0, TLSSocketClass = void 0;\n const bunTLS = this[bunTlsSymbol], options = this[bunSocketServerOptions];\n if (typeof bunTLS === \"function\")\n [tls, TLSSocketClass] = bunTLS.call(this, port, hostname, !1), options.servername = tls.serverName, options.InternalSocketClass = TLSSocketClass;\n else\n options.InternalSocketClass = SocketClass;\n this.#server = Bun.listen(path \? {\n exclusive,\n unix: path,\n tls,\n socket: SocketClass[bunSocketServerHandlers]\n } : {\n exclusive,\n port,\n hostname,\n tls,\n socket: SocketClass[bunSocketServerHandlers]\n }), this.#server.data = this, this.#listening = !0, setTimeout(emitListeningNextTick, 1, this, onListen);\n } catch (err) {\n this.#listening = !1, setTimeout(emitErrorNextTick, 1, this, err);\n }\n return this;\n }\n}\n$ = {\n createServer,\n Server,\n createConnection,\n connect,\n isIP,\n isIPv4,\n isIPv6,\n Socket,\n [Symbol.for(\"::bunternal::\")]: SocketClass\n};\nreturn $})\n"_s; // // @@ -589,7 +589,7 @@ static constexpr ASCIILiteral NodeInspectorCode = "(function (){\"use strict\";/ // // -static constexpr ASCIILiteral NodeNetCode = "(function (){\"use strict\";// src/js/out/tmp/node/net.ts\nvar isIPv4 = function(s) {\n return IPv4Reg.test(s);\n}, isIPv6 = function(s) {\n return IPv6Reg.test(s);\n}, isIP = function(s) {\n if (isIPv4(s))\n return 4;\n if (isIPv6(s))\n return 6;\n return 0;\n}, createConnection = function(port, host, connectListener) {\n if (typeof port === \"object\")\n return new Socket(port).connect(port, host, connectListener);\n return new Socket().connect(port, host, connectListener);\n}, emitErrorNextTick = function(self, error) {\n self.emit(\"error\", error);\n}, emitListeningNextTick = function(self, onListen) {\n if (typeof onListen === \"function\")\n try {\n onListen();\n } catch (err) {\n self.emit(\"error\", err);\n }\n self.emit(\"listening\");\n}, createServer = function(options, connectionListener) {\n return new Server(options, connectionListener);\n}, $, { Duplex } = @getInternalField(@internalModuleRegistry, 37) || @createInternalModuleById(37), EventEmitter = @getInternalField(@internalModuleRegistry, 18) || @createInternalModuleById(18);\nvar IPv4Reg = new RegExp(\"^((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$\");\nvar IPv6Reg = new RegExp(\"^((\?:(\?:[0-9a-fA-F]{1,4}):){7}(\?:(\?:[0-9a-fA-F]{1,4})|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){6}(\?:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|:(\?:[0-9a-fA-F]{1,4})|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){5}(\?::((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,2}|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){4}(\?:(:(\?:[0-9a-fA-F]{1,4})){0,1}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,3}|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){3}(\?:(:(\?:[0-9a-fA-F]{1,4})){0,2}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,4}|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){2}(\?:(:(\?:[0-9a-fA-F]{1,4})){0,3}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,5}|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){1}(\?:(:(\?:[0-9a-fA-F]{1,4})){0,4}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,6}|:)|(\?::((\?::(\?:[0-9a-fA-F]{1,4})){0,5}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(\?::(\?:[0-9a-fA-F]{1,4})){1,7}|:)))(%[0-9a-zA-Z-.:]{1,})\?$\"), { connect: bunConnect } = Bun, { setTimeout } = globalThis, bunTlsSymbol = Symbol.for(\"::buntls::\"), bunSocketServerHandlers = Symbol.for(\"::bunsocket_serverhandlers::\"), bunSocketServerConnections = Symbol.for(\"::bunnetserverconnections::\"), bunSocketServerOptions = Symbol.for(\"::bunnetserveroptions::\"), bunSocketInternal = Symbol.for(\"::bunnetsocketinternal::\"), bunTLSConnectOptions = Symbol.for(\"::buntlsconnectoptions::\"), SocketClass, Socket = function(InternalSocket) {\n return SocketClass = InternalSocket, Object.defineProperty(SocketClass.prototype, Symbol.toStringTag, {\n value: \"Socket\",\n enumerable: !1\n }), Object.defineProperty(function Socket(options) {\n return new InternalSocket(options);\n }, Symbol.hasInstance, {\n value(instance) {\n return instance instanceof InternalSocket;\n }\n });\n}(class Socket2 extends Duplex {\n static #Handlers = {\n close: Socket2.#Close,\n connectError(socket, error) {\n socket.data.emit(\"error\", error);\n },\n data({ data: self }, buffer) {\n self.bytesRead += buffer.length;\n const queue = self.#readQueue;\n if (queue.isEmpty()) {\n if (self.push(buffer))\n return;\n }\n queue.push(buffer);\n },\n drain: Socket2.#Drain,\n end: Socket2.#Close,\n error(socket, error) {\n const self = socket.data, callback = self.#writeCallback;\n if (callback)\n self.#writeCallback = null, callback(error);\n self.emit(\"error\", error);\n },\n open(socket) {\n const self = socket.data;\n socket.timeout(self.timeout), socket.ref(), self[bunSocketInternal] = socket, self.connecting = !1;\n const options = self[bunTLSConnectOptions];\n if (options) {\n const { session } = options;\n if (session)\n self.setSession(session);\n }\n if (!self.#upgraded)\n self.emit(\"connect\", self);\n Socket2.#Drain(socket);\n },\n handshake(socket, success, verifyError) {\n const { data: self } = socket;\n self._securePending = !1, self.secureConnecting = !1, self._secureEstablished = !!success, self.emit(\"secure\", self);\n const { checkServerIdentity } = self[bunTLSConnectOptions];\n if (!verifyError && typeof checkServerIdentity === \"function\" && self.servername) {\n const cert = self.getPeerCertificate(!0);\n verifyError = checkServerIdentity(self.servername, cert);\n }\n if (self._requestCert || self._rejectUnauthorized) {\n if (verifyError) {\n if (self.authorized = !1, self.authorizationError = verifyError.code || verifyError.message, self._rejectUnauthorized) {\n self.destroy(verifyError);\n return;\n }\n }\n } else\n self.authorized = !0;\n self.emit(\"secureConnect\", verifyError);\n },\n timeout(socket) {\n const self = socket.data;\n self.emit(\"timeout\", self);\n },\n binaryType: \"buffer\"\n };\n static #Close(socket) {\n const self = socket.data;\n if (self.#closed)\n return;\n self.#closed = !0, self[bunSocketInternal] = null;\n const queue = self.#readQueue;\n if (queue.isEmpty()) {\n if (self.push(null))\n return;\n }\n queue.push(null);\n }\n static #Drain(socket) {\n const self = socket.data, callback = self.#writeCallback;\n if (callback) {\n const chunk = self.#writeChunk, written = socket.write(chunk);\n if (self.bytesWritten += written, written < chunk.length)\n self.#writeChunk = chunk.slice(written);\n else\n self.#writeCallback = null, self.#writeChunk = null, callback(null);\n }\n }\n static [bunSocketServerHandlers] = {\n data: Socket2.#Handlers.data,\n close(socket) {\n Socket2.#Handlers.close(socket), this.data[bunSocketServerConnections]--;\n },\n end(socket) {\n Socket2.#Handlers.end(socket), this.data[bunSocketServerConnections]--;\n },\n open(socket) {\n const self = this.data, options = self[bunSocketServerOptions], { pauseOnConnect, connectionListener, InternalSocketClass, requestCert, rejectUnauthorized } = options, _socket = new InternalSocketClass({});\n if (_socket.isServer = !0, _socket._requestCert = requestCert, _socket._rejectUnauthorized = rejectUnauthorized, _socket.#attach(this.localPort, socket), self.maxConnections && self[bunSocketServerConnections] >= self.maxConnections) {\n const data = {\n localAddress: _socket.localAddress,\n localPort: _socket.localPort,\n localFamily: _socket.localFamily,\n remoteAddress: _socket.remoteAddress,\n remotePort: _socket.remotePort,\n remoteFamily: _socket.remoteFamily || \"IPv4\"\n };\n socket.end(), self.emit(\"drop\", data);\n return;\n }\n if (!pauseOnConnect)\n _socket.resume();\n if (self[bunSocketServerConnections]++, typeof connectionListener == \"function\")\n if (InternalSocketClass.name === \"TLSSocket\")\n self.once(\"secureConnection\", () => connectionListener(_socket));\n else\n connectionListener(_socket);\n self.emit(\"connection\", _socket);\n },\n handshake(socket, success, verifyError) {\n const { data: self } = socket;\n if (self.emit(\"secure\", self), self._securePending = !1, self.secureConnecting = !1, self._secureEstablished = !!success, self._requestCert || self._rejectUnauthorized) {\n if (verifyError) {\n if (self.authorized = !1, self.authorizationError = verifyError.code || verifyError.message, self._rejectUnauthorized) {\n self.destroy(verifyError);\n return;\n }\n }\n } else\n self.authorized = !0;\n self.emit(\"secureConnection\", verifyError);\n },\n error(socket, error) {\n Socket2.#Handlers.error(socket, error), this.data.emit(\"error\", error);\n },\n timeout: Socket2.#Handlers.timeout,\n connectError: Socket2.#Handlers.connectError,\n drain: Socket2.#Handlers.drain,\n binaryType: \"buffer\"\n };\n bytesRead = 0;\n bytesWritten = 0;\n #closed = !1;\n connecting = !1;\n localAddress = \"127.0.0.1\";\n #readQueue = @createFIFO();\n remotePort;\n [bunSocketInternal] = null;\n [bunTLSConnectOptions] = null;\n timeout = 0;\n #writeCallback;\n #writeChunk;\n #pendingRead;\n isServer = !1;\n _handle;\n _parent;\n _parentWrap;\n #socket;\n #upgraded;\n constructor(options) {\n const { socket, signal, write, read, allowHalfOpen = !1, ...opts } = options || {};\n super({\n ...opts,\n allowHalfOpen,\n readable: !0,\n writable: !0\n });\n if (this._handle = this, this._parent = this, this._parentWrap = this, this.#pendingRead = void 0, this.#upgraded = !1, socket instanceof Socket2)\n this.#socket = socket;\n signal\?.once(\"abort\", () => this.destroy()), this.once(\"connect\", () => this.emit(\"ready\"));\n }\n address() {\n return {\n address: this.localAddress,\n family: this.localFamily,\n port: this.localPort\n };\n }\n get bufferSize() {\n return this.writableLength;\n }\n #attach(port, socket) {\n if (this.remotePort = port, socket.data = this, socket.timeout(this.timeout), socket.ref(), this[bunSocketInternal] = socket, this.connecting = !1, !this.#upgraded)\n this.emit(\"connect\", this);\n Socket2.#Drain(socket);\n }\n connect(port, host, connectListener) {\n var path, connection = this.#socket, _checkServerIdentity = void 0;\n if (typeof port === \"string\") {\n if (path = port, port = void 0, typeof host === \"function\")\n connectListener = host, host = void 0;\n } else if (typeof host == \"function\") {\n if (typeof port === \"string\")\n path = port, port = void 0;\n connectListener = host, host = void 0;\n }\n if (typeof port == \"object\") {\n var {\n port,\n host,\n path,\n socket,\n localAddress,\n localPort,\n family,\n hints,\n lookup,\n noDelay,\n keepAlive,\n keepAliveInitialDelay,\n requestCert,\n rejectUnauthorized,\n pauseOnConnect,\n servername,\n checkServerIdentity,\n session\n } = port;\n if (_checkServerIdentity = checkServerIdentity, this.servername = servername, socket)\n connection = socket;\n }\n if (!pauseOnConnect)\n this.resume();\n this.connecting = !0, this.remotePort = port;\n const bunTLS = this[bunTlsSymbol];\n var tls = void 0;\n if (typeof bunTLS === \"function\") {\n if (tls = bunTLS.call(this, port, host, !0), this._requestCert = !0, this._rejectUnauthorized = rejectUnauthorized, tls) {\n if (tls.rejectUnauthorized = rejectUnauthorized, tls.requestCert = !0, tls.session = session || tls.session, this.servername = tls.servername, tls.checkServerIdentity = _checkServerIdentity || tls.checkServerIdentity, this[bunTLSConnectOptions] = tls, !connection && tls.socket)\n connection = tls.socket;\n }\n if (connection) {\n if (typeof connection !== \"object\" || !(connection instanceof Socket2) || typeof connection[bunTlsSymbol] === \"function\")\n @throwTypeError(\"socket must be an instance of net.Socket\");\n }\n if (this.authorized = !1, this.secureConnecting = !0, this._secureEstablished = !1, this._securePending = !0, connectListener)\n this.on(\"secureConnect\", connectListener);\n } else if (connectListener)\n this.on(\"connect\", connectListener);\n if (connection) {\n const socket2 = connection[bunSocketInternal];\n if (socket2) {\n this.connecting = !0, this.#upgraded = !0;\n const result = socket2.upgradeTLS({\n data: this,\n tls,\n socket: Socket2.#Handlers\n });\n if (result) {\n const [raw, tls2] = result;\n connection[bunSocketInternal] = raw, raw.timeout(raw.timeout), raw.connecting = !1, this[bunSocketInternal] = tls2;\n } else\n throw this[bunSocketInternal] = null, new Error(\"Invalid socket\");\n } else\n connection.once(\"connect\", () => {\n const socket3 = connection[bunSocketInternal];\n if (!socket3)\n return;\n this.connecting = !0, this.#upgraded = !0;\n const result = socket3.upgradeTLS({\n data: this,\n tls,\n socket: Socket2.#Handlers\n });\n if (result) {\n const [raw, tls2] = result;\n connection[bunSocketInternal] = raw, raw.timeout(raw.timeout), raw.connecting = !1, this[bunSocketInternal] = tls2;\n } else\n throw this[bunSocketInternal] = null, new Error(\"Invalid socket\");\n });\n } else if (path)\n bunConnect({\n data: this,\n unix: path,\n socket: Socket2.#Handlers,\n tls\n }).catch((error) => {\n this.emit(\"error\", error);\n });\n else\n bunConnect({\n data: this,\n hostname: host || \"localhost\",\n port,\n socket: Socket2.#Handlers,\n tls\n }).catch((error) => {\n this.emit(\"error\", error);\n });\n return this;\n }\n _destroy(err, callback) {\n this[bunSocketInternal]\?.end(), callback(err);\n }\n _final(callback) {\n this[bunSocketInternal]\?.end(), callback();\n }\n get localAddress() {\n return \"127.0.0.1\";\n }\n get localFamily() {\n return \"IPv4\";\n }\n get localPort() {\n return this[bunSocketInternal]\?.localPort;\n }\n get pending() {\n return this.connecting;\n }\n _read(size) {\n const queue = this.#readQueue;\n let chunk;\n while (chunk = queue.peek()) {\n if (!this.push(chunk))\n return;\n queue.shift();\n }\n }\n get readyState() {\n if (this.connecting)\n return \"opening\";\n if (this.readable)\n return this.writable \? \"open\" : \"readOnly\";\n else\n return this.writable \? \"writeOnly\" : \"closed\";\n }\n ref() {\n this[bunSocketInternal]\?.ref();\n }\n get remoteAddress() {\n return this[bunSocketInternal]\?.remoteAddress;\n }\n get remoteFamily() {\n return \"IPv4\";\n }\n resetAndDestroy() {\n this[bunSocketInternal]\?.end();\n }\n setKeepAlive(enable = !1, initialDelay = 0) {\n return this;\n }\n setNoDelay(noDelay = !0) {\n return this;\n }\n setTimeout(timeout, callback) {\n if (this[bunSocketInternal]\?.timeout(timeout), this.timeout = timeout, callback)\n this.once(\"timeout\", callback);\n return this;\n }\n unref() {\n this[bunSocketInternal]\?.unref();\n }\n _write(chunk, encoding, callback) {\n if (typeof chunk == \"string\" && encoding !== \"ascii\")\n chunk = Buffer.from(chunk, encoding);\n var written = this[bunSocketInternal]\?.write(chunk);\n if (written == chunk.length)\n callback();\n else if (this.#writeCallback)\n callback(new Error(\"overlapping _write()\"));\n else {\n if (written > 0)\n if (typeof chunk == \"string\")\n chunk = chunk.slice(written);\n else\n chunk = chunk.subarray(written);\n this.#writeCallback = callback, this.#writeChunk = chunk;\n }\n }\n}), connect = createConnection;\n\nclass Server extends EventEmitter {\n #server;\n #listening = !1;\n [bunSocketServerConnections] = 0;\n [bunSocketServerOptions];\n maxConnections = 0;\n constructor(options, connectionListener) {\n super();\n if (typeof options === \"function\")\n connectionListener = options, options = {};\n else if (options == null || typeof options === \"object\")\n options = { ...options };\n else\n throw new Error(\"bun-net-polyfill: invalid arguments\");\n const { maxConnections } = options;\n this.maxConnections = Number.isSafeInteger(maxConnections) && maxConnections > 0 \? maxConnections : 0, options.connectionListener = connectionListener, this[bunSocketServerOptions] = options;\n }\n ref() {\n return this.#server\?.ref(), this;\n }\n unref() {\n return this.#server\?.unref(), this;\n }\n close(callback) {\n if (this.#server) {\n if (this.#server.stop(!0), this.#server = null, this.#listening = !1, this[bunSocketServerConnections] = 0, this.emit(\"close\"), typeof callback === \"function\")\n callback();\n return this;\n }\n if (typeof callback === \"function\") {\n const error = new Error(\"Server is not running\");\n error.code = \"ERR_SERVER_NOT_RUNNING\", callback(error);\n }\n return this;\n }\n address() {\n const server = this.#server;\n if (server) {\n const unix = server.unix;\n if (unix)\n return unix;\n let address = server.hostname;\n const type = isIP(address), port = server.port;\n if (typeof port === \"number\")\n return {\n port,\n address,\n family: type \? `IPv${type}` : void 0\n };\n if (type)\n return {\n address,\n family: type \? `IPv${type}` : void 0\n };\n return address;\n }\n return null;\n }\n getConnections(callback) {\n if (typeof callback === \"function\")\n callback(null, this.#server \? this[bunSocketServerConnections] : 0);\n return this;\n }\n listen(port, hostname, onListen) {\n let backlog, path, exclusive = !1;\n if (typeof port === \"string\") {\n if (Number.isSafeInteger(hostname)) {\n if (hostname > 0)\n backlog = hostname;\n } else if (typeof hostname === \"function\")\n onListen = hostname;\n path = port, hostname = void 0, port = void 0;\n } else {\n if (typeof hostname === \"function\")\n onListen = hostname, hostname = void 0;\n if (typeof port === \"function\")\n onListen = port, port = 0;\n else if (typeof port === \"object\") {\n const options = port;\n options.signal\?.addEventListener(\"abort\", () => this.close()), hostname = options.host, exclusive = options.exclusive === !0;\n const path2 = options.path;\n if (port = options.port, !Number.isSafeInteger(port) || port < 0)\n if (path2)\n hostname = path2, port = void 0;\n else {\n let message = 'The argument \\'options\\' must have the property \"port\" or \"path\"';\n try {\n message = `${message}. Received ${JSON.stringify(options)}`;\n } catch {\n }\n const error = @makeTypeError(message);\n throw error.code = \"ERR_INVALID_ARG_VALUE\", error;\n }\n else if (!Number.isSafeInteger(port) || port < 0)\n port = 0;\n if (typeof port.callback === \"function\")\n onListen = port\?.callback;\n } else if (!Number.isSafeInteger(port) || port < 0)\n port = 0;\n hostname = hostname || \"::\";\n }\n try {\n var tls = void 0, TLSSocketClass = void 0;\n const bunTLS = this[bunTlsSymbol], options = this[bunSocketServerOptions];\n if (typeof bunTLS === \"function\")\n [tls, TLSSocketClass] = bunTLS.call(this, port, hostname, !1), options.servername = tls.serverName, options.InternalSocketClass = TLSSocketClass;\n else\n options.InternalSocketClass = SocketClass;\n this.#server = Bun.listen(path \? {\n exclusive,\n unix: path,\n tls,\n socket: SocketClass[bunSocketServerHandlers]\n } : {\n exclusive,\n port,\n hostname,\n tls,\n socket: SocketClass[bunSocketServerHandlers]\n }), this.#server.data = this, this.#listening = !0, setTimeout(emitListeningNextTick, 1, this, onListen);\n } catch (err) {\n this.#listening = !1, setTimeout(emitErrorNextTick, 1, this, err);\n }\n return this;\n }\n}\n$ = {\n createServer,\n Server,\n createConnection,\n connect,\n isIP,\n isIPv4,\n isIPv6,\n Socket,\n [Symbol.for(\"::bunternal::\")]: SocketClass\n};\nreturn $})\n"_s; +static constexpr ASCIILiteral NodeNetCode = "(function (){\"use strict\";// src/js/out/tmp/node/net.ts\nvar isIPv4 = function(s) {\n return IPv4Reg.test(s);\n}, isIPv6 = function(s) {\n return IPv6Reg.test(s);\n}, isIP = function(s) {\n if (isIPv4(s))\n return 4;\n if (isIPv6(s))\n return 6;\n return 0;\n}, createConnection = function(port, host, connectListener) {\n if (typeof port === \"object\")\n return new Socket(port).connect(port, host, connectListener);\n return new Socket().connect(port, host, connectListener);\n}, emitErrorNextTick = function(self, error) {\n self.emit(\"error\", error);\n}, emitListeningNextTick = function(self, onListen) {\n if (typeof onListen === \"function\")\n try {\n onListen();\n } catch (err) {\n self.emit(\"error\", err);\n }\n self.emit(\"listening\");\n}, createServer = function(options, connectionListener) {\n return new Server(options, connectionListener);\n}, $, { Duplex } = @getInternalField(@internalModuleRegistry, 37) || @createInternalModuleById(37), EventEmitter = @getInternalField(@internalModuleRegistry, 18) || @createInternalModuleById(18);\nvar IPv4Reg = new RegExp(\"^((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$\");\nvar IPv6Reg = new RegExp(\"^((\?:(\?:[0-9a-fA-F]{1,4}):){7}(\?:(\?:[0-9a-fA-F]{1,4})|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){6}(\?:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|:(\?:[0-9a-fA-F]{1,4})|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){5}(\?::((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,2}|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){4}(\?:(:(\?:[0-9a-fA-F]{1,4})){0,1}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,3}|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){3}(\?:(:(\?:[0-9a-fA-F]{1,4})){0,2}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,4}|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){2}(\?:(:(\?:[0-9a-fA-F]{1,4})){0,3}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,5}|:)|(\?:(\?:[0-9a-fA-F]{1,4}):){1}(\?:(:(\?:[0-9a-fA-F]{1,4})){0,4}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(:(\?:[0-9a-fA-F]{1,4})){1,6}|:)|(\?::((\?::(\?:[0-9a-fA-F]{1,4})){0,5}:((\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])[.]){3}(\?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|(\?::(\?:[0-9a-fA-F]{1,4})){1,7}|:)))(%[0-9a-zA-Z-.:]{1,})\?$\"), { connect: bunConnect } = Bun, { setTimeout } = globalThis, bunTlsSymbol = Symbol.for(\"::buntls::\"), bunSocketServerHandlers = Symbol.for(\"::bunsocket_serverhandlers::\"), bunSocketServerConnections = Symbol.for(\"::bunnetserverconnections::\"), bunSocketServerOptions = Symbol.for(\"::bunnetserveroptions::\"), bunSocketInternal = Symbol.for(\"::bunnetsocketinternal::\"), bunTLSConnectOptions = Symbol.for(\"::buntlsconnectoptions::\"), SocketClass, Socket = function(InternalSocket) {\n return SocketClass = InternalSocket, Object.defineProperty(SocketClass.prototype, Symbol.toStringTag, {\n value: \"Socket\",\n enumerable: !1\n }), Object.defineProperty(function Socket(options) {\n return new InternalSocket(options);\n }, Symbol.hasInstance, {\n value(instance) {\n return instance instanceof InternalSocket;\n }\n });\n}(class Socket2 extends Duplex {\n static #Handlers = {\n close: Socket2.#Close,\n data({ data: self }, buffer) {\n self.bytesRead += buffer.length;\n const queue = self.#readQueue;\n if (queue.isEmpty()) {\n if (self.push(buffer))\n return;\n }\n queue.push(buffer);\n },\n drain: Socket2.#Drain,\n end: Socket2.#Close,\n error(socket, error) {\n const self = socket.data, callback = self.#writeCallback;\n if (callback)\n self.#writeCallback = null, callback(error);\n self.emit(\"error\", error);\n },\n open(socket) {\n const self = socket.data;\n socket.timeout(self.timeout), socket.ref(), self[bunSocketInternal] = socket, self.connecting = !1;\n const options = self[bunTLSConnectOptions];\n if (options) {\n const { session } = options;\n if (session)\n self.setSession(session);\n }\n if (!self.#upgraded)\n self.emit(\"connect\", self);\n Socket2.#Drain(socket);\n },\n handshake(socket, success, verifyError) {\n const { data: self } = socket;\n self._securePending = !1, self.secureConnecting = !1, self._secureEstablished = !!success, self.emit(\"secure\", self);\n const { checkServerIdentity } = self[bunTLSConnectOptions];\n if (!verifyError && typeof checkServerIdentity === \"function\" && self.servername) {\n const cert = self.getPeerCertificate(!0);\n verifyError = checkServerIdentity(self.servername, cert);\n }\n if (self._requestCert || self._rejectUnauthorized) {\n if (verifyError) {\n if (self.authorized = !1, self.authorizationError = verifyError.code || verifyError.message, self._rejectUnauthorized) {\n self.destroy(verifyError);\n return;\n }\n }\n } else\n self.authorized = !0;\n self.emit(\"secureConnect\", verifyError);\n },\n timeout(socket) {\n const self = socket.data;\n self.emit(\"timeout\", self);\n },\n binaryType: \"buffer\"\n };\n static #Close(socket) {\n const self = socket.data;\n if (self.#closed)\n return;\n self.#closed = !0, self[bunSocketInternal] = null;\n const queue = self.#readQueue;\n if (queue.isEmpty()) {\n if (self.push(null))\n return;\n }\n queue.push(null);\n }\n static #Drain(socket) {\n const self = socket.data, callback = self.#writeCallback;\n if (callback) {\n const chunk = self.#writeChunk, written = socket.write(chunk);\n if (self.bytesWritten += written, written < chunk.length)\n self.#writeChunk = chunk.slice(written);\n else\n self.#writeCallback = null, self.#writeChunk = null, callback(null);\n }\n }\n static [bunSocketServerHandlers] = {\n data: Socket2.#Handlers.data,\n close(socket) {\n Socket2.#Handlers.close(socket), this.data[bunSocketServerConnections]--;\n },\n end(socket) {\n Socket2.#Handlers.end(socket), this.data[bunSocketServerConnections]--;\n },\n open(socket) {\n const self = this.data, options = self[bunSocketServerOptions], { pauseOnConnect, connectionListener, InternalSocketClass, requestCert, rejectUnauthorized } = options, _socket = new InternalSocketClass({});\n if (_socket.isServer = !0, _socket._requestCert = requestCert, _socket._rejectUnauthorized = rejectUnauthorized, _socket.#attach(this.localPort, socket), self.maxConnections && self[bunSocketServerConnections] >= self.maxConnections) {\n const data = {\n localAddress: _socket.localAddress,\n localPort: _socket.localPort,\n localFamily: _socket.localFamily,\n remoteAddress: _socket.remoteAddress,\n remotePort: _socket.remotePort,\n remoteFamily: _socket.remoteFamily || \"IPv4\"\n };\n socket.end(), self.emit(\"drop\", data);\n return;\n }\n if (!pauseOnConnect)\n _socket.resume();\n if (self[bunSocketServerConnections]++, typeof connectionListener == \"function\")\n if (InternalSocketClass.name === \"TLSSocket\")\n self.once(\"secureConnection\", () => connectionListener(_socket));\n else\n connectionListener(_socket);\n self.emit(\"connection\", _socket);\n },\n handshake(socket, success, verifyError) {\n const { data: self } = socket;\n if (self.emit(\"secure\", self), self._securePending = !1, self.secureConnecting = !1, self._secureEstablished = !!success, self._requestCert || self._rejectUnauthorized) {\n if (verifyError) {\n if (self.authorized = !1, self.authorizationError = verifyError.code || verifyError.message, self._rejectUnauthorized) {\n self.destroy(verifyError);\n return;\n }\n }\n } else\n self.authorized = !0;\n self.emit(\"secureConnection\", verifyError);\n },\n error(socket, error) {\n Socket2.#Handlers.error(socket, error), this.data.emit(\"error\", error);\n },\n timeout: Socket2.#Handlers.timeout,\n connectError: Socket2.#Handlers.connectError,\n drain: Socket2.#Handlers.drain,\n binaryType: \"buffer\"\n };\n bytesRead = 0;\n bytesWritten = 0;\n #closed = !1;\n connecting = !1;\n localAddress = \"127.0.0.1\";\n #readQueue = @createFIFO();\n remotePort;\n [bunSocketInternal] = null;\n [bunTLSConnectOptions] = null;\n timeout = 0;\n #writeCallback;\n #writeChunk;\n #pendingRead;\n isServer = !1;\n _handle;\n _parent;\n _parentWrap;\n #socket;\n #upgraded;\n constructor(options) {\n const { socket, signal, write, read, allowHalfOpen = !1, ...opts } = options || {};\n super({\n ...opts,\n allowHalfOpen,\n readable: !0,\n writable: !0\n });\n if (this._handle = this, this._parent = this, this._parentWrap = this, this.#pendingRead = void 0, this.#upgraded = !1, socket instanceof Socket2)\n this.#socket = socket;\n signal\?.once(\"abort\", () => this.destroy()), this.once(\"connect\", () => this.emit(\"ready\"));\n }\n address() {\n return {\n address: this.localAddress,\n family: this.localFamily,\n port: this.localPort\n };\n }\n get bufferSize() {\n return this.writableLength;\n }\n #attach(port, socket) {\n if (this.remotePort = port, socket.data = this, socket.timeout(this.timeout), socket.ref(), this[bunSocketInternal] = socket, this.connecting = !1, !this.#upgraded)\n this.emit(\"connect\", this);\n Socket2.#Drain(socket);\n }\n connect(port, host, connectListener) {\n var path, connection = this.#socket, _checkServerIdentity = void 0;\n if (typeof port === \"string\") {\n if (path = port, port = void 0, typeof host === \"function\")\n connectListener = host, host = void 0;\n } else if (typeof host == \"function\") {\n if (typeof port === \"string\")\n path = port, port = void 0;\n connectListener = host, host = void 0;\n }\n if (typeof port == \"object\") {\n var {\n port,\n host,\n path,\n socket,\n localAddress,\n localPort,\n family,\n hints,\n lookup,\n noDelay,\n keepAlive,\n keepAliveInitialDelay,\n requestCert,\n rejectUnauthorized,\n pauseOnConnect,\n servername,\n checkServerIdentity,\n session\n } = port;\n if (_checkServerIdentity = checkServerIdentity, this.servername = servername, socket)\n connection = socket;\n }\n if (!pauseOnConnect)\n this.resume();\n this.connecting = !0, this.remotePort = port;\n const bunTLS = this[bunTlsSymbol];\n var tls = void 0;\n if (typeof bunTLS === \"function\") {\n if (tls = bunTLS.call(this, port, host, !0), this._requestCert = !0, this._rejectUnauthorized = rejectUnauthorized, tls) {\n if (tls.rejectUnauthorized = rejectUnauthorized, tls.requestCert = !0, tls.session = session || tls.session, this.servername = tls.servername, tls.checkServerIdentity = _checkServerIdentity || tls.checkServerIdentity, this[bunTLSConnectOptions] = tls, !connection && tls.socket)\n connection = tls.socket;\n }\n if (connection) {\n if (typeof connection !== \"object\" || !(connection instanceof Socket2) || typeof connection[bunTlsSymbol] === \"function\")\n @throwTypeError(\"socket must be an instance of net.Socket\");\n }\n if (this.authorized = !1, this.secureConnecting = !0, this._secureEstablished = !1, this._securePending = !0, connectListener)\n this.on(\"secureConnect\", connectListener);\n } else if (connectListener)\n this.on(\"connect\", connectListener);\n if (connection) {\n const socket2 = connection[bunSocketInternal];\n if (socket2) {\n this.connecting = !0, this.#upgraded = !0;\n const result = socket2.upgradeTLS({\n data: this,\n tls,\n socket: Socket2.#Handlers\n });\n if (result) {\n const [raw, tls2] = result;\n connection[bunSocketInternal] = raw, raw.timeout(raw.timeout), raw.connecting = !1, this[bunSocketInternal] = tls2;\n } else\n throw this[bunSocketInternal] = null, new Error(\"Invalid socket\");\n } else\n connection.once(\"connect\", () => {\n const socket3 = connection[bunSocketInternal];\n if (!socket3)\n return;\n this.connecting = !0, this.#upgraded = !0;\n const result = socket3.upgradeTLS({\n data: this,\n tls,\n socket: Socket2.#Handlers\n });\n if (result) {\n const [raw, tls2] = result;\n connection[bunSocketInternal] = raw, raw.timeout(raw.timeout), raw.connecting = !1, this[bunSocketInternal] = tls2;\n } else\n throw this[bunSocketInternal] = null, new Error(\"Invalid socket\");\n });\n } else if (path)\n bunConnect({\n data: this,\n unix: path,\n socket: Socket2.#Handlers,\n tls\n }).catch((error) => {\n this.emit(\"error\", error), this.emit(\"close\");\n });\n else\n bunConnect({\n data: this,\n hostname: host || \"localhost\",\n port,\n socket: Socket2.#Handlers,\n tls\n }).catch((error) => {\n this.emit(\"error\", error), this.emit(\"close\");\n });\n return this;\n }\n _destroy(err, callback) {\n this[bunSocketInternal]\?.end(), callback(err);\n }\n _final(callback) {\n this[bunSocketInternal]\?.end(), callback();\n }\n get localAddress() {\n return \"127.0.0.1\";\n }\n get localFamily() {\n return \"IPv4\";\n }\n get localPort() {\n return this[bunSocketInternal]\?.localPort;\n }\n get pending() {\n return this.connecting;\n }\n _read(size) {\n const queue = this.#readQueue;\n let chunk;\n while (chunk = queue.peek()) {\n if (!this.push(chunk))\n return;\n queue.shift();\n }\n }\n get readyState() {\n if (this.connecting)\n return \"opening\";\n if (this.readable)\n return this.writable \? \"open\" : \"readOnly\";\n else\n return this.writable \? \"writeOnly\" : \"closed\";\n }\n ref() {\n this[bunSocketInternal]\?.ref();\n }\n get remoteAddress() {\n return this[bunSocketInternal]\?.remoteAddress;\n }\n get remoteFamily() {\n return \"IPv4\";\n }\n resetAndDestroy() {\n this[bunSocketInternal]\?.end();\n }\n setKeepAlive(enable = !1, initialDelay = 0) {\n return this;\n }\n setNoDelay(noDelay = !0) {\n return this;\n }\n setTimeout(timeout, callback) {\n if (this[bunSocketInternal]\?.timeout(timeout), this.timeout = timeout, callback)\n this.once(\"timeout\", callback);\n return this;\n }\n unref() {\n this[bunSocketInternal]\?.unref();\n }\n _write(chunk, encoding, callback) {\n if (typeof chunk == \"string\" && encoding !== \"ascii\")\n chunk = Buffer.from(chunk, encoding);\n var written = this[bunSocketInternal]\?.write(chunk);\n if (written == chunk.length)\n callback();\n else if (this.#writeCallback)\n callback(new Error(\"overlapping _write()\"));\n else {\n if (written > 0)\n if (typeof chunk == \"string\")\n chunk = chunk.slice(written);\n else\n chunk = chunk.subarray(written);\n this.#writeCallback = callback, this.#writeChunk = chunk;\n }\n }\n}), connect = createConnection;\n\nclass Server extends EventEmitter {\n #server;\n #listening = !1;\n [bunSocketServerConnections] = 0;\n [bunSocketServerOptions];\n maxConnections = 0;\n constructor(options, connectionListener) {\n super();\n if (typeof options === \"function\")\n connectionListener = options, options = {};\n else if (options == null || typeof options === \"object\")\n options = { ...options };\n else\n throw new Error(\"bun-net-polyfill: invalid arguments\");\n const { maxConnections } = options;\n this.maxConnections = Number.isSafeInteger(maxConnections) && maxConnections > 0 \? maxConnections : 0, options.connectionListener = connectionListener, this[bunSocketServerOptions] = options;\n }\n ref() {\n return this.#server\?.ref(), this;\n }\n unref() {\n return this.#server\?.unref(), this;\n }\n close(callback) {\n if (this.#server) {\n if (this.#server.stop(!0), this.#server = null, this.#listening = !1, this[bunSocketServerConnections] = 0, this.emit(\"close\"), typeof callback === \"function\")\n callback();\n return this;\n }\n if (typeof callback === \"function\") {\n const error = new Error(\"Server is not running\");\n error.code = \"ERR_SERVER_NOT_RUNNING\", callback(error);\n }\n return this;\n }\n address() {\n const server = this.#server;\n if (server) {\n const unix = server.unix;\n if (unix)\n return unix;\n let address = server.hostname;\n const type = isIP(address), port = server.port;\n if (typeof port === \"number\")\n return {\n port,\n address,\n family: type \? `IPv${type}` : void 0\n };\n if (type)\n return {\n address,\n family: type \? `IPv${type}` : void 0\n };\n return address;\n }\n return null;\n }\n getConnections(callback) {\n if (typeof callback === \"function\")\n callback(null, this.#server \? this[bunSocketServerConnections] : 0);\n return this;\n }\n listen(port, hostname, onListen) {\n let backlog, path, exclusive = !1;\n if (typeof port === \"string\") {\n if (Number.isSafeInteger(hostname)) {\n if (hostname > 0)\n backlog = hostname;\n } else if (typeof hostname === \"function\")\n onListen = hostname;\n path = port, hostname = void 0, port = void 0;\n } else {\n if (typeof hostname === \"function\")\n onListen = hostname, hostname = void 0;\n if (typeof port === \"function\")\n onListen = port, port = 0;\n else if (typeof port === \"object\") {\n const options = port;\n options.signal\?.addEventListener(\"abort\", () => this.close()), hostname = options.host, exclusive = options.exclusive === !0;\n const path2 = options.path;\n if (port = options.port, !Number.isSafeInteger(port) || port < 0)\n if (path2)\n hostname = path2, port = void 0;\n else {\n let message = 'The argument \\'options\\' must have the property \"port\" or \"path\"';\n try {\n message = `${message}. Received ${JSON.stringify(options)}`;\n } catch {\n }\n const error = @makeTypeError(message);\n throw error.code = \"ERR_INVALID_ARG_VALUE\", error;\n }\n else if (!Number.isSafeInteger(port) || port < 0)\n port = 0;\n if (typeof port.callback === \"function\")\n onListen = port\?.callback;\n } else if (!Number.isSafeInteger(port) || port < 0)\n port = 0;\n hostname = hostname || \"::\";\n }\n try {\n var tls = void 0, TLSSocketClass = void 0;\n const bunTLS = this[bunTlsSymbol], options = this[bunSocketServerOptions];\n if (typeof bunTLS === \"function\")\n [tls, TLSSocketClass] = bunTLS.call(this, port, hostname, !1), options.servername = tls.serverName, options.InternalSocketClass = TLSSocketClass;\n else\n options.InternalSocketClass = SocketClass;\n this.#server = Bun.listen(path \? {\n exclusive,\n unix: path,\n tls,\n socket: SocketClass[bunSocketServerHandlers]\n } : {\n exclusive,\n port,\n hostname,\n tls,\n socket: SocketClass[bunSocketServerHandlers]\n }), this.#server.data = this, this.#listening = !0, setTimeout(emitListeningNextTick, 1, this, onListen);\n } catch (err) {\n this.#listening = !1, setTimeout(emitErrorNextTick, 1, this, err);\n }\n return this;\n }\n}\n$ = {\n createServer,\n Server,\n createConnection,\n connect,\n isIP,\n isIPv4,\n isIPv6,\n Socket,\n [Symbol.for(\"::bunternal::\")]: SocketClass\n};\nreturn $})\n"_s; // // diff --git a/test/js/node/net/node-net.test.ts b/test/js/node/net/node-net.test.ts index c38a4ac1aa..efdff4bc55 100644 --- a/test/js/node/net/node-net.test.ts +++ b/test/js/node/net/node-net.test.ts @@ -357,14 +357,29 @@ describe("net.Socket write", () => { }); it("should handle connection error", done => { - var data = {}; + let errored = false; + // @ts-ignore - connect(55555, () => { + const socket = connect(55555, () => { done(new Error("Should not have connected")); - }).on("error", error => { + }); + + socket.on("error", error => { + if (errored) { + return done(new Error("Should not have errored twice")); + } + errored = true; expect(error).toBeDefined(); expect(error.name).toBe("SystemError"); expect(error.message).toBe("Failed to connect"); + }); + + socket.on("connect", () => { + done(new Error("Should not have connected")); + }); + + socket.on("close", () => { + expect(errored).toBe(true); done(); }); }); From acfd028e8f859a0e8139b7adab5d319e326c2373 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Tue, 5 Sep 2023 17:41:39 -0700 Subject: [PATCH 7/9] feat(runtime): Implement `fs.watchFile` (#4467) * really lame prototype * uses threads but badly * it works i guess * unwatchFile but lame * it works * test * a * aomitcs * fix unwatching race condition * use hasPendingActivity and GC stuff better * test * revert this --- ...igGeneratedClasses+DOMClientIsoSubspaces.h | 3 +- .../ZigGeneratedClasses+DOMIsoSubspaces.h | 3 +- .../ZigGeneratedClasses+lazyStructureHeader.h | 6 + .../ZigGeneratedClasses+lazyStructureImpl.h | 7 + src/bun.js/bindings/ZigGeneratedClasses.cpp | 336 ++++++++++++ src/bun.js/bindings/ZigGeneratedClasses.h | 83 +++ src/bun.js/bindings/generated_classes.zig | 95 ++++ .../bindings/generated_classes_list.zig | 1 + src/bun.js/node/node.classes.ts | 31 +- src/bun.js/node/node_fs.zig | 23 +- src/bun.js/node/node_fs_binding.zig | 2 + src/bun.js/node/node_fs_stat_watcher.zig | 503 ++++++++++++++++++ src/bun.js/rare_data.zig | 10 + src/js/node/fs.js | 90 +++- src/js/out/InternalModuleRegistryConstants.h | 6 +- src/jsc.zig | 1 + test/js/node/watch/fs.watchFile.test.ts | 121 +++++ 17 files changed, 1309 insertions(+), 12 deletions(-) create mode 100644 src/bun.js/node/node_fs_stat_watcher.zig create mode 100644 test/js/node/watch/fs.watchFile.test.ts diff --git a/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h b/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h index 3c3aa5669f..a364c0a489 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h +++ b/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h @@ -39,7 +39,8 @@ std::unique_ptr m_clientSubspaceForSHA256Constructor;std: std::unique_ptr m_clientSubspaceForSHA384Constructor;std::unique_ptr m_clientSubspaceForSHA512; std::unique_ptr m_clientSubspaceForSHA512Constructor;std::unique_ptr m_clientSubspaceForSHA512_256; std::unique_ptr m_clientSubspaceForSHA512_256Constructor;std::unique_ptr m_clientSubspaceForServerWebSocket; -std::unique_ptr m_clientSubspaceForServerWebSocketConstructor;std::unique_ptr m_clientSubspaceForStats; +std::unique_ptr m_clientSubspaceForServerWebSocketConstructor;std::unique_ptr m_clientSubspaceForStatWatcher; +std::unique_ptr m_clientSubspaceForStats; std::unique_ptr m_clientSubspaceForStatsConstructor;std::unique_ptr m_clientSubspaceForSubprocess; std::unique_ptr m_clientSubspaceForTCPSocket; std::unique_ptr m_clientSubspaceForTLSSocket; diff --git a/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h b/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h index a521090c07..e98ea16c35 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h +++ b/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h @@ -39,7 +39,8 @@ std::unique_ptr m_subspaceForSHA256Constructor;std::unique_ptr m_subspaceForSHA384Constructor;std::unique_ptr m_subspaceForSHA512; std::unique_ptr m_subspaceForSHA512Constructor;std::unique_ptr m_subspaceForSHA512_256; std::unique_ptr m_subspaceForSHA512_256Constructor;std::unique_ptr m_subspaceForServerWebSocket; -std::unique_ptr m_subspaceForServerWebSocketConstructor;std::unique_ptr m_subspaceForStats; +std::unique_ptr m_subspaceForServerWebSocketConstructor;std::unique_ptr m_subspaceForStatWatcher; +std::unique_ptr m_subspaceForStats; std::unique_ptr m_subspaceForStatsConstructor;std::unique_ptr m_subspaceForSubprocess; std::unique_ptr m_subspaceForTCPSocket; std::unique_ptr m_subspaceForTLSSocket; diff --git a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h index 0596372b4e..2c075a5089 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h +++ b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h @@ -244,6 +244,12 @@ JSC::Structure* JSServerWebSocketStructure() { return m_JSServerWebSocket.getIni JSC::LazyClassStructure m_JSServerWebSocket; bool hasJSServerWebSocketSetterValue { false }; mutable JSC::WriteBarrier m_JSServerWebSocketSetterValue; +JSC::Structure* JSStatWatcherStructure() { return m_JSStatWatcher.getInitializedOnMainThread(this); } + JSC::JSObject* JSStatWatcherConstructor() { return m_JSStatWatcher.constructorInitializedOnMainThread(this); } + JSC::JSValue JSStatWatcherPrototype() { return m_JSStatWatcher.prototypeInitializedOnMainThread(this); } + JSC::LazyClassStructure m_JSStatWatcher; + bool hasJSStatWatcherSetterValue { false }; + mutable JSC::WriteBarrier m_JSStatWatcherSetterValue; JSC::Structure* JSStatsStructure() { return m_JSStats.getInitializedOnMainThread(this); } JSC::JSObject* JSStatsConstructor() { return m_JSStats.constructorInitializedOnMainThread(this); } JSC::JSValue JSStatsPrototype() { return m_JSStats.prototypeInitializedOnMainThread(this); } diff --git a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h index 40a4737276..cb4c47ccb7 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h +++ b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h @@ -245,6 +245,12 @@ void GlobalObject::initGeneratedLazyClasses() { init.setStructure(WebCore::JSServerWebSocket::createStructure(init.vm, init.global, init.prototype)); init.setConstructor(WebCore::JSServerWebSocket::createConstructor(init.vm, init.global, init.prototype)); }); + m_JSStatWatcher.initLater( + [](LazyClassStructure::Initializer& init) { + init.setPrototype(WebCore::JSStatWatcher::createPrototype(init.vm, reinterpret_cast(init.global))); + init.setStructure(WebCore::JSStatWatcher::createStructure(init.vm, init.global, init.prototype)); + + }); m_JSStats.initLater( [](LazyClassStructure::Initializer& init) { init.setPrototype(WebCore::JSStats::createPrototype(init.vm, reinterpret_cast(init.global))); @@ -338,6 +344,7 @@ void GlobalObject::visitGeneratedLazyClasses(GlobalObject *thisObject, Visitor& thisObject->m_JSSHA512.visit(visitor); visitor.append(thisObject->m_JSSHA512SetterValue); thisObject->m_JSSHA512_256.visit(visitor); visitor.append(thisObject->m_JSSHA512_256SetterValue); thisObject->m_JSServerWebSocket.visit(visitor); visitor.append(thisObject->m_JSServerWebSocketSetterValue); + thisObject->m_JSStatWatcher.visit(visitor); visitor.append(thisObject->m_JSStatWatcherSetterValue); thisObject->m_JSStats.visit(visitor); visitor.append(thisObject->m_JSStatsSetterValue); thisObject->m_JSSubprocess.visit(visitor); visitor.append(thisObject->m_JSSubprocessSetterValue); thisObject->m_JSTCPSocket.visit(visitor); visitor.append(thisObject->m_JSTCPSocketSetterValue); diff --git a/src/bun.js/bindings/ZigGeneratedClasses.cpp b/src/bun.js/bindings/ZigGeneratedClasses.cpp index 523735c7b4..5a10a05d33 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses.cpp +++ b/src/bun.js/bindings/ZigGeneratedClasses.cpp @@ -14184,6 +14184,9 @@ JSC_DECLARE_HOST_FUNCTION(NodeJSFSPrototype__unlinkCallback); extern "C" EncodedJSValue NodeJSFSPrototype__unlinkSync(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(NodeJSFSPrototype__unlinkSyncCallback); +extern "C" EncodedJSValue NodeJSFSPrototype__unwatchFile(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(NodeJSFSPrototype__unwatchFileCallback); + extern "C" EncodedJSValue NodeJSFSPrototype__utimes(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(NodeJSFSPrototype__utimesCallback); @@ -14193,6 +14196,9 @@ JSC_DECLARE_HOST_FUNCTION(NodeJSFSPrototype__utimesSyncCallback); extern "C" EncodedJSValue NodeJSFSPrototype__watch(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(NodeJSFSPrototype__watchCallback); +extern "C" EncodedJSValue NodeJSFSPrototype__watchFile(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(NodeJSFSPrototype__watchFileCallback); + extern "C" EncodedJSValue NodeJSFSPrototype__write(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(NodeJSFSPrototype__writeCallback); @@ -14290,9 +14296,11 @@ static const HashTableValue JSNodeJSFSPrototypeTableValues[] = { { "truncateSync"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, NodeJSFSPrototype__truncateSyncCallback, 2 } }, { "unlink"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, NodeJSFSPrototype__unlinkCallback, 2 } }, { "unlinkSync"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, NodeJSFSPrototype__unlinkSyncCallback, 1 } }, + { "unwatchFile"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, NodeJSFSPrototype__unwatchFileCallback, 2 } }, { "utimes"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, NodeJSFSPrototype__utimesCallback, 4 } }, { "utimesSync"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, NodeJSFSPrototype__utimesSyncCallback, 3 } }, { "watch"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, NodeJSFSPrototype__watchCallback, 3 } }, + { "watchFile"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, NodeJSFSPrototype__watchFileCallback, 3 } }, { "write"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, NodeJSFSPrototype__writeCallback, 6 } }, { "writeFile"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, NodeJSFSPrototype__writeFileCallback, 4 } }, { "writeFileSync"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, NodeJSFSPrototype__writeFileSyncCallback, 3 } }, @@ -16411,6 +16419,34 @@ JSC_DEFINE_HOST_FUNCTION(NodeJSFSPrototype__unlinkSyncCallback, (JSGlobalObject return NodeJSFSPrototype__unlinkSync(thisObject->wrapped(), lexicalGlobalObject, callFrame); } +JSC_DEFINE_HOST_FUNCTION(NodeJSFSPrototype__unwatchFileCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSNodeJSFS* thisObject = jsDynamicCast(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + throwVMTypeError(lexicalGlobalObject, throwScope, "Expected 'this' to be instanceof NodeJSFS"_s); + return JSValue::encode({}); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + +#ifdef BUN_DEBUG + /** View the file name of the JS file that called this function + * from a debugger */ + SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm); + const char* fileName = sourceOrigin.string().utf8().data(); + static const char* lastFileName = nullptr; + if (lastFileName != fileName) { + lastFileName = fileName; + } +#endif + + return NodeJSFSPrototype__unwatchFile(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + JSC_DEFINE_HOST_FUNCTION(NodeJSFSPrototype__utimesCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { auto& vm = lexicalGlobalObject->vm(); @@ -16495,6 +16531,34 @@ JSC_DEFINE_HOST_FUNCTION(NodeJSFSPrototype__watchCallback, (JSGlobalObject * lex return NodeJSFSPrototype__watch(thisObject->wrapped(), lexicalGlobalObject, callFrame); } +JSC_DEFINE_HOST_FUNCTION(NodeJSFSPrototype__watchFileCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSNodeJSFS* thisObject = jsDynamicCast(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + throwVMTypeError(lexicalGlobalObject, throwScope, "Expected 'this' to be instanceof NodeJSFS"_s); + return JSValue::encode({}); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + +#ifdef BUN_DEBUG + /** View the file name of the JS file that called this function + * from a debugger */ + SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm); + const char* fileName = sourceOrigin.string().utf8().data(); + static const char* lastFileName = nullptr; + if (lastFileName != fileName) { + lastFileName = fileName; + } +#endif + + return NodeJSFSPrototype__watchFile(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + JSC_DEFINE_HOST_FUNCTION(NodeJSFSPrototype__writeCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { auto& vm = lexicalGlobalObject->vm(); @@ -21850,6 +21914,278 @@ void JSServerWebSocket::visitOutputConstraintsImpl(JSCell* cell, Visitor& visito } DEFINE_VISIT_OUTPUT_CONSTRAINTS(JSServerWebSocket); +class JSStatWatcherPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static JSStatWatcherPrototype* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSStatWatcherPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSStatWatcherPrototype(vm, globalObject, structure); + ptr->finishCreation(vm, globalObject); + return ptr; + } + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.plainObjectSpace(); + } + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + +private: + JSStatWatcherPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); +}; + +extern "C" void StatWatcherClass__finalize(void*); + +extern "C" EncodedJSValue StatWatcherPrototype__doClose(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(StatWatcherPrototype__closeCallback); + +extern "C" EncodedJSValue StatWatcherPrototype__doRef(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(StatWatcherPrototype__refCallback); + +extern "C" EncodedJSValue StatWatcherPrototype__doUnref(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(StatWatcherPrototype__unrefCallback); + +STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSStatWatcherPrototype, JSStatWatcherPrototype::Base); + +static const HashTableValue JSStatWatcherPrototypeTableValues[] = { + { "close"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, StatWatcherPrototype__closeCallback, 0 } }, + { "ref"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, StatWatcherPrototype__refCallback, 0 } }, + { "unref"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, StatWatcherPrototype__unrefCallback, 0 } } +}; + +const ClassInfo JSStatWatcherPrototype::s_info = { "StatWatcher"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSStatWatcherPrototype) }; + +JSC_DEFINE_HOST_FUNCTION(StatWatcherPrototype__closeCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSStatWatcher* thisObject = jsDynamicCast(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + throwVMTypeError(lexicalGlobalObject, throwScope, "Expected 'this' to be instanceof StatWatcher"_s); + return JSValue::encode({}); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + +#ifdef BUN_DEBUG + /** View the file name of the JS file that called this function + * from a debugger */ + SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm); + const char* fileName = sourceOrigin.string().utf8().data(); + static const char* lastFileName = nullptr; + if (lastFileName != fileName) { + lastFileName = fileName; + } +#endif + + return StatWatcherPrototype__doClose(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + +JSC_DEFINE_HOST_FUNCTION(StatWatcherPrototype__refCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSStatWatcher* thisObject = jsDynamicCast(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + throwVMTypeError(lexicalGlobalObject, throwScope, "Expected 'this' to be instanceof StatWatcher"_s); + return JSValue::encode({}); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + +#ifdef BUN_DEBUG + /** View the file name of the JS file that called this function + * from a debugger */ + SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm); + const char* fileName = sourceOrigin.string().utf8().data(); + static const char* lastFileName = nullptr; + if (lastFileName != fileName) { + lastFileName = fileName; + } +#endif + + return StatWatcherPrototype__doRef(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + +JSC_DEFINE_HOST_FUNCTION(StatWatcherPrototype__unrefCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSStatWatcher* thisObject = jsDynamicCast(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + throwVMTypeError(lexicalGlobalObject, throwScope, "Expected 'this' to be instanceof StatWatcher"_s); + return JSValue::encode({}); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + +#ifdef BUN_DEBUG + /** View the file name of the JS file that called this function + * from a debugger */ + SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm); + const char* fileName = sourceOrigin.string().utf8().data(); + static const char* lastFileName = nullptr; + if (lastFileName != fileName) { + lastFileName = fileName; + } +#endif + + return StatWatcherPrototype__doUnref(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + +extern "C" void StatWatcherPrototype__listenerSetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue value) +{ + auto& vm = globalObject->vm(); + auto* thisObject = jsCast(JSValue::decode(thisValue)); + thisObject->m_listener.set(vm, thisObject, JSValue::decode(value)); +} + +extern "C" EncodedJSValue StatWatcherPrototype__listenerGetCachedValue(JSC::EncodedJSValue thisValue) +{ + auto* thisObject = jsCast(JSValue::decode(thisValue)); + return JSValue::encode(thisObject->m_listener.get()); +} + +void JSStatWatcherPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSStatWatcher::info(), JSStatWatcherPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +extern "C" bool StatWatcher__hasPendingActivity(void* ptr); +bool JSStatWatcher::hasPendingActivity(void* ctx) +{ + return StatWatcher__hasPendingActivity(ctx); +} + +JSStatWatcher::~JSStatWatcher() +{ + if (m_ctx) { + StatWatcherClass__finalize(m_ctx); + } +} +void JSStatWatcher::destroy(JSCell* cell) +{ + static_cast(cell)->JSStatWatcher::~JSStatWatcher(); +} + +const ClassInfo JSStatWatcher::s_info = { "StatWatcher"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSStatWatcher) }; + +void JSStatWatcher::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); +} + +JSStatWatcher* JSStatWatcher::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx) +{ + JSStatWatcher* ptr = new (NotNull, JSC::allocateCell(vm)) JSStatWatcher(vm, structure, ctx); + ptr->finishCreation(vm); + return ptr; +} + +extern "C" void* StatWatcher__fromJS(JSC::EncodedJSValue value) +{ + JSC::JSValue decodedValue = JSC::JSValue::decode(value); + if (decodedValue.isEmpty() || !decodedValue.isCell()) + return nullptr; + + JSC::JSCell* cell = decodedValue.asCell(); + JSStatWatcher* object = JSC::jsDynamicCast(cell); + + if (!object) + return nullptr; + + return object->wrapped(); +} + +extern "C" bool StatWatcher__dangerouslySetPtr(JSC::EncodedJSValue value, void* ptr) +{ + JSStatWatcher* object = JSC::jsDynamicCast(JSValue::decode(value)); + if (!object) + return false; + + object->m_ctx = ptr; + return true; +} + +extern "C" const size_t StatWatcher__ptrOffset = JSStatWatcher::offsetOfWrapped(); + +void JSStatWatcher::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) +{ + auto* thisObject = jsCast(cell); + if (void* wrapped = thisObject->wrapped()) { + // if (thisObject->scriptExecutionContext()) + // analyzer.setLabelForCell(cell, "url " + thisObject->scriptExecutionContext()->url().string()); + } + Base::analyzeHeap(cell, analyzer); +} + +JSObject* JSStatWatcher::createPrototype(VM& vm, JSDOMGlobalObject* globalObject) +{ + return JSStatWatcherPrototype::create(vm, globalObject, JSStatWatcherPrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); +} + +extern "C" EncodedJSValue StatWatcher__create(Zig::GlobalObject* globalObject, void* ptr) +{ + auto& vm = globalObject->vm(); + JSC::Structure* structure = globalObject->JSStatWatcherStructure(); + JSStatWatcher* instance = JSStatWatcher::create(vm, globalObject, structure, ptr); + + return JSValue::encode(instance); +} + +template +void JSStatWatcher::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + JSStatWatcher* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + + thisObject->visitAdditionalChildren(visitor); +} + +DEFINE_VISIT_CHILDREN(JSStatWatcher); + +template +void JSStatWatcher::visitAdditionalChildren(Visitor& visitor) +{ + JSStatWatcher* thisObject = this; + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + visitor.append(thisObject->m_listener); + + visitor.addOpaqueRoot(this->wrapped()); +} + +DEFINE_VISIT_ADDITIONAL_CHILDREN(JSStatWatcher); + +template +void JSStatWatcher::visitOutputConstraintsImpl(JSCell* cell, Visitor& visitor) +{ + JSStatWatcher* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + thisObject->visitAdditionalChildren(visitor); +} + +DEFINE_VISIT_OUTPUT_CONSTRAINTS(JSStatWatcher); class JSStatsPrototype final : public JSC::JSNonFinalObject { public: using Base = JSC::JSNonFinalObject; diff --git a/src/bun.js/bindings/ZigGeneratedClasses.h b/src/bun.js/bindings/ZigGeneratedClasses.h index e0b10ff55f..142a96746a 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses.h +++ b/src/bun.js/bindings/ZigGeneratedClasses.h @@ -2283,6 +2283,89 @@ public: mutable JSC::WriteBarrier m_remoteAddress; }; +class JSStatWatcher final : public JSC::JSDestructibleObject { +public: + using Base = JSC::JSDestructibleObject; + static JSStatWatcher* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx); + + DECLARE_EXPORT_INFO; + template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForStatWatcher.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForStatWatcher = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForStatWatcher.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForStatWatcher = std::forward(space); }); + } + + static void destroy(JSC::JSCell*); + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(static_cast(0b11101110), StructureFlags), info()); + } + + static JSObject* createPrototype(VM& vm, JSDOMGlobalObject* globalObject); + ; + + ~JSStatWatcher(); + + void* wrapped() const { return m_ctx; } + + void detach() + { + m_ctx = nullptr; + } + + static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&); + static ptrdiff_t offsetOfWrapped() { return OBJECT_OFFSETOF(JSStatWatcher, m_ctx); } + + void* m_ctx { nullptr }; + + JSStatWatcher(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) + : Base(vm, structure) + { + m_ctx = sinkPtr; + m_weakThis = JSC::Weak(this, getOwner()); + } + + void finishCreation(JSC::VM&); + + JSC::Weak m_weakThis; + + static bool hasPendingActivity(void* ctx); + + class Owner final : public JSC::WeakHandleOwner { + public: + bool isReachableFromOpaqueRoots(JSC::Handle handle, void* context, JSC::AbstractSlotVisitor& visitor, const char** reason) final + { + auto* controller = JSC::jsCast(handle.slot()->asCell()); + if (JSStatWatcher::hasPendingActivity(controller->wrapped())) { + if (UNLIKELY(reason)) + *reason = "has pending activity"; + return true; + } + + return visitor.containsOpaqueRoot(context); + } + void finalize(JSC::Handle, void* context) final {} + }; + + static JSC::WeakHandleOwner* getOwner() + { + static NeverDestroyed m_owner; + return &m_owner.get(); + } + + DECLARE_VISIT_CHILDREN; + template void visitAdditionalChildren(Visitor&); + DECLARE_VISIT_OUTPUT_CONSTRAINTS; + + mutable JSC::WriteBarrier m_listener; +}; + class JSStats final : public JSC::JSDestructibleObject { public: using Base = JSC::JSDestructibleObject; diff --git a/src/bun.js/bindings/generated_classes.zig b/src/bun.js/bindings/generated_classes.zig index 392a5ae4d5..e7e16e8899 100644 --- a/src/bun.js/bindings/generated_classes.zig +++ b/src/bun.js/bindings/generated_classes.zig @@ -4114,12 +4114,16 @@ pub const JSNodeJSFS = struct { @compileLog("Expected NodeJSFS.unlink to be a callback but received " ++ @typeName(@TypeOf(NodeJSFS.unlink))); if (@TypeOf(NodeJSFS.unlinkSync) != CallbackType) @compileLog("Expected NodeJSFS.unlinkSync to be a callback but received " ++ @typeName(@TypeOf(NodeJSFS.unlinkSync))); + if (@TypeOf(NodeJSFS.unwatchFile) != CallbackType) + @compileLog("Expected NodeJSFS.unwatchFile to be a callback but received " ++ @typeName(@TypeOf(NodeJSFS.unwatchFile))); if (@TypeOf(NodeJSFS.utimes) != CallbackType) @compileLog("Expected NodeJSFS.utimes to be a callback but received " ++ @typeName(@TypeOf(NodeJSFS.utimes))); if (@TypeOf(NodeJSFS.utimesSync) != CallbackType) @compileLog("Expected NodeJSFS.utimesSync to be a callback but received " ++ @typeName(@TypeOf(NodeJSFS.utimesSync))); if (@TypeOf(NodeJSFS.watch) != CallbackType) @compileLog("Expected NodeJSFS.watch to be a callback but received " ++ @typeName(@TypeOf(NodeJSFS.watch))); + if (@TypeOf(NodeJSFS.watchFile) != CallbackType) + @compileLog("Expected NodeJSFS.watchFile to be a callback but received " ++ @typeName(@TypeOf(NodeJSFS.watchFile))); if (@TypeOf(NodeJSFS.write) != CallbackType) @compileLog("Expected NodeJSFS.write to be a callback but received " ++ @typeName(@TypeOf(NodeJSFS.write))); if (@TypeOf(NodeJSFS.writeFile) != CallbackType) @@ -4210,9 +4214,11 @@ pub const JSNodeJSFS = struct { @export(NodeJSFS.truncateSync, .{ .name = "NodeJSFSPrototype__truncateSync" }); @export(NodeJSFS.unlink, .{ .name = "NodeJSFSPrototype__unlink" }); @export(NodeJSFS.unlinkSync, .{ .name = "NodeJSFSPrototype__unlinkSync" }); + @export(NodeJSFS.unwatchFile, .{ .name = "NodeJSFSPrototype__unwatchFile" }); @export(NodeJSFS.utimes, .{ .name = "NodeJSFSPrototype__utimes" }); @export(NodeJSFS.utimesSync, .{ .name = "NodeJSFSPrototype__utimesSync" }); @export(NodeJSFS.watch, .{ .name = "NodeJSFSPrototype__watch" }); + @export(NodeJSFS.watchFile, .{ .name = "NodeJSFSPrototype__watchFile" }); @export(NodeJSFS.write, .{ .name = "NodeJSFSPrototype__write" }); @export(NodeJSFS.writeFile, .{ .name = "NodeJSFSPrototype__writeFile" }); @export(NodeJSFS.writeFileSync, .{ .name = "NodeJSFSPrototype__writeFileSync" }); @@ -5641,6 +5647,94 @@ pub const JSServerWebSocket = struct { } } }; +pub const JSStatWatcher = struct { + const StatWatcher = Classes.StatWatcher; + const GetterType = fn (*StatWatcher, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue; + const GetterTypeWithThisValue = fn (*StatWatcher, JSC.JSValue, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue; + const SetterType = fn (*StatWatcher, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool; + const SetterTypeWithThisValue = fn (*StatWatcher, JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool; + const CallbackType = fn (*StatWatcher, *JSC.JSGlobalObject, *JSC.CallFrame) callconv(.C) JSC.JSValue; + + /// Return the pointer to the wrapped object. + /// If the object does not match the type, return null. + pub fn fromJS(value: JSC.JSValue) ?*StatWatcher { + JSC.markBinding(@src()); + return StatWatcher__fromJS(value); + } + + extern fn StatWatcherPrototype__listenerSetCachedValue(JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) void; + + extern fn StatWatcherPrototype__listenerGetCachedValue(JSC.JSValue) JSC.JSValue; + + /// `StatWatcher.listener` setter + /// This value will be visited by the garbage collector. + pub fn listenerSetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { + JSC.markBinding(@src()); + StatWatcherPrototype__listenerSetCachedValue(thisValue, globalObject, value); + } + + /// `StatWatcher.listener` getter + /// This value will be visited by the garbage collector. + pub fn listenerGetCached(thisValue: JSC.JSValue) ?JSC.JSValue { + JSC.markBinding(@src()); + const result = StatWatcherPrototype__listenerGetCachedValue(thisValue); + if (result == .zero) + return null; + + return result; + } + + /// Create a new instance of StatWatcher + pub fn toJS(this: *StatWatcher, globalObject: *JSC.JSGlobalObject) JSC.JSValue { + JSC.markBinding(@src()); + if (comptime Environment.allow_assert) { + const value__ = StatWatcher__create(globalObject, this); + std.debug.assert(value__.as(StatWatcher).? == this); // If this fails, likely a C ABI issue. + return value__; + } else { + return StatWatcher__create(globalObject, this); + } + } + + /// Modify the internal ptr to point to a new instance of StatWatcher. + pub fn dangerouslySetPtr(value: JSC.JSValue, ptr: ?*StatWatcher) bool { + JSC.markBinding(@src()); + return StatWatcher__dangerouslySetPtr(value, ptr); + } + + /// Detach the ptr from the thisValue + pub fn detachPtr(_: *StatWatcher, value: JSC.JSValue) void { + JSC.markBinding(@src()); + std.debug.assert(StatWatcher__dangerouslySetPtr(value, null)); + } + + extern fn StatWatcher__fromJS(JSC.JSValue) ?*StatWatcher; + extern fn StatWatcher__getConstructor(*JSC.JSGlobalObject) JSC.JSValue; + + extern fn StatWatcher__create(globalObject: *JSC.JSGlobalObject, ptr: ?*StatWatcher) JSC.JSValue; + + extern fn StatWatcher__dangerouslySetPtr(JSC.JSValue, ?*StatWatcher) bool; + + comptime { + if (@TypeOf(StatWatcher.finalize) != (fn (*StatWatcher) callconv(.C) void)) { + @compileLog("StatWatcher.finalize is not a finalizer"); + } + + if (@TypeOf(StatWatcher.doClose) != CallbackType) + @compileLog("Expected StatWatcher.doClose to be a callback but received " ++ @typeName(@TypeOf(StatWatcher.doClose))); + if (@TypeOf(StatWatcher.doRef) != CallbackType) + @compileLog("Expected StatWatcher.doRef to be a callback but received " ++ @typeName(@TypeOf(StatWatcher.doRef))); + if (@TypeOf(StatWatcher.doUnref) != CallbackType) + @compileLog("Expected StatWatcher.doUnref to be a callback but received " ++ @typeName(@TypeOf(StatWatcher.doUnref))); + if (!JSC.is_bindgen) { + @export(StatWatcher.doClose, .{ .name = "StatWatcherPrototype__doClose" }); + @export(StatWatcher.doRef, .{ .name = "StatWatcherPrototype__doRef" }); + @export(StatWatcher.doUnref, .{ .name = "StatWatcherPrototype__doUnref" }); + @export(StatWatcher.finalize, .{ .name = "StatWatcherClass__finalize" }); + @export(StatWatcher.hasPendingActivity, .{ .name = "StatWatcher__hasPendingActivity" }); + } + } +}; pub const JSStats = struct { const Stats = Classes.Stats; const GetterType = fn (*Stats, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue; @@ -6932,6 +7026,7 @@ comptime { _ = JSSHA512; _ = JSSHA512_256; _ = JSServerWebSocket; + _ = JSStatWatcher; _ = JSStats; _ = JSSubprocess; _ = JSTCPSocket; diff --git a/src/bun.js/bindings/generated_classes_list.zig b/src/bun.js/bindings/generated_classes_list.zig index 20f36a458a..a86f74dd31 100644 --- a/src/bun.js/bindings/generated_classes_list.zig +++ b/src/bun.js/bindings/generated_classes_list.zig @@ -47,6 +47,7 @@ pub const Classes = struct { pub const BuildMessage = JSC.BuildMessage; pub const ResolveMessage = JSC.ResolveMessage; pub const FSWatcher = JSC.Node.FSWatcher; + pub const StatWatcher = JSC.Node.StatWatcher; pub const HTTPServer = JSC.API.HTTPServer; pub const HTTPSServer = JSC.API.HTTPSServer; pub const DebugHTTPServer = JSC.API.DebugHTTPServer; diff --git a/src/bun.js/node/node.classes.ts b/src/bun.js/node/node.classes.ts index 695a58d5ec..46c25ad064 100644 --- a/src/bun.js/node/node.classes.ts +++ b/src/bun.js/node/node.classes.ts @@ -30,6 +30,31 @@ export default [ }, values: ["listener"], }), + define({ + name: "StatWatcher", + construct: false, + noConstructor: true, + finalize: true, + configurable: false, + hasPendingActivity: true, + klass: {}, + JSType: "0b11101110", + proto: { + ref: { + fn: "doRef", + length: 0, + }, + unref: { + fn: "doUnref", + length: 0, + }, + close: { + fn: "doClose", + length: 0, + }, + }, + values: ["listener"], + }), define({ name: "Timeout", construct: false, @@ -471,15 +496,13 @@ export default [ symlinkSync: { fn: "symlinkSync", length: 3 }, truncate: { fn: "truncate", length: 3 }, truncateSync: { fn: "truncateSync", length: 2 }, - // TODO: - // unwatchFile: { fn: "unwatchFile", length: 2 }, + unwatchFile: { fn: "unwatchFile", length: 2 }, unlink: { fn: "unlink", length: 2 }, unlinkSync: { fn: "unlinkSync", length: 1 }, utimes: { fn: "utimes", length: 4 }, utimesSync: { fn: "utimesSync", length: 3 }, watch: { fn: "watch", length: 3 }, - // TODO: - // watchFile: { fn: "watchFile", length: 3 }, + watchFile: { fn: "watchFile", length: 3 }, writeFile: { fn: "writeFile", length: 4 }, writeFileSync: { fn: "writeFileSync", length: 3 }, write: { fn: "write", length: 6 }, diff --git a/src/bun.js/node/node_fs.zig b/src/bun.js/node/node_fs.zig index 5407c2d5c4..4f9e2bf687 100644 --- a/src/bun.js/node/node_fs.zig +++ b/src/bun.js/node/node_fs.zig @@ -3177,7 +3177,7 @@ pub const Arguments = struct { pub const UnwatchFile = void; pub const Watch = JSC.Node.FSWatcher.Arguments; - pub const WatchFile = void; + pub const WatchFile = JSC.Node.StatWatcher.Arguments; pub const Fsync = struct { fd: FileDescriptor, @@ -3362,7 +3362,7 @@ const Return = struct { pub const Unlink = void; pub const UnwatchFile = void; pub const Watch = JSC.JSValue; - pub const WatchFile = void; + pub const WatchFile = JSC.JSValue; pub const Utimes = void; pub const Chown = void; @@ -5245,6 +5245,25 @@ pub const NodeFS = struct { return Maybe(Return.Unlink).todo; } + pub fn watchFile(_: *NodeFS, args: Arguments.WatchFile, comptime flavor: Flavor) Maybe(Return.WatchFile) { + std.debug.assert(flavor == .sync); + + if (comptime Environment.isWindows) { + args.global_this.throwTODO("watch is not supported on Windows yet"); + return Maybe(Return.Watch){ .result = JSC.JSValue.undefined }; + } + + const watcher = args.createStatWatcher() catch |err| { + var buf = std.fmt.allocPrint(bun.default_allocator, "{s} watching {}", .{ @errorName(err), strings.QuotedFormatter{ .text = args.path.slice() } }) catch unreachable; + defer bun.default_allocator.free(buf); + args.global_this.throwValue((JSC.SystemError{ + .message = bun.String.init(buf), + .path = bun.String.init(args.path.slice()), + }).toErrorInstance(args.global_this)); + return Maybe(Return.Watch){ .result = JSC.JSValue.undefined }; + }; + return Maybe(Return.Watch){ .result = watcher }; + } pub fn unwatchFile(_: *NodeFS, _: Arguments.UnwatchFile, comptime _: Flavor) Maybe(Return.UnwatchFile) { return Maybe(Return.UnwatchFile).todo; } diff --git a/src/bun.js/node/node_fs_binding.zig b/src/bun.js/node/node_fs_binding.zig index 16128be443..b3866b5579 100644 --- a/src/bun.js/node/node_fs_binding.zig +++ b/src/bun.js/node/node_fs_binding.zig @@ -299,6 +299,8 @@ pub const NodeJSFS = struct { } pub const watch = callSync(.watch); + pub const watchFile = callSync(.watchFile); + pub const unwatchFile = callSync(.unwatchFile); // Not implemented yet: const notimpl = fdatasync; diff --git a/src/bun.js/node/node_fs_stat_watcher.zig b/src/bun.js/node/node_fs_stat_watcher.zig new file mode 100644 index 0000000000..c2690c2000 --- /dev/null +++ b/src/bun.js/node/node_fs_stat_watcher.zig @@ -0,0 +1,503 @@ +const std = @import("std"); +const JSC = @import("root").bun.JSC; +const bun = @import("root").bun; +const Fs = @import("../../fs.zig"); +const Path = @import("../../resolver/resolve_path.zig"); +const Encoder = JSC.WebCore.Encoder; +const Mutex = @import("../../lock.zig").Lock; +const uws = @import("../../deps/uws.zig"); + +const PathWatcher = @import("./path_watcher.zig"); + +const VirtualMachine = JSC.VirtualMachine; +const EventLoop = JSC.EventLoop; +const PathLike = JSC.Node.PathLike; +const ArgumentsSlice = JSC.Node.ArgumentsSlice; +const Output = bun.Output; +const string = bun.string; +const StoredFileDescriptorType = bun.StoredFileDescriptorType; +const Environment = bun.Environment; + +const StatsSmall = bun.JSC.Node.StatsSmall; +const StatsBig = bun.JSC.Node.StatsBig; + +const log = bun.Output.scoped(.StatWatcher, false); + +fn statToJSStats(globalThis: *JSC.JSGlobalObject, stats: bun.Stat, bigint: bool) JSC.JSValue { + if (bigint) { + return StatsBig.initWithAllocator(globalThis.allocator(), stats).toJS(globalThis); + } else { + return StatsSmall.initWithAllocator(globalThis.allocator(), stats).toJS(globalThis); + } +} + +/// This is a singleton struct that contains the timer used to schedule restat calls. +pub const StatWatcherScheduler = struct { + timer: ?*uws.Timer = null, + + head: std.atomic.Atomic(?*StatWatcher) = .{ .value = null }, + is_running: std.atomic.Atomic(bool) = .{ .value = false }, + + task: JSC.WorkPoolTask = .{ .callback = &workPoolCallback }, + + pub fn init(allocator: std.mem.Allocator, _: *bun.JSC.VirtualMachine) *StatWatcherScheduler { + var this = allocator.create(StatWatcherScheduler) catch @panic("out of memory"); + this.* = .{}; + return this; + } + + pub fn append(this: *StatWatcherScheduler, watcher: *StatWatcher) void { + log("append new watcher {s}", .{watcher.path}); + std.debug.assert(watcher.closed == false); + std.debug.assert(watcher.next == null); + + if (this.head.swap(watcher, .Monotonic)) |head| { + watcher.next = head; + if (!this.is_running.load(.Monotonic)) { + this.timer.?.set(this, timerCallback, 1, 0); + } + } else { + if (!this.is_running.load(.Monotonic)) { + watcher.last_check = std.time.Instant.now() catch unreachable; + + const vm = watcher.globalThis.bunVM(); + this.timer = uws.Timer.create( + vm.event_loop_handle orelse @panic("UWS Loop was not initialized yet."), + this, + ); + + this.timer.?.set(this, timerCallback, watcher.interval, 0); + log("I will wait {d} milli initially", .{watcher.interval}); + } + } + } + + pub fn timerCallback(timer: *uws.Timer) callconv(.C) void { + var this = timer.ext(StatWatcherScheduler).?; + this.is_running.store(true, .Monotonic); + JSC.WorkPool.schedule(&this.task); + } + + pub fn workPoolCallback(task: *JSC.WorkPoolTask) void { + var this: *StatWatcherScheduler = @fieldParentPtr(StatWatcherScheduler, "task", task); + // Instant.now will not fail on our target platforms. + const now = std.time.Instant.now() catch unreachable; + + var head: *StatWatcher = this.head.swap(null, .Monotonic).?; + + var prev = head; + while (prev.closed) { + var c = prev; + defer { + c.used_by_scheduler_thread = false; + } + + log("[1] removing closed watcher for '{s}'", .{prev.path}); + if (prev.next) |next| { + prev = next; + } else { + if (this.head.load(.Monotonic) == null) { + this.timer.?.deinit(); + this.timer = null; + // The scheduler is not deinit here, but it will get reused. + } + return; + } + } + + if (now.since(prev.last_check) > (@as(u64, @intCast(prev.interval)) * 1_000_000 -| 500)) { + prev.last_check = now; + prev.restat(); + } + var min_interval = prev.interval; + + var curr: ?*StatWatcher = prev.next; + while (curr) |c| : (curr = c.next) { + if (c.closed) { + log("[2] removing closed watcher for '{s}'", .{c.path}); + prev.next = c.next; + curr = c.next; + c.used_by_scheduler_thread = false; + continue; + } + if (now.since(c.last_check) > (@as(u64, @intCast(c.interval)) * 1_000_000 -| 500)) { + c.last_check = now; + c.restat(); + } + min_interval = @min(min_interval, c.interval); + prev = c; + curr = c.next; + } + + prev.next = this.head.swap(head, .Monotonic); + + log("I will wait {d} milli", .{min_interval}); + + this.timer.?.set(this, timerCallback, min_interval, 0); + } +}; + +pub const StatWatcher = struct { + next: ?*StatWatcher = null, + + ctx: *VirtualMachine, + + /// Closed is set to true to tell the scheduler to remove from list and mark `used_by_scheduler_thread` as false. + closed: bool, + /// When this is marked `false` this StatWatcher can get freed + used_by_scheduler_thread: bool, + + path: [:0]u8, + persistent: bool, + bigint: bool, + interval: i32, + last_check: std.time.Instant, + + globalThis: *JSC.JSGlobalObject, + js_this: JSC.JSValue, + + poll_ref: JSC.PollRef = .{}, + + last_stat: bun.Stat, + last_jsvalue: JSC.Strong, + + pub usingnamespace JSC.Codegen.JSStatWatcher; + + pub fn eventLoop(this: StatWatcher) *EventLoop { + return this.ctx.eventLoop(); + } + + pub fn enqueueTaskConcurrent(this: StatWatcher, task: *JSC.ConcurrentTask) void { + this.eventLoop().enqueueTaskConcurrent(task); + } + + pub fn deinit(this: *StatWatcher) void { + log("deinit\n", .{}); + std.debug.assert(!this.hasPendingActivity()); + + if (this.persistent) { + this.persistent = false; + this.poll_ref.unref(this.ctx); + } + this.closed = true; + this.last_jsvalue.clear(); + + bun.default_allocator.free(this.path); + bun.default_allocator.destroy(this); + } + + pub const Arguments = struct { + path: PathLike, + listener: JSC.JSValue, + + persistent: bool, + bigint: bool, + interval: i32, + + global_this: JSC.C.JSContextRef, + + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Arguments { + const vm = ctx.vm(); + const path = PathLike.fromJS(ctx, arguments, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "filename must be a string or TypedArray", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + + var listener: JSC.JSValue = .zero; + var persistent: bool = true; + var bigint: bool = false; + var interval: i32 = 5007; + + if (arguments.nextEat()) |options_or_callable| { + // options + if (options_or_callable.isObject()) { + if (options_or_callable.get(ctx, "persistent")) |persistent_| { + if (!persistent_.isBoolean()) { + JSC.throwInvalidArguments( + "persistent must be a boolean.", + .{}, + ctx, + exception, + ); + return null; + } + persistent = persistent_.toBoolean(); + } + + if (options_or_callable.get(ctx, "bigint")) |bigint_| { + if (!bigint_.isBoolean()) { + JSC.throwInvalidArguments( + "bigint must be a boolean.", + .{}, + ctx, + exception, + ); + return null; + } + bigint = bigint_.toBoolean(); + } + + if (options_or_callable.get(ctx, "interval")) |interval_| { + if (!interval_.isNumber()) { + JSC.throwInvalidArguments( + "bigint must be a boolean.", + .{}, + ctx, + exception, + ); + return null; + } + interval = interval_.toInt32(); //* + } + } + } + + if (arguments.nextEat()) |listener_| { + if (listener_.isCallable(vm)) { + listener = listener_.withAsyncContextIfNeeded(ctx); + } + } + + if (listener == .zero) { + exception.* = JSC.toInvalidArguments("Expected \"listener\" callback", .{}, ctx).asObjectRef(); + return null; + } + + return Arguments{ + .path = path, + .listener = listener, + .persistent = persistent, + .bigint = bigint, + .interval = interval, + .global_this = ctx, + }; + } + + pub fn createStatWatcher(this: Arguments) !JSC.JSValue { + const obj = try StatWatcher.init(this); + if (obj.js_this != .zero) { + return obj.js_this; + } + return JSC.JSValue.jsUndefined(); + } + }; + + pub fn doRef(this: *StatWatcher, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { + if (!this.closed and !this.persistent) { + this.persistent = true; + this.poll_ref.ref(this.ctx); + } + return JSC.JSValue.jsUndefined(); + } + + pub fn doUnref(this: *StatWatcher, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { + if (this.persistent) { + this.persistent = false; + this.poll_ref.unref(this.ctx); + } + return JSC.JSValue.jsUndefined(); + } + + pub fn hasPendingActivity(this: *StatWatcher) callconv(.C) bool { + return this.used_by_scheduler_thread; + } + + /// Stops file watching but does not free the instance. + pub fn close( + this: *StatWatcher, + ) void { + if (this.persistent) { + this.persistent = false; + this.poll_ref.unref(this.ctx); + } + this.closed = true; + this.last_jsvalue.clear(); + } + + pub fn doClose(this: *StatWatcher, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { + this.close(); + return JSC.JSValue.jsUndefined(); + } + + /// If the scheduler is not using this, free instantly, otherwise mark for being freed. + pub fn finalize(this: *StatWatcher) callconv(.C) void { + log("Finalize\n", .{}); + this.deinit(); + } + + pub const InitialStatTask = struct { + watcher: *StatWatcher, + task: JSC.WorkPoolTask = .{ .callback = &workPoolCallback }, + + pub fn createAndSchedule( + watcher: *StatWatcher, + ) void { + var task = bun.default_allocator.create(InitialStatTask) catch @panic("out of memory"); + task.* = .{ .watcher = watcher }; + JSC.WorkPool.schedule(&task.task); + } + + fn workPoolCallback(task: *JSC.WorkPoolTask) void { + var initial_stat_task: *InitialStatTask = @fieldParentPtr(InitialStatTask, "task", task); + defer bun.default_allocator.destroy(initial_stat_task); + const this = initial_stat_task.watcher; + + if (this.closed) { + this.used_by_scheduler_thread = false; + return; + } + + const stat = bun.sys.stat(this.path); + switch (stat) { + .result => |res| { + // we store the stat, but do not call the callback + this.last_stat = res; + this.enqueueTaskConcurrent(JSC.ConcurrentTask.fromCallback(this, initialStatSuccessOnMainThread)); + }, + .err => { + // on enoent, eperm, we call cb with two zeroed stat objects + // and store previous stat as a zeroed stat object, and then call the callback. + this.last_stat = std.mem.zeroes(bun.Stat); + this.enqueueTaskConcurrent(JSC.ConcurrentTask.fromCallback(this, initialStatErrorOnMainThread)); + }, + } + } + }; + + pub fn initialStatSuccessOnMainThread(this: *StatWatcher) void { + if (this.closed) { + this.used_by_scheduler_thread = false; + return; + } + + const jsvalue = statToJSStats(this.globalThis, this.last_stat, this.bigint); + this.last_jsvalue = JSC.Strong.create(jsvalue, this.globalThis); + + const vm = this.globalThis.bunVM(); + vm.rareData().nodeFSStatWatcherScheduler(vm).append(this); + } + + pub fn initialStatErrorOnMainThread(this: *StatWatcher) void { + if (this.closed) { + this.used_by_scheduler_thread = false; + return; + } + + const jsvalue = statToJSStats(this.globalThis, this.last_stat, this.bigint); + this.last_jsvalue = JSC.Strong.create(jsvalue, this.globalThis); + + const result = StatWatcher.listenerGetCached(this.js_this).?.call( + this.globalThis, + &[2]JSC.JSValue{ + jsvalue, + jsvalue, + }, + ); + + const vm = this.globalThis.bunVM(); + if (result.isAnyError()) { + vm.onUnhandledError(this.globalThis, result); + } + + vm.rareData().nodeFSStatWatcherScheduler(vm).append(this); + } + + /// Called from any thread + pub fn restat(this: *StatWatcher) void { + log("recalling stat", .{}); + const stat = bun.sys.stat(this.path); + const res = switch (stat) { + .result => |res| res, + .err => std.mem.zeroes(bun.Stat), + }; + + if (std.mem.eql(u8, std.mem.asBytes(&res), std.mem.asBytes(&this.last_stat))) return; + + this.last_stat = res; + this.enqueueTaskConcurrent(JSC.ConcurrentTask.fromCallback(this, swapAndCallListenerOnMainThread)); + } + + /// After a restat found the file changed, this calls the listener function. + pub fn swapAndCallListenerOnMainThread(this: *StatWatcher) void { + const prev_jsvalue = this.last_jsvalue.swap(); + const current_jsvalue = statToJSStats(this.globalThis, this.last_stat, this.bigint); + this.last_jsvalue.set(this.globalThis, current_jsvalue); + + const result = StatWatcher.listenerGetCached(this.js_this).?.call( + this.globalThis, + &[2]JSC.JSValue{ + current_jsvalue, + prev_jsvalue, + }, + ); + if (result.isAnyError()) { + const vm = this.globalThis.bunVM(); + vm.onUnhandledError(this.globalThis, result); + } + } + + pub fn onTimerInterval(timer: *uws.Timer) callconv(.C) void { + timer.ext(StatWatcher).?.restat(); + } + + pub fn init(args: Arguments) !*StatWatcher { + log("init", .{}); + + var buf: [bun.MAX_PATH_BYTES + 1]u8 = undefined; + var slice = args.path.slice(); + if (bun.strings.startsWith(slice, "file://")) { + slice = slice[6..]; + } + var parts = [_]string{slice}; + var file_path = Path.joinAbsStringBuf( + Fs.FileSystem.instance.top_level_dir, + &buf, + &parts, + .auto, + ); + + var alloc_file_path = try bun.default_allocator.allocSentinel(u8, file_path.len, 0); + errdefer bun.default_allocator.free(alloc_file_path); + @memcpy(alloc_file_path, file_path); + + var this = try bun.default_allocator.create(StatWatcher); + const vm = args.global_this.bunVM(); + this.* = .{ + .ctx = vm, + .persistent = args.persistent, + .bigint = args.bigint, + .interval = @max(5, args.interval), + .globalThis = args.global_this, + .js_this = .zero, + .closed = false, + .path = alloc_file_path, + .used_by_scheduler_thread = true, + // Instant.now will not fail on our target platforms. + .last_check = std.time.Instant.now() catch unreachable, + // InitStatTask is responsible for setting this + .last_stat = undefined, + .last_jsvalue = JSC.Strong.init(), + }; + errdefer this.deinit(); + + if (this.persistent) { + this.poll_ref.ref(this.ctx); + } + + const js_this = StatWatcher.toJS(this, this.globalThis); + this.js_this = js_this; + StatWatcher.listenerSetCached(js_this, this.globalThis, args.listener); + InitialStatTask.createAndSchedule(this); + + return this; + } +}; diff --git a/src/bun.js/rare_data.zig b/src/bun.js/rare_data.zig index 78354676fc..7068260331 100644 --- a/src/bun.js/rare_data.zig +++ b/src/bun.js/rare_data.zig @@ -10,6 +10,7 @@ const BoringSSL = @import("root").bun.BoringSSL; const bun = @import("root").bun; const WebSocketClientMask = @import("../http/websocket_http_client.zig").Mask; const UUID = @import("./uuid.zig"); +const StatWatcherScheduler = @import("./node/node_fs_stat_watcher.zig").StatWatcherScheduler; boring_ssl_engine: ?*BoringSSL.ENGINE = null, editor_context: EditorContext = EditorContext{}, @@ -32,6 +33,8 @@ global_dns_data: ?*JSC.DNS.GlobalData = null, mime_types: ?bun.HTTP.MimeType.Map = null, +node_fs_stat_watcher_scheduler: ?*StatWatcherScheduler = null, + pub fn hotMap(this: *RareData, allocator: std.mem.Allocator) *HotMap { if (this.hot_map == null) { this.hot_map = HotMap.init(allocator); @@ -314,3 +317,10 @@ pub fn globalDNSResolver(rare: *RareData, vm: *JSC.VirtualMachine) *JSC.DNS.DNSR return &rare.global_dns_data.?.resolver; } + +pub fn nodeFSStatWatcherScheduler(rare: *RareData, vm: *JSC.VirtualMachine) *StatWatcherScheduler { + return rare.node_fs_stat_watcher_scheduler orelse { + rare.node_fs_stat_watcher_scheduler = StatWatcherScheduler.init(vm.allocator, vm); + return rare.node_fs_stat_watcher_scheduler.?; + }; +} diff --git a/src/js/node/fs.js b/src/js/node/fs.js index ab68169043..2c6b8cfbe1 100644 --- a/src/js/node/fs.js +++ b/src/js/node/fs.js @@ -7,7 +7,6 @@ const Stream = require("node:stream"); const { isArrayBufferView } = require("node:util/types"); const constants = $processBindingConstants.fs; -const { COPYFILE_EXCL } = constants; var fs = Bun.fs(); class FSWatcher extends EventEmitter { @@ -65,6 +64,45 @@ class FSWatcher extends EventEmitter { unref() { this.#watcher?.unref(); } + + // https://github.com/nodejs/node/blob/9f51c55a47702dc6a0ca3569853dd7ba022bf7bb/lib/internal/fs/watchers.js#L259-L263 + start() {} +} + +/** Implemented in `node_fs_stat_watcher.zig` */ +// interface StatWatcherHandle { +// ref(); +// unref(); +// close(); +// } + +class StatWatcher extends EventEmitter { + // _handle: StatWatcherHandle; + + constructor(path, options) { + super(); + this._handle = fs.watchFile(path, options, this.#onChange.bind(this)); + } + + #onChange(curr, prev) { + this.emit("change", curr, prev); + } + + // https://github.com/nodejs/node/blob/9f51c55a47702dc6a0ca3569853dd7ba022bf7bb/lib/internal/fs/watchers.js#L259-L263 + start() {} + + stop() { + this._handle?.close(); + this._handle = null; + } + + ref() { + this._handle?.ref(); + } + + unref() { + this._handle?.unref(); + } } var access = function access(...args) { @@ -316,6 +354,54 @@ var access = function access(...args) { return new FSWatcher(path, options, listener); }; +// TODO: move this entire thing into native code. +// the reason it's not done right now is because there isnt a great way to have multiple +// listeners per StatWatcher with the current implementation in native code. the downside +// of this means we need to do path validation in the js side of things +const statWatchers = new Map(); +let _pathModule; +function getValidatedPath(p) { + if (p instanceof URL) return Bun.fileURLToPath(p); + if (typeof p !== "string") throw new TypeError("Path must be a string or URL."); + return (_pathModule ??= require("node:path")).resolve(p); +} +function watchFile(filename, options, listener) { + filename = getValidatedPath(filename); + + if (typeof options === "function") { + listener = options; + options = {}; + } + + if (typeof listener !== "function") { + throw new TypeError("listener must be a function"); + } + + var stat = statWatchers.get(filename); + if (!stat) { + stat = new StatWatcher(filename, options); + statWatchers.set(filename, stat); + } + stat.addListener("change", listener); + return stat; +} +function unwatchFile(filename, listener) { + filename = getValidatedPath(filename); + + var stat = statWatchers.get(filename); + if (!stat) return; + if (listener) { + stat.removeListener("change", listener); + if (stat.listenerCount("change") !== 0) { + return; + } + } else { + stat.removeAllListeners("change"); + } + stat.stop(); + statWatchers.delete(filename); +} + function callbackify(fsFunction, args) { try { const result = fsFunction.apply(fs, args.slice(0, args.length - 1)); @@ -1200,9 +1286,11 @@ export default { truncateSync, unlink, unlinkSync, + unwatchFile, utimes, utimesSync, watch, + watchFile, write, writeFile, writeFileSync, diff --git a/src/js/out/InternalModuleRegistryConstants.h b/src/js/out/InternalModuleRegistryConstants.h index edcbd8baf4..65da73e669 100644 --- a/src/js/out/InternalModuleRegistryConstants.h +++ b/src/js/out/InternalModuleRegistryConstants.h @@ -82,7 +82,7 @@ static constexpr ASCIILiteral NodeEventsCode = "(function (){\"use strict\";// s // // -static constexpr ASCIILiteral NodeFSCode = "(function (){\"use strict\";// src/js/out/tmp/node/fs.ts\nvar callbackify = function(fsFunction, args) {\n try {\n const result = fsFunction.apply(fs, args.slice(0, args.length - 1)), callback = args[args.length - 1];\n if (typeof callback === \"function\")\n queueMicrotask(() => callback(null, result));\n } catch (e) {\n const callback = args[args.length - 1];\n if (typeof callback === \"function\")\n queueMicrotask(() => callback(e));\n }\n}, createReadStream = function(path, options) {\n return new ReadStream(path, options);\n}, createWriteStream = function(path, options) {\n return new WriteStream(path, options);\n}, cpSync = function(src, dest, options) {\n if (!options)\n return fs.cpSync(src, dest);\n if (typeof options !== \"object\")\n @throwTypeError(\"options must be an object\");\n if (options.dereference || options.filter || options.preserveTimestamps || options.verbatimSymlinks) {\n if (!lazy_cpSync)\n lazy_cpSync = @getInternalField(@internalModuleRegistry, 3) || @createInternalModuleById(3);\n return lazy_cpSync(src, dest, options);\n }\n return fs.cpSync(src, dest, options.recursive, options.errorOnExist, options.force \?\? !0, options.mode);\n}, cp = function(src, dest, options, callback) {\n if (typeof options === \"function\")\n callback = options, options = void 0;\n promises.cp(src, dest, options).then(() => callback(), callback);\n}, _toUnixTimestamp = function(time, name = \"time\") {\n if (typeof time === \"string\" && +time == time)\n return +time;\n if (NumberIsFinite(time)) {\n if (time < 0)\n return DateNow() / 1000;\n return time;\n }\n if (isDate(time))\n return DatePrototypeGetTime(time) / 1000;\n @throwTypeError(`Expected ${name} to be a number or Date`);\n}, $, ReadStream, WriteStream, EventEmitter = @getInternalField(@internalModuleRegistry, 18) || @createInternalModuleById(18), promises = @getInternalField(@internalModuleRegistry, 20) || @createInternalModuleById(20), Stream = @getInternalField(@internalModuleRegistry, 37) || @createInternalModuleById(37), { isArrayBufferView } = @requireNativeModule(\"node:util/types\"), constants = @processBindingConstants.fs;\nvar fs = Bun.fs();\n\nclass FSWatcher extends EventEmitter {\n #watcher;\n #listener;\n constructor(path, options, listener) {\n super();\n if (typeof options === \"function\")\n listener = options, options = {};\n else if (typeof options === \"string\")\n options = { encoding: options };\n if (typeof listener !== \"function\")\n listener = () => {\n };\n this.#listener = listener;\n try {\n this.#watcher = fs.watch(path, options || {}, this.#onEvent.bind(this));\n } catch (e) {\n if (!e.message\?.startsWith(\"FileNotFound\"))\n throw e;\n const notFound = new Error(`ENOENT: no such file or directory, watch '${path}'`);\n throw notFound.code = \"ENOENT\", notFound.errno = -2, notFound.path = path, notFound.syscall = \"watch\", notFound.filename = path, notFound;\n }\n }\n #onEvent(eventType, filenameOrError) {\n if (eventType === \"error\" || eventType === \"close\")\n this.emit(eventType, filenameOrError);\n else\n this.emit(\"change\", eventType, filenameOrError), this.#listener(eventType, filenameOrError);\n }\n close() {\n this.#watcher\?.close(), this.#watcher = null;\n }\n ref() {\n this.#watcher\?.ref();\n }\n unref() {\n this.#watcher\?.unref();\n }\n}\nvar access = function access2(...args) {\n callbackify(fs.accessSync, args);\n}, appendFile = function appendFile2(...args) {\n callbackify(fs.appendFileSync, args);\n}, close = function close2(...args) {\n callbackify(fs.closeSync, args);\n}, rm = function rm2(...args) {\n callbackify(fs.rmSync, args);\n}, rmdir = function rmdir2(...args) {\n callbackify(fs.rmdirSync, args);\n}, copyFile = function copyFile2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.copyFile(...args).then((result) => callback(null, result), callback);\n}, exists = function exists2(...args) {\n callbackify(fs.existsSync, args);\n}, chown = function chown2(...args) {\n callbackify(fs.chownSync, args);\n}, chmod = function chmod2(...args) {\n callbackify(fs.chmodSync, args);\n}, fchmod = function fchmod2(...args) {\n callbackify(fs.fchmodSync, args);\n}, fchown = function fchown2(...args) {\n callbackify(fs.fchownSync, args);\n}, fstat = function fstat2(...args) {\n callbackify(fs.fstatSync, args);\n}, fsync = function fsync2(...args) {\n callbackify(fs.fsyncSync, args);\n}, ftruncate = function ftruncate2(...args) {\n callbackify(fs.ftruncateSync, args);\n}, futimes = function futimes2(...args) {\n callbackify(fs.futimesSync, args);\n}, lchmod = function lchmod2(...args) {\n callbackify(fs.lchmodSync, args);\n}, lchown = function lchown2(...args) {\n callbackify(fs.lchownSync, args);\n}, link = function link2(...args) {\n callbackify(fs.linkSync, args);\n}, mkdir = function mkdir2(...args) {\n callbackify(fs.mkdirSync, args);\n}, mkdtemp = function mkdtemp2(...args) {\n callbackify(fs.mkdtempSync, args);\n}, open = function open2(...args) {\n callbackify(fs.openSync, args);\n}, read = function read2(fd, buffer, offsetOrOptions, length, position, callback) {\n let offset = offsetOrOptions, params = null;\n if (arguments.length <= 4) {\n if (arguments.length === 4)\n callback = length, params = offsetOrOptions;\n else if (arguments.length === 3) {\n if (!isArrayBufferView(buffer))\n params = buffer, { buffer = Buffer.alloc(16384) } = params \?\? {};\n callback = offsetOrOptions;\n } else\n callback = buffer, buffer = Buffer.alloc(16384);\n ({ offset = 0, length = buffer\?.byteLength - offset, position = null } = params \?\? {});\n }\n queueMicrotask(() => {\n try {\n var bytesRead = fs.readSync(fd, buffer, offset, length, position);\n } catch (e) {\n callback(e);\n }\n callback(null, bytesRead, buffer);\n });\n}, write = function write2(...args) {\n callbackify(fs.writeSync, args);\n}, readdir = function readdir2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.readdir(...args).then((result) => callback(null, result), callback);\n}, readFile = function readFile2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.readFile(...args).then((result) => callback(null, result), callback);\n}, writeFile = function writeFile2(...args) {\n callbackify(fs.writeFileSync, args);\n}, readlink = function readlink2(...args) {\n callbackify(fs.readlinkSync, args);\n}, realpath = function realpath2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.realpath(...args).then((result) => callback(null, result), callback);\n}, rename = function rename2(...args) {\n callbackify(fs.renameSync, args);\n}, lstat = function lstat2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.lstat(...args).then((result) => callback(null, result), callback);\n}, stat = function stat2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.stat(...args).then((result) => callback(null, result), callback);\n}, symlink = function symlink2(...args) {\n callbackify(fs.symlinkSync, args);\n}, truncate = function truncate2(...args) {\n callbackify(fs.truncateSync, args);\n}, unlink = function unlink2(...args) {\n callbackify(fs.unlinkSync, args);\n}, utimes = function utimes2(...args) {\n callbackify(fs.utimesSync, args);\n}, lutimes = function lutimes2(...args) {\n callbackify(fs.lutimesSync, args);\n}, accessSync = fs.accessSync.bind(fs), appendFileSync = fs.appendFileSync.bind(fs), closeSync = fs.closeSync.bind(fs), copyFileSync = fs.copyFileSync.bind(fs), existsSync = fs.existsSync.bind(fs), chownSync = fs.chownSync.bind(fs), chmodSync = fs.chmodSync.bind(fs), fchmodSync = fs.fchmodSync.bind(fs), fchownSync = fs.fchownSync.bind(fs), fstatSync = fs.fstatSync.bind(fs), fsyncSync = fs.fsyncSync.bind(fs), ftruncateSync = fs.ftruncateSync.bind(fs), futimesSync = fs.futimesSync.bind(fs), lchmodSync = fs.lchmodSync.bind(fs), lchownSync = fs.lchownSync.bind(fs), linkSync = fs.linkSync.bind(fs), lstatSync = fs.lstatSync.bind(fs), mkdirSync = fs.mkdirSync.bind(fs), mkdtempSync = fs.mkdtempSync.bind(fs), openSync = fs.openSync.bind(fs), readSync = fs.readSync.bind(fs), writeSync = fs.writeSync.bind(fs), readdirSync = fs.readdirSync.bind(fs), readFileSync = fs.readFileSync.bind(fs), writeFileSync = fs.writeFileSync.bind(fs), readlinkSync = fs.readlinkSync.bind(fs), realpathSync = fs.realpathSync.bind(fs), renameSync = fs.renameSync.bind(fs), statSync = fs.statSync.bind(fs), symlinkSync = fs.symlinkSync.bind(fs), truncateSync = fs.truncateSync.bind(fs), unlinkSync = fs.unlinkSync.bind(fs), utimesSync = fs.utimesSync.bind(fs), lutimesSync = fs.lutimesSync.bind(fs), rmSync = fs.rmSync.bind(fs), rmdirSync = fs.rmdirSync.bind(fs), writev = (fd, buffers, position, callback) => {\n if (typeof position === \"function\")\n callback = position, position = null;\n queueMicrotask(() => {\n try {\n var written = fs.writevSync(fd, buffers, position);\n } catch (e) {\n callback(e);\n }\n callback(null, written, buffers);\n });\n}, writevSync = fs.writevSync.bind(fs), readv = (fd, buffers, position, callback) => {\n if (typeof position === \"function\")\n callback = position, position = null;\n queueMicrotask(() => {\n try {\n var written = fs.readvSync(fd, buffers, position);\n } catch (e) {\n callback(e);\n }\n callback(null, written, buffers);\n });\n}, readvSync = fs.readvSync.bind(fs), Dirent = fs.Dirent, Stats = fs.Stats, watch = function watch2(path, options, listener) {\n return new FSWatcher(path, options, listener);\n}, readStreamPathFastPathSymbol = Symbol.for(\"Bun.Node.readStreamPathFastPath\"), readStreamSymbol = Symbol.for(\"Bun.NodeReadStream\"), readStreamPathOrFdSymbol = Symbol.for(\"Bun.NodeReadStreamPathOrFd\"), writeStreamSymbol = Symbol.for(\"Bun.NodeWriteStream\"), writeStreamPathFastPathSymbol = Symbol.for(\"Bun.NodeWriteStreamFastPath\"), writeStreamPathFastPathCallSymbol = Symbol.for(\"Bun.NodeWriteStreamFastPathCall\"), kIoDone = Symbol.for(\"kIoDone\"), defaultReadStreamOptions = {\n file: void 0,\n fd: null,\n flags: \"r\",\n encoding: void 0,\n mode: 438,\n autoClose: !0,\n emitClose: !0,\n start: 0,\n end: Infinity,\n highWaterMark: 65536,\n fs: {\n read,\n open: (path, flags, mode, cb) => {\n var fd;\n try {\n fd = openSync(path, flags, mode);\n } catch (e) {\n cb(e);\n return;\n }\n cb(null, fd);\n },\n openSync,\n close\n },\n autoDestroy: !0\n}, ReadStreamClass;\nReadStream = function(InternalReadStream) {\n ReadStreamClass = InternalReadStream, Object.defineProperty(ReadStreamClass.prototype, Symbol.toStringTag, {\n value: \"ReadStream\",\n enumerable: !1\n });\n function ReadStream3(path, options) {\n return new InternalReadStream(path, options);\n }\n return ReadStream3.prototype = InternalReadStream.prototype, Object.defineProperty(ReadStream3, Symbol.hasInstance, {\n value(instance) {\n return instance instanceof InternalReadStream;\n }\n });\n}(class ReadStream2 extends Stream._getNativeReadableStreamPrototype(2, Stream.Readable) {\n constructor(pathOrFd, options = defaultReadStreamOptions) {\n if (typeof options !== \"object\" || !options)\n @throwTypeError(\"Expected options to be an object\");\n var {\n flags = defaultReadStreamOptions.flags,\n encoding = defaultReadStreamOptions.encoding,\n mode = defaultReadStreamOptions.mode,\n autoClose = defaultReadStreamOptions.autoClose,\n emitClose = defaultReadStreamOptions.emitClose,\n start = defaultReadStreamOptions.start,\n end = defaultReadStreamOptions.end,\n autoDestroy = defaultReadStreamOptions.autoClose,\n fs: fs2 = defaultReadStreamOptions.fs,\n highWaterMark = defaultReadStreamOptions.highWaterMark,\n fd = defaultReadStreamOptions.fd\n } = options;\n if (pathOrFd\?.constructor\?.name === \"URL\")\n pathOrFd = Bun.fileURLToPath(pathOrFd);\n var tempThis = {};\n if (fd != null) {\n if (typeof fd !== \"number\")\n @throwTypeError(\"Expected options.fd to be a number\");\n tempThis.fd = tempThis[readStreamPathOrFdSymbol] = fd, tempThis.autoClose = !1;\n } else if (typeof pathOrFd === \"string\") {\n if (pathOrFd.startsWith(\"file://\"))\n pathOrFd = Bun.fileURLToPath(pathOrFd);\n if (pathOrFd.length === 0)\n @throwTypeError(\"Expected path to be a non-empty string\");\n tempThis.path = tempThis.file = tempThis[readStreamPathOrFdSymbol] = pathOrFd;\n } else if (typeof pathOrFd === \"number\") {\n if (pathOrFd |= 0, pathOrFd < 0)\n @throwTypeError(\"Expected fd to be a positive integer\");\n tempThis.fd = tempThis[readStreamPathOrFdSymbol] = pathOrFd, tempThis.autoClose = !1;\n } else\n @throwTypeError(\"Expected a path or file descriptor\");\n if (tempThis.fd === void 0)\n tempThis.fd = fs2.openSync(pathOrFd, flags, mode);\n var fileRef = Bun.file(tempThis.fd), stream = fileRef.stream(), native = @direct(stream);\n if (!native)\n throw new Error(\"no native readable stream\");\n var { stream: ptr } = native;\n super(ptr, {\n ...options,\n encoding,\n autoDestroy,\n autoClose,\n emitClose,\n highWaterMark\n });\n if (Object.assign(this, tempThis), this.#fileRef = fileRef, this.end = end, this._read = this.#internalRead, this.start = start, this.flags = flags, this.mode = mode, this.emitClose = emitClose, this[readStreamPathFastPathSymbol] = start === 0 && end === Infinity && autoClose && fs2 === defaultReadStreamOptions.fs && (encoding === \"buffer\" || encoding === \"binary\" || encoding == null || encoding === \"utf-8\" || encoding === \"utf8\"), this._readableState.autoClose = autoDestroy = autoClose, this._readableState.highWaterMark = highWaterMark, start !== void 0)\n this.pos = start;\n }\n #fileRef;\n #fs;\n file;\n path;\n fd = null;\n flags;\n mode;\n start;\n end;\n pos;\n bytesRead = 0;\n #fileSize = -1;\n _read;\n [readStreamSymbol] = !0;\n [readStreamPathOrFdSymbol];\n [readStreamPathFastPathSymbol];\n _construct(callback) {\n if (super._construct)\n super._construct(callback);\n else\n callback();\n this.emit(\"open\", this.fd), this.emit(\"ready\");\n }\n _destroy(err, cb) {\n super._destroy(err, cb);\n try {\n var fd = this.fd;\n if (this[readStreamPathFastPathSymbol] = !1, !fd)\n cb(err);\n else\n this.#fs.close(fd, (er) => {\n cb(er || err);\n }), this.fd = null;\n } catch (e) {\n throw e;\n }\n }\n close(cb) {\n if (typeof cb === \"function\")\n Stream.eos(this, cb);\n this.destroy();\n }\n push(chunk) {\n var bytesRead = chunk\?.length \?\? 0;\n if (bytesRead > 0) {\n this.bytesRead += bytesRead;\n var currPos = this.pos;\n if (currPos !== void 0) {\n if (this.bytesRead < currPos)\n return !0;\n if (currPos === this.start) {\n var n = this.bytesRead - currPos;\n chunk = chunk.slice(-n);\n var [_, ...rest] = arguments;\n if (this.pos = this.bytesRead, this.end !== void 0 && this.bytesRead > this.end)\n chunk = chunk.slice(0, this.end - this.start + 1);\n return super.push(chunk, ...rest);\n }\n var end = this.end;\n if (end !== void 0 && this.bytesRead > end) {\n chunk = chunk.slice(0, end - currPos + 1);\n var [_, ...rest] = arguments;\n return this.pos = this.bytesRead, super.push(chunk, ...rest);\n }\n this.pos = this.bytesRead;\n }\n }\n return super.push(...arguments);\n }\n #internalRead(n) {\n var { pos, end, bytesRead, fd, encoding } = this;\n if (n = pos !== void 0 \? Math.min(end - pos + 1, n) : Math.min(end - bytesRead + 1, n), n <= 0) {\n this.push(null);\n return;\n }\n if (this.#fileSize === -1 && bytesRead === 0 && pos === void 0) {\n var stat3 = fstatSync(fd);\n if (this.#fileSize = stat3.size, this.#fileSize > 0 && n > this.#fileSize)\n n = this.#fileSize + 1;\n }\n this[kIoDone] = !1;\n var res = super._read(n);\n if (@isPromise(res)) {\n var then = res\?.then;\n if (then && @isCallable(then))\n res.then(() => {\n if (this[kIoDone] = !0, this.destroyed)\n this.emit(kIoDone);\n }, (er) => {\n this[kIoDone] = !0, this.#errorOrDestroy(er);\n });\n } else if (this[kIoDone] = !0, this.destroyed)\n this.emit(kIoDone), this.#errorOrDestroy(new Error(\"ERR_STREAM_PREMATURE_CLOSE\"));\n }\n #errorOrDestroy(err, sync = null) {\n var {\n _readableState: r = { destroyed: !1, autoDestroy: !1 },\n _writableState: w = { destroyed: !1, autoDestroy: !1 }\n } = this;\n if (w\?.destroyed || r\?.destroyed)\n return this;\n if (r\?.autoDestroy || w\?.autoDestroy)\n this.destroy(err);\n else if (err)\n this.emit(\"error\", err);\n }\n pause() {\n return this[readStreamPathFastPathSymbol] = !1, super.pause();\n }\n resume() {\n return this[readStreamPathFastPathSymbol] = !1, super.resume();\n }\n unshift(...args) {\n return this[readStreamPathFastPathSymbol] = !1, super.unshift(...args);\n }\n pipe(dest, pipeOpts) {\n if (this[readStreamPathFastPathSymbol] && (pipeOpts\?.end \?\? !0) && this._readableState\?.pipes\?.length === 0) {\n if ((writeStreamPathFastPathSymbol in dest) && dest[writeStreamPathFastPathSymbol]) {\n if (dest[writeStreamPathFastPathCallSymbol](this, pipeOpts))\n return this;\n }\n }\n return this[readStreamPathFastPathSymbol] = !1, super.pipe(dest, pipeOpts);\n }\n});\nvar defaultWriteStreamOptions = {\n fd: null,\n start: void 0,\n pos: void 0,\n encoding: void 0,\n flags: \"w\",\n mode: 438,\n fs: {\n write,\n close,\n open,\n openSync\n }\n}, WriteStreamClass;\nWriteStream = function(InternalWriteStream) {\n WriteStreamClass = InternalWriteStream, Object.defineProperty(WriteStreamClass.prototype, Symbol.toStringTag, {\n value: \"WritesStream\",\n enumerable: !1\n });\n function WriteStream3(path, options) {\n return new InternalWriteStream(path, options);\n }\n return WriteStream3.prototype = InternalWriteStream.prototype, Object.defineProperty(WriteStream3, Symbol.hasInstance, {\n value(instance) {\n return instance instanceof InternalWriteStream;\n }\n });\n}(class WriteStream2 extends Stream.NativeWritable {\n constructor(path, options = defaultWriteStreamOptions) {\n if (!options)\n @throwTypeError(\"Expected options to be an object\");\n var {\n fs: fs2 = defaultWriteStreamOptions.fs,\n start = defaultWriteStreamOptions.start,\n flags = defaultWriteStreamOptions.flags,\n mode = defaultWriteStreamOptions.mode,\n autoClose = !0,\n emitClose = !1,\n autoDestroy = autoClose,\n encoding = defaultWriteStreamOptions.encoding,\n fd = defaultWriteStreamOptions.fd,\n pos = defaultWriteStreamOptions.pos\n } = options, tempThis = {};\n if (fd != null) {\n if (typeof fd !== \"number\")\n throw new Error(\"Expected options.fd to be a number\");\n tempThis.fd = fd, tempThis[writeStreamPathFastPathSymbol] = !1;\n } else if (typeof path === \"string\") {\n if (path.length === 0)\n @throwTypeError(\"Expected a non-empty path\");\n if (path.startsWith(\"file:\"))\n path = Bun.fileURLToPath(path);\n tempThis.path = path, tempThis.fd = null, tempThis[writeStreamPathFastPathSymbol] = autoClose && (start === void 0 || start === 0) && fs2.write === defaultWriteStreamOptions.fs.write && fs2.close === defaultWriteStreamOptions.fs.close;\n }\n if (tempThis.fd == null)\n tempThis.fd = fs2.openSync(path, flags, mode);\n super(tempThis.fd, {\n ...options,\n decodeStrings: !1,\n autoDestroy,\n emitClose,\n fd: tempThis\n });\n if (Object.assign(this, tempThis), typeof fs2\?.write !== \"function\")\n @throwTypeError(\"Expected fs.write to be a function\");\n if (typeof fs2\?.close !== \"function\")\n @throwTypeError(\"Expected fs.close to be a function\");\n if (typeof fs2\?.open !== \"function\")\n @throwTypeError(\"Expected fs.open to be a function\");\n if (typeof path === \"object\" && path) {\n if (path instanceof URL)\n path = Bun.fileURLToPath(path);\n }\n if (typeof path !== \"string\" && typeof fd !== \"number\")\n @throwTypeError(\"Expected a path or file descriptor\");\n if (this.start = start, this.#fs = fs2, this.flags = flags, this.mode = mode, this.start !== void 0)\n this.pos = this.start;\n if (encoding !== defaultWriteStreamOptions.encoding) {\n if (this.setDefaultEncoding(encoding), encoding !== \"buffer\" && encoding !== \"utf8\" && encoding !== \"utf-8\" && encoding !== \"binary\")\n this[writeStreamPathFastPathSymbol] = !1;\n }\n }\n get autoClose() {\n return this._writableState.autoDestroy;\n }\n set autoClose(val) {\n this._writableState.autoDestroy = val;\n }\n destroySoon = this.end;\n open() {\n }\n path;\n fd;\n flags;\n mode;\n #fs;\n bytesWritten = 0;\n pos;\n [writeStreamPathFastPathSymbol];\n [writeStreamSymbol] = !0;\n start;\n [writeStreamPathFastPathCallSymbol](readStream, pipeOpts) {\n if (!this[writeStreamPathFastPathSymbol])\n return !1;\n if (this.fd !== null)\n return this[writeStreamPathFastPathSymbol] = !1, !1;\n return this[kIoDone] = !1, readStream[kIoDone] = !1, Bun.write(this[writeStreamPathFastPathSymbol], readStream[readStreamPathOrFdSymbol]).then((bytesWritten) => {\n readStream[kIoDone] = this[kIoDone] = !0, this.bytesWritten += bytesWritten, readStream.bytesRead += bytesWritten, this.end(), readStream.close();\n }, (err) => {\n readStream[kIoDone] = this[kIoDone] = !0, this.#errorOrDestroy(err), readStream.emit(\"error\", err);\n });\n }\n isBunFastPathEnabled() {\n return this[writeStreamPathFastPathSymbol];\n }\n disableBunFastPath() {\n this[writeStreamPathFastPathSymbol] = !1;\n }\n #handleWrite(er, bytes) {\n if (er)\n return this.#errorOrDestroy(er);\n this.bytesWritten += bytes;\n }\n #internalClose(err, cb) {\n this[writeStreamPathFastPathSymbol] = !1;\n var fd = this.fd;\n this.#fs.close(fd, (er) => {\n this.fd = null, cb(err || er);\n });\n }\n _construct(callback) {\n if (typeof this.fd === \"number\") {\n callback();\n return;\n }\n callback(), this.emit(\"open\", this.fd), this.emit(\"ready\");\n }\n _destroy(err, cb) {\n if (this.fd === null)\n return cb(err);\n if (this[kIoDone]) {\n this.once(kIoDone, () => this.#internalClose(err, cb));\n return;\n }\n this.#internalClose(err, cb);\n }\n [kIoDone] = !1;\n close(cb) {\n if (cb) {\n if (this.closed) {\n process.nextTick(cb);\n return;\n }\n this.on(\"close\", cb);\n }\n if (!this.autoClose)\n this.on(\"finish\", this.destroy);\n this.end();\n }\n write(chunk, encoding = this._writableState.defaultEncoding, cb) {\n if (this[writeStreamPathFastPathSymbol] = !1, typeof chunk === \"string\")\n chunk = Buffer.from(chunk, encoding);\n var native = this.pos === void 0;\n const callback = native \? (err, bytes) => {\n if (this[kIoDone] = !1, this.#handleWrite(err, bytes), this.emit(kIoDone), cb)\n !err \? cb() : cb(err);\n } : () => {\n };\n if (this[kIoDone] = !0, this._write)\n return this._write(chunk, encoding, callback);\n else\n return super.write(chunk, encoding, callback, native);\n }\n end(chunk, encoding, cb) {\n var native = this.pos === void 0;\n return super.end(chunk, encoding, cb, native);\n }\n _write = void 0;\n _writev = void 0;\n get pending() {\n return this.fd === null;\n }\n _destroy(err, cb) {\n this.close(err, cb);\n }\n #errorOrDestroy(err) {\n var {\n _readableState: r = { destroyed: !1, autoDestroy: !1 },\n _writableState: w = { destroyed: !1, autoDestroy: !1 }\n } = this;\n if (w\?.destroyed || r\?.destroyed)\n return this;\n if (r\?.autoDestroy || w\?.autoDestroy)\n this.destroy(err);\n else if (err)\n this.emit(\"error\", err);\n }\n});\nObject.defineProperties(fs, {\n createReadStream: {\n value: createReadStream\n },\n createWriteStream: {\n value: createWriteStream\n },\n ReadStream: {\n value: ReadStream\n },\n WriteStream: {\n value: WriteStream\n }\n});\nrealpath.native = realpath;\nrealpathSync.native = realpathSync;\nvar lazy_cpSync = null;\n$ = {\n Dirent,\n FSWatcher,\n ReadStream,\n Stats,\n WriteStream,\n _toUnixTimestamp,\n access,\n accessSync,\n appendFile,\n appendFileSync,\n chmod,\n chmodSync,\n chown,\n chownSync,\n close,\n closeSync,\n constants,\n copyFile,\n copyFileSync,\n cp,\n cpSync,\n createReadStream,\n createWriteStream,\n exists,\n existsSync,\n fchmod,\n fchmodSync,\n fchown,\n fchownSync,\n fstat,\n fstatSync,\n fsync,\n fsyncSync,\n ftruncate,\n ftruncateSync,\n futimes,\n futimesSync,\n lchmod,\n lchmodSync,\n lchown,\n lchownSync,\n link,\n linkSync,\n lstat,\n lstatSync,\n lutimes,\n lutimesSync,\n mkdir,\n mkdirSync,\n mkdtemp,\n mkdtempSync,\n open,\n openSync,\n promises,\n read,\n readFile,\n readFileSync,\n readSync,\n readdir,\n readdirSync,\n readlink,\n readlinkSync,\n readv,\n readvSync,\n realpath,\n realpathSync,\n rename,\n renameSync,\n rm,\n rmSync,\n rmdir,\n rmdirSync,\n stat,\n statSync,\n symlink,\n symlinkSync,\n truncate,\n truncateSync,\n unlink,\n unlinkSync,\n utimes,\n utimesSync,\n watch,\n write,\n writeFile,\n writeFileSync,\n writeSync,\n writev,\n writevSync,\n [Symbol.for(\"::bunternal::\")]: {\n ReadStreamClass,\n WriteStreamClass\n }\n};\nreturn $})\n"_s; +static constexpr ASCIILiteral NodeFSCode = "(function (){\"use strict\";// src/js/out/tmp/node/fs.ts\nvar getValidatedPath = function(p) {\n if (p instanceof URL)\n return Bun.fileURLToPath(p);\n if (typeof p !== \"string\")\n @throwTypeError(\"Path must be a string or URL.\");\n return (_pathModule \?\?= @getInternalField(@internalModuleRegistry, 28) || @createInternalModuleById(28)).resolve(p);\n}, watchFile = function(filename, options, listener) {\n if (filename = getValidatedPath(filename), typeof options === \"function\")\n listener = options, options = {};\n if (typeof listener !== \"function\")\n @throwTypeError(\"listener must be a function\");\n var stat = statWatchers.get(filename);\n if (!stat)\n stat = new StatWatcher(filename, options), statWatchers.set(filename, stat);\n return stat.addListener(\"change\", listener), stat;\n}, unwatchFile = function(filename, listener) {\n filename = getValidatedPath(filename);\n var stat = statWatchers.get(filename);\n if (!stat)\n return;\n if (listener) {\n if (stat.removeListener(\"change\", listener), stat.listenerCount(\"change\") !== 0)\n return;\n } else\n stat.removeAllListeners(\"change\");\n stat.stop(), statWatchers.delete(filename);\n}, callbackify = function(fsFunction, args) {\n try {\n const result = fsFunction.apply(fs, args.slice(0, args.length - 1)), callback = args[args.length - 1];\n if (typeof callback === \"function\")\n queueMicrotask(() => callback(null, result));\n } catch (e) {\n const callback = args[args.length - 1];\n if (typeof callback === \"function\")\n queueMicrotask(() => callback(e));\n }\n}, createReadStream = function(path, options) {\n return new ReadStream(path, options);\n}, createWriteStream = function(path, options) {\n return new WriteStream(path, options);\n}, cpSync = function(src, dest, options) {\n if (!options)\n return fs.cpSync(src, dest);\n if (typeof options !== \"object\")\n @throwTypeError(\"options must be an object\");\n if (options.dereference || options.filter || options.preserveTimestamps || options.verbatimSymlinks) {\n if (!lazy_cpSync)\n lazy_cpSync = @getInternalField(@internalModuleRegistry, 3) || @createInternalModuleById(3);\n return lazy_cpSync(src, dest, options);\n }\n return fs.cpSync(src, dest, options.recursive, options.errorOnExist, options.force \?\? !0, options.mode);\n}, cp = function(src, dest, options, callback) {\n if (typeof options === \"function\")\n callback = options, options = void 0;\n promises.cp(src, dest, options).then(() => callback(), callback);\n}, _toUnixTimestamp = function(time, name = \"time\") {\n if (typeof time === \"string\" && +time == time)\n return +time;\n if (NumberIsFinite(time)) {\n if (time < 0)\n return DateNow() / 1000;\n return time;\n }\n if (isDate(time))\n return DatePrototypeGetTime(time) / 1000;\n @throwTypeError(`Expected ${name} to be a number or Date`);\n}, $, ReadStream, WriteStream, EventEmitter = @getInternalField(@internalModuleRegistry, 18) || @createInternalModuleById(18), promises = @getInternalField(@internalModuleRegistry, 20) || @createInternalModuleById(20), Stream = @getInternalField(@internalModuleRegistry, 37) || @createInternalModuleById(37), { isArrayBufferView } = @requireNativeModule(\"node:util/types\"), constants = @processBindingConstants.fs, fs = Bun.fs();\n\nclass FSWatcher extends EventEmitter {\n #watcher;\n #listener;\n constructor(path, options, listener) {\n super();\n if (typeof options === \"function\")\n listener = options, options = {};\n else if (typeof options === \"string\")\n options = { encoding: options };\n if (typeof listener !== \"function\")\n listener = () => {\n };\n this.#listener = listener;\n try {\n this.#watcher = fs.watch(path, options || {}, this.#onEvent.bind(this));\n } catch (e) {\n if (!e.message\?.startsWith(\"FileNotFound\"))\n throw e;\n const notFound = new Error(`ENOENT: no such file or directory, watch '${path}'`);\n throw notFound.code = \"ENOENT\", notFound.errno = -2, notFound.path = path, notFound.syscall = \"watch\", notFound.filename = path, notFound;\n }\n }\n #onEvent(eventType, filenameOrError) {\n if (eventType === \"error\" || eventType === \"close\")\n this.emit(eventType, filenameOrError);\n else\n this.emit(\"change\", eventType, filenameOrError), this.#listener(eventType, filenameOrError);\n }\n close() {\n this.#watcher\?.close(), this.#watcher = null;\n }\n ref() {\n this.#watcher\?.ref();\n }\n unref() {\n this.#watcher\?.unref();\n }\n start() {\n }\n}\n\nclass StatWatcher extends EventEmitter {\n constructor(path, options) {\n super();\n this._handle = fs.watchFile(path, options, this.#onChange.bind(this));\n }\n #onChange(curr, prev) {\n this.emit(\"change\", curr, prev);\n }\n start() {\n }\n stop() {\n this._handle\?.close(), this._handle = null;\n }\n ref() {\n this._handle\?.ref();\n }\n unref() {\n this._handle\?.unref();\n }\n}\nvar access = function access2(...args) {\n callbackify(fs.accessSync, args);\n}, appendFile = function appendFile2(...args) {\n callbackify(fs.appendFileSync, args);\n}, close = function close2(...args) {\n callbackify(fs.closeSync, args);\n}, rm = function rm2(...args) {\n callbackify(fs.rmSync, args);\n}, rmdir = function rmdir2(...args) {\n callbackify(fs.rmdirSync, args);\n}, copyFile = function copyFile2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.copyFile(...args).then((result) => callback(null, result), callback);\n}, exists = function exists2(...args) {\n callbackify(fs.existsSync, args);\n}, chown = function chown2(...args) {\n callbackify(fs.chownSync, args);\n}, chmod = function chmod2(...args) {\n callbackify(fs.chmodSync, args);\n}, fchmod = function fchmod2(...args) {\n callbackify(fs.fchmodSync, args);\n}, fchown = function fchown2(...args) {\n callbackify(fs.fchownSync, args);\n}, fstat = function fstat2(...args) {\n callbackify(fs.fstatSync, args);\n}, fsync = function fsync2(...args) {\n callbackify(fs.fsyncSync, args);\n}, ftruncate = function ftruncate2(...args) {\n callbackify(fs.ftruncateSync, args);\n}, futimes = function futimes2(...args) {\n callbackify(fs.futimesSync, args);\n}, lchmod = function lchmod2(...args) {\n callbackify(fs.lchmodSync, args);\n}, lchown = function lchown2(...args) {\n callbackify(fs.lchownSync, args);\n}, link = function link2(...args) {\n callbackify(fs.linkSync, args);\n}, mkdir = function mkdir2(...args) {\n callbackify(fs.mkdirSync, args);\n}, mkdtemp = function mkdtemp2(...args) {\n callbackify(fs.mkdtempSync, args);\n}, open = function open2(...args) {\n callbackify(fs.openSync, args);\n}, read = function read2(fd, buffer, offsetOrOptions, length, position, callback) {\n let offset = offsetOrOptions, params = null;\n if (arguments.length <= 4) {\n if (arguments.length === 4)\n callback = length, params = offsetOrOptions;\n else if (arguments.length === 3) {\n if (!isArrayBufferView(buffer))\n params = buffer, { buffer = Buffer.alloc(16384) } = params \?\? {};\n callback = offsetOrOptions;\n } else\n callback = buffer, buffer = Buffer.alloc(16384);\n ({ offset = 0, length = buffer\?.byteLength - offset, position = null } = params \?\? {});\n }\n queueMicrotask(() => {\n try {\n var bytesRead = fs.readSync(fd, buffer, offset, length, position);\n } catch (e) {\n callback(e);\n }\n callback(null, bytesRead, buffer);\n });\n}, write = function write2(...args) {\n callbackify(fs.writeSync, args);\n}, readdir = function readdir2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.readdir(...args).then((result) => callback(null, result), callback);\n}, readFile = function readFile2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.readFile(...args).then((result) => callback(null, result), callback);\n}, writeFile = function writeFile2(...args) {\n callbackify(fs.writeFileSync, args);\n}, readlink = function readlink2(...args) {\n callbackify(fs.readlinkSync, args);\n}, realpath = function realpath2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.realpath(...args).then((result) => callback(null, result), callback);\n}, rename = function rename2(...args) {\n callbackify(fs.renameSync, args);\n}, lstat = function lstat2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.lstat(...args).then((result) => callback(null, result), callback);\n}, stat = function stat2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.stat(...args).then((result) => callback(null, result), callback);\n}, symlink = function symlink2(...args) {\n callbackify(fs.symlinkSync, args);\n}, truncate = function truncate2(...args) {\n callbackify(fs.truncateSync, args);\n}, unlink = function unlink2(...args) {\n callbackify(fs.unlinkSync, args);\n}, utimes = function utimes2(...args) {\n callbackify(fs.utimesSync, args);\n}, lutimes = function lutimes2(...args) {\n callbackify(fs.lutimesSync, args);\n}, accessSync = fs.accessSync.bind(fs), appendFileSync = fs.appendFileSync.bind(fs), closeSync = fs.closeSync.bind(fs), copyFileSync = fs.copyFileSync.bind(fs), existsSync = fs.existsSync.bind(fs), chownSync = fs.chownSync.bind(fs), chmodSync = fs.chmodSync.bind(fs), fchmodSync = fs.fchmodSync.bind(fs), fchownSync = fs.fchownSync.bind(fs), fstatSync = fs.fstatSync.bind(fs), fsyncSync = fs.fsyncSync.bind(fs), ftruncateSync = fs.ftruncateSync.bind(fs), futimesSync = fs.futimesSync.bind(fs), lchmodSync = fs.lchmodSync.bind(fs), lchownSync = fs.lchownSync.bind(fs), linkSync = fs.linkSync.bind(fs), lstatSync = fs.lstatSync.bind(fs), mkdirSync = fs.mkdirSync.bind(fs), mkdtempSync = fs.mkdtempSync.bind(fs), openSync = fs.openSync.bind(fs), readSync = fs.readSync.bind(fs), writeSync = fs.writeSync.bind(fs), readdirSync = fs.readdirSync.bind(fs), readFileSync = fs.readFileSync.bind(fs), writeFileSync = fs.writeFileSync.bind(fs), readlinkSync = fs.readlinkSync.bind(fs), realpathSync = fs.realpathSync.bind(fs), renameSync = fs.renameSync.bind(fs), statSync = fs.statSync.bind(fs), symlinkSync = fs.symlinkSync.bind(fs), truncateSync = fs.truncateSync.bind(fs), unlinkSync = fs.unlinkSync.bind(fs), utimesSync = fs.utimesSync.bind(fs), lutimesSync = fs.lutimesSync.bind(fs), rmSync = fs.rmSync.bind(fs), rmdirSync = fs.rmdirSync.bind(fs), writev = (fd, buffers, position, callback) => {\n if (typeof position === \"function\")\n callback = position, position = null;\n queueMicrotask(() => {\n try {\n var written = fs.writevSync(fd, buffers, position);\n } catch (e) {\n callback(e);\n }\n callback(null, written, buffers);\n });\n}, writevSync = fs.writevSync.bind(fs), readv = (fd, buffers, position, callback) => {\n if (typeof position === \"function\")\n callback = position, position = null;\n queueMicrotask(() => {\n try {\n var written = fs.readvSync(fd, buffers, position);\n } catch (e) {\n callback(e);\n }\n callback(null, written, buffers);\n });\n}, readvSync = fs.readvSync.bind(fs), Dirent = fs.Dirent, Stats = fs.Stats, watch = function watch2(path, options, listener) {\n return new FSWatcher(path, options, listener);\n}, statWatchers = new Map, _pathModule, readStreamPathFastPathSymbol = Symbol.for(\"Bun.Node.readStreamPathFastPath\"), readStreamSymbol = Symbol.for(\"Bun.NodeReadStream\"), readStreamPathOrFdSymbol = Symbol.for(\"Bun.NodeReadStreamPathOrFd\"), writeStreamSymbol = Symbol.for(\"Bun.NodeWriteStream\"), writeStreamPathFastPathSymbol = Symbol.for(\"Bun.NodeWriteStreamFastPath\"), writeStreamPathFastPathCallSymbol = Symbol.for(\"Bun.NodeWriteStreamFastPathCall\"), kIoDone = Symbol.for(\"kIoDone\"), defaultReadStreamOptions = {\n file: void 0,\n fd: null,\n flags: \"r\",\n encoding: void 0,\n mode: 438,\n autoClose: !0,\n emitClose: !0,\n start: 0,\n end: Infinity,\n highWaterMark: 65536,\n fs: {\n read,\n open: (path, flags, mode, cb) => {\n var fd;\n try {\n fd = openSync(path, flags, mode);\n } catch (e) {\n cb(e);\n return;\n }\n cb(null, fd);\n },\n openSync,\n close\n },\n autoDestroy: !0\n}, ReadStreamClass;\nReadStream = function(InternalReadStream) {\n ReadStreamClass = InternalReadStream, Object.defineProperty(ReadStreamClass.prototype, Symbol.toStringTag, {\n value: \"ReadStream\",\n enumerable: !1\n });\n function ReadStream3(path, options) {\n return new InternalReadStream(path, options);\n }\n return ReadStream3.prototype = InternalReadStream.prototype, Object.defineProperty(ReadStream3, Symbol.hasInstance, {\n value(instance) {\n return instance instanceof InternalReadStream;\n }\n });\n}(class ReadStream2 extends Stream._getNativeReadableStreamPrototype(2, Stream.Readable) {\n constructor(pathOrFd, options = defaultReadStreamOptions) {\n if (typeof options !== \"object\" || !options)\n @throwTypeError(\"Expected options to be an object\");\n var {\n flags = defaultReadStreamOptions.flags,\n encoding = defaultReadStreamOptions.encoding,\n mode = defaultReadStreamOptions.mode,\n autoClose = defaultReadStreamOptions.autoClose,\n emitClose = defaultReadStreamOptions.emitClose,\n start = defaultReadStreamOptions.start,\n end = defaultReadStreamOptions.end,\n autoDestroy = defaultReadStreamOptions.autoClose,\n fs: fs2 = defaultReadStreamOptions.fs,\n highWaterMark = defaultReadStreamOptions.highWaterMark,\n fd = defaultReadStreamOptions.fd\n } = options;\n if (pathOrFd\?.constructor\?.name === \"URL\")\n pathOrFd = Bun.fileURLToPath(pathOrFd);\n var tempThis = {};\n if (fd != null) {\n if (typeof fd !== \"number\")\n @throwTypeError(\"Expected options.fd to be a number\");\n tempThis.fd = tempThis[readStreamPathOrFdSymbol] = fd, tempThis.autoClose = !1;\n } else if (typeof pathOrFd === \"string\") {\n if (pathOrFd.startsWith(\"file://\"))\n pathOrFd = Bun.fileURLToPath(pathOrFd);\n if (pathOrFd.length === 0)\n @throwTypeError(\"Expected path to be a non-empty string\");\n tempThis.path = tempThis.file = tempThis[readStreamPathOrFdSymbol] = pathOrFd;\n } else if (typeof pathOrFd === \"number\") {\n if (pathOrFd |= 0, pathOrFd < 0)\n @throwTypeError(\"Expected fd to be a positive integer\");\n tempThis.fd = tempThis[readStreamPathOrFdSymbol] = pathOrFd, tempThis.autoClose = !1;\n } else\n @throwTypeError(\"Expected a path or file descriptor\");\n if (tempThis.fd === void 0)\n tempThis.fd = fs2.openSync(pathOrFd, flags, mode);\n var fileRef = Bun.file(tempThis.fd), stream = fileRef.stream(), native = @direct(stream);\n if (!native)\n throw new Error(\"no native readable stream\");\n var { stream: ptr } = native;\n super(ptr, {\n ...options,\n encoding,\n autoDestroy,\n autoClose,\n emitClose,\n highWaterMark\n });\n if (Object.assign(this, tempThis), this.#fileRef = fileRef, this.end = end, this._read = this.#internalRead, this.start = start, this.flags = flags, this.mode = mode, this.emitClose = emitClose, this[readStreamPathFastPathSymbol] = start === 0 && end === Infinity && autoClose && fs2 === defaultReadStreamOptions.fs && (encoding === \"buffer\" || encoding === \"binary\" || encoding == null || encoding === \"utf-8\" || encoding === \"utf8\"), this._readableState.autoClose = autoDestroy = autoClose, this._readableState.highWaterMark = highWaterMark, start !== void 0)\n this.pos = start;\n }\n #fileRef;\n #fs;\n file;\n path;\n fd = null;\n flags;\n mode;\n start;\n end;\n pos;\n bytesRead = 0;\n #fileSize = -1;\n _read;\n [readStreamSymbol] = !0;\n [readStreamPathOrFdSymbol];\n [readStreamPathFastPathSymbol];\n _construct(callback) {\n if (super._construct)\n super._construct(callback);\n else\n callback();\n this.emit(\"open\", this.fd), this.emit(\"ready\");\n }\n _destroy(err, cb) {\n super._destroy(err, cb);\n try {\n var fd = this.fd;\n if (this[readStreamPathFastPathSymbol] = !1, !fd)\n cb(err);\n else\n this.#fs.close(fd, (er) => {\n cb(er || err);\n }), this.fd = null;\n } catch (e) {\n throw e;\n }\n }\n close(cb) {\n if (typeof cb === \"function\")\n Stream.eos(this, cb);\n this.destroy();\n }\n push(chunk) {\n var bytesRead = chunk\?.length \?\? 0;\n if (bytesRead > 0) {\n this.bytesRead += bytesRead;\n var currPos = this.pos;\n if (currPos !== void 0) {\n if (this.bytesRead < currPos)\n return !0;\n if (currPos === this.start) {\n var n = this.bytesRead - currPos;\n chunk = chunk.slice(-n);\n var [_, ...rest] = arguments;\n if (this.pos = this.bytesRead, this.end !== void 0 && this.bytesRead > this.end)\n chunk = chunk.slice(0, this.end - this.start + 1);\n return super.push(chunk, ...rest);\n }\n var end = this.end;\n if (end !== void 0 && this.bytesRead > end) {\n chunk = chunk.slice(0, end - currPos + 1);\n var [_, ...rest] = arguments;\n return this.pos = this.bytesRead, super.push(chunk, ...rest);\n }\n this.pos = this.bytesRead;\n }\n }\n return super.push(...arguments);\n }\n #internalRead(n) {\n var { pos, end, bytesRead, fd, encoding } = this;\n if (n = pos !== void 0 \? Math.min(end - pos + 1, n) : Math.min(end - bytesRead + 1, n), n <= 0) {\n this.push(null);\n return;\n }\n if (this.#fileSize === -1 && bytesRead === 0 && pos === void 0) {\n var stat3 = fstatSync(fd);\n if (this.#fileSize = stat3.size, this.#fileSize > 0 && n > this.#fileSize)\n n = this.#fileSize + 1;\n }\n this[kIoDone] = !1;\n var res = super._read(n);\n if (@isPromise(res)) {\n var then = res\?.then;\n if (then && @isCallable(then))\n res.then(() => {\n if (this[kIoDone] = !0, this.destroyed)\n this.emit(kIoDone);\n }, (er) => {\n this[kIoDone] = !0, this.#errorOrDestroy(er);\n });\n } else if (this[kIoDone] = !0, this.destroyed)\n this.emit(kIoDone), this.#errorOrDestroy(new Error(\"ERR_STREAM_PREMATURE_CLOSE\"));\n }\n #errorOrDestroy(err, sync = null) {\n var {\n _readableState: r = { destroyed: !1, autoDestroy: !1 },\n _writableState: w = { destroyed: !1, autoDestroy: !1 }\n } = this;\n if (w\?.destroyed || r\?.destroyed)\n return this;\n if (r\?.autoDestroy || w\?.autoDestroy)\n this.destroy(err);\n else if (err)\n this.emit(\"error\", err);\n }\n pause() {\n return this[readStreamPathFastPathSymbol] = !1, super.pause();\n }\n resume() {\n return this[readStreamPathFastPathSymbol] = !1, super.resume();\n }\n unshift(...args) {\n return this[readStreamPathFastPathSymbol] = !1, super.unshift(...args);\n }\n pipe(dest, pipeOpts) {\n if (this[readStreamPathFastPathSymbol] && (pipeOpts\?.end \?\? !0) && this._readableState\?.pipes\?.length === 0) {\n if ((writeStreamPathFastPathSymbol in dest) && dest[writeStreamPathFastPathSymbol]) {\n if (dest[writeStreamPathFastPathCallSymbol](this, pipeOpts))\n return this;\n }\n }\n return this[readStreamPathFastPathSymbol] = !1, super.pipe(dest, pipeOpts);\n }\n});\nvar defaultWriteStreamOptions = {\n fd: null,\n start: void 0,\n pos: void 0,\n encoding: void 0,\n flags: \"w\",\n mode: 438,\n fs: {\n write,\n close,\n open,\n openSync\n }\n}, WriteStreamClass;\nWriteStream = function(InternalWriteStream) {\n WriteStreamClass = InternalWriteStream, Object.defineProperty(WriteStreamClass.prototype, Symbol.toStringTag, {\n value: \"WritesStream\",\n enumerable: !1\n });\n function WriteStream3(path, options) {\n return new InternalWriteStream(path, options);\n }\n return WriteStream3.prototype = InternalWriteStream.prototype, Object.defineProperty(WriteStream3, Symbol.hasInstance, {\n value(instance) {\n return instance instanceof InternalWriteStream;\n }\n });\n}(class WriteStream2 extends Stream.NativeWritable {\n constructor(path, options = defaultWriteStreamOptions) {\n if (!options)\n @throwTypeError(\"Expected options to be an object\");\n var {\n fs: fs2 = defaultWriteStreamOptions.fs,\n start = defaultWriteStreamOptions.start,\n flags = defaultWriteStreamOptions.flags,\n mode = defaultWriteStreamOptions.mode,\n autoClose = !0,\n emitClose = !1,\n autoDestroy = autoClose,\n encoding = defaultWriteStreamOptions.encoding,\n fd = defaultWriteStreamOptions.fd,\n pos = defaultWriteStreamOptions.pos\n } = options, tempThis = {};\n if (fd != null) {\n if (typeof fd !== \"number\")\n throw new Error(\"Expected options.fd to be a number\");\n tempThis.fd = fd, tempThis[writeStreamPathFastPathSymbol] = !1;\n } else if (typeof path === \"string\") {\n if (path.length === 0)\n @throwTypeError(\"Expected a non-empty path\");\n if (path.startsWith(\"file:\"))\n path = Bun.fileURLToPath(path);\n tempThis.path = path, tempThis.fd = null, tempThis[writeStreamPathFastPathSymbol] = autoClose && (start === void 0 || start === 0) && fs2.write === defaultWriteStreamOptions.fs.write && fs2.close === defaultWriteStreamOptions.fs.close;\n }\n if (tempThis.fd == null)\n tempThis.fd = fs2.openSync(path, flags, mode);\n super(tempThis.fd, {\n ...options,\n decodeStrings: !1,\n autoDestroy,\n emitClose,\n fd: tempThis\n });\n if (Object.assign(this, tempThis), typeof fs2\?.write !== \"function\")\n @throwTypeError(\"Expected fs.write to be a function\");\n if (typeof fs2\?.close !== \"function\")\n @throwTypeError(\"Expected fs.close to be a function\");\n if (typeof fs2\?.open !== \"function\")\n @throwTypeError(\"Expected fs.open to be a function\");\n if (typeof path === \"object\" && path) {\n if (path instanceof URL)\n path = Bun.fileURLToPath(path);\n }\n if (typeof path !== \"string\" && typeof fd !== \"number\")\n @throwTypeError(\"Expected a path or file descriptor\");\n if (this.start = start, this.#fs = fs2, this.flags = flags, this.mode = mode, this.start !== void 0)\n this.pos = this.start;\n if (encoding !== defaultWriteStreamOptions.encoding) {\n if (this.setDefaultEncoding(encoding), encoding !== \"buffer\" && encoding !== \"utf8\" && encoding !== \"utf-8\" && encoding !== \"binary\")\n this[writeStreamPathFastPathSymbol] = !1;\n }\n }\n get autoClose() {\n return this._writableState.autoDestroy;\n }\n set autoClose(val) {\n this._writableState.autoDestroy = val;\n }\n destroySoon = this.end;\n open() {\n }\n path;\n fd;\n flags;\n mode;\n #fs;\n bytesWritten = 0;\n pos;\n [writeStreamPathFastPathSymbol];\n [writeStreamSymbol] = !0;\n start;\n [writeStreamPathFastPathCallSymbol](readStream, pipeOpts) {\n if (!this[writeStreamPathFastPathSymbol])\n return !1;\n if (this.fd !== null)\n return this[writeStreamPathFastPathSymbol] = !1, !1;\n return this[kIoDone] = !1, readStream[kIoDone] = !1, Bun.write(this[writeStreamPathFastPathSymbol], readStream[readStreamPathOrFdSymbol]).then((bytesWritten) => {\n readStream[kIoDone] = this[kIoDone] = !0, this.bytesWritten += bytesWritten, readStream.bytesRead += bytesWritten, this.end(), readStream.close();\n }, (err) => {\n readStream[kIoDone] = this[kIoDone] = !0, this.#errorOrDestroy(err), readStream.emit(\"error\", err);\n });\n }\n isBunFastPathEnabled() {\n return this[writeStreamPathFastPathSymbol];\n }\n disableBunFastPath() {\n this[writeStreamPathFastPathSymbol] = !1;\n }\n #handleWrite(er, bytes) {\n if (er)\n return this.#errorOrDestroy(er);\n this.bytesWritten += bytes;\n }\n #internalClose(err, cb) {\n this[writeStreamPathFastPathSymbol] = !1;\n var fd = this.fd;\n this.#fs.close(fd, (er) => {\n this.fd = null, cb(err || er);\n });\n }\n _construct(callback) {\n if (typeof this.fd === \"number\") {\n callback();\n return;\n }\n callback(), this.emit(\"open\", this.fd), this.emit(\"ready\");\n }\n _destroy(err, cb) {\n if (this.fd === null)\n return cb(err);\n if (this[kIoDone]) {\n this.once(kIoDone, () => this.#internalClose(err, cb));\n return;\n }\n this.#internalClose(err, cb);\n }\n [kIoDone] = !1;\n close(cb) {\n if (cb) {\n if (this.closed) {\n process.nextTick(cb);\n return;\n }\n this.on(\"close\", cb);\n }\n if (!this.autoClose)\n this.on(\"finish\", this.destroy);\n this.end();\n }\n write(chunk, encoding = this._writableState.defaultEncoding, cb) {\n if (this[writeStreamPathFastPathSymbol] = !1, typeof chunk === \"string\")\n chunk = Buffer.from(chunk, encoding);\n var native = this.pos === void 0;\n const callback = native \? (err, bytes) => {\n if (this[kIoDone] = !1, this.#handleWrite(err, bytes), this.emit(kIoDone), cb)\n !err \? cb() : cb(err);\n } : () => {\n };\n if (this[kIoDone] = !0, this._write)\n return this._write(chunk, encoding, callback);\n else\n return super.write(chunk, encoding, callback, native);\n }\n end(chunk, encoding, cb) {\n var native = this.pos === void 0;\n return super.end(chunk, encoding, cb, native);\n }\n _write = void 0;\n _writev = void 0;\n get pending() {\n return this.fd === null;\n }\n _destroy(err, cb) {\n this.close(err, cb);\n }\n #errorOrDestroy(err) {\n var {\n _readableState: r = { destroyed: !1, autoDestroy: !1 },\n _writableState: w = { destroyed: !1, autoDestroy: !1 }\n } = this;\n if (w\?.destroyed || r\?.destroyed)\n return this;\n if (r\?.autoDestroy || w\?.autoDestroy)\n this.destroy(err);\n else if (err)\n this.emit(\"error\", err);\n }\n});\nObject.defineProperties(fs, {\n createReadStream: {\n value: createReadStream\n },\n createWriteStream: {\n value: createWriteStream\n },\n ReadStream: {\n value: ReadStream\n },\n WriteStream: {\n value: WriteStream\n }\n});\nrealpath.native = realpath;\nrealpathSync.native = realpathSync;\nvar lazy_cpSync = null;\n$ = {\n Dirent,\n FSWatcher,\n ReadStream,\n Stats,\n WriteStream,\n _toUnixTimestamp,\n access,\n accessSync,\n appendFile,\n appendFileSync,\n chmod,\n chmodSync,\n chown,\n chownSync,\n close,\n closeSync,\n constants,\n copyFile,\n copyFileSync,\n cp,\n cpSync,\n createReadStream,\n createWriteStream,\n exists,\n existsSync,\n fchmod,\n fchmodSync,\n fchown,\n fchownSync,\n fstat,\n fstatSync,\n fsync,\n fsyncSync,\n ftruncate,\n ftruncateSync,\n futimes,\n futimesSync,\n lchmod,\n lchmodSync,\n lchown,\n lchownSync,\n link,\n linkSync,\n lstat,\n lstatSync,\n lutimes,\n lutimesSync,\n mkdir,\n mkdirSync,\n mkdtemp,\n mkdtempSync,\n open,\n openSync,\n promises,\n read,\n readFile,\n readFileSync,\n readSync,\n readdir,\n readdirSync,\n readlink,\n readlinkSync,\n readv,\n readvSync,\n realpath,\n realpathSync,\n rename,\n renameSync,\n rm,\n rmSync,\n rmdir,\n rmdirSync,\n stat,\n statSync,\n symlink,\n symlinkSync,\n truncate,\n truncateSync,\n unlink,\n unlinkSync,\n unwatchFile,\n utimes,\n utimesSync,\n watch,\n watchFile,\n write,\n writeFile,\n writeFileSync,\n writeSync,\n writev,\n writevSync,\n [Symbol.for(\"::bunternal::\")]: {\n ReadStreamClass,\n WriteStreamClass\n }\n};\nreturn $})\n"_s; // // @@ -323,7 +323,7 @@ static constexpr ASCIILiteral NodeEventsCode = "(function (){\"use strict\";// s // // -static constexpr ASCIILiteral NodeFSCode = "(function (){\"use strict\";// src/js/out/tmp/node/fs.ts\nvar callbackify = function(fsFunction, args) {\n try {\n const result = fsFunction.apply(fs, args.slice(0, args.length - 1)), callback = args[args.length - 1];\n if (typeof callback === \"function\")\n queueMicrotask(() => callback(null, result));\n } catch (e) {\n const callback = args[args.length - 1];\n if (typeof callback === \"function\")\n queueMicrotask(() => callback(e));\n }\n}, createReadStream = function(path, options) {\n return new ReadStream(path, options);\n}, createWriteStream = function(path, options) {\n return new WriteStream(path, options);\n}, cpSync = function(src, dest, options) {\n if (!options)\n return fs.cpSync(src, dest);\n if (typeof options !== \"object\")\n @throwTypeError(\"options must be an object\");\n if (options.dereference || options.filter || options.preserveTimestamps || options.verbatimSymlinks) {\n if (!lazy_cpSync)\n lazy_cpSync = @getInternalField(@internalModuleRegistry, 3) || @createInternalModuleById(3);\n return lazy_cpSync(src, dest, options);\n }\n return fs.cpSync(src, dest, options.recursive, options.errorOnExist, options.force \?\? !0, options.mode);\n}, cp = function(src, dest, options, callback) {\n if (typeof options === \"function\")\n callback = options, options = void 0;\n promises.cp(src, dest, options).then(() => callback(), callback);\n}, _toUnixTimestamp = function(time, name = \"time\") {\n if (typeof time === \"string\" && +time == time)\n return +time;\n if (NumberIsFinite(time)) {\n if (time < 0)\n return DateNow() / 1000;\n return time;\n }\n if (isDate(time))\n return DatePrototypeGetTime(time) / 1000;\n @throwTypeError(`Expected ${name} to be a number or Date`);\n}, $, ReadStream, WriteStream, EventEmitter = @getInternalField(@internalModuleRegistry, 18) || @createInternalModuleById(18), promises = @getInternalField(@internalModuleRegistry, 20) || @createInternalModuleById(20), Stream = @getInternalField(@internalModuleRegistry, 37) || @createInternalModuleById(37), { isArrayBufferView } = @requireNativeModule(\"node:util/types\"), constants = @processBindingConstants.fs;\nvar fs = Bun.fs();\n\nclass FSWatcher extends EventEmitter {\n #watcher;\n #listener;\n constructor(path, options, listener) {\n super();\n if (typeof options === \"function\")\n listener = options, options = {};\n else if (typeof options === \"string\")\n options = { encoding: options };\n if (typeof listener !== \"function\")\n listener = () => {\n };\n this.#listener = listener;\n try {\n this.#watcher = fs.watch(path, options || {}, this.#onEvent.bind(this));\n } catch (e) {\n if (!e.message\?.startsWith(\"FileNotFound\"))\n throw e;\n const notFound = new Error(`ENOENT: no such file or directory, watch '${path}'`);\n throw notFound.code = \"ENOENT\", notFound.errno = -2, notFound.path = path, notFound.syscall = \"watch\", notFound.filename = path, notFound;\n }\n }\n #onEvent(eventType, filenameOrError) {\n if (eventType === \"error\" || eventType === \"close\")\n this.emit(eventType, filenameOrError);\n else\n this.emit(\"change\", eventType, filenameOrError), this.#listener(eventType, filenameOrError);\n }\n close() {\n this.#watcher\?.close(), this.#watcher = null;\n }\n ref() {\n this.#watcher\?.ref();\n }\n unref() {\n this.#watcher\?.unref();\n }\n}\nvar access = function access2(...args) {\n callbackify(fs.accessSync, args);\n}, appendFile = function appendFile2(...args) {\n callbackify(fs.appendFileSync, args);\n}, close = function close2(...args) {\n callbackify(fs.closeSync, args);\n}, rm = function rm2(...args) {\n callbackify(fs.rmSync, args);\n}, rmdir = function rmdir2(...args) {\n callbackify(fs.rmdirSync, args);\n}, copyFile = function copyFile2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.copyFile(...args).then((result) => callback(null, result), callback);\n}, exists = function exists2(...args) {\n callbackify(fs.existsSync, args);\n}, chown = function chown2(...args) {\n callbackify(fs.chownSync, args);\n}, chmod = function chmod2(...args) {\n callbackify(fs.chmodSync, args);\n}, fchmod = function fchmod2(...args) {\n callbackify(fs.fchmodSync, args);\n}, fchown = function fchown2(...args) {\n callbackify(fs.fchownSync, args);\n}, fstat = function fstat2(...args) {\n callbackify(fs.fstatSync, args);\n}, fsync = function fsync2(...args) {\n callbackify(fs.fsyncSync, args);\n}, ftruncate = function ftruncate2(...args) {\n callbackify(fs.ftruncateSync, args);\n}, futimes = function futimes2(...args) {\n callbackify(fs.futimesSync, args);\n}, lchmod = function lchmod2(...args) {\n callbackify(fs.lchmodSync, args);\n}, lchown = function lchown2(...args) {\n callbackify(fs.lchownSync, args);\n}, link = function link2(...args) {\n callbackify(fs.linkSync, args);\n}, mkdir = function mkdir2(...args) {\n callbackify(fs.mkdirSync, args);\n}, mkdtemp = function mkdtemp2(...args) {\n callbackify(fs.mkdtempSync, args);\n}, open = function open2(...args) {\n callbackify(fs.openSync, args);\n}, read = function read2(fd, buffer, offsetOrOptions, length, position, callback) {\n let offset = offsetOrOptions, params = null;\n if (arguments.length <= 4) {\n if (arguments.length === 4)\n callback = length, params = offsetOrOptions;\n else if (arguments.length === 3) {\n if (!isArrayBufferView(buffer))\n params = buffer, { buffer = Buffer.alloc(16384) } = params \?\? {};\n callback = offsetOrOptions;\n } else\n callback = buffer, buffer = Buffer.alloc(16384);\n ({ offset = 0, length = buffer\?.byteLength - offset, position = null } = params \?\? {});\n }\n queueMicrotask(() => {\n try {\n var bytesRead = fs.readSync(fd, buffer, offset, length, position);\n } catch (e) {\n callback(e);\n }\n callback(null, bytesRead, buffer);\n });\n}, write = function write2(...args) {\n callbackify(fs.writeSync, args);\n}, readdir = function readdir2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.readdir(...args).then((result) => callback(null, result), callback);\n}, readFile = function readFile2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.readFile(...args).then((result) => callback(null, result), callback);\n}, writeFile = function writeFile2(...args) {\n callbackify(fs.writeFileSync, args);\n}, readlink = function readlink2(...args) {\n callbackify(fs.readlinkSync, args);\n}, realpath = function realpath2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.realpath(...args).then((result) => callback(null, result), callback);\n}, rename = function rename2(...args) {\n callbackify(fs.renameSync, args);\n}, lstat = function lstat2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.lstat(...args).then((result) => callback(null, result), callback);\n}, stat = function stat2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.stat(...args).then((result) => callback(null, result), callback);\n}, symlink = function symlink2(...args) {\n callbackify(fs.symlinkSync, args);\n}, truncate = function truncate2(...args) {\n callbackify(fs.truncateSync, args);\n}, unlink = function unlink2(...args) {\n callbackify(fs.unlinkSync, args);\n}, utimes = function utimes2(...args) {\n callbackify(fs.utimesSync, args);\n}, lutimes = function lutimes2(...args) {\n callbackify(fs.lutimesSync, args);\n}, accessSync = fs.accessSync.bind(fs), appendFileSync = fs.appendFileSync.bind(fs), closeSync = fs.closeSync.bind(fs), copyFileSync = fs.copyFileSync.bind(fs), existsSync = fs.existsSync.bind(fs), chownSync = fs.chownSync.bind(fs), chmodSync = fs.chmodSync.bind(fs), fchmodSync = fs.fchmodSync.bind(fs), fchownSync = fs.fchownSync.bind(fs), fstatSync = fs.fstatSync.bind(fs), fsyncSync = fs.fsyncSync.bind(fs), ftruncateSync = fs.ftruncateSync.bind(fs), futimesSync = fs.futimesSync.bind(fs), lchmodSync = fs.lchmodSync.bind(fs), lchownSync = fs.lchownSync.bind(fs), linkSync = fs.linkSync.bind(fs), lstatSync = fs.lstatSync.bind(fs), mkdirSync = fs.mkdirSync.bind(fs), mkdtempSync = fs.mkdtempSync.bind(fs), openSync = fs.openSync.bind(fs), readSync = fs.readSync.bind(fs), writeSync = fs.writeSync.bind(fs), readdirSync = fs.readdirSync.bind(fs), readFileSync = fs.readFileSync.bind(fs), writeFileSync = fs.writeFileSync.bind(fs), readlinkSync = fs.readlinkSync.bind(fs), realpathSync = fs.realpathSync.bind(fs), renameSync = fs.renameSync.bind(fs), statSync = fs.statSync.bind(fs), symlinkSync = fs.symlinkSync.bind(fs), truncateSync = fs.truncateSync.bind(fs), unlinkSync = fs.unlinkSync.bind(fs), utimesSync = fs.utimesSync.bind(fs), lutimesSync = fs.lutimesSync.bind(fs), rmSync = fs.rmSync.bind(fs), rmdirSync = fs.rmdirSync.bind(fs), writev = (fd, buffers, position, callback) => {\n if (typeof position === \"function\")\n callback = position, position = null;\n queueMicrotask(() => {\n try {\n var written = fs.writevSync(fd, buffers, position);\n } catch (e) {\n callback(e);\n }\n callback(null, written, buffers);\n });\n}, writevSync = fs.writevSync.bind(fs), readv = (fd, buffers, position, callback) => {\n if (typeof position === \"function\")\n callback = position, position = null;\n queueMicrotask(() => {\n try {\n var written = fs.readvSync(fd, buffers, position);\n } catch (e) {\n callback(e);\n }\n callback(null, written, buffers);\n });\n}, readvSync = fs.readvSync.bind(fs), Dirent = fs.Dirent, Stats = fs.Stats, watch = function watch2(path, options, listener) {\n return new FSWatcher(path, options, listener);\n}, readStreamPathFastPathSymbol = Symbol.for(\"Bun.Node.readStreamPathFastPath\"), readStreamSymbol = Symbol.for(\"Bun.NodeReadStream\"), readStreamPathOrFdSymbol = Symbol.for(\"Bun.NodeReadStreamPathOrFd\"), writeStreamSymbol = Symbol.for(\"Bun.NodeWriteStream\"), writeStreamPathFastPathSymbol = Symbol.for(\"Bun.NodeWriteStreamFastPath\"), writeStreamPathFastPathCallSymbol = Symbol.for(\"Bun.NodeWriteStreamFastPathCall\"), kIoDone = Symbol.for(\"kIoDone\"), defaultReadStreamOptions = {\n file: void 0,\n fd: null,\n flags: \"r\",\n encoding: void 0,\n mode: 438,\n autoClose: !0,\n emitClose: !0,\n start: 0,\n end: Infinity,\n highWaterMark: 65536,\n fs: {\n read,\n open: (path, flags, mode, cb) => {\n var fd;\n try {\n fd = openSync(path, flags, mode);\n } catch (e) {\n cb(e);\n return;\n }\n cb(null, fd);\n },\n openSync,\n close\n },\n autoDestroy: !0\n}, ReadStreamClass;\nReadStream = function(InternalReadStream) {\n ReadStreamClass = InternalReadStream, Object.defineProperty(ReadStreamClass.prototype, Symbol.toStringTag, {\n value: \"ReadStream\",\n enumerable: !1\n });\n function ReadStream3(path, options) {\n return new InternalReadStream(path, options);\n }\n return ReadStream3.prototype = InternalReadStream.prototype, Object.defineProperty(ReadStream3, Symbol.hasInstance, {\n value(instance) {\n return instance instanceof InternalReadStream;\n }\n });\n}(class ReadStream2 extends Stream._getNativeReadableStreamPrototype(2, Stream.Readable) {\n constructor(pathOrFd, options = defaultReadStreamOptions) {\n if (typeof options !== \"object\" || !options)\n @throwTypeError(\"Expected options to be an object\");\n var {\n flags = defaultReadStreamOptions.flags,\n encoding = defaultReadStreamOptions.encoding,\n mode = defaultReadStreamOptions.mode,\n autoClose = defaultReadStreamOptions.autoClose,\n emitClose = defaultReadStreamOptions.emitClose,\n start = defaultReadStreamOptions.start,\n end = defaultReadStreamOptions.end,\n autoDestroy = defaultReadStreamOptions.autoClose,\n fs: fs2 = defaultReadStreamOptions.fs,\n highWaterMark = defaultReadStreamOptions.highWaterMark,\n fd = defaultReadStreamOptions.fd\n } = options;\n if (pathOrFd\?.constructor\?.name === \"URL\")\n pathOrFd = Bun.fileURLToPath(pathOrFd);\n var tempThis = {};\n if (fd != null) {\n if (typeof fd !== \"number\")\n @throwTypeError(\"Expected options.fd to be a number\");\n tempThis.fd = tempThis[readStreamPathOrFdSymbol] = fd, tempThis.autoClose = !1;\n } else if (typeof pathOrFd === \"string\") {\n if (pathOrFd.startsWith(\"file://\"))\n pathOrFd = Bun.fileURLToPath(pathOrFd);\n if (pathOrFd.length === 0)\n @throwTypeError(\"Expected path to be a non-empty string\");\n tempThis.path = tempThis.file = tempThis[readStreamPathOrFdSymbol] = pathOrFd;\n } else if (typeof pathOrFd === \"number\") {\n if (pathOrFd |= 0, pathOrFd < 0)\n @throwTypeError(\"Expected fd to be a positive integer\");\n tempThis.fd = tempThis[readStreamPathOrFdSymbol] = pathOrFd, tempThis.autoClose = !1;\n } else\n @throwTypeError(\"Expected a path or file descriptor\");\n if (tempThis.fd === void 0)\n tempThis.fd = fs2.openSync(pathOrFd, flags, mode);\n var fileRef = Bun.file(tempThis.fd), stream = fileRef.stream(), native = @direct(stream);\n if (!native)\n throw new Error(\"no native readable stream\");\n var { stream: ptr } = native;\n super(ptr, {\n ...options,\n encoding,\n autoDestroy,\n autoClose,\n emitClose,\n highWaterMark\n });\n if (Object.assign(this, tempThis), this.#fileRef = fileRef, this.end = end, this._read = this.#internalRead, this.start = start, this.flags = flags, this.mode = mode, this.emitClose = emitClose, this[readStreamPathFastPathSymbol] = start === 0 && end === Infinity && autoClose && fs2 === defaultReadStreamOptions.fs && (encoding === \"buffer\" || encoding === \"binary\" || encoding == null || encoding === \"utf-8\" || encoding === \"utf8\"), this._readableState.autoClose = autoDestroy = autoClose, this._readableState.highWaterMark = highWaterMark, start !== void 0)\n this.pos = start;\n }\n #fileRef;\n #fs;\n file;\n path;\n fd = null;\n flags;\n mode;\n start;\n end;\n pos;\n bytesRead = 0;\n #fileSize = -1;\n _read;\n [readStreamSymbol] = !0;\n [readStreamPathOrFdSymbol];\n [readStreamPathFastPathSymbol];\n _construct(callback) {\n if (super._construct)\n super._construct(callback);\n else\n callback();\n this.emit(\"open\", this.fd), this.emit(\"ready\");\n }\n _destroy(err, cb) {\n super._destroy(err, cb);\n try {\n var fd = this.fd;\n if (this[readStreamPathFastPathSymbol] = !1, !fd)\n cb(err);\n else\n this.#fs.close(fd, (er) => {\n cb(er || err);\n }), this.fd = null;\n } catch (e) {\n throw e;\n }\n }\n close(cb) {\n if (typeof cb === \"function\")\n Stream.eos(this, cb);\n this.destroy();\n }\n push(chunk) {\n var bytesRead = chunk\?.length \?\? 0;\n if (bytesRead > 0) {\n this.bytesRead += bytesRead;\n var currPos = this.pos;\n if (currPos !== void 0) {\n if (this.bytesRead < currPos)\n return !0;\n if (currPos === this.start) {\n var n = this.bytesRead - currPos;\n chunk = chunk.slice(-n);\n var [_, ...rest] = arguments;\n if (this.pos = this.bytesRead, this.end !== void 0 && this.bytesRead > this.end)\n chunk = chunk.slice(0, this.end - this.start + 1);\n return super.push(chunk, ...rest);\n }\n var end = this.end;\n if (end !== void 0 && this.bytesRead > end) {\n chunk = chunk.slice(0, end - currPos + 1);\n var [_, ...rest] = arguments;\n return this.pos = this.bytesRead, super.push(chunk, ...rest);\n }\n this.pos = this.bytesRead;\n }\n }\n return super.push(...arguments);\n }\n #internalRead(n) {\n var { pos, end, bytesRead, fd, encoding } = this;\n if (n = pos !== void 0 \? Math.min(end - pos + 1, n) : Math.min(end - bytesRead + 1, n), n <= 0) {\n this.push(null);\n return;\n }\n if (this.#fileSize === -1 && bytesRead === 0 && pos === void 0) {\n var stat3 = fstatSync(fd);\n if (this.#fileSize = stat3.size, this.#fileSize > 0 && n > this.#fileSize)\n n = this.#fileSize + 1;\n }\n this[kIoDone] = !1;\n var res = super._read(n);\n if (@isPromise(res)) {\n var then = res\?.then;\n if (then && @isCallable(then))\n res.then(() => {\n if (this[kIoDone] = !0, this.destroyed)\n this.emit(kIoDone);\n }, (er) => {\n this[kIoDone] = !0, this.#errorOrDestroy(er);\n });\n } else if (this[kIoDone] = !0, this.destroyed)\n this.emit(kIoDone), this.#errorOrDestroy(new Error(\"ERR_STREAM_PREMATURE_CLOSE\"));\n }\n #errorOrDestroy(err, sync = null) {\n var {\n _readableState: r = { destroyed: !1, autoDestroy: !1 },\n _writableState: w = { destroyed: !1, autoDestroy: !1 }\n } = this;\n if (w\?.destroyed || r\?.destroyed)\n return this;\n if (r\?.autoDestroy || w\?.autoDestroy)\n this.destroy(err);\n else if (err)\n this.emit(\"error\", err);\n }\n pause() {\n return this[readStreamPathFastPathSymbol] = !1, super.pause();\n }\n resume() {\n return this[readStreamPathFastPathSymbol] = !1, super.resume();\n }\n unshift(...args) {\n return this[readStreamPathFastPathSymbol] = !1, super.unshift(...args);\n }\n pipe(dest, pipeOpts) {\n if (this[readStreamPathFastPathSymbol] && (pipeOpts\?.end \?\? !0) && this._readableState\?.pipes\?.length === 0) {\n if ((writeStreamPathFastPathSymbol in dest) && dest[writeStreamPathFastPathSymbol]) {\n if (dest[writeStreamPathFastPathCallSymbol](this, pipeOpts))\n return this;\n }\n }\n return this[readStreamPathFastPathSymbol] = !1, super.pipe(dest, pipeOpts);\n }\n});\nvar defaultWriteStreamOptions = {\n fd: null,\n start: void 0,\n pos: void 0,\n encoding: void 0,\n flags: \"w\",\n mode: 438,\n fs: {\n write,\n close,\n open,\n openSync\n }\n}, WriteStreamClass;\nWriteStream = function(InternalWriteStream) {\n WriteStreamClass = InternalWriteStream, Object.defineProperty(WriteStreamClass.prototype, Symbol.toStringTag, {\n value: \"WritesStream\",\n enumerable: !1\n });\n function WriteStream3(path, options) {\n return new InternalWriteStream(path, options);\n }\n return WriteStream3.prototype = InternalWriteStream.prototype, Object.defineProperty(WriteStream3, Symbol.hasInstance, {\n value(instance) {\n return instance instanceof InternalWriteStream;\n }\n });\n}(class WriteStream2 extends Stream.NativeWritable {\n constructor(path, options = defaultWriteStreamOptions) {\n if (!options)\n @throwTypeError(\"Expected options to be an object\");\n var {\n fs: fs2 = defaultWriteStreamOptions.fs,\n start = defaultWriteStreamOptions.start,\n flags = defaultWriteStreamOptions.flags,\n mode = defaultWriteStreamOptions.mode,\n autoClose = !0,\n emitClose = !1,\n autoDestroy = autoClose,\n encoding = defaultWriteStreamOptions.encoding,\n fd = defaultWriteStreamOptions.fd,\n pos = defaultWriteStreamOptions.pos\n } = options, tempThis = {};\n if (fd != null) {\n if (typeof fd !== \"number\")\n throw new Error(\"Expected options.fd to be a number\");\n tempThis.fd = fd, tempThis[writeStreamPathFastPathSymbol] = !1;\n } else if (typeof path === \"string\") {\n if (path.length === 0)\n @throwTypeError(\"Expected a non-empty path\");\n if (path.startsWith(\"file:\"))\n path = Bun.fileURLToPath(path);\n tempThis.path = path, tempThis.fd = null, tempThis[writeStreamPathFastPathSymbol] = autoClose && (start === void 0 || start === 0) && fs2.write === defaultWriteStreamOptions.fs.write && fs2.close === defaultWriteStreamOptions.fs.close;\n }\n if (tempThis.fd == null)\n tempThis.fd = fs2.openSync(path, flags, mode);\n super(tempThis.fd, {\n ...options,\n decodeStrings: !1,\n autoDestroy,\n emitClose,\n fd: tempThis\n });\n if (Object.assign(this, tempThis), typeof fs2\?.write !== \"function\")\n @throwTypeError(\"Expected fs.write to be a function\");\n if (typeof fs2\?.close !== \"function\")\n @throwTypeError(\"Expected fs.close to be a function\");\n if (typeof fs2\?.open !== \"function\")\n @throwTypeError(\"Expected fs.open to be a function\");\n if (typeof path === \"object\" && path) {\n if (path instanceof URL)\n path = Bun.fileURLToPath(path);\n }\n if (typeof path !== \"string\" && typeof fd !== \"number\")\n @throwTypeError(\"Expected a path or file descriptor\");\n if (this.start = start, this.#fs = fs2, this.flags = flags, this.mode = mode, this.start !== void 0)\n this.pos = this.start;\n if (encoding !== defaultWriteStreamOptions.encoding) {\n if (this.setDefaultEncoding(encoding), encoding !== \"buffer\" && encoding !== \"utf8\" && encoding !== \"utf-8\" && encoding !== \"binary\")\n this[writeStreamPathFastPathSymbol] = !1;\n }\n }\n get autoClose() {\n return this._writableState.autoDestroy;\n }\n set autoClose(val) {\n this._writableState.autoDestroy = val;\n }\n destroySoon = this.end;\n open() {\n }\n path;\n fd;\n flags;\n mode;\n #fs;\n bytesWritten = 0;\n pos;\n [writeStreamPathFastPathSymbol];\n [writeStreamSymbol] = !0;\n start;\n [writeStreamPathFastPathCallSymbol](readStream, pipeOpts) {\n if (!this[writeStreamPathFastPathSymbol])\n return !1;\n if (this.fd !== null)\n return this[writeStreamPathFastPathSymbol] = !1, !1;\n return this[kIoDone] = !1, readStream[kIoDone] = !1, Bun.write(this[writeStreamPathFastPathSymbol], readStream[readStreamPathOrFdSymbol]).then((bytesWritten) => {\n readStream[kIoDone] = this[kIoDone] = !0, this.bytesWritten += bytesWritten, readStream.bytesRead += bytesWritten, this.end(), readStream.close();\n }, (err) => {\n readStream[kIoDone] = this[kIoDone] = !0, this.#errorOrDestroy(err), readStream.emit(\"error\", err);\n });\n }\n isBunFastPathEnabled() {\n return this[writeStreamPathFastPathSymbol];\n }\n disableBunFastPath() {\n this[writeStreamPathFastPathSymbol] = !1;\n }\n #handleWrite(er, bytes) {\n if (er)\n return this.#errorOrDestroy(er);\n this.bytesWritten += bytes;\n }\n #internalClose(err, cb) {\n this[writeStreamPathFastPathSymbol] = !1;\n var fd = this.fd;\n this.#fs.close(fd, (er) => {\n this.fd = null, cb(err || er);\n });\n }\n _construct(callback) {\n if (typeof this.fd === \"number\") {\n callback();\n return;\n }\n callback(), this.emit(\"open\", this.fd), this.emit(\"ready\");\n }\n _destroy(err, cb) {\n if (this.fd === null)\n return cb(err);\n if (this[kIoDone]) {\n this.once(kIoDone, () => this.#internalClose(err, cb));\n return;\n }\n this.#internalClose(err, cb);\n }\n [kIoDone] = !1;\n close(cb) {\n if (cb) {\n if (this.closed) {\n process.nextTick(cb);\n return;\n }\n this.on(\"close\", cb);\n }\n if (!this.autoClose)\n this.on(\"finish\", this.destroy);\n this.end();\n }\n write(chunk, encoding = this._writableState.defaultEncoding, cb) {\n if (this[writeStreamPathFastPathSymbol] = !1, typeof chunk === \"string\")\n chunk = Buffer.from(chunk, encoding);\n var native = this.pos === void 0;\n const callback = native \? (err, bytes) => {\n if (this[kIoDone] = !1, this.#handleWrite(err, bytes), this.emit(kIoDone), cb)\n !err \? cb() : cb(err);\n } : () => {\n };\n if (this[kIoDone] = !0, this._write)\n return this._write(chunk, encoding, callback);\n else\n return super.write(chunk, encoding, callback, native);\n }\n end(chunk, encoding, cb) {\n var native = this.pos === void 0;\n return super.end(chunk, encoding, cb, native);\n }\n _write = void 0;\n _writev = void 0;\n get pending() {\n return this.fd === null;\n }\n _destroy(err, cb) {\n this.close(err, cb);\n }\n #errorOrDestroy(err) {\n var {\n _readableState: r = { destroyed: !1, autoDestroy: !1 },\n _writableState: w = { destroyed: !1, autoDestroy: !1 }\n } = this;\n if (w\?.destroyed || r\?.destroyed)\n return this;\n if (r\?.autoDestroy || w\?.autoDestroy)\n this.destroy(err);\n else if (err)\n this.emit(\"error\", err);\n }\n});\nObject.defineProperties(fs, {\n createReadStream: {\n value: createReadStream\n },\n createWriteStream: {\n value: createWriteStream\n },\n ReadStream: {\n value: ReadStream\n },\n WriteStream: {\n value: WriteStream\n }\n});\nrealpath.native = realpath;\nrealpathSync.native = realpathSync;\nvar lazy_cpSync = null;\n$ = {\n Dirent,\n FSWatcher,\n ReadStream,\n Stats,\n WriteStream,\n _toUnixTimestamp,\n access,\n accessSync,\n appendFile,\n appendFileSync,\n chmod,\n chmodSync,\n chown,\n chownSync,\n close,\n closeSync,\n constants,\n copyFile,\n copyFileSync,\n cp,\n cpSync,\n createReadStream,\n createWriteStream,\n exists,\n existsSync,\n fchmod,\n fchmodSync,\n fchown,\n fchownSync,\n fstat,\n fstatSync,\n fsync,\n fsyncSync,\n ftruncate,\n ftruncateSync,\n futimes,\n futimesSync,\n lchmod,\n lchmodSync,\n lchown,\n lchownSync,\n link,\n linkSync,\n lstat,\n lstatSync,\n lutimes,\n lutimesSync,\n mkdir,\n mkdirSync,\n mkdtemp,\n mkdtempSync,\n open,\n openSync,\n promises,\n read,\n readFile,\n readFileSync,\n readSync,\n readdir,\n readdirSync,\n readlink,\n readlinkSync,\n readv,\n readvSync,\n realpath,\n realpathSync,\n rename,\n renameSync,\n rm,\n rmSync,\n rmdir,\n rmdirSync,\n stat,\n statSync,\n symlink,\n symlinkSync,\n truncate,\n truncateSync,\n unlink,\n unlinkSync,\n utimes,\n utimesSync,\n watch,\n write,\n writeFile,\n writeFileSync,\n writeSync,\n writev,\n writevSync,\n [Symbol.for(\"::bunternal::\")]: {\n ReadStreamClass,\n WriteStreamClass\n }\n};\nreturn $})\n"_s; +static constexpr ASCIILiteral NodeFSCode = "(function (){\"use strict\";// src/js/out/tmp/node/fs.ts\nvar getValidatedPath = function(p) {\n if (p instanceof URL)\n return Bun.fileURLToPath(p);\n if (typeof p !== \"string\")\n @throwTypeError(\"Path must be a string or URL.\");\n return (_pathModule \?\?= @getInternalField(@internalModuleRegistry, 28) || @createInternalModuleById(28)).resolve(p);\n}, watchFile = function(filename, options, listener) {\n if (filename = getValidatedPath(filename), typeof options === \"function\")\n listener = options, options = {};\n if (typeof listener !== \"function\")\n @throwTypeError(\"listener must be a function\");\n var stat = statWatchers.get(filename);\n if (!stat)\n stat = new StatWatcher(filename, options), statWatchers.set(filename, stat);\n return stat.addListener(\"change\", listener), stat;\n}, unwatchFile = function(filename, listener) {\n filename = getValidatedPath(filename);\n var stat = statWatchers.get(filename);\n if (!stat)\n return;\n if (listener) {\n if (stat.removeListener(\"change\", listener), stat.listenerCount(\"change\") !== 0)\n return;\n } else\n stat.removeAllListeners(\"change\");\n stat.stop(), statWatchers.delete(filename);\n}, callbackify = function(fsFunction, args) {\n try {\n const result = fsFunction.apply(fs, args.slice(0, args.length - 1)), callback = args[args.length - 1];\n if (typeof callback === \"function\")\n queueMicrotask(() => callback(null, result));\n } catch (e) {\n const callback = args[args.length - 1];\n if (typeof callback === \"function\")\n queueMicrotask(() => callback(e));\n }\n}, createReadStream = function(path, options) {\n return new ReadStream(path, options);\n}, createWriteStream = function(path, options) {\n return new WriteStream(path, options);\n}, cpSync = function(src, dest, options) {\n if (!options)\n return fs.cpSync(src, dest);\n if (typeof options !== \"object\")\n @throwTypeError(\"options must be an object\");\n if (options.dereference || options.filter || options.preserveTimestamps || options.verbatimSymlinks) {\n if (!lazy_cpSync)\n lazy_cpSync = @getInternalField(@internalModuleRegistry, 3) || @createInternalModuleById(3);\n return lazy_cpSync(src, dest, options);\n }\n return fs.cpSync(src, dest, options.recursive, options.errorOnExist, options.force \?\? !0, options.mode);\n}, cp = function(src, dest, options, callback) {\n if (typeof options === \"function\")\n callback = options, options = void 0;\n promises.cp(src, dest, options).then(() => callback(), callback);\n}, _toUnixTimestamp = function(time, name = \"time\") {\n if (typeof time === \"string\" && +time == time)\n return +time;\n if (NumberIsFinite(time)) {\n if (time < 0)\n return DateNow() / 1000;\n return time;\n }\n if (isDate(time))\n return DatePrototypeGetTime(time) / 1000;\n @throwTypeError(`Expected ${name} to be a number or Date`);\n}, $, ReadStream, WriteStream, EventEmitter = @getInternalField(@internalModuleRegistry, 18) || @createInternalModuleById(18), promises = @getInternalField(@internalModuleRegistry, 20) || @createInternalModuleById(20), Stream = @getInternalField(@internalModuleRegistry, 37) || @createInternalModuleById(37), { isArrayBufferView } = @requireNativeModule(\"node:util/types\"), constants = @processBindingConstants.fs, fs = Bun.fs();\n\nclass FSWatcher extends EventEmitter {\n #watcher;\n #listener;\n constructor(path, options, listener) {\n super();\n if (typeof options === \"function\")\n listener = options, options = {};\n else if (typeof options === \"string\")\n options = { encoding: options };\n if (typeof listener !== \"function\")\n listener = () => {\n };\n this.#listener = listener;\n try {\n this.#watcher = fs.watch(path, options || {}, this.#onEvent.bind(this));\n } catch (e) {\n if (!e.message\?.startsWith(\"FileNotFound\"))\n throw e;\n const notFound = new Error(`ENOENT: no such file or directory, watch '${path}'`);\n throw notFound.code = \"ENOENT\", notFound.errno = -2, notFound.path = path, notFound.syscall = \"watch\", notFound.filename = path, notFound;\n }\n }\n #onEvent(eventType, filenameOrError) {\n if (eventType === \"error\" || eventType === \"close\")\n this.emit(eventType, filenameOrError);\n else\n this.emit(\"change\", eventType, filenameOrError), this.#listener(eventType, filenameOrError);\n }\n close() {\n this.#watcher\?.close(), this.#watcher = null;\n }\n ref() {\n this.#watcher\?.ref();\n }\n unref() {\n this.#watcher\?.unref();\n }\n start() {\n }\n}\n\nclass StatWatcher extends EventEmitter {\n constructor(path, options) {\n super();\n this._handle = fs.watchFile(path, options, this.#onChange.bind(this));\n }\n #onChange(curr, prev) {\n this.emit(\"change\", curr, prev);\n }\n start() {\n }\n stop() {\n this._handle\?.close(), this._handle = null;\n }\n ref() {\n this._handle\?.ref();\n }\n unref() {\n this._handle\?.unref();\n }\n}\nvar access = function access2(...args) {\n callbackify(fs.accessSync, args);\n}, appendFile = function appendFile2(...args) {\n callbackify(fs.appendFileSync, args);\n}, close = function close2(...args) {\n callbackify(fs.closeSync, args);\n}, rm = function rm2(...args) {\n callbackify(fs.rmSync, args);\n}, rmdir = function rmdir2(...args) {\n callbackify(fs.rmdirSync, args);\n}, copyFile = function copyFile2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.copyFile(...args).then((result) => callback(null, result), callback);\n}, exists = function exists2(...args) {\n callbackify(fs.existsSync, args);\n}, chown = function chown2(...args) {\n callbackify(fs.chownSync, args);\n}, chmod = function chmod2(...args) {\n callbackify(fs.chmodSync, args);\n}, fchmod = function fchmod2(...args) {\n callbackify(fs.fchmodSync, args);\n}, fchown = function fchown2(...args) {\n callbackify(fs.fchownSync, args);\n}, fstat = function fstat2(...args) {\n callbackify(fs.fstatSync, args);\n}, fsync = function fsync2(...args) {\n callbackify(fs.fsyncSync, args);\n}, ftruncate = function ftruncate2(...args) {\n callbackify(fs.ftruncateSync, args);\n}, futimes = function futimes2(...args) {\n callbackify(fs.futimesSync, args);\n}, lchmod = function lchmod2(...args) {\n callbackify(fs.lchmodSync, args);\n}, lchown = function lchown2(...args) {\n callbackify(fs.lchownSync, args);\n}, link = function link2(...args) {\n callbackify(fs.linkSync, args);\n}, mkdir = function mkdir2(...args) {\n callbackify(fs.mkdirSync, args);\n}, mkdtemp = function mkdtemp2(...args) {\n callbackify(fs.mkdtempSync, args);\n}, open = function open2(...args) {\n callbackify(fs.openSync, args);\n}, read = function read2(fd, buffer, offsetOrOptions, length, position, callback) {\n let offset = offsetOrOptions, params = null;\n if (arguments.length <= 4) {\n if (arguments.length === 4)\n callback = length, params = offsetOrOptions;\n else if (arguments.length === 3) {\n if (!isArrayBufferView(buffer))\n params = buffer, { buffer = Buffer.alloc(16384) } = params \?\? {};\n callback = offsetOrOptions;\n } else\n callback = buffer, buffer = Buffer.alloc(16384);\n ({ offset = 0, length = buffer\?.byteLength - offset, position = null } = params \?\? {});\n }\n queueMicrotask(() => {\n try {\n var bytesRead = fs.readSync(fd, buffer, offset, length, position);\n } catch (e) {\n callback(e);\n }\n callback(null, bytesRead, buffer);\n });\n}, write = function write2(...args) {\n callbackify(fs.writeSync, args);\n}, readdir = function readdir2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.readdir(...args).then((result) => callback(null, result), callback);\n}, readFile = function readFile2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.readFile(...args).then((result) => callback(null, result), callback);\n}, writeFile = function writeFile2(...args) {\n callbackify(fs.writeFileSync, args);\n}, readlink = function readlink2(...args) {\n callbackify(fs.readlinkSync, args);\n}, realpath = function realpath2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.realpath(...args).then((result) => callback(null, result), callback);\n}, rename = function rename2(...args) {\n callbackify(fs.renameSync, args);\n}, lstat = function lstat2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.lstat(...args).then((result) => callback(null, result), callback);\n}, stat = function stat2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.stat(...args).then((result) => callback(null, result), callback);\n}, symlink = function symlink2(...args) {\n callbackify(fs.symlinkSync, args);\n}, truncate = function truncate2(...args) {\n callbackify(fs.truncateSync, args);\n}, unlink = function unlink2(...args) {\n callbackify(fs.unlinkSync, args);\n}, utimes = function utimes2(...args) {\n callbackify(fs.utimesSync, args);\n}, lutimes = function lutimes2(...args) {\n callbackify(fs.lutimesSync, args);\n}, accessSync = fs.accessSync.bind(fs), appendFileSync = fs.appendFileSync.bind(fs), closeSync = fs.closeSync.bind(fs), copyFileSync = fs.copyFileSync.bind(fs), existsSync = fs.existsSync.bind(fs), chownSync = fs.chownSync.bind(fs), chmodSync = fs.chmodSync.bind(fs), fchmodSync = fs.fchmodSync.bind(fs), fchownSync = fs.fchownSync.bind(fs), fstatSync = fs.fstatSync.bind(fs), fsyncSync = fs.fsyncSync.bind(fs), ftruncateSync = fs.ftruncateSync.bind(fs), futimesSync = fs.futimesSync.bind(fs), lchmodSync = fs.lchmodSync.bind(fs), lchownSync = fs.lchownSync.bind(fs), linkSync = fs.linkSync.bind(fs), lstatSync = fs.lstatSync.bind(fs), mkdirSync = fs.mkdirSync.bind(fs), mkdtempSync = fs.mkdtempSync.bind(fs), openSync = fs.openSync.bind(fs), readSync = fs.readSync.bind(fs), writeSync = fs.writeSync.bind(fs), readdirSync = fs.readdirSync.bind(fs), readFileSync = fs.readFileSync.bind(fs), writeFileSync = fs.writeFileSync.bind(fs), readlinkSync = fs.readlinkSync.bind(fs), realpathSync = fs.realpathSync.bind(fs), renameSync = fs.renameSync.bind(fs), statSync = fs.statSync.bind(fs), symlinkSync = fs.symlinkSync.bind(fs), truncateSync = fs.truncateSync.bind(fs), unlinkSync = fs.unlinkSync.bind(fs), utimesSync = fs.utimesSync.bind(fs), lutimesSync = fs.lutimesSync.bind(fs), rmSync = fs.rmSync.bind(fs), rmdirSync = fs.rmdirSync.bind(fs), writev = (fd, buffers, position, callback) => {\n if (typeof position === \"function\")\n callback = position, position = null;\n queueMicrotask(() => {\n try {\n var written = fs.writevSync(fd, buffers, position);\n } catch (e) {\n callback(e);\n }\n callback(null, written, buffers);\n });\n}, writevSync = fs.writevSync.bind(fs), readv = (fd, buffers, position, callback) => {\n if (typeof position === \"function\")\n callback = position, position = null;\n queueMicrotask(() => {\n try {\n var written = fs.readvSync(fd, buffers, position);\n } catch (e) {\n callback(e);\n }\n callback(null, written, buffers);\n });\n}, readvSync = fs.readvSync.bind(fs), Dirent = fs.Dirent, Stats = fs.Stats, watch = function watch2(path, options, listener) {\n return new FSWatcher(path, options, listener);\n}, statWatchers = new Map, _pathModule, readStreamPathFastPathSymbol = Symbol.for(\"Bun.Node.readStreamPathFastPath\"), readStreamSymbol = Symbol.for(\"Bun.NodeReadStream\"), readStreamPathOrFdSymbol = Symbol.for(\"Bun.NodeReadStreamPathOrFd\"), writeStreamSymbol = Symbol.for(\"Bun.NodeWriteStream\"), writeStreamPathFastPathSymbol = Symbol.for(\"Bun.NodeWriteStreamFastPath\"), writeStreamPathFastPathCallSymbol = Symbol.for(\"Bun.NodeWriteStreamFastPathCall\"), kIoDone = Symbol.for(\"kIoDone\"), defaultReadStreamOptions = {\n file: void 0,\n fd: null,\n flags: \"r\",\n encoding: void 0,\n mode: 438,\n autoClose: !0,\n emitClose: !0,\n start: 0,\n end: Infinity,\n highWaterMark: 65536,\n fs: {\n read,\n open: (path, flags, mode, cb) => {\n var fd;\n try {\n fd = openSync(path, flags, mode);\n } catch (e) {\n cb(e);\n return;\n }\n cb(null, fd);\n },\n openSync,\n close\n },\n autoDestroy: !0\n}, ReadStreamClass;\nReadStream = function(InternalReadStream) {\n ReadStreamClass = InternalReadStream, Object.defineProperty(ReadStreamClass.prototype, Symbol.toStringTag, {\n value: \"ReadStream\",\n enumerable: !1\n });\n function ReadStream3(path, options) {\n return new InternalReadStream(path, options);\n }\n return ReadStream3.prototype = InternalReadStream.prototype, Object.defineProperty(ReadStream3, Symbol.hasInstance, {\n value(instance) {\n return instance instanceof InternalReadStream;\n }\n });\n}(class ReadStream2 extends Stream._getNativeReadableStreamPrototype(2, Stream.Readable) {\n constructor(pathOrFd, options = defaultReadStreamOptions) {\n if (typeof options !== \"object\" || !options)\n @throwTypeError(\"Expected options to be an object\");\n var {\n flags = defaultReadStreamOptions.flags,\n encoding = defaultReadStreamOptions.encoding,\n mode = defaultReadStreamOptions.mode,\n autoClose = defaultReadStreamOptions.autoClose,\n emitClose = defaultReadStreamOptions.emitClose,\n start = defaultReadStreamOptions.start,\n end = defaultReadStreamOptions.end,\n autoDestroy = defaultReadStreamOptions.autoClose,\n fs: fs2 = defaultReadStreamOptions.fs,\n highWaterMark = defaultReadStreamOptions.highWaterMark,\n fd = defaultReadStreamOptions.fd\n } = options;\n if (pathOrFd\?.constructor\?.name === \"URL\")\n pathOrFd = Bun.fileURLToPath(pathOrFd);\n var tempThis = {};\n if (fd != null) {\n if (typeof fd !== \"number\")\n @throwTypeError(\"Expected options.fd to be a number\");\n tempThis.fd = tempThis[readStreamPathOrFdSymbol] = fd, tempThis.autoClose = !1;\n } else if (typeof pathOrFd === \"string\") {\n if (pathOrFd.startsWith(\"file://\"))\n pathOrFd = Bun.fileURLToPath(pathOrFd);\n if (pathOrFd.length === 0)\n @throwTypeError(\"Expected path to be a non-empty string\");\n tempThis.path = tempThis.file = tempThis[readStreamPathOrFdSymbol] = pathOrFd;\n } else if (typeof pathOrFd === \"number\") {\n if (pathOrFd |= 0, pathOrFd < 0)\n @throwTypeError(\"Expected fd to be a positive integer\");\n tempThis.fd = tempThis[readStreamPathOrFdSymbol] = pathOrFd, tempThis.autoClose = !1;\n } else\n @throwTypeError(\"Expected a path or file descriptor\");\n if (tempThis.fd === void 0)\n tempThis.fd = fs2.openSync(pathOrFd, flags, mode);\n var fileRef = Bun.file(tempThis.fd), stream = fileRef.stream(), native = @direct(stream);\n if (!native)\n throw new Error(\"no native readable stream\");\n var { stream: ptr } = native;\n super(ptr, {\n ...options,\n encoding,\n autoDestroy,\n autoClose,\n emitClose,\n highWaterMark\n });\n if (Object.assign(this, tempThis), this.#fileRef = fileRef, this.end = end, this._read = this.#internalRead, this.start = start, this.flags = flags, this.mode = mode, this.emitClose = emitClose, this[readStreamPathFastPathSymbol] = start === 0 && end === Infinity && autoClose && fs2 === defaultReadStreamOptions.fs && (encoding === \"buffer\" || encoding === \"binary\" || encoding == null || encoding === \"utf-8\" || encoding === \"utf8\"), this._readableState.autoClose = autoDestroy = autoClose, this._readableState.highWaterMark = highWaterMark, start !== void 0)\n this.pos = start;\n }\n #fileRef;\n #fs;\n file;\n path;\n fd = null;\n flags;\n mode;\n start;\n end;\n pos;\n bytesRead = 0;\n #fileSize = -1;\n _read;\n [readStreamSymbol] = !0;\n [readStreamPathOrFdSymbol];\n [readStreamPathFastPathSymbol];\n _construct(callback) {\n if (super._construct)\n super._construct(callback);\n else\n callback();\n this.emit(\"open\", this.fd), this.emit(\"ready\");\n }\n _destroy(err, cb) {\n super._destroy(err, cb);\n try {\n var fd = this.fd;\n if (this[readStreamPathFastPathSymbol] = !1, !fd)\n cb(err);\n else\n this.#fs.close(fd, (er) => {\n cb(er || err);\n }), this.fd = null;\n } catch (e) {\n throw e;\n }\n }\n close(cb) {\n if (typeof cb === \"function\")\n Stream.eos(this, cb);\n this.destroy();\n }\n push(chunk) {\n var bytesRead = chunk\?.length \?\? 0;\n if (bytesRead > 0) {\n this.bytesRead += bytesRead;\n var currPos = this.pos;\n if (currPos !== void 0) {\n if (this.bytesRead < currPos)\n return !0;\n if (currPos === this.start) {\n var n = this.bytesRead - currPos;\n chunk = chunk.slice(-n);\n var [_, ...rest] = arguments;\n if (this.pos = this.bytesRead, this.end !== void 0 && this.bytesRead > this.end)\n chunk = chunk.slice(0, this.end - this.start + 1);\n return super.push(chunk, ...rest);\n }\n var end = this.end;\n if (end !== void 0 && this.bytesRead > end) {\n chunk = chunk.slice(0, end - currPos + 1);\n var [_, ...rest] = arguments;\n return this.pos = this.bytesRead, super.push(chunk, ...rest);\n }\n this.pos = this.bytesRead;\n }\n }\n return super.push(...arguments);\n }\n #internalRead(n) {\n var { pos, end, bytesRead, fd, encoding } = this;\n if (n = pos !== void 0 \? Math.min(end - pos + 1, n) : Math.min(end - bytesRead + 1, n), n <= 0) {\n this.push(null);\n return;\n }\n if (this.#fileSize === -1 && bytesRead === 0 && pos === void 0) {\n var stat3 = fstatSync(fd);\n if (this.#fileSize = stat3.size, this.#fileSize > 0 && n > this.#fileSize)\n n = this.#fileSize + 1;\n }\n this[kIoDone] = !1;\n var res = super._read(n);\n if (@isPromise(res)) {\n var then = res\?.then;\n if (then && @isCallable(then))\n res.then(() => {\n if (this[kIoDone] = !0, this.destroyed)\n this.emit(kIoDone);\n }, (er) => {\n this[kIoDone] = !0, this.#errorOrDestroy(er);\n });\n } else if (this[kIoDone] = !0, this.destroyed)\n this.emit(kIoDone), this.#errorOrDestroy(new Error(\"ERR_STREAM_PREMATURE_CLOSE\"));\n }\n #errorOrDestroy(err, sync = null) {\n var {\n _readableState: r = { destroyed: !1, autoDestroy: !1 },\n _writableState: w = { destroyed: !1, autoDestroy: !1 }\n } = this;\n if (w\?.destroyed || r\?.destroyed)\n return this;\n if (r\?.autoDestroy || w\?.autoDestroy)\n this.destroy(err);\n else if (err)\n this.emit(\"error\", err);\n }\n pause() {\n return this[readStreamPathFastPathSymbol] = !1, super.pause();\n }\n resume() {\n return this[readStreamPathFastPathSymbol] = !1, super.resume();\n }\n unshift(...args) {\n return this[readStreamPathFastPathSymbol] = !1, super.unshift(...args);\n }\n pipe(dest, pipeOpts) {\n if (this[readStreamPathFastPathSymbol] && (pipeOpts\?.end \?\? !0) && this._readableState\?.pipes\?.length === 0) {\n if ((writeStreamPathFastPathSymbol in dest) && dest[writeStreamPathFastPathSymbol]) {\n if (dest[writeStreamPathFastPathCallSymbol](this, pipeOpts))\n return this;\n }\n }\n return this[readStreamPathFastPathSymbol] = !1, super.pipe(dest, pipeOpts);\n }\n});\nvar defaultWriteStreamOptions = {\n fd: null,\n start: void 0,\n pos: void 0,\n encoding: void 0,\n flags: \"w\",\n mode: 438,\n fs: {\n write,\n close,\n open,\n openSync\n }\n}, WriteStreamClass;\nWriteStream = function(InternalWriteStream) {\n WriteStreamClass = InternalWriteStream, Object.defineProperty(WriteStreamClass.prototype, Symbol.toStringTag, {\n value: \"WritesStream\",\n enumerable: !1\n });\n function WriteStream3(path, options) {\n return new InternalWriteStream(path, options);\n }\n return WriteStream3.prototype = InternalWriteStream.prototype, Object.defineProperty(WriteStream3, Symbol.hasInstance, {\n value(instance) {\n return instance instanceof InternalWriteStream;\n }\n });\n}(class WriteStream2 extends Stream.NativeWritable {\n constructor(path, options = defaultWriteStreamOptions) {\n if (!options)\n @throwTypeError(\"Expected options to be an object\");\n var {\n fs: fs2 = defaultWriteStreamOptions.fs,\n start = defaultWriteStreamOptions.start,\n flags = defaultWriteStreamOptions.flags,\n mode = defaultWriteStreamOptions.mode,\n autoClose = !0,\n emitClose = !1,\n autoDestroy = autoClose,\n encoding = defaultWriteStreamOptions.encoding,\n fd = defaultWriteStreamOptions.fd,\n pos = defaultWriteStreamOptions.pos\n } = options, tempThis = {};\n if (fd != null) {\n if (typeof fd !== \"number\")\n throw new Error(\"Expected options.fd to be a number\");\n tempThis.fd = fd, tempThis[writeStreamPathFastPathSymbol] = !1;\n } else if (typeof path === \"string\") {\n if (path.length === 0)\n @throwTypeError(\"Expected a non-empty path\");\n if (path.startsWith(\"file:\"))\n path = Bun.fileURLToPath(path);\n tempThis.path = path, tempThis.fd = null, tempThis[writeStreamPathFastPathSymbol] = autoClose && (start === void 0 || start === 0) && fs2.write === defaultWriteStreamOptions.fs.write && fs2.close === defaultWriteStreamOptions.fs.close;\n }\n if (tempThis.fd == null)\n tempThis.fd = fs2.openSync(path, flags, mode);\n super(tempThis.fd, {\n ...options,\n decodeStrings: !1,\n autoDestroy,\n emitClose,\n fd: tempThis\n });\n if (Object.assign(this, tempThis), typeof fs2\?.write !== \"function\")\n @throwTypeError(\"Expected fs.write to be a function\");\n if (typeof fs2\?.close !== \"function\")\n @throwTypeError(\"Expected fs.close to be a function\");\n if (typeof fs2\?.open !== \"function\")\n @throwTypeError(\"Expected fs.open to be a function\");\n if (typeof path === \"object\" && path) {\n if (path instanceof URL)\n path = Bun.fileURLToPath(path);\n }\n if (typeof path !== \"string\" && typeof fd !== \"number\")\n @throwTypeError(\"Expected a path or file descriptor\");\n if (this.start = start, this.#fs = fs2, this.flags = flags, this.mode = mode, this.start !== void 0)\n this.pos = this.start;\n if (encoding !== defaultWriteStreamOptions.encoding) {\n if (this.setDefaultEncoding(encoding), encoding !== \"buffer\" && encoding !== \"utf8\" && encoding !== \"utf-8\" && encoding !== \"binary\")\n this[writeStreamPathFastPathSymbol] = !1;\n }\n }\n get autoClose() {\n return this._writableState.autoDestroy;\n }\n set autoClose(val) {\n this._writableState.autoDestroy = val;\n }\n destroySoon = this.end;\n open() {\n }\n path;\n fd;\n flags;\n mode;\n #fs;\n bytesWritten = 0;\n pos;\n [writeStreamPathFastPathSymbol];\n [writeStreamSymbol] = !0;\n start;\n [writeStreamPathFastPathCallSymbol](readStream, pipeOpts) {\n if (!this[writeStreamPathFastPathSymbol])\n return !1;\n if (this.fd !== null)\n return this[writeStreamPathFastPathSymbol] = !1, !1;\n return this[kIoDone] = !1, readStream[kIoDone] = !1, Bun.write(this[writeStreamPathFastPathSymbol], readStream[readStreamPathOrFdSymbol]).then((bytesWritten) => {\n readStream[kIoDone] = this[kIoDone] = !0, this.bytesWritten += bytesWritten, readStream.bytesRead += bytesWritten, this.end(), readStream.close();\n }, (err) => {\n readStream[kIoDone] = this[kIoDone] = !0, this.#errorOrDestroy(err), readStream.emit(\"error\", err);\n });\n }\n isBunFastPathEnabled() {\n return this[writeStreamPathFastPathSymbol];\n }\n disableBunFastPath() {\n this[writeStreamPathFastPathSymbol] = !1;\n }\n #handleWrite(er, bytes) {\n if (er)\n return this.#errorOrDestroy(er);\n this.bytesWritten += bytes;\n }\n #internalClose(err, cb) {\n this[writeStreamPathFastPathSymbol] = !1;\n var fd = this.fd;\n this.#fs.close(fd, (er) => {\n this.fd = null, cb(err || er);\n });\n }\n _construct(callback) {\n if (typeof this.fd === \"number\") {\n callback();\n return;\n }\n callback(), this.emit(\"open\", this.fd), this.emit(\"ready\");\n }\n _destroy(err, cb) {\n if (this.fd === null)\n return cb(err);\n if (this[kIoDone]) {\n this.once(kIoDone, () => this.#internalClose(err, cb));\n return;\n }\n this.#internalClose(err, cb);\n }\n [kIoDone] = !1;\n close(cb) {\n if (cb) {\n if (this.closed) {\n process.nextTick(cb);\n return;\n }\n this.on(\"close\", cb);\n }\n if (!this.autoClose)\n this.on(\"finish\", this.destroy);\n this.end();\n }\n write(chunk, encoding = this._writableState.defaultEncoding, cb) {\n if (this[writeStreamPathFastPathSymbol] = !1, typeof chunk === \"string\")\n chunk = Buffer.from(chunk, encoding);\n var native = this.pos === void 0;\n const callback = native \? (err, bytes) => {\n if (this[kIoDone] = !1, this.#handleWrite(err, bytes), this.emit(kIoDone), cb)\n !err \? cb() : cb(err);\n } : () => {\n };\n if (this[kIoDone] = !0, this._write)\n return this._write(chunk, encoding, callback);\n else\n return super.write(chunk, encoding, callback, native);\n }\n end(chunk, encoding, cb) {\n var native = this.pos === void 0;\n return super.end(chunk, encoding, cb, native);\n }\n _write = void 0;\n _writev = void 0;\n get pending() {\n return this.fd === null;\n }\n _destroy(err, cb) {\n this.close(err, cb);\n }\n #errorOrDestroy(err) {\n var {\n _readableState: r = { destroyed: !1, autoDestroy: !1 },\n _writableState: w = { destroyed: !1, autoDestroy: !1 }\n } = this;\n if (w\?.destroyed || r\?.destroyed)\n return this;\n if (r\?.autoDestroy || w\?.autoDestroy)\n this.destroy(err);\n else if (err)\n this.emit(\"error\", err);\n }\n});\nObject.defineProperties(fs, {\n createReadStream: {\n value: createReadStream\n },\n createWriteStream: {\n value: createWriteStream\n },\n ReadStream: {\n value: ReadStream\n },\n WriteStream: {\n value: WriteStream\n }\n});\nrealpath.native = realpath;\nrealpathSync.native = realpathSync;\nvar lazy_cpSync = null;\n$ = {\n Dirent,\n FSWatcher,\n ReadStream,\n Stats,\n WriteStream,\n _toUnixTimestamp,\n access,\n accessSync,\n appendFile,\n appendFileSync,\n chmod,\n chmodSync,\n chown,\n chownSync,\n close,\n closeSync,\n constants,\n copyFile,\n copyFileSync,\n cp,\n cpSync,\n createReadStream,\n createWriteStream,\n exists,\n existsSync,\n fchmod,\n fchmodSync,\n fchown,\n fchownSync,\n fstat,\n fstatSync,\n fsync,\n fsyncSync,\n ftruncate,\n ftruncateSync,\n futimes,\n futimesSync,\n lchmod,\n lchmodSync,\n lchown,\n lchownSync,\n link,\n linkSync,\n lstat,\n lstatSync,\n lutimes,\n lutimesSync,\n mkdir,\n mkdirSync,\n mkdtemp,\n mkdtempSync,\n open,\n openSync,\n promises,\n read,\n readFile,\n readFileSync,\n readSync,\n readdir,\n readdirSync,\n readlink,\n readlinkSync,\n readv,\n readvSync,\n realpath,\n realpathSync,\n rename,\n renameSync,\n rm,\n rmSync,\n rmdir,\n rmdirSync,\n stat,\n statSync,\n symlink,\n symlinkSync,\n truncate,\n truncateSync,\n unlink,\n unlinkSync,\n unwatchFile,\n utimes,\n utimesSync,\n watch,\n watchFile,\n write,\n writeFile,\n writeFileSync,\n writeSync,\n writev,\n writevSync,\n [Symbol.for(\"::bunternal::\")]: {\n ReadStreamClass,\n WriteStreamClass\n }\n};\nreturn $})\n"_s; // // @@ -565,7 +565,7 @@ static constexpr ASCIILiteral NodeEventsCode = "(function (){\"use strict\";// s // // -static constexpr ASCIILiteral NodeFSCode = "(function (){\"use strict\";// src/js/out/tmp/node/fs.ts\nvar callbackify = function(fsFunction, args) {\n try {\n const result = fsFunction.apply(fs, args.slice(0, args.length - 1)), callback = args[args.length - 1];\n if (typeof callback === \"function\")\n queueMicrotask(() => callback(null, result));\n } catch (e) {\n const callback = args[args.length - 1];\n if (typeof callback === \"function\")\n queueMicrotask(() => callback(e));\n }\n}, createReadStream = function(path, options) {\n return new ReadStream(path, options);\n}, createWriteStream = function(path, options) {\n return new WriteStream(path, options);\n}, cpSync = function(src, dest, options) {\n if (!options)\n return fs.cpSync(src, dest);\n if (typeof options !== \"object\")\n @throwTypeError(\"options must be an object\");\n if (options.dereference || options.filter || options.preserveTimestamps || options.verbatimSymlinks) {\n if (!lazy_cpSync)\n lazy_cpSync = @getInternalField(@internalModuleRegistry, 3) || @createInternalModuleById(3);\n return lazy_cpSync(src, dest, options);\n }\n return fs.cpSync(src, dest, options.recursive, options.errorOnExist, options.force \?\? !0, options.mode);\n}, cp = function(src, dest, options, callback) {\n if (typeof options === \"function\")\n callback = options, options = void 0;\n promises.cp(src, dest, options).then(() => callback(), callback);\n}, _toUnixTimestamp = function(time, name = \"time\") {\n if (typeof time === \"string\" && +time == time)\n return +time;\n if (NumberIsFinite(time)) {\n if (time < 0)\n return DateNow() / 1000;\n return time;\n }\n if (isDate(time))\n return DatePrototypeGetTime(time) / 1000;\n @throwTypeError(`Expected ${name} to be a number or Date`);\n}, $, ReadStream, WriteStream, EventEmitter = @getInternalField(@internalModuleRegistry, 18) || @createInternalModuleById(18), promises = @getInternalField(@internalModuleRegistry, 20) || @createInternalModuleById(20), Stream = @getInternalField(@internalModuleRegistry, 37) || @createInternalModuleById(37), { isArrayBufferView } = @requireNativeModule(\"node:util/types\"), constants = @processBindingConstants.fs;\nvar fs = Bun.fs();\n\nclass FSWatcher extends EventEmitter {\n #watcher;\n #listener;\n constructor(path, options, listener) {\n super();\n if (typeof options === \"function\")\n listener = options, options = {};\n else if (typeof options === \"string\")\n options = { encoding: options };\n if (typeof listener !== \"function\")\n listener = () => {\n };\n this.#listener = listener;\n try {\n this.#watcher = fs.watch(path, options || {}, this.#onEvent.bind(this));\n } catch (e) {\n if (!e.message\?.startsWith(\"FileNotFound\"))\n throw e;\n const notFound = new Error(`ENOENT: no such file or directory, watch '${path}'`);\n throw notFound.code = \"ENOENT\", notFound.errno = -2, notFound.path = path, notFound.syscall = \"watch\", notFound.filename = path, notFound;\n }\n }\n #onEvent(eventType, filenameOrError) {\n if (eventType === \"error\" || eventType === \"close\")\n this.emit(eventType, filenameOrError);\n else\n this.emit(\"change\", eventType, filenameOrError), this.#listener(eventType, filenameOrError);\n }\n close() {\n this.#watcher\?.close(), this.#watcher = null;\n }\n ref() {\n this.#watcher\?.ref();\n }\n unref() {\n this.#watcher\?.unref();\n }\n}\nvar access = function access2(...args) {\n callbackify(fs.accessSync, args);\n}, appendFile = function appendFile2(...args) {\n callbackify(fs.appendFileSync, args);\n}, close = function close2(...args) {\n callbackify(fs.closeSync, args);\n}, rm = function rm2(...args) {\n callbackify(fs.rmSync, args);\n}, rmdir = function rmdir2(...args) {\n callbackify(fs.rmdirSync, args);\n}, copyFile = function copyFile2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.copyFile(...args).then((result) => callback(null, result), callback);\n}, exists = function exists2(...args) {\n callbackify(fs.existsSync, args);\n}, chown = function chown2(...args) {\n callbackify(fs.chownSync, args);\n}, chmod = function chmod2(...args) {\n callbackify(fs.chmodSync, args);\n}, fchmod = function fchmod2(...args) {\n callbackify(fs.fchmodSync, args);\n}, fchown = function fchown2(...args) {\n callbackify(fs.fchownSync, args);\n}, fstat = function fstat2(...args) {\n callbackify(fs.fstatSync, args);\n}, fsync = function fsync2(...args) {\n callbackify(fs.fsyncSync, args);\n}, ftruncate = function ftruncate2(...args) {\n callbackify(fs.ftruncateSync, args);\n}, futimes = function futimes2(...args) {\n callbackify(fs.futimesSync, args);\n}, lchmod = function lchmod2(...args) {\n callbackify(fs.lchmodSync, args);\n}, lchown = function lchown2(...args) {\n callbackify(fs.lchownSync, args);\n}, link = function link2(...args) {\n callbackify(fs.linkSync, args);\n}, mkdir = function mkdir2(...args) {\n callbackify(fs.mkdirSync, args);\n}, mkdtemp = function mkdtemp2(...args) {\n callbackify(fs.mkdtempSync, args);\n}, open = function open2(...args) {\n callbackify(fs.openSync, args);\n}, read = function read2(fd, buffer, offsetOrOptions, length, position, callback) {\n let offset = offsetOrOptions, params = null;\n if (arguments.length <= 4) {\n if (arguments.length === 4)\n callback = length, params = offsetOrOptions;\n else if (arguments.length === 3) {\n if (!isArrayBufferView(buffer))\n params = buffer, { buffer = Buffer.alloc(16384) } = params \?\? {};\n callback = offsetOrOptions;\n } else\n callback = buffer, buffer = Buffer.alloc(16384);\n ({ offset = 0, length = buffer\?.byteLength - offset, position = null } = params \?\? {});\n }\n queueMicrotask(() => {\n try {\n var bytesRead = fs.readSync(fd, buffer, offset, length, position);\n } catch (e) {\n callback(e);\n }\n callback(null, bytesRead, buffer);\n });\n}, write = function write2(...args) {\n callbackify(fs.writeSync, args);\n}, readdir = function readdir2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.readdir(...args).then((result) => callback(null, result), callback);\n}, readFile = function readFile2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.readFile(...args).then((result) => callback(null, result), callback);\n}, writeFile = function writeFile2(...args) {\n callbackify(fs.writeFileSync, args);\n}, readlink = function readlink2(...args) {\n callbackify(fs.readlinkSync, args);\n}, realpath = function realpath2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.realpath(...args).then((result) => callback(null, result), callback);\n}, rename = function rename2(...args) {\n callbackify(fs.renameSync, args);\n}, lstat = function lstat2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.lstat(...args).then((result) => callback(null, result), callback);\n}, stat = function stat2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.stat(...args).then((result) => callback(null, result), callback);\n}, symlink = function symlink2(...args) {\n callbackify(fs.symlinkSync, args);\n}, truncate = function truncate2(...args) {\n callbackify(fs.truncateSync, args);\n}, unlink = function unlink2(...args) {\n callbackify(fs.unlinkSync, args);\n}, utimes = function utimes2(...args) {\n callbackify(fs.utimesSync, args);\n}, lutimes = function lutimes2(...args) {\n callbackify(fs.lutimesSync, args);\n}, accessSync = fs.accessSync.bind(fs), appendFileSync = fs.appendFileSync.bind(fs), closeSync = fs.closeSync.bind(fs), copyFileSync = fs.copyFileSync.bind(fs), existsSync = fs.existsSync.bind(fs), chownSync = fs.chownSync.bind(fs), chmodSync = fs.chmodSync.bind(fs), fchmodSync = fs.fchmodSync.bind(fs), fchownSync = fs.fchownSync.bind(fs), fstatSync = fs.fstatSync.bind(fs), fsyncSync = fs.fsyncSync.bind(fs), ftruncateSync = fs.ftruncateSync.bind(fs), futimesSync = fs.futimesSync.bind(fs), lchmodSync = fs.lchmodSync.bind(fs), lchownSync = fs.lchownSync.bind(fs), linkSync = fs.linkSync.bind(fs), lstatSync = fs.lstatSync.bind(fs), mkdirSync = fs.mkdirSync.bind(fs), mkdtempSync = fs.mkdtempSync.bind(fs), openSync = fs.openSync.bind(fs), readSync = fs.readSync.bind(fs), writeSync = fs.writeSync.bind(fs), readdirSync = fs.readdirSync.bind(fs), readFileSync = fs.readFileSync.bind(fs), writeFileSync = fs.writeFileSync.bind(fs), readlinkSync = fs.readlinkSync.bind(fs), realpathSync = fs.realpathSync.bind(fs), renameSync = fs.renameSync.bind(fs), statSync = fs.statSync.bind(fs), symlinkSync = fs.symlinkSync.bind(fs), truncateSync = fs.truncateSync.bind(fs), unlinkSync = fs.unlinkSync.bind(fs), utimesSync = fs.utimesSync.bind(fs), lutimesSync = fs.lutimesSync.bind(fs), rmSync = fs.rmSync.bind(fs), rmdirSync = fs.rmdirSync.bind(fs), writev = (fd, buffers, position, callback) => {\n if (typeof position === \"function\")\n callback = position, position = null;\n queueMicrotask(() => {\n try {\n var written = fs.writevSync(fd, buffers, position);\n } catch (e) {\n callback(e);\n }\n callback(null, written, buffers);\n });\n}, writevSync = fs.writevSync.bind(fs), readv = (fd, buffers, position, callback) => {\n if (typeof position === \"function\")\n callback = position, position = null;\n queueMicrotask(() => {\n try {\n var written = fs.readvSync(fd, buffers, position);\n } catch (e) {\n callback(e);\n }\n callback(null, written, buffers);\n });\n}, readvSync = fs.readvSync.bind(fs), Dirent = fs.Dirent, Stats = fs.Stats, watch = function watch2(path, options, listener) {\n return new FSWatcher(path, options, listener);\n}, readStreamPathFastPathSymbol = Symbol.for(\"Bun.Node.readStreamPathFastPath\"), readStreamSymbol = Symbol.for(\"Bun.NodeReadStream\"), readStreamPathOrFdSymbol = Symbol.for(\"Bun.NodeReadStreamPathOrFd\"), writeStreamSymbol = Symbol.for(\"Bun.NodeWriteStream\"), writeStreamPathFastPathSymbol = Symbol.for(\"Bun.NodeWriteStreamFastPath\"), writeStreamPathFastPathCallSymbol = Symbol.for(\"Bun.NodeWriteStreamFastPathCall\"), kIoDone = Symbol.for(\"kIoDone\"), defaultReadStreamOptions = {\n file: void 0,\n fd: null,\n flags: \"r\",\n encoding: void 0,\n mode: 438,\n autoClose: !0,\n emitClose: !0,\n start: 0,\n end: Infinity,\n highWaterMark: 65536,\n fs: {\n read,\n open: (path, flags, mode, cb) => {\n var fd;\n try {\n fd = openSync(path, flags, mode);\n } catch (e) {\n cb(e);\n return;\n }\n cb(null, fd);\n },\n openSync,\n close\n },\n autoDestroy: !0\n}, ReadStreamClass;\nReadStream = function(InternalReadStream) {\n ReadStreamClass = InternalReadStream, Object.defineProperty(ReadStreamClass.prototype, Symbol.toStringTag, {\n value: \"ReadStream\",\n enumerable: !1\n });\n function ReadStream3(path, options) {\n return new InternalReadStream(path, options);\n }\n return ReadStream3.prototype = InternalReadStream.prototype, Object.defineProperty(ReadStream3, Symbol.hasInstance, {\n value(instance) {\n return instance instanceof InternalReadStream;\n }\n });\n}(class ReadStream2 extends Stream._getNativeReadableStreamPrototype(2, Stream.Readable) {\n constructor(pathOrFd, options = defaultReadStreamOptions) {\n if (typeof options !== \"object\" || !options)\n @throwTypeError(\"Expected options to be an object\");\n var {\n flags = defaultReadStreamOptions.flags,\n encoding = defaultReadStreamOptions.encoding,\n mode = defaultReadStreamOptions.mode,\n autoClose = defaultReadStreamOptions.autoClose,\n emitClose = defaultReadStreamOptions.emitClose,\n start = defaultReadStreamOptions.start,\n end = defaultReadStreamOptions.end,\n autoDestroy = defaultReadStreamOptions.autoClose,\n fs: fs2 = defaultReadStreamOptions.fs,\n highWaterMark = defaultReadStreamOptions.highWaterMark,\n fd = defaultReadStreamOptions.fd\n } = options;\n if (pathOrFd\?.constructor\?.name === \"URL\")\n pathOrFd = Bun.fileURLToPath(pathOrFd);\n var tempThis = {};\n if (fd != null) {\n if (typeof fd !== \"number\")\n @throwTypeError(\"Expected options.fd to be a number\");\n tempThis.fd = tempThis[readStreamPathOrFdSymbol] = fd, tempThis.autoClose = !1;\n } else if (typeof pathOrFd === \"string\") {\n if (pathOrFd.startsWith(\"file://\"))\n pathOrFd = Bun.fileURLToPath(pathOrFd);\n if (pathOrFd.length === 0)\n @throwTypeError(\"Expected path to be a non-empty string\");\n tempThis.path = tempThis.file = tempThis[readStreamPathOrFdSymbol] = pathOrFd;\n } else if (typeof pathOrFd === \"number\") {\n if (pathOrFd |= 0, pathOrFd < 0)\n @throwTypeError(\"Expected fd to be a positive integer\");\n tempThis.fd = tempThis[readStreamPathOrFdSymbol] = pathOrFd, tempThis.autoClose = !1;\n } else\n @throwTypeError(\"Expected a path or file descriptor\");\n if (tempThis.fd === void 0)\n tempThis.fd = fs2.openSync(pathOrFd, flags, mode);\n var fileRef = Bun.file(tempThis.fd), stream = fileRef.stream(), native = @direct(stream);\n if (!native)\n throw new Error(\"no native readable stream\");\n var { stream: ptr } = native;\n super(ptr, {\n ...options,\n encoding,\n autoDestroy,\n autoClose,\n emitClose,\n highWaterMark\n });\n if (Object.assign(this, tempThis), this.#fileRef = fileRef, this.end = end, this._read = this.#internalRead, this.start = start, this.flags = flags, this.mode = mode, this.emitClose = emitClose, this[readStreamPathFastPathSymbol] = start === 0 && end === Infinity && autoClose && fs2 === defaultReadStreamOptions.fs && (encoding === \"buffer\" || encoding === \"binary\" || encoding == null || encoding === \"utf-8\" || encoding === \"utf8\"), this._readableState.autoClose = autoDestroy = autoClose, this._readableState.highWaterMark = highWaterMark, start !== void 0)\n this.pos = start;\n }\n #fileRef;\n #fs;\n file;\n path;\n fd = null;\n flags;\n mode;\n start;\n end;\n pos;\n bytesRead = 0;\n #fileSize = -1;\n _read;\n [readStreamSymbol] = !0;\n [readStreamPathOrFdSymbol];\n [readStreamPathFastPathSymbol];\n _construct(callback) {\n if (super._construct)\n super._construct(callback);\n else\n callback();\n this.emit(\"open\", this.fd), this.emit(\"ready\");\n }\n _destroy(err, cb) {\n super._destroy(err, cb);\n try {\n var fd = this.fd;\n if (this[readStreamPathFastPathSymbol] = !1, !fd)\n cb(err);\n else\n this.#fs.close(fd, (er) => {\n cb(er || err);\n }), this.fd = null;\n } catch (e) {\n throw e;\n }\n }\n close(cb) {\n if (typeof cb === \"function\")\n Stream.eos(this, cb);\n this.destroy();\n }\n push(chunk) {\n var bytesRead = chunk\?.length \?\? 0;\n if (bytesRead > 0) {\n this.bytesRead += bytesRead;\n var currPos = this.pos;\n if (currPos !== void 0) {\n if (this.bytesRead < currPos)\n return !0;\n if (currPos === this.start) {\n var n = this.bytesRead - currPos;\n chunk = chunk.slice(-n);\n var [_, ...rest] = arguments;\n if (this.pos = this.bytesRead, this.end !== void 0 && this.bytesRead > this.end)\n chunk = chunk.slice(0, this.end - this.start + 1);\n return super.push(chunk, ...rest);\n }\n var end = this.end;\n if (end !== void 0 && this.bytesRead > end) {\n chunk = chunk.slice(0, end - currPos + 1);\n var [_, ...rest] = arguments;\n return this.pos = this.bytesRead, super.push(chunk, ...rest);\n }\n this.pos = this.bytesRead;\n }\n }\n return super.push(...arguments);\n }\n #internalRead(n) {\n var { pos, end, bytesRead, fd, encoding } = this;\n if (n = pos !== void 0 \? Math.min(end - pos + 1, n) : Math.min(end - bytesRead + 1, n), n <= 0) {\n this.push(null);\n return;\n }\n if (this.#fileSize === -1 && bytesRead === 0 && pos === void 0) {\n var stat3 = fstatSync(fd);\n if (this.#fileSize = stat3.size, this.#fileSize > 0 && n > this.#fileSize)\n n = this.#fileSize + 1;\n }\n this[kIoDone] = !1;\n var res = super._read(n);\n if (@isPromise(res)) {\n var then = res\?.then;\n if (then && @isCallable(then))\n res.then(() => {\n if (this[kIoDone] = !0, this.destroyed)\n this.emit(kIoDone);\n }, (er) => {\n this[kIoDone] = !0, this.#errorOrDestroy(er);\n });\n } else if (this[kIoDone] = !0, this.destroyed)\n this.emit(kIoDone), this.#errorOrDestroy(new Error(\"ERR_STREAM_PREMATURE_CLOSE\"));\n }\n #errorOrDestroy(err, sync = null) {\n var {\n _readableState: r = { destroyed: !1, autoDestroy: !1 },\n _writableState: w = { destroyed: !1, autoDestroy: !1 }\n } = this;\n if (w\?.destroyed || r\?.destroyed)\n return this;\n if (r\?.autoDestroy || w\?.autoDestroy)\n this.destroy(err);\n else if (err)\n this.emit(\"error\", err);\n }\n pause() {\n return this[readStreamPathFastPathSymbol] = !1, super.pause();\n }\n resume() {\n return this[readStreamPathFastPathSymbol] = !1, super.resume();\n }\n unshift(...args) {\n return this[readStreamPathFastPathSymbol] = !1, super.unshift(...args);\n }\n pipe(dest, pipeOpts) {\n if (this[readStreamPathFastPathSymbol] && (pipeOpts\?.end \?\? !0) && this._readableState\?.pipes\?.length === 0) {\n if ((writeStreamPathFastPathSymbol in dest) && dest[writeStreamPathFastPathSymbol]) {\n if (dest[writeStreamPathFastPathCallSymbol](this, pipeOpts))\n return this;\n }\n }\n return this[readStreamPathFastPathSymbol] = !1, super.pipe(dest, pipeOpts);\n }\n});\nvar defaultWriteStreamOptions = {\n fd: null,\n start: void 0,\n pos: void 0,\n encoding: void 0,\n flags: \"w\",\n mode: 438,\n fs: {\n write,\n close,\n open,\n openSync\n }\n}, WriteStreamClass;\nWriteStream = function(InternalWriteStream) {\n WriteStreamClass = InternalWriteStream, Object.defineProperty(WriteStreamClass.prototype, Symbol.toStringTag, {\n value: \"WritesStream\",\n enumerable: !1\n });\n function WriteStream3(path, options) {\n return new InternalWriteStream(path, options);\n }\n return WriteStream3.prototype = InternalWriteStream.prototype, Object.defineProperty(WriteStream3, Symbol.hasInstance, {\n value(instance) {\n return instance instanceof InternalWriteStream;\n }\n });\n}(class WriteStream2 extends Stream.NativeWritable {\n constructor(path, options = defaultWriteStreamOptions) {\n if (!options)\n @throwTypeError(\"Expected options to be an object\");\n var {\n fs: fs2 = defaultWriteStreamOptions.fs,\n start = defaultWriteStreamOptions.start,\n flags = defaultWriteStreamOptions.flags,\n mode = defaultWriteStreamOptions.mode,\n autoClose = !0,\n emitClose = !1,\n autoDestroy = autoClose,\n encoding = defaultWriteStreamOptions.encoding,\n fd = defaultWriteStreamOptions.fd,\n pos = defaultWriteStreamOptions.pos\n } = options, tempThis = {};\n if (fd != null) {\n if (typeof fd !== \"number\")\n throw new Error(\"Expected options.fd to be a number\");\n tempThis.fd = fd, tempThis[writeStreamPathFastPathSymbol] = !1;\n } else if (typeof path === \"string\") {\n if (path.length === 0)\n @throwTypeError(\"Expected a non-empty path\");\n if (path.startsWith(\"file:\"))\n path = Bun.fileURLToPath(path);\n tempThis.path = path, tempThis.fd = null, tempThis[writeStreamPathFastPathSymbol] = autoClose && (start === void 0 || start === 0) && fs2.write === defaultWriteStreamOptions.fs.write && fs2.close === defaultWriteStreamOptions.fs.close;\n }\n if (tempThis.fd == null)\n tempThis.fd = fs2.openSync(path, flags, mode);\n super(tempThis.fd, {\n ...options,\n decodeStrings: !1,\n autoDestroy,\n emitClose,\n fd: tempThis\n });\n if (Object.assign(this, tempThis), typeof fs2\?.write !== \"function\")\n @throwTypeError(\"Expected fs.write to be a function\");\n if (typeof fs2\?.close !== \"function\")\n @throwTypeError(\"Expected fs.close to be a function\");\n if (typeof fs2\?.open !== \"function\")\n @throwTypeError(\"Expected fs.open to be a function\");\n if (typeof path === \"object\" && path) {\n if (path instanceof URL)\n path = Bun.fileURLToPath(path);\n }\n if (typeof path !== \"string\" && typeof fd !== \"number\")\n @throwTypeError(\"Expected a path or file descriptor\");\n if (this.start = start, this.#fs = fs2, this.flags = flags, this.mode = mode, this.start !== void 0)\n this.pos = this.start;\n if (encoding !== defaultWriteStreamOptions.encoding) {\n if (this.setDefaultEncoding(encoding), encoding !== \"buffer\" && encoding !== \"utf8\" && encoding !== \"utf-8\" && encoding !== \"binary\")\n this[writeStreamPathFastPathSymbol] = !1;\n }\n }\n get autoClose() {\n return this._writableState.autoDestroy;\n }\n set autoClose(val) {\n this._writableState.autoDestroy = val;\n }\n destroySoon = this.end;\n open() {\n }\n path;\n fd;\n flags;\n mode;\n #fs;\n bytesWritten = 0;\n pos;\n [writeStreamPathFastPathSymbol];\n [writeStreamSymbol] = !0;\n start;\n [writeStreamPathFastPathCallSymbol](readStream, pipeOpts) {\n if (!this[writeStreamPathFastPathSymbol])\n return !1;\n if (this.fd !== null)\n return this[writeStreamPathFastPathSymbol] = !1, !1;\n return this[kIoDone] = !1, readStream[kIoDone] = !1, Bun.write(this[writeStreamPathFastPathSymbol], readStream[readStreamPathOrFdSymbol]).then((bytesWritten) => {\n readStream[kIoDone] = this[kIoDone] = !0, this.bytesWritten += bytesWritten, readStream.bytesRead += bytesWritten, this.end(), readStream.close();\n }, (err) => {\n readStream[kIoDone] = this[kIoDone] = !0, this.#errorOrDestroy(err), readStream.emit(\"error\", err);\n });\n }\n isBunFastPathEnabled() {\n return this[writeStreamPathFastPathSymbol];\n }\n disableBunFastPath() {\n this[writeStreamPathFastPathSymbol] = !1;\n }\n #handleWrite(er, bytes) {\n if (er)\n return this.#errorOrDestroy(er);\n this.bytesWritten += bytes;\n }\n #internalClose(err, cb) {\n this[writeStreamPathFastPathSymbol] = !1;\n var fd = this.fd;\n this.#fs.close(fd, (er) => {\n this.fd = null, cb(err || er);\n });\n }\n _construct(callback) {\n if (typeof this.fd === \"number\") {\n callback();\n return;\n }\n callback(), this.emit(\"open\", this.fd), this.emit(\"ready\");\n }\n _destroy(err, cb) {\n if (this.fd === null)\n return cb(err);\n if (this[kIoDone]) {\n this.once(kIoDone, () => this.#internalClose(err, cb));\n return;\n }\n this.#internalClose(err, cb);\n }\n [kIoDone] = !1;\n close(cb) {\n if (cb) {\n if (this.closed) {\n process.nextTick(cb);\n return;\n }\n this.on(\"close\", cb);\n }\n if (!this.autoClose)\n this.on(\"finish\", this.destroy);\n this.end();\n }\n write(chunk, encoding = this._writableState.defaultEncoding, cb) {\n if (this[writeStreamPathFastPathSymbol] = !1, typeof chunk === \"string\")\n chunk = Buffer.from(chunk, encoding);\n var native = this.pos === void 0;\n const callback = native \? (err, bytes) => {\n if (this[kIoDone] = !1, this.#handleWrite(err, bytes), this.emit(kIoDone), cb)\n !err \? cb() : cb(err);\n } : () => {\n };\n if (this[kIoDone] = !0, this._write)\n return this._write(chunk, encoding, callback);\n else\n return super.write(chunk, encoding, callback, native);\n }\n end(chunk, encoding, cb) {\n var native = this.pos === void 0;\n return super.end(chunk, encoding, cb, native);\n }\n _write = void 0;\n _writev = void 0;\n get pending() {\n return this.fd === null;\n }\n _destroy(err, cb) {\n this.close(err, cb);\n }\n #errorOrDestroy(err) {\n var {\n _readableState: r = { destroyed: !1, autoDestroy: !1 },\n _writableState: w = { destroyed: !1, autoDestroy: !1 }\n } = this;\n if (w\?.destroyed || r\?.destroyed)\n return this;\n if (r\?.autoDestroy || w\?.autoDestroy)\n this.destroy(err);\n else if (err)\n this.emit(\"error\", err);\n }\n});\nObject.defineProperties(fs, {\n createReadStream: {\n value: createReadStream\n },\n createWriteStream: {\n value: createWriteStream\n },\n ReadStream: {\n value: ReadStream\n },\n WriteStream: {\n value: WriteStream\n }\n});\nrealpath.native = realpath;\nrealpathSync.native = realpathSync;\nvar lazy_cpSync = null;\n$ = {\n Dirent,\n FSWatcher,\n ReadStream,\n Stats,\n WriteStream,\n _toUnixTimestamp,\n access,\n accessSync,\n appendFile,\n appendFileSync,\n chmod,\n chmodSync,\n chown,\n chownSync,\n close,\n closeSync,\n constants,\n copyFile,\n copyFileSync,\n cp,\n cpSync,\n createReadStream,\n createWriteStream,\n exists,\n existsSync,\n fchmod,\n fchmodSync,\n fchown,\n fchownSync,\n fstat,\n fstatSync,\n fsync,\n fsyncSync,\n ftruncate,\n ftruncateSync,\n futimes,\n futimesSync,\n lchmod,\n lchmodSync,\n lchown,\n lchownSync,\n link,\n linkSync,\n lstat,\n lstatSync,\n lutimes,\n lutimesSync,\n mkdir,\n mkdirSync,\n mkdtemp,\n mkdtempSync,\n open,\n openSync,\n promises,\n read,\n readFile,\n readFileSync,\n readSync,\n readdir,\n readdirSync,\n readlink,\n readlinkSync,\n readv,\n readvSync,\n realpath,\n realpathSync,\n rename,\n renameSync,\n rm,\n rmSync,\n rmdir,\n rmdirSync,\n stat,\n statSync,\n symlink,\n symlinkSync,\n truncate,\n truncateSync,\n unlink,\n unlinkSync,\n utimes,\n utimesSync,\n watch,\n write,\n writeFile,\n writeFileSync,\n writeSync,\n writev,\n writevSync,\n [Symbol.for(\"::bunternal::\")]: {\n ReadStreamClass,\n WriteStreamClass\n }\n};\nreturn $})\n"_s; +static constexpr ASCIILiteral NodeFSCode = "(function (){\"use strict\";// src/js/out/tmp/node/fs.ts\nvar getValidatedPath = function(p) {\n if (p instanceof URL)\n return Bun.fileURLToPath(p);\n if (typeof p !== \"string\")\n @throwTypeError(\"Path must be a string or URL.\");\n return (_pathModule \?\?= @getInternalField(@internalModuleRegistry, 28) || @createInternalModuleById(28)).resolve(p);\n}, watchFile = function(filename, options, listener) {\n if (filename = getValidatedPath(filename), typeof options === \"function\")\n listener = options, options = {};\n if (typeof listener !== \"function\")\n @throwTypeError(\"listener must be a function\");\n var stat = statWatchers.get(filename);\n if (!stat)\n stat = new StatWatcher(filename, options), statWatchers.set(filename, stat);\n return stat.addListener(\"change\", listener), stat;\n}, unwatchFile = function(filename, listener) {\n filename = getValidatedPath(filename);\n var stat = statWatchers.get(filename);\n if (!stat)\n return;\n if (listener) {\n if (stat.removeListener(\"change\", listener), stat.listenerCount(\"change\") !== 0)\n return;\n } else\n stat.removeAllListeners(\"change\");\n stat.stop(), statWatchers.delete(filename);\n}, callbackify = function(fsFunction, args) {\n try {\n const result = fsFunction.apply(fs, args.slice(0, args.length - 1)), callback = args[args.length - 1];\n if (typeof callback === \"function\")\n queueMicrotask(() => callback(null, result));\n } catch (e) {\n const callback = args[args.length - 1];\n if (typeof callback === \"function\")\n queueMicrotask(() => callback(e));\n }\n}, createReadStream = function(path, options) {\n return new ReadStream(path, options);\n}, createWriteStream = function(path, options) {\n return new WriteStream(path, options);\n}, cpSync = function(src, dest, options) {\n if (!options)\n return fs.cpSync(src, dest);\n if (typeof options !== \"object\")\n @throwTypeError(\"options must be an object\");\n if (options.dereference || options.filter || options.preserveTimestamps || options.verbatimSymlinks) {\n if (!lazy_cpSync)\n lazy_cpSync = @getInternalField(@internalModuleRegistry, 3) || @createInternalModuleById(3);\n return lazy_cpSync(src, dest, options);\n }\n return fs.cpSync(src, dest, options.recursive, options.errorOnExist, options.force \?\? !0, options.mode);\n}, cp = function(src, dest, options, callback) {\n if (typeof options === \"function\")\n callback = options, options = void 0;\n promises.cp(src, dest, options).then(() => callback(), callback);\n}, _toUnixTimestamp = function(time, name = \"time\") {\n if (typeof time === \"string\" && +time == time)\n return +time;\n if (NumberIsFinite(time)) {\n if (time < 0)\n return DateNow() / 1000;\n return time;\n }\n if (isDate(time))\n return DatePrototypeGetTime(time) / 1000;\n @throwTypeError(`Expected ${name} to be a number or Date`);\n}, $, ReadStream, WriteStream, EventEmitter = @getInternalField(@internalModuleRegistry, 18) || @createInternalModuleById(18), promises = @getInternalField(@internalModuleRegistry, 20) || @createInternalModuleById(20), Stream = @getInternalField(@internalModuleRegistry, 37) || @createInternalModuleById(37), { isArrayBufferView } = @requireNativeModule(\"node:util/types\"), constants = @processBindingConstants.fs, fs = Bun.fs();\n\nclass FSWatcher extends EventEmitter {\n #watcher;\n #listener;\n constructor(path, options, listener) {\n super();\n if (typeof options === \"function\")\n listener = options, options = {};\n else if (typeof options === \"string\")\n options = { encoding: options };\n if (typeof listener !== \"function\")\n listener = () => {\n };\n this.#listener = listener;\n try {\n this.#watcher = fs.watch(path, options || {}, this.#onEvent.bind(this));\n } catch (e) {\n if (!e.message\?.startsWith(\"FileNotFound\"))\n throw e;\n const notFound = new Error(`ENOENT: no such file or directory, watch '${path}'`);\n throw notFound.code = \"ENOENT\", notFound.errno = -2, notFound.path = path, notFound.syscall = \"watch\", notFound.filename = path, notFound;\n }\n }\n #onEvent(eventType, filenameOrError) {\n if (eventType === \"error\" || eventType === \"close\")\n this.emit(eventType, filenameOrError);\n else\n this.emit(\"change\", eventType, filenameOrError), this.#listener(eventType, filenameOrError);\n }\n close() {\n this.#watcher\?.close(), this.#watcher = null;\n }\n ref() {\n this.#watcher\?.ref();\n }\n unref() {\n this.#watcher\?.unref();\n }\n start() {\n }\n}\n\nclass StatWatcher extends EventEmitter {\n constructor(path, options) {\n super();\n this._handle = fs.watchFile(path, options, this.#onChange.bind(this));\n }\n #onChange(curr, prev) {\n this.emit(\"change\", curr, prev);\n }\n start() {\n }\n stop() {\n this._handle\?.close(), this._handle = null;\n }\n ref() {\n this._handle\?.ref();\n }\n unref() {\n this._handle\?.unref();\n }\n}\nvar access = function access2(...args) {\n callbackify(fs.accessSync, args);\n}, appendFile = function appendFile2(...args) {\n callbackify(fs.appendFileSync, args);\n}, close = function close2(...args) {\n callbackify(fs.closeSync, args);\n}, rm = function rm2(...args) {\n callbackify(fs.rmSync, args);\n}, rmdir = function rmdir2(...args) {\n callbackify(fs.rmdirSync, args);\n}, copyFile = function copyFile2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.copyFile(...args).then((result) => callback(null, result), callback);\n}, exists = function exists2(...args) {\n callbackify(fs.existsSync, args);\n}, chown = function chown2(...args) {\n callbackify(fs.chownSync, args);\n}, chmod = function chmod2(...args) {\n callbackify(fs.chmodSync, args);\n}, fchmod = function fchmod2(...args) {\n callbackify(fs.fchmodSync, args);\n}, fchown = function fchown2(...args) {\n callbackify(fs.fchownSync, args);\n}, fstat = function fstat2(...args) {\n callbackify(fs.fstatSync, args);\n}, fsync = function fsync2(...args) {\n callbackify(fs.fsyncSync, args);\n}, ftruncate = function ftruncate2(...args) {\n callbackify(fs.ftruncateSync, args);\n}, futimes = function futimes2(...args) {\n callbackify(fs.futimesSync, args);\n}, lchmod = function lchmod2(...args) {\n callbackify(fs.lchmodSync, args);\n}, lchown = function lchown2(...args) {\n callbackify(fs.lchownSync, args);\n}, link = function link2(...args) {\n callbackify(fs.linkSync, args);\n}, mkdir = function mkdir2(...args) {\n callbackify(fs.mkdirSync, args);\n}, mkdtemp = function mkdtemp2(...args) {\n callbackify(fs.mkdtempSync, args);\n}, open = function open2(...args) {\n callbackify(fs.openSync, args);\n}, read = function read2(fd, buffer, offsetOrOptions, length, position, callback) {\n let offset = offsetOrOptions, params = null;\n if (arguments.length <= 4) {\n if (arguments.length === 4)\n callback = length, params = offsetOrOptions;\n else if (arguments.length === 3) {\n if (!isArrayBufferView(buffer))\n params = buffer, { buffer = Buffer.alloc(16384) } = params \?\? {};\n callback = offsetOrOptions;\n } else\n callback = buffer, buffer = Buffer.alloc(16384);\n ({ offset = 0, length = buffer\?.byteLength - offset, position = null } = params \?\? {});\n }\n queueMicrotask(() => {\n try {\n var bytesRead = fs.readSync(fd, buffer, offset, length, position);\n } catch (e) {\n callback(e);\n }\n callback(null, bytesRead, buffer);\n });\n}, write = function write2(...args) {\n callbackify(fs.writeSync, args);\n}, readdir = function readdir2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.readdir(...args).then((result) => callback(null, result), callback);\n}, readFile = function readFile2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.readFile(...args).then((result) => callback(null, result), callback);\n}, writeFile = function writeFile2(...args) {\n callbackify(fs.writeFileSync, args);\n}, readlink = function readlink2(...args) {\n callbackify(fs.readlinkSync, args);\n}, realpath = function realpath2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.realpath(...args).then((result) => callback(null, result), callback);\n}, rename = function rename2(...args) {\n callbackify(fs.renameSync, args);\n}, lstat = function lstat2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.lstat(...args).then((result) => callback(null, result), callback);\n}, stat = function stat2(...args) {\n const callback = args[args.length - 1];\n if (typeof callback !== \"function\")\n @throwTypeError(\"Callback must be a function\");\n fs.stat(...args).then((result) => callback(null, result), callback);\n}, symlink = function symlink2(...args) {\n callbackify(fs.symlinkSync, args);\n}, truncate = function truncate2(...args) {\n callbackify(fs.truncateSync, args);\n}, unlink = function unlink2(...args) {\n callbackify(fs.unlinkSync, args);\n}, utimes = function utimes2(...args) {\n callbackify(fs.utimesSync, args);\n}, lutimes = function lutimes2(...args) {\n callbackify(fs.lutimesSync, args);\n}, accessSync = fs.accessSync.bind(fs), appendFileSync = fs.appendFileSync.bind(fs), closeSync = fs.closeSync.bind(fs), copyFileSync = fs.copyFileSync.bind(fs), existsSync = fs.existsSync.bind(fs), chownSync = fs.chownSync.bind(fs), chmodSync = fs.chmodSync.bind(fs), fchmodSync = fs.fchmodSync.bind(fs), fchownSync = fs.fchownSync.bind(fs), fstatSync = fs.fstatSync.bind(fs), fsyncSync = fs.fsyncSync.bind(fs), ftruncateSync = fs.ftruncateSync.bind(fs), futimesSync = fs.futimesSync.bind(fs), lchmodSync = fs.lchmodSync.bind(fs), lchownSync = fs.lchownSync.bind(fs), linkSync = fs.linkSync.bind(fs), lstatSync = fs.lstatSync.bind(fs), mkdirSync = fs.mkdirSync.bind(fs), mkdtempSync = fs.mkdtempSync.bind(fs), openSync = fs.openSync.bind(fs), readSync = fs.readSync.bind(fs), writeSync = fs.writeSync.bind(fs), readdirSync = fs.readdirSync.bind(fs), readFileSync = fs.readFileSync.bind(fs), writeFileSync = fs.writeFileSync.bind(fs), readlinkSync = fs.readlinkSync.bind(fs), realpathSync = fs.realpathSync.bind(fs), renameSync = fs.renameSync.bind(fs), statSync = fs.statSync.bind(fs), symlinkSync = fs.symlinkSync.bind(fs), truncateSync = fs.truncateSync.bind(fs), unlinkSync = fs.unlinkSync.bind(fs), utimesSync = fs.utimesSync.bind(fs), lutimesSync = fs.lutimesSync.bind(fs), rmSync = fs.rmSync.bind(fs), rmdirSync = fs.rmdirSync.bind(fs), writev = (fd, buffers, position, callback) => {\n if (typeof position === \"function\")\n callback = position, position = null;\n queueMicrotask(() => {\n try {\n var written = fs.writevSync(fd, buffers, position);\n } catch (e) {\n callback(e);\n }\n callback(null, written, buffers);\n });\n}, writevSync = fs.writevSync.bind(fs), readv = (fd, buffers, position, callback) => {\n if (typeof position === \"function\")\n callback = position, position = null;\n queueMicrotask(() => {\n try {\n var written = fs.readvSync(fd, buffers, position);\n } catch (e) {\n callback(e);\n }\n callback(null, written, buffers);\n });\n}, readvSync = fs.readvSync.bind(fs), Dirent = fs.Dirent, Stats = fs.Stats, watch = function watch2(path, options, listener) {\n return new FSWatcher(path, options, listener);\n}, statWatchers = new Map, _pathModule, readStreamPathFastPathSymbol = Symbol.for(\"Bun.Node.readStreamPathFastPath\"), readStreamSymbol = Symbol.for(\"Bun.NodeReadStream\"), readStreamPathOrFdSymbol = Symbol.for(\"Bun.NodeReadStreamPathOrFd\"), writeStreamSymbol = Symbol.for(\"Bun.NodeWriteStream\"), writeStreamPathFastPathSymbol = Symbol.for(\"Bun.NodeWriteStreamFastPath\"), writeStreamPathFastPathCallSymbol = Symbol.for(\"Bun.NodeWriteStreamFastPathCall\"), kIoDone = Symbol.for(\"kIoDone\"), defaultReadStreamOptions = {\n file: void 0,\n fd: null,\n flags: \"r\",\n encoding: void 0,\n mode: 438,\n autoClose: !0,\n emitClose: !0,\n start: 0,\n end: Infinity,\n highWaterMark: 65536,\n fs: {\n read,\n open: (path, flags, mode, cb) => {\n var fd;\n try {\n fd = openSync(path, flags, mode);\n } catch (e) {\n cb(e);\n return;\n }\n cb(null, fd);\n },\n openSync,\n close\n },\n autoDestroy: !0\n}, ReadStreamClass;\nReadStream = function(InternalReadStream) {\n ReadStreamClass = InternalReadStream, Object.defineProperty(ReadStreamClass.prototype, Symbol.toStringTag, {\n value: \"ReadStream\",\n enumerable: !1\n });\n function ReadStream3(path, options) {\n return new InternalReadStream(path, options);\n }\n return ReadStream3.prototype = InternalReadStream.prototype, Object.defineProperty(ReadStream3, Symbol.hasInstance, {\n value(instance) {\n return instance instanceof InternalReadStream;\n }\n });\n}(class ReadStream2 extends Stream._getNativeReadableStreamPrototype(2, Stream.Readable) {\n constructor(pathOrFd, options = defaultReadStreamOptions) {\n if (typeof options !== \"object\" || !options)\n @throwTypeError(\"Expected options to be an object\");\n var {\n flags = defaultReadStreamOptions.flags,\n encoding = defaultReadStreamOptions.encoding,\n mode = defaultReadStreamOptions.mode,\n autoClose = defaultReadStreamOptions.autoClose,\n emitClose = defaultReadStreamOptions.emitClose,\n start = defaultReadStreamOptions.start,\n end = defaultReadStreamOptions.end,\n autoDestroy = defaultReadStreamOptions.autoClose,\n fs: fs2 = defaultReadStreamOptions.fs,\n highWaterMark = defaultReadStreamOptions.highWaterMark,\n fd = defaultReadStreamOptions.fd\n } = options;\n if (pathOrFd\?.constructor\?.name === \"URL\")\n pathOrFd = Bun.fileURLToPath(pathOrFd);\n var tempThis = {};\n if (fd != null) {\n if (typeof fd !== \"number\")\n @throwTypeError(\"Expected options.fd to be a number\");\n tempThis.fd = tempThis[readStreamPathOrFdSymbol] = fd, tempThis.autoClose = !1;\n } else if (typeof pathOrFd === \"string\") {\n if (pathOrFd.startsWith(\"file://\"))\n pathOrFd = Bun.fileURLToPath(pathOrFd);\n if (pathOrFd.length === 0)\n @throwTypeError(\"Expected path to be a non-empty string\");\n tempThis.path = tempThis.file = tempThis[readStreamPathOrFdSymbol] = pathOrFd;\n } else if (typeof pathOrFd === \"number\") {\n if (pathOrFd |= 0, pathOrFd < 0)\n @throwTypeError(\"Expected fd to be a positive integer\");\n tempThis.fd = tempThis[readStreamPathOrFdSymbol] = pathOrFd, tempThis.autoClose = !1;\n } else\n @throwTypeError(\"Expected a path or file descriptor\");\n if (tempThis.fd === void 0)\n tempThis.fd = fs2.openSync(pathOrFd, flags, mode);\n var fileRef = Bun.file(tempThis.fd), stream = fileRef.stream(), native = @direct(stream);\n if (!native)\n throw new Error(\"no native readable stream\");\n var { stream: ptr } = native;\n super(ptr, {\n ...options,\n encoding,\n autoDestroy,\n autoClose,\n emitClose,\n highWaterMark\n });\n if (Object.assign(this, tempThis), this.#fileRef = fileRef, this.end = end, this._read = this.#internalRead, this.start = start, this.flags = flags, this.mode = mode, this.emitClose = emitClose, this[readStreamPathFastPathSymbol] = start === 0 && end === Infinity && autoClose && fs2 === defaultReadStreamOptions.fs && (encoding === \"buffer\" || encoding === \"binary\" || encoding == null || encoding === \"utf-8\" || encoding === \"utf8\"), this._readableState.autoClose = autoDestroy = autoClose, this._readableState.highWaterMark = highWaterMark, start !== void 0)\n this.pos = start;\n }\n #fileRef;\n #fs;\n file;\n path;\n fd = null;\n flags;\n mode;\n start;\n end;\n pos;\n bytesRead = 0;\n #fileSize = -1;\n _read;\n [readStreamSymbol] = !0;\n [readStreamPathOrFdSymbol];\n [readStreamPathFastPathSymbol];\n _construct(callback) {\n if (super._construct)\n super._construct(callback);\n else\n callback();\n this.emit(\"open\", this.fd), this.emit(\"ready\");\n }\n _destroy(err, cb) {\n super._destroy(err, cb);\n try {\n var fd = this.fd;\n if (this[readStreamPathFastPathSymbol] = !1, !fd)\n cb(err);\n else\n this.#fs.close(fd, (er) => {\n cb(er || err);\n }), this.fd = null;\n } catch (e) {\n throw e;\n }\n }\n close(cb) {\n if (typeof cb === \"function\")\n Stream.eos(this, cb);\n this.destroy();\n }\n push(chunk) {\n var bytesRead = chunk\?.length \?\? 0;\n if (bytesRead > 0) {\n this.bytesRead += bytesRead;\n var currPos = this.pos;\n if (currPos !== void 0) {\n if (this.bytesRead < currPos)\n return !0;\n if (currPos === this.start) {\n var n = this.bytesRead - currPos;\n chunk = chunk.slice(-n);\n var [_, ...rest] = arguments;\n if (this.pos = this.bytesRead, this.end !== void 0 && this.bytesRead > this.end)\n chunk = chunk.slice(0, this.end - this.start + 1);\n return super.push(chunk, ...rest);\n }\n var end = this.end;\n if (end !== void 0 && this.bytesRead > end) {\n chunk = chunk.slice(0, end - currPos + 1);\n var [_, ...rest] = arguments;\n return this.pos = this.bytesRead, super.push(chunk, ...rest);\n }\n this.pos = this.bytesRead;\n }\n }\n return super.push(...arguments);\n }\n #internalRead(n) {\n var { pos, end, bytesRead, fd, encoding } = this;\n if (n = pos !== void 0 \? Math.min(end - pos + 1, n) : Math.min(end - bytesRead + 1, n), n <= 0) {\n this.push(null);\n return;\n }\n if (this.#fileSize === -1 && bytesRead === 0 && pos === void 0) {\n var stat3 = fstatSync(fd);\n if (this.#fileSize = stat3.size, this.#fileSize > 0 && n > this.#fileSize)\n n = this.#fileSize + 1;\n }\n this[kIoDone] = !1;\n var res = super._read(n);\n if (@isPromise(res)) {\n var then = res\?.then;\n if (then && @isCallable(then))\n res.then(() => {\n if (this[kIoDone] = !0, this.destroyed)\n this.emit(kIoDone);\n }, (er) => {\n this[kIoDone] = !0, this.#errorOrDestroy(er);\n });\n } else if (this[kIoDone] = !0, this.destroyed)\n this.emit(kIoDone), this.#errorOrDestroy(new Error(\"ERR_STREAM_PREMATURE_CLOSE\"));\n }\n #errorOrDestroy(err, sync = null) {\n var {\n _readableState: r = { destroyed: !1, autoDestroy: !1 },\n _writableState: w = { destroyed: !1, autoDestroy: !1 }\n } = this;\n if (w\?.destroyed || r\?.destroyed)\n return this;\n if (r\?.autoDestroy || w\?.autoDestroy)\n this.destroy(err);\n else if (err)\n this.emit(\"error\", err);\n }\n pause() {\n return this[readStreamPathFastPathSymbol] = !1, super.pause();\n }\n resume() {\n return this[readStreamPathFastPathSymbol] = !1, super.resume();\n }\n unshift(...args) {\n return this[readStreamPathFastPathSymbol] = !1, super.unshift(...args);\n }\n pipe(dest, pipeOpts) {\n if (this[readStreamPathFastPathSymbol] && (pipeOpts\?.end \?\? !0) && this._readableState\?.pipes\?.length === 0) {\n if ((writeStreamPathFastPathSymbol in dest) && dest[writeStreamPathFastPathSymbol]) {\n if (dest[writeStreamPathFastPathCallSymbol](this, pipeOpts))\n return this;\n }\n }\n return this[readStreamPathFastPathSymbol] = !1, super.pipe(dest, pipeOpts);\n }\n});\nvar defaultWriteStreamOptions = {\n fd: null,\n start: void 0,\n pos: void 0,\n encoding: void 0,\n flags: \"w\",\n mode: 438,\n fs: {\n write,\n close,\n open,\n openSync\n }\n}, WriteStreamClass;\nWriteStream = function(InternalWriteStream) {\n WriteStreamClass = InternalWriteStream, Object.defineProperty(WriteStreamClass.prototype, Symbol.toStringTag, {\n value: \"WritesStream\",\n enumerable: !1\n });\n function WriteStream3(path, options) {\n return new InternalWriteStream(path, options);\n }\n return WriteStream3.prototype = InternalWriteStream.prototype, Object.defineProperty(WriteStream3, Symbol.hasInstance, {\n value(instance) {\n return instance instanceof InternalWriteStream;\n }\n });\n}(class WriteStream2 extends Stream.NativeWritable {\n constructor(path, options = defaultWriteStreamOptions) {\n if (!options)\n @throwTypeError(\"Expected options to be an object\");\n var {\n fs: fs2 = defaultWriteStreamOptions.fs,\n start = defaultWriteStreamOptions.start,\n flags = defaultWriteStreamOptions.flags,\n mode = defaultWriteStreamOptions.mode,\n autoClose = !0,\n emitClose = !1,\n autoDestroy = autoClose,\n encoding = defaultWriteStreamOptions.encoding,\n fd = defaultWriteStreamOptions.fd,\n pos = defaultWriteStreamOptions.pos\n } = options, tempThis = {};\n if (fd != null) {\n if (typeof fd !== \"number\")\n throw new Error(\"Expected options.fd to be a number\");\n tempThis.fd = fd, tempThis[writeStreamPathFastPathSymbol] = !1;\n } else if (typeof path === \"string\") {\n if (path.length === 0)\n @throwTypeError(\"Expected a non-empty path\");\n if (path.startsWith(\"file:\"))\n path = Bun.fileURLToPath(path);\n tempThis.path = path, tempThis.fd = null, tempThis[writeStreamPathFastPathSymbol] = autoClose && (start === void 0 || start === 0) && fs2.write === defaultWriteStreamOptions.fs.write && fs2.close === defaultWriteStreamOptions.fs.close;\n }\n if (tempThis.fd == null)\n tempThis.fd = fs2.openSync(path, flags, mode);\n super(tempThis.fd, {\n ...options,\n decodeStrings: !1,\n autoDestroy,\n emitClose,\n fd: tempThis\n });\n if (Object.assign(this, tempThis), typeof fs2\?.write !== \"function\")\n @throwTypeError(\"Expected fs.write to be a function\");\n if (typeof fs2\?.close !== \"function\")\n @throwTypeError(\"Expected fs.close to be a function\");\n if (typeof fs2\?.open !== \"function\")\n @throwTypeError(\"Expected fs.open to be a function\");\n if (typeof path === \"object\" && path) {\n if (path instanceof URL)\n path = Bun.fileURLToPath(path);\n }\n if (typeof path !== \"string\" && typeof fd !== \"number\")\n @throwTypeError(\"Expected a path or file descriptor\");\n if (this.start = start, this.#fs = fs2, this.flags = flags, this.mode = mode, this.start !== void 0)\n this.pos = this.start;\n if (encoding !== defaultWriteStreamOptions.encoding) {\n if (this.setDefaultEncoding(encoding), encoding !== \"buffer\" && encoding !== \"utf8\" && encoding !== \"utf-8\" && encoding !== \"binary\")\n this[writeStreamPathFastPathSymbol] = !1;\n }\n }\n get autoClose() {\n return this._writableState.autoDestroy;\n }\n set autoClose(val) {\n this._writableState.autoDestroy = val;\n }\n destroySoon = this.end;\n open() {\n }\n path;\n fd;\n flags;\n mode;\n #fs;\n bytesWritten = 0;\n pos;\n [writeStreamPathFastPathSymbol];\n [writeStreamSymbol] = !0;\n start;\n [writeStreamPathFastPathCallSymbol](readStream, pipeOpts) {\n if (!this[writeStreamPathFastPathSymbol])\n return !1;\n if (this.fd !== null)\n return this[writeStreamPathFastPathSymbol] = !1, !1;\n return this[kIoDone] = !1, readStream[kIoDone] = !1, Bun.write(this[writeStreamPathFastPathSymbol], readStream[readStreamPathOrFdSymbol]).then((bytesWritten) => {\n readStream[kIoDone] = this[kIoDone] = !0, this.bytesWritten += bytesWritten, readStream.bytesRead += bytesWritten, this.end(), readStream.close();\n }, (err) => {\n readStream[kIoDone] = this[kIoDone] = !0, this.#errorOrDestroy(err), readStream.emit(\"error\", err);\n });\n }\n isBunFastPathEnabled() {\n return this[writeStreamPathFastPathSymbol];\n }\n disableBunFastPath() {\n this[writeStreamPathFastPathSymbol] = !1;\n }\n #handleWrite(er, bytes) {\n if (er)\n return this.#errorOrDestroy(er);\n this.bytesWritten += bytes;\n }\n #internalClose(err, cb) {\n this[writeStreamPathFastPathSymbol] = !1;\n var fd = this.fd;\n this.#fs.close(fd, (er) => {\n this.fd = null, cb(err || er);\n });\n }\n _construct(callback) {\n if (typeof this.fd === \"number\") {\n callback();\n return;\n }\n callback(), this.emit(\"open\", this.fd), this.emit(\"ready\");\n }\n _destroy(err, cb) {\n if (this.fd === null)\n return cb(err);\n if (this[kIoDone]) {\n this.once(kIoDone, () => this.#internalClose(err, cb));\n return;\n }\n this.#internalClose(err, cb);\n }\n [kIoDone] = !1;\n close(cb) {\n if (cb) {\n if (this.closed) {\n process.nextTick(cb);\n return;\n }\n this.on(\"close\", cb);\n }\n if (!this.autoClose)\n this.on(\"finish\", this.destroy);\n this.end();\n }\n write(chunk, encoding = this._writableState.defaultEncoding, cb) {\n if (this[writeStreamPathFastPathSymbol] = !1, typeof chunk === \"string\")\n chunk = Buffer.from(chunk, encoding);\n var native = this.pos === void 0;\n const callback = native \? (err, bytes) => {\n if (this[kIoDone] = !1, this.#handleWrite(err, bytes), this.emit(kIoDone), cb)\n !err \? cb() : cb(err);\n } : () => {\n };\n if (this[kIoDone] = !0, this._write)\n return this._write(chunk, encoding, callback);\n else\n return super.write(chunk, encoding, callback, native);\n }\n end(chunk, encoding, cb) {\n var native = this.pos === void 0;\n return super.end(chunk, encoding, cb, native);\n }\n _write = void 0;\n _writev = void 0;\n get pending() {\n return this.fd === null;\n }\n _destroy(err, cb) {\n this.close(err, cb);\n }\n #errorOrDestroy(err) {\n var {\n _readableState: r = { destroyed: !1, autoDestroy: !1 },\n _writableState: w = { destroyed: !1, autoDestroy: !1 }\n } = this;\n if (w\?.destroyed || r\?.destroyed)\n return this;\n if (r\?.autoDestroy || w\?.autoDestroy)\n this.destroy(err);\n else if (err)\n this.emit(\"error\", err);\n }\n});\nObject.defineProperties(fs, {\n createReadStream: {\n value: createReadStream\n },\n createWriteStream: {\n value: createWriteStream\n },\n ReadStream: {\n value: ReadStream\n },\n WriteStream: {\n value: WriteStream\n }\n});\nrealpath.native = realpath;\nrealpathSync.native = realpathSync;\nvar lazy_cpSync = null;\n$ = {\n Dirent,\n FSWatcher,\n ReadStream,\n Stats,\n WriteStream,\n _toUnixTimestamp,\n access,\n accessSync,\n appendFile,\n appendFileSync,\n chmod,\n chmodSync,\n chown,\n chownSync,\n close,\n closeSync,\n constants,\n copyFile,\n copyFileSync,\n cp,\n cpSync,\n createReadStream,\n createWriteStream,\n exists,\n existsSync,\n fchmod,\n fchmodSync,\n fchown,\n fchownSync,\n fstat,\n fstatSync,\n fsync,\n fsyncSync,\n ftruncate,\n ftruncateSync,\n futimes,\n futimesSync,\n lchmod,\n lchmodSync,\n lchown,\n lchownSync,\n link,\n linkSync,\n lstat,\n lstatSync,\n lutimes,\n lutimesSync,\n mkdir,\n mkdirSync,\n mkdtemp,\n mkdtempSync,\n open,\n openSync,\n promises,\n read,\n readFile,\n readFileSync,\n readSync,\n readdir,\n readdirSync,\n readlink,\n readlinkSync,\n readv,\n readvSync,\n realpath,\n realpathSync,\n rename,\n renameSync,\n rm,\n rmSync,\n rmdir,\n rmdirSync,\n stat,\n statSync,\n symlink,\n symlinkSync,\n truncate,\n truncateSync,\n unlink,\n unlinkSync,\n unwatchFile,\n utimes,\n utimesSync,\n watch,\n watchFile,\n write,\n writeFile,\n writeFileSync,\n writeSync,\n writev,\n writevSync,\n [Symbol.for(\"::bunternal::\")]: {\n ReadStreamClass,\n WriteStreamClass\n }\n};\nreturn $})\n"_s; // // diff --git a/src/jsc.zig b/src/jsc.zig index c33192f327..e5be263210 100644 --- a/src/jsc.zig +++ b/src/jsc.zig @@ -51,6 +51,7 @@ pub const Node = struct { pub usingnamespace @import("./bun.js/node/types.zig"); pub usingnamespace @import("./bun.js/node/node_fs.zig"); pub usingnamespace @import("./bun.js/node/node_fs_watcher.zig"); + pub usingnamespace @import("./bun.js/node/node_fs_stat_watcher.zig"); pub usingnamespace @import("./bun.js/node/node_fs_binding.zig"); pub usingnamespace @import("./bun.js/node/node_os.zig"); pub const fs = @import("./bun.js/node/node_fs_constant.zig"); diff --git a/test/js/node/watch/fs.watchFile.test.ts b/test/js/node/watch/fs.watchFile.test.ts new file mode 100644 index 0000000000..25cd0c8c54 --- /dev/null +++ b/test/js/node/watch/fs.watchFile.test.ts @@ -0,0 +1,121 @@ +import fs, { FSWatcher } from "node:fs"; +import path from "path"; +import { tempDirWithFiles, bunRun, bunRunAsScript } from "harness"; +import { pathToFileURL } from "bun"; + +import { describe, expect, test } from "bun:test"; +// Because macOS (and possibly other operating systems) can return a watcher +// before it is actually watching, we need to repeat the operation to avoid +// a race condition. +function repeat(fn: any) { + const interval = setInterval(fn, 20); + return interval; +} +const encodingFileName = `新建文夹件.txt`; +const testDir = tempDirWithFiles("watch", { + "watch.txt": "hello", + [encodingFileName]: "hello", +}); + +describe("fs.watchFile", () => { + test("zeroed stats if does not exist", async () => { + let entries: any = []; + fs.watchFile(path.join(testDir, "does-not-exist"), (curr, prev) => { + entries.push([curr, prev]); + }); + + await Bun.sleep(35); + + fs.unwatchFile(path.join(testDir, "does-not-exist")); + + expect(entries.length).toBe(1); + expect(entries[0][0].size).toBe(0); + expect(entries[0][0].mtimeMs).toBe(0); + expect(entries[0][1].size).toBe(0); + expect(entries[0][1].mtimeMs).toBe(0); + }); + test("it watches a file", async () => { + let entries: any = []; + fs.watchFile(path.join(testDir, "watch.txt"), { interval: 50 }, (curr, prev) => { + entries.push([curr, prev]); + }); + await Bun.sleep(100); + fs.writeFileSync(path.join(testDir, "watch.txt"), "hello2"); + await Bun.sleep(100); + + fs.unwatchFile(path.join(testDir, "watch.txt")); + + expect(entries.length).toBeGreaterThan(0); + + expect(entries[0][0].size).toBe(6); + expect(entries[0][1].size).toBe(5); + expect(entries[0][0].mtimeMs).toBeGreaterThan(entries[0][1].mtimeMs); + }); + test("unicode file name", async () => { + let entries: any = []; + fs.watchFile(path.join(testDir, encodingFileName), { interval: 50 }, (curr, prev) => { + entries.push([curr, prev]); + }); + await Bun.sleep(100); + fs.writeFileSync(path.join(testDir, encodingFileName), "hello2"); + await Bun.sleep(100); + + fs.unwatchFile(path.join(testDir, encodingFileName)); + + expect(entries.length).toBeGreaterThan(0); + + expect(entries[0][0].size).toBe(6); + expect(entries[0][1].size).toBe(5); + expect(entries[0][0].mtimeMs).toBeGreaterThan(entries[0][1].mtimeMs); + }); + test("bigint stats", async () => { + let entries: any = []; + fs.watchFile(path.join(testDir, encodingFileName), { interval: 50, bigint: true }, (curr, prev) => { + entries.push([curr, prev]); + }); + await Bun.sleep(100); + fs.writeFileSync(path.join(testDir, encodingFileName), "hello2"); + await Bun.sleep(100); + + fs.unwatchFile(path.join(testDir, encodingFileName)); + + expect(entries.length).toBeGreaterThan(0); + + expect(typeof entries[0][0].mtimeMs === "bigint").toBe(true); + }); + + test("StatWatcherScheduler stress test (1000 watchers with random times)", async () => { + const EventEmitter = require("events"); + let defaultMaxListeners = EventEmitter.defaultMaxListeners; + try { + EventEmitter.defaultMaxListeners = 1000; + // This tests StatWatcher's scheduler for add/remove race conditions, + // as the actual stat()ing is done on another thread using a specialized linked list implementation + // so we're testing that here, less so that stats will properly notify js, since that code is already known to be very threadsafe. + const set = new Set(); + const { promise, resolve } = Promise.withResolvers(); + for (let i = 0; i < 1000; i++) { + const file = path.join(testDir, i + ".txt"); + setTimeout(() => { + let first = true; + fs.watchFile(file, { interval: 500 }, (curr, prev) => { + set.add(file); + if (first) { + first = false; + setTimeout(() => { + fs.unwatchFile(file); + + if (set.size === 1000) resolve(); + }, Math.random() * 2000); + } + }); + }, Math.random() * 2000); + } + await promise; + + expect(set.size).toBe(1000); + } finally { + EventEmitter.defaultMaxListeners = defaultMaxListeners; + } + }, 20000); +}); From 1bd5b245b8a55353e60a2decad507ef8014be044 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Tue, 5 Sep 2023 16:52:57 -0800 Subject: [PATCH 8/9] Align `process.nextTick` execution order with Node (#4409) * Align `process.nextTick` execution order with Node * some tests * formatting * fixups * fix the test failures * simplify the logic here * push it up --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Co-authored-by: dave caruso --- src/bun.js/bindings/JSNextTickQueue.cpp | 97 ++ src/bun.js/bindings/JSNextTickQueue.h | 41 + src/bun.js/bindings/Process.cpp | 98 +- src/bun.js/bindings/Process.lut.h | 2 +- src/bun.js/bindings/ZigGlobalObject.cpp | 124 ++- src/bun.js/bindings/ZigGlobalObject.h | 5 + src/bun.js/bindings/bindings.cpp | 3 - .../bindings/webcore/DOMClientIsoSubspaces.h | 1 + src/bun.js/bindings/webcore/DOMIsoSubspaces.h | 2 +- src/bun.js/event_loop.zig | 15 +- src/bun_js.zig | 2 + src/js/builtins/ProcessObjectInternals.ts | 202 ++++ src/js/out/WebCoreJSBuiltins.cpp | 8 + src/js/out/WebCoreJSBuiltins.h | 11 + test/js/bun/spawn/exit-code.test.ts | 5 + test/js/node/process/process-nexttick.test.js | 950 +++++++++++++++++- .../readline/readline_promises.node.test.ts | 2 +- 17 files changed, 1481 insertions(+), 87 deletions(-) create mode 100644 src/bun.js/bindings/JSNextTickQueue.cpp create mode 100644 src/bun.js/bindings/JSNextTickQueue.h diff --git a/src/bun.js/bindings/JSNextTickQueue.cpp b/src/bun.js/bindings/JSNextTickQueue.cpp new file mode 100644 index 0000000000..8916ef6c82 --- /dev/null +++ b/src/bun.js/bindings/JSNextTickQueue.cpp @@ -0,0 +1,97 @@ +#include "root.h" + +#include "JavaScriptCore/JSCJSValueInlines.h" +#include "JavaScriptCore/JSInternalPromise.h" +#include "JavaScriptCore/LazyPropertyInlines.h" +#include +#include + +#include "JSNextTickQueue.h" +#include +#include +#include +#include "ExtendedDOMClientIsoSubspaces.h" +#include "ExtendedDOMIsoSubspaces.h" +#include "BunClientData.h" + +namespace Bun { + +using namespace JSC; + +const JSC::ClassInfo JSNextTickQueue::s_info = { "JSNextTickQueue"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSNextTickQueue) }; + +template +JSC::GCClient::IsoSubspace* JSNextTickQueue::subspaceFor(JSC::VM& vm) +{ + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForJSNextTickQueue.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSNextTickQueue = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForJSNextTickQueue.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForJSNextTickQueue = std::forward(space); }); +} + +JSNextTickQueue* JSNextTickQueue::create(VM& vm, Structure* structure) +{ + JSNextTickQueue* mod = new (NotNull, allocateCell(vm)) JSNextTickQueue(vm, structure); + return mod; +} +Structure* JSNextTickQueue::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) +{ + return Structure::create(vm, globalObject, prototype, TypeInfo(CellType, StructureFlags), info()); +} + +JSNextTickQueue::JSNextTickQueue(VM& vm, Structure* structure) + : Base(vm, structure) +{ +} + +void JSNextTickQueue::finishCreation(VM& vm) +{ + Base::finishCreation(vm); +} + +template +void JSNextTickQueue::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + auto* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); +} + +DEFINE_VISIT_CHILDREN(JSNextTickQueue); + +JSNextTickQueue* JSNextTickQueue::create(JSC::JSGlobalObject* globalObject) +{ + auto& vm = globalObject->vm(); + auto* obj = create(vm, createStructure(vm, globalObject, jsNull())); + obj->finishCreation(vm); + return obj; +} + +bool JSNextTickQueue::isEmpty() +{ + return !internalField(0) || internalField(0).get().asNumber() == 0; +} + +void JSNextTickQueue::drain(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + bool mustResetContext = false; + if (isEmpty()) { + vm.drainMicrotasks(); + mustResetContext = true; + } + + if (!isEmpty()) { + if (mustResetContext) { + globalObject->m_asyncContextData.get()->putInternalField(vm, 0, jsUndefined()); + } + auto* drainFn = internalField(2).get().getObject(); + + auto throwScope = DECLARE_THROW_SCOPE(vm); + MarkedArgumentBuffer drainArgs; + JSC::call(globalObject, drainFn, drainArgs, "Failed to drain next tick queue"_s); + } +} + +} \ No newline at end of file diff --git a/src/bun.js/bindings/JSNextTickQueue.h b/src/bun.js/bindings/JSNextTickQueue.h new file mode 100644 index 0000000000..c3bd228cc3 --- /dev/null +++ b/src/bun.js/bindings/JSNextTickQueue.h @@ -0,0 +1,41 @@ +#include "root.h" +#include "headers-handwritten.h" + +#include "JavaScriptCore/JSCInlines.h" +#include "BunClientData.h" +#include + +namespace Bun { +using namespace JSC; + +class JSNextTickQueue : public JSC::JSInternalFieldObjectImpl<3> { +public: + static constexpr unsigned numberOfInternalFields = 3; + using Base = JSC::JSInternalFieldObjectImpl<3>; + + template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); + + JS_EXPORT_PRIVATE static JSNextTickQueue* create(VM&, Structure*); + static JSNextTickQueue* create(JSC::JSGlobalObject* globalObject); + static JSNextTickQueue* createWithInitialValues(VM&, Structure*); + static Structure* createStructure(VM&, JSGlobalObject*, JSValue); + + static std::array initialValues() + { + return { { + jsNumber(-1), + jsUndefined(), + jsUndefined(), + } }; + } + + DECLARE_EXPORT_INFO; + DECLARE_VISIT_CHILDREN; + + JSNextTickQueue(JSC::VM&, JSC::Structure*); + void finishCreation(JSC::VM&); + + bool isEmpty(); + void drain(JSC::VM& vm, JSC::JSGlobalObject* globalObject); +}; +} \ No newline at end of file diff --git a/src/bun.js/bindings/Process.cpp b/src/bun.js/bindings/Process.cpp index 5c9c03dd2d..252d00075a 100644 --- a/src/bun.js/bindings/Process.cpp +++ b/src/bun.js/bindings/Process.cpp @@ -19,6 +19,7 @@ #include #include #include +#include "JSNextTickQueue.h" #pragma mark - Node.js Process @@ -160,63 +161,6 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionInternalGetWindowSize, return JSC::JSValue::encode(jsBoolean(true)); } -JSC_DEFINE_HOST_FUNCTION(Process_functionNextTick, - (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - JSC::VM& vm = globalObject->vm(); - auto argCount = callFrame->argumentCount(); - if (argCount == 0) { - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); - JSC::throwTypeError(globalObject, scope, "nextTick requires 1 argument (a function)"_s); - return JSC::JSValue::encode(JSC::JSValue {}); - } - - JSC::JSValue job = callFrame->uncheckedArgument(0); - - if (!job.isObject() || !job.getObject()->isCallable()) { - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); - JSC::throwTypeError(globalObject, scope, "nextTick expects a function"_s); - return JSC::JSValue::encode(JSC::JSValue {}); - } - - Zig::GlobalObject* global = JSC::jsCast(globalObject); - JSC::JSValue asyncContextValue = globalObject->m_asyncContextData.get()->getInternalField(0); - - switch (callFrame->argumentCount()) { - case 1: { - global->queueMicrotask(global->performMicrotaskFunction(), job, asyncContextValue, JSC::JSValue {}, JSC::JSValue {}); - break; - } - case 2: { - global->queueMicrotask(global->performMicrotaskFunction(), job, asyncContextValue, callFrame->uncheckedArgument(1), JSC::JSValue {}); - break; - } - case 3: { - global->queueMicrotask(global->performMicrotaskFunction(), job, asyncContextValue, callFrame->uncheckedArgument(1), callFrame->uncheckedArgument(2)); - break; - } - default: { - JSC::JSArray* args = JSC::constructEmptyArray(globalObject, nullptr, argCount - 1); - if (UNLIKELY(!args)) { - auto scope = DECLARE_THROW_SCOPE(vm); - throwVMError(globalObject, scope, createOutOfMemoryError(globalObject)); - return JSC::JSValue::encode(JSC::JSValue {}); - } - - for (unsigned i = 1; i < argCount; i++) { - args->putDirectIndex(globalObject, i - 1, callFrame->uncheckedArgument(i)); - } - - global->queueMicrotask( - global->performMicrotaskVariadicFunction(), job, args, asyncContextValue, JSC::JSValue {}); - - break; - } - } - - return JSC::JSValue::encode(jsUndefined()); -} - JSC_DECLARE_HOST_FUNCTION(Process_functionDlopen); JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen, (JSC::JSGlobalObject * globalObject_, JSC::CallFrame* callFrame)) @@ -279,7 +223,7 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen, } } - JSC::EncodedJSValue (*napi_register_module_v1)(JSC::JSGlobalObject* globalObject, + JSC::EncodedJSValue (*napi_register_module_v1)(JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue exports); napi_register_module_v1 = reinterpret_castargument(0); + Bun__reportUnhandledError(globalObject, JSValue::encode(arg0)); + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsFunctionDrainMicrotaskQueue, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + globalObject->vm().drainMicrotasks(); + return JSValue::encode(jsUndefined()); +} + +static JSValue constructProcessNextTickFn(VM& vm, JSObject* processObject) +{ + JSGlobalObject* lexicalGlobalObject = processObject->globalObject(); + Zig::GlobalObject* globalObject = jsCast(lexicalGlobalObject); + JSValue nextTickQueueObject; + if (!globalObject->m_nextTickQueue) { + Bun::JSNextTickQueue* queue = Bun::JSNextTickQueue::create(globalObject); + globalObject->m_nextTickQueue.set(vm, globalObject, queue); + nextTickQueueObject = queue; + } else { + nextTickQueueObject = jsCast(globalObject->m_nextTickQueue.get()); + } + + JSC::JSFunction* initializer = JSC::JSFunction::create(vm, processObjectInternalsInitializeNextTickQueueCodeGenerator(vm), lexicalGlobalObject); + JSC::MarkedArgumentBuffer args; + args.append(processObject); + args.append(nextTickQueueObject); + args.append(JSC::JSFunction::create(vm, globalObject, 1, String(), jsFunctionDrainMicrotaskQueue, ImplementationVisibility::Private)); + args.append(JSC::JSFunction::create(vm, globalObject, 1, String(), jsFunctionReportUncaughtException, ImplementationVisibility::Private)); + + return JSC::call(globalObject, initializer, JSC::getCallData(initializer), globalObject->globalThis(), args); +} + static JSValue constructFeatures(VM& vm, JSObject* processObject) { // { @@ -1742,7 +1722,7 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionKill, mainModule JSBuiltin ReadOnly|Builtin|Accessor|Function 0 memoryUsage constructMemoryUsage PropertyCallback moduleLoadList Process_stubEmptyArray PropertyCallback - nextTick Process_functionNextTick Function 1 + nextTick constructProcessNextTickFn PropertyCallback openStdin Process_functionOpenStdin Function 0 pid constructPid PropertyCallback platform constructPlatform PropertyCallback diff --git a/src/bun.js/bindings/Process.lut.h b/src/bun.js/bindings/Process.lut.h index 3f2d9255d0..4086fb19ee 100644 --- a/src/bun.js/bindings/Process.lut.h +++ b/src/bun.js/bindings/Process.lut.h @@ -179,7 +179,7 @@ static const struct HashTableValue processObjectTableValues[62] = { { "mainModule"_s, ((static_cast(PropertyAttribute::ReadOnly|PropertyAttribute::Builtin|PropertyAttribute::Accessor|PropertyAttribute::Function)) & ~PropertyAttribute::Function) | PropertyAttribute::Builtin, NoIntrinsic, { HashTableValue::BuiltinGeneratorType, processObjectMainModuleCodeGenerator, 0 } }, { "memoryUsage"_s, static_cast(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructMemoryUsage } }, { "moduleLoadList"_s, static_cast(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, Process_stubEmptyArray } }, - { "nextTick"_s, static_cast(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, Process_functionNextTick, 1 } }, + { "nextTick"_s, static_cast(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructProcessNextTickFn } }, { "openStdin"_s, static_cast(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, Process_functionOpenStdin, 0 } }, { "pid"_s, static_cast(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructPid } }, { "platform"_s, static_cast(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructPlatform } }, diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 7edbd42e6d..286084b4d6 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -130,6 +130,7 @@ #endif #include "BunObject.h" +#include "JSNextTickQueue.h" using namespace Bun; @@ -294,7 +295,7 @@ extern "C" void JSCInitialize(const char* envp[], size_t envc, void (*onCrash)(c } extern "C" void* Bun__getVM(); -extern "C" JSGlobalObject* Bun__getDefaultGlobal(); +extern "C" Zig::GlobalObject* Bun__getDefaultGlobal(); // Error.captureStackTrace may cause computeErrorInfo to be called twice // Rather than figure out the plumbing in JSC, we just skip the next call @@ -432,6 +433,44 @@ static String computeErrorInfo(JSC::VM& vm, Vector& stackTrace, unsi return computeErrorInfoWithoutPrepareStackTrace(vm, stackTrace, line, column, sourceURL, errorInstance); } +static void resetOnEachMicrotaskTick(JSC::VM& vm, Zig::GlobalObject* globalObject); + +static void checkIfNextTickWasCalledDuringMicrotask(JSC::VM& vm) +{ + auto* globalObject = Bun__getDefaultGlobal(); + if (auto nextTickQueueValue = globalObject->m_nextTickQueue.get()) { + auto* queue = jsCast(nextTickQueueValue); + resetOnEachMicrotaskTick(vm, globalObject); + queue->drain(vm, globalObject); + } +} + +static void cleanupAsyncHooksData(JSC::VM& vm) +{ + auto* globalObject = Bun__getDefaultGlobal(); + globalObject->m_asyncContextData.get()->putInternalField(vm, 0, jsUndefined()); + globalObject->asyncHooksNeedsCleanup = false; + if (!globalObject->m_nextTickQueue) { + vm.setOnEachMicrotaskTick(&checkIfNextTickWasCalledDuringMicrotask); + checkIfNextTickWasCalledDuringMicrotask(vm); + } else { + vm.setOnEachMicrotaskTick(nullptr); + } +} + +static void resetOnEachMicrotaskTick(JSC::VM& vm, Zig::GlobalObject* globalObject) +{ + if (globalObject->asyncHooksNeedsCleanup) { + vm.setOnEachMicrotaskTick(&cleanupAsyncHooksData); + } else { + if (globalObject->m_nextTickQueue) { + vm.setOnEachMicrotaskTick(nullptr); + } else { + vm.setOnEachMicrotaskTick(&checkIfNextTickWasCalledDuringMicrotask); + } + } +} + extern "C" JSC__JSGlobalObject* Zig__GlobalObject__create(void* console_client, int32_t executionContextId, bool miniMode, void* worker_ptr) { auto heapSize = miniMode ? JSC::HeapType::Small : JSC::HeapType::Large; @@ -479,6 +518,16 @@ extern "C" JSC__JSGlobalObject* Zig__GlobalObject__create(void* console_client, JSC::gcProtect(globalObject); + vm.setOnEachMicrotaskTick([](JSC::VM& vm) -> void { + auto* globalObject = Bun__getDefaultGlobal(); + if (auto nextTickQueue = globalObject->m_nextTickQueue.get()) { + resetOnEachMicrotaskTick(vm, globalObject); + Bun::JSNextTickQueue* queue = jsCast(nextTickQueue); + queue->drain(vm, globalObject); + return; + } + }); + vm.ref(); return globalObject; } @@ -1115,6 +1164,17 @@ JSC_DEFINE_HOST_FUNCTION(functionSetTimeout, return JSC::JSValue::encode(JSC::JSValue {}); } +#ifdef BUN_DEBUG + /** View the file name of the JS file that called this function + * from a debugger */ + SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm); + const char* fileName = sourceOrigin.string().utf8().data(); + static const char* lastFileName = nullptr; + if (lastFileName != fileName) { + lastFileName = fileName; + } +#endif + return Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(num), JSValue::encode(arguments)); } @@ -1166,6 +1226,17 @@ JSC_DEFINE_HOST_FUNCTION(functionSetInterval, return JSC::JSValue::encode(JSC::JSValue {}); } +#ifdef BUN_DEBUG + /** View the file name of the JS file that called this function + * from a debugger */ + SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm); + const char* fileName = sourceOrigin.string().utf8().data(); + static const char* lastFileName = nullptr; + if (lastFileName != fileName) { + lastFileName = fileName; + } +#endif + return Bun__Timer__setInterval(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(num), JSValue::encode(arguments)); } @@ -1182,6 +1253,17 @@ JSC_DEFINE_HOST_FUNCTION(functionClearInterval, JSC::JSValue num = callFrame->argument(0); +#ifdef BUN_DEBUG + /** View the file name of the JS file that called this function + * from a debugger */ + SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm); + const char* fileName = sourceOrigin.string().utf8().data(); + static const char* lastFileName = nullptr; + if (lastFileName != fileName) { + lastFileName = fileName; + } +#endif + return Bun__Timer__clearInterval(globalObject, JSC::JSValue::encode(num)); } @@ -1198,6 +1280,17 @@ JSC_DEFINE_HOST_FUNCTION(functionClearTimeout, JSC::JSValue num = callFrame->argument(0); +#ifdef BUN_DEBUG + /** View the file name of the JS file that called this function + * from a debugger */ + SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm); + const char* fileName = sourceOrigin.string().utf8().data(); + static const char* lastFileName = nullptr; + if (lastFileName != fileName) { + lastFileName = fileName; + } +#endif + return Bun__Timer__clearTimeout(globalObject, JSC::JSValue::encode(num)); } @@ -1393,12 +1486,6 @@ JSC_DEFINE_HOST_FUNCTION(functionCallback, (JSC::JSGlobalObject * globalObject, return JSC::JSValue::encode(JSC::call(globalObject, callback, callData, JSC::jsUndefined(), JSC::MarkedArgumentBuffer())); } -static void cleanupAsyncHooksData(JSC::VM& vm) -{ - vm.setOnEachMicrotaskTick(nullptr); - Bun__getDefaultGlobal()->m_asyncContextData.get()->putInternalField(vm, 0, jsUndefined()); -} - // $lazy("async_hooks").cleanupLater JSC_DEFINE_HOST_FUNCTION(asyncHooksCleanupLater, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { @@ -1406,7 +1493,9 @@ JSC_DEFINE_HOST_FUNCTION(asyncHooksCleanupLater, (JSC::JSGlobalObject * globalOb // - nobody else uses setOnEachMicrotaskTick // - this is called by js if we set async context in a way we may not clear it // - AsyncLocalStorage.prototype.run cleans up after itself and does not call this cb - globalObject->vm().setOnEachMicrotaskTick(&cleanupAsyncHooksData); + auto* global = jsCast(globalObject); + global->asyncHooksNeedsCleanup = true; + resetOnEachMicrotaskTick(globalObject->vm(), global); return JSC::JSValue::encode(JSC::jsUndefined()); } @@ -3955,6 +4044,23 @@ extern "C" bool JSC__JSGlobalObject__startRemoteInspector(JSC__JSGlobalObject* g #endif } +void GlobalObject::drainMicrotasks() +{ + auto& vm = this->vm(); + if (auto nextTickQueue = this->m_nextTickQueue.get()) { + Bun::JSNextTickQueue* queue = jsCast(nextTickQueue); + queue->drain(vm, this); + return; + } + + vm.drainMicrotasks(); +} + +extern "C" void JSC__JSGlobalObject__drainMicrotasks(Zig::GlobalObject* globalObject) +{ + globalObject->drainMicrotasks(); +} + template void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) { @@ -4008,6 +4114,8 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) visitor.append(thisObject->m_JSWebSocketSetterValue); visitor.append(thisObject->m_JSWorkerSetterValue); + visitor.append(thisObject->m_nextTickQueue); + thisObject->m_JSArrayBufferSinkClassStructure.visit(visitor); thisObject->m_JSBufferListClassStructure.visit(visitor); thisObject->m_JSFFIFunctionStructure.visit(visitor); diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index 029f90132c..e622016dec 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -296,6 +296,8 @@ public: return m_processEnvObject.getInitializedOnMainThread(this); } + void drainMicrotasks(); + void handleRejectedPromises(); void initGeneratedLazyClasses(); @@ -363,6 +365,8 @@ public: return func; } + bool asyncHooksNeedsCleanup = false; + /** * WARNING: You must update visitChildrenImpl() if you add a new field. * @@ -381,6 +385,7 @@ public: mutable WriteBarrier m_readableStreamToText; mutable WriteBarrier m_readableStreamToFormData; + mutable WriteBarrier m_nextTickQueue; mutable WriteBarrier m_BunCommonJSModuleValue; mutable WriteBarrier m_JSBroadcastChannelSetterValue; mutable WriteBarrier m_JSBufferSetterValue; diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index ca072f2b15..6413e0470c 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -2527,7 +2527,6 @@ JSC__JSInternalPromise* JSC__JSModuleLoader__loadAndEvaluateModule(JSC__JSGlobalObject* globalObject, const BunString* arg1) { - globalObject->vm().drainMicrotasks(); auto name = Bun::toWTFString(*arg1); name.impl()->ref(); @@ -2546,9 +2545,7 @@ JSC__JSModuleLoader__loadAndEvaluateModule(JSC__JSGlobalObject* globalObject, JSC::JSInternalPromise::rejectedPromise(globalObject, callFrame->argument(0))); }); - globalObject->vm().drainMicrotasks(); auto result = promise->then(globalObject, resolverFunction, rejecterFunction); - globalObject->vm().drainMicrotasks(); // if (promise->status(globalObject->vm()) == // JSC::JSPromise::Status::Fulfilled) { diff --git a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h index 4c09df6a50..e21a62bf8c 100644 --- a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h @@ -42,6 +42,7 @@ public: std::unique_ptr m_clientSubspaceForProcessObject; std::unique_ptr m_clientSubspaceForInternalModuleRegistry; std::unique_ptr m_clientSubspaceForBunInspectorConnection; + std::unique_ptr m_clientSubspaceForJSNextTickQueue; #include "ZigGeneratedClasses+DOMClientIsoSubspaces.h" /* --- bun --- */ diff --git a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h index 2b834cf3c7..806aa4454d 100644 --- a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h @@ -42,7 +42,7 @@ public: std::unique_ptr m_subspaceForProcessObject; std::unique_ptr m_subspaceForInternalModuleRegistry; std::unique_ptr m_subspaceForBunInspectorConnection; - + std::unique_ptr m_subspaceForJSNextTickQueue; #include "ZigGeneratedClasses+DOMIsoSubspaces.h" /*-- BUN --*/ diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig index 640a9276c9..f1367c2391 100644 --- a/src/bun.js/event_loop.zig +++ b/src/bun.js/event_loop.zig @@ -529,14 +529,14 @@ pub const EventLoop = struct { this.virtual_machine.event_loop_handle.?.tick(); } } - - pub fn drainMicrotasksWithVM(this: *EventLoop, vm: *JSC.VM) void { - vm.drainMicrotasks(); + extern fn JSC__JSGlobalObject__drainMicrotasks(*JSC.JSGlobalObject) void; + fn drainMicrotasksWithGlobal(this: *EventLoop, globalObject: *JSC.JSGlobalObject) void { + JSC__JSGlobalObject__drainMicrotasks(globalObject); this.drainDeferredTasks(); } pub fn drainMicrotasks(this: *EventLoop) void { - this.drainMicrotasksWithVM(this.global.vm()); + this.drainMicrotasksWithGlobal(this.global); } pub fn ensureAliveForOneTick(this: *EventLoop) void { @@ -666,7 +666,7 @@ pub const EventLoop = struct { } global_vm.releaseWeakRefs(); - this.drainMicrotasksWithVM(global_vm); + this.drainMicrotasksWithGlobal(global); } this.tasks.head = if (this.tasks.count == 0) 0 else this.tasks.head; @@ -824,13 +824,14 @@ pub const EventLoop = struct { this.processGCTimer(); - var global_vm = ctx.global.vm(); + var global = ctx.global; + var global_vm = global.vm(); while (true) { while (this.tickWithCount() > 0) : (this.global.handleRejectedPromises()) { this.tickConcurrent(); } else { global_vm.releaseWeakRefs(); - this.drainMicrotasksWithVM(global_vm); + this.drainMicrotasksWithGlobal(global); this.tickConcurrent(); if (this.tasks.count > 0) continue; } diff --git a/src/bun_js.zig b/src/bun_js.zig index 46942b8499..0605c57a15 100644 --- a/src/bun_js.zig +++ b/src/bun_js.zig @@ -241,6 +241,8 @@ pub const Run = struct { pub fn start(this: *Run) void { var vm = this.vm; vm.hot_reload = this.ctx.debug.hot_reload; + vm.onUnhandledRejection = &onUnhandledRejectionBeforeClose; + if (this.ctx.debug.hot_reload != .none) { JSC.HotReloader.enableHotModuleReloading(vm); } diff --git a/src/js/builtins/ProcessObjectInternals.ts b/src/js/builtins/ProcessObjectInternals.ts index e6c04c90fd..2bb8648df0 100644 --- a/src/js/builtins/ProcessObjectInternals.ts +++ b/src/js/builtins/ProcessObjectInternals.ts @@ -1,4 +1,5 @@ /* + * Copyright Joyent, Inc. and other Node contributors. * Copyright 2023 Codeblog Corp. All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -199,3 +200,204 @@ export function getStdinStream(fd) { return stream; } + +export function initializeNextTickQueue(process, nextTickQueue, drainMicrotasksFn, reportUncaughtExceptionFn) { + var queue; + var process; + var nextTickQueue = nextTickQueue; + var drainMicrotasks = drainMicrotasksFn; + var reportUncaughtException = reportUncaughtExceptionFn; + + function validateFunction(cb) { + if (typeof cb !== "function") { + const err = new TypeError(`The "callback" argument must be of type "function". Received type ${typeof cb}`); + err.code = "ERR_INVALID_ARG_TYPE"; + throw err; + } + } + + var setup; + setup = () => { + queue = (function createQueue() { + // Currently optimal queue size, tested on V8 6.0 - 6.6. Must be power of two. + const kSize = 2048; + const kMask = kSize - 1; + + // The FixedQueue is implemented as a singly-linked list of fixed-size + // circular buffers. It looks something like this: + // + // head tail + // | | + // v v + // +-----------+ <-----\ +-----------+ <------\ +-----------+ + // | [null] | \----- | next | \------- | next | + // +-----------+ +-----------+ +-----------+ + // | item | <-- bottom | item | <-- bottom | [empty] | + // | item | | item | | [empty] | + // | item | | item | | [empty] | + // | item | | item | | [empty] | + // | item | | item | bottom --> | item | + // | item | | item | | item | + // | ... | | ... | | ... | + // | item | | item | | item | + // | item | | item | | item | + // | [empty] | <-- top | item | | item | + // | [empty] | | item | | item | + // | [empty] | | [empty] | <-- top top --> | [empty] | + // +-----------+ +-----------+ +-----------+ + // + // Or, if there is only one circular buffer, it looks something + // like either of these: + // + // head tail head tail + // | | | | + // v v v v + // +-----------+ +-----------+ + // | [null] | | [null] | + // +-----------+ +-----------+ + // | [empty] | | item | + // | [empty] | | item | + // | item | <-- bottom top --> | [empty] | + // | item | | [empty] | + // | [empty] | <-- top bottom --> | item | + // | [empty] | | item | + // +-----------+ +-----------+ + // + // Adding a value means moving `top` forward by one, removing means + // moving `bottom` forward by one. After reaching the end, the queue + // wraps around. + // + // When `top === bottom` the current queue is empty and when + // `top + 1 === bottom` it's full. This wastes a single space of storage + // but allows much quicker checks. + + class FixedCircularBuffer { + constructor() { + this.bottom = 0; + this.top = 0; + this.list = $newArrayWithSize(kSize); + this.next = null; + } + + isEmpty() { + return this.top === this.bottom; + } + + isFull() { + return ((this.top + 1) & kMask) === this.bottom; + } + + push(data) { + this.list[this.top] = data; + this.top = (this.top + 1) & kMask; + } + + shift() { + var { list, bottom } = this; + const nextItem = list[bottom]; + if (nextItem === undefined) return null; + list[bottom] = undefined; + this.bottom = (bottom + 1) & kMask; + return nextItem; + } + } + + class FixedQueue { + constructor() { + this.head = this.tail = new FixedCircularBuffer(); + } + + isEmpty() { + return this.head.isEmpty(); + } + + push(data) { + if (this.head.isFull()) { + // Head is full: Creates a new queue, sets the old queue's `.next` to it, + // and sets it as the new main queue. + this.head = this.head.next = new FixedCircularBuffer(); + } + this.head.push(data); + } + + shift() { + const tail = this.tail; + const next = tail.shift(); + if (tail.isEmpty() && tail.next !== null) { + // If there is another queue, it forms the new tail. + this.tail = tail.next; + tail.next = null; + } + return next; + } + } + + return new FixedQueue(); + })(); + + function processTicksAndRejections() { + var tock; + do { + while ((tock = queue.shift()) !== null) { + var callback = tock.callback; + var args = tock.args; + var frame = tock.frame; + var restore = $getInternalField($asyncContext, 0); + $putInternalField($asyncContext, 0, frame); + try { + if (args === undefined) { + callback(); + } else { + switch (args.length) { + case 1: + callback(args[0]); + break; + case 2: + callback(args[0], args[1]); + break; + case 3: + callback(args[0], args[1], args[2]); + break; + case 4: + callback(args[0], args[1], args[2], args[3]); + break; + default: + callback(...args); + break; + } + } + } catch (e) { + reportUncaughtException(e); + } finally { + $putInternalField($asyncContext, 0, restore); + } + } + + drainMicrotasks(); + } while (!queue.isEmpty()); + } + + $putInternalField(nextTickQueue, 0, 0); + $putInternalField(nextTickQueue, 1, queue); + $putInternalField(nextTickQueue, 2, processTicksAndRejections); + setup = undefined; + }; + + function nextTick(cb, args) { + validateFunction(cb); + if (setup) { + setup(); + process = globalThis.process; + } + if (process._exiting) return; + + queue.push({ + callback: cb, + args: $argumentCount() > 1 ? Array.prototype.slice.$call(arguments, 1) : undefined, + frame: $getInternalField($asyncContext, 0), + }); + $putInternalField(nextTickQueue, 0, 1); + } + + return nextTick; +} diff --git a/src/js/out/WebCoreJSBuiltins.cpp b/src/js/out/WebCoreJSBuiltins.cpp index 1003fd522d..d767a3c397 100644 --- a/src/js/out/WebCoreJSBuiltins.cpp +++ b/src/js/out/WebCoreJSBuiltins.cpp @@ -658,6 +658,14 @@ const int s_processObjectInternalsGetStdinStreamCodeLength = 1386; static const JSC::Intrinsic s_processObjectInternalsGetStdinStreamCodeIntrinsic = JSC::NoIntrinsic; const char* const s_processObjectInternalsGetStdinStreamCode = "(function (fd){\"use strict\";var reader,readerRef;function ref(){reader\?\?=@Bun.stdin.stream().getReader(),readerRef\?\?=setInterval(()=>{},1<<30)}function unref(){if(readerRef)clearInterval(readerRef),readerRef=@undefined;if(reader)reader.cancel(),reader=@undefined}const stream=new((@getInternalField(@internalModuleRegistry,44))||(@createInternalModuleById(44))).ReadStream(fd),originalOn=stream.on;stream.on=function(event,listener){if(event===\"readable\")ref();return originalOn.call(this,event,listener)},stream.fd=fd;const originalPause=stream.pause;stream.pause=function(){return unref(),originalPause.call(this)};const originalResume=stream.resume;stream.resume=function(){return ref(),originalResume.call(this)};async function internalRead(stream2){try{var done,value;const read=reader\?.readMany();if(@isPromise(read))({done,value}=await read);else({done,value}=read);if(!done){stream2.push(value[0]);const length=value.length;for(let i=1;i{ref(),stream._undestroy()}),stream._readableState.reading=!1,stream.on(\"pause\",()=>{process.nextTick(()=>{if(!stream.readableFlowing)stream._readableState.reading=!1})}),stream.on(\"close\",()=>{process.nextTick(()=>{stream.destroy(),unref()})}),stream})\n"; +// initializeNextTickQueue +const JSC::ConstructAbility s_processObjectInternalsInitializeNextTickQueueCodeConstructAbility = JSC::ConstructAbility::CannotConstruct; +const JSC::ConstructorKind s_processObjectInternalsInitializeNextTickQueueCodeConstructorKind = JSC::ConstructorKind::None; +const JSC::ImplementationVisibility s_processObjectInternalsInitializeNextTickQueueCodeImplementationVisibility = JSC::ImplementationVisibility::Public; +const int s_processObjectInternalsInitializeNextTickQueueCodeLength = 2336; +static const JSC::Intrinsic s_processObjectInternalsInitializeNextTickQueueCodeIntrinsic = JSC::NoIntrinsic; +const char* const s_processObjectInternalsInitializeNextTickQueueCode = "(function (process,nextTickQueue,drainMicrotasksFn,reportUncaughtExceptionFn){\"use strict\";var queue,process,nextTickQueue=nextTickQueue,drainMicrotasks=drainMicrotasksFn,reportUncaughtException=reportUncaughtExceptionFn;function validateFunction(cb){if(typeof cb!==\"function\"){const err=@makeTypeError(`The \"callback\" argument must be of type \"function\". Received type ${typeof cb}`);throw err.code=\"ERR_INVALID_ARG_TYPE\",err}}var setup=()=>{queue=function createQueue(){class FixedCircularBuffer{constructor(){this.bottom=0,this.top=0,this.list=@newArrayWithSize(2048),this.next=null}isEmpty(){return this.top===this.bottom}isFull(){return(this.top+1&2047)===this.bottom}push(data){this.list[this.top]=data,this.top=this.top+1&2047}shift(){var{list,bottom}=this;const nextItem=list[bottom];if(nextItem===@undefined)return null;return list[bottom]=@undefined,this.bottom=bottom+1&2047,nextItem}}class FixedQueue{constructor(){this.head=this.tail=new FixedCircularBuffer}isEmpty(){return this.head.isEmpty()}push(data){if(this.head.isFull())this.head=this.head.next=new FixedCircularBuffer;this.head.push(data)}shift(){const tail=this.tail,next=tail.shift();if(tail.isEmpty()&&tail.next!==null)this.tail=tail.next,tail.next=null;return next}}return new FixedQueue}();function processTicksAndRejections(){var tock;do{while((tock=queue.shift())!==null){var{callback,args,frame}=tock,restore=@getInternalField(@asyncContext,0);@putInternalField(@asyncContext,0,frame);try{if(args===@undefined)callback();else switch(args.length){case 1:callback(args[0]);break;case 2:callback(args[0],args[1]);break;case 3:callback(args[0],args[1],args[2]);break;case 4:callback(args[0],args[1],args[2],args[3]);break;default:callback(...args);break}}catch(e){reportUncaughtException(e)}finally{@putInternalField(@asyncContext,0,restore)}}drainMicrotasks()}while(!queue.isEmpty())}@putInternalField(nextTickQueue,0,0),@putInternalField(nextTickQueue,1,queue),@putInternalField(nextTickQueue,2,processTicksAndRejections),setup=@undefined};function nextTick(cb,args){if(validateFunction(cb),setup)setup(),process=globalThis.process;if(process._exiting)return;queue.push({callback:cb,args:@argumentCount()>1\?@Array.prototype.slice.@call(arguments,1):@undefined,frame:@getInternalField(@asyncContext,0)}),@putInternalField(nextTickQueue,0,1)}return nextTick})\n"; + #define DEFINE_BUILTIN_GENERATOR(codeName, functionName, overriddenName, argumentCount) \ JSC::FunctionExecutable* codeName##Generator(JSC::VM& vm) \ {\ diff --git a/src/js/out/WebCoreJSBuiltins.h b/src/js/out/WebCoreJSBuiltins.h index cf28fa82a2..4fc91dbd9a 100644 --- a/src/js/out/WebCoreJSBuiltins.h +++ b/src/js/out/WebCoreJSBuiltins.h @@ -1179,20 +1179,31 @@ extern const JSC::ConstructAbility s_processObjectInternalsGetStdinStreamCodeCon extern const JSC::ConstructorKind s_processObjectInternalsGetStdinStreamCodeConstructorKind; extern const JSC::ImplementationVisibility s_processObjectInternalsGetStdinStreamCodeImplementationVisibility; +// initializeNextTickQueue +#define WEBCORE_BUILTIN_PROCESSOBJECTINTERNALS_INITIALIZENEXTTICKQUEUE 1 +extern const char* const s_processObjectInternalsInitializeNextTickQueueCode; +extern const int s_processObjectInternalsInitializeNextTickQueueCodeLength; +extern const JSC::ConstructAbility s_processObjectInternalsInitializeNextTickQueueCodeConstructAbility; +extern const JSC::ConstructorKind s_processObjectInternalsInitializeNextTickQueueCodeConstructorKind; +extern const JSC::ImplementationVisibility s_processObjectInternalsInitializeNextTickQueueCodeImplementationVisibility; + #define WEBCORE_FOREACH_PROCESSOBJECTINTERNALS_BUILTIN_DATA(macro) \ macro(binding, processObjectInternalsBinding, 1) \ macro(getStdioWriteStream, processObjectInternalsGetStdioWriteStream, 1) \ macro(getStdinStream, processObjectInternalsGetStdinStream, 1) \ + macro(initializeNextTickQueue, processObjectInternalsInitializeNextTickQueue, 4) \ #define WEBCORE_FOREACH_PROCESSOBJECTINTERNALS_BUILTIN_CODE(macro) \ macro(processObjectInternalsBindingCode, binding, ASCIILiteral(), s_processObjectInternalsBindingCodeLength) \ macro(processObjectInternalsGetStdioWriteStreamCode, getStdioWriteStream, ASCIILiteral(), s_processObjectInternalsGetStdioWriteStreamCodeLength) \ macro(processObjectInternalsGetStdinStreamCode, getStdinStream, ASCIILiteral(), s_processObjectInternalsGetStdinStreamCodeLength) \ + macro(processObjectInternalsInitializeNextTickQueueCode, initializeNextTickQueue, ASCIILiteral(), s_processObjectInternalsInitializeNextTickQueueCodeLength) \ #define WEBCORE_FOREACH_PROCESSOBJECTINTERNALS_BUILTIN_FUNCTION_NAME(macro) \ macro(binding) \ macro(getStdioWriteStream) \ macro(getStdinStream) \ + macro(initializeNextTickQueue) \ #define DECLARE_BUILTIN_GENERATOR(codeName, functionName, overriddenName, argumentCount) \ JSC::FunctionExecutable* codeName##Generator(JSC::VM&); diff --git a/test/js/bun/spawn/exit-code.test.ts b/test/js/bun/spawn/exit-code.test.ts index cda76a395f..1b97179f62 100644 --- a/test/js/bun/spawn/exit-code.test.ts +++ b/test/js/bun/spawn/exit-code.test.ts @@ -17,6 +17,11 @@ it("unhandled promise rejection reports exit code 1", () => { expect(exitCode).toBe(1); }); +it("handled promise rejection reports exit code 0", () => { + const { exitCode } = spawnSync([bunExe(), import.meta.dir + "/exit-code-handled-throw.js"]); + expect(exitCode).toBe(1); +}); + it("process.exit(0) works", () => { const { exitCode } = spawnSync([bunExe(), import.meta.dir + "/exit-code-0.js"]); expect(exitCode).toBe(0); diff --git a/test/js/node/process/process-nexttick.test.js b/test/js/node/process/process-nexttick.test.js index becf3c236b..6f1eee6ba4 100644 --- a/test/js/node/process/process-nexttick.test.js +++ b/test/js/node/process/process-nexttick.test.js @@ -1,4 +1,8 @@ -import { it } from "bun:test"; +// Running this file in jest/vitest does not work as expected. Jest & Vitest +// mess with timers, producing unreliable results. You must manually test this +// in Node. +import { test, expect, it } from "bun:test"; +const isBun = !!process.versions.bun; it("process.nextTick", async () => { // You can verify this test is correct by copy pasting this into a browser's console and checking it doesn't throw an error. @@ -52,25 +56,35 @@ it("process.nextTick", async () => { }); { - var passed = false; + let passed = false; try { queueMicrotask(1234); } catch (exception) { - passed = exception instanceof TypeError; + if (isBun) { + passed = exception instanceof TypeError; + } else { + // Node.js throws a non-TypeError TypeError + passed = exception instanceof Error && exception.name === "TypeError"; + } } - if (!passed) throw new Error("queueMicrotask should throw a TypeError if the argument is not a function"); + if (!passed) throw new Error("1: queueMicrotask should throw a TypeError if the argument is not a function"); } { - var passed = false; + let passed = false; try { queueMicrotask(); } catch (exception) { - passed = exception instanceof TypeError; + if (isBun) { + passed = exception instanceof TypeError; + } else { + // Node.js throws a non-TypeError TypeError + passed = exception instanceof Error && exception.name === "TypeError"; + } } - if (!passed) throw new Error("queueMicrotask should throw a TypeError if the argument is empty"); + if (!passed) throw new Error("2: queueMicrotask should throw a TypeError if the argument is empty"); } }); @@ -97,3 +111,925 @@ it("process.nextTick 5 args", async () => { }, ...args); }); }); + +it("process.nextTick runs after queueMicrotask", async () => { + var resolve; + var promise = new Promise(_resolve => { + resolve = _resolve; + }); + + const order = []; + var nextTickI = 0; + var microtaskI = 0; + var remaining = 400; + var runs = []; + for (let i = 0; i < 100; i++) { + queueMicrotask(() => { + runs.push(queueMicrotask.name); + order.push("queueMicrotask " + microtaskI++); + if (--remaining === 0) resolve(order); + }); + process.nextTick(() => { + runs.push(process.nextTick.name); + order.push("process.nextTick " + nextTickI++); + if (--remaining === 0) resolve(order); + }); + } + + for (let i = 0; i < 100; i++) { + queueMicrotask(() => { + runs.push(queueMicrotask.name); + order.push("queueMicrotask " + microtaskI++); + if (--remaining === 0) resolve(order); + }); + } + + for (let i = 0; i < 100; i++) { + process.nextTick(() => { + runs.push(process.nextTick.name); + order.push("process.nextTick " + nextTickI++); + if (--remaining === 0) resolve(order); + }); + } + + await promise; + expect({ + order, + runs, + }).toEqual({ + "order": [ + "process.nextTick 0", + "process.nextTick 1", + "process.nextTick 2", + "process.nextTick 3", + "process.nextTick 4", + "process.nextTick 5", + "process.nextTick 6", + "process.nextTick 7", + "process.nextTick 8", + "process.nextTick 9", + "process.nextTick 10", + "process.nextTick 11", + "process.nextTick 12", + "process.nextTick 13", + "process.nextTick 14", + "process.nextTick 15", + "process.nextTick 16", + "process.nextTick 17", + "process.nextTick 18", + "process.nextTick 19", + "process.nextTick 20", + "process.nextTick 21", + "process.nextTick 22", + "process.nextTick 23", + "process.nextTick 24", + "process.nextTick 25", + "process.nextTick 26", + "process.nextTick 27", + "process.nextTick 28", + "process.nextTick 29", + "process.nextTick 30", + "process.nextTick 31", + "process.nextTick 32", + "process.nextTick 33", + "process.nextTick 34", + "process.nextTick 35", + "process.nextTick 36", + "process.nextTick 37", + "process.nextTick 38", + "process.nextTick 39", + "process.nextTick 40", + "process.nextTick 41", + "process.nextTick 42", + "process.nextTick 43", + "process.nextTick 44", + "process.nextTick 45", + "process.nextTick 46", + "process.nextTick 47", + "process.nextTick 48", + "process.nextTick 49", + "process.nextTick 50", + "process.nextTick 51", + "process.nextTick 52", + "process.nextTick 53", + "process.nextTick 54", + "process.nextTick 55", + "process.nextTick 56", + "process.nextTick 57", + "process.nextTick 58", + "process.nextTick 59", + "process.nextTick 60", + "process.nextTick 61", + "process.nextTick 62", + "process.nextTick 63", + "process.nextTick 64", + "process.nextTick 65", + "process.nextTick 66", + "process.nextTick 67", + "process.nextTick 68", + "process.nextTick 69", + "process.nextTick 70", + "process.nextTick 71", + "process.nextTick 72", + "process.nextTick 73", + "process.nextTick 74", + "process.nextTick 75", + "process.nextTick 76", + "process.nextTick 77", + "process.nextTick 78", + "process.nextTick 79", + "process.nextTick 80", + "process.nextTick 81", + "process.nextTick 82", + "process.nextTick 83", + "process.nextTick 84", + "process.nextTick 85", + "process.nextTick 86", + "process.nextTick 87", + "process.nextTick 88", + "process.nextTick 89", + "process.nextTick 90", + "process.nextTick 91", + "process.nextTick 92", + "process.nextTick 93", + "process.nextTick 94", + "process.nextTick 95", + "process.nextTick 96", + "process.nextTick 97", + "process.nextTick 98", + "process.nextTick 99", + "process.nextTick 100", + "process.nextTick 101", + "process.nextTick 102", + "process.nextTick 103", + "process.nextTick 104", + "process.nextTick 105", + "process.nextTick 106", + "process.nextTick 107", + "process.nextTick 108", + "process.nextTick 109", + "process.nextTick 110", + "process.nextTick 111", + "process.nextTick 112", + "process.nextTick 113", + "process.nextTick 114", + "process.nextTick 115", + "process.nextTick 116", + "process.nextTick 117", + "process.nextTick 118", + "process.nextTick 119", + "process.nextTick 120", + "process.nextTick 121", + "process.nextTick 122", + "process.nextTick 123", + "process.nextTick 124", + "process.nextTick 125", + "process.nextTick 126", + "process.nextTick 127", + "process.nextTick 128", + "process.nextTick 129", + "process.nextTick 130", + "process.nextTick 131", + "process.nextTick 132", + "process.nextTick 133", + "process.nextTick 134", + "process.nextTick 135", + "process.nextTick 136", + "process.nextTick 137", + "process.nextTick 138", + "process.nextTick 139", + "process.nextTick 140", + "process.nextTick 141", + "process.nextTick 142", + "process.nextTick 143", + "process.nextTick 144", + "process.nextTick 145", + "process.nextTick 146", + "process.nextTick 147", + "process.nextTick 148", + "process.nextTick 149", + "process.nextTick 150", + "process.nextTick 151", + "process.nextTick 152", + "process.nextTick 153", + "process.nextTick 154", + "process.nextTick 155", + "process.nextTick 156", + "process.nextTick 157", + "process.nextTick 158", + "process.nextTick 159", + "process.nextTick 160", + "process.nextTick 161", + "process.nextTick 162", + "process.nextTick 163", + "process.nextTick 164", + "process.nextTick 165", + "process.nextTick 166", + "process.nextTick 167", + "process.nextTick 168", + "process.nextTick 169", + "process.nextTick 170", + "process.nextTick 171", + "process.nextTick 172", + "process.nextTick 173", + "process.nextTick 174", + "process.nextTick 175", + "process.nextTick 176", + "process.nextTick 177", + "process.nextTick 178", + "process.nextTick 179", + "process.nextTick 180", + "process.nextTick 181", + "process.nextTick 182", + "process.nextTick 183", + "process.nextTick 184", + "process.nextTick 185", + "process.nextTick 186", + "process.nextTick 187", + "process.nextTick 188", + "process.nextTick 189", + "process.nextTick 190", + "process.nextTick 191", + "process.nextTick 192", + "process.nextTick 193", + "process.nextTick 194", + "process.nextTick 195", + "process.nextTick 196", + "process.nextTick 197", + "process.nextTick 198", + "process.nextTick 199", + "queueMicrotask 0", + "queueMicrotask 1", + "queueMicrotask 2", + "queueMicrotask 3", + "queueMicrotask 4", + "queueMicrotask 5", + "queueMicrotask 6", + "queueMicrotask 7", + "queueMicrotask 8", + "queueMicrotask 9", + "queueMicrotask 10", + "queueMicrotask 11", + "queueMicrotask 12", + "queueMicrotask 13", + "queueMicrotask 14", + "queueMicrotask 15", + "queueMicrotask 16", + "queueMicrotask 17", + "queueMicrotask 18", + "queueMicrotask 19", + "queueMicrotask 20", + "queueMicrotask 21", + "queueMicrotask 22", + "queueMicrotask 23", + "queueMicrotask 24", + "queueMicrotask 25", + "queueMicrotask 26", + "queueMicrotask 27", + "queueMicrotask 28", + "queueMicrotask 29", + "queueMicrotask 30", + "queueMicrotask 31", + "queueMicrotask 32", + "queueMicrotask 33", + "queueMicrotask 34", + "queueMicrotask 35", + "queueMicrotask 36", + "queueMicrotask 37", + "queueMicrotask 38", + "queueMicrotask 39", + "queueMicrotask 40", + "queueMicrotask 41", + "queueMicrotask 42", + "queueMicrotask 43", + "queueMicrotask 44", + "queueMicrotask 45", + "queueMicrotask 46", + "queueMicrotask 47", + "queueMicrotask 48", + "queueMicrotask 49", + "queueMicrotask 50", + "queueMicrotask 51", + "queueMicrotask 52", + "queueMicrotask 53", + "queueMicrotask 54", + "queueMicrotask 55", + "queueMicrotask 56", + "queueMicrotask 57", + "queueMicrotask 58", + "queueMicrotask 59", + "queueMicrotask 60", + "queueMicrotask 61", + "queueMicrotask 62", + "queueMicrotask 63", + "queueMicrotask 64", + "queueMicrotask 65", + "queueMicrotask 66", + "queueMicrotask 67", + "queueMicrotask 68", + "queueMicrotask 69", + "queueMicrotask 70", + "queueMicrotask 71", + "queueMicrotask 72", + "queueMicrotask 73", + "queueMicrotask 74", + "queueMicrotask 75", + "queueMicrotask 76", + "queueMicrotask 77", + "queueMicrotask 78", + "queueMicrotask 79", + "queueMicrotask 80", + "queueMicrotask 81", + "queueMicrotask 82", + "queueMicrotask 83", + "queueMicrotask 84", + "queueMicrotask 85", + "queueMicrotask 86", + "queueMicrotask 87", + "queueMicrotask 88", + "queueMicrotask 89", + "queueMicrotask 90", + "queueMicrotask 91", + "queueMicrotask 92", + "queueMicrotask 93", + "queueMicrotask 94", + "queueMicrotask 95", + "queueMicrotask 96", + "queueMicrotask 97", + "queueMicrotask 98", + "queueMicrotask 99", + "queueMicrotask 100", + "queueMicrotask 101", + "queueMicrotask 102", + "queueMicrotask 103", + "queueMicrotask 104", + "queueMicrotask 105", + "queueMicrotask 106", + "queueMicrotask 107", + "queueMicrotask 108", + "queueMicrotask 109", + "queueMicrotask 110", + "queueMicrotask 111", + "queueMicrotask 112", + "queueMicrotask 113", + "queueMicrotask 114", + "queueMicrotask 115", + "queueMicrotask 116", + "queueMicrotask 117", + "queueMicrotask 118", + "queueMicrotask 119", + "queueMicrotask 120", + "queueMicrotask 121", + "queueMicrotask 122", + "queueMicrotask 123", + "queueMicrotask 124", + "queueMicrotask 125", + "queueMicrotask 126", + "queueMicrotask 127", + "queueMicrotask 128", + "queueMicrotask 129", + "queueMicrotask 130", + "queueMicrotask 131", + "queueMicrotask 132", + "queueMicrotask 133", + "queueMicrotask 134", + "queueMicrotask 135", + "queueMicrotask 136", + "queueMicrotask 137", + "queueMicrotask 138", + "queueMicrotask 139", + "queueMicrotask 140", + "queueMicrotask 141", + "queueMicrotask 142", + "queueMicrotask 143", + "queueMicrotask 144", + "queueMicrotask 145", + "queueMicrotask 146", + "queueMicrotask 147", + "queueMicrotask 148", + "queueMicrotask 149", + "queueMicrotask 150", + "queueMicrotask 151", + "queueMicrotask 152", + "queueMicrotask 153", + "queueMicrotask 154", + "queueMicrotask 155", + "queueMicrotask 156", + "queueMicrotask 157", + "queueMicrotask 158", + "queueMicrotask 159", + "queueMicrotask 160", + "queueMicrotask 161", + "queueMicrotask 162", + "queueMicrotask 163", + "queueMicrotask 164", + "queueMicrotask 165", + "queueMicrotask 166", + "queueMicrotask 167", + "queueMicrotask 168", + "queueMicrotask 169", + "queueMicrotask 170", + "queueMicrotask 171", + "queueMicrotask 172", + "queueMicrotask 173", + "queueMicrotask 174", + "queueMicrotask 175", + "queueMicrotask 176", + "queueMicrotask 177", + "queueMicrotask 178", + "queueMicrotask 179", + "queueMicrotask 180", + "queueMicrotask 181", + "queueMicrotask 182", + "queueMicrotask 183", + "queueMicrotask 184", + "queueMicrotask 185", + "queueMicrotask 186", + "queueMicrotask 187", + "queueMicrotask 188", + "queueMicrotask 189", + "queueMicrotask 190", + "queueMicrotask 191", + "queueMicrotask 192", + "queueMicrotask 193", + "queueMicrotask 194", + "queueMicrotask 195", + "queueMicrotask 196", + "queueMicrotask 197", + "queueMicrotask 198", + "queueMicrotask 199", + ], + "runs": [ + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "nextTick", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + "queueMicrotask", + ], + }); +}); + +it("process.nextTick can be called 100,000 times", async () => { + var county = 0; + function ticky() { + county++; + } + for (let i = 0; i < 100_000; i++) { + process.nextTick(ticky); + } + + await 1; + expect(county).toBe(100_000); +}); + +it("process.nextTick works more than once", async () => { + var county = 0; + function ticky() { + county++; + } + for (let i = 0; i < 1000; i++) { + process.nextTick(ticky); + await 1; + } + expect(county).toBe(1); + await new Promise(resolve => setTimeout(resolve, 0)); + expect(county).toBe(1000); +}); + +// `enterWith` is problematic because it and `nextTick` both rely on +// JSC's `global.onEachMicrotaskTick`, and this test is designed to +// cover what happens when both are active +it("process.nextTick and AsyncLocalStorage.enterWith don't conflict", async () => { + const AsyncLocalStorage = require("async_hooks").AsyncLocalStorage; + const t = require("timers/promises"); + const storage = new AsyncLocalStorage(); + + let call1 = false; + let call2 = false; + + process.nextTick(() => (call1 = true)); + + const p = Promise.withResolvers(); + const p2 = p.promise.then(() => { + return storage.getStore(); // should not leak "hello" + }); + const promise = Promise.resolve().then(async () => { + storage.enterWith("hello"); + process.nextTick(() => (call2 = true)); + + let didCall = false; + let value = null; + function ticky() { + didCall = true; + value = storage.getStore(); + } + process.nextTick(ticky); + await t.setTimeout(1); + expect(didCall).toBe(true); + expect(value).toBe("hello"); + expect(storage.getStore()).toBe("hello"); + }); + + expect(storage.getStore()).toBe(undefined); + await promise; + p.resolve(); + expect(await p2).toBe(undefined); + + expect(call1).toBe(true); + expect(call2).toBe(true); +}); diff --git a/test/js/node/readline/readline_promises.node.test.ts b/test/js/node/readline/readline_promises.node.test.ts index a46fe841fc..a6c2fcef28 100644 --- a/test/js/node/readline/readline_promises.node.test.ts +++ b/test/js/node/readline/readline_promises.node.test.ts @@ -40,7 +40,7 @@ describe("readline/promises.createInterface()", () => { rli.on("line", mustNotCall()); fi.emit("data", "\t"); - process.nextTick(() => { + queueMicrotask(() => { expect(fi.output).toMatch(/^Tab completion error/); rli.close(); done(); From 70a5cfe9087c9836cef06ee3560f6111ed2fe18e Mon Sep 17 00:00:00 2001 From: Dylan Conway <35280289+dylan-conway@users.noreply.github.com> Date: Tue, 5 Sep 2023 17:53:31 -0700 Subject: [PATCH 9/9] fix text decode trim (#4495) * remove trim * separate function * a test * trim when `stream` is true --------- Co-authored-by: Jarred Sumner --- src/bun.js/webcore/encoding.zig | 19 +++- src/string_immutable.zig | 115 ++++++++++++++++++++++ test/js/web/encoding/text-decoder.test.js | 14 +++ 3 files changed, 143 insertions(+), 5 deletions(-) diff --git a/src/bun.js/webcore/encoding.zig b/src/bun.js/webcore/encoding.zig index 6f31fef82f..53933fdb77 100644 --- a/src/bun.js/webcore/encoding.zig +++ b/src/bun.js/webcore/encoding.zig @@ -592,14 +592,22 @@ pub const TextDecoder = struct { return JSValue.zero; }; - return this.decodeSlice(globalThis, array_buffer.slice()); + if (arguments.len > 1 and arguments[1].isObject()) { + if (arguments[1].get(globalThis, "stream")) |stream| { + if (stream.toBoolean()) { + return this.decodeSlice(globalThis, array_buffer.slice(), true); + } + } + } + + return this.decodeSlice(globalThis, array_buffer.slice(), false); } pub fn decodeWithoutTypeChecks(this: *TextDecoder, globalThis: *JSC.JSGlobalObject, uint8array: *JSC.JSUint8Array) callconv(.C) JSValue { - return this.decodeSlice(globalThis, uint8array.slice()); + return this.decodeSlice(globalThis, uint8array.slice(), false); } - fn decodeSlice(this: *TextDecoder, globalThis: *JSC.JSGlobalObject, buffer_slice: []const u8) JSValue { + fn decodeSlice(this: *TextDecoder, globalThis: *JSC.JSGlobalObject, buffer_slice: []const u8, comptime stream: bool) JSValue { switch (this.encoding) { EncodingLabel.latin1 => { if (strings.isAllASCII(buffer_slice)) { @@ -620,8 +628,9 @@ pub const TextDecoder = struct { return ZigString.toExternalU16(bytes.ptr, out.written, globalThis); }, EncodingLabel.@"UTF-8" => { + const toUTF16 = if (stream) strings.toUTF16Alloc else strings.toUTF16AllocNoTrim; if (this.fatal) { - if (strings.toUTF16Alloc(default_allocator, buffer_slice, true)) |result_| { + if (toUTF16(default_allocator, buffer_slice, true)) |result_| { if (result_) |result| { return ZigString.toExternalU16(result.ptr, result.len, globalThis); } @@ -640,7 +649,7 @@ pub const TextDecoder = struct { } } } else { - if (strings.toUTF16Alloc(default_allocator, buffer_slice, false)) |result_| { + if (toUTF16(default_allocator, buffer_slice, false)) |result_| { if (result_) |result| { return ZigString.toExternalU16(result.ptr, result.len, globalThis); } diff --git a/src/string_immutable.zig b/src/string_immutable.zig index c62266c62b..d2d71621fd 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -1415,6 +1415,121 @@ pub fn toUTF16Alloc(allocator: std.mem.Allocator, bytes: []const u8, comptime fa return null; } +pub fn toUTF16AllocNoTrim(allocator: std.mem.Allocator, bytes: []const u8, comptime fail_if_invalid: bool) !?[]u16 { + if (strings.firstNonASCII(bytes)) |i| { + const output_: ?std.ArrayList(u16) = if (comptime bun.FeatureFlags.use_simdutf) simd: { + const out_length = bun.simdutf.length.utf16.from.utf8.le(bytes); + + if (out_length == 0) + break :simd null; + + var out = try allocator.alloc(u16, out_length); + log("toUTF16 {d} UTF8 -> {d} UTF16", .{ bytes.len, out_length }); + + // avoid `.with_errors.le()` due to https://github.com/simdutf/simdutf/issues/213 + switch (bun.simdutf.convert.utf8.to.utf16.le(bytes, out)) { + 0 => { + if (comptime fail_if_invalid) { + allocator.free(out); + return error.InvalidByteSequence; + } + + break :simd .{ + .items = out[0..i], + .capacity = out.len, + .allocator = allocator, + }; + }, + else => return out, + } + } else null; + var output = output_ orelse fallback: { + var list = try std.ArrayList(u16).initCapacity(allocator, i + 2); + list.items.len = i; + strings.copyU8IntoU16(list.items, bytes[0..i]); + break :fallback list; + }; + errdefer output.deinit(); + + var remaining = bytes[i..]; + + { + const sequence: [4]u8 = switch (remaining.len) { + 0 => unreachable, + 1 => [_]u8{ remaining[0], 0, 0, 0 }, + 2 => [_]u8{ remaining[0], remaining[1], 0, 0 }, + 3 => [_]u8{ remaining[0], remaining[1], remaining[2], 0 }, + else => remaining[0..4].*, + }; + + const replacement = strings.convertUTF8BytesIntoUTF16(&sequence); + if (comptime fail_if_invalid) { + if (replacement.fail) { + if (comptime Environment.allow_assert) std.debug.assert(replacement.code_point == unicode_replacement); + return error.InvalidByteSequence; + } + } + remaining = remaining[@max(replacement.len, 1)..]; + + //#define U16_LENGTH(c) ((uint32_t)(c)<=0xffff ? 1 : 2) + switch (replacement.code_point) { + 0...0xffff => |c| { + try output.append(@as(u16, @intCast(c))); + }, + else => |c| { + try output.appendSlice(&[_]u16{ strings.u16Lead(c), strings.u16Trail(c) }); + }, + } + } + + while (strings.firstNonASCII(remaining)) |j| { + const end = output.items.len; + try output.ensureUnusedCapacity(j); + output.items.len += j; + strings.copyU8IntoU16(output.items[end..][0..j], remaining[0..j]); + remaining = remaining[j..]; + + const sequence: [4]u8 = switch (remaining.len) { + 0 => unreachable, + 1 => [_]u8{ remaining[0], 0, 0, 0 }, + 2 => [_]u8{ remaining[0], remaining[1], 0, 0 }, + 3 => [_]u8{ remaining[0], remaining[1], remaining[2], 0 }, + else => remaining[0..4].*, + }; + + const replacement = strings.convertUTF8BytesIntoUTF16(&sequence); + if (comptime fail_if_invalid) { + if (replacement.fail) { + if (comptime Environment.allow_assert) std.debug.assert(replacement.code_point == unicode_replacement); + return error.InvalidByteSequence; + } + } + remaining = remaining[@max(replacement.len, 1)..]; + + //#define U16_LENGTH(c) ((uint32_t)(c)<=0xffff ? 1 : 2) + switch (replacement.code_point) { + 0...0xffff => |c| { + try output.append(@as(u16, @intCast(c))); + }, + else => |c| { + try output.appendSlice(&[_]u16{ strings.u16Lead(c), strings.u16Trail(c) }); + }, + } + } + + if (remaining.len > 0) { + try output.ensureTotalCapacityPrecise(output.items.len + remaining.len); + + output.items.len += remaining.len; + strings.copyU8IntoU16(output.items[output.items.len - remaining.len ..], remaining); + } + + return output.items; + } + + return null; +} + pub fn utf16CodepointWithFFFD(comptime Type: type, input: Type) UTF16Replacement { const c0 = @as(u21, input[0]); diff --git a/test/js/web/encoding/text-decoder.test.js b/test/js/web/encoding/text-decoder.test.js index 8ec097f31b..4991cf3612 100644 --- a/test/js/web/encoding/text-decoder.test.js +++ b/test/js/web/encoding/text-decoder.test.js @@ -233,6 +233,20 @@ describe("TextDecoder", () => { }).toThrow(TypeError); }); + it("should not trim invalid byte sequences when fatal is false", () => { + const buf = Buffer.from([77, 97, 110, 32, 208, 129, 240, 164, 173]); + const received = new TextDecoder("utf-8").decode(buf); + const expected = "Man Ё\ufffd"; + expect(received).toBe(expected); + }); + + it("should trim when stream is true", () => { + const buf = Buffer.from([77, 97, 110, 32, 208, 129, 240, 164, 173]); + const received = new TextDecoder("utf-8").decode(buf, { stream: true }); + const expected = "Man Ё"; + expect(received).toBe(expected); + }); + it("constructor should set values", () => { const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: false }); expect(decoder.fatal).toBe(true);