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

@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 会自动执行:

  1. 使用存储的 refresh token 发送 POST /auth/refresh(并发的 401 会合并为一次刷新请求)。
  2. 刷新成功 — 调用 authStore.save(auth) 保存新 token,然后重试原请求一次。
  3. 刷新失败 — 调用 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