NukeBase

File Client API

The browser SDK exposes five file methods. They travel over HTTP rather than the WebSocket — the socket protocol is JSON-only, and framing binary through it would buy nothing a PUT doesn't already give. Cookies ride along, so the caller is the same admin the socket sees.

Client file methods
import createClient from './sdkmod.js';
const { getFile, setFile, listFiles, removeFile, fileUrl } = await createClient();

// Upload — body may be a Blob, File, ArrayBuffer, TypedArray or string
const picker = document.querySelector('input[type=file]');
const result = await setFile(["posts", "p1", "attachments", "hero.png"], picker.files[0], {
  onProgress: (fraction) => { bar.style.width = (fraction * 100) + '%'; }
});
// result → { status: "Success", size: 84213, type: "image/png",
//            name: "hero.png", path: ["posts","p1","attachments","hero.png"] }

// Download — resolves with a Blob
const blob = await getFile(["posts", "p1", "attachments", "hero.png"]);
img.src = URL.createObjectURL(blob);

// Or skip the download entirely and link straight to it.
// Rules still apply when the browser fetches the URL — this is a link, not a bypass.
img.src = fileUrl(["posts", "p1", "attachments", "hero.png"]);
// → "/_file/posts/p1/attachments/hero.png"

// List one directory level
const entries = await listFiles(["posts", "p1", "attachments"]);
// → [{ name: "hero.png", isDir: false, size: 84213, type: "image/png", mtime: 1770000000000 }, …]
// Names are relative — build a child path as [...path, entry.name]

// Delete. Directories need { recursive: true } so a mistyped path
// cannot take a whole tree with it.
await removeFile(["posts", "p1", "attachments", "hero.png"]);
await removeFile(["posts", "p1", "attachments"], { recursive: true });

File methods throw; data methods don't. get/set/query resolve with { status: "Failed" }. The four file methods reject with a NukeBaseFileError carrying .status (the HTTP code), .body and .url, so you branch on a number instead of parsing a message. Wrap them in try/catch.

Error handling and cancellation
const controller = new AbortController();
cancelBtn.onclick = () => controller.abort();

try {
  await setFile(["uploads", "big.zip"], file, {
    signal: controller.signal,
    onProgress: (p) => console.log(Math.round(p * 100) + '%')
  });
} catch (err) {
  if (err.name === 'AbortError') return;      // user cancelled
  switch (err.status) {
    case 403: alert("A rule refused this upload"); break;
    case 409: alert("A file with that name (differing only by case) already exists"); break;
    case 413: alert("File is too large"); break;
    case 429: alert("Too many uploads — slow down"); break;
    default:  alert("Upload failed: " + err.body);
  }
}

Every file method accepts opts.onProgress and opts.signal. onProgress is called with a fraction from 0 to 1 (upload progress for setFile, download progress for getFile). signal is a standard AbortSignal; an aborted request rejects with err.name === 'AbortError'.

All four run on XMLHttpRequest rather than fetch. That is not legacy: fetch cannot report upload progress at all — request streaming needs duplex: "half", is Chrome-only, and fails outright on HTTP/1.1 — and XHR also gives download progress and abort() for free. Nothing is given up, because these methods buffer into a Blob and never used response streaming. Uploads stream straight to disk server-side, so a large file never sits in server memory either.