• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

albe / node-event-storage / 23897068196

02 Apr 2026 10:56AM UTC coverage: 98.151% (+0.1%) from 98.054%
23897068196

Pull #257

github

web-flow
Merge b52671064 into 50d3642f2
Pull Request #257: Move to ES Modules (ESM)

890 of 929 branches covered (95.8%)

Branch coverage included in aggregate %.

118 of 118 new or added lines in 21 files covered. (100.0%)

55 existing lines in 12 files now uncovered.

4684 of 4750 relevant lines covered (98.61%)

787.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.35
/src/Watcher.js
1
import fs from 'fs';
4✔
2
import path from 'path';
4✔
3
import events from 'events';
4✔
4
import { assert } from './util.js';
4✔
5

4✔
6
/** @type {Map<string, DirectoryWatcher>} */
4✔
7
const directoryWatchers = new Map();
4✔
8

4✔
9
/**
4✔
10
 * A reference counting singleton nodejs watcher for directories.
4✔
11
 * Emits events 'change' and 'rename' with the file name as argument.
4✔
12
 */
4✔
13
class DirectoryWatcher extends events.EventEmitter {
4✔
14

4✔
15
    /**
4✔
16
     * @param {string} directory
4✔
17
     * @param {object} [options] The options to pass to the fs.watch call. See https://nodejs.org/api/fs.html#fs_fs_watch_filename_options_listener
4✔
18
     * @returns {DirectoryWatcher}
4✔
19
     */
4✔
20
    constructor(directory, options = {}) {
4✔
21
        directory = path.normalize(directory);
412✔
22

412✔
23
        if (directoryWatchers.has(directory)) {
412✔
24
            const watcher = directoryWatchers.get(directory);
184✔
25
            watcher.references++;
184✔
26
            return watcher;
184✔
27
        }
184✔
28
        assert(fs.existsSync(directory), `Can not watch a non-existing directory "${directory}".`);
228✔
29
        assert(fs.statSync(directory).isDirectory(), `Can only watch directories, but "${directory}" is none.`);
228✔
30
        super();
228✔
31
        this.setMaxListeners(1000);
228✔
32
        directoryWatchers.set(directory, this);
228✔
33
        this.directory = directory;
228✔
34
        this.watcher = fs.watch(directory, Object.assign({ persistent: false }, options), this.emit.bind(this));
228✔
35
        this.references = 1;
228✔
36
    }
412✔
37

4✔
38
    /**
4✔
39
     * Close this watcher.
4✔
40
     * @returns void
4✔
41
     */
4✔
42
    close() {
4✔
43
        this.references--;
412✔
44
        if (this.references === 0 && this.watcher) {
412✔
45
            this.watcher.close();
228✔
46
            this.watcher = null;
228✔
47
            directoryWatchers.delete(this.directory);
228✔
48
        }
228✔
49
    }
412✔
50

4✔
51
}
4✔
52

4✔
53
/**
4✔
54
 * A watcher for a single file or a directory, with the possibility to provide a filter method for file names to watch.
4✔
55
 */
4✔
56
class Watcher {
4✔
57

4✔
58
    /**
4✔
59
     * @param {string|string[]} fileOrDirectory The filename or directory or list of directories to watch
4✔
60
     * @param {function(string): boolean} [fileFilter] A filter that will receive a filename and needs to return true if this watcher should be invoked. Will be ignored if the first argument is a file.
4✔
61
     * @returns {Watcher}
4✔
62
     */
4✔
63
    constructor(fileOrDirectory, fileFilter = null) {
4✔
64
        let directories;
376✔
65
        if (typeof fileOrDirectory === 'string') {
376✔
66
            directories = [fileOrDirectory];
284✔
67
            if (!fs.statSync(fileOrDirectory).isDirectory()) {
284✔
68
                directories = [path.dirname(fileOrDirectory)];
248✔
69
                const filename = path.basename(fileOrDirectory);
248✔
70
                fileFilter = changedFilename => changedFilename === filename;
248✔
71
            }
248✔
72
        } else {
376✔
73
            directories = [...new Set(fileOrDirectory.map(path.normalize))];
92✔
74
        }
92✔
75

368✔
76
        this.watchers = directories.map(dir => new DirectoryWatcher(dir));
368✔
77

368✔
78
        if (fileFilter === null) {
376✔
79
            fileFilter = () => true;
28✔
80
        }
28✔
81

368✔
82
        this.fileFilter = fileFilter;
368✔
83
        this.onChange = this.onChange.bind(this);
368✔
84
        this.onRename = this.onRename.bind(this);
368✔
85
        this.watchers.forEach(watcher => {
368✔
86
            watcher.on('change', this.onChange);
412✔
87
            watcher.on('rename', this.onRename);
412✔
88
        });
368✔
89
        this.handlers = { change: [], rename: [] };
368✔
90
    }
376✔
91

4✔
92
    /**
4✔
93
     * Register a new handler that is triggered if the fileFilter matches.
4✔
94
     * @param {string} eventType
4✔
95
     * @param {function(string): void} handler A handler method that should be invoked with the filename as argument
4✔
96
     * @api
4✔
97
     */
4✔
98
    on(eventType, handler) {
4✔
99
        assert(eventType in this.handlers, `Event type ${eventType} is unknown. Only 'change' and 'rename' are supported.`);
592✔
100

592✔
101
        this.handlers[eventType].push(handler);
592✔
102
    }
592✔
103

4✔
104
    /**
4✔
105
     * @private
4✔
106
     * @param {string} filename
4✔
107
     */
4✔
108
    onChange(filename) {
4✔
109
        if (this.handlers.change.length === 0) {
212✔
110
            return;
80✔
111
        }
80✔
112
        if (!filename || !this.fileFilter(filename)) {
212✔
113
            return;
76✔
114
        }
76✔
115
        this.handlers.change
56✔
116
            .forEach((handler) => handler(filename));
56✔
117
    }
212✔
118

4✔
119
    /**
4✔
120
     * @private
4✔
121
     * @param {string} filename
4✔
122
     */
4✔
123
    onRename(filename) {
4✔
124
        if (this.handlers.rename.length === 0) {
124!
125
            return;
×
UNCOV
126
        }
×
127
        if (!filename || !this.fileFilter(filename)) {
124✔
128
            return;
72✔
129
        }
72✔
130
        this.handlers.rename
52✔
131
            .forEach((handler) => handler(filename));
52✔
132
    }
124✔
133

4✔
134
    /**
4✔
135
     * Close this watcher and release all handlers.
4✔
136
     * @api
4✔
137
     */
4✔
138
    close() {
4✔
139
        this.watchers.forEach(watcher => {
376✔
140
            watcher.removeListener('change', this.onChange);
412✔
141
            watcher.removeListener('rename', this.onRename);
412✔
142
            watcher.close();
412✔
143
        });
376✔
144
        this.watchers = [];
376✔
145
        this.handlers = { change: [], rename: [] };
376✔
146
    }
376✔
147

4✔
148
}
4✔
149

4✔
150
export default Watcher;
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc