fs-adapters
This package provides minimal JavaScript / TypeScript interfaces for file system abstraction. It tries to include enough to be comfortable for users, without being a burden on implementers.
The main use case is test instrumentation via dependency injection. Production code could use the directory adapter for persistent storage, while unit tests might use the memory adapter instead.
Still, at its heart this is just an interface, and can be implemented to fit any number of backends. Creating custom adapter types might enable you to swap cloud with local storage on the fly, for example.
Install
npm i fs-adapters
Specification
async init()
Initialize the adapter. This would create the underlying directory, for example, or connect to the storage API, etc.
- Returns:
<Promise>
A Promise for when initialization is done.
async listFiles()
Obtain a list of file names accessible through the adapter.
- Returns:
<Promise<string[]>>
A Promise resolving to a file name array.
async exists(fileName)
Checks whether the given file name exists.
-
fileName
<string>
: The name of the file to check. - Returns:
<Promise<boolean>>
A promise resolving to whether or not this file exists.
async rename(fileName, newFileName)
Rename a file.
-
fileName
<string>
: The old file name. -
newFileName
<string>
: The new file name. - Returns:
<Promise>
A Promise that resolves when done, or rejects on error.
async delete(fileName)
Delete a file.
-
fileName
<string>
The name of the file to delete. - Returns:
<Promise>
A Promise that resolves when done, or rejects on error.
createReadStream(fileName)
Create a read-stream for the given file name. This should be preferred over
read()
when the file is potentially large or does not need to be in memory all
at once.
-
fileName
<string>
The name of the file to read. - Returns:
<stream.Readable>
A readable stream for the file.
createWriteStream(fileName)
Create a write-stream for the given file name.
-
fileName
<string>
The name of the file to write. - Returns:
<stream.Readable>
A writable stream for the file.
async read(fileName[, options])
Read the file whole, resolving to its contents as a Buffer. If an encoding is specified, this will convert the buffer to a string and resolve to that.
-
fileName
<string>
The name of the file to read. -
options
<object|string>
-
encoding
<string|null>
: encoding to use for string conversion. Default:null
-
- Returns:
<Promise<Buffer|string>>
A Promise that resolves to the file contents, or rejects on error.
If options is a string, it is treated as the encoding.
async write(fileName, data[, options])
Write to the given file name in one go.
-
fileName
<string>
The name of the file to read. -
data
<Buffer|string>
The file contents to write. -
options
<object|string>
-
encoding
<string>
: encoding to use if data is a string. Default:'utf8'
-
- Returns:
<Promise>
A Promise that resolves when done, or rejects on error.
If options is a string, it is treated as the encoding.
Implementations
MemoryAdapter
This adapter implements the above specification by storing all data and metadata in the process memory. It is therefore not dependent on the actual file system and highly suitable for test scenarios, for example.
Constructors
new MemoryAdapter()
new MemoryAdapter(initialFiles)
-
initialFiles
<object | Map | Array[]>
(optional) A mapping from file names to their contents (instances ofBuffer
or string data).
Usage Example
const { MemoryAdapter } = require('fs-adapters')
const adapter = new MemoryAdapter({
'foo.txt': Buffer.from('hello world', 'utf8'),
'empty.bin': Buffer.alloc(0)
})
// alternatively:
new MemoryAdapter(new Map([
['foo.txt', Buffer.from('hello world', 'utf8')],
['empty.bin', Buffer.alloc(0)]
]))
// or even:
new MemoryAdapter([
['foo.txt', 'hello world'],
['empty.bin', Buffer.alloc(0)]
])
adapter.init().then(() => {
adapter.listFiles().then((files) => {
console.log(files) // ['foo.txt', 'empty.bin']
})
})
DirectoryAdapter
This adapter reads from and writes to a specific base directory. All file names
are interpreted as relative to the base directory, and navigating outside that
directory (e.g. via ..
) or accessing the directory itself (e.g. via .
)
results in an error.
Note that creating or accessing sub-directories is not (yet) supported. This might be added in the future, please feel free to make a Pull Request!
Constructors
new DirectoryAdapter(directory)
-
directory
<string>
The absolute path to the base directory.
Usage Example
const { DirectoryAdapter } = require('fs-adapters')
const path = require('path')
const directory = path.join(__dirname, 'data')
const adapter = new DirectoryAdapter(directory)
adapter.init().then(() => {
// do something with adapter
})
Extending
This is a loose specification. As such, creating a new adapter is as simple as writing a class with the specified methods. If you only need your adapter to work with plain JavaScript (not TypeScript) and want to skip practical hints from your IDE, this will be enough.
Yet, to facilitate implementation and ensure perfect interoperability between
adapters, you should extend the common superclass Adapter
. This has benefits
even in plain JavaScript but is basically mandatory in TypeScript.
All implementations supplied with this package (MemoryAdapter,
DirectoryAdapter) do this.
Example:
import { Adapter } from 'fs-adapters'
class CustomAdapter extends FSAdapter {
// implement methods here
}
The Adapter
class provides default implementations for the following methods:
-
init()
: does nothing by default -
read(...)
: creates a read-stream and wraps it into a promise -
write(...)
: creates a write-stream and wraps it into a promise
It is recommended that implementers override these default implementations when they can provide something more efficient.