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