key-value-file-system
Version:
Turn any key-value store into a file system for hierarchical object storage
144 lines (143 loc) • 5.51 kB
JavaScript
"use strict";
var __awaiter = (this && this.__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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
class KeyValueFileSystem {
constructor(store, keyNamespacePrefix = "/kvfs") {
if (!keyNamespacePrefix.length) {
throw new Error("keyNamespacePrefix must not be empty. This protects you from\
accidentally deleting everything else in your store.");
}
this.store = store;
this.prefix = keyNamespacePrefix;
}
ls(spec) {
return __awaiter(this, void 0, void 0, function* () {
const normalizedSpec = spec || "*";
const keys = yield this.store.getAllKeys();
const regex = this.regexFromSpec(normalizedSpec);
return keys
.filter(key => regex.test(key))
.map(key => key.slice(this.prefix.length));
});
}
read(path) {
return __awaiter(this, void 0, void 0, function* () {
this.validatePath(path);
const item = yield this.store.getItem(this.prefix + path);
if (item !== null) {
return JSON.parse(item);
}
return item;
});
}
readMulti(spec) {
return __awaiter(this, void 0, void 0, function* () {
const keys = yield this.ls(spec);
const values = yield this.store.multiGet(keys.map(key => this.prefix + key));
return values.map(([path, str]) => {
const value = str ? JSON.parse(str) : null;
return {
path: path.slice(this.prefix.length),
value,
};
});
});
}
write(path, value) {
return __awaiter(this, void 0, void 0, function* () {
this.validatePath(path);
return yield this.store.setItem(this.prefix + path, JSON.stringify(value));
});
}
/**
* @param basePath The base path to prepend to each subPath in values.
* @param values Each subPath and object you want written
*/
writeMulti(basePath, values) {
return __awaiter(this, void 0, void 0, function* () {
if (values.length === 0) {
return;
}
const keyValues = values.map(({ path: subPath, value }) => {
const fullPath = basePath ? `${basePath}${subPath}` : subPath;
this.validatePath(fullPath);
return [this.prefix + fullPath, JSON.stringify(value)];
});
return yield this.store.multiSet(keyValues);
});
}
rm(spec) {
return __awaiter(this, void 0, void 0, function* () {
this.validatePath(spec);
const keys = yield this.store.getAllKeys();
const regex = this.regexFromSpec(spec);
return yield this.store.multiRemove(keys.filter(key => regex.test(key)));
});
}
rmMulti(paths) {
return __awaiter(this, void 0, void 0, function* () {
if (paths.length === 0) {
return;
}
const keys = paths.map(path => {
this.validatePath(path);
return this.prefix + path;
});
return yield this.store.multiRemove(keys);
});
}
rmAllForce() {
return __awaiter(this, void 0, void 0, function* () {
const keys = yield this.store.getAllKeys();
const ourKeys = keys.filter(key => key.startsWith(this.prefix));
return yield this.store.multiRemove(ourKeys);
});
}
validatePath(path) {
if (path.length === 0) {
throw new Error("spec or path must not be empty");
}
}
regexFromSpec(spec) {
const segments = [];
let i = 0;
let curSegment = "";
while (i < spec.length) {
const char = spec[i];
switch (char) {
case "\\":
i++;
curSegment += spec[i];
break;
case "*":
if (curSegment.length) {
segments.push(curSegment);
curSegment = "";
}
break;
default:
curSegment += char;
break;
}
i++;
}
if (curSegment.length) {
segments.push(curSegment);
}
// Escapes all regex special characters, from ES7 proposal
// https://stackoverflow.com/a/63838890
const segRegex = segments
.map(seg => seg.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"))
.join(".*");
return new RegExp(`^${this.prefix}${segRegex}${spec[spec.length - 1] === "*" ? ".*" : ""}$`);
}
}
exports.default = KeyValueFileSystem;