use core::fmt;
use std::borrow::Cow;
use bun_collections::MultiArrayList;
use bun_core::{ThreadLock, ZStr, feature_flags, output as Output, strings, zstr};
use bun_sys::{self as sys, Fd};
use bun_threading::Mutex;
use crate::Loader;
use crate::watcher_trace as WatcherTrace;
#[cfg(any(target_os = "linux", target_os = "android"))]
use crate::inotify_watcher as platform;
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
use crate::kevent_watcher as platform;
#[cfg(windows)]
use crate::windows_watcher as platform;
#[cfg(target_arch = "wasm32")]
compile_error!("Unsupported platform");
bun_core::define_scoped_log!(log, watcher, visible);
pub const MAX_COUNT: usize = 128;
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
pub const REQUIRES_FILE_DESCRIPTORS: bool = true;
#[cfg(not(any(target_os = "macos", target_os = "freebsd")))]
pub const REQUIRES_FILE_DESCRIPTORS: bool = false;
#[cfg(target_os = "macos")]
pub const WATCH_OPEN_FLAGS: i32 = libc::O_EVTONLY;
#[cfg(not(target_os = "macos"))]
pub const WATCH_OPEN_FLAGS: i32 = bun_sys::O::RDONLY;
pub type Event = WatchEvent;
pub type Item = WatchItem;
pub type ItemList = WatchList;
pub type WatchList = MultiArrayList<WatchItem>;
pub type HashType = u32;
pub type WatchItemIndex = u16;
pub const MAX_EVICTION_COUNT: usize = 8096;
const NO_WATCH_ITEM: WatchItemIndex = WatchItemIndex::MAX;
#[repr(C)]
pub struct PackageJSON {
_opaque: [u8; 0],
_pinned: core::marker::PhantomPinned,
}
#[derive(Clone, Copy)]
pub struct AnyResolveWatcher {
pub context: *mut (),
pub callback: fn(*mut (), dir_path: &[u8], dir_fd: Fd),
}
impl AnyResolveWatcher {
#[inline]
pub fn watch(self, dir_path: &[u8], dir_fd: Fd) {
(self.callback)(self.context, dir_path, dir_fd)
}
}
pub(crate) type Platform = platform::Platform;
pub type ChangedFilePath = Option<&'static ZStr>;
pub struct Watcher {
pub watch_events: Box<[WatchEvent]>,
pub changed_filepaths: [ChangedFilePath; MAX_COUNT],
pub platform: Platform,
pub watchlist: WatchList,
pub watched_count: usize,
pub mutex: Mutex,
pub watchloop_handle: bun_core::AtomicCell<bool>,
pub cwd: &'static [u8],
pub thread: Option<std::thread::JoinHandle<()>>,
pub running: bun_core::AtomicCell<bool>,
pub close_descriptors: bun_core::AtomicCell<bool>,
pub evict_list: [WatchItemIndex; MAX_EVICTION_COUNT],
pub evict_list_i: WatchItemIndex,
pub ctx: *mut (),
pub on_file_update: fn(*mut (), &mut [WatchEvent], &[ChangedFilePath], &WatchList),
pub on_error: fn(*mut (), sys::Error),
pub thread_lock: ThreadLock,
}
pub trait WatcherContext {
fn on_file_update(
&mut self,
events: &mut [WatchEvent],
changed_files: &[ChangedFilePath],
watchlist: &WatchList,
);
fn on_error(&mut self, err: sys::Error);
fn on_watch_error(&mut self, err: sys::Error) {
self.on_error(err);
}
}
impl Watcher {
pub fn init<T: WatcherContext>(
ctx: *mut T,
top_level_dir: &'static [u8],
) -> Result<Box<Watcher>, bun_core::Error> {
fn on_file_update_wrapped<T: WatcherContext>(
ctx_opaque: *mut (),
events: &mut [WatchEvent],
changed_files: &[ChangedFilePath],
watchlist: &WatchList,
) {
let ctx = unsafe { &mut *ctx_opaque.cast::<T>() };
ctx.on_file_update(events, changed_files, watchlist);
}
fn on_error_wrapped<T: WatcherContext>(ctx_opaque: *mut (), err: sys::Error) {
let ctx = unsafe { &mut *ctx_opaque.cast::<T>() };
ctx.on_watch_error(err);
}
let mut this = Box::new(Watcher {
watched_count: 0,
watchlist: WatchList::default(),
mutex: Mutex::default(),
cwd: top_level_dir,
ctx: ctx.cast::<()>(),
on_file_update: on_file_update_wrapped::<T>,
on_error: on_error_wrapped::<T>,
platform: Platform::default(),
watch_events: vec![WatchEvent::default(); MAX_COUNT].into_boxed_slice(),
changed_filepaths: [const { None }; MAX_COUNT],
watchloop_handle: bun_core::AtomicCell::new(false),
thread: None,
running: bun_core::AtomicCell::new(true),
close_descriptors: bun_core::AtomicCell::new(false),
evict_list: [0; MAX_EVICTION_COUNT],
evict_list_i: 0,
thread_lock: ThreadLock::init_unlocked(),
});
this.platform.init(top_level_dir)?;
WatcherTrace::init();
Ok(this)
}
pub fn write_trace_events(&self, events: &[WatchEvent], changed_files: &[ChangedFilePath]) {
WatcherTrace::write_events(&self.watchlist, events, changed_files);
}
pub fn start(&mut self) -> Result<(), bun_core::Error> {
debug_assert!(!self.watchloop_handle.load());
let this = std::ptr::from_mut::<Watcher>(self) as usize;
self.thread = Some(
std::thread::Builder::new()
.name("FileWatcher".into())
.spawn(move || unsafe {
let _ = Watcher::thread_main(this as *mut Watcher);
})
.expect("spawn FileWatcher thread"),
);
Ok(())
}
pub unsafe fn shutdown(this: *mut Self, close_descriptors: bool) {
let me = unsafe { &mut *this };
if me.watchloop_handle.load() {
me.mutex.lock();
me.close_descriptors.store(close_descriptors);
me.running.store(false);
me.mutex.unlock();
} else {
if close_descriptors && me.running.load() {
let fds = me.watchlist.items_fd();
for &fd in fds {
let _ = bun_sys::close(fd);
}
}
drop(unsafe { bun_core::heap::take(this) });
}
}
pub fn get_hash(filepath: &[u8]) -> HashType {
bun_wyhash::hash(filepath) as HashType
}
unsafe fn thread_main(this: *mut Self) -> Result<(), bun_core::Error> {
{
let me = unsafe { &mut *this };
me.watchloop_handle.store(true);
me.thread_lock.lock();
Output::Source::configure_named_thread(zstr!("File Watcher"));
log!("Watcher started");
match me.watch_loop() {
Err(err) => {
me.watchloop_handle.store(false);
me.platform.stop();
if me.running.load() {
(me.on_error)(me.ctx, err);
}
}
Ok(()) => {}
}
if me.close_descriptors.load() {
let fds = me.watchlist.items_fd();
for &fd in fds {
let _ = bun_sys::close(fd);
}
}
}
WatcherTrace::deinit();
Output::flush();
drop(unsafe { bun_core::heap::take(this) });
Ok(())
}
pub fn flush_evictions(&mut self) {
if self.evict_list_i == 0 {
return;
}
debug_assert!(
self.mutex.is_held_by_current_thread(),
"flush_evictions: caller must hold self.mutex (platform watcher holds it around on_file_update)",
);
let evict_list_i = self.evict_list_i as usize;
self.evict_list[0..evict_list_i].sort_by(|a, b| b.cmp(a));
let slice = self.watchlist.slice();
let fds = slice.items_fd();
let fds_len = fds.len();
let mut last_item = NO_WATCH_ITEM;
for &item in &self.evict_list[0..evict_list_i] {
if item == last_item {
continue;
}
if item as usize >= fds_len {
continue;
}
#[cfg(not(windows))]
{
if fds[item as usize].is_valid() {
let _ = bun_sys::close(fds[item as usize]);
}
}
last_item = item;
}
last_item = NO_WATCH_ITEM;
for i in 0..evict_list_i {
let item = self.evict_list[i];
if item == last_item || self.watchlist.len() <= item as usize {
continue;
}
self.watchlist.swap_remove(item as usize);
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
{
if (item as usize) < self.watchlist.len() {
let moved_fd = self.watchlist.items_fd()[item as usize];
if moved_fd.is_valid() {
self.add_file_descriptor_to_kqueue_without_checks(moved_fd, item as usize);
}
}
}
last_item = item;
}
self.evict_list_i = 0;
}
fn watch_loop(&mut self) -> sys::Result<()> {
while self.running.load() {
platform::watch_loop_cycle(self)?;
}
Ok(())
}
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
pub fn add_file_descriptor_to_kqueue_without_checks(&mut self, fd: Fd, watchlist_id: usize) {
use libc::{EV_ADD, EV_CLEAR, EV_ENABLE, EVFILT_VNODE, kevent as KEvent};
use libc::{NOTE_DELETE, NOTE_RENAME, NOTE_WRITE};
let mut event: KEvent = bun_core::ffi::zeroed();
event.flags = (EV_ADD | EV_CLEAR | EV_ENABLE) as _;
event.filter = EVFILT_VNODE as _;
event.fflags = (NOTE_WRITE | NOTE_RENAME | NOTE_DELETE) as _;
event.ident = usize::try_from(fd.native()).expect("int cast");
event.udata = watchlist_id as _;
let mut events: [KEvent; 1] = [event];
let _ = unsafe {
libc::kevent(
self.platform.fd.unwrap().native(),
events.as_ptr(),
1,
events.as_mut_ptr(),
0,
core::ptr::null(),
)
};
}
#[cfg(not(any(target_os = "macos", target_os = "freebsd")))]
pub fn add_file_descriptor_to_kqueue_without_checks(&mut self, _fd: Fd, _watchlist_id: usize) {}
fn append_file_assume_capacity<const CLONE_FILE_PATH: bool>(
&mut self,
fd: Fd,
file_path: &[u8],
hash: HashType,
loader: Loader,
parent_hash: HashType,
package_json: Option<&'static PackageJSON>,
) -> sys::Result<()> {
#[cfg(windows)]
{
let rel = bun_paths::resolve_path::is_parent_or_equal(self.top_level_dir(), file_path);
if rel == bun_paths::resolve_path::ParentEqual::Unrelated {
Output::warn(format_args!(
"File {} is not in the project directory and will not be watched\n",
bstr::BStr::new(file_path)
));
return Ok(());
}
}
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
let watchlist_id = self.watchlist.len();
let file_path_: Cow<'static, [u8]> = if CLONE_FILE_PATH {
Cow::Owned(file_path.to_vec())
} else {
Cow::Borrowed(unsafe { bun_collections::detach_lifetime(file_path) })
};
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
self.add_file_descriptor_to_kqueue_without_checks(fd, watchlist_id);
#[cfg(any(target_os = "linux", target_os = "android"))]
let eventlist_index = {
let mut buf = bun_paths::path_buffer_pool::get();
let slice: &ZStr = if CLONE_FILE_PATH {
buf[0..file_path.len()].copy_from_slice(file_path);
buf[file_path.len()] = 0;
ZStr::from_buf(&buf[..], file_path.len())
} else {
unsafe { ZStr::from_raw(file_path.as_ptr(), file_path.len()) }
};
self.platform.watch_path(slice)?
};
self.watchlist.append_assume_capacity(WatchItem {
file_path: file_path_,
fd,
hash,
count: 0,
loader,
parent_hash,
package_json,
kind: WatchItemKind::File,
#[cfg(any(target_os = "linux", target_os = "android"))]
eventlist_index,
});
Ok(())
}
fn append_directory_assume_capacity<const CLONE_FILE_PATH: bool>(
&mut self,
stored_fd: Fd,
file_path: &[u8],
hash: HashType,
) -> sys::Result<WatchItemIndex> {
#[cfg(windows)]
{
let rel = bun_paths::resolve_path::is_parent_or_equal(self.top_level_dir(), file_path);
if rel == bun_paths::resolve_path::ParentEqual::Unrelated {
Output::warn(format_args!(
"Directory {} is not in the project directory and will not be watched\n",
bstr::BStr::new(file_path)
));
return Ok(NO_WATCH_ITEM);
}
}
let fd = if stored_fd.is_valid() {
stored_fd
} else {
bun_sys::open_a(file_path, 0, 0)?
};
let file_path_: Cow<'static, [u8]> = if CLONE_FILE_PATH {
Cow::Owned(file_path.to_vec())
} else {
Cow::Borrowed(unsafe { bun_collections::detach_lifetime(file_path) })
};
let parent_hash =
Self::get_hash(bun_paths::fs::PathName::init(file_path).dir_with_trailing_slash());
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
let watchlist_id = self.watchlist.len();
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
self.add_file_descriptor_to_kqueue_without_checks(fd, watchlist_id);
#[cfg(any(target_os = "linux", target_os = "android"))]
let eventlist_index = {
let mut buf = bun_paths::path_buffer_pool::get();
let path: &ZStr = if CLONE_FILE_PATH
&& !file_path.is_empty()
&& file_path[file_path.len() - 1] == 0
{
ZStr::from_slice_with_nul(file_path)
} else {
let trailing_slash = if file_path.len() > 1 {
strings::trim_right(file_path, &[0, b'/'])
} else {
file_path
};
buf[0..trailing_slash.len()].copy_from_slice(trailing_slash);
buf[trailing_slash.len()] = 0;
ZStr::from_buf(&buf[..], trailing_slash.len())
};
self.platform
.watch_dir(path)
.map_err(|e| e.with_path(file_path))?
};
self.watchlist.append_assume_capacity(WatchItem {
file_path: file_path_,
fd,
hash,
count: 0,
loader: Loader::File,
parent_hash,
kind: WatchItemKind::Directory,
package_json: None,
#[cfg(any(target_os = "linux", target_os = "android"))]
eventlist_index,
});
Ok((self.watchlist.len() - 1) as WatchItemIndex)
}
pub fn append_file_maybe_lock<const CLONE_FILE_PATH: bool, const LOCK: bool>(
&mut self,
fd: Fd,
file_path: &[u8],
hash: HashType,
loader: Loader,
dir_fd: Fd,
package_json: Option<&'static PackageJSON>,
) -> sys::Result<()> {
if LOCK {
self.mutex.lock();
}
debug_assert!(file_path.len() > 1);
let pathname = bun_paths::fs::PathName::init(file_path);
let parent_dir = pathname.dir_with_trailing_slash();
let parent_dir_hash: HashType = Self::get_hash(parent_dir);
let mut parent_watch_item: Option<WatchItemIndex> = None;
let autowatch_parent_dir =
feature_flags::WATCH_DIRECTORIES && self.is_eligible_directory(parent_dir);
if autowatch_parent_dir {
let watchlist_slice = self.watchlist.slice();
if dir_fd.is_valid() {
let fds = watchlist_slice.items_fd();
if let Some(i) = fds.iter().position(|f| *f == dir_fd) {
parent_watch_item = Some(i as WatchItemIndex);
}
}
if parent_watch_item.is_none() {
let hashes = watchlist_slice.items_hash();
if let Some(i) = hashes.iter().position(|h| *h == parent_dir_hash) {
parent_watch_item = Some(i as WatchItemIndex);
}
}
}
self.watchlist
.ensure_unused_capacity(1 + usize::from(parent_watch_item.is_none()))
.unwrap_or_else(|_| bun_core::out_of_memory());
if autowatch_parent_dir {
parent_watch_item = Some(match parent_watch_item {
Some(v) => v,
None => match self.append_directory_assume_capacity::<CLONE_FILE_PATH>(
dir_fd,
parent_dir,
parent_dir_hash,
) {
Err(err) => {
if LOCK {
self.mutex.unlock();
}
return Err(err.with_path(parent_dir));
}
Ok(r) => r,
},
});
}
let _ = parent_watch_item;
match self.append_file_assume_capacity::<CLONE_FILE_PATH>(
fd,
file_path,
hash,
loader,
parent_dir_hash,
package_json,
) {
Err(err) => {
if LOCK {
self.mutex.unlock();
}
return Err(err.with_path(file_path));
}
Ok(()) => {}
}
if true {
let cwd_len_with_slash = if self.cwd[self.cwd.len() - 1] == b'/' {
self.cwd.len()
} else {
self.cwd.len() + 1
};
let display_path =
if file_path.len() > cwd_len_with_slash && file_path.starts_with(self.cwd) {
&file_path[cwd_len_with_slash..]
} else {
file_path
};
log!(
"<d>Added <b>{}<r><d> to watch list.<r>",
bstr::BStr::new(display_path)
);
}
if LOCK {
self.mutex.unlock();
}
Ok(())
}
#[inline]
fn is_eligible_directory(&self, dir: &[u8]) -> bool {
strings::contains(dir, self.top_level_dir()) && !strings::contains(dir, b"node_modules")
}
#[inline]
fn top_level_dir(&self) -> &[u8] {
self.cwd
}
pub fn append_file<const CLONE_FILE_PATH: bool>(
&mut self,
fd: Fd,
file_path: &[u8],
hash: HashType,
loader: Loader,
dir_fd: Fd,
package_json: Option<&'static PackageJSON>,
) -> sys::Result<()> {
self.append_file_maybe_lock::<CLONE_FILE_PATH, true>(
fd,
file_path,
hash,
loader,
dir_fd,
package_json,
)
}
pub fn add_directory<const CLONE_FILE_PATH: bool>(
&mut self,
fd: Fd,
file_path: &[u8],
hash: HashType,
) -> sys::Result<WatchItemIndex> {
self.mutex.lock();
let result = (|| {
if let Some(idx) = self.index_of(hash) {
return Ok(idx as WatchItemIndex);
}
self.watchlist
.ensure_unused_capacity(1)
.unwrap_or_else(|_| bun_core::out_of_memory());
self.append_directory_assume_capacity::<CLONE_FILE_PATH>(fd, file_path, hash)
})();
self.mutex.unlock();
result
}
pub fn add_file_by_path_slow(&mut self, file_path: &[u8], loader: Loader) -> bool {
if file_path.is_empty() {
return false;
}
let hash = Self::get_hash(file_path);
{
self.mutex.lock();
let already_watched = self.index_of(hash).is_some();
self.mutex.unlock();
if already_watched {
return true;
}
}
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
let fd: Fd = {
let mut path_z = bun_paths::PathBuffer::uninit();
if file_path.len() >= path_z.len() {
return false;
}
path_z[..file_path.len()].copy_from_slice(file_path);
path_z[file_path.len()] = 0;
let z = ZStr::from_buf(&path_z[..], file_path.len());
match bun_sys::open(z, WATCH_OPEN_FLAGS, 0) {
Ok(opened) => opened,
Err(_) => return false,
}
};
#[cfg(not(any(target_os = "macos", target_os = "freebsd")))]
let fd: Fd = Fd::INVALID;
let res = self.add_file::<true>(fd, file_path, hash, loader, Fd::INVALID, None);
match res {
Ok(()) => {
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
if fd.is_valid() {
self.mutex.lock();
let maybe_idx = self.index_of(hash);
let stored_fd = if let Some(idx) = maybe_idx {
self.watchlist.items_fd()[idx as usize]
} else {
Fd::INVALID
};
self.mutex.unlock();
if maybe_idx.is_some() && stored_fd.native() != fd.native() {
let _ = bun_sys::close(fd);
}
}
true
}
Err(_) => {
if fd.is_valid() {
let _ = bun_sys::close(fd);
}
false
}
}
}
pub fn add_file<const CLONE_FILE_PATH: bool>(
&mut self,
fd: Fd,
file_path: &[u8],
hash: HashType,
loader: Loader,
dir_fd: Fd,
package_json: Option<&'static PackageJSON>,
) -> sys::Result<()> {
self.mutex.lock();
if let Some(index) = self.index_of(hash) {
if feature_flags::ATOMIC_FILE_WATCHER {
if fd.is_valid() {
let fds = self.watchlist.items_fd_mut();
fds[index as usize] = fd;
}
}
self.mutex.unlock();
return Ok(());
}
let r = self.append_file_maybe_lock::<CLONE_FILE_PATH, false>(
fd,
file_path,
hash,
loader,
dir_fd,
package_json,
);
self.mutex.unlock();
r
}
pub fn index_of(&self, hash: HashType) -> Option<u32> {
for (i, other) in self.watchlist.items_hash().iter().enumerate() {
if hash == *other {
return Some(i as u32);
}
}
None
}
pub fn remove(&mut self, hash: HashType) {
self.mutex.lock();
if let Some(index) = self.index_of(hash) {
self.remove_at_index(WatchItemKind::File, index as WatchItemIndex, hash, &[]);
}
self.mutex.unlock();
}
pub fn remove_at_index(
&mut self,
kind: WatchItemKind,
index: WatchItemIndex,
hash: HashType,
parents: &[HashType],
) {
debug_assert!(index != NO_WATCH_ITEM);
self.evict_list[self.evict_list_i as usize] = index;
self.evict_list_i += 1;
if kind == WatchItemKind::Directory {
for &parent in parents {
if parent == hash {
self.evict_list[self.evict_list_i as usize] = parent as WatchItemIndex;
self.evict_list_i += 1;
}
}
}
}
pub fn get_resolve_watcher(&mut self) -> AnyResolveWatcher {
fn wrap(ctx: *mut (), dir_path: &[u8], dir_fd: Fd) {
let this = unsafe { &mut *ctx.cast::<Watcher>() };
Watcher::on_maybe_watch_directory(this, dir_path, dir_fd);
}
AnyResolveWatcher {
context: std::ptr::from_mut::<Self>(self).cast::<()>(),
callback: wrap,
}
}
pub fn on_maybe_watch_directory(watch: &mut Self, file_path: &[u8], dir_fd: Fd) {
if !strings::contains(file_path, b"node_modules")
&& strings::contains(file_path, watch.top_level_dir())
{
let _ = watch.add_directory::<false>(dir_fd, file_path, Self::get_hash(file_path));
}
}
}
#[derive(Clone, Copy, Default)]
pub struct WatchEvent {
pub index: WatchItemIndex,
pub op: Op,
pub name_off: u8,
pub name_len: u8,
}
impl WatchEvent {
pub fn names<'b>(self, buf: &'b [ChangedFilePath]) -> &'b [ChangedFilePath] {
if self.name_len == 0 {
return &[];
}
&buf[self.name_off as usize..][..self.name_len as usize]
}
pub fn sort_by_index(event: WatchEvent, rhs: WatchEvent) -> core::cmp::Ordering {
event.index.cmp(&rhs.index)
}
pub fn merge(&mut self, other: WatchEvent) {
self.name_len += other.name_len;
self.op = Op::merge(self.op, other.op);
}
}
bitflags::bitflags! {
#[derive(Clone, Copy, Default, PartialEq, Eq)]
pub struct Op: u8 {
const DELETE = 1 << 0;
const METADATA = 1 << 1;
const RENAME = 1 << 2;
const WRITE = 1 << 3;
const MOVE_TO = 1 << 4;
const CREATE = 1 << 5;
}
}
impl Op {
pub fn merge(before: Op, after: Op) -> Op {
before | after
}
}
pub(crate) const OP_NAMES: &[(Op, &str)] = &[
(Op::DELETE, "delete"),
(Op::METADATA, "metadata"),
(Op::RENAME, "rename"),
(Op::WRITE, "write"),
(Op::MOVE_TO, "move_to"),
(Op::CREATE, "create"),
];
impl fmt::Display for Op {
fn fmt(&self, w: &mut fmt::Formatter<'_>) -> fmt::Result {
w.write_str("{")?;
let mut first = true;
for &(flag, name) in OP_NAMES {
if self.contains(flag) {
if !first {
w.write_str(",")?;
}
first = false;
w.write_str(name)?;
}
}
w.write_str("}")
}
}
pub struct WatchItem {
pub file_path: Cow<'static, [u8]>,
pub hash: u32,
pub loader: Loader,
pub fd: Fd,
pub count: u32,
pub parent_hash: u32,
pub kind: WatchItemKind,
pub package_json: Option<&'static PackageJSON>,
#[cfg(any(target_os = "linux", target_os = "android"))]
pub eventlist_index: platform::EventListIndex,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum WatchItemKind {
File,
Directory,
}
pub trait WatchItemColumns {
fn items_file_path(&self) -> &[Cow<'static, [u8]>];
fn items_hash(&self) -> &[u32];
fn items_fd(&self) -> &[Fd];
fn items_fd_mut(&mut self) -> &mut [Fd];
fn items_parent_hash(&self) -> &[u32];
fn items_kind(&self) -> &[WatchItemKind];
#[cfg(any(target_os = "linux", target_os = "android"))]
fn items_eventlist_index(&self) -> &[platform::EventListIndex];
}
impl WatchItemColumns for WatchList {
fn items_file_path(&self) -> &[Cow<'static, [u8]>] {
self.items::<"file_path", Cow<'static, [u8]>>()
}
fn items_hash(&self) -> &[u32] {
self.items::<"hash", u32>()
}
fn items_fd(&self) -> &[Fd] {
self.items::<"fd", Fd>()
}
fn items_fd_mut(&mut self) -> &mut [Fd] {
self.items_mut::<"fd", Fd>()
}
fn items_parent_hash(&self) -> &[u32] {
self.items::<"parent_hash", u32>()
}
fn items_kind(&self) -> &[WatchItemKind] {
self.items::<"kind", WatchItemKind>()
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn items_eventlist_index(&self) -> &[platform::EventListIndex] {
self.items::<"eventlist_index", platform::EventListIndex>()
}
}
impl WatchItemColumns for bun_collections::multi_array_list::Slice<WatchItem> {
fn items_file_path(&self) -> &[Cow<'static, [u8]>] {
self.items::<"file_path", Cow<'static, [u8]>>()
}
fn items_hash(&self) -> &[u32] {
self.items::<"hash", u32>()
}
fn items_fd(&self) -> &[Fd] {
self.items::<"fd", Fd>()
}
fn items_fd_mut(&mut self) -> &mut [Fd] {
self.items_mut::<"fd", Fd>()
}
fn items_parent_hash(&self) -> &[u32] {
self.items::<"parent_hash", u32>()
}
fn items_kind(&self) -> &[WatchItemKind] {
self.items::<"kind", WatchItemKind>()
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn items_eventlist_index(&self) -> &[platform::EventListIndex] {
self.items::<"eventlist_index", platform::EventListIndex>()
}
}
// ported from: src/watcher/Watcher.zig