SuperAPI AI 工具集

btst-client-plugin-dev

编程与开发

查看项目

更新于 10-16 08:27

技能介绍

BTST 客户端插件开发(btst-client-plugin-dev)是 better-stack-ai/better-stack 仓库内用于编写 BTST 客户端插件的模式与规范集合。以下内容依据资料整理,未提及的功能、收费、价格、平台或效果暂未提供。

一、用途 该技能面向在 monorepo 内开发 BTST 客户端插件的场景,提供 defineClientPlugin 结构、路由组成(PageComponent/loader/meta)、懒加载、资源声明与查询键工厂、SSR 加载器规则、服务端/客户端模块边界、Provider 接线与客户端覆盖等一整套约定与代码模式。

二、文件结构 资料给出的目录结构为:

src/plugins/{name}/ client/ constants.ts ← 一个字面量程序化插件 id plugin.tsx ← defineClientPlugin 入口 hooks.ts ← 仅含 "use client" 的 React 钩子 components/ pages/ my-page.tsx ← 包装层:ComposedRoute + 懒加载导入 my-page.internal.tsx ← 实际 UI:useSuspenseQuery query-keys.ts ← 资源声明 + 查询键工厂

三、数据钩子:使用 createResource 资料建议不要手写 useQuery/useMutation 以及 isErrorResponse/toError 管线,而是在 query-keys.ts 中一次性声明插件资源,然后自动生成。示例中通过 createResourceQueryKeys、ResourcesDeclaration 声明 posts 资源的 queries(list、detail)与 mutations(create),并给出 createMyQueryKeys 工厂供 SSR 加载器复用。在 client/hooks.ts 中通过 createResource({ plugin: MY_PLUGIN_ID, resources: myResources }) 生成 usePosts、useSuspensePost、useCreatePost 等钩子。

每个查询会生成:use(args, { enabled? })、useSuspense(args)(普通查询),或在 infinite: true 时生成 useInfinite/useSuspenseInfinite。Suspense 变体会自动重抛 refetch 错误。Mutation 会生成 use(),支持声明式 invalidates("resource" 或 "resource.query" 前缀)、可选的 setData 缓存填充,以及失效后 await 的 refresh()。错误会被规范化为 StackError(statusCode、来自 Zod issues 的字段级错误)。

每个资源还暴露:

  • my.posts.useForm({ action, id?, record?, defaults?, toCreateVars?, toUpdateVars?, successMessage?, errorMessage?, redirect?, onSuccess? }):创建/编辑生命周期,编辑时拉取记录、运行正确的 mutation、通过 useNotify() 通知、通过路由适配器重定向,并暴露 fieldErrors(把服务端 Zod issues 映射到 react-hook-form 的 setError)。
  • my.posts.useSelect({ searchArgs, getOptionValue, getOptionLabel, value?, preload? }):防抖的服务端搜索 + 当前值预加载,用于关系选择器。

资料强调 SHARED_QUERY_CONFIG、isErrorResponse、toError、StackError 位于 @btst/stack/plugins/client,不要复制到插件中;blog 插件(src/plugins/blog/query-keys.ts、client/hooks/blog-hooks.tsx)是参考实现。

四、服务端/客户端模块边界 client/plugin.tsx 必须在服务端保持可安全导入。Next.js(包括 SSG 构建)可能在服务端执行 createClientStack(),从而解析每个 *ClientPlugin() 工厂;若该模块标记 "use client" 或导入仅客户端模块,构建可能失败并报 "Attempted to call ... from the server"。规则包括:不要给 client/plugin.tsx 添加 "use client";保持其不含 React 钩子(useState、useEffect 等);把钩子工具放到单独的仅客户端模块(client/hooks.ts)并加 "use client",再从 client/index.ts 重新导出;createResource 来自仅客户端入口 @btst/stack/plugins/client/hooks,服务端安全的 @btst/stack/plugins/client 入口只暴露 createResourceQueryKeys 与声明类型,供 query-keys.ts 和加载器使用;UI 组件可按需保持为客户端组件,只有插件工厂入口必须保持服务端可导入。

五、路由组成 新路由使用 defineRoute / defineRoutes。参数化路由上的 page、loader、meta 处理器各自接收路由上下文,示例中 myRoute 为 "/path/:id",page 渲染 MyPageComponent,loader 仅用于 SSR,meta 用于 SEO 标签。

六、懒加载 所有页面组件使用 React.lazy()。具名导出需要 .then(),例如 lazy(() => import("./components/pages/my-page").then(m => ({ default: m.MyPage })));默认导出可直接 lazy(() => import("./components/pages/my-page"))。

七、SSR 加载器规则 仅在 if (typeof window === "undefined") 守卫内执行;绝不抛出——把错误存入 React Query,让 ErrorBoundary 在渲染时捕获;调用 beforeLoad / afterLoad / onErrorLoad 钩子;使用 queryClient.prefetchQuery() 预填数据;从 @btst/stack/plugins/client 导入 isConnectionError,并在构建期失败时告警。

八、ComposedRoute(包装页面) 示例中 MyPageComponent 使用 ComposedRoute,传入 path、PageComponent(懒加载)、ErrorComponent、LoadingComponent、NotFoundComponent、props 与 onError。资料特别强调:必须无条件传入 LoadingComponent——用 typeof window !== "undefined" 守卫它会改变 React 的 useId() 计数,导致 hydration 不匹配。

九、useSuspenseQuery + 错误抛出(.internal.tsx) useSuspenseQuery 只在首次获取时抛出。需要在 refetch 错误时手动重抛,示例中在 useMyData 内使用 useSuspenseQuery 并设置 staleTime: 60_000、retry: false,然后 if (error && !isFetching) throw error,这是 ErrorBoundary 捕获 refetch 失败所必需的。

十、Provider 接线与客户端覆盖 共享的 API、site、QueryClient 值应放在解析后的客户端 stack 上,而不是插件选项或 provider 覆盖中。示例中 StackProvider 接收 stack、router、auth 与 overrides(如 myPlugin: { uploadImage, localization })。插件覆盖类型只包含插件特有的定制。插件工厂只接收插件特有的选择(如 SEO 与加载器钩子),其 resolve(runtime) 回调从 createClientStack() 接收 API、site、QueryClient、headers 与 credentials。资料强调保持工厂选项独立于 StackProvider 覆盖,不要添加把 provider 字段复制回插件的 config(overrides) 适配器。

十一、注意事项(Gotchas)

  • 插件覆盖中的框架配置:Link、Image、导航与 refresh 来自 StackProvider;API 路径来自解析后的客户端 stack。
  • 从覆盖构建插件配置:插件工厂配置在 getStackClient(queryClient) 中创建;provider 覆盖仅是浏览器运行时定制。
  • staleTime: Infinity:用于不应自动重新获取的数据。
  • Next.js Link href undefined:使用 href={href || "#"} 模式。
  • Suspense 错误未被捕获:在每个 suspense 钩子中添加 if (error && !isFetching) throw error。
  • 缺少 ComposedRoute 包装:没有它,错误会崩溃整个应用而不是进入 ErrorBoundary。
  • client/plugin.tsx 上的 client 指令:当插件工厂在服务端被调用时,可能破坏 SSG/SSR。

十二、完整代码模式 资料指出可参见 REFERENCE.md 获取完整的 SSR 加载器、meta 生成器与查询键工厂代码;参见 EXAMPLES.md 获取完整的 ComposedRoute + 内部页面接线示例。