npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

alist-kratos-sdk

v2.2.1

Published

JavaScript SDK for AList file API with Ory Kratos authentication

Readme

alist-kratos-sdk

JavaScript / TypeScript SDK for the AList file API behind the Ory Oathkeeper edge. The edge validates the Kratos session cookie and injects the user's identity; the SDK sends cookie-authenticated requests and exposes typed file operations scoped to the user's own folder.

Install

npm install alist-kratos-sdk

Quick start

import { AlistClient } from "alist-kratos-sdk";

// Browser: the ory_kratos_session cookie is sent automatically (credentials:"include").
// The URL MUST include the /.assets/alist mount prefix — AList's edge rules only
// match under it, and paths like /api/me are appended verbatim.
const client = await AlistClient.fromKratosSession(
  "http://localhost:4455/.assets/alist", // edge + mount prefix (also the default)
);

// All paths are **relative to the user's BasePath**.
// The SDK lazily fetches base_path via /api/me on first use and
// forwards paths to AList, which enforces per-user containment server-side.
const { data } = await client.list("/");           // lists own root folder
await client.upload("/photos/sunset.jpg", fileBlob);
const blob = await client.download("/photos/sunset.jpg");

Path semantics (v1.0 — breaking change)

Starting with v1.0, paths passed to SDK methods are resolved relative to the authenticated user's BasePath. Callers no longer need to (and cannot) include their own identity id in the URL.

| Input path | Behaviour | |------------|-----------| | "/" or "" | Returns the user's BasePath root (the /\<identity_id\> folder). | | "photos/sunset.jpg" (no leading slash) | AList auto-prefixes BasePath server-side → /\<identity_id\>/photos/sunset.jpg. | | "/photos/sunset.jpg" (leading slash) | Treated as absolute; must be inside BasePath or the server returns 403. | | "/<other_identity_id>/..." | Always rejected with 403 (BasePath containment). |

The SDK never sends the literal BasePath prefix; it forwards whatever the caller supplies and lets AList enforce the boundary. The BasePath is discovered automatically via me() on first call (cached).

Migration from v0.x

- await client.upload("/abc123-id/photos/sunset.jpg", file);
+ await client.upload("/photos/sunset.jpg", file);

API

User

const me = await client.me();
// → { id, username, sso_id: "kratos:<identity_id>", base_path: "/<identity_id>", role, ... }

List directory

const res = await client.list("/", { page: 1, per_page: 50 });
for (const f of res.data.content) {
  console.log(f.name, f.size, f.is_dir);
}

Upload

// From a browser File (e.g. <input type=file>)
await client.upload("/photos/sunset.jpg", fileFromInput);

// From a Blob
const blob = new Blob([await response.arrayBuffer()]);
await client.upload("/data/file.bin", blob);

// From a Buffer (Node 18+)
const buf = await fs.promises.readFile("./local.pdf");
await client.upload("/docs/report.pdf", buf);

Download

// Full Blob (small files)
const blob = await client.download("/photos/sunset.jpg");
const url = URL.createObjectURL(blob);

// Server-signed URL (large files, <img src>, <a href>, window.open)
const presigned = await client.downloadUrl("/photos/sunset.jpg");
window.open(presigned, "_blank");

Both call /api/fs/get first so AList resolves the path under the user's BasePath and returns its canonical raw_url (full path + sign + proxy decision). The URL's host is rebased onto the edge origin this client was built with, so it stays reachable behind Oathkeeper. This is the only correct way to download — AList's /d/* route has no auth/identity, so the SDK cannot build that URL itself.

Browser cache (on by default)

download() caches the returned Blob in the browser Cache Storage, keyed by a content fingerprint (resolved path + size + mtime + hash from /api/fs/get). An unchanged file is served instantly with zero bytes re-transferred; a new version always re-fetches. Entries embed the user's BasePath, so they are per-user and never leak across accounts.

// Defaults: cache ON, maxEntries 100, no TTL. All tunable / disableable:
const client = new AlistClient({
  alistUrl: "http://localhost:4455/.assets/alist",
  cache: { enabled: false },                 // opt out entirely
  // or: { maxEntries: 500, ttlMs: 3_600_000 } // tune limits
});

await client.clearCache();                   // evict every cached Blob

Backed by the Cache Storage API (persists across sessions) with an in-memory fallback when caches is unavailable (Node) or over quota (private mode); quota/put errors are non-fatal. downloadUrl() is not cached (it returns the edge URL for <img src> etc., where the browser HTTP cache applies).

Other operations

await client.mkdir("/new-folder");
await client.rename("new-name.jpg", "/old-name.jpg");
await client.remove(["/file1.txt", "/file2.txt"]);
await client.move(["/file.txt"], "/archive/");
await client.copy(["/file.txt"], "/backup/");
const found = await client.search("/", "report", { scope: 0 });

Auth flow

The SDK relies on the Oathkeeper edge, not a bearer token:

  1. Every request is sent with credentials: "include", so the browser attaches the ory_kratos_session cookie. No Authorization header is sent — the edge's AList mutator blanks it anyway.
  2. Oathkeeper's cookie_session authenticator validates the cookie against Kratos /sessions/whoami and, on success, injects X-User-Id + identity traits (and blanks Authorization) before forwarding to AList.
  3. AList's middleware (server/middlewares/auth.go) reads the edge-injected X-User-Id. If the identity has no AList user yet, one is auto-provisioned with BasePath = /<kratos_identity_id> and SsoID = "kratos:<kratos_identity_id>".
  4. The me() endpoint returns the cached base_path; the SDK stores it internally.
  5. All subsequent file operations are scoped to that BasePath by AList's server-side containment check (internal/model/user.go:JoinPath).

You never need to log in to AList directly — the Kratos session (validated at the edge) is the single source of truth.

Routing note: the alistUrl must be the edge origin with the /.assets/alist prefix (e.g. http://localhost:4455/.assets/alist, or https://backend.getkawai.com/.assets/alist in prod). The SDK appends /api/..., /d/..., /p/..., /ping to it; without the prefix those paths match no edge rule and return 404. Direct-to-AList usage (bypassing the edge) is not supported in browser contexts — AList is localhost-only.

Browser vs Node

The SDK is designed for the browser, where the ory_kratos_session cookie is present and credentials: "include" delivers it to the edge:

  • fromKratosSession(url?) just constructs a client pointed at the edge; the cookie does the authentication.
  • fetch and FormData are native in browsers and Node 18+.
  • No DOM dependencies.

For server-side (Node) use without a browser cookie, talk to AList through the edge with a cookie forwarded manually, or run AList's own admin path out of band — the legacy kratos:<token> Authorization scheme is no longer used (the edge blanks Authorization).

Demo

import { AlistClient } from "alist-kratos-sdk";

const client = await AlistClient.fromKratosSession(); // default edge + prefix

// The SDK cannot know up-front whether the Kratos cookie is valid — the edge
// enforces that on the first request. A 401/403 from list()/me()/etc. means
// the user must log in (redirect to the Kratos login URL behind the same edge).
try {
  const { data } = await client.list();
  console.log(`You have ${data.total} files`);
} catch (err) {
  window.location.href =
    "http://localhost:4455/.ory/kratos/public/self-service/login/browser";
}

Types

interface AlistFile {
  name: string;
  size: number;
  is_dir: boolean;
  modified: string;
  created?: string;
  sign?: string;       // pre-signed URL token
  thumb?: string;      // thumbnail URL
  type: number;        // 0=unknown, 1=folder, 2=video, 3=audio, 4=text, 5=image, 6=archive
  hashinfo?: string;
}

interface AlistUser {
  id: number;
  username: string;
  sso_id?: string;       // "kratos:<identity_id>"
  base_path: string;     // "/<identity_id>"
  role: number;
  permission: number;
  disabled: boolean;
  otp?: boolean;
}