mirror of
https://github.com/Nystik-gh/ignis.git
synced 2026-06-17 04:35:53 +00:00
implement fs shims
This commit is contained in:
205
shims/fs/transport.js
Normal file
205
shims/fs/transport.js
Normal file
@@ -0,0 +1,205 @@
|
||||
// Transport abstraction layer
|
||||
// Decouples the fs shim from the sync mechanism (REST, WebSocket, or hybrid).
|
||||
// Currently implements a REST-based transport. This can be swapped or extended
|
||||
// once the sync strategy is finalized.
|
||||
|
||||
const API_BASE = "/api/fs";
|
||||
|
||||
// Strip leading slashes from paths before sending to server
|
||||
function normPath(p) {
|
||||
return (p || "").replace(/^\/+/, "");
|
||||
}
|
||||
|
||||
async function request(method, endpoint, params = {}) {
|
||||
const url = new URL(API_BASE + endpoint, window.location.origin);
|
||||
|
||||
const options = { method };
|
||||
|
||||
if (method === "GET" || method === "DELETE") {
|
||||
for (const [key, val] of Object.entries(params)) {
|
||||
url.searchParams.set(key, val);
|
||||
}
|
||||
} else {
|
||||
options.headers = { "Content-Type": "application/json" };
|
||||
options.body = JSON.stringify(params);
|
||||
}
|
||||
|
||||
const res = await fetch(url.toString(), options);
|
||||
if (!res.ok) {
|
||||
const err = await res
|
||||
.json()
|
||||
.catch(() => ({ error: res.statusText, code: "UNKNOWN" }));
|
||||
const e = new Error(err.error || res.statusText);
|
||||
e.code = err.code || "UNKNOWN";
|
||||
throw e;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
async function requestJson(method, endpoint, params = {}) {
|
||||
const res = await request(method, endpoint, params);
|
||||
return res.json();
|
||||
}
|
||||
|
||||
// Synchronous XHR - used only as fallback for sync fs calls on uncached content.
|
||||
// Blocking but functional. Should be rare after pre-warming.
|
||||
function requestSync(method, endpoint, params = {}) {
|
||||
const url = new URL(API_BASE + endpoint, window.location.origin);
|
||||
|
||||
if (method === "GET") {
|
||||
for (const [key, val] of Object.entries(params)) {
|
||||
url.searchParams.set(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open(method, url.toString(), false); // synchronous
|
||||
|
||||
if (method !== "GET") {
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.send(JSON.stringify(params));
|
||||
} else {
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
if (xhr.status >= 400) {
|
||||
let err;
|
||||
try {
|
||||
const body = JSON.parse(xhr.responseText);
|
||||
err = new Error(body.error || "Request failed");
|
||||
err.code = body.code || "UNKNOWN";
|
||||
} catch {
|
||||
err = new Error("Request failed: " + xhr.status);
|
||||
err.code = "UNKNOWN";
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
return xhr;
|
||||
}
|
||||
|
||||
export const transport = {
|
||||
// --- Async methods (used by fs.promises) ---
|
||||
|
||||
async fetchTree(basePath) {
|
||||
return requestJson("GET", "/tree", basePath ? { path: basePath } : {});
|
||||
},
|
||||
|
||||
async stat(path) {
|
||||
return requestJson("GET", "/stat", { path: normPath(path) });
|
||||
},
|
||||
|
||||
async readdir(path) {
|
||||
return requestJson("GET", "/readdir", { path: normPath(path) });
|
||||
},
|
||||
|
||||
async readFile(path, encoding) {
|
||||
const res = await request("GET", "/readFile", {
|
||||
path: normPath(path),
|
||||
encoding: encoding || "",
|
||||
});
|
||||
if (encoding === "utf8" || encoding === "utf-8") {
|
||||
return res.text();
|
||||
}
|
||||
const buf = await res.arrayBuffer();
|
||||
return new Uint8Array(buf);
|
||||
},
|
||||
|
||||
async writeFile(path, content, encoding) {
|
||||
const isText = typeof content === "string";
|
||||
return requestJson("POST", "/writeFile", {
|
||||
path: normPath(path),
|
||||
content: isText ? content : btoa(String.fromCharCode(...content)),
|
||||
encoding: encoding || (isText ? "utf-8" : "binary"),
|
||||
base64: !isText,
|
||||
});
|
||||
},
|
||||
|
||||
async appendFile(path, content) {
|
||||
return requestJson("POST", "/appendFile", {
|
||||
path: normPath(path),
|
||||
content,
|
||||
});
|
||||
},
|
||||
|
||||
async mkdir(path, recursive) {
|
||||
return requestJson("POST", "/mkdir", { path: normPath(path), recursive });
|
||||
},
|
||||
|
||||
async rename(oldPath, newPath) {
|
||||
return requestJson("POST", "/rename", {
|
||||
oldPath: normPath(oldPath),
|
||||
newPath: normPath(newPath),
|
||||
});
|
||||
},
|
||||
|
||||
async copyFile(src, dest) {
|
||||
return requestJson("POST", "/copyFile", {
|
||||
src: normPath(src),
|
||||
dest: normPath(dest),
|
||||
});
|
||||
},
|
||||
|
||||
async unlink(path) {
|
||||
return requestJson("DELETE", "/unlink", { path: normPath(path) });
|
||||
},
|
||||
|
||||
async rmdir(path) {
|
||||
return requestJson("DELETE", "/rmdir", { path: normPath(path) });
|
||||
},
|
||||
|
||||
async rm(path, recursive) {
|
||||
return requestJson("DELETE", "/rm", {
|
||||
path: normPath(path),
|
||||
recursive: recursive ? "true" : "false",
|
||||
});
|
||||
},
|
||||
|
||||
async access(path) {
|
||||
return requestJson("GET", "/access", { path: normPath(path) });
|
||||
},
|
||||
|
||||
async realpath(path) {
|
||||
const result = await requestJson("GET", "/realpath", {
|
||||
path: normPath(path),
|
||||
});
|
||||
return result.path;
|
||||
},
|
||||
|
||||
async utimes(path, atime, mtime) {
|
||||
return requestJson("POST", "/utimes", {
|
||||
path: normPath(path),
|
||||
atime,
|
||||
mtime,
|
||||
});
|
||||
},
|
||||
|
||||
// --- Sync methods (fallback) ---
|
||||
|
||||
readFileSync(path, encoding) {
|
||||
const xhr = requestSync("GET", "/readFile", {
|
||||
path: normPath(path),
|
||||
encoding: encoding || "",
|
||||
});
|
||||
if (encoding === "utf8" || encoding === "utf-8") {
|
||||
return xhr.responseText;
|
||||
}
|
||||
// Binary: return as Uint8Array
|
||||
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;
|
||||
},
|
||||
|
||||
writeFileSync(path, content, encoding) {
|
||||
const isText = typeof content === "string";
|
||||
requestSync("POST", "/writeFile", {
|
||||
path: normPath(path),
|
||||
content: isText ? content : btoa(String.fromCharCode(...content)),
|
||||
encoding: encoding || (isText ? "utf-8" : "binary"),
|
||||
base64: !isText,
|
||||
});
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user