Comparing version 0.15.3 to 0.16.0
@@ -0,1 +1,7 @@ | ||
# v0.16.0 | ||
- Add `type` field to object passed to transform | ||
- Add `onerror` handler callback option to `Defiler` constructor | ||
- Removed emitted events from `Defiler` instance, as these are now handled more generally by `type` and `onerror` | ||
# v0.15.3 | ||
@@ -2,0 +8,0 @@ |
@@ -10,24 +10,38 @@ 'use strict'; | ||
var symbols = new Proxy({}, { get: (_, key) => Symbol(key) }) | ||
const _path = Symbol(); | ||
const _dir = Symbol(); | ||
const _filename = Symbol(); | ||
const _ext = Symbol(); | ||
const _enc = Symbol(); | ||
const _bytes = Symbol(); | ||
const _text = Symbol(); | ||
let { _path, _dir, _filename, _ext, _enc, _bytes, _text } = symbols; | ||
class File { | ||
constructor() { | ||
this[_path] = null; // path of file | ||
this[_dir] = null; // cached dir | ||
this[_filename] = null; // cached filename | ||
this[_ext] = null; // cached ext | ||
this.stats = null; // stats of file | ||
this[_enc] = 'utf8'; // encoding | ||
this[_bytes] = null; // Buffer of file contents | ||
this[_text] = null; // string of file contents | ||
// path of file | ||
this[_path] = null; | ||
// cached dir | ||
this[_dir] = null; | ||
// cached filename | ||
this[_filename] = null; | ||
// cached ext | ||
this[_ext] = null; | ||
// stats of file | ||
this.stats = null; | ||
// encoding | ||
this[_enc] = 'utf8'; | ||
// Buffer of file contents | ||
this[_bytes] = null; | ||
// string of file contents | ||
this[_text] = null; | ||
} | ||
get path() { | ||
return this[_path] | ||
return this[_path]; | ||
} | ||
set path(path$$1) { | ||
if (typeof path$$1 !== 'string') throw new TypeError('file.path must be a string') | ||
if (typeof path$$1 !== 'string') { | ||
throw new TypeError('file.path must be a string'); | ||
} | ||
if (this[_path] !== path$$1) { | ||
@@ -41,10 +55,12 @@ this[_path] = path$$1; | ||
if (this[_dir] == null) { | ||
let p = this[_path].lastIndexOf('/'); | ||
const p = this[_path].lastIndexOf('/'); | ||
this[_dir] = p > -1 ? this[_path].slice(0, p) : ''; | ||
} | ||
return this[_dir] | ||
return this[_dir]; | ||
} | ||
set dir(dir) { | ||
if (typeof dir !== 'string') throw new TypeError('file.dir must be a string') | ||
if (typeof dir !== 'string') { | ||
throw new TypeError('file.dir must be a string'); | ||
} | ||
this.path = (dir ? dir + '/' : '') + this.filename; | ||
@@ -55,12 +71,15 @@ } | ||
if (this[_filename] == null) { | ||
let p = this[_path].lastIndexOf('/'); | ||
const p = this[_path].lastIndexOf('/'); | ||
this[_filename] = p > -1 ? this[_path].slice(p + 1) : ''; | ||
} | ||
return this[_filename] | ||
return this[_filename]; | ||
} | ||
set filename(filename) { | ||
if (typeof filename !== 'string') throw new TypeError('file.filename must be a string') | ||
let old = this.filename; | ||
this.path = (old ? this[_path].slice(0, -old.length) : this[_path]) + filename; | ||
if (typeof filename !== 'string') { | ||
throw new TypeError('file.filename must be a string'); | ||
} | ||
const old = this.filename; | ||
this.path = | ||
(old ? this[_path].slice(0, -old.length) : this[_path]) + filename; | ||
} | ||
@@ -70,12 +89,14 @@ | ||
if (this[_ext] == null) { | ||
let p1 = this[_path].lastIndexOf('.'); | ||
let p2 = this[_path].lastIndexOf('/'); | ||
const p1 = this[_path].lastIndexOf('.'); | ||
const p2 = this[_path].lastIndexOf('/'); | ||
this[_ext] = p1 > -1 && p1 > p2 ? this[_path].slice(p1) : ''; | ||
} | ||
return this[_ext] | ||
return this[_ext]; | ||
} | ||
set ext(ext) { | ||
if (typeof ext !== 'string') throw new TypeError('file.ext must be a string') | ||
let old = this.ext; | ||
if (typeof ext !== 'string') { | ||
throw new TypeError('file.ext must be a string'); | ||
} | ||
const old = this.ext; | ||
this.path = (old ? this[_path].slice(0, -old.length) : this[_path]) + ext; | ||
@@ -85,7 +106,9 @@ } | ||
get enc() { | ||
return this[_enc] | ||
return this[_enc]; | ||
} | ||
set enc(enc) { | ||
if (!Buffer.isEncoding(enc)) throw new TypeError('file.enc must be a supported encoding') | ||
if (!Buffer.isEncoding(enc)) { | ||
throw new TypeError('file.enc must be a supported encoding'); | ||
} | ||
this[_enc] = enc; | ||
@@ -97,3 +120,3 @@ } | ||
? (this[_bytes] = Buffer.from(this[_text], this[_enc])) | ||
: this[_bytes] | ||
: this[_bytes]; | ||
} | ||
@@ -103,3 +126,3 @@ | ||
if (bytes != null && !Buffer.isBuffer(bytes)) { | ||
throw new TypeError('file.bytes must be a Buffer or null') | ||
throw new TypeError('file.bytes must be a Buffer or null'); | ||
} | ||
@@ -113,3 +136,3 @@ this[_bytes] = bytes; | ||
? (this[_text] = this[_bytes].toString(this[_enc])) | ||
: this[_text] | ||
: this[_text]; | ||
} | ||
@@ -119,3 +142,3 @@ | ||
if (text != null && typeof text !== 'string') { | ||
throw new TypeError('file.text must be a string or null') | ||
throw new TypeError('file.text must be a string or null'); | ||
} | ||
@@ -131,3 +154,10 @@ this[_text] = text; | ||
let { _watchers, _stats, _timeouts, _queue, _isProcessing, _recurse, _handle, _enqueue } = symbols; | ||
const _watchers = Symbol(); | ||
const _stats = Symbol(); | ||
const _timeouts = Symbol(); | ||
const _queue = Symbol(); | ||
const _isProcessing = Symbol(); | ||
const _recurse = Symbol(); | ||
const _handle = Symbol(); | ||
const _enqueue = Symbol(); | ||
@@ -138,14 +168,22 @@ class Watcher extends EventEmitter { | ||
Object.assign(this, data); | ||
this[_watchers] = new Map(); // paths of all directories -> FSWatcher instances | ||
this[_stats] = new Map(); // paths of all files -> file stats | ||
this[_timeouts] = new Map(); // paths of files with pending debounced events -> setTimeout timer ids | ||
this[_queue] = []; // queue of pending FSWatcher events to handle | ||
this[_isProcessing] = false; // whether some FSWatcher event is currently already in the process of being handled | ||
// paths of all directories -> FSWatcher instances | ||
this[_watchers] = new Map(); | ||
// paths of all files -> file stats | ||
this[_stats] = new Map(); | ||
// paths of files with pending debounced events -> setTimeout timer ids | ||
this[_timeouts] = new Map(); | ||
// queue of pending FSWatcher events to handle | ||
this[_queue] = []; | ||
// whether some FSWatcher event is currently already in the process of being handled | ||
this[_isProcessing] = false; | ||
} | ||
// recurse directroy, get stats, set up FSWatcher instances | ||
// recurse directory, get stats, set up FSWatcher instances | ||
// returns array of { path, stats } | ||
async init() { | ||
await this[_recurse](this.dir); | ||
return [...this[_stats].entries()].map(([path$$1, stats]) => ({ path: path$$1, stats })) | ||
return [...this[_stats].entries()].map(([path$$1, stats]) => ({ | ||
path: path$$1, | ||
stats, | ||
})); | ||
} | ||
@@ -155,10 +193,16 @@ | ||
async [_recurse](full) { | ||
let path$$1 = full.slice(this.dir.length + 1); | ||
let stats = await stat(full); | ||
if (this.filter && !await this.filter({ path: path$$1, stats })) return | ||
const path$$1 = full.slice(this.dir.length + 1); | ||
const stats = await stat(full); | ||
if (this.filter && !await this.filter({ path: path$$1, stats })) { | ||
return; | ||
} | ||
if (stats.isFile()) { | ||
this[_stats].set(path$$1, stats); | ||
} else if (stats.isDirectory()) { | ||
if (this.watch) this[_watchers].set(path$$1, fs.watch(full, this[_handle].bind(this, full))); | ||
await Promise.all((await readdir(full)).map(sub => this[_recurse](full + '/' + sub))); | ||
if (this.watch) { | ||
this[_watchers].set(path$$1, fs.watch(full, this[_handle].bind(this, full))); | ||
} | ||
await Promise.all( | ||
(await readdir(full)).map(sub => this[_recurse](full + '/' + sub)), | ||
); | ||
} | ||
@@ -169,3 +213,3 @@ } | ||
[_handle](dir, event, file) { | ||
let full = dir + '/' + file; | ||
const full = dir + '/' + file; | ||
if (this[_timeouts].has(full)) clearTimeout(this[_timeouts].get(full)); | ||
@@ -184,10 +228,14 @@ this[_timeouts].set( | ||
this[_queue].push(full); | ||
if (this[_isProcessing]) return | ||
if (this[_isProcessing]) { | ||
return; | ||
} | ||
this[_isProcessing] = true; | ||
while (this[_queue].length) { | ||
let full = this[_queue].shift(); | ||
let path$$1 = full.slice(this.dir.length + 1); | ||
const full = this[_queue].shift(); | ||
const path$$1 = full.slice(this.dir.length + 1); | ||
try { | ||
let stats = await stat(full); | ||
if (this.filter && !await this.filter({ path: path$$1, stats })) continue | ||
const stats = await stat(full); | ||
if (this.filter && !await this.filter({ path: path$$1, stats })) { | ||
continue; | ||
} | ||
if (stats.isFile()) { | ||
@@ -200,4 +248,6 @@ // note the new/changed file | ||
await this[_recurse](full); | ||
for (let [newPath, stats] of this[_stats].entries()) { | ||
if (newPath.startsWith(path$$1 + '/')) this.emit('', { event: '+', path: newPath, stats }); | ||
for (const [newPath, stats] of this[_stats].entries()) { | ||
if (newPath.startsWith(path$$1 + '/')) { | ||
this.emit('', { event: '+', path: newPath, stats }); | ||
} | ||
} | ||
@@ -213,3 +263,3 @@ } | ||
// note the deleted directory: stop watching it, and report any files that were in it | ||
for (let old of this[_watchers].keys()) { | ||
for (const old of this[_watchers].keys()) { | ||
if (old === path$$1 || old.startsWith(path$$1 + '/')) { | ||
@@ -220,3 +270,3 @@ this[_watchers].get(old).close(); | ||
} | ||
for (let old of this[_stats].keys()) { | ||
for (const old of this[_stats].keys()) { | ||
if (old.startsWith(path$$1 + '/')) { | ||
@@ -234,10 +284,36 @@ this[_stats].delete(old); | ||
// prettier-ignore | ||
let { _origData, _status, _watchers: _watchers$1, _transform, _generators, _resolver, _active, _waitingFor, _whenFound, _deps, _queue: _queue$1, _isProcessing: _isProcessing$1, _startWave, _endWave, _enqueue: _enqueue$1, _processPhysicalFile, _processFile, _processGenerator, _checkWave, _current, _newProxy, _processDependents, _markFound } = symbols; | ||
const _origData = Symbol(); | ||
const _status = Symbol(); | ||
const _before = Symbol(); | ||
const _during = Symbol(); | ||
const _after = Symbol(); | ||
const _watchers$1 = Symbol(); | ||
const _transform = Symbol(); | ||
const _generators = Symbol(); | ||
const _resolver = Symbol(); | ||
const _onerror = Symbol(); | ||
const _active = Symbol(); | ||
const _waitingFor = Symbol(); | ||
const _whenFound = Symbol(); | ||
const _deps = Symbol(); | ||
const _queue$1 = Symbol(); | ||
const _isProcessing$1 = Symbol(); | ||
const _startWave = Symbol(); | ||
const _endWave = Symbol(); | ||
const _enqueue$1 = Symbol(); | ||
const _processPhysicalFile = Symbol(); | ||
const _processFile = Symbol(); | ||
const _callTransform = Symbol(); | ||
const _processGenerator = Symbol(); | ||
const _checkWave = Symbol(); | ||
const _current = Symbol(); | ||
const _newProxy = Symbol(); | ||
const _processDependents = Symbol(); | ||
const _markFound = Symbol(); | ||
class Defiler extends EventEmitter { | ||
constructor(...dirs) { | ||
let { transform, generators = [], resolver } = dirs.pop(); | ||
class Defiler { | ||
constructor(...args) { | ||
const { transform, generators = [], resolver, onerror } = args.pop(); | ||
if (typeof transform !== 'function') { | ||
throw new TypeError('defiler: transform must be a function') | ||
throw new TypeError('defiler: transform must be a function'); | ||
} | ||
@@ -248,42 +324,87 @@ if ( | ||
) { | ||
throw new TypeError('defiler: generators must be an array of functions') | ||
throw new TypeError('defiler: generators must be an array of functions'); | ||
} | ||
if (typeof resolver !== 'undefined' && typeof resolver !== 'function') { | ||
throw new TypeError('defiler: resolver must be a function') | ||
if (resolver && typeof resolver !== 'function') { | ||
throw new TypeError('defiler: resolver must be a function'); | ||
} | ||
super(); | ||
this.paths = new Set(); // set of original paths for all physical files | ||
this[_origData] = new Map(); // original paths -> original file data for all physical files ({ path, stats, bytes, enc }) | ||
this.files = new Map(); // original paths -> transformed files for all physical and virtual files | ||
this[_status] = null; // null = exec not called; false = exec pending; true = exec finished | ||
this[_watchers$1] = dirs.map( | ||
({ dir, filter, read = true, enc = 'utf8', pre, watch = true, debounce = 10 }) => { | ||
if (typeof dir !== 'string') throw new TypeError('defiler: dir must be a string') | ||
if (typeof filter !== 'undefined' && typeof filter !== 'function') { | ||
throw new TypeError('defiler: filter must be a function') | ||
if (onerror && typeof onerror !== 'function') { | ||
throw new TypeError('defiler: onerror must be a function'); | ||
} | ||
// set of original paths for all physical files | ||
this.paths = new Set(); | ||
// original paths -> original file data for all physical files ({ path, stats, bytes, enc }) | ||
this[_origData] = new Map(); | ||
// original paths -> transformed files for all physical and virtual files | ||
this.files = new Map(); | ||
// _before, _during, or _after exec has been called | ||
this[_status] = _before; | ||
// Watcher instances | ||
this[_watchers$1] = args.map( | ||
({ | ||
dir, | ||
filter, | ||
read = true, | ||
enc = 'utf8', | ||
pre, | ||
watch = true, | ||
debounce = 10, | ||
}) => { | ||
if (typeof dir !== 'string') { | ||
throw new TypeError('defiler: dir must be a string'); | ||
} | ||
if (filter && typeof filter !== 'function') { | ||
throw new TypeError('defiler: filter must be a function'); | ||
} | ||
if (typeof read !== 'boolean' && typeof read !== 'function') { | ||
throw new TypeError('defiler: read must be a boolean or a function') | ||
throw new TypeError('defiler: read must be a boolean or a function'); | ||
} | ||
if (!Buffer.isEncoding(enc) && typeof enc !== 'function') { | ||
throw new TypeError('defiler: enc must be a supported encoding or a function') | ||
throw new TypeError( | ||
'defiler: enc must be a supported encoding or a function', | ||
); | ||
} | ||
if (typeof pre !== 'undefined' && typeof pre !== 'function') { | ||
throw new TypeError('defiler: pre must be a function') | ||
if (pre && typeof pre !== 'function') { | ||
throw new TypeError('defiler: pre must be a function'); | ||
} | ||
if (typeof watch !== 'boolean') throw new TypeError('defiler: watch must be a boolean') | ||
if (typeof debounce !== 'number') throw new TypeError('defiler: debounce must be a number') | ||
return new Watcher({ dir: path.resolve(dir), filter, read, enc, pre, watch, debounce }) | ||
if (typeof watch !== 'boolean') { | ||
throw new TypeError('defiler: watch must be a boolean'); | ||
} | ||
if (typeof debounce !== 'number') { | ||
throw new TypeError('defiler: debounce must be a number'); | ||
} | ||
return new Watcher({ | ||
dir: path.resolve(dir), | ||
filter, | ||
read, | ||
enc, | ||
pre, | ||
watch, | ||
debounce, | ||
}); | ||
}, | ||
); // Watcher instances | ||
this[_transform] = transform; // the transform to run on all files | ||
this[_generators] = new Map(generators.map(generator => [Symbol(), generator])); // unique symbols -> registered generators | ||
this[_resolver] = resolver; // (base, path) => path resolver function, used in defiler.get and defiler.add from transform | ||
this[_active] = new Set(); // original paths of all files currently undergoing transformation and symbols of all generators currently running | ||
this[_waitingFor] = new Map(); // original paths -> number of other files they're currently waiting on to exist | ||
this[_whenFound] = new Map(); // original paths -> { promise, resolve } objects for when awaited files become available | ||
this[_current] = null; // (set via proxy) the current immediate dependent (path or generator symbol), for use in _deps, _waitingFor, and the resolver | ||
this[_deps] = []; // array of [dependent, dependency] pairs, specifying changes to which files should trigger re-processing which other files | ||
this[_queue$1] = []; // queue of pending Watcher events to handle | ||
this[_isProcessing$1] = false; // whether some Watcher event is currently already in the process of being handled | ||
); | ||
// the transform to run on all files | ||
this[_transform] = transform; | ||
// unique symbols -> registered generators | ||
this[_generators] = new Map( | ||
generators.map(generator => [Symbol(), generator]), | ||
); | ||
// (base, path) => path resolver function, used in defiler.get and defiler.add from transform | ||
this[_resolver] = resolver; | ||
// handler to call when errors occur | ||
this[_onerror] = onerror; | ||
// original paths of all files currently undergoing transformation and symbols of all generators currently running | ||
this[_active] = new Set(); | ||
// original paths -> number of other files they're currently waiting on to exist | ||
this[_waitingFor] = new Map(); | ||
// original paths -> { promise, resolve } objects for when awaited files become available | ||
this[_whenFound] = new Map(); | ||
// (set via proxy) the current immediate dependent (path or generator symbol), for use in _deps, _waitingFor, and the resolver | ||
this[_current] = null; | ||
// array of [dependent, dependency] pairs, specifying changes to which files should trigger re-processing which other files | ||
this[_deps] = []; | ||
// queue of pending Watcher events to handle | ||
this[_queue$1] = []; | ||
// whether some Watcher event is currently already in the process of being handled | ||
this[_isProcessing$1] = false; | ||
} | ||
@@ -293,8 +414,10 @@ | ||
async exec() { | ||
if (this[_status] !== null) throw new Error('defiler.exec: cannot call more than once') | ||
this[_status] = false; | ||
if (this[_status] !== _before) { | ||
throw new Error('defiler.exec: cannot call more than once'); | ||
} | ||
this[_status] = _during; | ||
this[_isProcessing$1] = true; | ||
let done = this[_startWave](); | ||
const done = this[_startWave](); | ||
// init the Watcher instances | ||
let files = []; | ||
const files = []; | ||
await Promise.all( | ||
@@ -306,4 +429,6 @@ this[_watchers$1].map(async watcher => { | ||
(await watcher.init()).map(async file => { | ||
let { path: path$$1 } = file; | ||
if (watcher.pre) await watcher.pre(file); | ||
const { path: path$$1 } = file; | ||
if (watcher.pre) { | ||
await watcher.pre(file); | ||
} | ||
this.paths.add(file.path); | ||
@@ -316,10 +441,16 @@ this[_active].add(file.path); | ||
); | ||
for (let symbol of this[_generators].keys()) this[_active].add(symbol); | ||
for (const symbol of this[_generators].keys()) { | ||
this[_active].add(symbol); | ||
} | ||
// process each physical file | ||
for (let [watcher, path$$1, file] of files) this[_processPhysicalFile](watcher, path$$1, file); | ||
for (const [watcher, path$$1, file] of files) { | ||
this[_processPhysicalFile](watcher, path$$1, file); | ||
} | ||
// process each generator | ||
for (let symbol of this[_generators].keys()) this[_processGenerator](symbol); | ||
for (const symbol of this[_generators].keys()) { | ||
this[_processGenerator](symbol); | ||
} | ||
// wait and finish up | ||
await done; | ||
this[_status] = true; | ||
this[_status] = _after; | ||
this[_isProcessing$1] = false; | ||
@@ -331,17 +462,30 @@ this[_enqueue$1](); | ||
async get(path$$1) { | ||
if (Array.isArray(path$$1)) return Promise.all(path$$1.map(path$$1 => this.get(path$$1))) | ||
let { [_current]: current, [_waitingFor]: waitingFor } = this; | ||
if (Array.isArray(path$$1)) { | ||
return Promise.all(path$$1.map(path$$1 => this.get(path$$1))); | ||
} | ||
const { [_current]: current, [_waitingFor]: waitingFor } = this; | ||
path$$1 = this.resolve(path$$1); | ||
if (typeof path$$1 !== 'string') throw new TypeError('defiler.get: path must be a string') | ||
if (current) this[_deps].push([current, path$$1]); | ||
if (!this[_status] && !this.files.has(path$$1)) { | ||
if (current) waitingFor.set(current, (waitingFor.get(current) || 0) + 1); | ||
if (typeof path$$1 !== 'string') { | ||
throw new TypeError('defiler.get: path must be a string'); | ||
} | ||
if (current) { | ||
this[_deps].push([current, path$$1]); | ||
} | ||
if (this[_status] === _during && !this.files.has(path$$1)) { | ||
if (current) { | ||
waitingFor.set(current, (waitingFor.get(current) || 0) + 1); | ||
} | ||
if (!this[_whenFound].has(path$$1)) { | ||
let resolve; | ||
this[_whenFound].set(path$$1, { promise: new Promise(res => (resolve = res)), resolve }); | ||
this[_whenFound].set(path$$1, { | ||
promise: new Promise(res => (resolve = res)), | ||
resolve, | ||
}); | ||
} | ||
await this[_whenFound].get(path$$1).promise; | ||
if (current) waitingFor.set(current, waitingFor.get(current) - 1); | ||
if (current) { | ||
waitingFor.set(current, waitingFor.get(current) - 1); | ||
} | ||
} | ||
return this.files.get(path$$1) | ||
return this.files.get(path$$1); | ||
} | ||
@@ -351,7 +495,11 @@ | ||
add(file) { | ||
if (this[_status] === null) throw new Error('defiler.add: cannot call before calling exec') | ||
if (typeof file !== 'object') throw new TypeError('defiler.add: file must be an object') | ||
if (this[_status] === _before) { | ||
throw new Error('defiler.add: cannot call before calling exec'); | ||
} | ||
if (typeof file !== 'object') { | ||
throw new TypeError('defiler.add: file must be an object'); | ||
} | ||
file.path = this.resolve(file.path); | ||
this[_origData].set(file.path, file); | ||
this[_processFile](file); | ||
this[_processFile](file, 'add'); | ||
} | ||
@@ -363,3 +511,3 @@ | ||
? this[_resolver](this[_current], path$$1) | ||
: path$$1 | ||
: path$$1; | ||
} | ||
@@ -371,3 +519,3 @@ | ||
[_startWave]() { | ||
return new Promise(res => (this[_endWave] = res)) | ||
return new Promise(res => (this[_endWave] = res)); | ||
} | ||
@@ -377,18 +525,25 @@ | ||
async [_enqueue$1](watcher, event) { | ||
if (event) this[_queue$1].push([watcher, event]); | ||
if (this[_isProcessing$1]) return | ||
if (event) { | ||
this[_queue$1].push([watcher, event]); | ||
} | ||
if (this[_isProcessing$1]) { | ||
return; | ||
} | ||
this[_isProcessing$1] = true; | ||
while (this[_queue$1].length) { | ||
let done = this[_startWave](); | ||
let [watcher, { event, path: path$$1, stats }] = this[_queue$1].shift(); | ||
let file = { path: path$$1, stats }; | ||
if (watcher.pre) await watcher.pre(file); | ||
if (event === '+') this[_processPhysicalFile](watcher, path$$1, file); | ||
else if (event === '-') { | ||
let { path: path$$1 } = file; | ||
file = this.files.get(path$$1); | ||
const done = this[_startWave](); | ||
const [watcher, { event, path: path$$1, stats }] = this[_queue$1].shift(); | ||
const file = { path: path$$1, stats }; | ||
if (watcher.pre) { | ||
await watcher.pre(file); | ||
} | ||
if (event === '+') { | ||
this[_processPhysicalFile](watcher, path$$1, file); | ||
} else if (event === '-') { | ||
const { path: path$$1 } = file; | ||
const oldFile = this.files.get(path$$1); | ||
this.paths.delete(path$$1); | ||
this[_origData].delete(path$$1); | ||
this.files.delete(path$$1); | ||
this.emit('deleted', { defiler: this, file }); | ||
await this[_callTransform](oldFile, 'delete'); | ||
this[_processDependents](path$$1); | ||
@@ -403,27 +558,28 @@ } | ||
async [_processPhysicalFile]({ dir, read, enc }, path$$1, file) { | ||
if (typeof read === 'function') read = await read({ path: path$$1, stats: file.stats }); | ||
if (read) file.bytes = await readFile(dir + '/' + path$$1); | ||
if (typeof enc === 'function') enc = await enc({ path: path$$1, stats: file.stats, bytes: file.bytes }); | ||
if (typeof read === 'function') { | ||
read = await read({ path: path$$1, stats: file.stats }); | ||
} | ||
if (read) { | ||
file.bytes = await readFile(dir + '/' + path$$1); | ||
} | ||
if (typeof enc === 'function') { | ||
enc = await enc({ path: path$$1, stats: file.stats, bytes: file.bytes }); | ||
} | ||
file.enc = enc; | ||
this.paths.add(file.path); | ||
this[_origData].set(file.path, file); | ||
this.emit('read', { defiler: this, file }); | ||
await this[_processFile](file); | ||
await this[_processFile](file, 'read'); | ||
} | ||
// transform a file, store it, and process dependents | ||
async [_processFile](data) { | ||
let file = Object.assign(new File(), data); | ||
let { path: path$$1 } = file; | ||
async [_processFile](data, type) { | ||
const file = Object.assign(new File(), data); | ||
const { path: path$$1 } = file; | ||
this[_active].add(path$$1); | ||
let defiler = this[_newProxy](path$$1); | ||
try { | ||
await this[_transform]({ defiler, file }); | ||
} catch (error) { | ||
this.emit('error', { defiler, file, error }); | ||
} | ||
await this[_callTransform](file, type); | ||
this.files.set(path$$1, file); | ||
this.emit('file', { defiler: this, file }); | ||
this[_markFound](path$$1); | ||
if (this[_status]) this[_processDependents](path$$1); | ||
if (this[_status] === _after) { | ||
this[_processDependents](path$$1); | ||
} | ||
this[_active].delete(path$$1); | ||
@@ -433,11 +589,25 @@ this[_checkWave](); | ||
// call the transform on a file with the given changed and deleted flags, and handle errors | ||
async [_callTransform](file, type) { | ||
let defiler = this[_newProxy](file.path); | ||
try { | ||
await this[_transform]({ defiler, file, type }); | ||
} catch (error) { | ||
if (this[_onerror]) { | ||
this[_onerror]({ defiler, file, type, error }); | ||
} | ||
} | ||
} | ||
// run the generator given by the symbol | ||
async [_processGenerator](symbol) { | ||
this[_active].add(symbol); | ||
let generator = this[_generators].get(symbol); | ||
let defiler = this[_newProxy](symbol); | ||
const generator = this[_generators].get(symbol); | ||
const defiler = this[_newProxy](symbol); | ||
try { | ||
await generator({ defiler }); | ||
} catch (error) { | ||
this.emit('error', { defiler, generator, error }); | ||
if (this[_onerror]) { | ||
this[_onerror]({ defiler, generator, error }); | ||
} | ||
} | ||
@@ -450,10 +620,21 @@ this[_active].delete(symbol); | ||
[_processDependents](path$$1) { | ||
let dependents = new Set(); | ||
for (let [dependent, dep] of this[_deps]) if (dep === path$$1) dependents.add(dependent); | ||
this[_deps] = this[_deps].filter(([dependent]) => !dependents.has(dependent)); | ||
if (!dependents.size && !this[_active].size) this[_endWave](); | ||
for (let dependent of dependents) { | ||
if (this[_origData].has(dependent)) this[_processFile](this[_origData].get(dependent)); | ||
else if (this[_generators].has(dependent)) this[_processGenerator](dependent); | ||
const dependents = new Set(); | ||
for (const [dependent, dep] of this[_deps]) { | ||
if (dep === path$$1) { | ||
dependents.add(dependent); | ||
} | ||
} | ||
this[_deps] = this[_deps].filter( | ||
([dependent]) => !dependents.has(dependent), | ||
); | ||
if (!dependents.size && !this[_active].size) { | ||
this[_endWave](); | ||
} | ||
for (const dependent of dependents) { | ||
if (this[_origData].has(dependent)) { | ||
this[_processFile](this[_origData].get(dependent), 'retransform'); | ||
} else if (this[_generators].has(dependent)) { | ||
this[_processGenerator](dependent); | ||
} | ||
} | ||
} | ||
@@ -463,7 +644,15 @@ | ||
[_checkWave]() { | ||
if (!this[_active].size) this[_endWave](); | ||
else if (!this[_status] && [...this[_active]].every(path$$1 => this[_waitingFor].get(path$$1))) { | ||
if (!this[_active].size) { | ||
this[_endWave](); | ||
} else if ( | ||
this[_status] === _during && | ||
[...this[_active]].every(path$$1 => this[_waitingFor].get(path$$1)) | ||
) { | ||
// all pending files are currently waiting for one or more other files to exist | ||
// break deadlock: assume all files that have not appeared yet will never do so | ||
for (let path$$1 of this[_whenFound].keys()) if (!this[_active].has(path$$1)) this[_markFound](path$$1); | ||
for (const path$$1 of this[_whenFound].keys()) { | ||
if (!this[_active].has(path$$1)) { | ||
this[_markFound](path$$1); | ||
} | ||
} | ||
} | ||
@@ -474,3 +663,5 @@ } | ||
[_newProxy](path$$1) { | ||
return new Proxy(this, { get: (_, key) => (key === _current ? path$$1 : this[key]) }) | ||
return new Proxy(this, { | ||
get: (_, key) => (key === _current ? path$$1 : this[key]), | ||
}); | ||
} | ||
@@ -480,3 +671,3 @@ | ||
[_markFound](path$$1) { | ||
if (!this[_status] && this[_whenFound].has(path$$1)) { | ||
if (this[_status] === _during && this[_whenFound].has(path$$1)) { | ||
this[_whenFound].get(path$$1).resolve(); | ||
@@ -483,0 +674,0 @@ this[_whenFound].delete(path$$1); |
@@ -6,24 +6,38 @@ import { readdir, readFile, stat, watch } from 'fs'; | ||
var symbols = new Proxy({}, { get: (_, key) => Symbol(key) }) | ||
const _path = Symbol(); | ||
const _dir = Symbol(); | ||
const _filename = Symbol(); | ||
const _ext = Symbol(); | ||
const _enc = Symbol(); | ||
const _bytes = Symbol(); | ||
const _text = Symbol(); | ||
let { _path, _dir, _filename, _ext, _enc, _bytes, _text } = symbols; | ||
class File { | ||
constructor() { | ||
this[_path] = null; // path of file | ||
this[_dir] = null; // cached dir | ||
this[_filename] = null; // cached filename | ||
this[_ext] = null; // cached ext | ||
this.stats = null; // stats of file | ||
this[_enc] = 'utf8'; // encoding | ||
this[_bytes] = null; // Buffer of file contents | ||
this[_text] = null; // string of file contents | ||
// path of file | ||
this[_path] = null; | ||
// cached dir | ||
this[_dir] = null; | ||
// cached filename | ||
this[_filename] = null; | ||
// cached ext | ||
this[_ext] = null; | ||
// stats of file | ||
this.stats = null; | ||
// encoding | ||
this[_enc] = 'utf8'; | ||
// Buffer of file contents | ||
this[_bytes] = null; | ||
// string of file contents | ||
this[_text] = null; | ||
} | ||
get path() { | ||
return this[_path] | ||
return this[_path]; | ||
} | ||
set path(path) { | ||
if (typeof path !== 'string') throw new TypeError('file.path must be a string') | ||
if (typeof path !== 'string') { | ||
throw new TypeError('file.path must be a string'); | ||
} | ||
if (this[_path] !== path) { | ||
@@ -37,10 +51,12 @@ this[_path] = path; | ||
if (this[_dir] == null) { | ||
let p = this[_path].lastIndexOf('/'); | ||
const p = this[_path].lastIndexOf('/'); | ||
this[_dir] = p > -1 ? this[_path].slice(0, p) : ''; | ||
} | ||
return this[_dir] | ||
return this[_dir]; | ||
} | ||
set dir(dir) { | ||
if (typeof dir !== 'string') throw new TypeError('file.dir must be a string') | ||
if (typeof dir !== 'string') { | ||
throw new TypeError('file.dir must be a string'); | ||
} | ||
this.path = (dir ? dir + '/' : '') + this.filename; | ||
@@ -51,12 +67,15 @@ } | ||
if (this[_filename] == null) { | ||
let p = this[_path].lastIndexOf('/'); | ||
const p = this[_path].lastIndexOf('/'); | ||
this[_filename] = p > -1 ? this[_path].slice(p + 1) : ''; | ||
} | ||
return this[_filename] | ||
return this[_filename]; | ||
} | ||
set filename(filename) { | ||
if (typeof filename !== 'string') throw new TypeError('file.filename must be a string') | ||
let old = this.filename; | ||
this.path = (old ? this[_path].slice(0, -old.length) : this[_path]) + filename; | ||
if (typeof filename !== 'string') { | ||
throw new TypeError('file.filename must be a string'); | ||
} | ||
const old = this.filename; | ||
this.path = | ||
(old ? this[_path].slice(0, -old.length) : this[_path]) + filename; | ||
} | ||
@@ -66,12 +85,14 @@ | ||
if (this[_ext] == null) { | ||
let p1 = this[_path].lastIndexOf('.'); | ||
let p2 = this[_path].lastIndexOf('/'); | ||
const p1 = this[_path].lastIndexOf('.'); | ||
const p2 = this[_path].lastIndexOf('/'); | ||
this[_ext] = p1 > -1 && p1 > p2 ? this[_path].slice(p1) : ''; | ||
} | ||
return this[_ext] | ||
return this[_ext]; | ||
} | ||
set ext(ext) { | ||
if (typeof ext !== 'string') throw new TypeError('file.ext must be a string') | ||
let old = this.ext; | ||
if (typeof ext !== 'string') { | ||
throw new TypeError('file.ext must be a string'); | ||
} | ||
const old = this.ext; | ||
this.path = (old ? this[_path].slice(0, -old.length) : this[_path]) + ext; | ||
@@ -81,7 +102,9 @@ } | ||
get enc() { | ||
return this[_enc] | ||
return this[_enc]; | ||
} | ||
set enc(enc) { | ||
if (!Buffer.isEncoding(enc)) throw new TypeError('file.enc must be a supported encoding') | ||
if (!Buffer.isEncoding(enc)) { | ||
throw new TypeError('file.enc must be a supported encoding'); | ||
} | ||
this[_enc] = enc; | ||
@@ -93,3 +116,3 @@ } | ||
? (this[_bytes] = Buffer.from(this[_text], this[_enc])) | ||
: this[_bytes] | ||
: this[_bytes]; | ||
} | ||
@@ -99,3 +122,3 @@ | ||
if (bytes != null && !Buffer.isBuffer(bytes)) { | ||
throw new TypeError('file.bytes must be a Buffer or null') | ||
throw new TypeError('file.bytes must be a Buffer or null'); | ||
} | ||
@@ -109,3 +132,3 @@ this[_bytes] = bytes; | ||
? (this[_text] = this[_bytes].toString(this[_enc])) | ||
: this[_text] | ||
: this[_text]; | ||
} | ||
@@ -115,3 +138,3 @@ | ||
if (text != null && typeof text !== 'string') { | ||
throw new TypeError('file.text must be a string or null') | ||
throw new TypeError('file.text must be a string or null'); | ||
} | ||
@@ -127,3 +150,10 @@ this[_text] = text; | ||
let { _watchers, _stats, _timeouts, _queue, _isProcessing, _recurse, _handle, _enqueue } = symbols; | ||
const _watchers = Symbol(); | ||
const _stats = Symbol(); | ||
const _timeouts = Symbol(); | ||
const _queue = Symbol(); | ||
const _isProcessing = Symbol(); | ||
const _recurse = Symbol(); | ||
const _handle = Symbol(); | ||
const _enqueue = Symbol(); | ||
@@ -134,14 +164,22 @@ class Watcher extends EventEmitter { | ||
Object.assign(this, data); | ||
this[_watchers] = new Map(); // paths of all directories -> FSWatcher instances | ||
this[_stats] = new Map(); // paths of all files -> file stats | ||
this[_timeouts] = new Map(); // paths of files with pending debounced events -> setTimeout timer ids | ||
this[_queue] = []; // queue of pending FSWatcher events to handle | ||
this[_isProcessing] = false; // whether some FSWatcher event is currently already in the process of being handled | ||
// paths of all directories -> FSWatcher instances | ||
this[_watchers] = new Map(); | ||
// paths of all files -> file stats | ||
this[_stats] = new Map(); | ||
// paths of files with pending debounced events -> setTimeout timer ids | ||
this[_timeouts] = new Map(); | ||
// queue of pending FSWatcher events to handle | ||
this[_queue] = []; | ||
// whether some FSWatcher event is currently already in the process of being handled | ||
this[_isProcessing] = false; | ||
} | ||
// recurse directroy, get stats, set up FSWatcher instances | ||
// recurse directory, get stats, set up FSWatcher instances | ||
// returns array of { path, stats } | ||
async init() { | ||
await this[_recurse](this.dir); | ||
return [...this[_stats].entries()].map(([path, stats]) => ({ path, stats })) | ||
return [...this[_stats].entries()].map(([path, stats]) => ({ | ||
path, | ||
stats, | ||
})); | ||
} | ||
@@ -151,10 +189,16 @@ | ||
async [_recurse](full) { | ||
let path = full.slice(this.dir.length + 1); | ||
let stats = await stat$1(full); | ||
if (this.filter && !await this.filter({ path, stats })) return | ||
const path = full.slice(this.dir.length + 1); | ||
const stats = await stat$1(full); | ||
if (this.filter && !await this.filter({ path, stats })) { | ||
return; | ||
} | ||
if (stats.isFile()) { | ||
this[_stats].set(path, stats); | ||
} else if (stats.isDirectory()) { | ||
if (this.watch) this[_watchers].set(path, watch(full, this[_handle].bind(this, full))); | ||
await Promise.all((await readdir$1(full)).map(sub => this[_recurse](full + '/' + sub))); | ||
if (this.watch) { | ||
this[_watchers].set(path, watch(full, this[_handle].bind(this, full))); | ||
} | ||
await Promise.all( | ||
(await readdir$1(full)).map(sub => this[_recurse](full + '/' + sub)), | ||
); | ||
} | ||
@@ -165,3 +209,3 @@ } | ||
[_handle](dir, event, file) { | ||
let full = dir + '/' + file; | ||
const full = dir + '/' + file; | ||
if (this[_timeouts].has(full)) clearTimeout(this[_timeouts].get(full)); | ||
@@ -180,10 +224,14 @@ this[_timeouts].set( | ||
this[_queue].push(full); | ||
if (this[_isProcessing]) return | ||
if (this[_isProcessing]) { | ||
return; | ||
} | ||
this[_isProcessing] = true; | ||
while (this[_queue].length) { | ||
let full = this[_queue].shift(); | ||
let path = full.slice(this.dir.length + 1); | ||
const full = this[_queue].shift(); | ||
const path = full.slice(this.dir.length + 1); | ||
try { | ||
let stats = await stat$1(full); | ||
if (this.filter && !await this.filter({ path, stats })) continue | ||
const stats = await stat$1(full); | ||
if (this.filter && !await this.filter({ path, stats })) { | ||
continue; | ||
} | ||
if (stats.isFile()) { | ||
@@ -196,4 +244,6 @@ // note the new/changed file | ||
await this[_recurse](full); | ||
for (let [newPath, stats] of this[_stats].entries()) { | ||
if (newPath.startsWith(path + '/')) this.emit('', { event: '+', path: newPath, stats }); | ||
for (const [newPath, stats] of this[_stats].entries()) { | ||
if (newPath.startsWith(path + '/')) { | ||
this.emit('', { event: '+', path: newPath, stats }); | ||
} | ||
} | ||
@@ -209,3 +259,3 @@ } | ||
// note the deleted directory: stop watching it, and report any files that were in it | ||
for (let old of this[_watchers].keys()) { | ||
for (const old of this[_watchers].keys()) { | ||
if (old === path || old.startsWith(path + '/')) { | ||
@@ -216,3 +266,3 @@ this[_watchers].get(old).close(); | ||
} | ||
for (let old of this[_stats].keys()) { | ||
for (const old of this[_stats].keys()) { | ||
if (old.startsWith(path + '/')) { | ||
@@ -230,10 +280,36 @@ this[_stats].delete(old); | ||
// prettier-ignore | ||
let { _origData, _status, _watchers: _watchers$1, _transform, _generators, _resolver, _active, _waitingFor, _whenFound, _deps, _queue: _queue$1, _isProcessing: _isProcessing$1, _startWave, _endWave, _enqueue: _enqueue$1, _processPhysicalFile, _processFile, _processGenerator, _checkWave, _current, _newProxy, _processDependents, _markFound } = symbols; | ||
const _origData = Symbol(); | ||
const _status = Symbol(); | ||
const _before = Symbol(); | ||
const _during = Symbol(); | ||
const _after = Symbol(); | ||
const _watchers$1 = Symbol(); | ||
const _transform = Symbol(); | ||
const _generators = Symbol(); | ||
const _resolver = Symbol(); | ||
const _onerror = Symbol(); | ||
const _active = Symbol(); | ||
const _waitingFor = Symbol(); | ||
const _whenFound = Symbol(); | ||
const _deps = Symbol(); | ||
const _queue$1 = Symbol(); | ||
const _isProcessing$1 = Symbol(); | ||
const _startWave = Symbol(); | ||
const _endWave = Symbol(); | ||
const _enqueue$1 = Symbol(); | ||
const _processPhysicalFile = Symbol(); | ||
const _processFile = Symbol(); | ||
const _callTransform = Symbol(); | ||
const _processGenerator = Symbol(); | ||
const _checkWave = Symbol(); | ||
const _current = Symbol(); | ||
const _newProxy = Symbol(); | ||
const _processDependents = Symbol(); | ||
const _markFound = Symbol(); | ||
class Defiler extends EventEmitter { | ||
constructor(...dirs) { | ||
let { transform, generators = [], resolver } = dirs.pop(); | ||
class Defiler { | ||
constructor(...args) { | ||
const { transform, generators = [], resolver, onerror } = args.pop(); | ||
if (typeof transform !== 'function') { | ||
throw new TypeError('defiler: transform must be a function') | ||
throw new TypeError('defiler: transform must be a function'); | ||
} | ||
@@ -244,42 +320,87 @@ if ( | ||
) { | ||
throw new TypeError('defiler: generators must be an array of functions') | ||
throw new TypeError('defiler: generators must be an array of functions'); | ||
} | ||
if (typeof resolver !== 'undefined' && typeof resolver !== 'function') { | ||
throw new TypeError('defiler: resolver must be a function') | ||
if (resolver && typeof resolver !== 'function') { | ||
throw new TypeError('defiler: resolver must be a function'); | ||
} | ||
super(); | ||
this.paths = new Set(); // set of original paths for all physical files | ||
this[_origData] = new Map(); // original paths -> original file data for all physical files ({ path, stats, bytes, enc }) | ||
this.files = new Map(); // original paths -> transformed files for all physical and virtual files | ||
this[_status] = null; // null = exec not called; false = exec pending; true = exec finished | ||
this[_watchers$1] = dirs.map( | ||
({ dir, filter, read = true, enc = 'utf8', pre, watch: watch$$1 = true, debounce = 10 }) => { | ||
if (typeof dir !== 'string') throw new TypeError('defiler: dir must be a string') | ||
if (typeof filter !== 'undefined' && typeof filter !== 'function') { | ||
throw new TypeError('defiler: filter must be a function') | ||
if (onerror && typeof onerror !== 'function') { | ||
throw new TypeError('defiler: onerror must be a function'); | ||
} | ||
// set of original paths for all physical files | ||
this.paths = new Set(); | ||
// original paths -> original file data for all physical files ({ path, stats, bytes, enc }) | ||
this[_origData] = new Map(); | ||
// original paths -> transformed files for all physical and virtual files | ||
this.files = new Map(); | ||
// _before, _during, or _after exec has been called | ||
this[_status] = _before; | ||
// Watcher instances | ||
this[_watchers$1] = args.map( | ||
({ | ||
dir, | ||
filter, | ||
read = true, | ||
enc = 'utf8', | ||
pre, | ||
watch$$1 = true, | ||
debounce = 10, | ||
}) => { | ||
if (typeof dir !== 'string') { | ||
throw new TypeError('defiler: dir must be a string'); | ||
} | ||
if (filter && typeof filter !== 'function') { | ||
throw new TypeError('defiler: filter must be a function'); | ||
} | ||
if (typeof read !== 'boolean' && typeof read !== 'function') { | ||
throw new TypeError('defiler: read must be a boolean or a function') | ||
throw new TypeError('defiler: read must be a boolean or a function'); | ||
} | ||
if (!Buffer.isEncoding(enc) && typeof enc !== 'function') { | ||
throw new TypeError('defiler: enc must be a supported encoding or a function') | ||
throw new TypeError( | ||
'defiler: enc must be a supported encoding or a function', | ||
); | ||
} | ||
if (typeof pre !== 'undefined' && typeof pre !== 'function') { | ||
throw new TypeError('defiler: pre must be a function') | ||
if (pre && typeof pre !== 'function') { | ||
throw new TypeError('defiler: pre must be a function'); | ||
} | ||
if (typeof watch$$1 !== 'boolean') throw new TypeError('defiler: watch must be a boolean') | ||
if (typeof debounce !== 'number') throw new TypeError('defiler: debounce must be a number') | ||
return new Watcher({ dir: resolve(dir), filter, read, enc, pre, watch: watch$$1, debounce }) | ||
if (typeof watch$$1 !== 'boolean') { | ||
throw new TypeError('defiler: watch must be a boolean'); | ||
} | ||
if (typeof debounce !== 'number') { | ||
throw new TypeError('defiler: debounce must be a number'); | ||
} | ||
return new Watcher({ | ||
dir: resolve(dir), | ||
filter, | ||
read, | ||
enc, | ||
pre, | ||
watch: watch$$1, | ||
debounce, | ||
}); | ||
}, | ||
); // Watcher instances | ||
this[_transform] = transform; // the transform to run on all files | ||
this[_generators] = new Map(generators.map(generator => [Symbol(), generator])); // unique symbols -> registered generators | ||
this[_resolver] = resolver; // (base, path) => path resolver function, used in defiler.get and defiler.add from transform | ||
this[_active] = new Set(); // original paths of all files currently undergoing transformation and symbols of all generators currently running | ||
this[_waitingFor] = new Map(); // original paths -> number of other files they're currently waiting on to exist | ||
this[_whenFound] = new Map(); // original paths -> { promise, resolve } objects for when awaited files become available | ||
this[_current] = null; // (set via proxy) the current immediate dependent (path or generator symbol), for use in _deps, _waitingFor, and the resolver | ||
this[_deps] = []; // array of [dependent, dependency] pairs, specifying changes to which files should trigger re-processing which other files | ||
this[_queue$1] = []; // queue of pending Watcher events to handle | ||
this[_isProcessing$1] = false; // whether some Watcher event is currently already in the process of being handled | ||
); | ||
// the transform to run on all files | ||
this[_transform] = transform; | ||
// unique symbols -> registered generators | ||
this[_generators] = new Map( | ||
generators.map(generator => [Symbol(), generator]), | ||
); | ||
// (base, path) => path resolver function, used in defiler.get and defiler.add from transform | ||
this[_resolver] = resolver; | ||
// handler to call when errors occur | ||
this[_onerror] = onerror; | ||
// original paths of all files currently undergoing transformation and symbols of all generators currently running | ||
this[_active] = new Set(); | ||
// original paths -> number of other files they're currently waiting on to exist | ||
this[_waitingFor] = new Map(); | ||
// original paths -> { promise, resolve } objects for when awaited files become available | ||
this[_whenFound] = new Map(); | ||
// (set via proxy) the current immediate dependent (path or generator symbol), for use in _deps, _waitingFor, and the resolver | ||
this[_current] = null; | ||
// array of [dependent, dependency] pairs, specifying changes to which files should trigger re-processing which other files | ||
this[_deps] = []; | ||
// queue of pending Watcher events to handle | ||
this[_queue$1] = []; | ||
// whether some Watcher event is currently already in the process of being handled | ||
this[_isProcessing$1] = false; | ||
} | ||
@@ -289,8 +410,10 @@ | ||
async exec() { | ||
if (this[_status] !== null) throw new Error('defiler.exec: cannot call more than once') | ||
this[_status] = false; | ||
if (this[_status] !== _before) { | ||
throw new Error('defiler.exec: cannot call more than once'); | ||
} | ||
this[_status] = _during; | ||
this[_isProcessing$1] = true; | ||
let done = this[_startWave](); | ||
const done = this[_startWave](); | ||
// init the Watcher instances | ||
let files = []; | ||
const files = []; | ||
await Promise.all( | ||
@@ -302,4 +425,6 @@ this[_watchers$1].map(async watcher => { | ||
(await watcher.init()).map(async file => { | ||
let { path } = file; | ||
if (watcher.pre) await watcher.pre(file); | ||
const { path } = file; | ||
if (watcher.pre) { | ||
await watcher.pre(file); | ||
} | ||
this.paths.add(file.path); | ||
@@ -312,10 +437,16 @@ this[_active].add(file.path); | ||
); | ||
for (let symbol of this[_generators].keys()) this[_active].add(symbol); | ||
for (const symbol of this[_generators].keys()) { | ||
this[_active].add(symbol); | ||
} | ||
// process each physical file | ||
for (let [watcher, path, file] of files) this[_processPhysicalFile](watcher, path, file); | ||
for (const [watcher, path, file] of files) { | ||
this[_processPhysicalFile](watcher, path, file); | ||
} | ||
// process each generator | ||
for (let symbol of this[_generators].keys()) this[_processGenerator](symbol); | ||
for (const symbol of this[_generators].keys()) { | ||
this[_processGenerator](symbol); | ||
} | ||
// wait and finish up | ||
await done; | ||
this[_status] = true; | ||
this[_status] = _after; | ||
this[_isProcessing$1] = false; | ||
@@ -327,17 +458,30 @@ this[_enqueue$1](); | ||
async get(path) { | ||
if (Array.isArray(path)) return Promise.all(path.map(path => this.get(path))) | ||
let { [_current]: current, [_waitingFor]: waitingFor } = this; | ||
if (Array.isArray(path)) { | ||
return Promise.all(path.map(path => this.get(path))); | ||
} | ||
const { [_current]: current, [_waitingFor]: waitingFor } = this; | ||
path = this.resolve(path); | ||
if (typeof path !== 'string') throw new TypeError('defiler.get: path must be a string') | ||
if (current) this[_deps].push([current, path]); | ||
if (!this[_status] && !this.files.has(path)) { | ||
if (current) waitingFor.set(current, (waitingFor.get(current) || 0) + 1); | ||
if (typeof path !== 'string') { | ||
throw new TypeError('defiler.get: path must be a string'); | ||
} | ||
if (current) { | ||
this[_deps].push([current, path]); | ||
} | ||
if (this[_status] === _during && !this.files.has(path)) { | ||
if (current) { | ||
waitingFor.set(current, (waitingFor.get(current) || 0) + 1); | ||
} | ||
if (!this[_whenFound].has(path)) { | ||
let resolve$$1; | ||
this[_whenFound].set(path, { promise: new Promise(res => (resolve$$1 = res)), resolve: resolve$$1 }); | ||
this[_whenFound].set(path, { | ||
promise: new Promise(res => (resolve$$1 = res)), | ||
resolve: resolve$$1, | ||
}); | ||
} | ||
await this[_whenFound].get(path).promise; | ||
if (current) waitingFor.set(current, waitingFor.get(current) - 1); | ||
if (current) { | ||
waitingFor.set(current, waitingFor.get(current) - 1); | ||
} | ||
} | ||
return this.files.get(path) | ||
return this.files.get(path); | ||
} | ||
@@ -347,7 +491,11 @@ | ||
add(file) { | ||
if (this[_status] === null) throw new Error('defiler.add: cannot call before calling exec') | ||
if (typeof file !== 'object') throw new TypeError('defiler.add: file must be an object') | ||
if (this[_status] === _before) { | ||
throw new Error('defiler.add: cannot call before calling exec'); | ||
} | ||
if (typeof file !== 'object') { | ||
throw new TypeError('defiler.add: file must be an object'); | ||
} | ||
file.path = this.resolve(file.path); | ||
this[_origData].set(file.path, file); | ||
this[_processFile](file); | ||
this[_processFile](file, 'add'); | ||
} | ||
@@ -359,3 +507,3 @@ | ||
? this[_resolver](this[_current], path) | ||
: path | ||
: path; | ||
} | ||
@@ -367,3 +515,3 @@ | ||
[_startWave]() { | ||
return new Promise(res => (this[_endWave] = res)) | ||
return new Promise(res => (this[_endWave] = res)); | ||
} | ||
@@ -373,18 +521,25 @@ | ||
async [_enqueue$1](watcher, event) { | ||
if (event) this[_queue$1].push([watcher, event]); | ||
if (this[_isProcessing$1]) return | ||
if (event) { | ||
this[_queue$1].push([watcher, event]); | ||
} | ||
if (this[_isProcessing$1]) { | ||
return; | ||
} | ||
this[_isProcessing$1] = true; | ||
while (this[_queue$1].length) { | ||
let done = this[_startWave](); | ||
let [watcher, { event, path, stats }] = this[_queue$1].shift(); | ||
let file = { path, stats }; | ||
if (watcher.pre) await watcher.pre(file); | ||
if (event === '+') this[_processPhysicalFile](watcher, path, file); | ||
else if (event === '-') { | ||
let { path } = file; | ||
file = this.files.get(path); | ||
const done = this[_startWave](); | ||
const [watcher, { event, path, stats }] = this[_queue$1].shift(); | ||
const file = { path, stats }; | ||
if (watcher.pre) { | ||
await watcher.pre(file); | ||
} | ||
if (event === '+') { | ||
this[_processPhysicalFile](watcher, path, file); | ||
} else if (event === '-') { | ||
const { path } = file; | ||
const oldFile = this.files.get(path); | ||
this.paths.delete(path); | ||
this[_origData].delete(path); | ||
this.files.delete(path); | ||
this.emit('deleted', { defiler: this, file }); | ||
await this[_callTransform](oldFile, 'delete'); | ||
this[_processDependents](path); | ||
@@ -399,27 +554,28 @@ } | ||
async [_processPhysicalFile]({ dir, read, enc }, path, file) { | ||
if (typeof read === 'function') read = await read({ path, stats: file.stats }); | ||
if (read) file.bytes = await readFile$1(dir + '/' + path); | ||
if (typeof enc === 'function') enc = await enc({ path, stats: file.stats, bytes: file.bytes }); | ||
if (typeof read === 'function') { | ||
read = await read({ path, stats: file.stats }); | ||
} | ||
if (read) { | ||
file.bytes = await readFile$1(dir + '/' + path); | ||
} | ||
if (typeof enc === 'function') { | ||
enc = await enc({ path, stats: file.stats, bytes: file.bytes }); | ||
} | ||
file.enc = enc; | ||
this.paths.add(file.path); | ||
this[_origData].set(file.path, file); | ||
this.emit('read', { defiler: this, file }); | ||
await this[_processFile](file); | ||
await this[_processFile](file, 'read'); | ||
} | ||
// transform a file, store it, and process dependents | ||
async [_processFile](data) { | ||
let file = Object.assign(new File(), data); | ||
let { path } = file; | ||
async [_processFile](data, type) { | ||
const file = Object.assign(new File(), data); | ||
const { path } = file; | ||
this[_active].add(path); | ||
let defiler = this[_newProxy](path); | ||
try { | ||
await this[_transform]({ defiler, file }); | ||
} catch (error) { | ||
this.emit('error', { defiler, file, error }); | ||
} | ||
await this[_callTransform](file, type); | ||
this.files.set(path, file); | ||
this.emit('file', { defiler: this, file }); | ||
this[_markFound](path); | ||
if (this[_status]) this[_processDependents](path); | ||
if (this[_status] === _after) { | ||
this[_processDependents](path); | ||
} | ||
this[_active].delete(path); | ||
@@ -429,11 +585,25 @@ this[_checkWave](); | ||
// call the transform on a file with the given changed and deleted flags, and handle errors | ||
async [_callTransform](file, type) { | ||
let defiler = this[_newProxy](file.path); | ||
try { | ||
await this[_transform]({ defiler, file, type }); | ||
} catch (error) { | ||
if (this[_onerror]) { | ||
this[_onerror]({ defiler, file, type, error }); | ||
} | ||
} | ||
} | ||
// run the generator given by the symbol | ||
async [_processGenerator](symbol) { | ||
this[_active].add(symbol); | ||
let generator = this[_generators].get(symbol); | ||
let defiler = this[_newProxy](symbol); | ||
const generator = this[_generators].get(symbol); | ||
const defiler = this[_newProxy](symbol); | ||
try { | ||
await generator({ defiler }); | ||
} catch (error) { | ||
this.emit('error', { defiler, generator, error }); | ||
if (this[_onerror]) { | ||
this[_onerror]({ defiler, generator, error }); | ||
} | ||
} | ||
@@ -446,10 +616,21 @@ this[_active].delete(symbol); | ||
[_processDependents](path) { | ||
let dependents = new Set(); | ||
for (let [dependent, dep] of this[_deps]) if (dep === path) dependents.add(dependent); | ||
this[_deps] = this[_deps].filter(([dependent]) => !dependents.has(dependent)); | ||
if (!dependents.size && !this[_active].size) this[_endWave](); | ||
for (let dependent of dependents) { | ||
if (this[_origData].has(dependent)) this[_processFile](this[_origData].get(dependent)); | ||
else if (this[_generators].has(dependent)) this[_processGenerator](dependent); | ||
const dependents = new Set(); | ||
for (const [dependent, dep] of this[_deps]) { | ||
if (dep === path) { | ||
dependents.add(dependent); | ||
} | ||
} | ||
this[_deps] = this[_deps].filter( | ||
([dependent]) => !dependents.has(dependent), | ||
); | ||
if (!dependents.size && !this[_active].size) { | ||
this[_endWave](); | ||
} | ||
for (const dependent of dependents) { | ||
if (this[_origData].has(dependent)) { | ||
this[_processFile](this[_origData].get(dependent), 'retransform'); | ||
} else if (this[_generators].has(dependent)) { | ||
this[_processGenerator](dependent); | ||
} | ||
} | ||
} | ||
@@ -459,7 +640,15 @@ | ||
[_checkWave]() { | ||
if (!this[_active].size) this[_endWave](); | ||
else if (!this[_status] && [...this[_active]].every(path => this[_waitingFor].get(path))) { | ||
if (!this[_active].size) { | ||
this[_endWave](); | ||
} else if ( | ||
this[_status] === _during && | ||
[...this[_active]].every(path => this[_waitingFor].get(path)) | ||
) { | ||
// all pending files are currently waiting for one or more other files to exist | ||
// break deadlock: assume all files that have not appeared yet will never do so | ||
for (let path of this[_whenFound].keys()) if (!this[_active].has(path)) this[_markFound](path); | ||
for (const path of this[_whenFound].keys()) { | ||
if (!this[_active].has(path)) { | ||
this[_markFound](path); | ||
} | ||
} | ||
} | ||
@@ -470,3 +659,5 @@ } | ||
[_newProxy](path) { | ||
return new Proxy(this, { get: (_, key) => (key === _current ? path : this[key]) }) | ||
return new Proxy(this, { | ||
get: (_, key) => (key === _current ? path : this[key]), | ||
}); | ||
} | ||
@@ -476,3 +667,3 @@ | ||
[_markFound](path) { | ||
if (!this[_status] && this[_whenFound].has(path)) { | ||
if (this[_status] === _during && this[_whenFound].has(path)) { | ||
this[_whenFound].get(path).resolve(); | ||
@@ -479,0 +670,0 @@ this[_whenFound].delete(path); |
{ | ||
"name": "defiler", | ||
"version": "0.15.3", | ||
"version": "0.16.0", | ||
"description": "A small, strange building block", | ||
"keywords": ["build", "framework", "async", "watch"], | ||
"keywords": [ | ||
"build", | ||
"framework", | ||
"async", | ||
"watch" | ||
], | ||
"main": "./dist/index.cjs.js", | ||
"module": "./dist/index.es.js", | ||
"files": ["dist"], | ||
"files": [ | ||
"dist" | ||
], | ||
"engines": { | ||
@@ -21,3 +28,9 @@ "node": ">=8" | ||
}, | ||
"homepage": "https://cndtr.io/defiler/" | ||
"homepage": "https://cndtr.io/defiler/", | ||
"devDependencies": { | ||
"rollup": "*" | ||
}, | ||
"scripts": { | ||
"build": "rollup -c" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
126883
1207
1
1