aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/node
diff options
context:
space:
mode:
Diffstat (limited to 'src/bun.js/node')
-rw-r--r--src/bun.js/node/buffer.zig2
-rw-r--r--src/bun.js/node/dir_iterator.zig54
-rw-r--r--src/bun.js/node/fs_events.zig609
-rw-r--r--src/bun.js/node/node.classes.ts31
-rw-r--r--src/bun.js/node/node_fs.zig445
-rw-r--r--src/bun.js/node/node_fs_binding.zig10
-rw-r--r--src/bun.js/node/node_fs_constant.zig6
-rw-r--r--src/bun.js/node/node_fs_watcher.zig919
-rw-r--r--src/bun.js/node/node_os.zig66
-rw-r--r--src/bun.js/node/os/constants.zig4
-rw-r--r--src/bun.js/node/syscall.zig190
-rw-r--r--src/bun.js/node/types.zig148
12 files changed, 2274 insertions, 210 deletions
diff --git a/src/bun.js/node/buffer.zig b/src/bun.js/node/buffer.zig
index f73498069..3a0750f05 100644
--- a/src/bun.js/node/buffer.zig
+++ b/src/bun.js/node/buffer.zig
@@ -50,7 +50,7 @@ pub const BufferVectorized = struct {
switch (written) {
0 => {},
- 1 => @memset(buf.ptr, buf[0], buf.len),
+ 1 => @memset(buf, buf[0]),
else => {
var contents = buf[0..written];
buf = buf[written..];
diff --git a/src/bun.js/node/dir_iterator.zig b/src/bun.js/node/dir_iterator.zig
index aa939679c..dac78e5e2 100644
--- a/src/bun.js/node/dir_iterator.zig
+++ b/src/bun.js/node/dir_iterator.zig
@@ -78,15 +78,15 @@ pub const Iterator = switch (builtin.os.tag) {
}
const entry_kind = switch (darwin_entry.d_type) {
- os.DT.BLK => Entry.Kind.BlockDevice,
- os.DT.CHR => Entry.Kind.CharacterDevice,
- os.DT.DIR => Entry.Kind.Directory,
- os.DT.FIFO => Entry.Kind.NamedPipe,
- os.DT.LNK => Entry.Kind.SymLink,
- os.DT.REG => Entry.Kind.File,
- os.DT.SOCK => Entry.Kind.UnixDomainSocket,
- os.DT.WHT => Entry.Kind.Whiteout,
- else => Entry.Kind.Unknown,
+ os.DT.BLK => Entry.Kind.block_device,
+ os.DT.CHR => Entry.Kind.character_device,
+ os.DT.DIR => Entry.Kind.directory,
+ os.DT.FIFO => Entry.Kind.named_pipe,
+ os.DT.LNK => Entry.Kind.sym_link,
+ os.DT.REG => Entry.Kind.file,
+ os.DT.SOCK => Entry.Kind.unix_domain_socket,
+ os.DT.WHT => Entry.Kind.whiteout,
+ else => Entry.Kind.unknown,
};
return .{
.result = IteratorResult{
@@ -134,14 +134,14 @@ pub const Iterator = switch (builtin.os.tag) {
}
const entry_kind = switch (linux_entry.d_type) {
- linux.DT.BLK => Entry.Kind.BlockDevice,
- linux.DT.CHR => Entry.Kind.CharacterDevice,
- linux.DT.DIR => Entry.Kind.Directory,
- linux.DT.FIFO => Entry.Kind.NamedPipe,
- linux.DT.LNK => Entry.Kind.SymLink,
- linux.DT.REG => Entry.Kind.File,
- linux.DT.SOCK => Entry.Kind.UnixDomainSocket,
- else => Entry.Kind.Unknown,
+ linux.DT.BLK => Entry.Kind.block_device,
+ linux.DT.CHR => Entry.Kind.character_device,
+ linux.DT.DIR => Entry.Kind.directory,
+ linux.DT.FIFO => Entry.Kind.named_pipe,
+ linux.DT.LNK => Entry.Kind.sym_link,
+ linux.DT.REG => Entry.Kind.file,
+ linux.DT.SOCK => Entry.Kind.unix_domain_socket,
+ else => Entry.Kind.unknown,
};
return .{
.result = IteratorResult{
@@ -213,9 +213,9 @@ pub const Iterator = switch (builtin.os.tag) {
const name_utf8 = self.name_data[0..name_utf8_len];
const kind = blk: {
const attrs = dir_info.FileAttributes;
- if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk Entry.Kind.Directory;
- if (attrs & w.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk Entry.Kind.SymLink;
- break :blk Entry.Kind.File;
+ if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk Entry.Kind.directory;
+ if (attrs & w.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk Entry.Kind.sym_link;
+ break :blk Entry.Kind.file;
};
return .{
.result = IteratorResult{
@@ -275,13 +275,13 @@ pub const Iterator = switch (builtin.os.tag) {
}
const entry_kind = switch (entry.d_type) {
- .BLOCK_DEVICE => Entry.Kind.BlockDevice,
- .CHARACTER_DEVICE => Entry.Kind.CharacterDevice,
- .DIRECTORY => Entry.Kind.Directory,
- .SYMBOLIC_LINK => Entry.Kind.SymLink,
- .REGULAR_FILE => Entry.Kind.File,
- .SOCKET_STREAM, .SOCKET_DGRAM => Entry.Kind.UnixDomainSocket,
- else => Entry.Kind.Unknown,
+ .BLOCK_DEVICE => Entry.Kind.block_device,
+ .CHARACTER_DEVICE => Entry.Kind.character_device,
+ .DIRECTORY => Entry.Kind.directory,
+ .SYMBOLIC_LINK => Entry.Kind.sym_link,
+ .REGULAR_FILE => Entry.Kind.file,
+ .SOCKET_STREAM, .SOCKET_DGRAM => Entry.Kind.unix_domain_socket,
+ else => Entry.Kind.unknown,
};
return IteratorResult{
.name = name,
diff --git a/src/bun.js/node/fs_events.zig b/src/bun.js/node/fs_events.zig
new file mode 100644
index 000000000..a3fba5441
--- /dev/null
+++ b/src/bun.js/node/fs_events.zig
@@ -0,0 +1,609 @@
+const std = @import("std");
+const bun = @import("root").bun;
+const Environment = bun.Environment;
+const Mutex = @import("../../lock.zig").Lock;
+const sync = @import("../../sync.zig");
+const Semaphore = sync.Semaphore;
+const UnboundedQueue = @import("../unbounded_queue.zig").UnboundedQueue;
+const TaggedPointerUnion = @import("../../tagged_pointer.zig").TaggedPointerUnion;
+const string = bun.string;
+
+pub const CFAbsoluteTime = f64;
+pub const CFTimeInterval = f64;
+pub const CFArrayCallBacks = anyopaque;
+
+pub const FSEventStreamEventFlags = c_int;
+pub const OSStatus = c_int;
+pub const CFIndex = c_long;
+
+pub const FSEventStreamCreateFlags = u32;
+pub const FSEventStreamEventId = u64;
+
+pub const CFStringEncoding = c_uint;
+
+pub const CFArrayRef = ?*anyopaque;
+pub const CFAllocatorRef = ?*anyopaque;
+pub const CFBundleRef = ?*anyopaque;
+pub const CFDictionaryRef = ?*anyopaque;
+pub const CFRunLoopRef = ?*anyopaque;
+pub const CFRunLoopSourceRef = ?*anyopaque;
+pub const CFStringRef = ?*anyopaque;
+pub const CFTypeRef = ?*anyopaque;
+pub const FSEventStreamRef = ?*anyopaque;
+pub const FSEventStreamCallback = *const fn (FSEventStreamRef, ?*anyopaque, usize, ?*anyopaque, *FSEventStreamEventFlags, *FSEventStreamEventId) callconv(.C) void;
+
+// we only care about info and perform
+pub const CFRunLoopSourceContext = extern struct {
+ version: CFIndex = 0,
+ info: *anyopaque,
+ retain: ?*anyopaque = null,
+ release: ?*anyopaque = null,
+ copyDescription: ?*anyopaque = null,
+ equal: ?*anyopaque = null,
+ hash: ?*anyopaque = null,
+ schedule: ?*anyopaque = null,
+ cancel: ?*anyopaque = null,
+ perform: *const fn (?*anyopaque) callconv(.C) void,
+};
+
+pub const FSEventStreamContext = extern struct {
+ version: CFIndex = 0,
+ info: ?*anyopaque = null,
+ pad: [3]?*anyopaque = .{ null, null, null },
+};
+
+pub const kCFStringEncodingUTF8: CFStringEncoding = 0x8000100;
+pub const noErr: OSStatus = 0;
+
+pub const kFSEventStreamCreateFlagNoDefer: c_int = 2;
+pub const kFSEventStreamCreateFlagFileEvents: c_int = 16;
+
+pub const kFSEventStreamEventFlagEventIdsWrapped: c_int = 8;
+pub const kFSEventStreamEventFlagHistoryDone: c_int = 16;
+pub const kFSEventStreamEventFlagItemChangeOwner: c_int = 0x4000;
+pub const kFSEventStreamEventFlagItemCreated: c_int = 0x100;
+pub const kFSEventStreamEventFlagItemFinderInfoMod: c_int = 0x2000;
+pub const kFSEventStreamEventFlagItemInodeMetaMod: c_int = 0x400;
+pub const kFSEventStreamEventFlagItemIsDir: c_int = 0x20000;
+pub const kFSEventStreamEventFlagItemModified: c_int = 0x1000;
+pub const kFSEventStreamEventFlagItemRemoved: c_int = 0x200;
+pub const kFSEventStreamEventFlagItemRenamed: c_int = 0x800;
+pub const kFSEventStreamEventFlagItemXattrMod: c_int = 0x8000;
+pub const kFSEventStreamEventFlagKernelDropped: c_int = 4;
+pub const kFSEventStreamEventFlagMount: c_int = 64;
+pub const kFSEventStreamEventFlagRootChanged: c_int = 32;
+pub const kFSEventStreamEventFlagUnmount: c_int = 128;
+pub const kFSEventStreamEventFlagUserDropped: c_int = 2;
+
+// Lazy function call binding.
+const RTLD_LAZY = 0x1;
+// Symbols exported from this image (dynamic library or bundle)
+// are generally hidden and only availble to dlsym() when
+// directly using the handle returned by this call to dlopen().
+const RTLD_LOCAL = 0x4;
+
+pub const kFSEventsModified: c_int =
+ kFSEventStreamEventFlagItemChangeOwner |
+ kFSEventStreamEventFlagItemFinderInfoMod |
+ kFSEventStreamEventFlagItemInodeMetaMod |
+ kFSEventStreamEventFlagItemModified |
+ kFSEventStreamEventFlagItemXattrMod;
+
+pub const kFSEventsRenamed: c_int =
+ kFSEventStreamEventFlagItemCreated |
+ kFSEventStreamEventFlagItemRemoved |
+ kFSEventStreamEventFlagItemRenamed;
+
+pub const kFSEventsSystem: c_int =
+ kFSEventStreamEventFlagUserDropped |
+ kFSEventStreamEventFlagKernelDropped |
+ kFSEventStreamEventFlagEventIdsWrapped |
+ kFSEventStreamEventFlagHistoryDone |
+ kFSEventStreamEventFlagMount |
+ kFSEventStreamEventFlagUnmount |
+ kFSEventStreamEventFlagRootChanged;
+
+var fsevents_mutex: Mutex = Mutex.init();
+var fsevents_default_loop_mutex: Mutex = Mutex.init();
+var fsevents_default_loop: ?*FSEventsLoop = null;
+
+fn dlsym(handle: ?*anyopaque, comptime Type: type, comptime symbol: [:0]const u8) ?Type {
+ if (std.c.dlsym(handle, symbol)) |ptr| {
+ return bun.cast(Type, ptr);
+ }
+ return null;
+}
+
+pub const CoreFoundation = struct {
+ handle: ?*anyopaque,
+ ArrayCreate: *fn (CFAllocatorRef, [*]?*anyopaque, CFIndex, ?*CFArrayCallBacks) callconv(.C) CFArrayRef,
+ Release: *fn (CFTypeRef) callconv(.C) void,
+
+ RunLoopAddSource: *fn (CFRunLoopRef, CFRunLoopSourceRef, CFStringRef) callconv(.C) void,
+ RunLoopGetCurrent: *fn () callconv(.C) CFRunLoopRef,
+ RunLoopRemoveSource: *fn (CFRunLoopRef, CFRunLoopSourceRef, CFStringRef) callconv(.C) void,
+ RunLoopRun: *fn () callconv(.C) void,
+ RunLoopSourceCreate: *fn (CFAllocatorRef, CFIndex, *CFRunLoopSourceContext) callconv(.C) CFRunLoopSourceRef,
+ RunLoopSourceSignal: *fn (CFRunLoopSourceRef) callconv(.C) void,
+ RunLoopStop: *fn (CFRunLoopRef) callconv(.C) void,
+ RunLoopWakeUp: *fn (CFRunLoopRef) callconv(.C) void,
+ StringCreateWithFileSystemRepresentation: *fn (CFAllocatorRef, [*]const u8) callconv(.C) CFStringRef,
+ RunLoopDefaultMode: *CFStringRef,
+
+ pub fn get() CoreFoundation {
+ if (fsevents_cf) |cf| return cf;
+ fsevents_mutex.lock();
+ defer fsevents_mutex.unlock();
+ if (fsevents_cf) |cf| return cf;
+
+ InitLibrary();
+
+ return fsevents_cf.?;
+ }
+
+ // We Actually never deinit it
+ // pub fn deinit(this: *CoreFoundation) void {
+ // if(this.handle) | ptr| {
+ // this.handle = null;
+ // _ = std.c.dlclose(this.handle);
+ // }
+ // }
+
+};
+
+pub const CoreServices = struct {
+ handle: ?*anyopaque,
+ FSEventStreamCreate: *fn (CFAllocatorRef, FSEventStreamCallback, *FSEventStreamContext, CFArrayRef, FSEventStreamEventId, CFTimeInterval, FSEventStreamCreateFlags) callconv(.C) FSEventStreamRef,
+ FSEventStreamInvalidate: *fn (FSEventStreamRef) callconv(.C) void,
+ FSEventStreamRelease: *fn (FSEventStreamRef) callconv(.C) void,
+ FSEventStreamScheduleWithRunLoop: *fn (FSEventStreamRef, CFRunLoopRef, CFStringRef) callconv(.C) void,
+ FSEventStreamStart: *fn (FSEventStreamRef) callconv(.C) c_int,
+ FSEventStreamStop: *fn (FSEventStreamRef) callconv(.C) void,
+ // libuv set it to -1 so the actual value is this
+ kFSEventStreamEventIdSinceNow: FSEventStreamEventId = 18446744073709551615,
+
+ pub fn get() CoreServices {
+ if (fsevents_cs) |cs| return cs;
+ fsevents_mutex.lock();
+ defer fsevents_mutex.unlock();
+ if (fsevents_cs) |cs| return cs;
+
+ InitLibrary();
+
+ return fsevents_cs.?;
+ }
+
+ // We Actually never deinit it
+ // pub fn deinit(this: *CoreServices) void {
+ // if(this.handle) | ptr| {
+ // this.handle = null;
+ // _ = std.c.dlclose(this.handle);
+ // }
+ // }
+
+};
+
+var fsevents_cf: ?CoreFoundation = null;
+var fsevents_cs: ?CoreServices = null;
+
+fn InitLibrary() void {
+ const fsevents_cf_handle = std.c.dlopen("/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation", RTLD_LAZY | RTLD_LOCAL);
+ if (fsevents_cf_handle == null) @panic("Cannot Load CoreFoundation");
+
+ fsevents_cf = CoreFoundation{
+ .handle = fsevents_cf_handle,
+ .ArrayCreate = dlsym(fsevents_cf_handle, *fn (CFAllocatorRef, [*]?*anyopaque, CFIndex, ?*CFArrayCallBacks) callconv(.C) CFArrayRef, "CFArrayCreate") orelse @panic("Cannot Load CoreFoundation"),
+ .Release = dlsym(fsevents_cf_handle, *fn (CFTypeRef) callconv(.C) void, "CFRelease") orelse @panic("Cannot Load CoreFoundation"),
+ .RunLoopAddSource = dlsym(fsevents_cf_handle, *fn (CFRunLoopRef, CFRunLoopSourceRef, CFStringRef) callconv(.C) void, "CFRunLoopAddSource") orelse @panic("Cannot Load CoreFoundation"),
+ .RunLoopGetCurrent = dlsym(fsevents_cf_handle, *fn () callconv(.C) CFRunLoopRef, "CFRunLoopGetCurrent") orelse @panic("Cannot Load CoreFoundation"),
+ .RunLoopRemoveSource = dlsym(fsevents_cf_handle, *fn (CFRunLoopRef, CFRunLoopSourceRef, CFStringRef) callconv(.C) void, "CFRunLoopRemoveSource") orelse @panic("Cannot Load CoreFoundation"),
+ .RunLoopRun = dlsym(fsevents_cf_handle, *fn () callconv(.C) void, "CFRunLoopRun") orelse @panic("Cannot Load CoreFoundation"),
+ .RunLoopSourceCreate = dlsym(fsevents_cf_handle, *fn (CFAllocatorRef, CFIndex, *CFRunLoopSourceContext) callconv(.C) CFRunLoopSourceRef, "CFRunLoopSourceCreate") orelse @panic("Cannot Load CoreFoundation"),
+ .RunLoopSourceSignal = dlsym(fsevents_cf_handle, *fn (CFRunLoopSourceRef) callconv(.C) void, "CFRunLoopSourceSignal") orelse @panic("Cannot Load CoreFoundation"),
+ .RunLoopStop = dlsym(fsevents_cf_handle, *fn (CFRunLoopRef) callconv(.C) void, "CFRunLoopStop") orelse @panic("Cannot Load CoreFoundation"),
+ .RunLoopWakeUp = dlsym(fsevents_cf_handle, *fn (CFRunLoopRef) callconv(.C) void, "CFRunLoopWakeUp") orelse @panic("Cannot Load CoreFoundation"),
+ .StringCreateWithFileSystemRepresentation = dlsym(fsevents_cf_handle, *fn (CFAllocatorRef, [*]const u8) callconv(.C) CFStringRef, "CFStringCreateWithFileSystemRepresentation") orelse @panic("Cannot Load CoreFoundation"),
+ .RunLoopDefaultMode = dlsym(fsevents_cf_handle, *CFStringRef, "kCFRunLoopDefaultMode") orelse @panic("Cannot Load CoreFoundation"),
+ };
+
+ const fsevents_cs_handle = std.c.dlopen("/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices", RTLD_LAZY | RTLD_LOCAL);
+ if (fsevents_cs_handle == null) @panic("Cannot Load CoreServices");
+
+ fsevents_cs = CoreServices{
+ .handle = fsevents_cs_handle,
+ .FSEventStreamCreate = dlsym(fsevents_cs_handle, *fn (CFAllocatorRef, FSEventStreamCallback, *FSEventStreamContext, CFArrayRef, FSEventStreamEventId, CFTimeInterval, FSEventStreamCreateFlags) callconv(.C) FSEventStreamRef, "FSEventStreamCreate") orelse @panic("Cannot Load CoreServices"),
+ .FSEventStreamInvalidate = dlsym(fsevents_cs_handle, *fn (FSEventStreamRef) callconv(.C) void, "FSEventStreamInvalidate") orelse @panic("Cannot Load CoreServices"),
+ .FSEventStreamRelease = dlsym(fsevents_cs_handle, *fn (FSEventStreamRef) callconv(.C) void, "FSEventStreamRelease") orelse @panic("Cannot Load CoreServices"),
+ .FSEventStreamScheduleWithRunLoop = dlsym(fsevents_cs_handle, *fn (FSEventStreamRef, CFRunLoopRef, CFStringRef) callconv(.C) void, "FSEventStreamScheduleWithRunLoop") orelse @panic("Cannot Load CoreServices"),
+ .FSEventStreamStart = dlsym(fsevents_cs_handle, *fn (FSEventStreamRef) callconv(.C) c_int, "FSEventStreamStart") orelse @panic("Cannot Load CoreServices"),
+ .FSEventStreamStop = dlsym(fsevents_cs_handle, *fn (FSEventStreamRef) callconv(.C) void, "FSEventStreamStop") orelse @panic("Cannot Load CoreServices"),
+ };
+}
+
+pub const FSEventsLoop = struct {
+ signal_source: CFRunLoopSourceRef,
+ mutex: Mutex,
+ loop: CFRunLoopRef = null,
+ sem: Semaphore,
+ thread: std.Thread = undefined,
+ tasks: ConcurrentTask.Queue = ConcurrentTask.Queue{},
+ watchers: bun.BabyList(?*FSEventsWatcher) = .{},
+ watcher_count: u32 = 0,
+ fsevent_stream: FSEventStreamRef = null,
+ paths: ?[]?*anyopaque = null,
+ cf_paths: CFArrayRef = null,
+ has_scheduled_watchers: bool = false,
+
+ pub const Task = struct {
+ ctx: ?*anyopaque,
+ callback: *const (fn (*anyopaque) void),
+
+ pub fn run(this: *Task) void {
+ var callback = this.callback;
+ var ctx = this.ctx;
+ callback(ctx.?);
+ }
+
+ pub fn New(comptime Type: type, comptime Callback: anytype) type {
+ return struct {
+ pub fn init(ctx: *Type) Task {
+ return Task{
+ .callback = wrap,
+ .ctx = ctx,
+ };
+ }
+
+ pub fn wrap(this: ?*anyopaque) void {
+ @call(.always_inline, Callback, .{@ptrCast(*Type, @alignCast(@alignOf(Type), this.?))});
+ }
+ };
+ }
+ };
+
+ pub const ConcurrentTask = struct {
+ task: Task = undefined,
+ next: ?*ConcurrentTask = null,
+ auto_delete: bool = false,
+
+ pub const Queue = UnboundedQueue(ConcurrentTask, .next);
+
+ pub fn from(this: *ConcurrentTask, task: Task) *ConcurrentTask {
+ this.* = .{
+ .task = task,
+ .next = null,
+ };
+ return this;
+ }
+ };
+
+ pub fn CFThreadLoop(this: *FSEventsLoop) void {
+ bun.Output.Source.configureNamedThread("CFThreadLoop");
+
+ const CF = CoreFoundation.get();
+
+ this.loop = CF.RunLoopGetCurrent();
+
+ CF.RunLoopAddSource(this.loop, this.signal_source, CF.RunLoopDefaultMode.*);
+
+ this.sem.post();
+
+ CF.RunLoopRun();
+ CF.RunLoopRemoveSource(this.loop, this.signal_source, CF.RunLoopDefaultMode.*);
+
+ this.loop = null;
+ }
+
+ // Runs in CF thread, executed after `enqueueTaskConcurrent()`
+ fn CFLoopCallback(arg: ?*anyopaque) callconv(.C) void {
+ if (arg) |self| {
+ const this = bun.cast(*FSEventsLoop, self);
+
+ var concurrent = this.tasks.popBatch();
+ const count = concurrent.count;
+ if (count == 0)
+ return;
+
+ var iter = concurrent.iterator();
+ while (iter.next()) |task| {
+ task.task.run();
+ if (task.auto_delete) bun.default_allocator.destroy(task);
+ }
+ }
+ }
+
+ pub fn init() !*FSEventsLoop {
+ const this = bun.default_allocator.create(FSEventsLoop) catch unreachable;
+
+ const CF = CoreFoundation.get();
+
+ var ctx = CFRunLoopSourceContext{
+ .info = this,
+ .perform = CFLoopCallback,
+ };
+
+ const signal_source = CF.RunLoopSourceCreate(null, 0, &ctx);
+ if (signal_source == null) {
+ return error.FailedToCreateCoreFoudationSourceLoop;
+ }
+
+ var fs_loop = FSEventsLoop{ .sem = Semaphore.init(0), .mutex = Mutex.init(), .signal_source = signal_source };
+
+ this.* = fs_loop;
+ this.thread = try std.Thread.spawn(.{}, FSEventsLoop.CFThreadLoop, .{this});
+
+ // sync threads
+ this.sem.wait();
+ return this;
+ }
+
+ fn enqueueTaskConcurrent(this: *FSEventsLoop, task: Task) void {
+ const CF = CoreFoundation.get();
+ var concurrent = bun.default_allocator.create(ConcurrentTask) catch unreachable;
+ concurrent.auto_delete = true;
+ this.tasks.push(concurrent.from(task));
+ CF.RunLoopSourceSignal(this.signal_source);
+ CF.RunLoopWakeUp(this.loop);
+ }
+
+ // Runs in CF thread, when there're events in FSEventStream
+ fn _events_cb(_: FSEventStreamRef, info: ?*anyopaque, numEvents: usize, eventPaths: ?*anyopaque, eventFlags: *FSEventStreamEventFlags, _: *FSEventStreamEventId) callconv(.C) void {
+ const paths_ptr = bun.cast([*][*:0]const u8, eventPaths);
+ const paths = paths_ptr[0..numEvents];
+ var loop = bun.cast(*FSEventsLoop, info);
+ const event_flags = bun.cast([*]FSEventStreamEventFlags, eventFlags);
+
+ for (loop.watchers.slice()) |watcher| {
+ if (watcher) |handle| {
+ for (paths, 0..) |path_ptr, i| {
+ var flags = event_flags[i];
+ var path = path_ptr[0..bun.len(path_ptr)];
+ // Filter out paths that are outside handle's request
+ if (path.len < handle.path.len or !bun.strings.startsWith(path, handle.path)) {
+ continue;
+ }
+ const is_file = (flags & kFSEventStreamEventFlagItemIsDir) == 0;
+
+ // Remove common prefix, unless the watched folder is "/"
+ if (!(handle.path.len == 1 and handle.path[0] == '/')) {
+ path = path[handle.path.len..];
+
+ // Ignore events with path equal to directory itself
+ if (path.len <= 1 and is_file) {
+ continue;
+ }
+ if (path.len == 0) {
+ // Since we're using fsevents to watch the file itself, path == handle.path, and we now need to get the basename of the file back
+ while (path.len > 0) {
+ if (bun.strings.startsWithChar(path, '/')) {
+ path = path[1..];
+ break;
+ } else {
+ path = path[1..];
+ }
+ }
+
+ // Created and Removed seem to be always set, but don't make sense
+ flags &= ~kFSEventsRenamed;
+ } else {
+ // Skip forward slash
+ path = path[1..];
+ }
+ }
+
+ // Do not emit events from subdirectories (without option set)
+ if (path.len == 0 or (bun.strings.containsChar(path, '/') and !handle.recursive)) {
+ continue;
+ }
+
+ var is_rename = true;
+
+ if ((flags & kFSEventsRenamed) == 0) {
+ if ((flags & kFSEventsModified) != 0 or is_file) {
+ is_rename = false;
+ }
+ }
+
+ handle.callback(handle.ctx, path, is_file, is_rename);
+ }
+ }
+ }
+ }
+
+ // Runs on CF Thread
+ pub fn _schedule(this: *FSEventsLoop) void {
+ this.mutex.lock();
+ defer this.mutex.unlock();
+ this.has_scheduled_watchers = false;
+
+ var watchers = this.watchers.slice();
+
+ const CF = CoreFoundation.get();
+ const CS = CoreServices.get();
+
+ if (this.fsevent_stream) |stream| {
+ // Stop emitting events
+ CS.FSEventStreamStop(stream);
+
+ // Release stream
+ CS.FSEventStreamInvalidate(stream);
+ CS.FSEventStreamRelease(stream);
+ this.fsevent_stream = null;
+ }
+ // clean old paths
+ if (this.paths) |p| {
+ this.paths = null;
+ bun.default_allocator.destroy(p);
+ }
+ if (this.cf_paths) |cf| {
+ this.cf_paths = null;
+ CF.Release(cf);
+ }
+
+ const paths = bun.default_allocator.alloc(?*anyopaque, this.watcher_count) catch unreachable;
+ var count: u32 = 0;
+ for (watchers) |w| {
+ if (w) |watcher| {
+ const path = CF.StringCreateWithFileSystemRepresentation(null, watcher.path.ptr);
+ paths[count] = path;
+ count += 1;
+ }
+ }
+
+ const cf_paths = CF.ArrayCreate(null, paths.ptr, count, null);
+ var ctx: FSEventStreamContext = .{
+ .info = this,
+ };
+
+ const latency: CFAbsoluteTime = 0.05;
+ // Explanation of selected flags:
+ // 1. NoDefer - without this flag, events that are happening continuously
+ // (i.e. each event is happening after time interval less than `latency`,
+ // counted from previous event), will be deferred and passed to callback
+ // once they'll either fill whole OS buffer, or when this continuous stream
+ // will stop (i.e. there'll be delay between events, bigger than
+ // `latency`).
+ // Specifying this flag will invoke callback after `latency` time passed
+ // since event.
+ // 2. FileEvents - fire callback for file changes too (by default it is firing
+ // it only for directory changes).
+ //
+ const flags: FSEventStreamCreateFlags = kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents;
+
+ //
+ // NOTE: It might sound like a good idea to remember last seen StreamEventId,
+ // but in reality one dir might have last StreamEventId less than, the other,
+ // that is being watched now. Which will cause FSEventStream API to report
+ // changes to files from the past.
+ //
+ const ref = CS.FSEventStreamCreate(null, _events_cb, &ctx, cf_paths, CS.kFSEventStreamEventIdSinceNow, latency, flags);
+
+ CS.FSEventStreamScheduleWithRunLoop(ref, this.loop, CF.RunLoopDefaultMode.*);
+ if (CS.FSEventStreamStart(ref) == 0) {
+ //clean in case of failure
+ bun.default_allocator.destroy(paths);
+ CF.Release(cf_paths);
+ CS.FSEventStreamInvalidate(ref);
+ CS.FSEventStreamRelease(ref);
+ return;
+ }
+ this.fsevent_stream = ref;
+ this.paths = paths;
+ this.cf_paths = cf_paths;
+ }
+
+ fn registerWatcher(this: *FSEventsLoop, watcher: *FSEventsWatcher) void {
+ this.mutex.lock();
+ defer this.mutex.unlock();
+ if (this.watcher_count == this.watchers.len) {
+ this.watcher_count += 1;
+ this.watchers.push(bun.default_allocator, watcher) catch unreachable;
+ } else {
+ var watchers = this.watchers.slice();
+ for (watchers, 0..) |w, i| {
+ if (w == null) {
+ watchers[i] = watcher;
+ this.watcher_count += 1;
+ break;
+ }
+ }
+ }
+
+ if (this.has_scheduled_watchers == false) {
+ this.has_scheduled_watchers = true;
+ this.enqueueTaskConcurrent(Task.New(FSEventsLoop, _schedule).init(this));
+ }
+ }
+
+ fn unregisterWatcher(this: *FSEventsLoop, watcher: *FSEventsWatcher) void {
+ this.mutex.lock();
+ defer this.mutex.unlock();
+ var watchers = this.watchers.slice();
+ for (watchers, 0..) |w, i| {
+ if (w) |item| {
+ if (item == watcher) {
+ watchers[i] = null;
+ // if is the last one just pop
+ if (i == watchers.len - 1) {
+ this.watchers.len -= 1;
+ }
+ this.watcher_count -= 1;
+ break;
+ }
+ }
+ }
+ }
+
+ // Runs on CF loop to close the loop
+ fn _stop(this: *FSEventsLoop) void {
+ const CF = CoreFoundation.get();
+ CF.RunLoopStop(this.loop);
+ }
+ fn deinit(this: *FSEventsLoop) void {
+ // signal close and wait
+ this.enqueueTaskConcurrent(Task.New(FSEventsLoop, FSEventsLoop._stop).init(this));
+ this.thread.join();
+ const CF = CoreFoundation.get();
+
+ CF.Release(this.signal_source);
+ this.signal_source = null;
+
+ this.sem.deinit();
+ this.mutex.deinit();
+ if (this.watcher_count > 0) {
+ while (this.watchers.popOrNull()) |watcher| {
+ if (watcher) |w| {
+ // unlink watcher
+ w.loop = null;
+ }
+ }
+ }
+
+ this.watchers.deinitWithAllocator(bun.default_allocator);
+
+ bun.default_allocator.destroy(this);
+ }
+};
+
+pub const FSEventsWatcher = struct {
+ path: string,
+ callback: Callback,
+ loop: ?*FSEventsLoop,
+ recursive: bool,
+ ctx: ?*anyopaque,
+
+ const Callback = *const fn (ctx: ?*anyopaque, path: string, is_file: bool, is_rename: bool) void;
+
+ pub fn init(loop: *FSEventsLoop, path: string, recursive: bool, callback: Callback, ctx: ?*anyopaque) *FSEventsWatcher {
+ var this = bun.default_allocator.create(FSEventsWatcher) catch unreachable;
+ this.* = FSEventsWatcher{
+ .path = path,
+ .callback = callback,
+ .loop = loop,
+ .recursive = recursive,
+ .ctx = ctx,
+ };
+
+ loop.registerWatcher(this);
+ return this;
+ }
+
+ pub fn deinit(this: *FSEventsWatcher) void {
+ if (this.loop) |loop| {
+ loop.unregisterWatcher(this);
+ }
+ bun.default_allocator.destroy(this);
+ }
+};
+
+pub fn watch(path: string, recursive: bool, callback: FSEventsWatcher.Callback, ctx: ?*anyopaque) !*FSEventsWatcher {
+ if (fsevents_default_loop) |loop| {
+ return FSEventsWatcher.init(loop, path, recursive, callback, ctx);
+ } else {
+ fsevents_default_loop_mutex.lock();
+ defer fsevents_default_loop_mutex.unlock();
+ if (fsevents_default_loop == null) {
+ fsevents_default_loop = try FSEventsLoop.init();
+ }
+ return FSEventsWatcher.init(fsevents_default_loop.?, path, recursive, callback, ctx);
+ }
+}
diff --git a/src/bun.js/node/node.classes.ts b/src/bun.js/node/node.classes.ts
index f984077e4..2efad5245 100644
--- a/src/bun.js/node/node.classes.ts
+++ b/src/bun.js/node/node.classes.ts
@@ -2,6 +2,35 @@ import { define } from "../scripts/class-definitions";
export default [
define({
+ name: "FSWatcher",
+ construct: false,
+ noConstructor: true,
+ finalize: true,
+ configurable: false,
+ hasPendingActivity: true,
+ klass: {},
+ JSType: "0b11101110",
+ proto: {
+ ref: {
+ fn: "doRef",
+ length: 0,
+ },
+ unref: {
+ fn: "doUnref",
+ length: 0,
+ },
+ hasRef: {
+ fn: "hasRef",
+ length: 0,
+ },
+ close: {
+ fn: "doClose",
+ length: 0,
+ },
+ },
+ values: ["listener"],
+ }),
+ define({
name: "Timeout",
construct: false,
noConstructor: true,
@@ -300,7 +329,7 @@ export default [
utimes: { fn: "utimes", length: 4 },
utimesSync: { fn: "utimesSync", length: 3 },
// TODO:
- // watch: { fn: "watch", length: 3 },
+ watch: { fn: "watch", length: 3 },
// watchFile: { fn: "watchFile", length: 3 },
writeFile: { fn: "writeFile", length: 4 },
writeFileSync: { fn: "writeFileSync", length: 3 },
diff --git a/src/bun.js/node/node_fs.zig b/src/bun.js/node/node_fs.zig
index 254d58455..3f298c5c7 100644
--- a/src/bun.js/node/node_fs.zig
+++ b/src/bun.js/node/node_fs.zig
@@ -34,9 +34,8 @@ const Mode = JSC.Node.Mode;
const uid_t = std.os.uid_t;
const gid_t = std.os.gid_t;
-
/// u63 to allow one null bit
-const ReadPosition = u63;
+const ReadPosition = i64;
const Stats = JSC.Node.Stats;
const Dirent = JSC.Node.Dirent;
@@ -136,6 +135,154 @@ pub const Arguments = struct {
}
};
+ pub const Writev = struct {
+ fd: FileDescriptor,
+ buffers: JSC.Node.VectorArrayBuffer,
+ position: ?u52 = 0,
+
+ pub fn deinit(_: *const @This()) void {}
+
+ pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Writev {
+ const fd_value = arguments.nextEat() orelse {
+ if (exception.* == null) {
+ JSC.throwInvalidArguments(
+ "file descriptor is required",
+ .{},
+ ctx,
+ exception,
+ );
+ }
+ return null;
+ };
+
+ const fd = JSC.Node.fileDescriptorFromJS(ctx, fd_value, exception) orelse {
+ if (exception.* == null) {
+ JSC.throwInvalidArguments(
+ "file descriptor must be a number",
+ .{},
+ ctx,
+ exception,
+ );
+ }
+ return null;
+ };
+
+ const buffers = JSC.Node.VectorArrayBuffer.fromJS(
+ ctx,
+ arguments.protectEatNext() orelse {
+ JSC.throwInvalidArguments("Expected an ArrayBufferView[]", .{}, ctx, exception);
+ return null;
+ },
+ exception,
+ arguments.arena.allocator(),
+ ) orelse {
+ if (exception.* == null) {
+ JSC.throwInvalidArguments(
+ "buffers must be an array of TypedArray",
+ .{},
+ ctx,
+ exception,
+ );
+ }
+ return null;
+ };
+
+ var position: ?u52 = null;
+
+ if (arguments.nextEat()) |pos_value| {
+ if (!pos_value.isUndefinedOrNull()) {
+ if (pos_value.isNumber()) {
+ position = pos_value.to(u52);
+ } else {
+ JSC.throwInvalidArguments(
+ "position must be a number",
+ .{},
+ ctx,
+ exception,
+ );
+ return null;
+ }
+ }
+ }
+
+ return Writev{ .fd = fd, .buffers = buffers, .position = position };
+ }
+ };
+
+ pub const Readv = struct {
+ fd: FileDescriptor,
+ buffers: JSC.Node.VectorArrayBuffer,
+ position: ?u52 = 0,
+
+ pub fn deinit(_: *const @This()) void {}
+
+ pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Readv {
+ const fd_value = arguments.nextEat() orelse {
+ if (exception.* == null) {
+ JSC.throwInvalidArguments(
+ "file descriptor is required",
+ .{},
+ ctx,
+ exception,
+ );
+ }
+ return null;
+ };
+
+ const fd = JSC.Node.fileDescriptorFromJS(ctx, fd_value, exception) orelse {
+ if (exception.* == null) {
+ JSC.throwInvalidArguments(
+ "file descriptor must be a number",
+ .{},
+ ctx,
+ exception,
+ );
+ }
+ return null;
+ };
+
+ const buffers = JSC.Node.VectorArrayBuffer.fromJS(
+ ctx,
+ arguments.protectEatNext() orelse {
+ JSC.throwInvalidArguments("Expected an ArrayBufferView[]", .{}, ctx, exception);
+ return null;
+ },
+ exception,
+ arguments.arena.allocator(),
+ ) orelse {
+ if (exception.* == null) {
+ JSC.throwInvalidArguments(
+ "buffers must be an array of TypedArray",
+ .{},
+ ctx,
+ exception,
+ );
+ }
+ return null;
+ };
+
+ var position: ?u52 = null;
+
+ if (arguments.nextEat()) |pos_value| {
+ if (!pos_value.isUndefinedOrNull()) {
+ if (pos_value.isNumber()) {
+ position = pos_value.to(u52);
+ } else {
+ JSC.throwInvalidArguments(
+ "position must be a number",
+ .{},
+ ctx,
+ exception,
+ );
+ return null;
+ }
+ }
+ }
+
+ return Readv{ .fd = fd, .buffers = buffers, .position = position };
+ }
+ };
+
pub const FTruncate = struct {
fd: FileDescriptor,
len: ?JSC.WebCore.Blob.SizeType = null,
@@ -505,6 +652,7 @@ pub const Arguments = struct {
pub const Stat = struct {
path: PathLike,
big_int: bool = false,
+ throw_if_no_entry: bool = true,
pub fn deinit(this: Stat) void {
this.path.deinit();
@@ -525,13 +673,25 @@ pub const Arguments = struct {
if (exception.* != null) return null;
+ var throw_if_no_entry = true;
+
const big_int = brk: {
if (arguments.next()) |next_val| {
if (next_val.isObject()) {
if (next_val.isCallable(ctx.ptr().vm())) break :brk false;
arguments.eat();
- if (next_val.getOptional(ctx.ptr(), "bigint", bool) catch false) |big_int| {
+ if (next_val.getOptional(ctx.ptr(), "throwIfNoEntry", bool) catch {
+ path.deinit();
+ return null;
+ }) |throw_if_no_entry_val| {
+ throw_if_no_entry = throw_if_no_entry_val;
+ }
+
+ if (next_val.getOptional(ctx.ptr(), "bigint", bool) catch {
+ path.deinit();
+ return null;
+ }) |big_int| {
break :brk big_int;
}
}
@@ -541,7 +701,7 @@ pub const Arguments = struct {
if (exception.* != null) return null;
- return Stat{ .path = path, .big_int = big_int };
+ return Stat{ .path = path, .big_int = big_int, .throw_if_no_entry = throw_if_no_entry };
}
};
@@ -1377,7 +1537,7 @@ pub const Arguments = struct {
// fs.write(fd, string[, position[, encoding]], callback)
.string => {
if (current.isNumber()) {
- args.position = current.toU32();
+ args.position = current.to(i52);
arguments.eat();
current = arguments.next() orelse break :parse;
}
@@ -1393,18 +1553,18 @@ pub const Arguments = struct {
break :parse;
}
- if (!current.isNumber()) break :parse;
- args.offset = current.toU32();
+ if (!(current.isNumber() or current.isBigInt())) break :parse;
+ args.offset = current.to(u52);
arguments.eat();
current = arguments.next() orelse break :parse;
- if (!current.isNumber()) break :parse;
- args.length = current.toU32();
+ if (!(current.isNumber() or current.isBigInt())) break :parse;
+ args.length = current.to(u52);
arguments.eat();
current = arguments.next() orelse break :parse;
- if (!current.isNumber()) break :parse;
- args.position = current.toU32();
+ if (!(current.isNumber() or current.isBigInt())) break :parse;
+ args.position = current.to(i52);
arguments.eat();
},
}
@@ -1484,8 +1644,8 @@ pub const Arguments = struct {
if (arguments.next()) |current| {
arguments.eat();
- if (current.isNumber()) {
- args.offset = current.toU32();
+ if (current.isNumber() or current.isBigInt()) {
+ args.offset = current.to(u52);
if (arguments.remaining.len < 2) {
JSC.throwInvalidArguments(
@@ -1497,8 +1657,8 @@ pub const Arguments = struct {
return null;
}
-
- args.length = arguments.remaining[0].toU32();
+ if (arguments.remaining[0].isNumber() or arguments.remaining[0].isBigInt())
+ args.length = arguments.remaining[0].to(u52);
if (args.length == 0) {
JSC.throwInvalidArguments(
@@ -1511,26 +1671,26 @@ pub const Arguments = struct {
return null;
}
- const position: i32 = if (arguments.remaining[1].isNumber())
- arguments.remaining[1].toInt32()
- else
- -1;
+ if (arguments.remaining[1].isNumber() or arguments.remaining[1].isBigInt())
+ args.position = @intCast(ReadPosition, arguments.remaining[1].to(i52));
- args.position = if (position > -1) @intCast(ReadPosition, position) else null;
arguments.remaining = arguments.remaining[2..];
} else if (current.isObject()) {
- if (current.getIfPropertyExists(ctx.ptr(), "offset")) |num| {
- args.offset = num.toU32();
+ if (current.getTruthy(ctx.ptr(), "offset")) |num| {
+ if (num.isNumber() or num.isBigInt()) {
+ args.offset = num.to(u52);
+ }
}
- if (current.getIfPropertyExists(ctx.ptr(), "length")) |num| {
- args.length = num.toU32();
+ if (current.getTruthy(ctx.ptr(), "length")) |num| {
+ if (num.isNumber() or num.isBigInt()) {
+ args.length = num.to(u52);
+ }
}
- if (current.getIfPropertyExists(ctx.ptr(), "position")) |num| {
- const position: i32 = if (num.isEmptyOrUndefinedOrNull()) -1 else num.coerce(i32, ctx);
- if (position > -1) {
- args.position = @intCast(ReadPosition, position);
+ if (current.getTruthy(ctx.ptr(), "position")) |num| {
+ if (num.isNumber() or num.isBigInt()) {
+ args.position = num.to(i52);
}
}
}
@@ -2264,7 +2424,7 @@ pub const Arguments = struct {
return CopyFile{
.src = src,
.dest = dest,
- .mode = @intToEnum(Constants.Copyfile, mode),
+ .mode = @enumFromInt(Constants.Copyfile, mode),
};
}
};
@@ -2313,7 +2473,7 @@ pub const Arguments = struct {
};
pub const UnwatchFile = void;
- pub const Watch = void;
+ pub const Watch = JSC.Node.FSWatcher.Arguments;
pub const WatchFile = void;
pub const Fsync = struct {
fd: FileDescriptor,
@@ -2350,6 +2510,18 @@ pub const Arguments = struct {
};
};
+pub const StatOrNotFound = union(enum) {
+ stats: Stats,
+ not_found: void,
+
+ pub fn toJS(this: *StatOrNotFound, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
+ return switch (this.*) {
+ .stats => this.stats.toJS(globalObject),
+ .not_found => JSC.JSValue.undefined,
+ };
+ }
+};
+
const Return = struct {
pub const Access = void;
pub const AppendFile = void;
@@ -2368,11 +2540,12 @@ const Return = struct {
pub const Lchmod = void;
pub const Lchown = void;
pub const Link = void;
- pub const Lstat = Stats;
- pub const Mkdir = string;
+ pub const Lstat = StatOrNotFound;
+ pub const Mkdir = bun.String;
pub const Mkdtemp = JSC.ZigString;
pub const Open = FileDescriptor;
pub const WriteFile = void;
+ pub const Readv = Read;
pub const Read = struct {
bytes_read: u52,
@@ -2469,18 +2642,20 @@ const Return = struct {
pub const RealpathNative = Realpath;
pub const Rename = void;
pub const Rmdir = void;
- pub const Stat = Stats;
+ pub const Stat = StatOrNotFound;
pub const Symlink = void;
pub const Truncate = void;
pub const Unlink = void;
pub const UnwatchFile = void;
- pub const Watch = void;
+ pub const Watch = JSC.JSValue;
pub const WatchFile = void;
pub const Utimes = void;
pub const Chown = void;
pub const Lutimes = void;
+
+ pub const Writev = Write;
};
/// Bun's implementation of the Node.js "fs" module
@@ -2493,12 +2668,13 @@ pub const NodeFS = struct {
/// That means a stack-allocated buffer won't suffice. Instead, we re-use
/// the heap allocated buffer on the NodefS struct
sync_error_buf: [bun.MAX_PATH_BYTES]u8 = undefined,
+ vm: ?*JSC.VirtualMachine = null,
pub const ReturnType = Return;
pub fn access(this: *NodeFS, args: Arguments.Access, comptime _: Flavor) Maybe(Return.Access) {
var path = args.path.sliceZ(&this.sync_error_buf);
- const rc = Syscall.system.access(path, @enumToInt(args.mode));
+ const rc = Syscall.system.access(path, @intFromEnum(args.mode));
return Maybe(Return.Access).errnoSysP(rc, .access, path) orelse Maybe(Return.Access).success;
}
@@ -2528,7 +2704,7 @@ pub const NodeFS = struct {
const path = path_.sliceZ(&this.sync_error_buf);
switch (comptime flavor) {
.sync => {
- const fd = switch (Syscall.open(path, @enumToInt(FileSystemFlags.a), 0o000666)) {
+ const fd = switch (Syscall.open(path, @intFromEnum(FileSystemFlags.a), 0o000666)) {
.result => |result| result,
.err => |err| return .{ .err = err },
};
@@ -2594,7 +2770,7 @@ pub const NodeFS = struct {
};
if (!os.S.ISREG(stat_.mode)) {
- return Maybe(Return.CopyFile){ .err = .{ .errno = @enumToInt(C.SystemErrno.ENOTSUP) } };
+ return Maybe(Return.CopyFile){ .err = .{ .errno = @intFromEnum(C.SystemErrno.ENOTSUP) } };
}
// 64 KB is about the break-even point for clonefile() to be worth it
@@ -2723,7 +2899,7 @@ pub const NodeFS = struct {
};
if (!os.S.ISREG(stat_.mode)) {
- return Maybe(Return.CopyFile){ .err = .{ .errno = @enumToInt(C.SystemErrno.ENOTSUP) } };
+ return Maybe(Return.CopyFile){ .err = .{ .errno = @intFromEnum(C.SystemErrno.ENOTSUP) } };
}
var flags: Mode = std.os.O.CREAT | std.os.O.WRONLY;
@@ -2978,8 +3154,13 @@ pub const NodeFS = struct {
&this.sync_error_buf,
),
)) {
- .result => |result| Maybe(Return.Lstat){ .result = Return.Lstat.init(result, false) },
- .err => |err| Maybe(Return.Lstat){ .err = err },
+ .result => |result| Maybe(Return.Lstat){ .result = .{ .stats = Stats.init(result, args.big_int) } },
+ .err => |err| brk: {
+ if (!args.throw_if_no_entry and err.getErrno() == .NOENT) {
+ return Maybe(Return.Lstat){ .result = .{ .not_found = {} } };
+ }
+ break :brk Maybe(Return.Lstat){ .err = err };
+ },
};
},
else => {},
@@ -2997,7 +3178,7 @@ pub const NodeFS = struct {
.sync => {
const path = args.path.sliceZ(&this.sync_error_buf);
return switch (Syscall.mkdir(path, args.mode)) {
- .result => Maybe(Return.Mkdir){ .result = "" },
+ .result => Maybe(Return.Mkdir){ .result = bun.String.empty },
.err => |err| Maybe(Return.Mkdir){ .err = err },
};
},
@@ -3026,24 +3207,29 @@ pub const NodeFS = struct {
.err => |err| {
switch (err.getErrno()) {
else => {
- @memcpy(&this.sync_error_buf, path.ptr, len);
+ @memcpy(this.sync_error_buf[0..len], path[0..len]);
return .{ .err = err.withPath(this.sync_error_buf[0..len]) };
},
.EXIST => {
- return Option{ .result = "" };
+ return Option{ .result = bun.String.empty };
},
// continue
.NOENT => {},
}
},
.result => {
- return Option{ .result = args.path.slice() };
+ return Option{
+ .result = if (args.path == .slice_with_underlying_string)
+ args.path.slice_with_underlying_string.underlying
+ else
+ bun.String.create(args.path.slice()),
+ };
},
}
var working_mem = &this.sync_error_buf;
- @memcpy(working_mem, path.ptr, len);
+ @memcpy(working_mem[0..len], path[0..len]);
var i: u16 = len - 1;
@@ -3111,10 +3297,9 @@ pub const NodeFS = struct {
switch (err.getErrno()) {
// handle the race condition
.EXIST => {
- var display_path: []const u8 = "";
+ var display_path = bun.String.empty;
if (first_match != std.math.maxInt(u16)) {
- // TODO: this leaks memory
- display_path = bun.default_allocator.dupe(u8, display_path[0..first_match]) catch unreachable;
+ display_path = bun.String.create(working_mem[0..first_match]);
}
return Option{ .result = display_path };
},
@@ -3126,12 +3311,14 @@ pub const NodeFS = struct {
}
},
.result => {
- var display_path = args.path.slice();
- if (first_match != std.math.maxInt(u16)) {
- // TODO: this leaks memory
- display_path = bun.default_allocator.dupe(u8, display_path[0..first_match]) catch unreachable;
- }
- return Option{ .result = display_path };
+ return Option{
+ .result = if (first_match != std.math.maxInt(u16))
+ bun.String.create(working_mem[0..first_match])
+ else if (args.path == .slice_with_underlying_string)
+ args.path.slice_with_underlying_string.underlying
+ else
+ bun.String.create(args.path.slice()),
+ };
},
}
},
@@ -3146,7 +3333,7 @@ pub const NodeFS = struct {
const prefix_slice = args.prefix.slice();
const len = @min(prefix_slice.len, prefix_buf.len -| 7);
if (len > 0) {
- @memcpy(prefix_buf, prefix_slice.ptr, len);
+ @memcpy(prefix_buf[0..len], prefix_slice[0..len]);
}
prefix_buf[len..][0..6].* = "XXXXXX".*;
prefix_buf[len..][6] = 0;
@@ -3162,15 +3349,15 @@ pub const NodeFS = struct {
};
}
// std.c.getErrno(rc) returns SUCCESS if rc is null so we call std.c._errno() directly
- const errno = @intToEnum(std.c.E, std.c._errno().*);
- return .{ .err = Syscall.Error{ .errno = @truncate(Syscall.Error.Int, @enumToInt(errno)), .syscall = .mkdtemp } };
+ const errno = @enumFromInt(std.c.E, std.c._errno().*);
+ return .{ .err = Syscall.Error{ .errno = @truncate(Syscall.Error.Int, @intFromEnum(errno)), .syscall = .mkdtemp } };
}
pub fn open(this: *NodeFS, args: Arguments.Open, comptime flavor: Flavor) Maybe(Return.Open) {
switch (comptime flavor) {
// The sync version does no allocation except when returning the path
.sync => {
const path = args.path.sliceZ(&this.sync_error_buf);
- return switch (Syscall.open(path, @enumToInt(args.flags), args.mode)) {
+ return switch (Syscall.open(path, @intFromEnum(args.flags), args.mode)) {
.err => |err| .{
.err = err.withPath(args.path.slice()),
},
@@ -3250,6 +3437,14 @@ pub const NodeFS = struct {
);
}
+ pub fn readv(this: *NodeFS, args: Arguments.Readv, comptime flavor: Flavor) Maybe(Return.Read) {
+ return if (args.position != null) _preadv(this, args, flavor) else _readv(this, args, flavor);
+ }
+
+ pub fn writev(this: *NodeFS, args: Arguments.Writev, comptime flavor: Flavor) Maybe(Return.Write) {
+ return if (args.position != null) _pwritev(this, args, flavor) else _writev(this, args, flavor);
+ }
+
pub fn write(this: *NodeFS, args: Arguments.Write, comptime flavor: Flavor) Maybe(Return.Write) {
return if (args.position != null) _pwrite(this, args, flavor) else _write(this, args, flavor);
}
@@ -3301,6 +3496,82 @@ pub const NodeFS = struct {
return Maybe(Return.Write).todo;
}
+ fn _preadv(_: *NodeFS, args: Arguments.Readv, comptime flavor: Flavor) Maybe(Return.Readv) {
+ const position = args.position.?;
+
+ switch (comptime flavor) {
+ .sync => {
+ return switch (Syscall.preadv(args.fd, args.buffers.buffers.items, position)) {
+ .err => |err| .{
+ .err = err,
+ },
+ .result => |amt| .{ .result = .{
+ .bytes_read = @truncate(u52, amt),
+ } },
+ };
+ },
+ else => {},
+ }
+
+ return Maybe(Return.Write).todo;
+ }
+
+ fn _readv(_: *NodeFS, args: Arguments.Readv, comptime flavor: Flavor) Maybe(Return.Readv) {
+ switch (comptime flavor) {
+ .sync => {
+ return switch (Syscall.readv(args.fd, args.buffers.buffers.items)) {
+ .err => |err| .{
+ .err = err,
+ },
+ .result => |amt| .{ .result = .{
+ .bytes_read = @truncate(u52, amt),
+ } },
+ };
+ },
+ else => {},
+ }
+
+ return Maybe(Return.Write).todo;
+ }
+
+ fn _pwritev(_: *NodeFS, args: Arguments.Writev, comptime flavor: Flavor) Maybe(Return.Write) {
+ const position = args.position.?;
+
+ switch (comptime flavor) {
+ .sync => {
+ return switch (Syscall.pwritev(args.fd, args.buffers.buffers.items, position)) {
+ .err => |err| .{
+ .err = err,
+ },
+ .result => |amt| .{ .result = .{
+ .bytes_written = @truncate(u52, amt),
+ } },
+ };
+ },
+ else => {},
+ }
+
+ return Maybe(Return.Write).todo;
+ }
+
+ fn _writev(_: *NodeFS, args: Arguments.Writev, comptime flavor: Flavor) Maybe(Return.Write) {
+ switch (comptime flavor) {
+ .sync => {
+ return switch (Syscall.writev(args.fd, args.buffers.buffers.items)) {
+ .err => |err| .{
+ .err = err,
+ },
+ .result => |amt| .{ .result = .{
+ .bytes_written = @truncate(u52, amt),
+ } },
+ };
+ },
+ else => {},
+ }
+
+ return Maybe(Return.Write).todo;
+ }
+
pub fn readdir(this: *NodeFS, args: Arguments.Readdir, comptime flavor: Flavor) Maybe(Return.Readdir) {
return switch (args.encoding) {
.buffer => _readdir(
@@ -3443,6 +3714,35 @@ pub const NodeFS = struct {
const fd = switch (args.path) {
.path => brk: {
path = args.path.path.sliceZ(&this.sync_error_buf);
+ if (this.vm) |vm| {
+ if (vm.standalone_module_graph) |graph| {
+ if (graph.find(path)) |file| {
+ if (args.encoding == .buffer) {
+ return .{
+ .result = .{
+ .buffer = Buffer.fromBytes(
+ bun.default_allocator.dupe(u8, file.contents) catch @panic("out of memory"),
+ bun.default_allocator,
+ .Uint8Array,
+ ),
+ },
+ };
+ } else if (comptime string_type == .default)
+ return .{
+ .result = .{
+ .string = bun.default_allocator.dupe(u8, file.contents) catch @panic("out of memory"),
+ },
+ }
+ else
+ return .{
+ .result = .{
+ .null_terminated = bun.default_allocator.dupeZ(u8, file.contents) catch @panic("out of memory"),
+ },
+ };
+ }
+ }
+ }
+
break :brk switch (Syscall.open(
path,
os.O.RDONLY | os.O.NOCTTY,
@@ -3605,7 +3905,7 @@ pub const NodeFS = struct {
break :brk switch (Syscall.openat(
args.dirfd,
path,
- @enumToInt(args.flag) | os.O.NOCTTY,
+ @intFromEnum(args.flag) | os.O.NOCTTY,
args.mode,
)) {
.err => |err| return .{
@@ -3672,7 +3972,7 @@ pub const NodeFS = struct {
}
// https://github.com/oven-sh/bun/issues/2931
- if ((@enumToInt(args.flag) & std.os.O.APPEND) == 0) {
+ if ((@intFromEnum(args.flag) & std.os.O.APPEND) == 0) {
_ = ftruncateSync(.{ .fd = fd, .len = @truncate(JSC.WebCore.Blob.SizeType, written) });
}
@@ -3819,12 +4119,12 @@ pub const NodeFS = struct {
while (true) {
if (Maybe(Return.Rmdir).errnoSys(bun.C.darwin.removefileat(std.os.AT.FDCWD, dest, null, flags), .rmdir)) |errno| {
- switch (@intToEnum(os.E, errno.err.errno)) {
+ switch (@enumFromInt(os.E, errno.err.errno)) {
.AGAIN, .INTR => continue,
.NOENT => return Maybe(Return.Rmdir).success,
.MLINK => {
var copy: [bun.MAX_PATH_BYTES]u8 = undefined;
- @memcpy(&copy, dest.ptr, dest.len);
+ @memcpy(copy[0..dest.len], dest);
copy[dest.len] = 0;
var dest_copy = copy[0..dest.len :0];
switch (Syscall.unlink(dest_copy).getErrno()) {
@@ -3906,7 +4206,7 @@ pub const NodeFS = struct {
}
if (Maybe(Return.Rm).errnoSys(bun.C.darwin.removefileat(std.os.AT.FDCWD, dest, null, flags), .unlink)) |errno| {
- switch (@intToEnum(os.E, errno.err.errno)) {
+ switch (@enumFromInt(os.E, errno.err.errno)) {
.AGAIN, .INTR => continue,
.NOENT => {
if (args.force) {
@@ -3918,7 +4218,7 @@ pub const NodeFS = struct {
.MLINK => {
var copy: [bun.MAX_PATH_BYTES]u8 = undefined;
- @memcpy(&copy, dest.ptr, dest.len);
+ @memcpy(copy[0..dest.len], dest);
copy[dest.len] = 0;
var dest_copy = copy[0..dest.len :0];
switch (Syscall.unlink(dest_copy).getErrno()) {
@@ -4063,8 +4363,13 @@ pub const NodeFS = struct {
&this.sync_error_buf,
),
)) {
- .result => |result| Maybe(Return.Stat){ .result = Return.Stat.init(result, false) },
- .err => |err| Maybe(Return.Stat){ .err = err },
+ .result => |result| Maybe(Return.Stat){ .result = .{ .stats = Stats.init(result, args.big_int) } },
+ .err => |err| brk: {
+ if (!args.throw_if_no_entry and err.getErrno() == .NOENT) {
+ return Maybe(Return.Stat){ .result = .{ .not_found = {} } };
+ }
+ break :brk Maybe(Return.Stat){ .err = err };
+ },
});
},
else => {},
@@ -4181,8 +4486,12 @@ pub const NodeFS = struct {
return Maybe(Return.Lutimes).todo;
}
- pub fn watch(_: *NodeFS, _: Arguments.Watch, comptime _: Flavor) Maybe(Return.Watch) {
- return Maybe(Return.Watch).todo;
+ pub fn watch(_: *NodeFS, args: Arguments.Watch, comptime _: Flavor) Maybe(Return.Watch) {
+ const watcher = args.createFSWatcher() catch |err| {
+ args.global_this.throwError(err, "Failed to watch filename");
+ return Maybe(Return.Watch){ .result = JSC.JSValue.jsUndefined() };
+ };
+ return Maybe(Return.Watch){ .result = watcher };
}
pub fn createReadStream(_: *NodeFS, _: Arguments.CreateReadStream, comptime _: Flavor) Maybe(Return.CreateReadStream) {
return Maybe(Return.CreateReadStream).todo;
diff --git a/src/bun.js/node/node_fs_binding.zig b/src/bun.js/node/node_fs_binding.zig
index 74b769bf6..a4cc62cd3 100644
--- a/src/bun.js/node/node_fs_binding.zig
+++ b/src/bun.js/node/node_fs_binding.zig
@@ -229,6 +229,10 @@ pub const NodeJSFS = struct {
pub const lutimesSync = callSync(.lutimes);
pub const rmSync = callSync(.rm);
pub const rmdirSync = callSync(.rmdir);
+ pub const writev = call(.writev);
+ pub const writevSync = callSync(.writev);
+ pub const readv = call(.readv);
+ pub const readvSync = callSync(.readv);
pub const fdatasyncSync = callSync(.fdatasync);
pub const fdatasync = call(.fdatasync);
@@ -241,12 +245,10 @@ pub const NodeJSFS = struct {
return JSC.Node.Stats.getConstructor(globalThis);
}
+ pub const watch = callSync(.watch);
+
// Not implemented yet:
const notimpl = fdatasync;
pub const opendir = notimpl;
pub const opendirSync = notimpl;
- pub const readv = notimpl;
- pub const readvSync = notimpl;
- pub const writev = notimpl;
- pub const writevSync = notimpl;
};
diff --git a/src/bun.js/node/node_fs_constant.zig b/src/bun.js/node/node_fs_constant.zig
index 378f332c6..8e642ebad 100644
--- a/src/bun.js/node/node_fs_constant.zig
+++ b/src/bun.js/node/node_fs_constant.zig
@@ -26,17 +26,17 @@ pub const Constants = struct {
pub const force = 4;
pub inline fn isForceClone(this: Copyfile) bool {
- return (@enumToInt(this) & COPYFILE_FICLONE_FORCE) != 0;
+ return (@intFromEnum(this) & COPYFILE_FICLONE_FORCE) != 0;
}
pub inline fn shouldntOverwrite(this: Copyfile) bool {
- return (@enumToInt(this) & COPYFILE_EXCL) != 0;
+ return (@intFromEnum(this) & COPYFILE_EXCL) != 0;
}
pub inline fn canUseClone(this: Copyfile) bool {
_ = this;
return Environment.isMac;
- // return (@enumToInt(this) | COPYFILE_FICLONE) != 0;
+ // return (@intFromEnum(this) | COPYFILE_FICLONE) != 0;
}
};
diff --git a/src/bun.js/node/node_fs_watcher.zig b/src/bun.js/node/node_fs_watcher.zig
new file mode 100644
index 000000000..d0af350c0
--- /dev/null
+++ b/src/bun.js/node/node_fs_watcher.zig
@@ -0,0 +1,919 @@
+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 FSEvents = @import("./fs_events.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;
+
+pub const FSWatcher = struct {
+ const watcher = @import("../../watcher.zig");
+ const options = @import("../../options.zig");
+ pub const Watcher = watcher.NewWatcher(*FSWatcher);
+ const log = Output.scoped(.FSWatcher, false);
+
+ pub const ChangeEvent = struct {
+ hash: Watcher.HashType = 0,
+ event_type: FSWatchTask.EventType = .change,
+ time_stamp: i64 = 0,
+ };
+
+ onAccept: std.ArrayHashMapUnmanaged(FSWatcher.Watcher.HashType, bun.BabyList(OnAcceptCallback), bun.ArrayIdentityContext, false) = .{},
+ ctx: *VirtualMachine,
+ verbose: bool = false,
+ file_paths: bun.BabyList(string) = .{},
+ entry_path: ?string = null,
+ entry_dir: string = "",
+ last_change_event: ChangeEvent = .{},
+
+ // JSObject
+ mutex: Mutex,
+ signal: ?*JSC.AbortSignal,
+ persistent: bool,
+ default_watcher: ?*FSWatcher.Watcher,
+ fsevents_watcher: ?*FSEvents.FSEventsWatcher,
+ poll_ref: JSC.PollRef = .{},
+ globalThis: *JSC.JSGlobalObject,
+ js_this: JSC.JSValue,
+ encoding: JSC.Node.Encoding,
+ // user can call close and pre-detach so we need to track this
+ closed: bool,
+ // counts pending tasks so we only deinit after all tasks are done
+ task_count: u32,
+ has_pending_activity: std.atomic.Atomic(bool),
+ pub usingnamespace JSC.Codegen.JSFSWatcher;
+
+ pub fn eventLoop(this: FSWatcher) *EventLoop {
+ return this.ctx.eventLoop();
+ }
+
+ pub fn enqueueTaskConcurrent(this: FSWatcher, task: *JSC.ConcurrentTask) void {
+ this.eventLoop().enqueueTaskConcurrent(task);
+ }
+
+ pub fn deinit(this: *FSWatcher) void {
+ // stop all managers and signals
+ this.detach();
+
+ while (this.file_paths.popOrNull()) |file_path| {
+ bun.default_allocator.destroy(file_path);
+ }
+ this.file_paths.deinitWithAllocator(bun.default_allocator);
+ if (this.entry_path) |path| {
+ this.entry_path = null;
+ bun.default_allocator.destroy(path);
+ }
+ bun.default_allocator.destroy(this);
+ }
+
+ pub const FSWatchTask = struct {
+ ctx: *FSWatcher,
+ count: u8 = 0,
+
+ entries: [8]Entry = undefined,
+ concurrent_task: JSC.ConcurrentTask = undefined,
+
+ pub const EventType = enum {
+ rename,
+ change,
+ @"error",
+ abort,
+ };
+
+ pub const EventFreeType = enum {
+ destroy,
+ free,
+ none,
+ };
+
+ pub const Entry = struct {
+ file_path: string,
+ event_type: EventType,
+ free_type: EventFreeType,
+ };
+
+ pub fn append(this: *FSWatchTask, file_path: string, event_type: EventType, free_type: EventFreeType) void {
+ if (this.count == 8) {
+ this.enqueue();
+ var ctx = this.ctx;
+ this.* = .{
+ .ctx = ctx,
+ .count = 0,
+ };
+ }
+
+ this.entries[this.count] = .{
+ .file_path = file_path,
+ .event_type = event_type,
+ .free_type = free_type,
+ };
+ this.count += 1;
+ }
+
+ pub fn run(this: *FSWatchTask) void {
+ // this runs on JS Context Thread
+
+ for (this.entries[0..this.count]) |entry| {
+ switch (entry.event_type) {
+ .rename => {
+ this.ctx.emit(entry.file_path, "rename");
+ },
+ .change => {
+ this.ctx.emit(entry.file_path, "change");
+ },
+ .@"error" => {
+ // file_path is the error message in this case
+ this.ctx.emitError(entry.file_path);
+ },
+ .abort => {
+ this.ctx.emitIfAborted();
+ },
+ }
+ }
+
+ this.ctx.unrefTask();
+ }
+
+ pub fn enqueue(this: *FSWatchTask) void {
+ if (this.count == 0)
+ return;
+
+ // if false is closed or detached (can still contain valid refs but will not create a new one)
+ if (this.ctx.refTask()) {
+ var that = bun.default_allocator.create(FSWatchTask) catch unreachable;
+
+ that.* = this.*;
+ this.count = 0;
+ that.concurrent_task.task = JSC.Task.init(that);
+ this.ctx.enqueueTaskConcurrent(&that.concurrent_task);
+ return;
+ }
+ // closed or detached so just cleanEntries
+ this.cleanEntries();
+ }
+ pub fn cleanEntries(this: *FSWatchTask) void {
+ while (this.count > 0) {
+ this.count -= 1;
+ switch (this.entries[this.count].free_type) {
+ .destroy => bun.default_allocator.destroy(this.entries[this.count].file_path),
+ .free => bun.default_allocator.free(this.entries[this.count].file_path),
+ else => {},
+ }
+ }
+ }
+
+ pub fn deinit(this: *FSWatchTask) void {
+ this.cleanEntries();
+ bun.default_allocator.destroy(this);
+ }
+ };
+
+ fn NewCallback(comptime FunctionSignature: type) type {
+ return union(enum) {
+ javascript_callback: JSC.Strong,
+ zig_callback: struct {
+ ptr: *anyopaque,
+ function: *const FunctionSignature,
+ },
+ };
+ }
+
+ pub const OnAcceptCallback = NewCallback(fn (
+ vm: *JSC.VirtualMachine,
+ specifier: []const u8,
+ ) void);
+
+ fn addDirectory(ctx: *FSWatcher, fs_watcher: *FSWatcher.Watcher, fd: StoredFileDescriptorType, file_path: string, recursive: bool, buf: *[bun.MAX_PATH_BYTES + 1]u8, is_entry_path: bool) !void {
+ var dir_path_clone = bun.default_allocator.dupeZ(u8, file_path) catch unreachable;
+
+ if (is_entry_path) {
+ ctx.entry_path = dir_path_clone;
+ ctx.entry_dir = dir_path_clone;
+ } else {
+ ctx.file_paths.push(bun.default_allocator, dir_path_clone) catch unreachable;
+ }
+ fs_watcher.addDirectory(fd, dir_path_clone, FSWatcher.Watcher.getHash(file_path), false) catch |err| {
+ ctx.deinit();
+ fs_watcher.deinit(true);
+ return err;
+ };
+
+ var iter = (std.fs.IterableDir{ .dir = std.fs.Dir{
+ .fd = fd,
+ } }).iterate();
+
+ while (iter.next() catch |err| {
+ ctx.deinit();
+ fs_watcher.deinit(true);
+ return err;
+ }) |entry| {
+ var parts = [2]string{ dir_path_clone, entry.name };
+ var entry_path = Path.joinAbsStringBuf(
+ Fs.FileSystem.instance.topLevelDirWithoutTrailingSlash(),
+ buf,
+ &parts,
+ .auto,
+ );
+
+ buf[entry_path.len] = 0;
+ var entry_path_z = buf[0..entry_path.len :0];
+
+ var fs_info = fdFromAbsolutePathZ(entry_path_z) catch |err| {
+ ctx.deinit();
+ fs_watcher.deinit(true);
+ return err;
+ };
+
+ if (fs_info.is_file) {
+ const file_path_clone = bun.default_allocator.dupeZ(u8, entry_path) catch unreachable;
+
+ ctx.file_paths.push(bun.default_allocator, file_path_clone) catch unreachable;
+
+ fs_watcher.addFile(fs_info.fd, file_path_clone, FSWatcher.Watcher.getHash(entry_path), options.Loader.file, 0, null, false) catch |err| {
+ ctx.deinit();
+ fs_watcher.deinit(true);
+ return err;
+ };
+ } else {
+ if (recursive) {
+ addDirectory(ctx, fs_watcher, fs_info.fd, entry_path, recursive, buf, false) catch |err| {
+ ctx.deinit();
+ fs_watcher.deinit(true);
+ return err;
+ };
+ }
+ }
+ }
+ }
+
+ pub fn onError(
+ this: *FSWatcher,
+ err: anyerror,
+ ) void {
+ var current_task: FSWatchTask = .{
+ .ctx = this,
+ };
+ current_task.append(@errorName(err), .@"error", .none);
+ current_task.enqueue();
+ }
+
+ pub fn onFSEventUpdate(
+ ctx: ?*anyopaque,
+ path: string,
+ _: bool,
+ is_rename: bool,
+ ) void {
+ const this = bun.cast(*FSWatcher, ctx.?);
+
+ var current_task: FSWatchTask = .{
+ .ctx = this,
+ };
+ defer current_task.enqueue();
+
+ const relative_path = bun.default_allocator.dupe(u8, path) catch unreachable;
+ const event_type: FSWatchTask.EventType = if (is_rename) .rename else .change;
+
+ current_task.append(relative_path, event_type, .destroy);
+ }
+
+ pub fn onFileUpdate(
+ this: *FSWatcher,
+ events: []watcher.WatchEvent,
+ changed_files: []?[:0]u8,
+ watchlist: watcher.Watchlist,
+ ) void {
+ var slice = watchlist.slice();
+ const file_paths = slice.items(.file_path);
+
+ var counts = slice.items(.count);
+ const kinds = slice.items(.kind);
+ var _on_file_update_path_buf: [bun.MAX_PATH_BYTES]u8 = undefined;
+
+ var ctx = this.default_watcher.?;
+ defer ctx.flushEvictions();
+ defer Output.flush();
+
+ var bundler = if (@TypeOf(this.ctx.bundler) == *bun.Bundler)
+ this.ctx.bundler
+ else
+ &this.ctx.bundler;
+
+ var fs: *Fs.FileSystem = bundler.fs;
+
+ var current_task: FSWatchTask = .{
+ .ctx = this,
+ };
+ defer current_task.enqueue();
+
+ const time_stamp = std.time.milliTimestamp();
+ const time_diff = time_stamp - this.last_change_event.time_stamp;
+
+ for (events) |event| {
+ const file_path = file_paths[event.index];
+ const update_count = counts[event.index] + 1;
+ counts[event.index] = update_count;
+ const kind = kinds[event.index];
+
+ if (comptime Environment.isDebug) {
+ if (this.verbose) {
+ Output.prettyErrorln("[watch] {s} ({s}, {})", .{ file_path, @tagName(kind), event.op });
+ }
+ }
+
+ switch (kind) {
+ .file => {
+ if (event.op.delete) {
+ ctx.removeAtIndex(
+ event.index,
+ 0,
+ &.{},
+ .file,
+ );
+ }
+
+ var file_hash: FSWatcher.Watcher.HashType = FSWatcher.Watcher.getHash(file_path);
+
+ if (event.op.write or event.op.delete or event.op.rename) {
+ const event_type: FSWatchTask.EventType = if (event.op.delete or event.op.rename or event.op.move_to) .rename else .change;
+ // skip consecutive duplicates
+ if ((this.last_change_event.time_stamp == 0 or time_diff > 1) or this.last_change_event.event_type != event_type and this.last_change_event.hash != file_hash) {
+ this.last_change_event.time_stamp = time_stamp;
+ this.last_change_event.event_type = event_type;
+ this.last_change_event.hash = file_hash;
+
+ const relative_slice = fs.relative(this.entry_dir, file_path);
+
+ if (this.verbose)
+ Output.prettyErrorln("<r><d>File changed: {s}<r>", .{relative_slice});
+
+ const relative_path = bun.default_allocator.dupe(u8, relative_slice) catch unreachable;
+
+ current_task.append(relative_path, event_type, .destroy);
+ }
+ }
+ },
+ .directory => {
+ // macOS should use FSEvents for directories
+ if (comptime Environment.isMac) {
+ @panic("Unexpected directory watch");
+ }
+
+ const affected = event.names(changed_files);
+
+ for (affected) |changed_name_| {
+ const changed_name: []const u8 = bun.asByteSlice(changed_name_.?);
+ if (changed_name.len == 0 or changed_name[0] == '~' or changed_name[0] == '.') continue;
+
+ var file_hash: FSWatcher.Watcher.HashType = 0;
+ const relative_slice: string = brk: {
+ var file_path_without_trailing_slash = std.mem.trimRight(u8, file_path, std.fs.path.sep_str);
+
+ @memcpy(_on_file_update_path_buf[0..file_path_without_trailing_slash.len], file_path_without_trailing_slash);
+
+ _on_file_update_path_buf[file_path_without_trailing_slash.len] = std.fs.path.sep;
+
+ @memcpy(_on_file_update_path_buf[file_path_without_trailing_slash.len + 1 ..][0..changed_name.len], changed_name);
+ const path_slice = _on_file_update_path_buf[0 .. file_path_without_trailing_slash.len + changed_name.len + 1];
+ file_hash = FSWatcher.Watcher.getHash(path_slice);
+
+ const relative = fs.relative(this.entry_dir, path_slice);
+
+ break :brk relative;
+ };
+
+ // skip consecutive duplicates
+ const event_type: FSWatchTask.EventType = .rename; // renaming folders, creating folder or files will be always be rename
+ if ((this.last_change_event.time_stamp == 0 or time_diff > 1) or this.last_change_event.event_type != event_type and this.last_change_event.hash != file_hash) {
+ const relative_path = bun.default_allocator.dupe(u8, relative_slice) catch unreachable;
+
+ this.last_change_event.time_stamp = time_stamp;
+ this.last_change_event.event_type = event_type;
+ this.last_change_event.hash = file_hash;
+
+ current_task.append(relative_path, event_type, .destroy);
+
+ if (this.verbose)
+ Output.prettyErrorln("<r> <d>Dir change: {s}<r>", .{relative_path});
+ }
+ }
+
+ if (this.verbose and affected.len == 0) {
+ Output.prettyErrorln("<r> <d>Dir change: {s}<r>", .{fs.relative(this.entry_dir, file_path)});
+ }
+ },
+ }
+ }
+ }
+
+ pub const Arguments = struct {
+ path: PathLike,
+ listener: JSC.JSValue,
+ global_this: JSC.C.JSContextRef,
+ signal: ?*JSC.AbortSignal,
+ persistent: bool,
+ recursive: bool,
+ encoding: JSC.Node.Encoding,
+ verbose: bool,
+ 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 signal: ?*JSC.AbortSignal = null;
+ var persistent: bool = true;
+ var recursive: bool = false;
+ var encoding: JSC.Node.Encoding = .utf8;
+ var verbose = false;
+ 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, "verbose")) |verbose_| {
+ if (!verbose_.isBoolean()) {
+ JSC.throwInvalidArguments(
+ "verbose must be a boolean.",
+ .{},
+ ctx,
+ exception,
+ );
+ return null;
+ }
+ verbose = verbose_.toBoolean();
+ }
+
+ if (options_or_callable.get(ctx, "encoding")) |encoding_| {
+ if (!encoding_.isString()) {
+ JSC.throwInvalidArguments(
+ "encoding must be a string.",
+ .{},
+ ctx,
+ exception,
+ );
+ return null;
+ }
+ if (JSC.Node.Encoding.fromJS(encoding_, ctx.ptr())) |node_encoding| {
+ encoding = node_encoding;
+ } else {
+ JSC.throwInvalidArguments(
+ "invalid encoding.",
+ .{},
+ ctx,
+ exception,
+ );
+ return null;
+ }
+ }
+
+ if (options_or_callable.get(ctx, "recursive")) |recursive_| {
+ if (!recursive_.isBoolean()) {
+ JSC.throwInvalidArguments(
+ "recursive must be a boolean.",
+ .{},
+ ctx,
+ exception,
+ );
+ return null;
+ }
+ recursive = recursive_.toBoolean();
+ }
+
+ // abort signal
+ if (options_or_callable.get(ctx, "signal")) |signal_| {
+ if (JSC.AbortSignal.fromJS(signal_)) |signal_obj| {
+ //Keep it alive
+ signal_.ensureStillAlive();
+ signal = signal_obj;
+ } else {
+ JSC.throwInvalidArguments(
+ "signal is not of type AbortSignal.",
+ .{},
+ ctx,
+ exception,
+ );
+
+ return null;
+ }
+ }
+
+ // listener
+ if (arguments.nextEat()) |callable| {
+ if (!callable.isCell() or !callable.isCallable(vm)) {
+ exception.* = JSC.toInvalidArguments("Expected \"listener\" callback to be a function", .{}, ctx).asObjectRef();
+ return null;
+ }
+ listener = callable;
+ }
+ } else {
+ if (!options_or_callable.isCell() or !options_or_callable.isCallable(vm)) {
+ exception.* = JSC.toInvalidArguments("Expected \"listener\" callback to be a function", .{}, ctx).asObjectRef();
+ return null;
+ }
+ listener = options_or_callable;
+ }
+ }
+ if (listener == .zero) {
+ exception.* = JSC.toInvalidArguments("Expected \"listener\" callback", .{}, ctx).asObjectRef();
+ return null;
+ }
+
+ return Arguments{
+ .path = path,
+ .listener = listener,
+ .global_this = ctx,
+ .signal = signal,
+ .persistent = persistent,
+ .recursive = recursive,
+ .encoding = encoding,
+ .verbose = verbose,
+ };
+ }
+
+ pub fn createFSWatcher(this: Arguments) !JSC.JSValue {
+ const obj = try FSWatcher.init(this);
+ if (obj.js_this != .zero) {
+ return obj.js_this;
+ }
+ return JSC.JSValue.jsUndefined();
+ }
+ };
+
+ pub fn initJS(this: *FSWatcher, listener: JSC.JSValue) void {
+ if (this.persistent) {
+ this.poll_ref.ref(this.ctx);
+ }
+
+ const js_this = FSWatcher.toJS(this, this.globalThis);
+ js_this.ensureStillAlive();
+ this.js_this = js_this;
+ FSWatcher.listenerSetCached(js_this, this.globalThis, listener);
+
+ if (this.signal) |s| {
+ // already aborted?
+ if (s.aborted()) {
+ // safely abort next tick
+ var current_task: FSWatchTask = .{
+ .ctx = this,
+ };
+ current_task.append("", .abort, .none);
+ current_task.enqueue();
+ } else {
+ // watch for abortion
+ this.signal = s.listen(FSWatcher, this, FSWatcher.emitAbort);
+ }
+ }
+ }
+
+ pub fn emitIfAborted(this: *FSWatcher) void {
+ if (this.signal) |s| {
+ if (s.aborted()) {
+ const err = s.abortReason();
+ this.emitAbort(err);
+ }
+ }
+ }
+
+ pub fn emitAbort(this: *FSWatcher, err: JSC.JSValue) void {
+ if (this.closed) return;
+ defer this.close();
+
+ err.ensureStillAlive();
+ if (this.js_this != .zero) {
+ const js_this = this.js_this;
+ js_this.ensureStillAlive();
+ if (FSWatcher.listenerGetCached(js_this)) |listener| {
+ listener.ensureStillAlive();
+ var args = [_]JSC.JSValue{
+ JSC.ZigString.static("error").toValue(this.globalThis),
+ if (err.isEmptyOrUndefinedOrNull()) JSC.WebCore.AbortSignal.createAbortError(JSC.ZigString.static("The user aborted a request"), &JSC.ZigString.Empty, this.globalThis) else err,
+ };
+ _ = listener.callWithGlobalThis(
+ this.globalThis,
+ &args,
+ );
+ }
+ }
+ }
+ pub fn emitError(this: *FSWatcher, err: string) void {
+ if (this.closed) return;
+ defer this.close();
+
+ if (this.js_this != .zero) {
+ const js_this = this.js_this;
+ js_this.ensureStillAlive();
+ if (FSWatcher.listenerGetCached(js_this)) |listener| {
+ listener.ensureStillAlive();
+ var args = [_]JSC.JSValue{
+ JSC.ZigString.static("error").toValue(this.globalThis),
+ JSC.ZigString.fromUTF8(err).toErrorInstance(this.globalThis),
+ };
+ _ = listener.callWithGlobalThis(
+ this.globalThis,
+ &args,
+ );
+ }
+ }
+ }
+
+ pub fn emit(this: *FSWatcher, file_name: string, comptime eventType: string) void {
+ if (this.js_this != .zero) {
+ const js_this = this.js_this;
+ js_this.ensureStillAlive();
+ if (FSWatcher.listenerGetCached(js_this)) |listener| {
+ listener.ensureStillAlive();
+ var filename: JSC.JSValue = JSC.JSValue.jsUndefined();
+ if (file_name.len > 0) {
+ if (this.encoding == .buffer)
+ filename = JSC.ArrayBuffer.createBuffer(this.globalThis, file_name)
+ else if (this.encoding == .utf8) {
+ filename = JSC.ZigString.fromUTF8(file_name).toValueGC(this.globalThis);
+ } else {
+ // convert to desired encoding
+ filename = Encoder.toStringAtRuntime(file_name.ptr, file_name.len, this.globalThis, this.encoding);
+ }
+ }
+ var args = [_]JSC.JSValue{
+ JSC.ZigString.static(eventType).toValue(this.globalThis),
+ filename,
+ };
+ _ = listener.callWithGlobalThis(
+ this.globalThis,
+ &args,
+ );
+ }
+ }
+ }
+
+ pub fn doRef(this: *FSWatcher, _: *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: *FSWatcher, _: *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 hasRef(this: *FSWatcher, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
+ return JSC.JSValue.jsBoolean(this.persistent);
+ }
+
+ // this can be called from Watcher Thread or JS Context Thread
+ pub fn refTask(this: *FSWatcher) bool {
+ this.mutex.lock();
+ defer this.mutex.unlock();
+ // stop new references
+ if (this.closed) return false;
+ this.task_count += 1;
+ return true;
+ }
+
+ pub fn hasPendingActivity(this: *FSWatcher) callconv(.C) bool {
+ @fence(.Acquire);
+ return this.has_pending_activity.load(.Acquire);
+ }
+ // only called from Main Thread
+ pub fn updateHasPendingActivity(this: *FSWatcher) void {
+ @fence(.Release);
+ this.has_pending_activity.store(false, .Release);
+ }
+
+ // unref is always called on main JS Context Thread
+ pub fn unrefTask(this: *FSWatcher) void {
+ this.mutex.lock();
+ defer this.mutex.unlock();
+ this.task_count -= 1;
+ if (this.closed and this.task_count == 0) {
+ this.updateHasPendingActivity();
+ }
+ }
+
+ pub fn close(
+ this: *FSWatcher,
+ ) void {
+ this.mutex.lock();
+ if (!this.closed) {
+ this.closed = true;
+
+ // emit should only be called unlocked
+ this.mutex.unlock();
+
+ this.emit("", "close");
+ // we immediately detach here
+ this.detach();
+
+ // no need to lock again, because ref checks closed and unref is only called on main thread
+ if (this.task_count == 0) {
+ this.updateHasPendingActivity();
+ }
+ } else {
+ this.mutex.unlock();
+ }
+ }
+
+ // this can be called multiple times
+ pub fn detach(this: *FSWatcher) void {
+ if (this.persistent) {
+ this.persistent = false;
+ this.poll_ref.unref(this.ctx);
+ }
+
+ if (this.signal) |signal| {
+ this.signal = null;
+ signal.detach(this);
+ }
+
+ if (this.default_watcher) |default_watcher| {
+ this.default_watcher = null;
+ default_watcher.deinit(true);
+ }
+
+ if (this.fsevents_watcher) |fsevents_watcher| {
+ this.fsevents_watcher = null;
+ fsevents_watcher.deinit();
+ }
+
+ this.js_this = .zero;
+ }
+
+ pub fn doClose(this: *FSWatcher, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
+ this.close();
+ return JSC.JSValue.jsUndefined();
+ }
+
+ pub fn finalize(this: *FSWatcher) callconv(.C) void {
+ this.deinit();
+ }
+
+ const PathResult = struct {
+ fd: StoredFileDescriptorType = 0,
+ is_file: bool = true,
+ };
+
+ fn fdFromAbsolutePathZ(
+ absolute_path_z: [:0]const u8,
+ ) !PathResult {
+ var stat = try bun.C.lstat_absolute(absolute_path_z);
+ var result = PathResult{};
+
+ switch (stat.kind) {
+ .sym_link => {
+ var file = try std.fs.openFileAbsoluteZ(absolute_path_z, .{ .mode = .read_only });
+ result.fd = file.handle;
+ const _stat = try file.stat();
+
+ result.is_file = _stat.kind != .directory;
+ },
+ .directory => {
+ const dir = (try std.fs.openIterableDirAbsoluteZ(absolute_path_z, .{
+ .access_sub_paths = true,
+ })).dir;
+ result.fd = dir.fd;
+ result.is_file = false;
+ },
+ else => {
+ const file = try std.fs.openFileAbsoluteZ(absolute_path_z, .{ .mode = .read_only });
+ result.fd = file.handle;
+ result.is_file = true;
+ },
+ }
+ return result;
+ }
+
+ pub fn init(args: Arguments) !*FSWatcher {
+ 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,
+ );
+
+ buf[file_path.len] = 0;
+ var file_path_z = buf[0..file_path.len :0];
+
+ var fs_type = try fdFromAbsolutePathZ(file_path_z);
+
+ var ctx = try bun.default_allocator.create(FSWatcher);
+ const vm = args.global_this.bunVM();
+ ctx.* = .{
+ .ctx = vm,
+ .mutex = Mutex.init(),
+ .signal = if (args.signal) |s| s.ref() else null,
+ .persistent = args.persistent,
+ .default_watcher = null,
+ .fsevents_watcher = null,
+ .globalThis = args.global_this,
+ .js_this = .zero,
+ .encoding = args.encoding,
+ .closed = false,
+ .task_count = 0,
+ .has_pending_activity = std.atomic.Atomic(bool).init(true),
+ .verbose = args.verbose,
+ .file_paths = bun.BabyList(string).initCapacity(bun.default_allocator, 1) catch |err| {
+ ctx.deinit();
+ return err;
+ },
+ };
+
+ if (comptime Environment.isMac) {
+ if (!fs_type.is_file) {
+ var dir_path_clone = bun.default_allocator.dupeZ(u8, file_path) catch unreachable;
+ ctx.entry_path = dir_path_clone;
+ ctx.entry_dir = dir_path_clone;
+
+ ctx.fsevents_watcher = FSEvents.watch(dir_path_clone, args.recursive, onFSEventUpdate, bun.cast(*anyopaque, ctx)) catch |err| {
+ ctx.deinit();
+ return err;
+ };
+
+ ctx.initJS(args.listener);
+ return ctx;
+ }
+ }
+
+ var default_watcher = FSWatcher.Watcher.init(
+ ctx,
+ vm.bundler.fs,
+ bun.default_allocator,
+ ) catch |err| {
+ ctx.deinit();
+ return err;
+ };
+
+ ctx.default_watcher = default_watcher;
+
+ if (fs_type.is_file) {
+ var file_path_clone = bun.default_allocator.dupeZ(u8, file_path) catch unreachable;
+
+ ctx.entry_path = file_path_clone;
+ ctx.entry_dir = std.fs.path.dirname(file_path_clone) orelse file_path_clone;
+
+ default_watcher.addFile(fs_type.fd, file_path_clone, FSWatcher.Watcher.getHash(file_path), options.Loader.file, 0, null, false) catch |err| {
+ ctx.deinit();
+ return err;
+ };
+ } else {
+ addDirectory(ctx, default_watcher, fs_type.fd, file_path, args.recursive, &buf, true) catch |err| {
+ ctx.deinit();
+ return err;
+ };
+ }
+
+ default_watcher.start() catch |err| {
+ ctx.deinit();
+ return err;
+ };
+
+ ctx.initJS(args.listener);
+ return ctx;
+ }
+};
diff --git a/src/bun.js/node/node_os.zig b/src/bun.js/node/node_os.zig
index 4b37640cf..483acb3e2 100644
--- a/src/bun.js/node/node_os.zig
+++ b/src/bun.js/node/node_os.zig
@@ -16,7 +16,7 @@ pub const Os = struct {
pub const code = @embedFile("../os.exports.js");
pub fn create(globalObject: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue {
- const module = JSC.JSValue.createEmptyObject(globalObject, 20);
+ const module = JSC.JSValue.createEmptyObject(globalObject, 22);
module.put(globalObject, JSC.ZigString.static("arch"), JSC.NewFunction(globalObject, JSC.ZigString.static("arch"), 0, arch, true));
module.put(globalObject, JSC.ZigString.static("cpus"), JSC.NewFunction(globalObject, JSC.ZigString.static("cpus"), 0, cpus, true));
@@ -31,7 +31,6 @@ pub const Os = struct {
module.put(globalObject, JSC.ZigString.static("platform"), JSC.NewFunction(globalObject, JSC.ZigString.static("platform"), 0, platform, true));
module.put(globalObject, JSC.ZigString.static("release"), JSC.NewFunction(globalObject, JSC.ZigString.static("release"), 0, release, true));
module.put(globalObject, JSC.ZigString.static("setPriority"), JSC.NewFunction(globalObject, JSC.ZigString.static("setPriority"), 2, setPriority, true));
- module.put(globalObject, JSC.ZigString.static("tmpdir"), JSC.NewFunction(globalObject, JSC.ZigString.static("tmpdir"), 0, tmpdir, true));
module.put(globalObject, JSC.ZigString.static("totalmem"), JSC.NewFunction(globalObject, JSC.ZigString.static("totalmem"), 0, totalmem, true));
module.put(globalObject, JSC.ZigString.static("type"), JSC.NewFunction(globalObject, JSC.ZigString.static("type"), 0, Os.type, true));
module.put(globalObject, JSC.ZigString.static("uptime"), JSC.NewFunction(globalObject, JSC.ZigString.static("uptime"), 0, uptime, true));
@@ -79,8 +78,8 @@ pub const Os = struct {
return if (comptime Environment.isLinux)
cpusImplLinux(globalThis) catch {
const err = JSC.SystemError{
- .message = JSC.ZigString.init("Failed to get cpu information"),
- .code = JSC.ZigString.init(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))),
+ .message = bun.String.static("Failed to get cpu information"),
+ .code = bun.String.static(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))),
};
globalThis.vm().throwError(globalThis, err.toErrorInstance(globalThis));
@@ -89,8 +88,8 @@ pub const Os = struct {
else if (comptime Environment.isMac)
cpusImplDarwin(globalThis) catch {
const err = JSC.SystemError{
- .message = JSC.ZigString.init("Failed to get cpu information"),
- .code = JSC.ZigString.init(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))),
+ .message = bun.String.static("Failed to get cpu information"),
+ .code = bun.String.static(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))),
};
globalThis.vm().throwError(globalThis, err.toErrorInstance(globalThis));
@@ -211,7 +210,7 @@ pub const Os = struct {
if (local_bindings.host_processor_info(std.c.mach_host_self(), local_bindings.PROCESSOR_CPU_LOAD_INFO, &num_cpus, @ptrCast(*local_bindings.processor_info_array_t, &info), &info_size) != .SUCCESS) {
return error.no_processor_info;
}
- defer _ = std.c.vm_deallocate(std.c.mach_task_self(), @ptrToInt(info), info_size);
+ defer _ = std.c.vm_deallocate(std.c.mach_task_self(), @intFromPtr(info), info_size);
// Ensure we got the amount of data we expected to guard against buffer overruns
if (info_size != C.PROCESSOR_CPU_LOAD_INFO_COUNT * num_cpus) {
@@ -319,11 +318,11 @@ pub const Os = struct {
//info.put(globalThis, JSC.ZigString.static("syscall"), JSC.ZigString.init("uv_os_getpriority").withEncoding().toValueGC(globalThis));
const err = JSC.SystemError{
- .message = JSC.ZigString.init("A system error occurred: uv_os_getpriority returned ESRCH (no such process)"),
- .code = JSC.ZigString.init(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))),
+ .message = bun.String.static("A system error occurred: uv_os_getpriority returned ESRCH (no such process)"),
+ .code = bun.String.static(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))),
//.info = info,
.errno = -3,
- .syscall = JSC.ZigString.init("uv_os_getpriority"),
+ .syscall = bun.String.static("uv_os_getpriority"),
};
globalThis.vm().throwError(globalThis, err.toErrorInstance(globalThis));
@@ -378,10 +377,10 @@ pub const Os = struct {
const rc = C.getifaddrs(&interface_start);
if (rc != 0) {
const err = JSC.SystemError{
- .message = JSC.ZigString.init("A system error occurred: getifaddrs returned an error"),
- .code = JSC.ZigString.init(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))),
- .errno = @enumToInt(std.os.errno(rc)),
- .syscall = JSC.ZigString.init("getifaddrs"),
+ .message = bun.String.static("A system error occurred: getifaddrs returned an error"),
+ .code = bun.String.static(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))),
+ .errno = @intFromEnum(std.os.errno(rc)),
+ .syscall = bun.String.static("getifaddrs"),
};
globalThis.vm().throwError(globalThis, err.toErrorInstance(globalThis));
@@ -461,7 +460,7 @@ pub const Os = struct {
var cidr = JSC.JSValue.null;
if (maybe_suffix) |suffix| {
//NOTE addr_str might not start at buf[0] due to slicing in formatIp
- const start = @ptrToInt(addr_str.ptr) - @ptrToInt(&buf[0]);
+ const start = @intFromPtr(addr_str.ptr) - @intFromPtr(&buf[0]);
// Start writing the suffix immediately after the address
const suffix_str = std.fmt.bufPrint(buf[start + addr_str.len ..], "/{}", .{suffix}) catch unreachable;
// The full cidr value is the address + the suffix
@@ -485,7 +484,7 @@ pub const Os = struct {
std.os.AF.INET => JSC.ZigString.static("IPv4"),
std.os.AF.INET6 => JSC.ZigString.static("IPv6"),
else => JSC.ZigString.static("unknown"),
- }).toValue(globalThis));
+ }).toValueGC(globalThis));
// mac <string> The MAC address of the network interface
{
@@ -592,11 +591,11 @@ pub const Os = struct {
switch (errcode) {
.SRCH => {
const err = JSC.SystemError{
- .message = JSC.ZigString.init("A system error occurred: uv_os_setpriority returned ESRCH (no such process)"),
- .code = JSC.ZigString.init(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))),
+ .message = bun.String.static("A system error occurred: uv_os_setpriority returned ESRCH (no such process)"),
+ .code = bun.String.static(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))),
//.info = info,
.errno = -3,
- .syscall = JSC.ZigString.init("uv_os_setpriority"),
+ .syscall = bun.String.static("uv_os_setpriority"),
};
globalThis.vm().throwError(globalThis, err.toErrorInstance(globalThis));
@@ -604,11 +603,11 @@ pub const Os = struct {
},
.ACCES => {
const err = JSC.SystemError{
- .message = JSC.ZigString.init("A system error occurred: uv_os_setpriority returned EACCESS (permission denied)"),
- .code = JSC.ZigString.init(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))),
+ .message = bun.String.static("A system error occurred: uv_os_setpriority returned EACCESS (permission denied)"),
+ .code = bun.String.static(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))),
//.info = info,
.errno = -13,
- .syscall = JSC.ZigString.init("uv_os_setpriority"),
+ .syscall = bun.String.static("uv_os_setpriority"),
};
globalThis.vm().throwError(globalThis, err.toErrorInstance(globalThis));
@@ -620,29 +619,6 @@ pub const Os = struct {
return JSC.JSValue.jsUndefined();
}
- pub fn tmpdir(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
- JSC.markBinding(@src());
-
- const dir: []const u8 = brk: {
- if (comptime Environment.isWindows) {
- if (bun.getenvZ("TEMP") orelse bun.getenvZ("TMP")) |tmpdir_| {
- break :brk tmpdir_;
- }
-
- if (bun.getenvZ("SYSTEMROOT") orelse bun.getenvZ("WINDIR")) |systemdir_| {
- break :brk systemdir_ ++ "\\temp";
- }
- } else {
- const dir = bun.asByteSlice(bun.getenvZ("TMPDIR") orelse bun.getenvZ("TMP") orelse bun.getenvZ("TEMP") orelse "/tmp");
- break :brk strings.withoutTrailingSlash(dir);
- }
-
- break :brk "unknown";
- };
-
- return JSC.ZigString.init(dir).withEncoding().toValueGC(globalThis);
- }
-
pub fn totalmem(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
JSC.markBinding(@src());
diff --git a/src/bun.js/node/os/constants.zig b/src/bun.js/node/os/constants.zig
index a3508d383..9cf754e03 100644
--- a/src/bun.js/node/os/constants.zig
+++ b/src/bun.js/node/os/constants.zig
@@ -8,14 +8,14 @@ const ConstantType = enum { ERRNO, ERRNO_WIN, SIG, DLOPEN, OTHER };
fn getErrnoConstant(comptime name: []const u8) ?comptime_int {
return if (@hasField(std.os.E, name))
- return @enumToInt(@field(std.os.E, name))
+ return @intFromEnum(@field(std.os.E, name))
else
return null;
}
fn getWindowsErrnoConstant(comptime name: []const u8) ?comptime_int {
return if (@hasField(std.os.E, name))
- return @enumToInt(@field(std.os.windows.ws2_32.WinsockError, name))
+ return @intFromEnum(@field(std.os.windows.ws2_32.WinsockError, name))
else
return null;
}
diff --git a/src/bun.js/node/syscall.zig b/src/bun.js/node/syscall.zig
index 7b10a3028..5ff0b2f44 100644
--- a/src/bun.js/node/syscall.zig
+++ b/src/bun.js/node/syscall.zig
@@ -106,6 +106,10 @@ pub const Tag = enum(u8) {
waitpid,
posix_spawn,
getaddrinfo,
+ writev,
+ pwritev,
+ readv,
+ preadv,
pub var strings = std.EnumMap(Tag, JSC.C.JSStringRef).initFull(null);
};
const PathString = @import("root").bun.PathString;
@@ -202,7 +206,7 @@ pub fn openat(dirfd: bun.FileDescriptor, file_path: [:0]const u8, flags: JSC.Nod
.SUCCESS => .{ .result = @intCast(bun.FileDescriptor, rc) },
else => |err| .{
.err = .{
- .errno = @truncate(Syscall.Error.Int, @enumToInt(err)),
+ .errno = @truncate(Syscall.Error.Int, @intFromEnum(err)),
.syscall = .open,
},
},
@@ -218,7 +222,7 @@ pub fn openat(dirfd: bun.FileDescriptor, file_path: [:0]const u8, flags: JSC.Nod
else => |err| {
return Maybe(std.os.fd_t){
.err = .{
- .errno = @truncate(Syscall.Error.Int, @enumToInt(err)),
+ .errno = @truncate(Syscall.Error.Int, @intFromEnum(err)),
.syscall = .open,
},
};
@@ -253,14 +257,14 @@ pub fn closeAllowingStdoutAndStderr(fd: std.os.fd_t) ?Syscall.Error {
if (comptime Environment.isMac) {
// This avoids the EINTR problem.
return switch (system.getErrno(system.@"close$NOCANCEL"(fd))) {
- .BADF => Syscall.Error{ .errno = @enumToInt(os.E.BADF), .syscall = .close },
+ .BADF => Syscall.Error{ .errno = @intFromEnum(os.E.BADF), .syscall = .close },
else => null,
};
}
if (comptime Environment.isLinux) {
return switch (linux.getErrno(linux.close(fd))) {
- .BADF => Syscall.Error{ .errno = @enumToInt(os.E.BADF), .syscall = .close },
+ .BADF => Syscall.Error{ .errno = @intFromEnum(os.E.BADF), .syscall = .close },
else => null,
};
}
@@ -302,6 +306,154 @@ pub fn write(fd: os.fd_t, bytes: []const u8) Maybe(usize) {
}
}
+fn veclen(buffers: anytype) usize {
+ var len: usize = 0;
+ for (buffers) |buffer| {
+ len += buffer.iov_len;
+ }
+ return len;
+}
+
+pub fn writev(fd: os.fd_t, buffers: []std.os.iovec) Maybe(usize) {
+ if (comptime Environment.isMac) {
+ const rc = writev_sym(fd, @ptrCast([*]std.os.iovec_const, buffers.ptr), @intCast(i32, buffers.len));
+ if (comptime Environment.allow_assert)
+ log("writev({d}, {d}) = {d}", .{ fd, veclen(buffers), rc });
+
+ if (Maybe(usize).errnoSysFd(rc, .writev, fd)) |err| {
+ return err;
+ }
+
+ return Maybe(usize){ .result = @intCast(usize, rc) };
+ } else {
+ while (true) {
+ const rc = writev_sym(fd, @ptrCast([*]std.os.iovec_const, buffers.ptr), buffers.len);
+ if (comptime Environment.allow_assert)
+ log("writev({d}, {d}) = {d}", .{ fd, veclen(buffers), rc });
+
+ if (Maybe(usize).errnoSysFd(rc, .writev, fd)) |err| {
+ if (err.getErrno() == .INTR) continue;
+ return err;
+ }
+
+ return Maybe(usize){ .result = @intCast(usize, rc) };
+ }
+ unreachable;
+ }
+}
+
+pub fn pwritev(fd: os.fd_t, buffers: []std.os.iovec, position: isize) Maybe(usize) {
+ if (comptime Environment.isMac) {
+ const rc = pwritev_sym(fd, @ptrCast([*]std.os.iovec_const, buffers.ptr), @intCast(i32, buffers.len), position);
+ if (comptime Environment.allow_assert)
+ log("pwritev({d}, {d}) = {d}", .{ fd, veclen(buffers), rc });
+
+ if (Maybe(usize).errnoSysFd(rc, .pwritev, fd)) |err| {
+ return err;
+ }
+
+ return Maybe(usize){ .result = @intCast(usize, rc) };
+ } else {
+ while (true) {
+ const rc = pwritev_sym(fd, @ptrCast([*]std.os.iovec_const, buffers.ptr), buffers.len, position);
+ if (comptime Environment.allow_assert)
+ log("pwritev({d}, {d}) = {d}", .{ fd, veclen(buffers), rc });
+
+ if (Maybe(usize).errnoSysFd(rc, .pwritev, fd)) |err| {
+ if (err.getErrno() == .INTR) continue;
+ return err;
+ }
+
+ return Maybe(usize){ .result = @intCast(usize, rc) };
+ }
+ unreachable;
+ }
+}
+
+pub fn readv(fd: os.fd_t, buffers: []std.os.iovec) Maybe(usize) {
+ if (comptime Environment.isMac) {
+ const rc = readv_sym(fd, buffers.ptr, @intCast(i32, buffers.len));
+ if (comptime Environment.allow_assert)
+ log("readv({d}, {d}) = {d}", .{ fd, veclen(buffers), rc });
+
+ if (Maybe(usize).errnoSysFd(rc, .readv, fd)) |err| {
+ return err;
+ }
+
+ return Maybe(usize){ .result = @intCast(usize, rc) };
+ } else {
+ while (true) {
+ const rc = readv_sym(fd, buffers.ptr, buffers.len);
+ if (comptime Environment.allow_assert)
+ log("readv({d}, {d}) = {d}", .{ fd, veclen(buffers), rc });
+
+ if (Maybe(usize).errnoSysFd(rc, .readv, fd)) |err| {
+ if (err.getErrno() == .INTR) continue;
+ return err;
+ }
+
+ return Maybe(usize){ .result = @intCast(usize, rc) };
+ }
+ unreachable;
+ }
+}
+
+pub fn preadv(fd: os.fd_t, buffers: []std.os.iovec, position: isize) Maybe(usize) {
+ if (comptime Environment.isMac) {
+ const rc = preadv_sym(fd, buffers.ptr, @intCast(i32, buffers.len), position);
+ if (comptime Environment.allow_assert)
+ log("preadv({d}, {d}) = {d}", .{ fd, veclen(buffers), rc });
+
+ if (Maybe(usize).errnoSysFd(rc, .preadv, fd)) |err| {
+ return err;
+ }
+
+ return Maybe(usize){ .result = @intCast(usize, rc) };
+ } else {
+ while (true) {
+ const rc = preadv_sym(fd, buffers.ptr, buffers.len, position);
+ if (comptime Environment.allow_assert)
+ log("preadv({d}, {d}) = {d}", .{ fd, veclen(buffers), rc });
+
+ if (Maybe(usize).errnoSysFd(rc, .preadv, fd)) |err| {
+ if (err.getErrno() == .INTR) continue;
+ return err;
+ }
+
+ return Maybe(usize){ .result = @intCast(usize, rc) };
+ }
+ unreachable;
+ }
+}
+
+const preadv_sym = if (builtin.os.tag == .linux and builtin.link_libc)
+ std.os.linux.preadv
+else if (builtin.os.tag.isDarwin())
+ system.@"preadv$NOCANCEL"
+else
+ system.preadv;
+
+const readv_sym = if (builtin.os.tag == .linux and builtin.link_libc)
+ std.os.linux.readv
+else if (builtin.os.tag.isDarwin())
+ system.@"readv$NOCANCEL"
+else
+ system.readv;
+
+const pwritev_sym = if (builtin.os.tag == .linux and builtin.link_libc)
+ std.os.linux.pwritev
+else if (builtin.os.tag.isDarwin())
+ system.@"pwritev$NOCANCEL"
+else
+ system.pwritev;
+
+const writev_sym = if (builtin.os.tag == .linux and builtin.link_libc)
+ std.os.linux.writev
+else if (builtin.os.tag.isDarwin())
+ system.@"writev$NOCANCEL"
+else
+ system.writev;
+
const pread_sym = if (builtin.os.tag == .linux and builtin.link_libc)
sys.pread64
else if (builtin.os.tag.isDarwin())
@@ -546,7 +698,7 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) Maybe([]u8) {
.macos, .ios, .watchos, .tvos => {
// On macOS, we can use F.GETPATH fcntl command to query the OS for
// the path to the file descriptor.
- @memset(out_buffer, 0, MAX_PATH_BYTES);
+ @memset(out_buffer[0..MAX_PATH_BYTES], 0);
if (Maybe([]u8).errnoSys(system.fcntl(fd, os.F.GETPATH, out_buffer), .fcntl)) |err| {
return err;
}
@@ -594,7 +746,7 @@ fn mmap(
const fail = std.c.MAP.FAILED;
if (rc == fail) {
return Maybe([]align(mem.page_size) u8){
- .err = .{ .errno = @truncate(Syscall.Error.Int, @enumToInt(std.c.getErrno(@bitCast(i64, @ptrToInt(fail))))), .syscall = .mmap },
+ .err = .{ .errno = @truncate(Syscall.Error.Int, @intFromEnum(std.c.getErrno(@bitCast(i64, @intFromPtr(fail))))), .syscall = .mmap },
};
}
@@ -643,16 +795,16 @@ pub fn munmap(memory: []align(mem.page_size) const u8) Maybe(void) {
pub const Error = struct {
const max_errno_value = brk: {
const errno_values = std.enums.values(os.E);
- var err = @enumToInt(os.E.SUCCESS);
+ var err = @intFromEnum(os.E.SUCCESS);
for (errno_values) |errn| {
- err = @max(err, @enumToInt(errn));
+ err = @max(err, @intFromEnum(errn));
}
break :brk err;
};
pub const Int: type = std.math.IntFittingRange(0, max_errno_value + 5);
errno: Int,
- syscall: Syscall.Tag = @intToEnum(Syscall.Tag, 0),
+ syscall: Syscall.Tag = @enumFromInt(Syscall.Tag, 0),
path: []const u8 = "",
fd: i32 = -1,
@@ -661,7 +813,7 @@ pub const Error = struct {
}
pub fn fromCode(errno: os.E, syscall: Syscall.Tag) Error {
- return .{ .errno = @truncate(Int, @enumToInt(errno)), .syscall = syscall };
+ return .{ .errno = @truncate(Int, @intFromEnum(errno)), .syscall = syscall };
}
pub fn format(self: Error, comptime fmt: []const u8, opts: std.fmt.FormatOptions, writer: anytype) !void {
@@ -672,16 +824,16 @@ pub const Error = struct {
pub const retry = Error{
.errno = if (Environment.isLinux)
- @intCast(Int, @enumToInt(os.E.AGAIN))
+ @intCast(Int, @intFromEnum(os.E.AGAIN))
else if (Environment.isMac)
- @intCast(Int, @enumToInt(os.E.WOULDBLOCK))
+ @intCast(Int, @intFromEnum(os.E.WOULDBLOCK))
else
- @intCast(Int, @enumToInt(os.E.INTR)),
+ @intCast(Int, @intFromEnum(os.E.INTR)),
.syscall = .retry,
};
pub inline fn getErrno(this: Error) os.E {
- return @intToEnum(os.E, this.errno);
+ return @enumFromInt(os.E, this.errno);
}
pub inline fn withPath(this: Error, path: anytype) Error {
@@ -721,20 +873,20 @@ pub const Error = struct {
pub fn toSystemError(this: Error) SystemError {
var err = SystemError{
.errno = @as(c_int, this.errno) * -1,
- .syscall = JSC.ZigString.init(@tagName(this.syscall)),
+ .syscall = bun.String.static(@tagName(this.syscall)),
};
// errno label
if (this.errno > 0 and this.errno < C.SystemErrno.max) {
- const system_errno = @intToEnum(C.SystemErrno, this.errno);
- err.code = JSC.ZigString.init(@tagName(system_errno));
+ const system_errno = @enumFromInt(C.SystemErrno, this.errno);
+ err.code = bun.String.static(@tagName(system_errno));
if (C.SystemErrno.labels.get(system_errno)) |label| {
- err.message = JSC.ZigString.init(label);
+ err.message = bun.String.static(label);
}
}
if (this.path.len > 0) {
- err.path = JSC.ZigString.init(this.path);
+ err.path = bun.String.create(this.path);
}
if (this.fd != -1) {
diff --git a/src/bun.js/node/types.zig b/src/bun.js/node/types.zig
index 1fe378a84..642039ba5 100644
--- a/src/bun.js/node/types.zig
+++ b/src/bun.js/node/types.zig
@@ -93,6 +93,10 @@ pub fn Maybe(comptime ResultType: type) type {
return JSC.JSValue.jsUndefined();
}
+ if (comptime ReturnType == JSC.JSValue) {
+ return r;
+ }
+
if (comptime ReturnType == JSC.ArrayBuffer) {
return r.toJS(globalThis, null);
}
@@ -135,7 +139,7 @@ pub fn Maybe(comptime ResultType: type) type {
pub inline fn getErrno(this: @This()) os.E {
return switch (this) {
.result => os.E.SUCCESS,
- .err => |err| @intToEnum(os.E, err.errno),
+ .err => |err| @enumFromInt(os.E, err.errno),
};
}
@@ -144,7 +148,7 @@ pub fn Maybe(comptime ResultType: type) type {
.SUCCESS => null,
else => |err| @This(){
// always truncate
- .err = .{ .errno = @truncate(Syscall.Error.Int, @enumToInt(err)) },
+ .err = .{ .errno = @truncate(Syscall.Error.Int, @intFromEnum(err)) },
},
};
}
@@ -154,7 +158,7 @@ pub fn Maybe(comptime ResultType: type) type {
.SUCCESS => null,
else => |err| @This(){
// always truncate
- .err = .{ .errno = @truncate(Syscall.Error.Int, @enumToInt(err)), .syscall = syscall },
+ .err = .{ .errno = @truncate(Syscall.Error.Int, @intFromEnum(err)), .syscall = syscall },
},
};
}
@@ -165,7 +169,7 @@ pub fn Maybe(comptime ResultType: type) type {
else => |err| @This(){
// always truncate
.err = .{
- .errno = @truncate(Syscall.Error.Int, @enumToInt(err)),
+ .errno = @truncate(Syscall.Error.Int, @intFromEnum(err)),
.syscall = syscall,
.fd = @intCast(i32, fd),
},
@@ -178,7 +182,7 @@ pub fn Maybe(comptime ResultType: type) type {
.SUCCESS => null,
else => |err| @This(){
// always truncate
- .err = .{ .errno = @truncate(Syscall.Error.Int, @enumToInt(err)), .syscall = syscall, .path = bun.asByteSlice(path) },
+ .err = .{ .errno = @truncate(Syscall.Error.Int, @intFromEnum(err)), .syscall = syscall, .path = bun.asByteSlice(path) },
},
};
}
@@ -520,8 +524,8 @@ pub const Encoding = enum(u8) {
switch (encoding) {
.base64 => {
var base64: [std.base64.standard.Encoder.calcSize(size)]u8 = undefined;
- const result = JSC.ZigString.init(std.base64.standard.Encoder.encode(&base64, input)).toValueGC(globalThis);
- return result;
+ const len = bun.base64.encode(&base64, input);
+ return JSC.ZigString.init(base64[0..len]).toValueGC(globalThis);
},
.base64url => {
var buf: [std.base64.url_safe.Encoder.calcSize(size) + "data:;base64,".len]u8 = undefined;
@@ -537,9 +541,18 @@ pub const Encoding = enum(u8) {
const result = JSC.ZigString.init(out).toValueGC(globalThis);
return result;
},
- else => {
- globalThis.throwInvalidArguments("Unexpected encoding", .{});
- return JSC.JSValue.zero;
+ .buffer => {
+ return JSC.ArrayBuffer.createBuffer(globalThis, input);
+ },
+
+ inline else => |enc| {
+ const res = JSC.WebCore.Encoder.toString(input.ptr, size, globalThis, enc);
+ if (res.isError()) {
+ globalThis.throwValue(res);
+ return .zero;
+ }
+
+ return res;
},
}
}
@@ -567,9 +580,18 @@ pub const Encoding = enum(u8) {
const result = JSC.ZigString.init(out).toValueGC(globalThis);
return result;
},
- else => {
- globalThis.throwInvalidArguments("Unexpected encoding", .{});
- return JSC.JSValue.zero;
+ .buffer => {
+ return JSC.ArrayBuffer.createBuffer(globalThis, input);
+ },
+ inline else => |enc| {
+ const res = JSC.WebCore.Encoder.toString(input.ptr, input.len, globalThis, enc);
+
+ if (res.isError()) {
+ globalThis.throwValue(res);
+ return .zero;
+ }
+
+ return res;
},
}
}
@@ -632,7 +654,7 @@ pub const PathLike = union(Tag) {
}
}
- @memcpy(buf, sliced.ptr, sliced.len);
+ @memcpy(buf[0..sliced.len], sliced);
buf[sliced.len] = 0;
return buf[0..sliced.len :0];
}
@@ -809,6 +831,50 @@ pub const Valid = struct {
}
};
+pub const VectorArrayBuffer = struct {
+ value: JSC.JSValue,
+ buffers: std.ArrayList(std.os.iovec),
+
+ pub fn toJS(this: VectorArrayBuffer, _: *JSC.JSGlobalObject) JSC.JSValue {
+ return this.value;
+ }
+
+ pub fn fromJS(globalObject: *JSC.JSGlobalObject, val: JSC.JSValue, exception: JSC.C.ExceptionRef, allocator: std.mem.Allocator) ?VectorArrayBuffer {
+ if (!val.jsType().isArrayLike()) {
+ JSC.throwInvalidArguments("Expected ArrayBufferView[]", .{}, globalObject, exception);
+ return null;
+ }
+
+ var bufferlist = std.ArrayList(std.os.iovec).init(allocator);
+ var i: usize = 0;
+ const len = val.getLength(globalObject);
+ bufferlist.ensureTotalCapacityPrecise(len) catch @panic("Failed to allocate memory for ArrayBuffer[]");
+
+ while (i < len) {
+ const element = val.getIndex(globalObject, @truncate(u32, i));
+
+ if (!element.isCell()) {
+ JSC.throwInvalidArguments("Expected ArrayBufferView[]", .{}, globalObject, exception);
+ return null;
+ }
+
+ const array_buffer = element.asArrayBuffer(globalObject) orelse {
+ JSC.throwInvalidArguments("Expected ArrayBufferView[]", .{}, globalObject, exception);
+ return null;
+ };
+
+ var buf = array_buffer.byteSlice();
+ bufferlist.append(std.os.iovec{
+ .iov_base = buf.ptr,
+ .iov_len = buf.len,
+ }) catch @panic("Failed to allocate memory for ArrayBuffer[]");
+ i += 1;
+ }
+
+ return VectorArrayBuffer{ .value = val, .buffers = bufferlist };
+ }
+};
+
pub const ArgumentsSlice = struct {
remaining: []const JSC.JSValue,
vm: *JSC.VirtualMachine,
@@ -904,7 +970,7 @@ pub fn timeLikeFromJS(globalThis: *JSC.JSGlobalObject, value: JSC.JSValue, _: JS
return null;
}
- return @truncate(TimeLike, @floatToInt(i64, milliseconds / @as(f64, std.time.ms_per_s)));
+ return @truncate(TimeLike, @intFromFloat(i64, milliseconds / @as(f64, std.time.ms_per_s)));
}
if (!value.isNumber() and !value.isString()) {
@@ -916,7 +982,7 @@ pub fn timeLikeFromJS(globalThis: *JSC.JSGlobalObject, value: JSC.JSValue, _: JS
return null;
}
- return @truncate(TimeLike, @floatToInt(i64, seconds));
+ return @truncate(TimeLike, @intFromFloat(i64, seconds));
}
pub fn modeFromJS(ctx: JSC.C.JSContextRef, value: JSC.JSValue, exception: JSC.C.ExceptionRef) ?Mode {
@@ -968,8 +1034,8 @@ pub const PathOrFileDescriptor = union(Tag) {
pub fn hash(this: JSC.Node.PathOrFileDescriptor) u64 {
return switch (this) {
- .path => std.hash.Wyhash.hash(0, this.path.slice()),
- .fd => std.hash.Wyhash.hash(0, std.mem.asBytes(&this.fd)),
+ .path => bun.hash(this.path.slice()),
+ .fd => bun.hash(std.mem.asBytes(&this.fd)),
};
}
@@ -1104,7 +1170,7 @@ pub const FileSystemFlags = enum(Mode) {
pub fn fromJS(ctx: JSC.C.JSContextRef, val: JSC.JSValue, exception: JSC.C.ExceptionRef) ?FileSystemFlags {
if (val.isNumber()) {
const number = val.coerce(i32, ctx);
- return @intToEnum(FileSystemFlags, @intCast(Mode, @max(number, 0)));
+ return @enumFromInt(FileSystemFlags, @intCast(Mode, @max(number, 0)));
}
const jsType = val.jsType();
@@ -1160,7 +1226,7 @@ pub const FileSystemFlags = enum(Mode) {
return null;
};
- return @intToEnum(FileSystemFlags, @intCast(Mode, flags));
+ return @enumFromInt(FileSystemFlags, @intCast(Mode, flags));
}
return null;
@@ -1172,7 +1238,7 @@ pub const Date = enum(u64) {
_,
pub fn toJS(this: Date, ctx: JSC.C.JSContextRef, exception: JSC.C.ExceptionRef) JSC.C.JSValueRef {
- const seconds = @floatCast(f64, @intToFloat(f64, @enumToInt(this)) * 1000.0);
+ const seconds = @floatCast(f64, @floatFromInt(f64, @intFromEnum(this)) * 1000.0);
const unix_timestamp = JSC.JSValue.jsNumber(seconds);
const array: [1]JSC.C.JSValueRef = .{unix_timestamp.asObjectRef()};
const obj = JSC.C.JSObjectMakeDate(ctx, 1, &array, exception);
@@ -1219,12 +1285,12 @@ fn StatsDataType(comptime T: type) type {
.size = @truncate(T, @intCast(i64, stat_.size)),
.blksize = @truncate(T, @intCast(i64, stat_.blksize)),
.blocks = @truncate(T, @intCast(i64, stat_.blocks)),
- .atime_ms = (@intToFloat(f64, @max(atime.tv_sec, 0)) * std.time.ms_per_s) + (@intToFloat(f64, @intCast(usize, @max(atime.tv_nsec, 0))) / std.time.ns_per_ms),
- .mtime_ms = (@intToFloat(f64, @max(mtime.tv_sec, 0)) * std.time.ms_per_s) + (@intToFloat(f64, @intCast(usize, @max(mtime.tv_nsec, 0))) / std.time.ns_per_ms),
- .ctime_ms = (@intToFloat(f64, @max(ctime.tv_sec, 0)) * std.time.ms_per_s) + (@intToFloat(f64, @intCast(usize, @max(ctime.tv_nsec, 0))) / std.time.ns_per_ms),
- .atime = @intToEnum(Date, @intCast(u64, @max(atime.tv_sec, 0))),
- .mtime = @intToEnum(Date, @intCast(u64, @max(mtime.tv_sec, 0))),
- .ctime = @intToEnum(Date, @intCast(u64, @max(ctime.tv_sec, 0))),
+ .atime_ms = (@floatFromInt(f64, @max(atime.tv_sec, 0)) * std.time.ms_per_s) + (@floatFromInt(f64, @intCast(usize, @max(atime.tv_nsec, 0))) / std.time.ns_per_ms),
+ .mtime_ms = (@floatFromInt(f64, @max(mtime.tv_sec, 0)) * std.time.ms_per_s) + (@floatFromInt(f64, @intCast(usize, @max(mtime.tv_nsec, 0))) / std.time.ns_per_ms),
+ .ctime_ms = (@floatFromInt(f64, @max(ctime.tv_sec, 0)) * std.time.ms_per_s) + (@floatFromInt(f64, @intCast(usize, @max(ctime.tv_nsec, 0))) / std.time.ns_per_ms),
+ .atime = @enumFromInt(Date, @intCast(u64, @max(atime.tv_sec, 0))),
+ .mtime = @enumFromInt(Date, @intCast(u64, @max(mtime.tv_sec, 0))),
+ .ctime = @enumFromInt(Date, @intCast(u64, @max(ctime.tv_sec, 0))),
// Linux doesn't include this info in stat
// maybe it does in statx, but do you really need birthtime? If you do please file an issue.
@@ -1234,9 +1300,9 @@ fn StatsDataType(comptime T: type) type {
@truncate(T, @intCast(i64, if (stat_.birthtime().tv_nsec > 0) (@intCast(usize, stat_.birthtime().tv_nsec) / std.time.ns_per_ms) else 0)),
.birthtime = if (Environment.isLinux)
- @intToEnum(Date, 0)
+ @enumFromInt(Date, 0)
else
- @intToEnum(Date, @intCast(u64, @max(stat_.birthtime().tv_sec, 0))),
+ @enumFromInt(Date, @intCast(u64, @max(stat_.birthtime().tv_sec, 0))),
};
}
};
@@ -1426,49 +1492,49 @@ pub const Dirent = struct {
_: *JSC.JSGlobalObject,
_: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
- return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.BlockDevice);
+ return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.block_device);
}
pub fn isCharacterDevice(
this: *Dirent,
_: *JSC.JSGlobalObject,
_: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
- return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.CharacterDevice);
+ return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.character_device);
}
pub fn isDirectory(
this: *Dirent,
_: *JSC.JSGlobalObject,
_: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
- return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.Directory);
+ return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.directory);
}
pub fn isFIFO(
this: *Dirent,
_: *JSC.JSGlobalObject,
_: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
- return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.NamedPipe or this.kind == std.fs.File.Kind.EventPort);
+ return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.named_pipe or this.kind == std.fs.File.Kind.event_port);
}
pub fn isFile(
this: *Dirent,
_: *JSC.JSGlobalObject,
_: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
- return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.File);
+ return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.file);
}
pub fn isSocket(
this: *Dirent,
_: *JSC.JSGlobalObject,
_: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
- return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.UnixDomainSocket);
+ return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.unix_domain_socket);
}
pub fn isSymbolicLink(
this: *Dirent,
_: *JSC.JSGlobalObject,
_: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
- return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.SymLink);
+ return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.sym_link);
}
pub fn finalize(this: *Dirent) callconv(.C) void {
@@ -1490,14 +1556,14 @@ pub const Emitter = struct {
pub fn append(this: *List, allocator: std.mem.Allocator, ctx: JSC.C.JSContextRef, listener: Listener) !void {
JSC.C.JSValueProtect(ctx, listener.callback.asObjectRef());
try this.list.append(allocator, listener);
- this.once_count +|= @as(u32, @boolToInt(listener.once));
+ this.once_count +|= @as(u32, @intFromBool(listener.once));
}
pub fn prepend(this: *List, allocator: std.mem.Allocator, ctx: JSC.C.JSContextRef, listener: Listener) !void {
JSC.C.JSValueProtect(ctx, listener.callback.asObjectRef());
try this.list.ensureUnusedCapacity(allocator, 1);
this.list.insertAssumeCapacity(0, listener);
- this.once_count +|= @as(u32, @boolToInt(listener.once));
+ this.once_count +|= @as(u32, @intFromBool(listener.once));
}
// removeListener() will remove, at most, one instance of a listener from the
@@ -1510,7 +1576,7 @@ pub const Emitter = struct {
for (callbacks, 0..) |item, i| {
if (callback.eqlValue(item)) {
JSC.C.JSValueUnprotect(ctx, callback.asObjectRef());
- this.once_count -|= @as(u32, @boolToInt(this.list.items(.once)[i]));
+ this.once_count -|= @as(u32, @intFromBool(this.list.items(.once)[i]));
this.list.orderedRemove(i);
return true;
}
@@ -2154,7 +2220,9 @@ pub const Process = struct {
}
}
- pub fn exit(_: *JSC.JSGlobalObject, code: i32) callconv(.C) void {
+ pub fn exit(globalObject: *JSC.JSGlobalObject, code: i32) callconv(.C) void {
+ globalObject.bunVM().onExit();
+
std.os.exit(@truncate(u8, @intCast(u32, @max(code, 0))));
}