aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Ashcon Partovi <ashcon@partovi.net> 2023-07-25 23:55:39 -0700
committerGravatar Ashcon Partovi <ashcon@partovi.net> 2023-07-25 23:55:39 -0700
commit57928f7e8061783bb7a4fb41f68d32b4b9c7bca2 (patch)
tree03c1205d6fb930ed45d41f949ebddf33de51215f
parent6bfee02301a2e2a0b79339974af0445eb5a2688f (diff)
downloadbun-fs-watch-file.tar.gz
bun-fs-watch-file.tar.zst
bun-fs-watch-file.zip
Implement `fs.watchFile()`fs-watch-file
Closes #3812
-rw-r--r--packages/bun-types/fs.d.ts156
-rw-r--r--src/bun.js/bindings/bindings.zig21
-rw-r--r--src/bun.js/node/node_fs_watcher.zig5
-rw-r--r--src/bun.js/node/types.zig47
-rw-r--r--src/js/node/fs.js159
-rw-r--r--src/js/out/modules/node/fs.js105
6 files changed, 479 insertions, 14 deletions
diff --git a/packages/bun-types/fs.d.ts b/packages/bun-types/fs.d.ts
index 5fb552b7c..3338bcb82 100644
--- a/packages/bun-types/fs.d.ts
+++ b/packages/bun-types/fs.d.ts
@@ -4067,6 +4067,162 @@ declare module "fs" {
filename: PathLike,
listener?: WatchListener<string>,
): FSWatcher;
+
+ /**
+ * A successful call to {@link watchFile} will return a new fs.StatWatcher object.
+ * @since 0.7.1
+ */
+ export interface StatWatcher extends EventEmitter {
+ /**
+ * When called, requests that the Node.js event loop not exit so long as the watcher is active.
+ *
+ * Calling watcher.ref() multiple times will have no effect.
+ */
+ ref(): this;
+
+ /**
+ * When called, the active watcher will not require the Node.js event loop to remain active.
+ * If there is no other activity keeping the event loop running, the process may exit before
+ * the watcher's callback is invoked.
+ *
+ * Calling watcher.unref() multiple times will have no effect.
+ */
+ unref(): this;
+ }
+
+ /**
+ * Watch for changes on `filename`. The callback `listener` will be called each
+ * time the file is accessed.
+ *
+ * The `options` argument may be omitted. If provided, it should be an object. The`options` object may contain a boolean named `persistent` that indicates
+ * whether the process should continue to run as long as files are being watched.
+ * The `options` object may specify an `interval` property indicating how often the
+ * target should be polled in milliseconds.
+ *
+ * The `listener` gets two arguments the current stat object and the previous
+ * stat object:
+ *
+ * ```js
+ * import { watchFile } from "node:fs";
+ *
+ * watchFile("example.txt", (curr, prev) => {
+ * console.log(`the current mtime is: ${curr.mtime}`);
+ * console.log(`the previous mtime was: ${prev.mtime}`);
+ * });
+ * ```
+ *
+ * These stat objects are instances of `fs.Stat`. If the `bigint` option is `true`,
+ * the numeric values in these objects are specified as `BigInt`s.
+ *
+ * To be notified when the file was modified, not just accessed, it is necessary
+ * to compare `curr.mtimeMs` and `prev.mtimeMs`.
+ *
+ * When an `fs.watchFile` operation results in an `ENOENT` error, it
+ * will invoke the listener once, with all the fields zeroed (or, for dates, the
+ * Unix Epoch). If the file is created later on, the listener will be called
+ * again, with the latest stat objects. This is a change in functionality since
+ * v0.10.
+ *
+ * Using {@link watch} is more efficient than `fs.watchFile` and`fs.unwatchFile`.
+ * `fs.watch` should be used instead of `fs.watchFile` and`fs.unwatchFile` when possible.
+ *
+ * When a file being watched by `fs.watchFile()` disappears and reappears,
+ * then the contents of `previous` in the second callback event (the file's
+ * reappearance) will be the same as the contents of `previous` in the first
+ * callback event (its disappearance).
+ *
+ * This happens when:
+ *
+ * * the file is deleted, followed by a restore
+ * * the file is renamed and then renamed a second time back to its original name
+ *
+ * @since 0.7.1
+ */
+ export type WatchFileOptions = {
+ bigint?: boolean;
+ persistent?: boolean;
+ interval?: number;
+ };
+
+ export type StatsListener = (curr: Stats, prev: Stats) => void;
+ export type BigIntStatsListener = (curr: BigIntStats, prev: BigIntStats) => void;
+
+ /**
+ * Watch for changes on `filename`. The callback `listener` will be called each
+ * time the file is accessed.
+ *
+ * The `options` argument may be omitted. If provided, it should be an object. The`options` object may contain a boolean named `persistent` that indicates
+ * whether the process should continue to run as long as files are being watched.
+ * The `options` object may specify an `interval` property indicating how often the
+ * target should be polled in milliseconds.
+ *
+ * The `listener` gets two arguments the current stat object and the previous
+ * stat object:
+ *
+ * ```js
+ * import { watchFile } from "node:fs";
+ *
+ * watchFile("example.txt", (curr, prev) => {
+ * console.log(`the current mtime is: ${curr.mtime}`);
+ * console.log(`the previous mtime was: ${prev.mtime}`);
+ * });
+ * ```
+ *
+ * These stat objects are instances of `fs.Stat`. If the `bigint` option is `true`,
+ * the numeric values in these objects are specified as `BigInt`s.
+ *
+ * To be notified when the file was modified, not just accessed, it is necessary
+ * to compare `curr.mtimeMs` and `prev.mtimeMs`.
+ *
+ * When an `fs.watchFile` operation results in an `ENOENT` error, it
+ * will invoke the listener once, with all the fields zeroed (or, for dates, the
+ * Unix Epoch). If the file is created later on, the listener will be called
+ * again, with the latest stat objects. This is a change in functionality since
+ * v0.10.
+ *
+ * Using {@link watch} is more efficient than `fs.watchFile` and`fs.unwatchFile`.
+ * `fs.watch` should be used instead of `fs.watchFile` and`fs.unwatchFile` when possible.
+ *
+ * When a file being watched by `fs.watchFile()` disappears and reappears,
+ * then the contents of `previous` in the second callback event (the file's
+ * reappearance) will be the same as the contents of `previous` in the first
+ * callback event (its disappearance).
+ *
+ * This happens when:
+ *
+ * * the file is deleted, followed by a restore
+ * * the file is renamed and then renamed a second time back to its original name
+ *
+ * @since 0.7.1
+ */
+ export function watchFile(
+ filename: PathLike,
+ options:
+ | (WatchFileOptions & {
+ bigint?: false | undefined;
+ })
+ | undefined,
+ listener: StatsListener,
+ ): StatWatcher;
+
+ export function watchFile(
+ filename: PathLike,
+ options:
+ | (WatchFileOptions & {
+ bigint: true;
+ })
+ | undefined,
+ listener: BigIntStatsListener,
+ ): StatWatcher;
+
+ /**
+ * Watch for changes on `filename`. The callback `listener` will be called each time the file is accessed.
+ * @param filename A path to a file or directory. If a URL is provided, it must use the `file:` protocol.
+ */
+ export function watchFile(
+ filename: PathLike,
+ listener: StatsListener,
+ ): StatWatcher;
}
declare module "node:fs" {
diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig
index 924eb27a1..f6b9f3ce1 100644
--- a/src/bun.js/bindings/bindings.zig
+++ b/src/bun.js/bindings/bindings.zig
@@ -3461,6 +3461,7 @@ pub const JSValue = enum(JSValueReprInt) {
i8 => @as(i8, @truncate(toInt32(this))),
i32 => @as(i32, @truncate(toInt32(this))),
i64 => this.toInt64(),
+ f64 => this.asNumber(),
bool => this.toBoolean(),
else => @compileError("Not implemented yet"),
};
@@ -3989,6 +3990,10 @@ pub const JSValue = enum(JSValueReprInt) {
return FFI.JSVALUE_IS_NUMBER(.{ .asJSValue = this });
}
+ pub fn isNumeric(this: JSValue) bool {
+ return this.isNumber() or this.isBigInt();
+ }
+
pub fn isError(this: JSValue) bool {
if (!this.isCell())
return false;
@@ -5211,15 +5216,21 @@ pub const CallFrame = opaque {
var ptr = self.argumentsPtr();
return switch (@as(u4, @min(len, max))) {
0 => .{ .ptr = undefined, .len = 0 },
- 4 => Arguments(max).init(comptime @min(4, max), ptr),
+ 1 => Arguments(max).init(comptime @min(1, max), ptr),
2 => Arguments(max).init(comptime @min(2, max), ptr),
- 6 => Arguments(max).init(comptime @min(6, max), ptr),
3 => Arguments(max).init(comptime @min(3, max), ptr),
- 8 => Arguments(max).init(comptime @min(8, max), ptr),
+ 4 => Arguments(max).init(comptime @min(4, max), ptr),
5 => Arguments(max).init(comptime @min(5, max), ptr),
- 1 => Arguments(max).init(comptime @min(1, max), ptr),
+ 6 => Arguments(max).init(comptime @min(6, max), ptr),
7 => Arguments(max).init(comptime @min(7, max), ptr),
- else => unreachable,
+ 8 => Arguments(max).init(comptime @min(8, max), ptr),
+ 9 => Arguments(max).init(comptime @min(9, max), ptr),
+ 10 => Arguments(max).init(comptime @min(10, max), ptr),
+ 11 => Arguments(max).init(comptime @min(11, max), ptr),
+ 12 => Arguments(max).init(comptime @min(12, max), ptr),
+ 13 => Arguments(max).init(comptime @min(13, max), ptr),
+ 14 => Arguments(max).init(comptime @min(14, max), ptr),
+ 15 => Arguments(max).init(comptime @min(15, max), ptr),
};
}
diff --git a/src/bun.js/node/node_fs_watcher.zig b/src/bun.js/node/node_fs_watcher.zig
index d0af350c0..4819b61fe 100644
--- a/src/bun.js/node/node_fs_watcher.zig
+++ b/src/bun.js/node/node_fs_watcher.zig
@@ -364,11 +364,6 @@ pub const FSWatcher = struct {
}
},
.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_| {
diff --git a/src/bun.js/node/types.zig b/src/bun.js/node/types.zig
index 23d693d69..1f8964e2a 100644
--- a/src/bun.js/node/types.zig
+++ b/src/bun.js/node/types.zig
@@ -1302,6 +1302,29 @@ fn StatsDataType(comptime T: type) type {
@as(Date, @enumFromInt(@as(u64, @intCast(@max(stat_.birthtime().tv_sec, 0))))),
};
}
+
+ pub fn fromJS(args: []JSC.JSValue) @This() {
+ return @This(){
+ .dev = if (args.len > 0 and args[0].isNumeric()) args[0].to(T) else 0,
+ .ino = if (args.len > 1 and args[1].isNumeric()) args[1].to(T) else 0,
+ .mode = if (args.len > 2 and args[2].isNumeric()) args[2].to(T) else 0,
+ .nlink = if (args.len > 3 and args[3].isNumeric()) args[3].to(T) else 0,
+ .uid = if (args.len > 4 and args[4].isNumeric()) args[4].to(T) else 0,
+ .gid = if (args.len > 5 and args[5].isNumeric()) args[5].to(T) else 0,
+ .rdev = if (args.len > 6 and args[6].isNumeric()) args[6].to(T) else 0,
+ .size = if (args.len > 7 and args[7].isNumeric()) args[7].to(T) else 0,
+ .blksize = if (args.len > 8 and args[8].isNumeric()) args[8].to(T) else 0,
+ .blocks = if (args.len > 9 and args[9].isNumeric()) args[9].to(T) else 0,
+ .atime_ms = if (args.len > 10 and args[10].isNumeric()) args[10].to(f64) else 0,
+ .mtime_ms = if (args.len > 11 and args[11].isNumeric()) args[11].to(f64) else 0,
+ .ctime_ms = if (args.len > 12 and args[12].isNumeric()) args[12].to(f64) else 0,
+ .birthtime_ms = if (args.len > 13 and args[13].isNumeric()) args[13].to(T) else 0,
+ .atime = @as(Date, @enumFromInt(if (args.len > 10 and args[10].isNumeric()) args[10].to(u64) else 0)),
+ .mtime = @as(Date, @enumFromInt(if (args.len > 11 and args[11].isNumeric()) args[11].to(u64) else 0)),
+ .ctime = @as(Date, @enumFromInt(if (args.len > 12 and args[12].isNumeric()) args[12].to(u64) else 0)),
+ .birthtime = @as(Date, @enumFromInt(if (args.len > 13 and args[13].isNumeric()) args[13].to(u64) else 0)),
+ };
+ }
};
}
@@ -1431,10 +1454,26 @@ pub const Stats = union(enum) {
return this;
}
- pub fn constructor(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) ?*Stats {
- globalThis.throw("Stats is not constructable. use fs.stat()", .{});
-
- return null;
+ pub fn constructor(_: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) ?*This {
+ var this = bun.default_allocator.create(Stats) catch unreachable;
+ var arguments = callframe.arguments(15);
+ var args = arguments.ptr[0..arguments.len];
+ if (args.len > 0 and args[0].isBoolean()) {
+ if (args[0].toBoolean()) {
+ this.* = .{
+ .big = StatsDataType(i64).fromJS(args[1..]),
+ };
+ } else {
+ this.* = .{
+ .small = StatsDataType(i32).fromJS(args[1..]),
+ };
+ }
+ } else {
+ this.* = .{
+ .small = StatsDataType(i32).fromJS(args),
+ };
+ }
+ return this;
}
comptime {
diff --git a/src/js/node/fs.js b/src/js/node/fs.js
index 5e72d6e27..37f109a33 100644
--- a/src/js/node/fs.js
+++ b/src/js/node/fs.js
@@ -8,6 +8,7 @@ var { direct, isPromise, isCallable } = $lazy("primordials");
import promises from "node:fs/promises";
export { default as promises } from "node:fs/promises";
import * as Stream from "node:stream";
+import { resolve } from "node:path";
var fs = Bun.fs();
var debug = process.env.DEBUG ? console.log : () => {};
@@ -68,6 +69,124 @@ class FSWatcher extends EventEmitter {
this.#watcher?.unref();
}
}
+
+/** @type {Map<string, Array<[Function, StatWatcher]>>} */
+const statWatchers = new Map();
+
+/** @link https://nodejs.org/api/fs.html#class-fsstatwatcher */
+class StatWatcher extends EventEmitter {
+ #filename;
+ #options;
+ #listener;
+ #watcher;
+ #timer;
+ #stat;
+
+ constructor(filename, options, listener) {
+ super();
+ this.#filename = filename;
+ if (typeof options === "function") {
+ listener = options;
+ options = undefined;
+ } else if (typeof listener !== "function") {
+ listener = () => {};
+ }
+ this.#listener = listener;
+ this.#options = options;
+ const watchKey = resolve(filename);
+ const watchers = statWatchers.get(watchKey);
+ if (watchers === undefined) {
+ statWatchers.set(watchKey, [[this.#listener, this]]);
+ } else {
+ watchers.push([this.#listener, this]);
+ }
+ this.#watch();
+ }
+
+ #watch() {
+ let previous = this.#stat;
+ let current;
+ try {
+ current = this.#stat = fs.statSync(this.#filename);
+ debug("fs.watchFile mtime", current.mtime);
+
+ if (this.#watcher === undefined) {
+ this.#watcher = fs.watch(this.#filename, this.#options, this.#onEvent.bind(this));
+ }
+ } catch (error) {
+ debug("fs.watchFile error", error);
+ if (error.code !== "ENOENT") {
+ throw error;
+ }
+
+ // When an `fs.watchFile` operation results in an ENOENT error,
+ // it will invoke the listener once, with all the fields zeroed (or, for dates, the Unix Epoch).
+ // If the file is created later on, the listener will be called again, with the latest stat objects.
+ if (previous === undefined) {
+ current = this.#stat = new fs.Stats(this.#options?.bigint === true);
+ this.#listener?.(current, current);
+ }
+
+ if (this.#timer === undefined) {
+ this.#timer = setInterval(
+ this.#watch.bind(this),
+ this.#options?.interval ?? 5007, // libuv default
+ );
+ }
+ return;
+ }
+ if (previous !== undefined && previous.mtimeMs !== current.mtimeMs) {
+ this.#listener?.(current, previous);
+ }
+ this.#clear();
+ }
+
+ #onEvent(eventType, filename) {
+ debug("fs.watchFile event", eventType, filename);
+ switch (eventType) {
+ case "close":
+ this.close();
+ break;
+ case "error":
+ this.close();
+ // fallthrough
+ case "rename":
+ case "change":
+ this.#watch();
+ break;
+ }
+ }
+
+ #clear() {
+ if (this.#timer !== undefined) {
+ debug("fs.watchFile clear timer");
+ clearInterval(this.#timer);
+ this.#timer = undefined;
+ }
+ }
+
+ close() {
+ debug("fs.watchFile close");
+ this.#watcher?.close();
+ this.#watcher = undefined;
+ this.#clear();
+ }
+
+ ref() {
+ debug("fs.watchFile ref");
+ this.#watcher?.ref();
+ this.#timer?.ref();
+ return this;
+ }
+
+ unref() {
+ debug("fs.watchFile unref");
+ this.#watcher?.unref();
+ this.#timer?.unref();
+ return this;
+ }
+}
+
export var access = function access(...args) {
callbackify(fs.accessSync, args);
},
@@ -250,6 +369,43 @@ export var access = function access(...args) {
Stats = fs.Stats,
watch = function watch(path, options, listener) {
return new FSWatcher(path, options, listener);
+ },
+ watchFile = function watchFile(path, options, listener) {
+ return new StatWatcher(path, options, listener);
+ },
+ unwatchFile = function unwatchFile(path, listener) {
+ const watchKey = resolve(path);
+ const watchers = statWatchers.get(watchKey);
+ if (watchers === undefined) {
+ return;
+ }
+ if (typeof listener === "function") {
+ const deleted = new Set();
+ for (const [func, watcher] of watchers) {
+ if (listener !== func) {
+ continue;
+ }
+ try {
+ watcher.close();
+ } finally {
+ deleted.add(watcher);
+ }
+ }
+ const remaining = watchers.filter(([_, watcher]) => !deleted.has(watcher));
+ if (remaining.length) {
+ statWatchers.set(watchKey, remaining);
+ } else {
+ statWatchers.delete(watchKey);
+ }
+ return;
+ }
+ try {
+ for (const [_, watcher] of watchers) {
+ watcher.close();
+ }
+ } finally {
+ statWatchers.delete(watchKey);
+ }
};
function callbackify(fsFunction, args) {
@@ -1102,6 +1258,9 @@ export default {
ReadStream,
watch,
FSWatcher,
+ watchFile,
+ unwatchFile,
+ StatWatcher,
writev,
writevSync,
readv,
diff --git a/src/js/out/modules/node/fs.js b/src/js/out/modules/node/fs.js
index b7457f104..5c67f3e0c 100644
--- a/src/js/out/modules/node/fs.js
+++ b/src/js/out/modules/node/fs.js
@@ -2,6 +2,7 @@ import {EventEmitter} from "node:events";
import promises2 from "node:fs/promises";
import {default as default2} from "node:fs/promises";
import * as Stream from "node:stream";
+import {resolve} from "node:path";
var callbackify = function(fsFunction, args) {
try {
const result = fsFunction.apply(fs, args.slice(0, args.length - 1)), callback = args[args.length - 1];
@@ -61,6 +62,75 @@ class FSWatcher extends EventEmitter {
this.#watcher?.unref();
}
}
+var statWatchers = new Map;
+
+class StatWatcher extends EventEmitter {
+ #filename;
+ #options;
+ #listener;
+ #watcher;
+ #timer;
+ #stat;
+ constructor(filename, options, listener) {
+ super();
+ if (this.#filename = filename, typeof options === "function")
+ listener = options, options = void 0;
+ else if (typeof listener !== "function")
+ listener = () => {
+ };
+ this.#listener = listener, this.#options = options;
+ const watchKey = resolve(filename), watchers = statWatchers.get(watchKey);
+ if (watchers === void 0)
+ statWatchers.set(watchKey, [[this.#listener, this]]);
+ else
+ watchers.push([this.#listener, this]);
+ this.#watch();
+ }
+ #watch() {
+ let previous = this.#stat, current;
+ try {
+ if (current = this.#stat = fs.statSync(this.#filename), debug("fs.watchFile mtime", current.mtime), this.#watcher === void 0)
+ this.#watcher = fs.watch(this.#filename, this.#options, this.#onEvent.bind(this));
+ } catch (error) {
+ if (debug("fs.watchFile error", error), error.code !== "ENOENT")
+ throw error;
+ if (previous === void 0)
+ current = this.#stat = new fs.Stats(this.#options?.bigint === !0), this.#listener?.(current, current);
+ if (this.#timer === void 0)
+ this.#timer = setInterval(this.#watch.bind(this), this.#options?.interval ?? 5007);
+ return;
+ }
+ if (previous !== void 0 && previous.mtimeMs !== current.mtimeMs)
+ this.#listener?.(current, previous);
+ this.#clear();
+ }
+ #onEvent(eventType, filename) {
+ switch (debug("fs.watchFile event", eventType, filename), eventType) {
+ case "close":
+ this.close();
+ break;
+ case "error":
+ this.close();
+ case "rename":
+ case "change":
+ this.#watch();
+ break;
+ }
+ }
+ #clear() {
+ if (this.#timer !== void 0)
+ debug("fs.watchFile clear timer"), clearInterval(this.#timer), this.#timer = void 0;
+ }
+ close() {
+ debug("fs.watchFile close"), this.#watcher?.close(), this.#watcher = void 0, this.#clear();
+ }
+ ref() {
+ return debug("fs.watchFile ref"), this.#watcher?.ref(), this.#timer?.ref(), this;
+ }
+ unref() {
+ return debug("fs.watchFile unref"), this.#watcher?.unref(), this.#timer?.unref(), this;
+ }
+}
var access = function access2(...args) {
callbackify(fs.accessSync, args);
}, appendFile = function appendFile2(...args) {
@@ -157,6 +227,36 @@ var access = function access2(...args) {
});
}, readvSync = fs.readvSync.bind(fs), Dirent = fs.Dirent, Stats = fs.Stats, watch = function watch2(path, options, listener) {
return new FSWatcher(path, options, listener);
+}, watchFile = function watchFile2(path, options, listener) {
+ return new StatWatcher(path, options, listener);
+}, unwatchFile = function unwatchFile2(path, listener) {
+ const watchKey = resolve(path), watchers = statWatchers.get(watchKey);
+ if (watchers === void 0)
+ return;
+ if (typeof listener === "function") {
+ const deleted = new Set;
+ for (let [func, watcher] of watchers) {
+ if (listener !== func)
+ continue;
+ try {
+ watcher.close();
+ } finally {
+ deleted.add(watcher);
+ }
+ }
+ const remaining = watchers.filter(([_, watcher]) => !deleted.has(watcher));
+ if (remaining.length)
+ statWatchers.set(watchKey, remaining);
+ else
+ statWatchers.delete(watchKey);
+ return;
+ }
+ try {
+ for (let [_, watcher] of watchers)
+ watcher.close();
+ } finally {
+ statWatchers.delete(watchKey);
+ }
}, readStreamPathFastPathSymbol = Symbol.for("Bun.Node.readStreamPathFastPath"), readStreamSymbol = Symbol.for("Bun.NodeReadStream"), readStreamPathOrFdSymbol = Symbol.for("Bun.NodeReadStreamPathOrFd"), writeStreamSymbol = Symbol.for("Bun.NodeWriteStream"), writeStreamPathFastPathSymbol = Symbol.for("Bun.NodeWriteStreamFastPath"), writeStreamPathFastPathCallSymbol = Symbol.for("Bun.NodeWriteStreamFastPathCall"), kIoDone = Symbol.for("kIoDone"), defaultReadStreamOptions = {
file: void 0,
fd: void 0,
@@ -664,6 +764,9 @@ var fs_default = {
ReadStream,
watch,
FSWatcher,
+ watchFile,
+ unwatchFile,
+ StatWatcher,
writev,
writevSync,
readv,
@@ -680,9 +783,11 @@ export {
writeFileSync,
writeFile,
write,
+ watchFile,
watch,
utimesSync,
utimes,
+ unwatchFile,
unlinkSync,
unlink,
truncateSync,