key-file-storage
Version:
Simple key-value storage directly on file system, maps each key to a separate file.
233 lines (173 loc) • 7.63 kB
Markdown
# key-file-storage
#### Simple key-value storage (a persistent data structure) directly on file system, maps each key to a separate file.
+ Simple *key-value* storage model
+ Very easy to learn and use
+ Both *Synchronous* and *Asynchronous* APIs
+ One *JSON* containing file per each key
+ Built-in configurable cache
+ Both *Promise* and *Callback* support
```ts
const store = require("key-file-storage")('my/storage/path')
// Write something to file 'my/storage/path/myfile'
store.myfile = { x: 123 }
// Read contents of file 'my/storage/path/myfile'
const x = store.myfile.x
// Delete file 'my/storage/path/myfile'
delete store.myfile
```
A nice alternative for any of these libraries: [node-persist](https://www.npmjs.com/package/node-persist), [configstore](https://www.npmjs.com/package/configstore), [flat-cache](https://www.npmjs.com/package/flat-cache), [conf](https://www.npmjs.com/package/conf), [simple-store](https://www.npmjs.com/package/simple-store), and more...
## Installation
Installing package on Node.js:
```sh
$ npm install key-file-storage
```
## Initialization
Initializing a key-file storage:
```ts
// ES Modules import style:
import kfs from 'key-file-storage'
// CommonJS import style:
const kfs = require("key-file-storage")
const store = kfs('/storage/directory/path', caching)
```
The value of `caching` can be
1. **`true`** (_By default, if not specified_) : Unlimited cache, anything will be cached on memory, good for small data volumes.
2. **`false`** : No cache, read the files from disk every time, good when other applications can modify the files' contents arbitrarily.
3. **`n`** (_An integer number_) : Limited cache, only the **`n`** latest referred key-values will be cached, good for large data volumes where only a fraction of data is being used frequently .
## Usage
### Synchronous API
As simple as native javascript objects:
```ts
store['key'] = value // Writes file
```
```ts
store['key'] // Reads file
```
```ts
delete store['key'] // Deletes file
```
```ts
delete store['*'] // Deletes all storage files
```
```ts
'key' in store // Checks for file existence
//=> true or false
```
- You can use `store.keyName` instead of `store['keyName']` anywhere if the key name allows.
- `undefined` is not supported as a savable value, but `null` is. Saving a key with value `undefined` is equivalent to removing it. So, you can use `store['key'] = undefined` or even `store['*'] = undefined` to delete files.
- Synchronous API will throw an exception if any errors happen, so you shall handle it your way.
### Asynchronous API with Promises
Every one of the following calls **returns a promise**:
```ts
store('key', value) // Writes file
```
```ts
store('key') // Reads file
```
```ts
new store('key') // Resets/deletes file
```
```ts
new store('*') /* or */
new store() /* or */
new store // Deletes all storage files
```
```ts
('key' in store(), store()) // Checks for file existence
// Resolves to true or false
```
- Once again, `undefined` is not supported as a savable value, but `null` is. Saving a key with value `undefined` is equivalent to removing it. So, you can use `store('key', undefined)` or even `store('*', undefined)` to delete files.
### Asynchronous API with Callbacks
The same as asynchronous with promises, but with callback function as the last input parameter of `store()` :
```ts
store('key', value, cb) // Writes file
```
```ts
store('key', cb) // Reads file
```
```ts
new store('key', cb) // Resets/Deletes file
```
```ts
new store('*', cb) /* or */
new store(cb) // Deletes all storage files
```
```ts
'key' in store(cb) // Checks for file existence
// without promise output
/* or */
('key' in store(), store(cb))
// Checks for file existence
// with promise output
```
- These calls *still* return a promise on their output (except for `'key' in store(callback)` form of existence check).
- The first input parameter of all callback functions is `err`, so you shall handle it within the callback. *Reading* and *Existence checking* callbacks provide the return values as their second input parameter.
### Folders as Collections
Every folder in the storage can be treated as a *collection* of *key-values*.
You can query the list of all containing keys (*filenames*) within a collection (*folder*) like this (_**Note** that a collection path must end with a **forward slash** `'/'`_):
#### Synchronous API
```ts
try {
const keys = store['col/path/']
// keys = ['col/path/key1', 'col/path/sub/key2', ... ]
} catch (error) {
// Handle error...
}
```
#### Asynchronous API with Promises
```ts
store('col/path/')
.then(keys => {
// keys = ['col/path/key1', 'col/path/sub/key2', ... ]
})
.catch(error => {
// Handle error...
})
```
#### Asynchronous API with Callbacks
```ts
store('col/path/', (error, keys) => {
if (error) {
// Handle error...
}
// keys = ['col/path/key1', 'col/path/sub/key2', ... ]
})
```
## Notes
- **NOTE 1 :** Each key will map to a separate file (*using the key itself as its relative path*). Therefore, keys may be relative paths, e.g: `'data.json'`, `'/my/key/01'` or `'any/other/relative/path/to/a/file'`. The only exceptions are the strings including `'..'` (*double dot*) which will not be accepted for security reasons.
- **NOTE 2 :** You may have hidden key files by simply add a `'.'` before the filename in the key path.
- **NOTE 3 :** If a key's relative path ends with a *forward slash* `'/'`, it will be considered to be a collection (*folder*) name. So, `'data/set/'` is a collection and `'data/set/key'` is a key in that collection.
- **NOTE 4 :** This module has a built-in implemented **cache**, so, when activated, accessing a certain key more than once won't require file-system level operations again for that file.
- **NOTE 5 :** When activated, caching will include queries on *collections* too.
## Example
```ts
import kfs from "key-file-storage"
// Locate 'db' folder in the current directory as the storage path,
// Require 100 latest accessed key-values to be cached:
const store = kfs('./db', 100)
// Create file './db/users/hessam' containing this user data, synchronously:
store['users/hessam'] = ({
name: "Hessam",
skills: {
java: 10,
csharp: 15
}
})
// Read file './db/users/hessam' as a JSON object, asynchronously:
store('users/hessam').then(hessam => {
console.log(`Hessam's java skill is ${hessam.skills.java}.`)
})
// Check whether file './db/users/mahdiar' exists or not, asynchronously:
'users/mahdiar' in store((error, exists) => {
if (exists) {
console.log("User Mahdiar exists!")
}
})
// List all the keys in './db/users/', synchronously:
const allUsers = store['users/']
//=> ['users/hessam', 'users/mahdiar', ... ]
```
## Contribute
It would be very appreciated if you had any suggestions or contribution on this repository or submitted any issue.
+ See the code on [GitHub](https://github.com/ahs502/key-file-storage)
+ Contact me by [my gmail address](ahs502@gmail.com) *(Hessamoddin A Shokravi)*