aboutsummaryrefslogtreecommitdiff
path: root/src/js/node
diff options
context:
space:
mode:
Diffstat (limited to 'src/js/node')
-rw-r--r--src/js/node/fs.js65
-rw-r--r--src/js/node/fs.promises.ts51
2 files changed, 115 insertions, 1 deletions
diff --git a/src/js/node/fs.js b/src/js/node/fs.js
index f117020dd..6b0e3954e 100644
--- a/src/js/node/fs.js
+++ b/src/js/node/fs.js
@@ -1,3 +1,5 @@
+import { EventEmitter } from "stream";
+
// Hardcoded module "node:fs"
var { direct, isPromise, isCallable } = import.meta.primordials;
var promises = import.meta.require("node:fs/promises");
@@ -7,6 +9,63 @@ var NativeReadable = _getNativeReadableStreamPrototype(2, Readable); // 2 means
var fs = Bun.fs();
var debug = process.env.DEBUG ? console.log : () => {};
+
+class FSWatcher extends EventEmitter {
+ #watcher;
+ #listener;
+ constructor(path, options, listener) {
+ super();
+
+ if (typeof options === "function") {
+ listener = options;
+ options = {};
+ } else if (typeof options === "string") {
+ options = { encoding: options };
+ }
+
+ if (typeof listener !== "function") {
+ listener = () => {};
+ }
+
+ this.#listener = listener;
+ try {
+ this.#watcher = fs.watch(path, options || {}, this.#onEvent.bind(this));
+ } catch (e) {
+ if (!e.message?.startsWith("FileNotFound")) {
+ throw e;
+ }
+ const notFound = new Error(`ENOENT: no such file or directory, watch '${path}'`);
+ notFound.code = "ENOENT";
+ notFound.errno = -2;
+ notFound.path = path;
+ notFound.syscall = "watch";
+ notFound.filename = path;
+ throw notFound;
+ }
+ }
+
+ #onEvent(eventType, filenameOrError) {
+ if (eventType === "error" || eventType === "close") {
+ this.emit(eventType, filenameOrError);
+ } else {
+ this.emit("change", eventType, filenameOrError);
+ this.#listener(eventType, filenameOrError);
+ }
+ }
+
+ close() {
+ this.#watcher?.close();
+ this.#watcher = null;
+ }
+
+ ref() {
+ this.#watcher?.ref();
+ }
+
+ unref() {
+ this.#watcher?.unref();
+ }
+}
export var access = function access(...args) {
callbackify(fs.accessSync, args);
},
@@ -153,6 +212,9 @@ export var access = function access(...args) {
rmdirSync = fs.rmdirSync.bind(fs),
Dirent = fs.Dirent,
Stats = fs.Stats,
+ watch = function watch(path, options, listener) {
+ return new FSWatcher(path, options, listener);
+ },
promises = import.meta.require("node:fs/promises");
function callbackify(fsFunction, args) {
@@ -1002,7 +1064,8 @@ export default {
writeSync,
WriteStream,
ReadStream,
-
+ watch,
+ FSWatcher,
[Symbol.for("::bunternal::")]: {
ReadStreamClass,
WriteStreamClass,
diff --git a/src/js/node/fs.promises.ts b/src/js/node/fs.promises.ts
index de802928b..7df446ccb 100644
--- a/src/js/node/fs.promises.ts
+++ b/src/js/node/fs.promises.ts
@@ -1,4 +1,5 @@
// Hardcoded module "node:fs/promises"
+
// Note: `constants` is injected into the top of this file
declare var constants: typeof import("node:fs/promises").constants;
@@ -38,6 +39,55 @@ var promisify = {
},
}[notrace];
+export function watch(
+ filename: string | Buffer | URL,
+ options: { encoding?: BufferEncoding; persistent?: boolean; recursive?: boolean; signal?: AbortSignal } = {},
+) {
+ type Event = {
+ eventType: string;
+ filename: string | Buffer | undefined;
+ };
+ const events: Array<Event> = [];
+ if (filename instanceof URL) {
+ throw new TypeError("Watch URLs are not supported yet");
+ } else if (Buffer.isBuffer(filename)) {
+ filename = filename.toString();
+ } else if (typeof filename !== "string") {
+ throw new TypeError("Expected path to be a string or Buffer");
+ }
+ let nextEventResolve: Function | null = null;
+ if (typeof options === "string") {
+ options = { encoding: options };
+ }
+ fs.watch(filename, options || {}, (eventType: string, filename: string | Buffer | undefined) => {
+ events.push({ eventType, filename });
+ if (nextEventResolve) {
+ const resolve = nextEventResolve;
+ nextEventResolve = null;
+ resolve();
+ }
+ });
+ return {
+ async *[Symbol.asyncIterator]() {
+ let closed = false;
+ while (!closed) {
+ while (events.length) {
+ let event = events.shift() as Event;
+ if (event.eventType === "close") {
+ closed = true;
+ break;
+ }
+ if (event.eventType === "error") {
+ closed = true;
+ throw event.filename;
+ }
+ yield event;
+ }
+ await new Promise((resolve: Function) => (nextEventResolve = resolve));
+ }
+ },
+ };
+}
export var access = promisify(fs.accessSync),
appendFile = promisify(fs.appendFileSync),
close = promisify(fs.closeSync),
@@ -112,6 +162,7 @@ export default {
lutimes,
rm,
rmdir,
+ watch,
constants,
[Symbol.for("CommonJS")]: 0,
};