some styling cleanup

This commit is contained in:
Nystik
2026-03-17 12:38:30 +01:00
parent c70b9e9d0f
commit 0738c47ac5
27 changed files with 479 additions and 105 deletions

View File

@@ -20,6 +20,7 @@ export class ContentCache {
entry.accessedAt = Date.now();
return entry.data;
}
return null;
}
@@ -44,6 +45,7 @@ export class ContentCache {
delete(path) {
const norm = this._normalize(path);
const entry = this._cache.get(norm);
if (entry) {
this._currentSize -= entry.size;
this._cache.delete(norm);
@@ -71,12 +73,14 @@ export class ContentCache {
_evictOne() {
let oldest = null;
let oldestTime = Infinity;
for (const [key, entry] of this._cache) {
if (entry.accessedAt < oldestTime) {
oldest = key;
oldestTime = entry.accessedAt;
}
}
if (oldest) {
this.delete(oldest);
}

View File

@@ -1,6 +1,6 @@
// In-memory metadata cache
// Populated from /api/fs/tree on startup, kept in sync via transport events.
// All stat/exists/readdir calls are served from this cache (zero latency).
// All stat/exists/readdir calls are served from this cache.
export class MetadataCache {
constructor() {
@@ -38,10 +38,12 @@ export class MetadataCache {
const oldNorm = this._normalize(oldPath);
const newNorm = this._normalize(newPath);
const meta = this._entries.get(oldNorm);
if (meta) {
this._entries.delete(oldNorm);
this._entries.set(newNorm, meta);
}
// Move children
const prefix = oldNorm + "/";
for (const [key, val] of this._entries) {
@@ -65,9 +67,11 @@ export class MetadataCache {
const rest = key.slice(prefix.length);
const slashIdx = rest.indexOf("/");
const childName = slashIdx >= 0 ? rest.slice(0, slashIdx) : rest;
if (childName && !seen.has(childName)) {
seen.add(childName);
const childMeta = this._entries.get(prefix + childName);
results.push({
name: childName,
type: childMeta?.type || (slashIdx >= 0 ? "directory" : "file"),
@@ -82,10 +86,13 @@ export class MetadataCache {
return this._entries.size;
}
// Build a stat-like object from metadata
toStat(path) {
const meta = this.get(path);
if (!meta) return null;
if (!meta) {
return null;
}
return {
size: meta.size || 0,
mtimeMs: meta.mtime || 0,

View File

@@ -2,7 +2,10 @@ export function createFsPromises(metadataCache, contentCache, transport) {
return {
async stat(path) {
const cached = metadataCache.toStat(path);
if (cached) return cached;
if (cached) {
return cached;
}
const meta = await transport.stat(path);
metadataCache.set(path, meta);
@@ -16,13 +19,16 @@ export function createFsPromises(metadataCache, contentCache, transport) {
async readdir(path) {
const meta = metadataCache.get(path);
if (meta && meta.type === "file") {
return [];
}
if (!meta && path && path !== "/" && path !== ".") {
const e = new Error(
`ENOENT: no such file or directory, scandir '${path}'`,
);
e.code = "ENOENT";
throw e;
}
@@ -31,7 +37,10 @@ export function createFsPromises(metadataCache, contentCache, transport) {
},
async readFile(path, encoding) {
if (typeof encoding === "object") encoding = encoding?.encoding;
if (typeof encoding === "object") {
encoding = encoding?.encoding;
}
const wantText = encoding === "utf8" || encoding === "utf-8";
const meta = metadataCache.get(path);
@@ -40,6 +49,7 @@ export function createFsPromises(metadataCache, contentCache, transport) {
e.code = "EISDIR";
throw e;
}
if (!meta && path) {
const e = new Error(
`ENOENT: no such file or directory, open '${path}'`,
@@ -49,16 +59,19 @@ export function createFsPromises(metadataCache, contentCache, transport) {
}
const cached = contentCache.get(path);
if (cached !== null) {
if (wantText) {
return typeof cached === "string"
? cached
: new TextDecoder().decode(cached);
}
// binary. ensure we return a proper Uint8Array with .buffer
if (typeof cached === "string") {
return new TextEncoder().encode(cached);
}
return cached;
}
@@ -68,11 +81,15 @@ export function createFsPromises(metadataCache, contentCache, transport) {
},
async writeFile(path, data, encoding) {
if (typeof encoding === "object") encoding = encoding?.encoding;
if (typeof encoding === "object") {
encoding = encoding?.encoding;
}
contentCache.set(path, data);
const size =
typeof data === "string" ? data.length : data.byteLength || 0;
metadataCache.set(path, {
type: "file",
size,
@@ -81,6 +98,7 @@ export function createFsPromises(metadataCache, contentCache, transport) {
});
const result = await transport.writeFile(path, data, encoding);
if (result.mtime) {
metadataCache.set(path, {
type: "file",
@@ -93,6 +111,7 @@ export function createFsPromises(metadataCache, contentCache, transport) {
async appendFile(path, data, encoding) {
contentCache.invalidate(path);
await transport.appendFile(path, data);
const meta = await transport.stat(path);
@@ -102,15 +121,18 @@ export function createFsPromises(metadataCache, contentCache, transport) {
async unlink(path) {
contentCache.delete(path);
metadataCache.delete(path);
await transport.unlink(path);
},
async rename(oldPath, newPath) {
const content = contentCache.get(oldPath);
if (content !== null) {
contentCache.set(newPath, content);
contentCache.delete(oldPath);
}
metadataCache.rename(oldPath, newPath);
await transport.rename(oldPath, newPath);
@@ -119,7 +141,9 @@ export function createFsPromises(metadataCache, contentCache, transport) {
async mkdir(path, options) {
const recursive =
typeof options === "object" ? !!options.recursive : !!options;
metadataCache.set(path, { type: "directory" });
await transport.mkdir(path, recursive);
},
@@ -131,19 +155,25 @@ export function createFsPromises(metadataCache, contentCache, transport) {
async rm(path, options) {
const recursive =
typeof options === "object" ? !!options.recursive : false;
metadataCache.delete(path);
contentCache.delete(path);
await transport.rm(path, recursive);
},
async copyFile(src, dest) {
await transport.copyFile(src, dest);
const meta = await transport.stat(dest);
metadataCache.set(dest, meta);
},
async access(path) {
if (metadataCache.has(path)) return;
if (metadataCache.has(path)) {
return;
}
const e = new Error(
`ENOENT: no such file or directory, access '${path}'`,
);
@@ -152,7 +182,10 @@ export function createFsPromises(metadataCache, contentCache, transport) {
},
async realpath(path) {
if (!path || path === "/" || path === ".") return "/";
if (!path || path === "/" || path === ".") {
return "/";
}
return transport.realpath(path);
},

View File

@@ -6,6 +6,7 @@ export function createFsSync(metadataCache, contentCache, transport) {
statSync(path) {
const stat = metadataCache.toStat(path);
if (!stat) {
const err = new Error(
`ENOENT: no such file or directory, stat '${path}'`,
@@ -13,6 +14,7 @@ export function createFsSync(metadataCache, contentCache, transport) {
err.code = "ENOENT";
throw err;
}
return stat;
},
@@ -27,7 +29,9 @@ export function createFsSync(metadataCache, contentCache, transport) {
},
readFileSync(path, encoding) {
if (typeof encoding === "object") encoding = encoding?.encoding;
if (typeof encoding === "object") {
encoding = encoding?.encoding;
}
const meta = metadataCache.get(path);
if (meta && meta.type === "directory") {
@@ -43,21 +47,28 @@ export function createFsSync(metadataCache, contentCache, transport) {
? cached
: new TextDecoder().decode(cached);
}
return cached;
}
console.warn("[shim:fs] readFileSync cache miss, using sync XHR:", path);
const data = transport.readFileSync(path, encoding);
contentCache.set(path, data);
return data;
},
writeFileSync(path, data, encoding) {
if (typeof encoding === "object") encoding = encoding?.encoding;
if (typeof encoding === "object") {
encoding = encoding?.encoding;
}
contentCache.set(path, data);
const size =
typeof data === "string" ? data.length : data.byteLength || 0;
metadataCache.set(path, {
type: "file",
size,

View File

@@ -7,9 +7,11 @@ function normPath(p) {
function uint8ToBase64(bytes) {
let binary = "";
const chunk = 8192;
for (let i = 0; i < bytes.length; i += chunk) {
binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunk));
}
return btoa(binary);
}
@@ -23,7 +25,10 @@ async function request(method, endpoint, params = {}) {
const options = { method };
if (method === "GET" || method === "DELETE") {
if (vaultId()) url.searchParams.set("vault", vaultId());
if (vaultId()) {
url.searchParams.set("vault", vaultId());
}
for (const [key, val] of Object.entries(params)) {
url.searchParams.set(key, val);
}
@@ -41,6 +46,7 @@ async function request(method, endpoint, params = {}) {
e.code = err.code || "UNKNOWN";
throw e;
}
return res;
}
@@ -53,7 +59,10 @@ function requestSync(method, endpoint, params = {}) {
const url = new URL(API_BASE + endpoint, window.location.origin);
if (method === "GET" || method === "DELETE") {
if (vaultId()) url.searchParams.set("vault", vaultId());
if (vaultId()) {
url.searchParams.set("vault", vaultId());
}
for (const [key, val] of Object.entries(params)) {
url.searchParams.set(key, val);
}
@@ -71,6 +80,7 @@ function requestSync(method, endpoint, params = {}) {
if (xhr.status >= 400) {
let err;
try {
const body = JSON.parse(xhr.responseText);
err = new Error(body.error || "Request failed");
@@ -79,6 +89,7 @@ function requestSync(method, endpoint, params = {}) {
err = new Error("Request failed: " + xhr.status);
err.code = "UNKNOWN";
}
throw err;
}
@@ -103,9 +114,11 @@ export const transport = {
path: normPath(path),
encoding: encoding || "",
});
if (encoding === "utf8" || encoding === "utf-8") {
return res.text();
}
const buf = await res.arrayBuffer();
return new Uint8Array(buf);
},
@@ -184,14 +197,18 @@ export const transport = {
path: normPath(path),
encoding: encoding || "",
});
if (encoding === "utf8" || encoding === "utf-8") {
return xhr.responseText;
}
const binary = xhr.responseText;
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes;
},