@alinex/datastore
Version:
Read, work and write data structures from and to differents locations and formats.
268 lines • 9.71 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.compressions = exports.formats = exports.DataStore = void 0;
const path_1 = require("path");
const url_1 = require("url");
const objectPath = require("object-path");
const async_1 = require("@alinex/async");
const data = require("@alinex/data");
const debug_1 = require("debug");
const util = require("util");
const protocol_1 = require("./protocol");
const compressor = require("./compression");
const formatter = require("./format");
const debug = debug_1.default("datastore");
const debugDetails = debug_1.default("datastore:details");
let debuglog = util.debuglog;
if (debugDetails.enabled) {
process.env.NODE_DEBUG = process.env.NODE_DEBUG ? `${process.env.NODE_DEBUG},request` : "request";
util.debuglog = (set) => {
debugDetails(`See more using NODE_DEBUG=${set}`);
return debuglog(set);
};
}
function parseUri(uri) {
if (!uri.match(/^[a-z]+:/))
uri = `file:${path_1.resolve(uri)}`;
let parsed = new url_1.URL(uri);
return parsed;
}
class DataStore {
constructor(...input) {
this.data = {};
this.options = {};
this._source = [];
this._map = {};
this._load = undefined;
this.changed = false;
if (!input)
return;
this._source = input;
}
static async load(...input) {
const ds = new DataStore();
return ds.load(...input).then(() => Promise.resolve(ds));
}
static async url(source, options) {
const ds = new DataStore();
return ds.load({ source, options }).then(() => Promise.resolve(ds));
}
static async data(data, source) {
const ds = new DataStore();
ds.data = data;
if (source && !source.startsWith('preset:'))
source = `preset:${source}`;
ds.source = source;
return Promise.resolve(ds);
}
async load(...input) {
if (!input.length && this._load)
return Promise.resolve(this.data);
if (input.length)
this._source = input;
debug("Loading: %o", this._source);
if (this._source.filter(e => e.source && !e.source.startsWith('preset:')).length)
await async_1.default.map(this._source, this.multiload);
if (this._source.length === 1)
this.data = data.clone(this._source[0].data);
else if (this._source.length) {
const res = data.clone(data.merge(...this._source));
this.data = res.data;
this._map = res.map;
}
debug("Result: %o", this.data);
this.changed = false;
return Promise.resolve(this.data);
}
async multiload(entry, index, list) {
if (!entry.source)
return;
const source = parseUri(entry.source);
const options = Object.assign({}, entry.options);
return (protocol_1.default
.load(source, options)
.then(([buffer, meta]) => {
entry.meta = meta;
return compressor.uncompress(source, buffer, options);
})
.then(buffer => {
entry.meta.raw = buffer;
return formatter.parse(source, buffer, options);
})
.then(data => {
entry.data = data;
})
.catch(e => {
debug("ERROR: %s", entry.source, e.message || e);
if (list && list.length == 1)
throw e;
}));
}
async reload(time = 0) {
if (!this._source.length)
throw new Error("No source defined to reload DataStore from.");
if (!this._source.filter(e => e.source && !e.source.startsWith('preset:')).length)
throw Error("The datastore is not associated with a path to load from.");
if (!this.changed)
return false;
let cache = new Date();
cache.setSeconds(cache.getSeconds() + time);
if (this._load && cache < this._load)
return false;
const list = await async_1.default.map(this._source, e => {
if (!e.source)
return Promise.resolve(undefined);
return protocol_1.default.modified(parseUri(e.source));
});
const change = list.reduce((prev, cur) => {
if (!cur)
return prev;
if (!prev)
return prev;
if (prev < cur)
return cur;
return prev;
});
if (this._load && change && change < this._load)
return false;
await this.load();
return true;
}
async save(output) {
if (output)
this.source = [output];
if (this._source.length > 1)
throw new Error("Storing in multiple source list not possible, yet.");
if (!this._source.length || !this._source[0].source)
throw new Error("No source defined to save DataStore to.");
const source = parseUri(this._source[0].source);
const options = Object.assign({}, this._source[0].options);
return formatter
.format(source, this.data, options)
.then(buffer => compressor.compress(source, buffer, options))
.then(buffer => protocol_1.default.save(source, buffer, options))
.then(() => {
this._load = new Date();
this.changed = false;
return true;
});
}
async parse(uri, buffer, options) {
if (uri)
this.source = uri;
if (options)
this._source[0].options = Object.assign(this._source[0].options, options);
if (typeof buffer == "string")
buffer = Buffer.from(buffer);
if (!this._source.length)
this._source = [{ source: "file:/dev/null" }];
const source = parseUri(this._source[0].source || "file:/dev/null");
this.data = await formatter.parse(source, buffer, this.options);
return this.data;
}
async format(uri, options) {
let c_uri = parseUri((this._source.length && this._source[0].source) || "file:/dev/null");
if (uri)
c_uri = parseUri(uri);
let c_options = this.options;
if (options)
c_options = Object.assign({}, this.options, options);
return await formatter.format(c_uri, this.data, c_options);
}
get source() {
if (this._source.length === 1)
return this._source[0].source;
if (this._source.length > 1)
return this._source;
return undefined;
}
set source(data) {
if (data) {
delete this._load;
if (typeof data === "string")
this._source = [{ source: data }];
else if (Array.isArray(data) && data.length)
this.source = data;
else
throw new Error("Need a DataSource list or source string for DataStore.");
}
else {
this._source = [];
}
}
get meta() {
if (this._source.length === 1)
return this._source[0].meta;
if (this._source.length > 1)
return this._source.map(e => e.meta);
return undefined;
}
get map() {
return this._map;
}
has(command) {
const res = data.filter(this.data, command || "");
return res ? true : false;
}
get(command, fallback) {
const res = data.filter(this.data, command || "");
return typeof res === undefined ? fallback : res;
}
filter(command, fallback) {
const res = data.filter(this.data, command || "");
const ds = new DataStore();
ds.data = typeof res === undefined ? fallback : res;
return ds;
}
set(path, value, doNotReplace) {
this.changed = true;
if (path === "" || (Array.isArray(path) && path.length == 0)) {
if (this.data === undefined)
return;
const old = this.data;
this.data = value;
return old;
}
let p = typeof path === "number" ? path : path.split(/\[([^\]]*)\]|\./g).filter(e => e);
return objectPath.set(this.data, p, value, doNotReplace);
}
insert(path, value, pos) {
this.changed = true;
let p = typeof path === "number" ? path : path.split(/\[([^\]]*)\]|\./g).filter(e => e);
objectPath.insert(this.data, p, value, pos);
}
push(path, ...values) {
this.changed = true;
let p = typeof path === "number" ? path : path.split(/\[([^\]]*)\]|\./g).filter(e => e);
values.unshift(p);
values.unshift(this.data);
objectPath.push.apply(objectPath, values);
}
empty(path) {
this.changed = true;
if (path === "" || (Array.isArray(path) && path.length == 0)) {
const old = this.data;
this.data = {};
return old;
}
let p = typeof path === "number" ? path : path.split(/\[([^\]]*)\]|\./g).filter(e => e);
return objectPath.empty(this.data, p);
}
delete(path) {
this.changed = true;
if (path === "" || (Array.isArray(path) && path.length == 0)) {
const old = this.data;
delete this.data;
return old;
}
let p = typeof path === "number" ? path : path.split(/\[([^\]]*)\]|\./g).filter(e => e);
return objectPath.del(this.data, p);
}
}
exports.DataStore = DataStore;
const formats = formatter.formats;
exports.formats = formats;
const compressions = compressor.compressions;
exports.compressions = compressions;
exports.default = DataStore;
//# sourceMappingURL=index.js.map