opfs-tools
Version:
EN: A simple, high-performance, and comprehensive file system API running in the browser, built on [OPFS](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API/Origin_private_file_system).
521 lines (520 loc) • 16.5 kB
JavaScript
var z = (r) => {
throw TypeError(r);
};
var j = (r, e, t) => e.has(r) || z("Cannot " + t);
var n = (r, e, t) => (j(r, e, "read from private field"), t ? t.call(r) : e.get(r)), o = (r, e, t) => e.has(r) ? z("Cannot add the same private member more than once") : e instanceof WeakSet ? e.add(r) : e.set(r, t), l = (r, e, t, a) => (j(r, e, "write to private field"), a ? a.call(r, t) : e.set(r, t), t);
const J = "KGZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiO2Z1bmN0aW9uIHUobil7aWYobj09PSIvIilyZXR1cm57cGFyZW50Om51bGwsbmFtZToiIn07Y29uc3QgZT1uLnNwbGl0KCIvIikuZmlsdGVyKGk9PmkubGVuZ3RoPjApO2lmKGUubGVuZ3RoPT09MCl0aHJvdyBFcnJvcigiSW52YWxpZCBwYXRoIik7Y29uc3QgYT1lW2UubGVuZ3RoLTFdLHI9Ii8iK2Uuc2xpY2UoMCwtMSkuam9pbigiLyIpO3JldHVybntuYW1lOmEscGFyZW50OnJ9fWFzeW5jIGZ1bmN0aW9uIHcobixlKXtjb25zdHtwYXJlbnQ6YSxuYW1lOnJ9PXUobik7aWYoYT09bnVsbClyZXR1cm4gYXdhaXQgbmF2aWdhdG9yLnN0b3JhZ2UuZ2V0RGlyZWN0b3J5KCk7Y29uc3QgaT1hLnNwbGl0KCIvIikuZmlsdGVyKHQ9PnQubGVuZ3RoPjApO3RyeXtsZXQgdD1hd2FpdCBuYXZpZ2F0b3Iuc3RvcmFnZS5nZXREaXJlY3RvcnkoKTtmb3IoY29uc3QgcyBvZiBpKXQ9YXdhaXQgdC5nZXREaXJlY3RvcnlIYW5kbGUocyx7Y3JlYXRlOmUuY3JlYXRlfSk7aWYoZS5pc0ZpbGUpcmV0dXJuIGF3YWl0IHQuZ2V0RmlsZUhhbmRsZShyLHtjcmVhdGU6ZS5jcmVhdGV9KX1jYXRjaCh0KXtpZih0Lm5hbWU9PT0iTm90Rm91bmRFcnJvciIpcmV0dXJuIG51bGw7dGhyb3cgdH19Y29uc3QgZj17fTtzZWxmLm9ubWVzc2FnZT1hc3luYyBuPT57dmFyIGk7Y29uc3R7ZXZ0VHlwZTplLGFyZ3M6YX09bi5kYXRhO2xldCByPWZbYS5maWxlSWRdO3RyeXtsZXQgdDtjb25zdCBzPVtdO2lmKGU9PT0icmVnaXN0ZXIiKXtjb25zdCBsPWF3YWl0IHcoYS5maWxlUGF0aCx7Y3JlYXRlOiEwLGlzRmlsZTohMH0pO2lmKGw9PW51bGwpdGhyb3cgRXJyb3IoYG5vdCBmb3VuZCBmaWxlOiAke2EuZmlsZUlkfWApO3I9YXdhaXQgbC5jcmVhdGVTeW5jQWNjZXNzSGFuZGxlKHttb2RlOmEubW9kZX0pLGZbYS5maWxlSWRdPXJ9ZWxzZSBpZihlPT09ImNsb3NlIilhd2FpdCByLmNsb3NlKCksZGVsZXRlIGZbYS5maWxlSWRdO2Vsc2UgaWYoZT09PSJ0cnVuY2F0ZSIpYXdhaXQgci50cnVuY2F0ZShhLm5ld1NpemUpO2Vsc2UgaWYoZT09PSJ3cml0ZSIpe2NvbnN0e2RhdGE6bCxvcHRzOm99PW4uZGF0YS5hcmdzO3Q9YXdhaXQgci53cml0ZShsLG8pfWVsc2UgaWYoZT09PSJyZWFkIil7Y29uc3R7b2Zmc2V0Omwsc2l6ZTpvfT1uLmRhdGEuYXJncyxnPW5ldyBVaW50OEFycmF5KG8pLGQ9YXdhaXQgci5yZWFkKGcse2F0Omx9KSxjPWcuYnVmZmVyO3Q9ZD09PW8/YzooKGk9Yy50cmFuc2Zlcik9PW51bGw/dm9pZCAwOmkuY2FsbChjLGQpKT8/Yy5zbGljZSgwLGQpLHMucHVzaCh0KX1lbHNlIGU9PT0iZ2V0U2l6ZSI/dD1hd2FpdCByLmdldFNpemUoKTplPT09ImZsdXNoIiYmYXdhaXQgci5mbHVzaCgpO3NlbGYucG9zdE1lc3NhZ2Uoe2V2dFR5cGU6ImNhbGxiYWNrIixjYklkOm4uZGF0YS5jYklkLHJldHVyblZhbDp0fSxzKX1jYXRjaCh0KXtjb25zdCBzPXQ7c2VsZi5wb3N0TWVzc2FnZSh7ZXZ0VHlwZToidGhyb3dFcnJvciIsY2JJZDpuLmRhdGEuY2JJZCxlcnJNc2c6cy5uYW1lKyI6ICIrcy5tZXNzYWdlK2AKYCtKU09OLnN0cmluZ2lmeShuLmRhdGEpfSl9fX0pKCk7Ci8vIyBzb3VyY2VNYXBwaW5nVVJMPW9wZnMtd29ya2VyLUY0UldscWNfLmpzLm1hcAo=", D = (r) => Uint8Array.from(atob(r), (e) => e.charCodeAt(0)), K = typeof self < "u" && self.Blob && new Blob([D(J)], { type: "text/javascript;charset=utf-8" });
function M(r) {
let e;
try {
if (e = K && (self.URL || self.webkitURL).createObjectURL(K), !e) throw "";
const t = new Worker(e, {
name: r == null ? void 0 : r.name
});
return t.addEventListener("error", () => {
(self.URL || self.webkitURL).revokeObjectURL(e);
}), t;
} catch {
return new Worker(
"data:text/javascript;base64," + J,
{
name: r == null ? void 0 : r.name
}
);
} finally {
e && (self.URL || self.webkitURL).revokeObjectURL(e);
}
}
async function _(r, e, t) {
const a = A();
return await a("register", { fileId: r, filePath: e, mode: t }), {
read: async (i, s) => await a("read", {
fileId: r,
offset: i,
size: s
}),
write: async (i, s) => await a(
"write",
{
fileId: r,
data: i,
opts: s
},
[ArrayBuffer.isView(i) ? i.buffer : i]
),
close: async () => await a("close", {
fileId: r
}),
truncate: async (i) => await a("truncate", {
fileId: r,
newSize: i
}),
getSize: async () => await a("getSize", {
fileId: r
}),
flush: async () => await a("flush", {
fileId: r
})
};
}
const v = [];
let x = 0;
function A() {
if (v.length < 3) {
const e = r();
return v.push(e), e;
} else {
const e = v[x];
return x = (x + 1) % v.length, e;
}
function r() {
const e = new M();
let t = 0, a = {};
return e.onmessage = ({
data: i
}) => {
var s, c;
i.evtType === "callback" ? (s = a[i.cbId]) == null || s.resolve(i.returnVal) : i.evtType === "throwError" && ((c = a[i.cbId]) == null || c.reject(Error(i.errMsg))), delete a[i.cbId];
}, async function(s, c, h = []) {
t += 1;
const w = new Promise((b, k) => {
a[t] = { resolve: b, reject: k };
});
return e.postMessage(
{
cbId: t,
evtType: s,
args: c
},
h
), w;
};
}
}
function V(r) {
if (r === "/") return { parent: null, name: "" };
const e = r.split("/").filter((i) => i.length > 0);
if (e.length === 0) throw Error("Invalid path");
const t = e[e.length - 1], a = "/" + e.slice(0, -1).join("/");
return { name: t, parent: a };
}
async function m(r, e) {
const { parent: t, name: a } = V(r);
if (t == null) return await navigator.storage.getDirectory();
const i = t.split("/").filter((s) => s.length > 0);
try {
let s = await navigator.storage.getDirectory();
for (const c of i)
s = await s.getDirectoryHandle(c, {
create: e.create
});
return e.isFile ? await s.getFileHandle(a, {
create: e.create
}) : await s.getDirectoryHandle(a, {
create: e.create
});
} catch (s) {
if (s.name === "NotFoundError")
return null;
throw s;
}
}
async function L(r) {
const { parent: e, name: t } = V(r);
if (e == null) {
const i = await navigator.storage.getDirectory();
for await (const s of i.keys())
await i.removeEntry(s, { recursive: !0 });
return;
}
const a = await m(e, {
create: !1,
isFile: !1
});
if (a != null)
try {
await a.removeEntry(t, { recursive: !0 });
} catch (i) {
if (i.name === "NotFoundError") return;
throw i;
}
}
function E(r, e) {
return `${r}/${e}`.replace("//", "/");
}
function g(r) {
return new T(r);
}
var f, S, p;
const C = class C {
constructor(e) {
o(this, f);
o(this, S);
o(this, p);
l(this, f, e);
const { parent: t, name: a } = V(e);
l(this, S, a), l(this, p, t);
}
get kind() {
return "dir";
}
get name() {
return n(this, S);
}
get path() {
return n(this, f);
}
get parent() {
return n(this, p) == null ? null : g(n(this, p));
}
/**
* Creates the directory.
* return A promise that resolves when the directory is created.
*/
async create() {
return await m(n(this, f), {
create: !0,
isFile: !1
}), g(n(this, f));
}
/**
* Checks if the directory exists.
* return A promise that resolves to true if the directory exists, otherwise false.
*/
async exists() {
return await m(n(this, f), {
create: !1,
isFile: !1
}) instanceof FileSystemDirectoryHandle;
}
/**
* Removes the directory.
* return A promise that resolves when the directory is removed.
*/
async remove(e = {}) {
for (const t of await this.children())
try {
await t.remove(e);
} catch (a) {
console.warn(a);
}
try {
await L(n(this, f));
} catch (t) {
console.warn(t);
}
}
/**
* Retrieves the children of the directory.
* return A promise that resolves to an array of objects representing the children.
*/
async children() {
const e = await m(n(this, f), {
create: !1,
isFile: !1
});
if (e == null) return [];
const t = [];
for await (const a of e.values())
t.push((a.kind === "file" ? F : g)(E(n(this, f), a.name)));
return t;
}
async copyTo(e) {
if (!await this.exists())
throw Error(`dir ${this.path} not exists`);
if (e instanceof C) {
const t = await e.exists() ? g(E(e.path, this.name)) : e;
return await t.create(), await Promise.all((await this.children()).map((a) => a.copyTo(t))), t;
} else if (e instanceof FileSystemDirectoryHandle)
return await Promise.all(
(await this.children()).map(async (t) => {
t.kind === "file" ? await t.copyTo(
await e.getFileHandle(t.name, { create: !0 })
) : await t.copyTo(
await e.getDirectoryHandle(t.name, { create: !0 })
);
})
), null;
throw Error("Illegal target type");
}
/**
* move directory, copy then remove current
*/
async moveTo(e) {
const t = await this.copyTo(e);
return await this.remove(), t;
}
};
f = new WeakMap(), S = new WeakMap(), p = new WeakMap();
let T = C;
const P = /* @__PURE__ */ new Map();
function F(r, e = "rw") {
if (e === "rw") {
const t = P.get(r) ?? new W(r, e);
return P.set(r, t), t;
}
return new W(r, e);
}
async function B(r, e, t = { overwrite: !0 }) {
if (e instanceof W) {
await B(r, await e.stream(), t);
return;
}
const a = await (r instanceof W ? r : F(r, "rw")).createWriter();
try {
if (t.overwrite && await a.truncate(0), e instanceof ReadableStream) {
const i = e.getReader();
for (; ; ) {
const { done: s, value: c } = await i.read();
if (s) break;
await a.write(c);
}
} else
await a.write(e);
} catch (i) {
throw i;
} finally {
await a.close();
}
}
let $ = 0;
const q = () => ++$;
var u, Z, G, Y, X, d, R, I, y;
const O = class O {
constructor(e, t) {
o(this, u);
o(this, Z);
o(this, G);
o(this, Y);
o(this, X);
o(this, d, 0);
o(this, R, async () => {
});
o(this, I, /* @__PURE__ */ (() => {
let e = null;
return () => (l(this, d, n(this, d) + 1), e != null || (e = new Promise(async (t, a) => {
try {
const i = await _(
n(this, X),
n(this, u),
n(this, Y)
);
l(this, R, async () => {
e != null && (e = null, l(this, d, 0), await i.close().catch(console.error));
}), t([
i,
async () => {
l(this, d, n(this, d) - 1), !(n(this, d) > 0) && (e = null, await i.close());
}
]);
} catch (i) {
a(i);
}
})), e);
})());
o(this, y, !1);
l(this, X, q()), l(this, u, e), l(this, Y, {
r: "read-only",
rw: "readwrite",
"rw-unsafe": "readwrite-unsafe"
}[t]);
const { parent: a, name: i } = V(e);
if (a == null) throw Error("Invalid path");
l(this, G, i), l(this, Z, a);
}
get kind() {
return "file";
}
get path() {
return n(this, u);
}
get name() {
return n(this, G);
}
get parent() {
return n(this, Z) == null ? null : g(n(this, Z));
}
/**
* Random write to file
*/
async createWriter() {
if (n(this, Y) === "read-only") throw Error("file is read-only");
if (n(this, y)) throw Error("Other writer have not been closed");
l(this, y, !0);
try {
const e = new TextEncoder(), [t, a] = await n(this, I).call(this);
let i = await t.getSize(), s = !1;
return {
write: async (c, h = {}) => {
if (s) throw Error("Writer is closed");
const w = typeof c == "string" ? e.encode(c) : c, b = h.at ?? i, k = w.byteLength;
return i = b + k, await t.write(w, { at: b });
},
truncate: async (c) => {
if (s) throw Error("Writer is closed");
await t.truncate(c), i > c && (i = c);
},
flush: async () => {
if (s) throw Error("Writer is closed");
await t.flush();
},
close: async () => {
if (s) throw Error("Writer is closed");
s = !0, l(this, y, !1), await a();
}
};
} catch (e) {
throw l(this, y, !1), e;
}
}
/**
* Random access to file
*/
async createReader() {
const [e, t] = await n(this, I).call(this);
let a = !1, i = 0;
return {
read: async (s, c = {}) => {
if (a) throw Error("Reader is closed");
const h = c.at ?? i, w = await e.read(h, s);
return i = h + w.byteLength, w;
},
getSize: async () => {
if (a) throw Error("Reader is closed");
return await e.getSize();
},
close: async () => {
a || (a = !0, await t());
}
};
}
async text() {
return new TextDecoder().decode(await this.arrayBuffer());
}
async arrayBuffer() {
const e = await m(n(this, u), { create: !1, isFile: !0 });
return e == null ? new ArrayBuffer(0) : (await e.getFile()).arrayBuffer();
}
async stream() {
const e = await this.getOriginFile();
return e == null ? new ReadableStream({
pull: (t) => {
t.close();
}
}) : e.stream();
}
async getOriginFile() {
var e;
return (e = await m(n(this, u), { create: !1, isFile: !0 })) == null ? void 0 : e.getFile();
}
async getSize() {
const e = await m(n(this, u), { create: !1, isFile: !0 });
return e == null ? 0 : (await e.getFile()).size;
}
async exists() {
return await m(n(this, u), {
create: !1,
isFile: !0
}) instanceof FileSystemFileHandle;
}
async remove(e = {}) {
if (e.force === !0) {
await n(this, R).call(this), await L(n(this, u)), P.delete(n(this, u));
return;
}
if (n(this, d) > 0) throw Error("exists unclosed reader/writer");
await L(n(this, u));
}
async copyTo(e) {
if (e instanceof O)
return e.path === this.path ? this : (await B(e, this), e);
if (e instanceof T) {
if (!await this.exists())
throw Error(`file ${this.path} not exists`);
return await this.copyTo(F(E(e.path, this.name)));
} else if (e instanceof FileSystemFileHandle)
return await (await this.stream()).pipeTo(await e.createWritable()), null;
throw Error("Illegal target type");
}
/**
* move file, copy then remove current
*/
async moveTo(e) {
const t = await this.copyTo(e);
return await this.remove(), t;
}
};
u = new WeakMap(), Z = new WeakMap(), G = new WeakMap(), Y = new WeakMap(), X = new WeakMap(), d = new WeakMap(), R = new WeakMap(), I = new WeakMap(), y = new WeakMap();
let W = O;
const U = "/.opfs-tools-temp-dir";
async function Q(r) {
try {
if (r.kind === "file") {
if (!await r.exists()) return !0;
const e = await r.createWriter();
await e.truncate(0), await e.close(), await r.remove();
} else
await r.remove();
return !0;
} catch (e) {
return console.warn(e), !1;
}
}
function ee() {
setInterval(async () => {
for (const e of await g(U).children()) {
const t = /^\d+-(\d+)$/.exec(e.name);
(t == null || Date.now() - Number(t[1]) > 2592e5) && await Q(e);
}
}, 60 * 1e3);
}
const H = [];
let N = !1;
async function te() {
if (globalThis.localStorage == null) return;
const r = "OPFS_TOOLS_EXPIRES_TMP_FILES";
N || (N = !0, globalThis.addEventListener("unload", () => {
H.length !== 0 && localStorage.setItem(
r,
`${localStorage.getItem(r) ?? ""},${H.join(",")}`
);
}));
let e = localStorage.getItem(r) ?? "";
for (const t of e.split(","))
t.length !== 0 && await Q(F(`${U}/${t}`)) && (e = e.replace(t, ""));
localStorage.setItem(r, e.replace(/,{2,}/g, ","));
}
(async function() {
var e;
globalThis.__opfs_tools_tmpfile_init__ !== !0 && (globalThis.__opfs_tools_tmpfile_init__ = !0, !(globalThis.FileSystemDirectoryHandle == null || globalThis.FileSystemFileHandle == null || ((e = globalThis.navigator) == null ? void 0 : e.storage.getDirectory) == null) && (ee(), await te()));
})();
function ae() {
const r = `${Math.random().toString().slice(2)}-${Date.now()}`;
return H.push(r), F(`${U}/${r}`);
}
function ie(r, e) {
let t = F(r), a = 0, i = t.createWriter(), s = t.createReader();
const c = async (h) => {
const b = await (await s).read(a, { at: Math.round(a * 0.3) });
a = await h.write(b, { at: 0 }), await h.truncate(a);
};
return {
append: async (h) => {
const w = await i;
a += await w.write(h), a >= e && await c(w);
},
text: t.text.bind(t),
remove: async () => {
await (await s).close(), await (await i).close(), await t.remove();
},
getSize: async () => a
};
}
export {
g as dir,
F as file,
ie as rollfile,
ae as tmpfile,
B as write
};
//# sourceMappingURL=opfs-tools.js.map