NubleStation

Vault stores files for your app — documents, images, reports — on the NubleStation host, scoped to your app. Files live in collections, which are just strings in the path (think folders, no setup required). Every file is private by default; you opt individual files into public access or share them per user.

Get up and running

  1. Get an API key. In the Console, open your app and copy its API key (nbl_<key_id>.<secret>).

  2. Install the SDK.

    npm install @nublestation/vault
    # or
    pnpm add @nublestation/vault
  3. Create a client and move some files.

    import { createVaultClient } from "@nublestation/vault";
    
    const vault = createVaultClient({
      url:    "http://api.clinic.local",  // your Gateway
      apiKey: "nbl_<key_id>.<secret>",    // from the Console
    });
    
    // Upload (Blob, Uint8Array, or ArrayBuffer)
    const uploaded = await vault.upload("reports", "q1.pdf", file);
    
    // List a collection
    const files = await vault.list("reports");
    
    // Download as ArrayBuffer
    const bytes = await vault.download("reports", "q1.pdf");

That’s it. Every call is scoped to the app that issued the API key — one app can never touch another app’s files.

Everyday usage

Upload

upload never overwrites. If a file already exists at that path you get a 409 — delete and re-upload:

import { VaultError } from "@nublestation/vault";

try {
  await vault.upload("reports", "q1.pdf", file);
} catch (err) {
  if (err instanceof VaultError && err.status === 409) {
    await vault.delete("reports", "q1.pdf");
    await vault.upload("reports", "q1.pdf", file);
  } else {
    throw err;
  }
}

List

const all     = await vault.list();              // everything you can access
const reports = await vault.list("reports");     // one collection

// Scoped views — these need Identity wired in (signed-in user):
const mine    = await vault.listMine();          // files you own
const shared  = await vault.listSharedWithMe();  // shared with you (with your role)
const open    = await vault.listPublic();        // the public space

listMine / listSharedWithMe / listPublic rely on the session cookie from Identity. Without it, listMine returns [].

Download

const bytes = await vault.download("reports", "q1.pdf"); // ArrayBuffer

// Browser: display it
const url = URL.createObjectURL(new Blob([bytes], { type: "application/pdf" }));

Make a file public

const f = await vault.setPublic("avatars", "user-42.png", true);

Public files are served — no API key, no login — at a stable URL:

http://api.{org}.local/vault/{app_slug}/{collection}/{filename}

Flip it back with setPublic(..., false) and the URL returns 403.

Share with a user

Owners can share a file (or a whole collection, by passing null as the filename) with one user at a time, as viewer (read/download) or editor (read/download/delete):

await vault.share("reports", "q1.pdf", granteeUserId, "viewer");
await vault.unshare("reports", "q1.pdf", granteeUserId);

const grants = await vault.listGrants("reports", "q1.pdf"); // who has access

Sharing requires Identity. Get shareable users from identity.listAppUsers(). The rules — who counts as an owner, what each role can do, why recipients must already have app access — are enforced server-side; see Vault — Access Control.

Delete

await vault.delete("reports", "q1.pdf");

Removes the bytes from disk and the metadata. This cannot be undone.

Reference

Methods

MethodReturnsNotes
upload(collection, filename, data)FileResult409 if path exists
list(collection?)FileResult[]everything you can access
listMine(collection?)FileResult[]needs Identity session
listSharedWithMe()FileResult[]needs Identity session
listPublic(collection?)FileResult[]the public space
download(collection, filename)ArrayBufferraw bytes
setPublic(collection, filename, isPublic)FileResulttoggles the public URL
share(collection, filename | null, userId, role)voidrole: "viewer" | "editor"
unshare(collection, filename | null, userId)voidrevokes a grant
listGrants(collection, filename | null)Grant[]owner only
delete(collection, filename)voidpermanent

Errors

All methods throw VaultError with .status (HTTP code) and .code (machine string):

CodeStatusMeaning
file_already_exists409A file already exists at this path
file_too_large413Exceeds the app’s max file size
extension_not_allowed415Extension blocked by the app’s allowlist
not_found404File does not exist

Limits & gotchas

  • No overwrite, no rename. A path is write-once — delete and re-upload (see the 409 pattern above).
  • 50 MB per file by default. Configurable per app in Console → App → Vault Settings.
  • Extension allowlist. If the admin sets one, anything else is rejected with 415. Empty list = allow all.
  • No versioning. One file per path, one version. Keep history yourself if you need it.
  • LAN only. Files — including “public” ones — are only reachable inside the organisation’s network.

Go deeper