2026-08-11 11:50:24 +08:00
2026-08-11 11:50:24 +08:00
2026-08-11 11:50:24 +08:00
2026-05-22 13:39:37 +08:00
2026-06-02 15:16:05 +08:00
2026-08-11 11:50:24 +08:00
2026-08-11 11:50:24 +08:00
2026-08-11 11:50:24 +08:00
2026-05-22 13:39:37 +08:00
2026-05-25 11:23:49 +08:00


RaisFast

@raisfast/sdk

The official TypeScript SDK for RaisFast.
Framework-agnostic · Auto token refresh · Full type safety · Zero dependencies
Website · Docs · npm · Quick Start

npm version npm downloads GitHub Stars License TypeScript Zero Dependencies


Install

pnpm add @raisfast/sdk
# or
npm install @raisfast/sdk

Quick Start

import { RaisFast } from "@raisfast/sdk";

const client = new RaisFast("http://localhost:9898/api/v1");

// Auth
await client.auth.login("user@example.com", "password");
console.log(client.auth.user);

// Content Type CRUD (custom content type, e.g. "books")
const books = client.ct<Book>("books");
const list = await books.getList(1, 25);
const book = await books.getOne("slug-or-id");
const created = await books.create({ title: "Hello", author: "World" });
await books.update(created.id, { title: "Updated" });
await books.delete(created.id);

Auth

// Login (auto-saves to localStorage)
await client.auth.login("user@example.com", "password");

// Register
await client.auth.register({
  email: "user@example.com",
  password: "secret",
  nickname: "Alice",
});

// Get current user
const me = await client.auth.getMe();

// Update profile
await client.auth.updateMe({ nickname: "New Name" });

// Change password
await client.auth.changePassword({ old_password: "old", new_password: "new" });

// Logout
await client.auth.logout();

The default LocalAuthStore persists auth state to localStorage. Access token refresh is handled automatically on 401 responses.

Token Auto-Refresh

When a request returns 401, the SDK automatically:

  1. Sends a POST /auth/refresh with the stored refresh token (concurrent 401s are deduplicated to a single refresh call).
  2. On success — calls authStore.save(auth) with the new tokens, then retries the original request once.
  3. On failure — calls authStore.clear(), ending the session.

This works out of the box with the default LocalAuthStore. API tokens (Bearer rf_...) skip the refresh path since they have no refresh token.

To integrate with your own state management (e.g. Zustand, Pinia, Redux) and handle session expiry, extend BaseAuthStore and override save() / clear(). Below uses Zustand as an example (myStateStore is your own store — replace it with whatever you use):

import { create } from "zustand";
import { BaseAuthStore, RaisFast } from "@raisfast/sdk";

// 1. Your app's state store (Zustand, Pinia, Redux, etc.)
interface MyAuthState {
  accessToken: string | null;
  refreshToken: string | null;
  setTokens: (access: string, refresh: string) => void;
  logout: () => void;
}

const myStateStore = create<MyAuthState>((set) => ({
  accessToken: null,
  refreshToken: null,
  setTokens: (access, refresh) =>
    set({ accessToken: access, refreshToken: refresh }),
  logout: () => set({ accessToken: null, refreshToken: null }),
}));

// 2. Bridge: SDK AuthStore <-> your state store
class MyAuthStore extends BaseAuthStore {
  constructor() {
    super();
    // hydrate from your store on boot
    const saved = myStateStore.getState();
    if (saved.accessToken) {
      this._token = saved.accessToken;
      this._refreshToken = saved.refreshToken;
    }
  }

  // Called on login AND after every successful token refresh
  save(auth) {
    this._token = auth.access_token;
    this._refreshToken = auth.refresh_token;
    this._user = auth.user;
    this._notify();
    myStateStore.getState().setTokens(auth.access_token, auth.refresh_token);
  }

  // Called when refresh fails — session is dead
  clear() {
    super.clear();
    myStateStore.getState().logout();
    if (typeof window !== "undefined") {
      window.location.href = "/login?expired=1";
    }
  }
}

// 3. Pass it to the SDK client
const client = new RaisFast("http://localhost:9000/api/v1", {
  authStore: new MyAuthStore(),
});

Listen for Auth Changes

const unsub = client.authStore.onChange((token, user) => {
  console.log("auth changed:", { token, user });
}, true); // fire immediately with current state

unsub(); // unsubscribe

Content Types (ct)

Content Types (CT) are the core data model in RaisFast. Built-in CTs (like posts, pages, products) are ready to use. Custom CTs are defined via the Admin UI (Content Type Builder) or TOML config — not from application code. The SDK's job is to read and write the data stored in those CTs.

const books = client.ct<Book>("books");

// Paginated list
const page = await books.getList(1, 25, {
  sort: "created_at:desc",
  // filter expression (PocketBase style, supports parens & && ||)
  filter: 'status = "published" && price >= 10',
  // or bracket-style field conditions (Strapi style)
  filterCond: {
    status: "published",
    "price[$gte]": 10,
    "author[$in]": ["Alice", "Bob"],
  },
  search: "hello",
  fields: "id,title",
});

// Full list (auto-paginates)
const all = await books.getFullList({ sort: "title:asc" });

// First item matching filter
const first = await books.getFirstListItem('slug = "hello-world"');

// Single item
const book = await books.getOne("id-or-slug");

// Create / Update / Delete
const created = await books.create({ title: "New Book", author: "Alice" });
await books.update(created.id, { title: "Updated" });
await books.delete(created.id);

Admin Content Type

Admin content types use the /admin/cms/ prefix:

const adminBooks = client.adminCt<Book>("books");

Admin

// Dashboard stats
const stats = await client.admin.stats.overview();

// Content stats
const contentStats = await client.admin.stats.content("posts");

// Trends
const trends = await client.admin.stats.trends("posts", 30);

// Plugins
const plugins = await client.admin.plugins.list();
await client.admin.plugins.enable("my-plugin");

// Content Types (defined via Admin UI / TOML; SDK only reads the schema)
const types = await client.admin.contentTypes.list();
const booksSchema = await client.admin.contentTypes.get("books");

Request Hooks

beforeSend

Intercept requests before they are sent (e.g. add custom headers):

client.beforeSend = (url, options) => {
  console.log("Requesting:", url);
  return { url, options };
};

afterSend

Transform responses after they are received:

client.afterSend = (response, data) => {
  console.log("Response status:", response.status);
  return data;
};

Request Options

All request methods accept an optional RequestOptions:

await books.getList(1, 25, {
  headers: { "X-Custom": "value" },
  query: { foo: "bar" },
  signal: abortController.signal,
  fetch: customFetch, // override global fetch
});

Multi-tenant

client.setTenantId("tenant-123");
client.setTenantId(null); // reset to default

Error Handling

import { SDKError } from "@raisfast/sdk";

try {
  await books.getOne("nonexistent");
} catch (e) {
  if (e instanceof SDKError) {
    console.log(e.code);        // backend error code
    console.log(e.status);      // HTTP status
    console.log(e.message);     // error message
    console.log(e.url);         // request URL
    console.log(e.response);    // full response body
    console.log(e.isAbort);     // was request aborted
    console.log(e.originalError); // original Error if any
  }
}

TypeScript

The SDK is written in TypeScript and ships type definitions. Generic parameters are available for content types:

interface Book {
  id: string;
  title: string;
  author: string;
  price: number;
  created_at: string;
}

const books = client.ct<Book>("books");
const book = await books.getOne("slug"); // typed as Book

Build

pnpm build

Outputs CJS + ESM + type declarations via tsup.

License

MIT

S
Description
No description provided
Readme
224 KiB
Languages
TypeScript 99.6%
JavaScript 0.4%