@raisfast/sdk
v0.2.5
Published
RaisFast frontend SDK
Readme
安装
pnpm add @raisfast/sdk
# 或
npm install @raisfast/sdk快速开始
import { RaisFast } from "@raisfast/sdk";
const client = new RaisFast("http://localhost:9898/api/v1");
// 登录
await client.auth.login("[email protected]", "password");
console.log(client.auth.user);
// 内容类型(Content Type)增删改查(自定义 CT,例如 "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);认证
// 登录(自动保存到 localStorage)
await client.auth.login("[email protected]", "password");
// 注册
await client.auth.register({
email: "[email protected]",
password: "secret",
nickname: "Alice",
});
// 获取当前用户
const me = await client.auth.getMe();
// 更新资料
await client.auth.updateMe({ nickname: "New Name" });
// 修改密码
await client.auth.changePassword({ old_password: "old", new_password: "new" });
// 登出
await client.auth.logout();默认的 LocalAuthStore 会将认证状态持久化到 localStorage。Access Token 过期时(接口返回 401)会自动刷新。
Token 自动刷新
当请求返回 401 时,SDK 会自动执行:
- 使用存储的 refresh token 发送
POST /auth/refresh(并发的 401 会合并为一次刷新请求)。 - 刷新成功 — 调用
authStore.save(auth)保存新 token,然后重试原请求一次。 - 刷新失败 — 调用
authStore.clear()结束会话。
使用默认的 LocalAuthStore 即可开箱即用。API Token(Bearer rf_...)没有 refresh token,会跳过刷新流程。
如需对接自己的状态管理(如 Zustand、Pinia、Redux)并处理会话过期,可继承 BaseAuthStore 并重写 save() / clear()。下面以 Zustand 为例(myStateStore 是你自己的 store,替换为你实际使用的即可):
import { create } from "zustand";
import { BaseAuthStore, RaisFast } from "@raisfast/sdk";
// 1. 应用的状态 store(Zustand / Pinia / Redux 等)
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. 桥接:SDK AuthStore <-> 你的状态 store
class MyAuthStore extends BaseAuthStore {
constructor() {
super();
// 启动时从你的 store 恢复
const saved = myStateStore.getState();
if (saved.accessToken) {
this._token = saved.accessToken;
this._refreshToken = saved.refreshToken;
}
}
// 登录成功以及每次 token 刷新成功后调用
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);
}
// 刷新失败时调用 — 会话已失效
clear() {
super.clear();
myStateStore.getState().logout();
if (typeof window !== "undefined") {
window.location.href = "/login?expired=1";
}
}
}
// 3. 注入到 SDK client
const client = new RaisFast("http://localhost:9000/api/v1", {
authStore: new MyAuthStore(),
});监听认证状态变化
const unsub = client.authStore.onChange((token, user) => {
console.log("认证状态变化:", { token, user });
}, true); // 立即触发一次当前状态
unsub(); // 取消订阅内容类型(ct)
内容类型(Content Type,简称 CT)是 RaisFast 的核心数据模型。内置 CT(如 posts、pages、products)可直接使用。自定义 CT 通过后台管理界面(Content Type Builder)或 TOML 配置来定义,而非在应用代码中创建。SDK 的职责是对这些 CT 中存储的数据进行读写。
const books = client.ct<Book>("books");
// 分页列表
const page = await books.getList(1, 25, {
sort: "created_at:desc",
// 过滤表达式(PocketBase 风格,支持括号 && ||)
filter: 'status = "published" && price >= 10',
// 或方括号风格字段条件(Strapi 风格)
filterCond: {
status: "published",
"price[$gte]": 10,
"author[$in]": ["Alice", "Bob"],
},
search: "hello",
fields: "id,title",
});
// 全量列表(自动分页拉取)
const all = await books.getFullList({ sort: "title:asc" });
// 获取匹配过滤条件的第一条
const first = await books.getFirstListItem('slug = "hello-world"');
// 单条记录
const book = await books.getOne("id-or-slug");
// 增 / 改 / 删
const created = await books.create({ title: "New Book", author: "Alice" });
await books.update(created.id, { title: "Updated" });
await books.delete(created.id);管理端内容类型
管理端内容类型使用 /admin/cms/ 前缀:
const adminBooks = client.adminCt<Book>("books");管理端(Admin)
// 仪表盘统计
const stats = await client.admin.stats.overview();
// 内容统计
const contentStats = await client.admin.stats.content("posts");
// 趋势
const trends = await client.admin.stats.trends("posts", 30);
// 插件
const plugins = await client.admin.plugins.list();
await client.admin.plugins.enable("my-plugin");
// 内容类型(通过后台 UI / TOML 定义;SDK 仅读取 schema)
const types = await client.admin.contentTypes.list();
const booksSchema = await client.admin.contentTypes.get("books");请求钩子
beforeSend
在请求发送前拦截(例如添加自定义请求头):
client.beforeSend = (url, options) => {
console.log("请求:", url);
return { url, options };
};afterSend
在响应返回后转换数据:
client.afterSend = (response, data) => {
console.log("响应状态:", response.status);
return data;
};请求选项
所有请求方法都接受可选的 RequestOptions:
await books.getList(1, 25, {
headers: { "X-Custom": "value" },
query: { foo: "bar" },
signal: abortController.signal,
fetch: customFetch, // 覆盖全局 fetch
});多租户
client.setTenantId("tenant-123");
client.setTenantId(null); // 重置为默认错误处理
import { SDKError } from "@raisfast/sdk";
try {
await books.getOne("nonexistent");
} catch (e) {
if (e instanceof SDKError) {
console.log(e.code); // 后端错误码
console.log(e.status); // HTTP 状态码
console.log(e.message); // 错误信息
console.log(e.url); // 请求 URL
console.log(e.response); // 完整响应体
console.log(e.isAbort); // 请求是否被中止
console.log(e.originalError); // 原始 Error(如有)
}
}TypeScript
本 SDK 使用 TypeScript 编写并附带类型定义。内容类型支持泛型参数:
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"); // 类型为 Book构建
pnpm build通过 tsup 输出 CJS + ESM + 类型声明。
License
MIT
