@leelaing/file-library
v0.2.0
Published
Reusable Node.js file scanning, filtering, metadata, duplicate detection, sorting, and scan-cache utilities.
Maintainers
Readme
@leelaing/file-library
Reusable Node.js file scanning, filtering, metadata, duplicate detection, deterministic sorting, content hashing, and versioned scan-cache utilities.
Install
npm install @leelaing/file-libraryNode.js 20 or newer is required. The package has no runtime dependencies and exposes identical ESM and CommonJS APIs.
Scan files
import { scanFiles } from '@leelaing/file-library';
const library = await scanFiles('/home/lee/Music', {
extensions: ['mp3', 'wav', 'flac'],
excludedDirectoryNames: ['node_modules', '.git'],
onProgress(progress) {
console.log(progress.filesMatched);
},
});
console.log(library.files, library.errors);Scans are recursive by default, do not follow directory symlinks by default, and continue past unreadable entries while returning structured errors. Pass recursive: false, an AbortSignal, include/exclude globs, or an excludePath predicate when needed. Metadata scans do not read file contents.
Filter and sort metadata
import { filterFiles, sortFiles } from '@leelaing/file-library';
const recentLargeFiles = filterFiles(library.files, {
extensions: ['mp3'],
minSize: 1_000_000,
modifiedAfter: new Date('2026-01-01'),
});
const newestFirst = sortFiles(recentLargeFiles, {
by: 'modified',
direction: 'desc',
});Every sort uses stable path tie breakers so results are deterministic.
Find numbered duplicates
import { findDuplicateGroups } from '@leelaing/file-library';
const groups = findDuplicateGroups(library.files);
for (const group of groups) {
console.log(group.kept, group.duplicates, group.reason);
}The default strategy treats Song_1.mp3 and Song_2.mp3 as duplicates only when Song.mp3 exists in the same parent directory. Identical names in different directories are never combined. Use mode: 'exact-name' or provide key(file) for a custom strategy.
Optional content hashing
import { hashFileContent } from '@leelaing/file-library';
const digest = await hashFileContent('/path/to/file.flac');Hashing is explicit and streaming. Normal scans never hash or read file contents.
Versioned scan cache
import { scanFilesCached } from '@leelaing/file-library';
const library = await scanFilesCached('/home/lee/Music', {
cacheFile: '/home/lee/.local/share/my-app/library-cache.json',
extensions: ['mp3'],
maxAgeMs: 60 * 60 * 1000,
});The caller owns the cache location. Cache identity includes the normalized root and scan options. Saves use a temporary sibling plus atomic rename; missing, corrupt, incompatible, or mismatched caches are clean cache misses.
Development
npm install
npm start
npm run checkLicense
MIT
