diff --git a/README.md b/README.md index 706beb1d..cb025058 100644 --- a/README.md +++ b/README.md @@ -18,16 +18,19 @@ const watcher = require('@parcel/watcher'); const path = require('path'); // Subscribe to events -watcher.subscribe(process.cwd(), (events) => { +let subscription = await watcher.subscribe(process.cwd(), (events) => { console.log(events); }); +// later on... +await subscription.unsubscribe(); + // Get events since some saved snapshot in the past let snapshotPath = path.join(process.cwd(), 'snapshot.txt'); -let events = watcher.getEventsSince(process.cwd(), snapshotPath); +let events = await watcher.getEventsSince(process.cwd(), snapshotPath); // Save a snapshot for later -watcher.writeSnapshot(process.cwd(), snapshotPath); +await watcher.writeSnapshot(process.cwd(), snapshotPath); ``` ## Watching @@ -38,11 +41,23 @@ Events are throttled and coalesced for performance during large changes like `gi Only one notification will be emitted per file. For example, if a file was both created and updated since the last event, you'll get only a `create` event. If a file is both created and deleted, you will not be notifed of that file. Renames cause two events: a `delete` for the old name, and a `create` for the new name. +```javascript +let subscription = await watcher.subscribe(process.cwd(), (events) => { + console.log(events); +}); +``` + Events have two properties: * `type` - the event type: `create`, `update`, or `delete`. * `path` - the absolute realpath to the file. +To unsubscribe from change notifications, call the `unsubscribe` method on the returned subscription object. + +```javascript +await subscription.unsubscribe(); +``` + `@parcel/watcher` has the following watcher backends, listed in priority order: * [FSEvents](https://developer.apple.com/documentation/coreservices/file_system_events) on macOS @@ -59,13 +74,13 @@ You can specify the exact backend you wish to use by passing the `backend` optio In order to query for historical changes, you first need a previous snapshot to compare to. This can be saved to a file with the `writeSnapshot` function, e.g. just before your program exits. ```javascript -watcher.writeSnapshot(dirPath, snapshotPath); +await watcher.writeSnapshot(dirPath, snapshotPath); ``` When your program starts up, you can query for changes that have occurred since that snapshot using the `getEventsSince` function. ```javascript -let events = watcher.getEventsSince(dirPath, snapshotPath); +let events = await watcher.getEventsSince(dirPath, snapshotPath); ``` The events returned are exactly the same as the events that would be passed to the `subscribe` callback (see above). @@ -87,7 +102,7 @@ You can specify the exact backend you wish to use by passing the `backend` optio All of the APIs in `@parcel/watcher` support the following options, which are passed as an object as the last function argument. -* `ignore` - an array of paths to ignore. They can be either files or directories. No events will be emitted about these files or directories or their children. They must be absolute paths. +* `ignore` - an array of paths to ignore. They can be either files or directories. No events will be emitted about these files or directories or their children. * `backend` - the name of an explicitly chosen backend to use. Allowed options are `"fs-events"`, `"watchman"`, `"inotify"`, `"windows"`, or `"brute-force"` (only for querying). If the specified backend is not available on the current platform, the default backend will be used instead. ## License diff --git a/index.js b/index.js index 2244c5c5..f04a5c95 100644 --- a/index.js +++ b/index.js @@ -1 +1,36 @@ -module.exports = require('bindings')('fschanges.node'); \ No newline at end of file +const binding = require('bindings')('fschanges.node'); +const path = require('path'); + +function normalizeOptions(dir, opts = {}) { + if (Array.isArray(opts.ignore)) { + opts = Object.assign({}, opts, { + ignore: opts.ignore.map(ignore => path.resolve(dir, ignore)) + }); + } + + return opts; +} + +exports.writeSnapshot = (dir, snapshot, opts) => { + return binding.writeSnapshot(path.resolve(dir), path.resolve(snapshot), normalizeOptions(dir, opts)); +}; + +exports.getEventsSince = (dir, snapshot, opts) => { + return binding.getEventsSince(path.resolve(dir), path.resolve(snapshot), normalizeOptions(dir, opts)); +}; + +exports.subscribe = async (dir, fn, opts) => { + dir = path.resolve(dir); + opts = normalizeOptions(dir, opts); + await binding.subscribe(dir, fn, opts); + + return { + unsubscribe() { + return binding.unsubscribe(dir, fn, opts); + } + }; +}; + +exports.unsubscribe = (dir, fn, opts) => { + return binding.unsubscribe(path.resolve(dir), fn, normalizeOptions(dir, opts)); +}; diff --git a/test/watcher.js b/test/watcher.js index e3dc4065..d306c788 100644 --- a/test/watcher.js +++ b/test/watcher.js @@ -36,7 +36,7 @@ describe('watcher', () => { let c = 0; const getFilename = (...dir) => path.join(tmpDir, ...dir, `test${c++}${Math.random().toString(31).slice(2)}`); - let ignoreDir, ignoreFile; + let ignoreDir, ignoreFile, sub; before(async () => { tmpDir = path.join(fs.realpathSync(require('os').tmpdir()), Math.random().toString(31).slice(2)); @@ -44,11 +44,11 @@ describe('watcher', () => { ignoreDir = getFilename(); ignoreFile = getFilename(); await new Promise(resolve => setTimeout(resolve, 100)); - await fschanges.subscribe(tmpDir, fn, {backend, ignore: [ignoreDir, ignoreFile]}); + sub = await fschanges.subscribe(tmpDir, fn, {backend, ignore: [ignoreDir, ignoreFile]}); }); after(async () => { - await fschanges.unsubscribe(tmpDir, fn, {backend, ignore: [ignoreDir, ignoreFile]}); + await sub.unsubscribe(); }); describe('files', () => { @@ -442,13 +442,11 @@ describe('watcher', () => { await new Promise(resolve => setTimeout(resolve, 100)); function listen() { - return new Promise(resolve => { - let fn = events => { + return new Promise(async resolve => { + let sub = await fschanges.subscribe(dir, async events => { setImmediate(() => resolve(events)); - fschanges.unsubscribe(dir, fn, {backend}); - }; - - fschanges.subscribe(dir, fn, {backend}); + await sub.unsubscribe(); + }, {backend}); }); } @@ -471,13 +469,11 @@ describe('watcher', () => { await new Promise(resolve => setTimeout(resolve, 100)); function listen(ignore) { - return new Promise(resolve => { - let fn = events => { + return new Promise(async resolve => { + let sub = await fschanges.subscribe(dir, async events => { setImmediate(() => resolve(events)); - fschanges.unsubscribe(dir, fn, {backend, ignore}); - }; - - fschanges.subscribe(dir, fn, {backend, ignore}); + await sub.unsubscribe(); + }, {backend, ignore}); }); } @@ -503,13 +499,11 @@ describe('watcher', () => { await new Promise(resolve => setTimeout(resolve, 100)); function listen(dir) { - return new Promise(resolve => { - let fn = events => { + return new Promise(async resolve => { + let sub = await fschanges.subscribe(dir, async events => { setImmediate(() => resolve(events)); - fschanges.unsubscribe(dir, fn, {backend}); - }; - - fschanges.subscribe(dir, fn, {backend}); + await sub.unsubscribe(); + }, {backend}); }); } @@ -534,12 +528,10 @@ describe('watcher', () => { await new Promise(resolve => setTimeout(resolve, 100)); function listen(dir) { - return new Promise(resolve => { - let fn = events => { - setImmediate(() => resolve([events, fn])); - }; - - fschanges.subscribe(dir, fn, {backend}); + return new Promise(async resolve => { + let sub = await fschanges.subscribe(dir, events => { + setImmediate(() => resolve([events, sub])); + }); }); } @@ -555,7 +547,7 @@ describe('watcher', () => { await fs.writeFile(path.join(dir, 'test2.txt'), 'hello2'); await new Promise(resolve => setTimeout(resolve, 100)); - let [watched, fn] = await l; + let [watched, sub] = await l; assert.deepEqual(watched, [ {type: 'create', path: path.join(dir, 'test1.txt')} ]); @@ -565,7 +557,7 @@ describe('watcher', () => { {type: 'create', path: path.join(dir, 'test2.txt')} ]); - fschanges.unsubscribe(dir, fn, {backend}); + await sub.unsubscribe(); }); }); });