unstorage
Version:
Universal Storage Layer
375 lines (369 loc) • 11.2 kB
JavaScript
import destr from 'destr';
import { n as normalizeBaseKey, a as normalizeKey, b as asyncCall, d as deserializeRaw, s as stringify, c as serializeRaw } from './shared/unstorage.c2211058.mjs';
export { j as joinKeys, p as prefixStorage } from './shared/unstorage.c2211058.mjs';
function defineDriver(factory) {
return factory;
}
const memory = defineDriver(() => {
const data = /* @__PURE__ */ new Map();
return {
name: "memory",
options: {},
hasItem(key) {
return data.has(key);
},
getItem(key) {
return data.get(key) || null;
},
getItemRaw(key) {
return data.get(key) || null;
},
setItem(key, value) {
data.set(key, value);
},
setItemRaw(key, value) {
data.set(key, value);
},
removeItem(key) {
data.delete(key);
},
getKeys() {
return Array.from(data.keys());
},
clear() {
data.clear();
},
dispose() {
data.clear();
}
};
});
function createStorage(options = {}) {
const context = {
mounts: { "": options.driver || memory() },
mountpoints: [""],
watching: false,
watchListeners: [],
unwatch: {}
};
const getMount = (key) => {
for (const base of context.mountpoints) {
if (key.startsWith(base)) {
return {
base,
relativeKey: key.slice(base.length),
driver: context.mounts[base]
};
}
}
return {
base: "",
relativeKey: key,
driver: context.mounts[""]
};
};
const getMounts = (base, includeParent) => {
return context.mountpoints.filter(
(mountpoint) => mountpoint.startsWith(base) || includeParent && base.startsWith(mountpoint)
).map((mountpoint) => ({
relativeBase: base.length > mountpoint.length ? base.slice(mountpoint.length) : void 0,
mountpoint,
driver: context.mounts[mountpoint]
}));
};
const onChange = (event, key) => {
if (!context.watching) {
return;
}
key = normalizeKey(key);
for (const listener of context.watchListeners) {
listener(event, key);
}
};
const startWatch = async () => {
if (context.watching) {
return;
}
context.watching = true;
for (const mountpoint in context.mounts) {
context.unwatch[mountpoint] = await watch(
context.mounts[mountpoint],
onChange,
mountpoint
);
}
};
const stopWatch = async () => {
if (!context.watching) {
return;
}
for (const mountpoint in context.unwatch) {
await context.unwatch[mountpoint]();
}
context.unwatch = {};
context.watching = false;
};
const storage = {
// Item
hasItem(key, opts = {}) {
key = normalizeKey(key);
const { relativeKey, driver } = getMount(key);
return asyncCall(driver.hasItem, relativeKey, opts);
},
getItem(key, opts = {}) {
key = normalizeKey(key);
const { relativeKey, driver } = getMount(key);
return asyncCall(driver.getItem, relativeKey, opts).then(
(value) => destr(value)
);
},
getItemRaw(key, opts = {}) {
key = normalizeKey(key);
const { relativeKey, driver } = getMount(key);
if (driver.getItemRaw) {
return asyncCall(driver.getItemRaw, relativeKey, opts);
}
return asyncCall(driver.getItem, relativeKey, opts).then(
(value) => deserializeRaw(value)
);
},
async setItem(key, value, opts = {}) {
if (value === void 0) {
return storage.removeItem(key);
}
key = normalizeKey(key);
const { relativeKey, driver } = getMount(key);
if (!driver.setItem) {
return;
}
await asyncCall(driver.setItem, relativeKey, stringify(value), opts);
if (!driver.watch) {
onChange("update", key);
}
},
async setItemRaw(key, value, opts = {}) {
if (value === void 0) {
return storage.removeItem(key, opts);
}
key = normalizeKey(key);
const { relativeKey, driver } = getMount(key);
if (driver.setItemRaw) {
await asyncCall(driver.setItemRaw, relativeKey, value, opts);
} else if (driver.setItem) {
await asyncCall(driver.setItem, relativeKey, serializeRaw(value), opts);
} else {
return;
}
if (!driver.watch) {
onChange("update", key);
}
},
async removeItem(key, opts = {}) {
if (typeof opts === "boolean") {
opts = { removeMata: opts };
}
key = normalizeKey(key);
const { relativeKey, driver } = getMount(key);
if (!driver.removeItem) {
return;
}
await asyncCall(driver.removeItem, relativeKey, opts);
if (opts.removeMata) {
await asyncCall(driver.removeItem, relativeKey + "$", opts);
}
if (!driver.watch) {
onChange("remove", key);
}
},
// Meta
async getMeta(key, opts = {}) {
if (typeof opts === "boolean") {
opts = { nativeOnly: opts };
}
key = normalizeKey(key);
const { relativeKey, driver } = getMount(key);
const meta = /* @__PURE__ */ Object.create(null);
if (driver.getMeta) {
Object.assign(meta, await asyncCall(driver.getMeta, relativeKey, opts));
}
if (!opts.nativeOnly) {
const value = await asyncCall(
driver.getItem,
relativeKey + "$",
opts
).then((value_) => destr(value_));
if (value && typeof value === "object") {
if (typeof value.atime === "string") {
value.atime = new Date(value.atime);
}
if (typeof value.mtime === "string") {
value.mtime = new Date(value.mtime);
}
Object.assign(meta, value);
}
}
return meta;
},
setMeta(key, value, opts = {}) {
return this.setItem(key + "$", value, opts);
},
removeMeta(key, opts = {}) {
return this.removeItem(key + "$", opts);
},
// Keys
async getKeys(base, opts = {}) {
base = normalizeBaseKey(base);
const mounts = getMounts(base, true);
let maskedMounts = [];
const allKeys = [];
for (const mount of mounts) {
const rawKeys = await asyncCall(
mount.driver.getKeys,
mount.relativeBase,
opts
);
const keys = rawKeys.map((key) => mount.mountpoint + normalizeKey(key)).filter((key) => !maskedMounts.some((p) => key.startsWith(p)));
allKeys.push(...keys);
maskedMounts = [
mount.mountpoint,
...maskedMounts.filter((p) => !p.startsWith(mount.mountpoint))
];
}
return base ? allKeys.filter((key) => key.startsWith(base) && !key.endsWith("$")) : allKeys.filter((key) => !key.endsWith("$"));
},
// Utils
async clear(base, opts = {}) {
base = normalizeBaseKey(base);
await Promise.all(
getMounts(base, false).map(async (m) => {
if (m.driver.clear) {
return asyncCall(m.driver.clear, m.relativeBase, opts);
}
if (m.driver.removeItem) {
const keys = await m.driver.getKeys(m.relativeBase, opts);
return Promise.all(keys.map((key) => m.driver.removeItem(key)));
}
})
);
},
async dispose() {
await Promise.all(
Object.values(context.mounts).map((driver) => dispose(driver))
);
},
async watch(callback) {
await startWatch();
context.watchListeners.push(callback);
return async () => {
context.watchListeners = context.watchListeners.filter(
(listener) => listener !== callback
);
if (context.watchListeners.length === 0) {
await stopWatch();
}
};
},
async unwatch() {
context.watchListeners = [];
await stopWatch();
},
// Mount
mount(base, driver) {
base = normalizeBaseKey(base);
if (base && context.mounts[base]) {
throw new Error(`already mounted at ${base}`);
}
if (base) {
context.mountpoints.push(base);
context.mountpoints.sort((a, b) => b.length - a.length);
}
context.mounts[base] = driver;
if (context.watching) {
Promise.resolve(watch(driver, onChange, base)).then((unwatcher) => {
context.unwatch[base] = unwatcher;
}).catch(console.error);
}
return storage;
},
async unmount(base, _dispose = true) {
base = normalizeBaseKey(base);
if (!base || !context.mounts[base]) {
return;
}
if (context.watching && base in context.unwatch) {
context.unwatch[base]();
delete context.unwatch[base];
}
if (_dispose) {
await dispose(context.mounts[base]);
}
context.mountpoints = context.mountpoints.filter((key) => key !== base);
delete context.mounts[base];
},
getMount(key = "") {
key = normalizeKey(key) + ":";
const m = getMount(key);
return {
driver: m.driver,
base: m.base
};
},
getMounts(base = "", opts = {}) {
base = normalizeKey(base);
const mounts = getMounts(base, opts.parents);
return mounts.map((m) => ({
driver: m.driver,
base: m.mountpoint
}));
}
};
return storage;
}
async function snapshot(storage, base) {
base = normalizeBaseKey(base);
const keys = await storage.getKeys(base);
const snapshot2 = {};
await Promise.all(
keys.map(async (key) => {
snapshot2[key.slice(base.length)] = await storage.getItem(key);
})
);
return snapshot2;
}
async function restoreSnapshot(driver, snapshot2, base = "") {
base = normalizeBaseKey(base);
await Promise.all(
Object.entries(snapshot2).map((e) => driver.setItem(base + e[0], e[1]))
);
}
function watch(driver, onChange, base) {
return driver.watch ? driver.watch((event, key) => onChange(event, base + key)) : () => {
};
}
async function dispose(driver) {
if (typeof driver.dispose === "function") {
await asyncCall(driver.dispose);
}
}
const builtinDrivers = {
azureStorageTable: "unstorage/drivers/azure-storage-table",
azureCosmos: "unstorage/drivers/azure-cosmos",
azureStorageBlob: "unstorage/drivers/azure-storage-blob",
cloudflareKVHTTP: "unstorage/drivers/cloudflare-kv-http",
cloudflareKVBinding: "unstorage/drivers/cloudflare-kv-binding",
"cloudflare-kv-http": "unstorage/drivers/cloudflare-kv-http",
"cloudflare-kv-binding": "unstorage/drivers/cloudflare-kv-binding",
fs: "unstorage/drivers/fs",
github: "unstorage/drivers/github",
http: "unstorage/drivers/http",
localStorage: "unstorage/drivers/localstorage",
lruCache: "unstorage/drivers/lru-cache",
localstorage: "unstorage/drivers/localstorage",
memory: "unstorage/drivers/memory",
mongodb: "unstorage/drivers/mongodb",
overlay: "unstorage/drivers/overlay",
planetscale: "unstorage/drivers/planetscale",
redis: "unstorage/drivers/redis",
azureKeyVault: "unstorage/drivers/azure-key-vault"
};
export { builtinDrivers, createStorage, defineDriver, normalizeBaseKey, normalizeKey, restoreSnapshot, snapshot };