翻译:
官方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/client | SSR/接地装载器的客户-不可知图QL运输 - foirRequest, (中文(简体) ), isFoirPreview, (中文(简体) ), absolutiseFoirMediaUrl | graphql |
@foir/sdk/data | 输入数据客户端结束 /graphql —— 说 createFoirClient每个键形行动, include编号 :omit, (中文(简体) ), fetchFoirManifest; 为您打印文档, 所以仍然没有 graphql | 无 |
@foir/sdk/auth | 为您的应用程序用户提供框架不可知的签名:登录/登记/OTP/提供者流量、象征性生命周期(SessionAdapter, (中文(简体) ), createFoirSessionClient (fir主持饼干会), authedRequest警卫 | graphql |
@foir/sdk/react | Drop- in React 认证 - <FoirProvider> (电话或 hostedSession 模式), useFoirSession编号 :useFoirUser编号 :useFoirAuth, (中文(简体) ), <SignIn>, (中文(简体) ), <OAuthButtons>, (中文(简体) ), <UserButton> | react, (中文(简体) ), graphql |
@foir/sdk/next | Next.js App 路由器 BFF 认证 : createFoirCookieAdapter, (中文(简体) ), getFoirSession, (中文(简体) ), foirSignIn*, (中文(简体) ), startFoirProviderSignIn, (中文(简体) ), finishFoirProviderSignIn, (中文(简体) ), createFoirMiddleware | graphql |
@foir/sdk/server | 用于扩展/配置 API 后端的服务器端助手 —— createServerClient, (中文(简体) ), verifyWebhookSignature, (中文(简体) ), verifyScopedToken客户端 | —— 说 |
@foir/sdk/live | Live reads——福尔公司无效图中的客户端:标签索引,活载量商店, seq 命令、 更改记录 ~ 标记映射 | 无 |
@foir/sdk/live/react | <FoirLive> 一个套接字,每个模型重新计算,连接到您已有的查询客户端 | react |
@foir/sdk/media | 进步媒体—— ProgressiveMedia, (中文(简体) ), BlurhashCanvas, (中文(简体) ), useProgressiveLoad,变位适配器 | react (单位:千美元)hls.js, (中文(简体) ), blurhash 可选) |
@foir/sdk/content | 内容的原始人—— createContentRenderer 块发送 + Lexical RichTextRenderer | react |
@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 数字变成一个
错了.
许可证
麻省理工学院