generated from astrohelm/node-workspace
-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
107 lines (92 loc) · 3.8 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
'use strict';
const { STATS_OPTS, WATCH_OPTS, REC_WATCH_OPTS, EVENTS, createFilter } = require('./lib/utilities');
const { ERR_ACCESS_DENIED, ERR_DUPLICATE, ERR_CAN_NOT_OPEN } = require('./lib/utilities');
const { existsSync, promises: fsp, watch } = require('node:fs');
const { ERR_UNSUPPORTED } = require('./lib/utilities');
const { join, sep } = require('node:path');
module.exports = class FSnitch extends require('./lib/scheduler') {
static watchSync = (path, options, cb) => new FSnitch(options).watchSync(path, cb);
static watch = async (path, options, cb) => new FSnitch(options).watch(path, cb);
static EVENTS = EVENTS;
#watchers = new Map();
#home = process.cwd();
#recursive = true;
predict;
constructor(options = {}) {
super(options?.timeout);
if (typeof options !== 'object') throw new TypeError(`${ERR_UNSUPPORTED} 'options'`);
if (typeof recursive === 'boolean') this.#recursive = options.recursive;
if (typeof home === 'string') this.#home = options.home;
this.predict = createFilter(options.filter);
}
watchSync(path, cb) {
if (cb && typeof cb !== 'function') throw new TypeError(`${ERR_UNSUPPORTED} 'callback'`);
if (typeof path !== 'string') throw new TypeError(`${ERR_UNSUPPORTED} 'path'`);
if (this.#watchers.has(path)) throw new Error(ERR_DUPLICATE);
if (!this.predict(path)) throw new Error(`${ERR_ACCESS_DENIED}: ${path}`);
if (!existsSync(path)) throw new Error(`${ERR_CAN_NOT_OPEN}: ${path}`);
const options = this.#recursive ? REC_WATCH_OPTS : WATCH_OPTS;
const watcher = watch(path, options, this.#listener.bind(this, path, cb));
this.#watchers.set(path, watcher);
return this;
}
async watch(path, cb) {
if (cb && typeof cb !== 'function') throw new TypeError(`${ERR_UNSUPPORTED} 'callback'`);
if (typeof path !== 'string') throw new TypeError(`${ERR_UNSUPPORTED} 'path'`);
if (!this.predict(path)) throw new Error(`${ERR_ACCESS_DENIED}: ${path}`);
const stats = await fsp.stat(path).catch(() => null);
if (stats === null) throw new Error(`${ERR_CAN_NOT_OPEN}: ${path}`);
if (this.#watchers.has(path)) throw new Error(ERR_DUPLICATE);
const options = this.#recursive ? REC_WATCH_OPTS : WATCH_OPTS;
const watcher = watch(path, options, this.#listener.bind(this, path, cb));
this.#watchers.set(path, watcher);
return this;
}
async #listener(path, cb, _, filename) {
const target = path.endsWith(sep + filename) ? path : join(path, filename);
const parsed = this.#home ? target.replace(this.#home, '') : target;
if (!this.predict(target)) return;
const stats = await fsp.stat(target, STATS_OPTS).catch(() => null);
var event = EVENTS.UPDATE;
if (!stats) {
const watcher = this.#watchers.get(target);
if (watcher) watcher.close(), this.#watchers.delete(target);
this._schedule(EVENTS.UNLINK, parsed, null);
cb && cb(event, parsed, null);
return;
}
const { birthtimeMs: bornAt, mtimeMs: modAt, ctimeMs: changedAt } = stats;
if (bornAt === changedAt && bornAt === modAt) event = EVENTS.NEW;
this._schedule(event, parsed, stats);
cb && cb(event, parsed, stats);
}
get observed() {
return [...this.#watchers.keys()];
}
unwatch(path) {
if (typeof path !== 'string') throw new TypeError(`${ERR_UNSUPPORTED} 'path'`);
const watcher = this.#watchers.get(path);
if (!watcher) return false;
this.#watchers.delete(path);
watcher.close();
return true;
}
ref() {
for (var watcher of this.#watchers) watcher.ref();
return this;
}
unref() {
for (var watcher of this.#watchers) watcher.unref();
return this;
}
close() {
super.close();
this.clear();
return this;
}
clear() {
for (var watcher of this.#watchers.values()) watcher.close();
this.#watchers.clear();
return this;
}
};