Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

node-persist

Package Overview
Dependencies
Maintainers
3
Versions
41
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

node-persist - npm Package Compare versions

Comparing version 2.1.0 to 3.0.0

LICENSE

137

examples/counter/counter.js

@@ -11,89 +11,80 @@ /*

var storage = require('../../src/node-persist');
var http = require('http');
const storage = require('../../src/node-persist');
const http = require('http');
var ttl = 3000;
const ttl = 3000;
const host = '127.0.0.1';
const port = 8080;
storage.init({
logging: true,
ttl: ttl
}).then(function() {
const resolveType = function (str) {
let type = typeof str;
if (type !== 'string') {
return str;
} else {
let nb = parseFloat(str);
if (!isNaN(parseFloat(str)) && isFinite(str))
return nb;
if (str === 'false')
return false;
if (str === 'true')
return true;
if (str === 'undefined')
return undefined;
if (str === 'null')
return null;
try {
str = JSON.parse(str);
} catch (e) {
}
return str;
}
};
return storage.getItem('counter');
}).then(function(counter) {
(async () => {
await storage.init({logging: true, ttl: ttl});
if (! counter) {
return storage.setItemSync('counter', 0);
}
return counter
}).then(function() {
let counter = await storage.getItem('counter');
return storage.getItem('counter');
}).then(function(counter) {
if (! counter) {
await storage.setItemSync('counter', 0);
}
counter = await storage.getItem('counter');
console.log('counter is ' + counter);
console.log('counter is ' + counter);
}).catch(function(err) {
console.error(err);
throw err;
});
http.createServer(async function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
if (req.url === '/') {
let c = await storage.getItemSync('counter');
if (!c) {
console.log('counter ttl expired, resetting to 0');
c = 0;
await storage.setItemSync('counter', 0);
}
await storage.setItemSync('counter', c + 1);
var resolveType = function (str) {
var type = typeof str;
if (type !== 'string') {
return str;
} else {
var nb = parseFloat(str);
if (!isNaN(parseFloat(str)) && isFinite(str))
return nb;
if (str === 'false')
return false;
if (str === 'true')
return true;
if (str === 'undefined')
return undefined;
if (str === 'null')
return null;
try {
str = JSON.parse(str);
} catch (e) {
}
return str;
}
};
res.end("counter is: " + (await storage.getItem('counter')) + ' (every time you refresh you reset the ttl timer, but just wait ' + ttl / 1000 + ' seconds, it should reset back to 1)');
}
if (/\/\w+/.test(req.url)) { // secret paths
let url = req.url.slice(1);
let parts = url.split('?');
let fn = parts[0];
let args = (parts[1] || '').split(',').map(v => resolveType(v));
var host = '127.0.0.1';
var port = 8080;
if (typeof storage[fn] === 'function') {
res.end(JSON.stringify(await storage[fn].apply(storage, args), undefined, 4));
} else {
res.end(fn + ' is not a known storage function');
}
} else {
res.end();
}
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
if (req.url === '/') {
var c = storage.getItemSync('counter');
if (!c) {
console.log('counter ttl expired, resetting to 0');
c = 0;
storage.setItemSync('counter', 0);
}
storage.setItemSync('counter', c + 1);
res.end("counter is: " + storage.getItem('counter') + ' (everytime you refresh you reset the ttl timer, but just wait ' + ttl / 1000 + ' seconds, it should reset back to 1)');
}).listen(port, host);
} if (/\/\w+/.test(req.url)) { // secret paths
var url = req.url.slice(1);
var parts = url.split('?');
var fn = parts[0];
var args = (parts[1] || '').split(',').map(function(v) { return resolveType(v); });
if (typeof storage[fn] === 'function') {
res.end(JSON.stringify(storage[fn].apply(storage, args), undefined, 4));
} else {
res.end(fn + ' is not a known storage function');
}
console.log("running on " + host + ":" + port);
} else {
res.end();
}
})();
}).listen(port, host);
console.log("running on " + host + ":" + port);
{
"name": "node-persist",
"version": "2.1.0",
"version": "3.0.0",
"description": "Super-easy (and fast) persistent data structures in Node.js, modeled after HTML5 localStorage",

@@ -40,10 +40,9 @@ "main": "./src/node-persist.js",

"is-absolute": "^0.2.6",
"mkdirp": "~0.5.1",
"q": "~1.1.1"
"mkdirp": "~0.5.1"
},
"devDependencies": {
"chai": "^3.5.0",
"mocha": "^2.3.3",
"chai": "^4.1.2",
"mocha": "^5.0.5",
"rimraf": "^2.4.3"
}
}
# node-persist
## (localStorage on the server)
### Super-easy (and fast) persistent data structures in Node.js, modeled after HTML5 localStorage
Node-persist doesn't use a database. Instead, JSON documents are stored in the file system for persistence. Because there is no network overhead and your data is just in-memory, node-persist is just about as fast as a database can get. Node-persist uses the HTML5 localStorage API, so it's easy to learn.
### Super-easy asynchronous persistent data structures in Node.js, modeled after HTML5 localStorage
Node-persist doesn't use a database. Instead, JSON documents are stored in the file system for persistence. Because there is no network overhead, node-persist is just about as fast as a database can get. Node-persist uses the HTML5 localStorage API, so it's easy to learn.
This is still a work in progress. Send pull requests please.
## Note
If you're looking for the version that supports both `synchronous` and `asynchronous` use `node-persist@2.10`

@@ -15,48 +17,32 @@ ## Install

Then in code you can do:
## Basic Example
```js
var storage = require('node-persist');
```
const storage = require('node-persist');
## Basic Example
Async example
```js
//you must first call storage.init
await storage.init( /* options ... */ );
await storage.setItem('name','yourname')
console.log(await storage.getItem('name')); // yourname
```
storage.init( /* options ... */ ).then(function() {
//then start using it
storage.setItem('name','yourname')
.then(function() {
## Run the counter example:
return storage.getItem('name')
})
.then(function(value) {
console.log(value); // yourname
})
});
```sh
$ cd examples/counter
$ node counter.js
$ open up localhost:8080
```
Sync example
```
//you must first call storage.initSync
storage.initSync();
## 3.0.0 change logs
//then start using it
storage.setItemSync('name','yourname');
console.log(storage.getItemSync('name')); // yourname
Non-backward changes
```
* All the `*Sync` functions were removed, __every__ operation is now __asynchronous__
* All the `persist*` functions were removed
* __Nothing__ is held up in __RAM__ use your own memory caching module, i.e. [nano-cache](https://github.com/akhoury/nano-cache)
* [Node 7.6+](https://stackoverflow.com/a/41757243/493756) is required now, we're using `async/await`
* `continuous` and `interval` options were removed, since we immediately persist to disk now, __asynchronously__
* `forEach` callback now accepts an object `callback({key, value})` instead of 2 arguments `callback(key, value)
## Run the examples:
```sh
$ cd examples/examplename
$ node examplename.js
$ open up localhost:8080
```
## 2.0.0 change logs

@@ -87,4 +73,4 @@

#### `init(options, [callback])` - asynchronous*, returns Promise
This function reads what's on disk and loads it into memory, if the storage dir is new, it will create it
#### `async init(options, [callback])`
if the storage dir is new, it will create it
##### Options

@@ -95,3 +81,3 @@ You can pass `init()` or `initSync()` an options object to customize the behavior of node-persist

```js
storage.init({
await storage.init({
dir: 'relative/path/to/persist',

@@ -107,200 +93,93 @@

continuous: true, // continously persist to disk
interval: false, // milliseconds, persist to disk on an interval
ttl: false, // ttl* [NEW], can be true for 24h default or a number in MILLISECONDS
expiredInterval: 2 * 60 * 1000, // [NEW] every 2 minutes the process will clean-up the expired cache
expiredInterval: 2 * 60 * 1000, // every 2 minutes the process will clean-up the expired cache
// in some cases, you (or some other service) might add non-valid storage files to your
// storage dir, i.e. Google Drive, make this true if you'd like to ignore these files and not throw an error
forgiveParseErrors: false // [NEW]
forgiveParseErrors: false
}, /* optional callback */ ).then(onSuccess, onError); // or use the promise
});
```
\* With ttl (time to live), it is recommended that you use `getItem(key, callback)` or `getItemSync(key)` since, if a `ttl` of a certain key is expired the key-file is immediately deleted from disk, the callback will execute whenever that happends, if there is no ttl used or it has expired yet, the callback will also immediately execute in a synchronous fashion.
#### `async getItem(key)`
This function will get the value for that key stored on disk
##### Node-persist has 3 ways of running:
1. By default, keys will be persisted after every call of setItem
2. If you set an interval, node-persist will persist changed keys at that interval instead of after every call of setItem.
3. If you set continuous to false and don't specify an interval, keys aren't persisted automatically, giving you complete control over when to persist them.
#### `initSync(options)` - synchronous, throws Error on failure
like `init()` but synchronous,
#### `getItem(key, [callback])` - returns promise,
This function will get a key from your database in memory
```js
// callback
storage.getItem('name', function (err, value) {
// use value here after making sure expired-ttl key deletion has occured, in that case value === undefined
});
// promise
storage.getItem('obj').then(function(value) {
})
let value = await storage.getItem('obj');
```
#### `getItemSync(key)` - returns value
All synchronous part along with the deletion of an expired-ttl key, if `options.ttl` is used
#### `setItem(key, value, [options, callback])` - asynchronous*, returns Promise
This function sets 'key' in your database to 'value'. It also sets a flag, notifying that 'key' has been changed and needs to be persisted in the next sweep. Because the flag must be set for the object to be persisted, it is best to use node-persist in a functional way, as shown below.
#### `async setItem(key, value, [options])`
This function sets 'key' in your database to 'value'
```js
storage.setItem('fibonacci',[0,1,1,2,3,5,8]);
storage.setItem(42,'the answer to life, the universe, and everything.', function(err) {
// done
});
storage.setItem(42,'the answer to life, the universe, and everything.', {ttl: 1000*60 /* 1 min */ }, function(err) {
// done
});
var batman = storage.getItem('batman');
batman.sidekick = 'Robin';
// using the promise
storage.setItem('batman', batman).then(
function() {
// success
},
function() {
// error
})
await storage.setItem('fibonacci',[0,1,1,2,3,5,8]);
await storage.setItem(42,'the answer to life, the universe, and everything.');
await storage.setItem(42,'the answer to life, the universe, and everything.', {ttl: 1000*60 /* 1 min */ });
```
\* The only option available when calling `setItem(key, value, option)` is `{ttl: $milliseconds}`
\* `setItem()` is asynchronous, however, depending on your global options, the item might not persist to disk immediately, in the case where you set `options.interval` or `options.continuous=false`, your (optional) callback or your returned promise from this function will still get resolved immediately, even if the value has not been persisted to disk yet, which could be either waiting for the interval to kick in or for your manual call to `persist()` - kind of how the `redis` database works.
#### `async removeItem(key)`
This function immediately deletes it from the file system asynchronously
#### `setItemSync(key, value, [options])` - synchronous, throws Error on failure
If you want to immediately persist to disk, __regardless of the `this.options.interval` and `this.options.continuous`__ settings, use this function. The only option available when calling `setItemSync(key, value, option)` is `{ttl: $milliseconds}`
```javascript
storage.setItemSync('foo', 'bar');
storage.setItemSync('hello', 'world', {ttl: 1000 * 60 /* ttl 1 minute */})
```
#### `removeItem(key, [callback])` - asynchronous, returns Promise
This function removes key in the database if it is present, and immediately deletes it from the file system asynchronously. If ttl is used, the corrresponding ttl-key is removed as well
```js
storage.removeItem('me', /* optional callback */ function(err) {
// done
}).then(onSuccess, onError); // or use the promise
await storage.removeItem('me');
```
#### `removeItemSync(key)` - synchronous, throws Error on failure
just like removeItem, but synchronous
```js
storage.removeItemSync('me');
```
#### `clear([callback])` - asynchronous, returns Promise
This function removes all keys in the database, and immediately deletes all keys from the file system asynchronously.
#### `clearSync()` - synchronous, throws Error on failure
like `clear()` but synchronous
#### `values()` - synchronous, returns array
This function returns all of the values in the database in memory.
#### `async clear()`
This function immediately deletes all files from the file system asynchronously.
```js
storage.setItem("batman", {name: "Bruce Wayne"});
storage.setItem("superman", {name: "Clark Kent"});
console.log(storage.values()); //output: [{name: "Bruce Wayne"},{name: "Clark Kent"}]
await storage.clear();
```
#### `values()` - returns array
#### `async values()`
This function returns all of the values
```js
var values = storage.values();
await storage.setItem("batman", {name: "Bruce Wayne"});
await storage.setItem("superman", {name: "Clark Kent"});
console.log(await storage.values()); //output: [{name: "Bruce Wayne"},{name: "Clark Kent"}]
```
#### `valuesWithKeyMatch(match)` - synchronous, returns array
This function returns all of the values in the database matching a string or RegExp
#### `async valuesWithKeyMatch(match)`
This function returns all of the values matching a string or RegExp
```js
storage.setItem("batman", {name: "Bruce Wayne"});
storage.setItem("superman", {name: "Clark Kent"});
storage.setItem("hulk", {name: "Bruce Banner"});
console.log(storage.valuesWithKeyMatch('man')); //output: [{name: "Bruce Wayne"},{name: "Clark Kent"}]
await storage.setItem("batman", {name: "Bruce Wayne"});
await storage.setItem("superman", {name: "Clark Kent"});
await storage.setItem("hulk", {name: "Bruce Banner"});
console.log(await storage.valuesWithKeyMatch('man')); //output: [{name: "Bruce Wayne"},{name: "Clark Kent"}]
// also accepts a Regular Expression
console.log(storage.valuesWithKeyMatch(/man/)); //output: [{name: "Bruce Wayne"},{name: "Clark Kent"}]
console.log(await storage.valuesWithKeyMatch(/man/)); //output: [{name: "Bruce Wayne"},{name: "Clark Kent"}]
```
#### `valuesWithKeyMatch(match)` - synchronous, returns array
#### `async keys()`
this function returns an array of all the keys in the database.
```js
var values = storage.valuesWithKeyMatch('man');
console.log(await storage.keys()); // ['batman', 'superman']
```
#### `keys()` - synchronous, returns array
this function returns an array of all the keys in the database. This function returns the number of keys stored in the database.
#### `length()` - synchronous, returns number
#### `async length()`
This function returns the number of keys stored in the database.
```js
console.log(await storage.length()); // 2
```
#### `async forEach(callback)`
This function iterates over each key/value pair and executes an asynchronous callback as well
#### `forEach(callback)` - synchronous, assuming callback is as well.
This function iterates over each key/value pair and executes a callback.
```javascript
storage.forEach(function(key, value) {
// use key and value
storage.forEach(async function(datum) {
// use datum.key and datum.value
});
```
### Fine-grained control
Make sure you set `continuous:false` in the `options` hash, and you don't set an `interval`
#### `persist([callback])` - asynchronous, returns Promise
These function can be used to manually persist the database
```js
storage.persist( /* optional callback */ function(err) {
// when done
}).then(onSuccess, onError); // or you can use the promise
```
#### `persistSync()` - synchronous, throws Error on failure
like `persist()` but synchronous
```js
storage.persistSync();
```
##### note:
Both `persist()`, `persistSync()`, `persistKey()`, and `persistKeySync()` will automatically persist the ttl keys/values in the persistance process
#### `persistKey(key, [callback])` - asynchronous, returns Promise
This function manually persist a 'key' within the database
```js
storage.setItem('name','myname');
storage.persistKey('name', /* optional callback */ function(err) {
// when done
}).then(onSuccess, onError); // or you can use the promise
```
#### `persistKeySync(key)`
like `persistKey()` but synchronous
```js
storage.setItem('name','myname');
storage.persistKeySync('name');
```
### Factory method
#### `create(options)` - synchronous, static method
#### `create(options)` - synchronous, static method
If you choose to create multiple instances of storage, you can. Just avoid using the same `dir` for the storage location.
__You still have to call `init` or `initSync` after `create`__ - you can pass your configs to either `create` or `init/Sync`
__You still have to call `init` after `create`__ - you can pass your configs to either `create` or `init`
The reason we don't call `init` in the constructor (or when you `create`) because we can only do so for the `initSync` version, the async `init` returns a promise, and in order to maintain that API, we cannot return the promise in the constructor, so `init` must be called on the instance of new LocalStorage();
```javascript
var storage = require('node-persist');
var myStorage = storage.create({dir: 'myDir', ttl: 3000});
myStorage.init().then(function() { // or you can use initSync()
// ...
});
const storage = require('node-persist');
const myStorage = storage.create({dir: 'myDir', ttl: 3000});
await myStorage.init();
```
### Contributing
#### Tests

@@ -307,0 +186,0 @@

@@ -6,718 +6,364 @@ /*

var fs = require('fs'),
path = require('path'),
crypto = require('crypto'),
mkdirp = require('mkdirp'),
isAbsolute = require('is-absolute'),
Q = require('q'),
pkg = require('../package.json'),
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const mkdirp = require('mkdirp');
const isAbsolute = require('is-absolute');
const pkg = require('../package.json');
defaults = {
dir: '.' + pkg.name + '/storage',
stringify: JSON.stringify,
parse: JSON.parse,
encoding: 'utf8',
logging: false,
continuous: true,
interval: false,
expiredInterval: 2 * 60 * 1000, /* every 2 minutes */
forgiveParseErrors: false,
ttl: false
},
const defaults = {
dir: '.' + pkg.name + '/storage',
stringify: JSON.stringify,
parse: JSON.parse,
encoding: 'utf8',
logging: false,
expiredInterval: 2 * 60 * 1000, /* every 2 minutes */
forgiveParseErrors: false,
ttl: false
};
defaultTTL = 24 * 60 * 60 * 1000 /* if ttl is truthy but it's not a number, use 24h as default */,
const defaultTTL = 24 * 60 * 60 * 1000; /* if ttl is truthy but it's not a number, use 24h as default */
isNumber = function(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
},
const isFunction = function(fn) {
return typeof fn === 'function';
};
isFunction = function(fn) {
return typeof fn === 'function';
},
const isNumber = function(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
};
noop = function() {},
const md5 = function (key) {
return crypto.createHash('md5').update(key).digest('hex');
};
md5 = function (data) {
return crypto.createHash('md5').update(data).digest("hex");
},
const isValidStorageFileContent = function (content) {
return content && content.key;
};
isValidStorageFileContent = function (content) {
return content && content.key;
};
const isExpired = function (datum) {
return datum.ttl && datum.ttl < (new Date()).getTime();
};
var LocalStorage = function (userOptions) {
if(!(this instanceof LocalStorage)) {
return new LocalStorage(userOptions);
}
this.data = {};
this.changes = {};
this.setOptions(userOptions);
const isNotExpired = function (datum) {
return !isExpired(datum);
};
// we don't call init in the constructor because we can only do so for the initSync
// for init async, it returns a promise, and in order to maintain that API, we cannot return the promise in the constructor
// so init must be called, separately, on the instance of new LocalStorage();
const resolveDir = function(dir) {
dir = path.normalize(dir);
if (isAbsolute(dir)) {
return dir;
}
return path.join(process.cwd(), dir);
};
const LocalStorage = function (options) {
if(!(this instanceof LocalStorage)) {
return new LocalStorage(options);
}
this.setOptions(options);
};
LocalStorage.prototype = {
setOptions: function (userOptions) {
var options = {};
init: async function (options) {
if (options) {
this.setOptions(options);
}
await this.ensureDirectory(this.options.dir);
if (this.options.expiredInterval) {
this.startExpiredKeysInterval();
}
return this.options;
},
if (!userOptions) {
options = defaults;
} else {
for (var key in defaults) {
if (userOptions.hasOwnProperty(key)) {
options[key] = userOptions[key];
} else {
options[key] = defaults[key];
}
}
setOptions: function (userOptions) {
let options = {};
options.dir = this.resolveDir(options.dir);
options.ttl = options.ttl ? isNumber(options.ttl) && options.ttl > 0 ? options.ttl : defaultTTL : false;
}
if (!userOptions) {
options = defaults;
} else {
for (let key in defaults) {
if (userOptions.hasOwnProperty(key)) {
options[key] = userOptions[key];
} else {
options[key] = this.options && this.options[key] != null ? this.options[key] : defaults[key];
}
}
options.dir = resolveDir(options.dir);
options.ttl = options.ttl ? isNumber(options.ttl) && options.ttl > 0 ? options.ttl : defaultTTL : false;
}
// Check to see if we received an external logging function
if (isFunction(options.logging)) {
// Overwrite log function with external logging function
this.log = options.logging;
options.logging = true;
}
// Check to see if we received an external logging function
if (isFunction(options.logging)) {
// Overwrite log function with external logging function
this.log = options.logging;
options.logging = true;
}
this.options = options;
},
this.options = options;
},
data: async function () {
return this.readDirectory(this.options.dir);
},
init: function (userOptions, callback) {
if (isFunction(userOptions)) {
callback = userOptions;
userOptions = null;
}
if (userOptions) {
this.setOptions(userOptions);
}
callback = isFunction(callback) ? callback : noop;
keys: async function (filter) {
let data = await this.data();
if (filter) {
data = data.filter(filter);
}
return data.map(datum => datum.key);
},
var deferred = Q.defer();
var deferreds = [];
values: async function (filter) {
let data = await this.data();
if (filter) {
data = data.filter(filter);
}
return data.map(datum => datum.value);
},
var options = this.options;
length: async function (filter) {
let data = await this.data();
if (filter) {
data = data.filter(filter);
}
return data.length;
},
var result = {dir: options.dir};
deferreds.push(this.parseStorageDir());
forEach: async function(callback) {
let data = await this.data();
for (let i in data) {
await callback(data[i]);
}
},
//start persisting
if (options.interval && options.interval > 0) {
this.startPersistInterval(this.persist.bind(this));
}
valuesWithKeyMatch: async function(match) {
match = match || /.*/;
let filter = match instanceof RegExp ? key => match.test(key) : key => key.indexOf(match) !== -1;
return this.values(filter);
},
if (options.expiredInterval) {
this.startExpiredKeysInterval();
}
set: function (key, value, options = {}) {
return this.setItem(key, value, options);
},
Q.all(deferreds).then(
function() {
deferred.resolve(result);
callback(null, result);
},
function(err) {
deferred.reject(err);
callback(err);
});
setItem: function (key, datumValue, options = {}) {
let value = this.copy(datumValue);
let ttl = this.calcTTL(options.ttl);
if (this.logging) {
this.log(`set ('${key}': '${this.stringify(value)}')`);
}
let datum = {key: key, value: value, ttl: ttl};
return this.writeFile(this.getDatumPath(key), datum);
},
return deferred.promise;
},
get: function (key) {
return this.getItem(key);
},
initSync: function (userOptions) {
if (userOptions) {
this.setOptions(userOptions);
}
getItem: async function (key) {
let datum = await this.getDatum(key);
if (isExpired(datum)) {
this.log(`${key} has expired`);
await this.removeItem(key);
} else {
return datum.value;
}
},
var options = this.options;
getDatum: async function (key) {
return this.readFile(this.getDatumPath(key));
},
if (options.logging) {
this.log("options:");
this.log(this.stringify(options));
}
getRawDatum: async function (key) {
return this.readFile(this.getDatumPath(key), {raw: true});
},
this.parseStorageDirSync();
if (options.expiredInterval) {
this.startExpiredKeysInterval();
}
//start synchronous persisting,
if (options.interval && options.interval > 0) {
this._persistInterval = setInterval(this.persistSync.bind(this), options.interval);
}
},
getDatumValue: async function (key) {
let datum = await this.getDatum(key);
return datum && datum.value;
},
keys: function () {
return Object.keys(this.data);
},
getDatumPath: function (key) {
return path.join(this.options.dir, md5(key));
},
length: function () {
return this.keys().length;
},
del: function (key) {
return this.removeItem(key);
},
forEach: function(callback) {
return this.keys().forEach(function(key) {
callback(key, this.getDataValue(key));
}.bind(this));
},
rm: function (key) {
return this.removeItem(key);
},
values: function() {
return this.keys().map(function(key) {
return this.getDataValue(key);
}.bind(this));
},
removeItem: async function (key) {
return this.deleteFile(this.getDatumPath(key));
},
valuesWithKeyMatch: function(match) {
match = match || /.*/;
removeExpiredItems: async function () {
let keys = await this.keys(isExpired);
for (let i in keys) {
await this.removeItem(keys[i]);
}
},
var filter = match instanceof RegExp ?
function(key) {
return match.test(key);
} :
function(key) {
return key.indexOf(match) !== -1;
};
clear: async function () {
let data = await this.data();
for (let i in data) {
await this.removeItem(data[i].key);
}
},
var values = [];
this.keys().forEach(function(key) {
if (filter(key)) {
values.push(this.getDataValue(key));
}
}.bind(this));
ensureDirectory: function (dir) {
return new Promise((resolve, reject) => {
let result = {dir: dir};
//check to see if dir is present
fs.exists(dir, (exists) => {
if (exists) {
return resolve(result);
} else {
//create the directory
mkdirp(dir, (err) => {
if (err) {
return reject(err);
}
this.log('created ' + dir);
resolve(result);
});
}
});
});
},
return values;
},
readDirectory: function (dir) {
return new Promise((resolve, reject) => {
//check to see if dir is present
fs.exists(dir, (exists) => {
if (exists) {
//load data
fs.readdir(dir, async (err, arr) => {
if (err) {
return reject(err);
}
let data = [];
for (let i in arr) {
let currentFile = arr[i];
if (currentFile[0] !== '.') {
data.push(await this.readFile(path.join(this.options.dir, currentFile)));
}
}
resolve(data);
});
} else {
reject(new Error(`[node-persist][readDirectory] ${dir} does not exists!`));
}
});
});
},
set: function (key, value, options, callback) {
return this.setItem(key, value, options, callback);
},
readFile: function (file, options = {}) {
return new Promise((resolve, reject) => {
fs.readFile(file, this.options.encoding, (err, text) => {
if (err) {
/* Only throw the error if the error is something else other than the file doesn't exist */
if (err.code === 'ENOENT') {
this.log(`${file} does not exist, returning undefined value`);
resolve(options.raw ? '{}' : {});
} else {
return reject(err);
}
}
let input = options.raw ? text : this.parse(text);
if (!options.raw && !isValidStorageFileContent(input)) {
return this.options.forgiveParseErrors ? resolve() : reject(new Error(`[node-persist][readFile] ${file} does not look like a valid storage file!`));
}
resolve(input);
});
});
},
setItem: function (key, dataValue, options, callback) {
if (typeof options == 'function') {
callback = options;
options = null;
}
options = options || {};
callback = isFunction(callback) ? callback : noop;
writeFile: function (file, content) {
return new Promise((resolve, reject) => {
fs.writeFile(file, this.stringify(content), this.options.encoding, (err) => {
if (err) {
return reject(err);
}
resolve({file: file, content: content});
this.log('wrote: ' + file);
});
});
},
var deferred = Q.defer();
var deferreds = [];
deleteFile: function (file) {
return new Promise((resolve, reject) => {
fs.exists(file, (exists) => {
if (exists) {
this.log(`Removing file:${file}`);
fs.unlink(file, (err) => {
/* Only throw the error if the error is something else */
if (err && err.code !== 'ENOENT') {
return reject(err);
}
let result = {file: file, removed: !err, existed: exists};
err && this.log(`Failed to remove file:${file} because it doesn't exist anymore.`);
resolve(result);
});
} else {
this.log(`Not removing file:${file} because it doesn't exist`);
let result = {file: file, removed: false, existed: exists};
resolve(result);
}
});
});
},
var value = this.copy(dataValue);
// ttl is different that the other options because we can pass a different for each setItem, as well as have a default one.
var ttl = this.calcTTL(options.ttl);
this.data[key] = {value: value, ttl: ttl};
stringify: function (obj) {
return this.options.stringify(obj);
},
var instanceOptions = this.options;
var result = {key: key, value: value, ttl: ttl, queued: !!instanceOptions.interval, manual: !instanceOptions.interval && !instanceOptions.continuous};
parse: function(str) {
if (str == null) {
return undefined;
}
try {
return this.options.parse(str);
} catch(e) {
this.log('parse error: ', this.stringify(e), 'for:', str);
return undefined;
}
},
var onSuccess = function () {
callback(null, result);
deferred.resolve(result);
};
copy: function (value) {
// don't copy literals since they're passed by value
if (typeof value !== 'object') {
return value;
}
return this.parse(this.stringify(value));
},
var onError = function (err) {
callback(err);
deferred.reject(err);
};
startExpiredKeysInterval: function () {
this.stopExpiredKeysInterval();
this._expiredKeysInterval = setInterval(this.removeExpiredItems.bind(this), this.options.expiredInterval);
this._expiredKeysInterval.unref && this._expiredKeysInterval.unref();
},
if (instanceOptions.logging) {
this.log("set (" + key + ": " + this.stringify(value) + ")");
}
stopExpiredKeysInterval: function () {
clearInterval(this._expiredKeysInterval);
},
if (instanceOptions.interval || !instanceOptions.continuous) {
this.changes[key] = {onError: onError};
process.nextTick(onSuccess);
} else {
deferreds.push(this.persistKey(key));
log: function () {
this.options && this.options.logging && console.log.apply(console, arguments);
},
Q.all(deferreds).then(
function(result) {
deferred.resolve(result);
callback(null, result);
}.bind(this),
function(err) {
deferred.reject(err);
callback(err);
});
}
return deferred.promise;
},
setItemSync: function (key, dataValue, options) {
options = options || {};
var ttl = this.calcTTL(options.ttl);
var value = this.copy(dataValue);
this.data[key] = {key: key, value: value, ttl: ttl};
this.persistKeySync(key);
if (this.options.logging) {
this.log("set (" + key + ": " + this.stringify(value) + ")");
}
},
get: function (key, callback) {
return this.getItem(key, callback);
},
getItem: function (key, callback) {
callback = isFunction(callback) ? callback : noop;
var deferred = Q.defer();
if (this.isExpired(key)) {
this.log(key + ' has expired');
if (this.options.interval || !this.options.continuous) {
callback(null, null);
return deferred.resolve(null);
}
return this.removeItem(key).then(function() {
callback(null, null);
return null;
}, callback);
} else {
var value = this.getDataValue(key);
callback(null, value);
deferred.resolve(value);
}
return deferred.promise;
},
getItemSync: function (key) {
if (this.isExpired(key)) {
this.removeItemSync(key);
} else {
return this.getDataValue(key);
}
},
getDataValue: function (key) {
return this.data[key] ? this.copy(this.data[key].value) : undefined;
},
del: function (key, callback) {
return this.removeItem(key, callback);
},
rm: function (key, callback) {
return this.removeItem(key, callback);
},
removeItem: function (key, callback) {
callback = isFunction(callback) ? callback : noop;
var deferred = Q.defer();
var deferreds = [];
deferreds.push(this.removePersistedKey(key));
Q.all(deferreds).then(
function() {
var value = this.getDataValue(key);
delete this.data[key];
this.log('removed: ' + key);
callback(null, value);
deferred.resolve(value);
}.bind(this),
function(err) {
callback(err);
deferred.reject(err);
}
);
return deferred.promise;
},
removeItemSync: function (key) {
var value = this.getDataValue(key);
this.removePersistedKeySync(key);
delete this.data[key];
this.log('removed: ' + key);
return value;
},
removeExpiredItems: function (callback) {
callback = isFunction(callback) ? callback : noop;
var deferred = Q.defer();
var deferreds = [];
var keys = this.keys();
for (var i = 0; i < keys.length; i++) {
if (this.isExpired(keys[i])) {
deferreds.push(this.removeItem(keys[i]));
}
}
Q.all(deferreds).then(
function() {
deferred.resolve();
callback();
},
function(err) {
deferred.reject(err);
callback(err);
});
return deferred.promise;
},
clear: function (callback) {
callback = isFunction(callback) ? callback : noop;
var deferred = Q.defer();
var deferreds = [];
var keys = this.keys();
for (var i = 0; i < keys.length; i++) {
deferreds.push(this.removePersistedKey(keys[i]));
}
Q.all(deferreds).then(
function() {
this.data = {};
this.changes = {};
deferred.resolve();
callback();
}.bind(this),
function(err) {
deferred.reject(err);
callback(err);
});
return deferred.promise;
},
clearSync: function () {
var keys = this.keys(true);
for (var i = 0; i < keys.length; i++) {
this.removePersistedKeySync(keys[i]);
}
this.data = {};
this.changes = {};
},
persist: function (callback) {
callback = isFunction(callback) ? callback : noop;
var deferred = Q.defer();
var result;
var deferreds = [];
for (var key in this.data) {
if (this.changes[key]) {
deferreds.push(this.persistKey(key));
}
}
Q.all(deferreds).then(
function(result) {
deferred.resolve(result);
callback(null, result);
this.log('persist done');
}.bind(this),
function(err) {
deferred.reject(result);
callback(err);
});
return deferred.promise;
},
persistSync: function () {
for (var key in this.data) {
if (this.changes[key]) {
this.persistKeySync(key);
}
}
this.log('persistSync done');
},
/*
* This function triggers a key within the database to persist asynchronously.
*/
persistKey: function (key, callback) {
callback = isFunction(callback) ? callback : noop;
var self = this;
var options = this.options;
var file = path.join(options.dir, md5(key));
var deferred = Q.defer();
var output = {key: key, value: this.data[key] && this.data[key].value, ttl: this.data[key] && this.data[key].ttl};
fs.writeFile(file, this.stringify(output), options.encoding, function(err) {
if (err) {
self.changes[key] && self.changes[key].onError && self.changes[key].onError(err);
deferred.reject(err);
return callback(err);
}
self.changes[key] && self.changes[key].onSuccess && self.changes[key].onSuccess();
delete self.changes[key];
deferred.resolve(output);
callback(null, output);
self.log("wrote: " + key);
});
return deferred.promise;
},
persistKeySync: function (key) {
var options = this.options;
var file = path.join(options.dir, md5(key));
var output = {key: key, value: this.data[key] && this.data[key].value, ttl: this.data[key] && this.data[key].ttl};
try {
fs.writeFileSync(file, this.stringify(output));
this.changes[key] && this.changes[key].onSuccess && this.changes[key].onSuccess();
} catch (err) {
this.changes[key] && this.changes[key].onError && this.changes[key].onError(err);
throw err;
}
delete this.changes[key];
this.log("wrote: " + key);
},
removePersistedKey: function (key, callback) {
callback = isFunction(callback) ? callback : noop;
var options = this.options;
var deferred = Q.defer();
var result;
//check to see if key has been persisted
var file = path.join(options.dir, md5(key));
fs.exists(file, function (exists) {
if (exists) {
fs.unlink(file, function (err) {
result = {key: key, removed: !err, existed: exists};
if (err && err.code != 'ENOENT') { /* Only throw the error if the error is something else */
deferred.reject(err);
return callback(err);
}
err && this.log('Failed to remove file:' + file + ' because it doesn\'t exist anymore.');
deferred.resolve(result);
callback(null, result);
}.bind(this));
} else {
result = {key: key, removed: false, existed: exists};
deferred.resolve(result);
callback(null, result);
}
}.bind(this));
return deferred.promise;
},
removePersistedKeySync: function(key) {
var options = this.options;
var file = path.join(options.dir, md5(key));
if (fs.existsSync(file)) {
try {
fs.unlinkSync(file);
} catch (err) {
if (err.code != 'ENOENT') { /* Only throw the error if the error is something else */
throw err;
}
this.log('Failed to remove file:' + file + ' because it doesn\'t exist anymore.');
}
return {key: key, removed: true, existed: true};
}
return {key: key, removed: false, existed: false};
},
stringify: function (obj) {
return this.options.stringify(obj);
},
parse: function(str){
try {
return this.options.parse(str);
} catch(e) {
this.log("parse error: ", this.stringify(e));
return undefined;
}
},
copy: function (value) {
// don't copy literals since they're passed by value
if (typeof value != 'object') {
return value;
}
return this.parse(this.stringify(value));
},
parseStorageDir: function(callback) {
callback = isFunction(callback) ? callback : noop;
var deferred = Q.defer();
var deferreds = [];
var dir = this.options.dir;
var self = this;
var result = {dir: dir};
//check to see if dir is present
fs.exists(dir, function (exists) {
if (exists) {
//load data
fs.readdir(dir, function (err, arr) {
if (err) {
deferred.reject(err);
callback(err);
}
for (var i in arr) {
var currentFile = arr[i];
if (currentFile[0] !== '.') {
deferreds.push(self.parseFile(currentFile));
}
}
Q.all(deferreds).then(
function() {
deferred.resolve(result);
callback(null, result);
},
function(err) {
deferred.reject(err);
callback(err);
});
});
} else {
//create the directory
mkdirp(dir, function (err) {
if (err) {
console.error(err);
deferred.reject(err);
callback(err);
} else {
self.log('created ' + dir);
deferred.resolve(result);
callback(null, result);
}
});
}
});
return deferred.promise;
},
parseStorageDirSync: function() {
var dir = this.options.dir;
var exists = fs.existsSync(dir);
if (exists) { //load data
var arr = fs.readdirSync(dir);
for (var i = 0; i < arr.length; i++) {
var currentFile = arr[i];
if (arr[i] && currentFile[0] !== '.') {
this.parseFileSync(currentFile);
}
}
} else { //create the directory
mkdirp.sync(dir);
}
},
parseFile: function (filename, callback) {
callback = isFunction(callback) ? callback : noop;
var deferred = Q.defer();
var self = this;
var options = this.options;
var dir = this.options.dir;
var file = path.join(dir, filename);
var error = function (err) {
deferred.reject(err);
return callback(err);
};
var done = function (input) {
deferred.resolve(input);
callback(null, input);
};
fs.readFile(file, options.encoding, function (err, text) {
if (err) {
return error(err);
}
var input = self.parse(text);
if (!isValidStorageFileContent(input)) {
return options.forgiveParseErrors ? done() : error(new Error('[PARSE-ERROR] ' + file + ' does not look like a valid storage file!'));
}
self.data[input.key] = input;
self.log("loaded: " + dir + "/" + input.key);
done(input);
});
return deferred.promise;
},
parseFileSync: function(filename) {
var dir = this.options.dir;
var file = path.join(dir, filename);
var input = this.parse(fs.readFileSync(file, this.options.encoding));
if (!isValidStorageFileContent(input)) {
if (this.options.forgiveParseErrors) {
return;
}
throw Error('[PARSE-ERROR] ' + file + ' does not look like a valid storage file!');
}
this.data[input.key] = input;
this.log("loaded: " + dir + "/" + input.key);
return this.data[input.key];
},
calcTTL: function (ttl) {
// only check for undefined, if null was passed in setItem then we probably didn't want to use the this.options.ttl
if (typeof ttl == 'undefined') {
ttl = this.options.ttl;
} else {
ttl = ttl ? isNumber(ttl) && ttl > 0 ? ttl : defaultTTL : false;
}
return ttl ? new Date().getTime() + ttl : undefined;
},
isExpired: function (key) {
return this.data[key] && this.data[key].ttl && this.data[key].ttl < (new Date()).getTime();
},
resolveDir: function(dir) {
dir = path.normalize(dir);
if (isAbsolute(dir)) {
return dir;
}
return path.join(process.cwd(), dir);
},
startPersistInterval: function (persistFunction) {
this.stopPersistInterval();
this._persistInterval = setInterval(persistFunction || this.persist.bind(this), this.options.interval);
this._persistInterval.unref && this._persistInterval.unref();
},
stopPersistInterval: function () {
clearInterval(this._persistInterval);
},
startExpiredKeysInterval: function () {
this.stopExpiredKeysInterval();
this._expiredKeysInterval = setInterval(this.removeExpiredItems.bind(this), this.options.expiredInterval);
this._expiredKeysInterval.unref && this._expiredKeysInterval.unref();
},
stopExpiredKeysInterval: function () {
clearInterval(this._expiredKeysInterval);
},
log: function () {
this.options && this.options.logging && console.log.apply(console, arguments);
},
md5: md5
calcTTL: function (ttl) {
// only check for undefined, if null was passed in setItem then we probably didn't want to use the this.options.ttl
if (typeof ttl === 'undefined') {
ttl = this.options.ttl;
} else {
ttl = ttl ? isNumber(ttl) && ttl > 0 ? ttl : defaultTTL : false;
}
return ttl ? new Date().getTime() + ttl : undefined;
}
};
module.exports = LocalStorage;

@@ -6,7 +6,5 @@ /*

var LocalStorage = require('./local-storage');
const LocalStorage = require('./local-storage');
(function(nodePersist) {
var localStorage;
/*

@@ -27,17 +25,7 @@ * This function just creates a localStorage instance, incase you don't plan on using the default one

nodePersist.init = function (userOptions, callback) {
localStorage = nodePersist.defaultInstance = nodePersist.create(userOptions);
var ret = localStorage.init(callback);
const localStorage = nodePersist.defaultInstance = nodePersist.create(userOptions);
let ret = localStorage.init(callback);
mixin(nodePersist, localStorage, {skip: ['init', 'initSync', 'create']});
return ret;
};
/*
* This function, (or initSync) must be called before the library can be used.
* An options hash can be optionally passed.
*/
nodePersist.initSync = function (userOptions) {
localStorage = nodePersist.defaultInstance = nodePersist.create(userOptions);
var ret = localStorage.initSync();
mixin(nodePersist, localStorage, {skip: ['init', 'initSync', 'create']});
return ret;
};

@@ -48,5 +36,4 @@ // expose all the API methods on the main module using a default instance

options.skip = options.skip || [];
var key;
for (key in source) {
if (typeof source[key] === 'function' && key.indexOf('_') !== 0 && options.skip.indexOf(key) == -1) {
for (let key in source) {
if (typeof source[key] === 'function' && key.indexOf('_') !== 0 && options.skip.indexOf(key) === -1) {
target[key] = source[key].bind(source);

@@ -53,0 +40,0 @@ }

var path = require("path");
var fs = require("fs");
var mkdirp = require("mkdirp");
var assert = require("chai").assert;
var rmdir = require('rimraf');
const path = require('path');
const fs = require('fs');
const mkdirp = require('mkdirp');
const assert = require('chai').assert;
const rmdir = require('rimraf');
var pkg = require("../package.json");
var LocalStorage = require('../src/local-storage');
var nodePersist = require('../src/node-persist');
const pkg = require('../package.json');
const LocalStorage = require('../src/local-storage');
const nodePersist = require('../src/node-persist');
var TEST_BASE_DIR = path.join(__dirname, '/storage-dirs');
const TEST_BASE_DIR = path.join(__dirname, '/storage-dirs');
var rand = function (prefix) {
return (prefix ? prefix + '-' : '') + (+ new Date()) + '-' + Math.floor(Math.random() * 1000);
const rand = function (prefix) {
return (prefix ? prefix + '-' : '') + (+ new Date()) + '-' + Math.floor(Math.random() * 1000);
};
var randDir = function () {
return path.join(TEST_BASE_DIR, "/" + rand());
const randDir = function () {
return path.join(TEST_BASE_DIR, '/' + rand());
};
describe("node-persist " + pkg.version + " tests:", function() {
process.on('unhandledRejection', (reason, p) => {
console.error('Unhandled Rejection at: Promise', p);
});
before(function(done) {
mkdirp(TEST_BASE_DIR, done);
});
describe('node-persist ' + pkg.version + ' tests:', async function() {
describe("instances", function() {
var dir1 = randDir();
var storage1 = nodePersist.create({
dir: dir1
});
var dir2 = randDir();
var storage2 = nodePersist.create({
dir: dir2
});
before(function(done) {
mkdirp(TEST_BASE_DIR, done);
});
storage1.initSync();
storage2.initSync();
after(function(done) {
rmdir(TEST_BASE_DIR, done);
});
it("should create 2 new different instances of LocalStorage", function(done) {
assert.ok(storage1 instanceof LocalStorage);
assert.ok(storage2 instanceof LocalStorage);
assert.ok(storage1 != storage2);
done();
});
describe('instances', function() {
let dir1, dir2, storage1, storage2, storage11, storage22;
storage1.setItemSync("s1", 1111);
storage2.setItemSync("s2", {a: 1});
beforeEach(async function () {
dir1 = randDir();
storage1 = nodePersist.create({
dir: dir1
});
dir2 = randDir();
storage2 = nodePersist.create({
dir: dir2
});
await storage1.init();
await storage2.init();
var storage11 = nodePersist.create({
dir: dir1
});
var storage22 = nodePersist.create({
dir: dir2
});
await storage1.setItem('s1', 1111);
await storage2.setItem('s2', {a: 1});
it("should use the 2 previous dirs and initSync correctly", function(done) {
storage11.initSync();
assert.equal(storage11.getItemSync("s1"), 1111, "write/read didn't work");
storage11 = nodePersist.create({
dir: dir1
});
storage22 = nodePersist.create({
dir: dir2
});
});
storage22.init().then(function() {
storage2.getItem("s2").then(function(value) {
assert.deepEqual(value, {a: 1}, "write/read didn't work");
done();
})
});
});
it('should create 2 new different instances of LocalStorage', async function() {
assert.ok(storage1 instanceof LocalStorage);
assert.ok(storage2 instanceof LocalStorage);
assert.ok(storage1 != storage2);
});
it("should create the default instance of LocalStorage sync and use it", function(done) {
nodePersist.initSync({
dir: randDir()
});
assert.ok(nodePersist.defaultInstance instanceof LocalStorage);
nodePersist.setItemSync("item8877", "hello");
assert.equal(nodePersist.getItemSync("item8877"), 'hello', "write/read didn't work");
done();
});
it('should use the 2 previous dirs and init correctly', async function() {
await storage11.init();
assert.equal(await storage11.getItem('s1'), 1111, `write/read didn't work`);
await storage22.init();
let value = await storage2.getItem('s2');
assert.deepEqual(value, {a: 1}, `write/read didn't work`);
});
it("should create a default instance", function(done) {
var dir = randDir();
nodePersist.init({dir: dir}).then(function(options) {
assert.equal(options.dir, dir, "Options don't match");
done();
});
});
});
it('should create the default instance of LocalStorage sync and use it', async function() {
await nodePersist.init({dir: randDir()});
assert.ok(nodePersist.defaultInstance instanceof LocalStorage);
await nodePersist.setItem('item8877', 'hello');
assert.equal(await nodePersist.getItem('item8877'), 'hello', `write/read didn't work`);
});
describe("synchronous operations", function() {
var options = {
dir: randDir()
};
var storage = nodePersist.create();
it('should create a default instance', async function() {
let dir = randDir();
let options = await nodePersist.init({dir: dir});
assert.equal(options.dir, dir, `Options don't match`);
});
});
var items = {
"item1": 1,
"item2": 2
};
describe('operations', function() {
let options = {
dir: randDir(),
// logging: true
};
let storage = nodePersist.create();
describe("general items operations", function() {
let items = {
'item1': 1,
'item2': 2
};
it("should initSync()", function(done) {
storage.initSync(options);
assert.equal(storage.options.dir, options.dir);
assert.ok(fs.existsSync(options.dir));
done();
});
describe('general items operations', function() {
it('should init()', async function() {
await storage.init(options);
assert.equal(storage.options.dir, options.dir);
assert.ok(fs.existsSync(options.dir));
});
it("should setItemSync()", function(done) {
storage.setItemSync("item1", items.item1);
assert.equal(storage.getItemSync("item1"), items.item1);
done();
});
it('should setItem()', async function() {
await storage.setItem('item1', items.item1);
assert.equal(await storage.getItem('item1'), items.item1);
});
it("should getItemSync()", function(done) {
assert.equal(storage.getItemSync("item1"), items.item1);
done();
});
it("should removeItemSync()", function(done) {
storage.removeItemSync("item1");
assert.equal(storage.getItemSync("item1"), undefined);
done();
});
});
it('should getItem()', async function() {
let value = await storage.getItem('item1');
assert.equal(value, items.item1);
});
describe("general sync operations", function() {
beforeEach(function(done) {
storage.setItemSync("item1", items.item1);
done();
});
afterEach(function(done) {
storage.clearSync();
done();
});
it('should getRawDatum()', async function() {
let value = await storage.getRawDatum('item1');
assert.equal(value, JSON.stringify({key: 'item1', value: items.item1}));
});
it("should clearSync()", function(done) {
storage.clearSync();
assert.equal(storage.getItemSync("item1"), undefined);
done();
});
it('should removeItem()', async function() {
await storage.removeItem('item1');
assert.equal(await storage.getItem('item1'), undefined);
});
});
it("should return return all values()", function(done) {
assert.deepEqual(storage.values(), [items.item1]);
done();
});
describe('general global operations', function() {
beforeEach(async function() {
await storage.init(options);
await storage.setItem('item1', items.item1);
await storage.setItem('item2', items.item2);
});
it("should return return all valuesWithKeyMatch()", function(done) {
storage.setItemSync("item2", items.item2);
storage.setItemSync("item11", items.item1);
assert.deepEqual(storage.valuesWithKeyMatch('item1'), [items.item1, items.item1]);
storage.removeItemSync("item2");
done();
});
afterEach(async function() {
await storage.clear();
});
it("should return all keys()", function(done) {
assert.deepEqual(storage.keys(), ["item1"]);
done();
});
it('should keys()', async function() {
assert.equal((await storage.keys()).length, 2);
});
it("should iterate over each key/value pair using forEach()", function(done) {
var hash = {};
storage.forEach(function(key, value) {
hash[key] = value;
});
assert.deepEqual({item1: 1}, hash);
done();
});
});
});
it('should length()', async function() {
assert.equal((await storage.length()), 2);
});
describe("asynchronous operations", function() {
var options = {
dir: randDir()
};
var storage = nodePersist.create();
it('should values()', async function() {
assert.equal((await storage.values()).length, 2);
});
var items = {
"item1": 1,
"item2": 2
};
it('should clear()', async function() {
await storage.clear();
assert.equal(await storage.getItem('item1'), undefined);
assert.equal(await storage.getItem('item2'), undefined);
});
});
});
describe("general items operations", function() {
describe('interval and ttl ', function() {
this.timeout(5000); // increase the default mocha test timeout.
it("should init().then()", function(done) {
storage.init(options).then(function() {
assert.equal(storage.options.dir, options.dir);
assert.ok(fs.existsSync(options.dir));
done();
});
});
it('should respect expired ttl and delete the items', async function() {
let storage = nodePersist.create();
await storage.init({
dir: randDir(),
ttl: 1000 // 1 second
});
await storage.setItem('item1', 1);
it("should init(callback)", function(done) {
storage.init(options, function(err) {
if (err) throw err;
// wait 2 seconds, then try to read the file, should be undefined.
await new Promise((resolve, reject) => {
setTimeout(async function() {
try {
let value = await storage.getItem('item1');
assert.equal(value, undefined);
resolve();
} catch (e) {
reject(e);
}
}, 2000);
});
});
assert.equal(storage.options.dir, options.dir);
assert.ok(fs.existsSync(options.dir));
done();
});
});
it('should respect an expired different ttl per setItem and delete the items', async function() {
this.timeout(10000);
let storage = nodePersist.create();
it("should setItem().then()", function(done) {
storage.setItem("item1", items.item1).then(function() {
assert.equal(storage.getItemSync("item1"), items.item1);
done();
});
});
await storage.init({
dir: randDir(),
ttl: 1000 // 1 second,
});
it("should setItem(.., callback)", function(done) {
storage.setItem("item2", items.item2, function(err) {
if (err) throw err;
await storage.setItem('item1', 1, {ttl: 5000});
assert.equal(storage.getItemSync("item2"), items.item2);
done();
});
});
// wait 2 seconds, then try to read the item1 file, should still be there because we asked this one to live for 5 seconds, despite the default 1 second ttl
await new Promise((resolve, reject) => {
setTimeout(async function() {
try {
let value = await storage.getItem('item1');
assert.equal(value, 1);
resolve();
} catch (e) {
reject(e);
}
}, 2000);
});
it("should getItem().then() from cache", function(done) {
storage.getItem("item1").then(function(value) {
assert.equal(value, items.item1);
done();
})
});
// wait 5.5 seconds, then try to read the item1 file, should be undefined
await new Promise((resolve, reject) => {
setTimeout(async function() {
try {
let value = await storage.getItem('item1');
assert.equal(value, undefined);
resolve();
} catch (e) {
reject(e);
}
}, 5500);
});
});
it("should getItem(.., callback)", function(done) {
storage.getItem("item2", function(err, value) {
if (err) throw err;
it('should automatically delete expired items', async function() {
this.timeout(10000);
assert.equal(value, items.item2);
done();
});
});
let storage = nodePersist.create();
await storage.init({dir: randDir(), expiredInterval: 3000});
it("should removeItem().then()", function(done) {
storage.removeItem("item1").then(function() {
assert.equal(storage.getItemSync("item1"), undefined);
done();
});
});
storage.setItem('item1', 1, {ttl: 5000});
it("should removeItem(..., callback)", function(done) {
storage.removeItem("item2", function(err) {
if (err) throw err;
// wait 8 seconds, then check the keys, should be empty, item1 should've been deleted automatically based on the expiredInterval
await new Promise((resolve, reject) => {
setTimeout(async function() {
try {
let length = await storage.length();
assert.equal(length, 0);
resolve();
} catch (e) {
reject(e);
}
}, 7000);
});
});
});
assert.equal(storage.getItemSync("item2"), undefined);
done();
});
});
});
describe('Parsing errors', function() {
it('should throw an error because of an invalid file in the storage dir', async function () {
this.timeout(5000);
let dir = randDir();
let storage = nodePersist.create();
describe("general sync operations", function() {
beforeEach(function(done) {
storage.setItemSync("item1", items.item1);
storage.setItemSync("item2", items.item2);
done();
});
afterEach(function(done) {
storage.clearSync();
done();
});
// make sure the dir is there, and write a random file in there
mkdirp.sync(dir);
fs.writeFileSync(dir + '/foo.bar', 'nothing that makes sense');
it("should clear().then()", function(done) {
storage.clear().then(function() {
assert.equal(storage.getItemSync("item1"), undefined);
assert.equal(storage.getItemSync("item2"), undefined);
done();
});
});
try {
await storage.init({
dir: dir
});
} catch (e) {
assert.equal(true, /^\[node-persist]\[readFile]*does not look like a valid storage file/.test(e.message));
}
});
it('should NOT throw an error because of an invalid file in the storage dir, because forgiveParseErrors=true', async function () {
this.timeout(5000);
let dir = randDir();
let storage = nodePersist.create();
it("should clear(callback)", function(done) {
storage.clear(function(err) {
if (err) throw err;
// make sure the dir is there, and write a random file in there
mkdirp.sync(dir);
fs.writeFileSync(dir + '/foo.bar', 'nothing that makes sense');
assert.equal(storage.getItemSync("item1"), undefined);
assert.equal(storage.getItemSync("item2"), undefined);
done();
});
});
});
});
describe("interval and ttl ", function() {
this.timeout(5000); // increase the default mocha test timeout.
it("should respect expired ttl and delete the items", function(done) {
var storage = nodePersist.create();
storage.initSync({
dir: randDir(),
ttl: 1000 // 1 second
});
storage.setItemSync("item1", 1);
// wait 2 seconds, then try to read the file, should be undefined.
setTimeout(function() {
var value = storage.getItemSync("item1");
assert.equal(value, undefined);
done();
}, 2000);
});
it("should respect an expired different ttl per setItem and delete the items", function(done) {
this.timeout(10000);
var storage = nodePersist.create();
storage.initSync({
dir: randDir(),
ttl: 1000 // 1 second,
});
storage.setItemSync("item1", 1, {ttl: 5000});
// wait 2 seconds, then try to read the item1 file, should still be there because we asked this one to live for 5 seconds, despite the default 1 second ttl
setTimeout(function() {
var value = storage.getItemSync("item1");
assert.equal(value, 1);
}, 2000);
// wait 5.5 seconds, then try to read the item1 file, should be undefined
setTimeout(function() {
var value = storage.getItemSync("item1");
assert.equal(value, undefined);
// call done only once here, since it's 3 seconds after
done();
}, 5500);
});
it("should automatically delete expired items", function(done) {
this.timeout(10000);
var storage = nodePersist.create();
storage.initSync({
dir: randDir(),
expiredInterval: 3000
});
storage.setItemSync("item1", 1, {ttl: 5000});
// wait 8 seconds, then check the keys, should be empty, item1 should've been deleted automatically based on the expiredInterval
setTimeout(function() {
var value = storage.keys();
assert.equal(value.length, 0);
done();
}, 7000);
});
it("don't persist to disk immediately, but rather on a timely interval", function(done) {
var storage = nodePersist.create();
storage.init({
dir: randDir(),
interval: 2000 // persist to disk every 2 seconds
}).then(function() {
var startTime = +new Date();
storage.setItem("item999", 1).then(function () {
// should resolve immediately but should not create the storate file immediately
assert.notEqual(true, fs.existsSync(storage.options.dir + "/" + storage.md5("item999")));
setTimeout(function() {
// 2 seconds later, that file should be there
var endTime = +new Date();
assert.approximately(endTime, startTime, 2500, "within 2.5s or so");
assert.equal(true, fs.existsSync(storage.options.dir + "/" + storage.md5("item999")));
done();
}, 2000);
});
// check if the item1 file exists immediately, it shouldnt
assert.notEqual(true, fs.existsSync(storage.options.dir + "/" + storage.md5("item999")));
});
});
});
describe("Parsing errors", function() {
it("should throw an error (sync) because of an invalid file in the storage dir", function (done) {
this.timeout(5000);
var dir = randDir();
var storage = nodePersist.create();
// make sure the dir is there, and write a random file in there
mkdirp.sync(dir);
fs.writeFileSync(dir + '/foo.bar', 'nothing that makes sense');
try {
storage.initSync({
dir: dir
});
} catch (e) {
assert.equal(true, /^\[PARSE-ERROR\].*does not look like a valid storage file/.test(e.message));
done();
}
});
it("should NOT throw an error (sync) because of an invalid file in the storage dir, because forgiveParseErrors=true", function (done) {
this.timeout(5000);
var dir = randDir();
var storage = nodePersist.create();
// make sure the dir is there, and write a random file in there
mkdirp.sync(dir);
fs.writeFileSync(dir + '/foo.bar', 'nothing that makes sense');
storage.initSync({
dir: dir,
forgiveParseErrors: true
});
assert.equal(storage.options.dir, dir, "options.dir don't match");
done();
});
it("should throw an error (async) because of an invalid file in the storage dir", function (done) {
this.timeout(5000);
var dir = randDir();
var storage = nodePersist.create();
// make sure the dir is there, and write a random file in there
mkdirp.sync(dir);
fs.writeFileSync(dir + '/foo.bar', 'nothing that makes sense');
storage.init({
dir: dir
}).catch(function(err) {
assert.equal(true, /^\[PARSE-ERROR\].*does not look like a valid storage file/.test(err.message));
done();
});
});
it("should NOT throw an error (async) because of an invalid file in the storage dir, because forgiveParseErrors=true", function (done) {
this.timeout(5000);
var dir = randDir();
var storage = nodePersist.create();
// make sure the dir is there, and write a random file in there
mkdirp.sync(dir);
fs.writeFileSync(dir + '/foo.bar', 'nothing that makes sense');
storage.init({
dir: dir,
forgiveParseErrors: true
}).then(function(options) {
assert.equal(options.dir, dir, "options.dir don't match");
done();
}).catch(function(err) {
throw err;
});
});
});
after(function(done) {
rmdir(TEST_BASE_DIR, done);
});
await storage.init({
dir: dir,
forgiveParseErrors: true
});
assert.equal(storage.options.dir, dir, `options.dir don't match`);
});
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc