@randajan/vault-kit
Version:
A tiny and universal data vault that behaves like a writable file system. Store, sync, forget. Works on client and server.
455 lines (446 loc) • 13.1 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to2, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to2, key) && key !== except)
__defProp(to2, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to2;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.js
var index_exports = {};
__export(index_exports, {
Vault: () => Vault,
default: () => index_default
});
module.exports = __toCommonJS(index_exports);
// src/tools.js
var is = (type, any) => typeof type === "string" ? typeof any === type : any instanceof type;
var to = (type, any, errorName, req = false) => {
if (is(type, any)) {
return any;
}
if (!req && (any == null || !errorName)) {
return;
}
throw new TypeError(`Expected ${errorName} to be a '${type}', got '${typeof any}'`);
};
var toFn = (any, errorName, req = false) => to("function", any, errorName, req);
var toObj = (any, errorName, req = false) => to("object", any, errorName, req);
var toBol = (any, errorName, req = false) => to("boolean", any, errorName, req);
var toStr = (any, errorName, req = false) => to("string", any, errorName, req);
var toNum = (any, errorName, req = false) => to("number", any, errorName, req);
var toRng = (any, min, max, errorName, req = false) => {
const num = toNum(any, errorName, req);
if (num == null) {
return;
}
if (num < min) {
throw new Error(`Expected ${errorName} to be greater than '${min}' got '${num}'`);
}
if (num > max) {
throw new Error(`Expected ${errorName} to be lesser than '${max}' got '${num}'`);
}
return num;
};
var timeout = (ms, msg = "Timeout") => new Promise((_, rej) => setTimeout((_2) => rej(new Error(msg)), ms));
var withTimeout = (fn, ms, msg = "Timeout") => {
if (!fn || !ms) {
return fn;
}
return (...args) => Promise.race([fn(...args), timeout(ms, msg)]);
};
// src/class/Cell.js
var Cell = class {
constructor(_vault) {
this.status = "init";
this._vault = _vault;
}
notDestroyed() {
if (this.status !== "destroyed") {
return true;
}
throw new Error("Was destroyed before");
}
isExpired() {
return this.expiresAt && this.expiresAt < Date.now();
}
onSet(persistent, before, ...a) {
const { _vault, status, data, error } = this;
const { ttl, handlers } = _vault;
if (persistent) {
delete this.expiresAt;
} else if (ttl > 0) {
this.expiresAt = Date.now() + ttl;
}
return handlers.run({ status, data, error, before }, ...a);
}
async fetchRemote(status, data, ...a) {
try {
const { push, pull } = this._vault.remote;
const prom = status === "push" ? push(data, ...a) : pull(...a);
return this.setReady(status, await prom, ...a);
} catch (err) {
throw this.setError(err);
}
}
setProm(status, data, ...a) {
const { data: d, status: s } = this;
this.status = status;
this.prom = this.fetchRemote(status, data, ...a);
delete this.error;
this.onSet(true, { data: d, status: s }, ...a);
return this.prom;
}
setError(err, ...a) {
const { data: d, status: s } = this;
this.status = "error";
this.error = err;
delete this.prom;
this.onSet(false, { data: d, status: s }, ...a);
return err;
}
async setReady(mode, data, ...a) {
const { _vault, data: d, status: s } = this;
const { unfold, trait, purge } = _vault;
let res = data;
if (unfold && (mode === "local" || mode === "push")) {
[data, res] = await unfold(data);
}
if (this.data != null) {
purge(this.data);
}
this.data = await trait(data, res);
this.status = "ready";
delete this.error;
delete this.prom;
this.onSet(false, { data: d, status: s }, ...a);
return res;
}
pick() {
return this.isExpired() ? this.reset("expired") : this;
}
async get(...a) {
this.notDestroyed();
const { _vault, status, data, prom } = this.pick();
const { remote } = _vault;
if (status === "ready") {
return data;
}
if (!remote) {
return;
}
if (status === "pull") {
return prom;
}
if (status === "push") {
await prom;
return this.data;
}
return this.setProm("pull", void 0, ...a);
}
async set(data, ...a) {
this.notDestroyed();
const { _vault, prom } = this;
const { remote, act } = _vault;
if (!remote) {
return this.setReady("local", await act(data, ...a), ...a);
}
const push = async (_) => this.setProm("push", await act(data, ...a), ...a);
return !prom ? push() : this.prom = this.prom.then(push);
}
reset(status, ...a) {
this.notDestroyed();
const { _vault, data: d, status: s } = this;
const { purge } = _vault;
try {
if (this.data != null) {
purge(this.data);
}
} catch (err) {
throw this.setError(err);
}
this.status = status;
delete this.data;
delete this.prom;
delete this.error;
delete this.expiresAt;
this.onSet(true, { status: s, data: d }, ...a);
return this;
}
};
// src/class/Cells.js
var Cells = class extends Map {
constructor(_vault) {
super();
this._vault = _vault;
}
pick(id) {
const c = super.get(id);
return c?.isExpired() ? this.reset("expired", id) : c;
}
async get(id, ...a) {
const { _vault } = this;
let c = this.pick(id);
if (!c) {
if (!_vault.remote) {
return;
}
super.set(id, c = new Cell(_vault));
}
return c.get(id, ...a);
}
ensure(id) {
let c = super.get(id);
if (!c) {
super.set(id, c = new Cell(this._vault));
}
return c;
}
async setReady(mode, data, id, ...a) {
return this.ensure(id).setReady(mode, data, id, ...a);
}
async set(data, id, ...a) {
return this.ensure(id).set(data, id, ...a);
}
reset(status, id, ...a) {
const c = super.get(id);
super.delete(id);
return c?.reset(status, id, ...a);
}
};
// src/class/Handlers.js
var Handlers = class {
constructor(emitter) {
this.list = [];
const run = (...args) => {
for (const fn of [...this.list]) {
fn(...args);
}
};
this.run = !emitter ? run : (...a) => emitter(run, ...a);
}
on(fn) {
fn = toFn(fn, "fn argument", true);
const { list } = this;
list.push(fn);
return () => {
const x = list.indexOf(fn);
if (x >= 0) {
list.splice(x, 1);
}
};
}
once(fn) {
let rem;
return rem = this.on((...a) => {
rem();
return fn(...a);
});
}
};
// src/class/VaultPrivate.js
var formatRemote = (remote, reqPreserveActions = false) => {
const m = "options.remote";
remote = toObj(remote, m);
if (!remote) {
return;
}
const timeout2 = remote.timeout = toRng(remote.timeout, 0, 2147483647, m + ".timeout") ?? 5e3;
remote.init = toFn(remote.init, m + ".init");
remote.push = withTimeout(toFn(remote.push, m + ".push"), timeout2);
remote.pull = withTimeout(toFn(remote.pull, m + ".pull", true), timeout2);
remote.preserveAction = toBol(remote.preserveAction, m + ".preserveAction", reqPreserveActions);
remote.destroy = toFn(remote.destroy, m + ".destroy");
return remote;
};
var formatUnfold = (fold, errorName, req = false) => {
const f = toFn(fold);
if (f) {
return f;
}
let prop = toStr(fold, errorName);
if (prop) {
return (r) => [r[prop], r];
}
if (req) {
return (r) => [r, r];
}
};
var formatActions = (actions, preserveAction) => {
if (!actions) {
return (req) => req;
}
return async (req, ...a) => {
const { action, params } = toObj(req, "request", true);
if (!action) {
throw new Error(`Action is required`);
}
const act = actions[action];
if (!act) {
throw new Error(`Action '${action}' is not defined`);
}
const data = await act(params, ...a);
return preserveAction ? { action, params: data } : data;
};
};
var VaultPrivate = class {
constructor(opt) {
opt = toObj(opt, "options") || {};
const actions = toObj(opt.actions, "options.actions");
const remote = this.remote = formatRemote(opt.remote, actions);
const hasMany = this.hasMany = toBol(opt.hasMany, "options.hasMany") || false;
this.readonly = remote && !remote.push || toBol(opt.readonly, "options.readonly") || false;
const ttl = this.ttl = toRng(opt.ttl, 0, 2147483647 * 2, "options.ttl") ?? 0;
this.act = formatActions(actions, remote?.preserveAction);
this.unfold = formatUnfold(opt.unfold, "options.unfold");
this.trait = toFn(opt.trait, "options.trait") || ((data) => data);
this.purge = toFn(opt.purge, "options.purge") || ((data) => data);
this.handlers = new Handlers(toFn(opt.emitter, "options.emitter"));
const store = this.store = hasMany ? new Cells(this) : new Cell(this);
if (remote?.init) {
this.initDestroy = remote.init((...a) => store.setReady("remote", ...a));
}
if (remote?.destroy) {
this.destroy = (_) => remote.destroy(this.initDestroy);
} else {
this.destroy = toFn(this.initDestroy, "remote.init return value") || (() => {
});
}
if (!ttl) {
return;
}
const cleanUp = !hasMany ? (_) => store.pick() : (_) => {
for (const id of store.keys()) {
store.pick(id);
}
};
setInterval(cleanUp, ttl / 2);
}
reset(status, ...a) {
const { store } = this;
store.reset(status, ...a);
}
resetAll(status, ...a) {
if (!this.hasMany) {
this.reset(status, ...a);
} else {
this.forEach((ctx, id) => this.reset(status, id, ...a));
}
}
};
// src/class/Vault.js
var _privates = /* @__PURE__ */ new WeakMap();
var Vault = class {
constructor(options = {}) {
const _p = new VaultPrivate(options);
const enumerable = true;
Object.defineProperty(this, "hasMany", { enumerable, value: _p.hasMany });
Object.defineProperty(this, "hasRemote", { enumerable, value: !!_p.remote });
const act = this.act.bind(this);
this.act = this.withActions(act, act);
_privates.set(this, _p);
}
getStatus(...a) {
return _privates.get(this).store.pick(...a)?.status || "init";
}
getData(...a) {
return _privates.get(this).store.pick(...a)?.data;
}
getError(...a) {
return _privates.get(this).store.pick(...a)?.error;
}
isStatus(statuses, ...a) {
const s = this.getStatus(...a);
return Array.isArray(statuses) ? statuses.includes(s) : s === statuses;
}
async get(...a) {
return _privates.get(this).store.get(...a);
}
async set(data, ...a) {
const { readonly, store } = _privates.get(this);
if (readonly) {
throw new Error(`Set is not allowed`);
}
return store.set(data, ...a);
}
async act(action, params, ...a) {
const { readonly, store } = _privates.get(this);
if (readonly) {
throw new Error(`Do is not allowed`);
}
return store.set({ action, params }, ...a);
}
reset(...a) {
_privates.get(this).reset("init", ...a);
return this;
}
resetAll(...a) {
_privates.get(this).resetAll("init", ...a);
return this;
}
destroy(...a) {
const _vault = _privates.get(this);
_vault.resetAll("destroyed", ...a);
_vault.destroy();
return this;
}
on(fn) {
return _privates.get(this).handlers.on(fn);
}
once(fn) {
return _privates.get(this).handlers.once(fn);
}
async has(...a) {
const data = await this.get(...a);
return data !== void 0;
}
forEach(exe) {
const { store, hasMany } = _privates.get(this);
if (!hasMany) {
const { status, data } = store.pick();
return exe({ status, data });
}
let proms;
for (const id of [...store.keys()]) {
const { status, data } = store.pick(id);
if (status === "init") {
continue;
}
const res = exe({ status, data }, id);
if (res instanceof Promise) {
(proms ??= []).push(res);
}
}
if (proms) {
return Promise.all(proms);
}
}
collect(collector, exe) {
const res = this.forEach((ctx, id) => exe(collector, ctx, id));
return res instanceof Promise ? res.then((_) => collector) : collector;
}
withActions(target, execute) {
const d = toFn(execute, "second argument") || this.act;
return new Proxy(target, {
get(t, prop, receiver) {
const val = Reflect.get(t, prop, receiver);
if (val !== void 0) {
return val;
}
return (params, ...a) => d(prop, params, ...a);
}
});
}
};
// src/index.js
var index_default = (options = {}) => new Vault(options);
//# sourceMappingURL=index.js.map