@thingts/fs-path
v3.0.0
Published
Type-safe, ergonomic package for working with paths and files in Node.js
Maintainers
Readme
@thingts/fs-path
Type-safe, ergonomic package for working with paths and files in Node.js
Why?
Instead of juggling raw strings with node:path and node:fs, this package makes file paths first-class citizens in your code -- with type-safe manipulation and convenient filesystem methods.
Features
- Immutable, chainable path objects with type-safe operations
- Path normalization and resolution on construction
- Easy access to path parts (filename, stem, extension, parent, etc)
- Path transformations (replace stem/extension/parent, transform filename, etc)
- Path navigation (join, resolve, relativeTo, descendsFrom, etc)
- Async filesystem operations (exists, isFile, isDirectory, stat, read, write, makeDirectory, readDirectory, glob, etc)
- Temporary directory & file management
Together, these features give you a safer, more expressive way to work with paths, files, and directories in Node.js
Under the hood, this package uses:
@thingts/pathfor path manipulationnode:fs/promisesfor async filesystem operationstinyglobbyfor globbing
Documentation
- See API reference at https://thingts.github.io/fs-path/.
- See CHANGELOG.md for release history and breaking changes.
Overview
@thingts/fs-path provides a set of classes to represent and manipulate filesystem paths. All classes are immutable; any path manipulation operation returns a new instance.
This package defines:
FsPath- An absolute filesystem path object, with path manipulation and filesystem operations.FsPathextendsAbsolutePathfrom@thingts/pathwhich provides path manipulation, and adds the filesystem operations.
For convenience this package also re-exports these two classes from
@thingts/path:
RelativePath- Relative path object with path manipulation.Filename- Filename object with file part manipulation.
The classes work together to maintain type safety and ergonomics. For
example, the .relativeTo() method of FsPath returns a RelativePath
-- which would need to be joined to a base FsPath in order to
perform filesystem operations.
⚠️ Currently only POSIX-style paths are supported (e.g.
/foo/bar).
🔧 This package supports most commonly used
node:fsfeatures & options. But not all; contributions to expand functionality are welcome.
Installation
npm install @thingts/fs-pathUsage examples
This is a quick overview of the available operations. It does not all all possible options; For complete docs see the API Reference.
import { FsPath } from '@thingts/fs-path'Normalize & resolve on construction
const a = new FsPath('/foo/../bar/file.txt')
a.equals('/bar/file.txt') // → true
const b = new FsPath('relative/to/cwd.txt')
b.equals(FsPath.cwd().join('relative/to/cwd.txt')) // → truePath parts & transforms
const a = new FsPath('/bar/file.txt')
a.toString() // → '/bar/file.txt'
a.filename // → Filename('file.txt')
a.filename.toString() // → 'file.txt'
a.stem // → 'file'
a.extension // → '.txt'
a.parent // → FsPath('/bar')
a.segments // → ['bar', 'file.txt']
const b = a.replaceStem('report') // → FsPath('/bar/report.txt')
const c = b.replaceExtension('.md') // → FsPath('/bar/report.md')
const d = c.replaceParent('/other') // → FsPath('/other/report.md')
const e = d.replaceFilename('REPORT.MD') // → FsPath('/other/REPORT.MD')
const f = d.transformFilename(f => String(f).toUpperCase()) // → FsPath('/other/REPORT.MD')Navigation
const base = new FsPath('/projects/demo')
base.join('src/index.ts') // → FsPath('/projects/demo/src/index.ts')
base.join('/src/index.ts') // → FsPath('/projects/demo/src/index.ts')
base.resolve('src/index.ts') // → FsPath('/projects/demo/src/index.ts')
base.resolve('/src/index.ts') // → FsPath('/src/index.ts')
base.descendsFrom(new FsPath('/projects')) // → true
base.parent.equals('/projects') // → true
const rel = base.join('src/main.ts').relativeTo(base) // → RelativePath('src/main.ts')Filesystem operations
const dir = new FsPath('/projects/demo')
const file = dir.join('logs/app.log')
// --- Writing and reading ---
await file.write('start\n', { makeParents: true })
await file.write('listening\n', { append: true })
await file.read() // → 'start\nlistening\n'
await file.readBytes({ size: 16 }) // → Buffer containing first 16 bytes
await file.readStream() // → Readable stream of file contents
// --- File info ---
await file.exists() // → true
await file.isFile() // → true
await file.isDirectory() // → false
await file.parent.isDirectory() // → true
await file.access('read') // → true
await file.access(['read', 'write']) // → true
await file.stat() // → fs.Stats object
await file.realPath() // → FsPath of the real path (resolving symlinks)
// --- Directory operations...
await dir.join('sub').makeDirectory()
const files = await dir.readDirectory() // → [FsPath, ...]
const txts = await dir.glob('**/*.log') // glob within a directory → [FsPath, ...]
// --- File and directory manipulation ---
const backupDir = new FsPath('/projects/backup')
const backupFile = backupDir.join('app.log')
await file.copyTo(backupFile, { makeParents: true }) // copy file
await file.copyTo(backupDir, { intoDir: true }) // copy into a directory
await dir.copyTo(backupDir, { recursive: true }) // copy a directory recursively
await file.copyTo(backupFile, { overwrite: false }) // throws if destination exists
await file.moveTo(backupFile, { overwrite: true }) // move file, overwriting if exists
await file.moveTo(backupDir, { intoDir: true }) // move into a directory
await file.remove() // remove file
await file.setPermissions({ mode: 0o600 }) // set file permissions mode
await file.setPermissions({ user: ['read', 'write'] }) // set permissions symbolically
await file.touch() // update file timestamps or create if not existsTemporary (a.k.a. disposable) files and directories
// --- Explicit resource management ---
{
using dir = await FsPath.makeTempDirectory() // returns disposable directory
using file = new FsPath('/projects/tempfile.txt').disposable() // register for disposal
dir.exists() // → true
file.write('data') // create file
...
// dir and file are removed when they go out of scope
}
// --- Removed eventually, on gc or exit ---
const dir = await FsPath.makeTempDirectory()
const file = new FsPath('/projects/tempfile.txt').disposable()Related
- @thingts/path – Path manipulation only (no fs), pure javascript, no node.js dependences (browser-safe)
Contributing
Contributions are welcome!
As usual: fork the repo, create a feature branch, and open a pull request, with tests and docs for any new functionality. Thanks!
