@foir/sdk

中文文档
0.34.0发布于 2026/11/09MITTypeScriptES Module近 30 天下载 2477

官方 Foir SDK – 编辑 Iframe 客户端 + 服务器侧平台帮助器

中文使用文档
文档目录13 节
  1. 安装
  2. 子路径导出
  3. 使用量
  4. 已键入数据客户端( 没有 GraphQL, 没有代码阶梯)
  5. 从服务器加载器输入的图形QL
  6. 将用户签名
  7. 反应
  8. 非后端SPA(Foir主持会议)
  9. Next.js (App路由器, BFF) (英语)
  10. 代码
  11. 编辑器扩展
  12. 现场阅读
  13. 许可证

翻译:

官方Foir SDK——客户端-不可知的GraphQL传输,为您的应用用户签名, 进步媒体、内容渲染、实时协作和编辑/服务器 用于创建应用程序的助手 转弯 平台.

SDK被分割为独立的子路径输出,所以消费者只拉什么 他们使用。 所有重物 (react, (中文(简体) ), graphql, (中文(简体) ), yjs, (中文(简体) ), hls.js, )是一个 可选的对等依赖性被锁定到需要它的子路径上.

安装

pnpm add @foir/sdk
# plus the peers for the subpaths you use, e.g.:
pnpm add graphql            # for @foir/sdk/client + @foir/sdk/auth
pnpm add react              # for @foir/sdk (editor), /media, /content
pnpm add yjs y-websocket    # for @foir/sdk/realtime

子路径导出

导入这是什么同伴
@foir/sdk编辑器 iframe 客户端 - EditorProvider, (中文(简体) ), useEditor, (中文(简体) ), useAutoResize, (中文(简体) ), createPlatformClient,主机编辑器信件协议react
@foir/sdk/clientSSR/接地装载器的客户-不可知图QL运输 - foirRequest, (中文(简体) ), isFoirPreview, (中文(简体) ), absolutiseFoirMediaUrlgraphql
@foir/sdk/data输入数据客户端结束 /graphql —— 说 createFoirClient每个键形行动, include编号 :omit, (中文(简体) ), fetchFoirManifest; 为您打印文档, 所以仍然没有 graphql无
@foir/sdk/auth为您的应用程序用户提供框架不可知的签名:登录/登记/OTP/提供者流量、象征性生命周期(SessionAdapter, (中文(简体) ), createFoirSessionClient (fir主持饼干会), authedRequest警卫graphql
@foir/sdk/reactDrop- in React 认证 - <FoirProvider> (电话或 hostedSession 模式), useFoirSession编号 :useFoirUser编号 :useFoirAuth, (中文(简体) ), <SignIn>, (中文(简体) ), <OAuthButtons>, (中文(简体) ), <UserButton>react, (中文(简体) ), graphql
@foir/sdk/nextNext.js App 路由器 BFF 认证 : createFoirCookieAdapter, (中文(简体) ), getFoirSession, (中文(简体) ), foirSignIn*, (中文(简体) ), startFoirProviderSignIn, (中文(简体) ), finishFoirProviderSignIn, (中文(简体) ), createFoirMiddlewaregraphql
@foir/sdk/server用于扩展/配置 API 后端的服务器端助手 —— createServerClient, (中文(简体) ), verifyWebhookSignature, (中文(简体) ), verifyScopedToken客户端—— 说
@foir/sdk/liveLive reads——福尔公司无效图中的客户端:标签索引,活载量商店, seq 命令、 更改记录 ~ 标记映射无
@foir/sdk/live/react<FoirLive> 一个套接字,每个模型重新计算,连接到您已有的查询客户端react
@foir/sdk/media进步媒体—— ProgressiveMedia, (中文(简体) ), BlurhashCanvas, (中文(简体) ), useProgressiveLoad,变位适配器react (单位:千美元)hls.js, (中文(简体) ), blurhash 可选)
@foir/sdk/content内容的原始人—— createContentRenderer 块发送 + Lexical RichTextRendererreact
@foir/sdk/realtime细串联帮助结束 y-websocket - 警犬室地址、存在、装饰品刷新yjs, (中文(简体) ), y-websocket
@foir/sdk/codegen构建时的图克l-代码源预设( T)foirCodegen+ 详尽无遗的块注册插件@graphql-codegen/cli, (中文(简体) ), @graphql-codegen/client-preset (滴答声)

使用量

已键入数据客户端( 没有 GraphQL, 没有代码阶梯)

@foir/sdk/data 执行相同 /graphql 结束点 其他消费者 使用——一个执行表面,一个执行者,一组范围检查,一个答复 缓存。 您跳过的是写入文档: 客户端加载选择 树 GET /v1/manifest 发布操作,应用您的 include编号 :omit,以及 打印一个.

打印一个预先解开的树是字符串结构, 因此软件包仍然需要 没有 graphql 完全依赖 没有 graphql,没有 @graphql-typed-document-node/core,没有 @graphql-codegen/*,没有新的对等点.

import { createFoirClient } from '@foir/sdk/data';
// generated by `foir types` from GET /v1/manifest
import * as generated from './foir.generated';

const foir = createFoirClient({
  apiUrl: process.env.PLATFORM_API_URL!,   // a bare origin or the /graphql URL
  apiKey: process.env.PLATFORM_API_KEY!,
  generated,                               // the whole module: types, trees, version
});

const post = await foir.post.findUnique({
  naturalKey: 'hello',
  include: { author: true },   // relations opt in
  omit: ['bodyRichtext'],      // drop the heavy field
});

const posts = await foir.post.findMany({ first: 10 });   // a Relay connection
const hits  = await foir.ns.search.searchRecords({ query: 'shoes' });
const other = await foir.raw('{ designTokens { colors } }');  // any document, any root field

一个导入, 没有类型参数 . generated 载着一半的 foir types 写作—— FoirSchema 类型和运行时间选择树 文件的印制来自 FoirSchema 推定而不是重述, 并且一个添加到发电机上的文物 之后花费你的呼叫站点 什么也没有。 A 类 模块中缺少一个导出是一个编译错误,而不是安静 贬:通通取出,或不通取出.

它替换的字段( :surfaceVersion, (中文(简体) ), surfaceHashes, (中文(简体) ), selections, (中文(简体) ), namespaceSelections, (中文(简体) ), selectionUnions)仍然工作,并且仍然赢得了两者 已经给出.

漂流被地表按键. 模块记录 surfaceVersion 这个 产生自: 散起于什么 foir types 读取,仅移动 当再生成会写出不同的文件时。 推动操作、角色或 键会移动服务器的无效计数器而不是表面,所以不是 漂来漂去。 在类型包括的模型上发布字段,客户端 报告一次,命名模型.

漂流不会停止通话. 已经长大的地表仍然能回答每一个问题 文档模块打印。 拒绝打入的电话 FoirSchemaDriftError, (中文(简体) ), 只有在它所依赖的东西不再作为生成服务时: 选择名称、其行动、其论点之一或关系的目标 包含。 信件名称 drift.incompatible 列表中 ,作为路径(models.order.selection.status) (中文(简体) ). 补救措施是: foir types· ; strictSchemaDrift: false 从来没有.

斯卡勒总是完整的这样 post.title 键入到当前和 实值 目前没有部分select 脚枪. include 添加关系密钥, omit 移除了平地,多形态的地块在 __typename 在详尽无遗的情况下 switch 没有铸造物.

客户是由你的证书塑造的. 生成的 FoirSchema 包含 所以只读可公布的密钥没有 create 改为 电话 :

foir.post.create({ data });   // ✗ compile error under a publishable key: not in its manifest

generated 是可选的。 没有它,也没有 selections - 客户 获取 GET /v1/manifest 第一次使用 并读取同样的树 从它。 A 类 倒计时,所以一个零码的开始工作, 成本在前方的往返 每一个寒冷引用的第一个问题。 上面说,一次,当它建成的时候 那样的话 安静 announceManifestFallback: false。 。 。 .

可共享的读取器发送到 GET /cdn/graphql 仅限 在 URL 中坚持- query 散列, 这样您前面的 CDN 可以服务下一个 访问者未到达原籍地。 这是一个查询,一个可公布的密钥,没有 用户无记名, 在已公布的频道上; 其他的都是 POST /graphql。 。 。 .

foir.raw() 是逃生舱门,它需要一个 图QL 文档 - 这个 达到任何证书所能达到的,包括根字段,没有模型或命名空间 索赔。 整个都回来了 data 对象.

命名空间( N)search, (中文(简体) ), users, (中文(简体) ), notifications(活在地下) foir.ns 因为 客户端没有运行时间计划从模型中告诉命名空间. ns 财务报告和审定财务报表 raw 是唯一的保留名称.

带会话的读取 bearerToken,作为字符串或已解决的函数 请:

const foir = createFoirClient({
  apiUrl,
  apiKey,
  generated,
  bearerToken: () => session.get('foir_access_token'),
});

已经 PLATFORM_API_URL 编号 : PLATFORM_API_KEY 编号 : FOIR_PREVIEW 电线 图QL 路径 ? createFoirClientFromEnv(env, { generated }) 重新使用它们, 包括预览密钥和预览频道草稿.

一份以组织名称为基础的产品,其中要求与 orgId, (中文(简体) ), 客户端发送的 X-Org-Id 每一个请求。 这是为项目 证书——后台,或刚刚解决子域的应用程序 一个组织; 存储面继续使用仅限于其自身的证书 组织 orgId 无法向别处行驶 :

const foir = createFoirClient({ apiUrl, apiKey, generated, orgId });

从服务器加载器输入的图形QL

foirRequest 是输入的 fetch 包装器, 不是 GraphQL 客户端: 它携带 B. 与特定地点有关的运输问题x-api-key,预览计划通道,出错 形状)和运行生成 TypedDocumentNodes. 带来客户 (Apollo/urql/react-query)直接使用生成的文档并复制 那些头.

import { foirRequest, type FoirEnv } from '@foir/sdk/client';
import { HomePageDocument } from './generated/graphql';

export async function loader({ context }: LoaderArgs) {
  const env: FoirEnv = {
    PLATFORM_API_URL: context.env.PLATFORM_API_URL,
    PLATFORM_API_KEY: context.env.PLATFORM_API_KEY,
  };
  const data = await foirRequest(env, HomePageDocument, { slug: 'home' });
  return data.homePage;
}

将用户签名

import {
  signInWithPassword,
  authedRequest,
  type SessionAdapter,
} from '@foir/sdk/auth';
import { MyProfileDocument } from './generated/graphql';

// SessionAdapter = { get, set, delete } over your session store (cookie, KV…).
const user = await signInWithPassword(adapter, env, { email, password });

// Bearer-authed request with automatic refresh-and-retry on expired sessions.
const { myProfile } = await authedRequest(adapter, env, MyProfileDocument);

反应

@foir/sdk/react 将反应层建在 @foir/sdk/auth组合 您的应用程序一次,然后用钩子读取会话或者在组件中掉入. 这个 默认会话存储为 localStorage; 通过自己的 adapter 饼干.

'use client';
import { FoirProvider, SignIn, UserButton, useFoirSession } from '@foir/sdk/react';

const env = {
  PLATFORM_API_URL: process.env.NEXT_PUBLIC_PLATFORM_API_URL,
  PLATFORM_API_KEY: process.env.NEXT_PUBLIC_PLATFORM_API_KEY, // a publishable key
};

export function App() {
  return (
    <FoirProvider env={env}>
      <Header />
      <SignIn afterSignInUrl="/dashboard" />
    </FoirProvider>
  );
}

function Header() {
  const { isAuthenticated, isLoading } = useFoirSession();
  if (isLoading) return null;
  return isAuthenticated ? <UserButton afterSignOutUrl="/" /> : null;
}

对于自定义表单, 使用 useFoirAuth() (单位:千美元)signIn, (中文(简体) ), register, (中文(简体) ), signInWithOtp, (中文(简体) ), requestOtp, (中文(简体) ), signInWithProvider, (中文(简体) ), signOut, (中文(简体) ), getToken和 useFoirUser()。 。 。 .

非后端SPA(Foir主持会议)

默认 <FoirProvider> 存储符号在 localStorage如果您的 SPA 没有 后端,但你有一个 已验证自定义登录域 同一可登记 域( 选项)app.acme.com + 键 login.acme.com),更喜欢 hostedSession: 持久 证书会变成你的域名上的Htp Only Cookie Foir 和15分钟 访问符只在记忆中存在。 没有与认证相关的内容可以达到 因此,安全局没有什么可告密的——BFF的安全姿态没有 后端 .

<FoirProvider
  env={{ PLATFORM_API_URL: 'https://api.foir.dev/graphql', PLATFORM_API_KEY: 'foir_pub_…' }}
  hostedSession={{ loginOrigin: 'https://login.acme.com' }}
>
  <App />
</FoirProvider>

签入是一个方向,而不是一种形式—— useFoirAuth().hostedSignInUrl() 给你 uRL(在此模式中投放的密码/OTP动作). getToken() 交换 饼干和在过期前悄悄地再交换; signOut() 撤销会话 服务器侧并立即关闭当前客户端,甚至下线. 之后 下个主机的登录重定向回来, 创建新客户端( 或重挂载) 提供方)。 一个被关闭或被摧毁的客户从不恢复交换. 其它的都一样.. useFoirSession, (中文(简体) ), useFoirUser, (中文(简体) ), <UserButton> —— 说 行为也一样.

框架不可预见等同性:

import { createFoirSessionClient } from '@foir/sdk/auth';

const session = createFoirSessionClient({ loginOrigin: 'https://login.acme.com' });
const token = await session.getToken(); // null when signed out

Next.js (App路由器, BFF) (英语)

@foir/sdk/next 将符号保存在 HttpOnly 饼干服务器侧。 保护 用中间软件的路由,在服务器组件中读取会话,并签名于 从路由处理器.

// middleware.ts
import { createFoirMiddleware } from '@foir/sdk/next';
export const middleware = createFoirMiddleware({ signInUrl: '/sign-in' });
export const config = { matcher: ['/dashboard/:path*'] };
// app/dashboard/page.tsx (Server Component)
import { cookies } from 'next/headers';
import { getFoirSession } from '@foir/sdk/next';

export default async function Dashboard() {
  const user = await getFoirSession(await cookies());
  return <p>Signed in as {user?.email}</p>;
}
// app/sign-in/route.ts (Route Handler)
import { cookies } from 'next/headers';
import { foirSignInWithPassword } from '@foir/sdk/next';

export async function POST(req: Request) {
  const { email, password } = await req.json();
  await foirSignInWithPassword({ env, store: await cookies(), email, password });
  return Response.json({ ok: true });
}

代码

// codegen.ts
import { foirCodegen } from '@foir/sdk/codegen';

export default foirCodegen({
  apiUrl: process.env.PLATFORM_API_URL!,
  apiKey: process.env.PLATFORM_API_KEY!,
  // documents: 'app/graphql/foir/**/*.graphql' (default)
  // outputDir: './app/gql/foir/' (default)
});

编辑器扩展

import { EditorProvider, useEditor } from '@foir/sdk';

function App() {
  return (
    <EditorProvider>
      <MyEditor />
    </EditorProvider>
  );
}

function MyEditor() {
  const { init, client } = useEditor(); // record context + platform GraphQL client
  // ...
}

现场阅读

@foir/sdk/live 将已读取的缓存绑定到服务器自身的依赖图中。 每个 可缓存的响应已经告诉你它取决于哪些记录和收藏 上,变化流已经告诉你 当其中之一移动;这是 来.

import { FoirLive, useFoirLive, useFoirLiveStatus } from '@foir/sdk/live/react';

<FoirLive
  queryClient={queryClient}
  subscribe={(model, options, handlers) => foir[model].onChange({ ...handlers, ...options })}
>
  <ProductList />
</FoirLive>;

function ProductList() {
  useFoirLive('product');                       // watch, for as long as this is mounted
  const { newCounts } = useFoirLiveStatus();    // "3 new products — show"
  // ...
}

一个 适配器,不是商店:您保留您的查询库,它拥有 你的数据。 它拥有的是哪个条目的更改无效 记录,而不是模型,所以一个编辑不会反射每个列表.

活着 每个模式的选入。没有订阅,因为提供者 挂载:共享的CDN车道是主要资产,毛毯活地推取读取 把车开到原地.

它不是,也不会变成

没有本地突变日志, 没有离线模式, 没有冲突解决, 没有乐观 倒转. 服务器是权威的: 变异通过线和 无效还原 .

这是一个产品边界,而不是一个阶段。 加上乐观的突变 回滚将使这个图书馆成为另一个类别,有自己的行进图 和永久的支撑表面。 如果你需要,就用一个——这不是它 它的缺失不是一个错误.

以及改变反应的部署

批量进口或出版物的扫射会给一个模型起爆. <FoirLive> 连接它: 一个 25 ms 窗口内一个查询的五十个无效是一个 调试,最多4个调试一次,查询再次失效 中后退还差一点 调色符 coalesceWindowMs 财务报告和审定财务报表 maxConcurrentRefetches。 。 。 .

斯克马漂流是分开的,很容易错过,因为没有别的东西抓住它. A 类 部署重命名字段时, 每个缓存条目都会流出并悄悄错误 ——记录没有改变,没有出现出错,字节描述的是一种图谋 这已经不复存在。 连线一次:

import { createFoirDriftHandler } from '@foir/sdk/live';

createFoirClient({
  // ...
  checkSchemaDrift: true,
  onSchemaDrift: createFoirDriftHandler({
    resync: () => {
      store.resync();
      queryClient.clear();
    },
  }),
});

已知限制:可变排序键

由可变键命令的查询.. updatedAt,或一个 position 用户拖动 - 更新时可以在页面之间有记录移动,因此可能出现两次 或消失,直到下一个完全反射.

故意没有检测到:这需要客户方的知识 排序字段,且完全正确的选项将常规编辑转换为 N- page refetch on most feed. (原始内容存档于2018-09-21). 过 mode="notify" 在屏幕上,它很重要 并按用户的条件进行调试.

刺痛,以及为什么这三种情况不同

Foir 是纯中继光标的无 offset,因此加载的页面保持 他们的秩序和 endCursor 保持正确的。 只有三样东西破了,每样 获得数据支持的处理:

  • 删除 ——行被id删除,与 无获取。该活动带有 id,所以这是立即正确的.
  • 创建 a 包括: 计数不是反弹 创建无法被分解( 记录) 事件没有尸体,你不能在不知道之前放置行 排序),并重排头页预放在读取器中卷下.
  • 更新 查证记录的查询 成员为: 没有改变,所以光标保持.

totalCount 被标记为大致而非调整:它是服务器的 在查询的过滤器下计数,猜测会使一个 stale 数字变成一个 错了.

许可证

麻省理工学院

版本与发布记录

@foir/sdk 共发布 35 个版本,首次发布于 2026/06/24;过去 12 个月发布了 35 个版本。

版本发布日期距今
0.34.0latest2026/11/096 天前
0.33.02026/11/0213 天前
0.32.0next2026/10/2918 天前
0.31.02026/10/2521 天前
0.30.12026/10/2323 天前
0.30.02026/10/2323 天前

关于 @foir/sdk 的常见问题

@foir/sdk 是什么?

官方 Foir SDK – 编辑 Iframe 客户端 + 服务器侧平台帮助器

如何安装 @foir/sdk?

在项目目录运行 npm install @foir/sdk,或使用 pnpm add / yarn add / bun add。

@foir/sdk 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@foir/sdk 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@foir/sdk 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@foir/sdk 可以商用吗?

@foir/sdk 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@foir/sdk 的下载量有多少?

根据 npm 官方下载统计,@foir/sdk 在 2026/10/15 – 2026/11/13 这 30 天内被下载 2,477 次,日均约 83 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@foir/sdk 还在维护吗?

最新版本 0.34.0 发布于 2026/11/09(6 天前);至今共发布 35 个版本,其中过去 12 个月发布了 35 个;首次发布于 2026/06/24。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。