diff options
Diffstat (limited to 'src/js/node')
-rw-r--r-- | src/js/node/fs.js | 65 | ||||
-rw-r--r-- | src/js/node/fs.promises.ts | 51 |
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, }; |