flash-store
Version:
FlashStore is a Key-Value persistent storage with easy to use ES6 Map-like API(both Async and Sync support), powered by LevelDB and TypeScript.
604 lines (593 loc) • 25.6 kB
JavaScript
/* flash-store version 1.0.6 */
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('brolog'), require('path'), require('app-root-path'), require('rimraf'), require('level'), require('cuid'), require('state-switch')) :
typeof define === 'function' && define.amd ? define(['exports', 'brolog', 'path', 'app-root-path', 'rimraf', 'level', 'cuid', 'state-switch'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.window = {}, global.brolog, global.path, global.appRootPath, global.rimraf, global.level, global.cuid, global.stateSwitch));
}(this, (function (exports, brolog, path, appRootPath, rimraf, level, cuid, stateSwitch) { 'use strict';
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () {
return e[k];
}
});
}
});
}
n['default'] = e;
return Object.freeze(n);
}
var path__namespace = /*#__PURE__*/_interopNamespace(path);
var rimraf__default = /*#__PURE__*/_interopDefaultLegacy(rimraf);
var level__default = /*#__PURE__*/_interopDefaultLegacy(level);
var cuid__default = /*#__PURE__*/_interopDefaultLegacy(cuid);
/**
* This file was auto generated from scripts/generate-version.sh
*/
const VERSION = '1.0.6';
/// <reference path="./typings.d.ts" />
// https://github.com/Microsoft/TypeScript/issues/14151#issuecomment-280812617
if (!Symbol.asyncIterator) {
Symbol.asyncIterator = Symbol.for('Symbol.asyncIterator');
}
var __awaiter$1 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __asyncValues$1 = (undefined && undefined.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
var __await = (undefined && undefined.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); };
var __asyncGenerator = (undefined && undefined.__asyncGenerator) || function (thisArg, _arguments, generator) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
function fulfill(value) { resume("next", value); }
function reject(value) { resume("throw", value); }
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
};
var __asyncDelegator = (undefined && undefined.__asyncDelegator) || function (o) {
var i, p;
return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i;
function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; }
};
class FlashStore {
/**
* FlashStore is a Key-Value database tool and makes using leveldb more easy for Node.js
*
* Creates an instance of FlashStore.
* @param {string} [workdir=path.join(appRoot, 'flash-store.workdir')]
* @example
* import { FlashStore } from 'flash-store'
* const flashStore = new FlashStore('flashstore.workdir')
*/
constructor(workdir = path__namespace.join(appRootPath.path, '.flash-store')) {
this.workdir = workdir;
brolog.log.verbose('FlashStore', 'constructor(%s)', workdir);
// https://twitter.com/juliangruber/status/908688876381892608
// const encoded = encoding<K, V>(
// leveldown(workdir),
// {
// // FIXME: issue #2
// valueEncoding: 'json',
// },
// )
this.levelDb = level__default['default'](workdir);
// console.log((this.levelDb as any)._db.codec)
this.levelDb.setMaxListeners(17); // default is Infinity
}
version() {
return VERSION;
}
/**
* Put data in database
*
* @param {K} key
* @param {V} value
* @returns {Promise<void>}
* @example
* await flashStore.put(1, 1)
*/
put(key, value) {
return __awaiter$1(this, void 0, void 0, function* () {
brolog.log.warn('FlashStore', '`put()` DEPRECATED. use `set()` instead.');
yield this.set(key, value);
});
}
set(key, value) {
return __awaiter$1(this, void 0, void 0, function* () {
brolog.log.verbose('FlashStore', 'set(%s, %s) value type: %s', key, value, typeof value);
// FIXME: issue #2
yield this.levelDb.put(key, JSON.stringify(value));
return this;
});
}
/**
* Get value from database by key
*
* @param {K} key
* @returns {(Promise<V | null>)}
* @example
* console.log(await flashStore.get(1))
*/
get(key) {
return __awaiter$1(this, void 0, void 0, function* () {
brolog.log.verbose('FlashStore', 'get(%s)', key);
try {
// FIXME: issue #2
return JSON.parse(yield this.levelDb.get(key));
}
catch (e) {
if (/^NotFoundError/.test(e)) {
return undefined;
}
throw e;
}
});
}
/**
* Del data by key
*
* @param {K} key
* @returns {Promise<void>}
* @example
* await flashStore.del(1)
*/
del(key) {
return __awaiter$1(this, void 0, void 0, function* () {
brolog.log.verbose('FlashStore', '`del()` DEPRECATED. use `delete()` instead');
yield this.delete(key);
});
}
delete(key) {
return __awaiter$1(this, void 0, void 0, function* () {
brolog.log.verbose('FlashStore', 'delete(%s)', key);
yield this.levelDb.del(key);
// TODO: `del` returns `true` or `false`
return true;
});
}
/**
* @typedef IteratorOptions
*
* @property { any } gt - Matches values that are greater than a specified value
* @property { any } gte - Matches values that are greater than or equal to a specified value.
* @property { any } lt - Matches values that are less than a specified value.
* @property { any } lte - Matches values that are less than or equal to a specified value.
* @property { boolean } reverse - Reverse the result set
* @property { number } limit - Limits the number in the result set.
* @property { any } prefix - Make the same prefix key get together.
*/
/**
* Find keys by IteratorOptions
*
* @param {IteratorOptions} [options={}]
* @returns {AsyncIterableIterator<K>}
* @example
* const flashStore = new FlashStore('flashstore.workdir')
* for await(const key of flashStore.keys({gte: 1})) {
* console.log(key)
* }
*/
keys(options = {}) {
return __asyncGenerator(this, arguments, function* keys_1() {
var e_1, _a;
brolog.log.verbose('FlashStore', 'keys()');
// options = Object.assign(options, {
// keys : true,
// values : false,
// })
if (options.prefix) {
if (options.gte || options.lte) {
throw new Error('can not specify `prefix` with `gte`/`lte` together.');
}
options.gte = options.prefix;
options.lte = options.prefix + '\xff';
}
try {
for (var _b = __asyncValues$1(this.entries(options)), _c; _c = yield __await(_b.next()), !_c.done;) {
const [key] = _c.value;
yield yield __await(key);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) yield __await(_a.call(_b));
}
finally { if (e_1) throw e_1.error; }
}
});
}
/**
* Find all values
*
* @returns {AsyncIterableIterator<V>}
* @example
* const flashStore = new FlashStore('flashstore.workdir')
* for await(const value of flashStore.values()) {
* console.log(value)
* }
*/
values(options = {}) {
return __asyncGenerator(this, arguments, function* values_1() {
var e_2, _a;
brolog.log.verbose('FlashStore', 'values()');
try {
// options = Object.assign(options, {
// keys : false,
// values : true,
// })
for (var _b = __asyncValues$1(this.entries(options)), _c; _c = yield __await(_b.next()), !_c.done;) {
const [, value] = _c.value;
yield yield __await(value);
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) yield __await(_a.call(_b));
}
finally { if (e_2) throw e_2.error; }
}
});
}
/**
* Get the counts of the database
* @deprecated use property `size` instead
* @returns {Promise<number>}
* @example
* const count = await flashStore.count()
* console.log(`database count: ${count}`)
*/
count() {
return __awaiter$1(this, void 0, void 0, function* () {
brolog.log.warn('FlashStore', '`count()` DEPRECATED. use `size()` instead.');
const size = yield this.size;
return size;
});
}
get size() {
brolog.log.verbose('FlashStore', 'size()');
/* eslint no-async-promise-executor: 0 */
// TODO: is there a better way to count all items from the db?
return new Promise((resolve, reject) => __awaiter$1(this, void 0, void 0, function* () {
var e_3, _a;
try {
let count = 0;
try {
for (var _b = __asyncValues$1(this), _c; _c = yield _b.next(), !_c.done;) {
const _ = _c.value;
count++;
}
}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) yield _a.call(_b);
}
finally { if (e_3) throw e_3.error; }
}
resolve(count);
}
catch (e) {
reject(e);
}
}));
}
/**
* FIXME: use better way to do this
*/
has(key) {
return __awaiter$1(this, void 0, void 0, function* () {
const val = yield this.get(key);
return !!val;
});
}
/**
* TODO: use better way to do this with leveldb
*/
clear() {
var e_4, _a;
return __awaiter$1(this, void 0, void 0, function* () {
try {
for (var _b = __asyncValues$1(this.keys()), _c; _c = yield _b.next(), !_c.done;) {
const key = _c.value;
yield this.delete(key);
}
}
catch (e_4_1) { e_4 = { error: e_4_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) yield _a.call(_b);
}
finally { if (e_4) throw e_4.error; }
}
});
}
get [Symbol.toStringTag]() {
return Promise.resolve('FlashStore');
}
[Symbol.iterator]() {
brolog.log.verbose('FlashStore', '[Symbol.iterator]()');
/**
* Huan(202108): what is this???
* does it equals to `entries()`?
*/
return this.entries();
}
/**
* @private
*/
entries(options) {
return __asyncGenerator(this, arguments, function* entries_1() {
brolog.log.verbose('FlashStore', '*entries(%s)', JSON.stringify(options));
const iterator = this.levelDb.db.iterator(options);
while (true) {
const pair = yield __await(new Promise((resolve, reject) => {
iterator.next(function (err, key, val) {
if (err) {
reject(err);
}
if (!key && !val) {
return resolve(null);
}
if (val) {
// FIXME: issue #2
val = JSON.parse(val);
}
return resolve([key, val]);
});
}));
if (!pair) {
break;
}
yield yield __await(pair);
}
});
}
[Symbol.asyncIterator]() {
return __asyncGenerator(this, arguments, function* _a() {
brolog.log.verbose('FlashStore', '*[Symbol.asyncIterator]()');
yield __await(yield* __asyncDelegator(__asyncValues$1(this.entries())));
});
}
/**
* @private
*/
streamAsyncIterator() {
return __asyncGenerator(this, arguments, function* streamAsyncIterator_1() {
brolog.log.warn('FlashStore', 'DEPRECATED *[Symbol.asyncIterator]()');
const readStream = this.levelDb.createReadStream();
const endPromise = new Promise((resolve, reject) => {
readStream
.once('end', () => resolve(false))
.once('error', reject);
});
let pair;
do {
const dataPromise = new Promise(resolve => {
readStream.once('data', (data) => resolve([data.key, data.value]));
});
pair = yield __await(Promise.race([
dataPromise,
endPromise,
]));
if (pair) {
yield yield __await(pair);
}
} while (pair);
});
}
forEach(callbackfn, thisArg) {
var e_5, _a;
return __awaiter$1(this, void 0, void 0, function* () {
brolog.log.verbose('FlashStore', 'forEach()');
try {
for (var _b = __asyncValues$1(this), _c; _c = yield _b.next(), !_c.done;) {
const [key, value] = _c.value;
callbackfn.call(thisArg, value, key, this);
}
}
catch (e_5_1) { e_5 = { error: e_5_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) yield _a.call(_b);
}
finally { if (e_5) throw e_5.error; }
}
});
}
close() {
return __awaiter$1(this, void 0, void 0, function* () {
brolog.log.verbose('FlashStore', 'close()');
yield this.levelDb.close();
});
}
/**
* Destroy the database
*
* @returns {Promise<void>}
*/
destroy() {
return __awaiter$1(this, void 0, void 0, function* () {
brolog.log.verbose('FlashStore', 'destroy()');
yield this.levelDb.close();
yield new Promise(resolve => rimraf__default['default'](this.workdir, resolve));
});
}
}
FlashStore.VERSION = VERSION;
var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __asyncValues = (undefined && undefined.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
class FlashStoreSync {
constructor(workdir) {
this.workdir = workdir;
brolog.log.verbose('CacheStore', 'constructor(%s)', workdir);
workdir = workdir || '.flash-store-sync';
this.asyncBusyDict = {};
this.asyncBusyState = new stateSwitch.StateSwitch('Busy:' + workdir.split('/').pop(), // get the latest folder name
brolog.log);
this.cacheMap = new Map();
this.flashStore = new FlashStore(workdir);
this.asyncBusyAdd(this.loadStoreToCache());
}
loadStoreToCache() {
var e_1, _a;
return __awaiter(this, void 0, void 0, function* () {
this.cacheMap.clear();
try {
for (var _b = __asyncValues(this.flashStore), _c; _c = yield _b.next(), !_c.done;) {
const [key, val] = _c.value;
this.cacheMap.set(key, val);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) yield _a.call(_b);
}
finally { if (e_1) throw e_1.error; }
}
});
}
asyncBusyAdd(task) {
this.asyncBusyState.on(true);
const id = cuid__default['default']();
this.asyncBusyDict[id] = task.finally(() => {
delete this.asyncBusyDict[id];
if (Object.keys(this.asyncBusyDict).length <= 0) {
this.asyncBusyState.off(true);
}
});
}
version() {
return this.flashStore.version();
}
/**
*
* Async methods:
*
*/
close() {
return __awaiter(this, void 0, void 0, function* () {
yield this.ready();
yield this.flashStore.close();
});
}
destroy() {
return __awaiter(this, void 0, void 0, function* () {
this.clear();
// add destroy task at the end of the event loop
this.asyncBusyState.ready('off')
.then(() => this.flashStore.destroy()).catch(console.error);
});
}
ready() {
return __awaiter(this, void 0, void 0, function* () {
yield this.asyncBusyState.ready('off');
});
}
/*******************************************************
*
*
* The following methods is all for ES6 Map Interface
*
*
*******************************************************/
get size() {
return this.cacheMap.size;
}
get [Symbol.toStringTag]() {
return 'FlashStoreSync';
}
[Symbol.iterator]() {
return this.cacheMap[Symbol.iterator]();
}
entries() {
return this.cacheMap.entries();
}
keys() {
return this.cacheMap.keys();
}
values() {
return this.cacheMap.values();
}
clear() {
this.asyncBusyAdd(this.flashStore.clear());
return this.cacheMap.clear();
}
delete(key) {
this.asyncBusyAdd(this.flashStore.delete(key));
return this.cacheMap.delete(key);
}
/**
* Do not mutate the key/value in the forEach loop!
*/
forEach(callbackfn, thisArg) {
/**
* 1. no need to call flashStore
* 2. callbackfn should not mutate the data, or the data will be lost sync between cache & store
*/
return this.cacheMap.forEach(callbackfn, thisArg);
}
get(key) {
return this.cacheMap.get(key);
}
has(key) {
return this.cacheMap.has(key);
}
set(key, value) {
this.asyncBusyAdd(this.flashStore.set(key, value));
this.cacheMap.set(key, value);
return this;
}
}
FlashStoreSync.VERSION = FlashStore.VERSION;
Object.defineProperty(exports, 'log', {
enumerable: true,
get: function () {
return brolog.log;
}
});
exports.FlashStore = FlashStore;
exports.FlashStoreSync = FlashStoreSync;
exports.VERSION = VERSION;
exports['default'] = FlashStore;
Object.defineProperty(exports, '__esModule', { value: true });
})));
/* https://github.com/huan */
//# sourceMappingURL=flash-store.es6.umd.js.map