Arri RPC - 字迹服务器
字型执行 阿里·RPC它建在顶部 H3级 和用途 兴建 为捆绑.
参数和反应的定义使用 @ arrirpc/ schema (韩语) 对投入和产出进行自动验证和序列化,并为客户发电机生成Arri类型定义.
目录
快速启动
# npm
npx arri init [project-name]
cd [project-name]
npm install
npm run dev
# pnpm
pnpm dlx arri init [project-name]
cd [project-name]
pnpm install
pnpm run dev
手动设置
安装依赖关系
# npm
npm install -D arri
npm install @arrirpc/server @arrirpc/schema
# pnpm
pnpm install -D arri
pnpm install @arrirpc/server @arrirpc/schema
脚手架您的工程
一个基本的Arri应用目录看起来是这样的:
|-- project-dir
|-- .arri // temp files go here
|-- .output // final bundle goes here
|-- src
|-- procedures // .rpc.ts files go here
|-- app.ts
|-- arri.config.ts
|-- package.json
|-- tsconfig.json
|
两者 .arri 财务报告和审定财务报表 .output 应添加到您的 .gitignore 文件
.arri
.output
node_modules
配置文件
创建一个 arri.config.ts 在项目目录中
// arri.config.ts
import { defineConfig, servers, generators } from 'arri';
export default defineConfig({
server: servers.tsServer(),
generators: [
generators.typescriptClient({
// options
}),
generators.dartClient({
// options
}),
],
});
App 条目
在您的 src 目录中创建应用程序条目文件 。 文件名称必须匹配您所显示的 entry 在你身边 arri.config.ts。 。 。 .
// ./src/app.ts
import { ArriApp } from 'arri';
const app = new ArriApp();
export default app;
软件包.json
设置 npm 脚本 :
{
"name": "my-arri-app",
"type": "module",
"scripts": {
"dev": "arri dev",
"build": "arri build"
},
"dependencies": {
...
},
"devDependencies": {
...
}
}
使用量
创建程序
基于文件的运行
ARRI RPC 带有一个基于文件的可选路由器,该路由器将在 ./procedures 以 .rpc.ts 文件扩展名.
|-- src
|-- procedures
|-- sayHello.rpc.ts // becomes sayHello()
|-- users
|-- getUser.rpc.ts // becomes users.getUser()
|-- updateUser.rpc.ts // becomes users.updateUser()
示例 .rpc.ts 文件
// ./src/users/getUser.rpc.ts
import { defineRpc } from '@arrirpc/server';
import { a } from '@arrirpc/schema';
export default defineRpc({
params: a.object({
userId: a.string(),
}),
response: a.object({
id: a.string(),
name: a.string(),
createdAt: a.timestamp(),
}),
handler({ params }) {
// function body
},
});
自定义基于文件的路由器
export default defineConfig({
servers: servers.tsServer({
procedureDir: 'procedures', // change which directory to look for procedures (This is relative to the srcDir)
procedureGlobPatterns: ['**/*.rpc.ts'], // change the file name glob pattern for finding rpcs
}),
// rest of config
});
手动运行
对于那些想要选择退出基于文件的路由系统的人,您可以手动注册这样的程序.
// using the app instance
const app = new ArriApp()
app.rpc('sayHello',
defineRpc({...})
);
// defining a service
const app = new ArriApp();
const usersService = defineService("users");
usersService.rpc("getUser", defineRpc({...}));
usersService.rpc("createUser", defineRpc({...}));
// register the service on the app instance
app.use(usersService);
还有一个简写,用于初始化 服务与程序
// this is equivalent to what we showed above
const usersService = defineService("users", {
getUser: defineRpc({..}),
createUser: defineRpc({..}),
});
创建事件流程序
活动流程程序利用 服务器已发送事件 将事件流到客户端.
Arri事件流发送了以下事件类型:
message- 以JSON为序列的响应数据的标准信息start- 启动溪流的起始信息done- 告诉客户 不会再发生任何事件heartbeat- 服务器定期发送信息,以保持连接.
/// message event ///
id: string | undefined;
event: 'message';
data: Response; // whatever you have specified as the response serialized to json
/// done event ///
event: 'done';
data: 'this stream has ended';
/// heartbeat event ///
event: 'heartbeat';
data: '';
示例使用法:
// procedures/users/watchUser.rpc.ts
export default defineEventStreamRpc({
params: a.object({
userId: a.string(),
}),
response: a.object({
id: a.string(),
name: a.string(),
createdAt: a.timestamp(),
updatedAt: a.timestamp(),
}),
handler({ params, stream }) {
// initialize the stream and send it to the client
stream.send();
// send a message every second
const interval = setInterval(async () => {
await stream.push({
id: '1',
name: 'John Doe',
createdAt: new Date(),
updatedAt: new Date(),
});
}, 1000);
// cleanup when the client disconnects
stream.on('close', () => {
clearInterval(interval);
});
},
});
事件串联方法
// send the stream to the client. Must be called before pushing any messages
stream.send()
// push a new message to the client
stream.push(data: Data, eventId?: string)
// close the stream and tell the client that there will be no more messages
stream.close()
// register a callback that will fire after the stream has been close by the server or the connection has been dropped
stream.onClosed(cb: () => any)
添加非 RPC 路由
对于基于信件的 RPC 端点不符合的情况,也可以添加通用端点.
// using the app instance
const app = new ArriApp();
app.route({
method: "get",
path: "/hello-world",
handler(event) {
return "hello world";
},
});
// using a sub-router
const app = new ArriApp();
const router = new ArriRouter();
router.route({
method: "get",
path: "/hello-world",
handler(event) {
return "hello world",
}
})
app.use(router)
// sup-routers can also specify a route prefix
const router = new ArriRouter("/v1")
router.route({
method: "get",
path: "/hello-world", // this will become /v1/hello-world
handler(event) {
return "hello world"
}
});
正在添加中间软件
const app = new ArriApp();
const requestLoggerMiddleware = defineMiddleware((event) => {
console.log(`new request at ${event.path}`);
});
app.use(requestLoggerMiddleware);
添加到 RPC 背景
添加到任何数值 event.context 将在 rpc 实例中可用
const authMiddleware = defineMiddleware(async (event) => {
// assume you did something to get the user from the request
event.context.user = {
id: 1,
name: 'John Doe',
email: '[email protected]',
};
});
app.rpc('sayHello', {
params: undefined,
response: a.object({
message: a.string(),
}),
// user is available here
handler({ user }) {
return {
message: `Hello ${user.name}`,
};
},
});
要为这些新属性获得类型安全性 .d.ts 文档和增强 ArriEventContext 国家 @arrirpc/server
import '@arrirpc/server';
declare module '@arrirpc/server' {
interface ArriEventContext {
user?: {
id: number;
name: string;
email: string;
};
}
}
背景工人和生命周期钩
您可以注册背景工人 和生命周期钩 ArriApp当应用程序在编码时导入时,工人和生命周期钩不运行(arri build);它们只在网页服务器开始收听时开始.
import { ArriApp, defineWorker } from '@arrirpc/server';
const app = new ArriApp({
onStart() {
console.log('Server is starting up');
},
onStop() {
console.log('Server is shutting down');
},
onWorkerError(err, worker) {
logger.error({ err, worker: worker.name }, 'Worker error');
},
});
// Registering a worker with a continuous loop and AbortSignal
app.registerWorker(
defineWorker({
name: 'queue-consumer',
async start(signal) {
while (!signal.aborted) {
const job = await queue.pop({ signal });
if (job) await processJob(job);
}
},
async stop() {
// Optional: cleanup or drain
},
}),
);
// Registering a third-party worker (e.g. BullMQ)
app.registerWorker(
defineWorker({
name: 'bullmq-emails',
async start() {
worker = new Worker('emails', async (job) => {
await sendEmail(job.data);
});
},
async stop() {
await worker.close();
},
}),
);
// Function shorthand
app.registerWorker(async (signal) => {
while (!signal.aborted) {
await heartbeat();
await delay(5000, signal);
}
});
服务处还可对工人进行登记:
const authService = new ArriService('auth');
authService.registerWorker(tokenCleanupWorker);
app.use(authService); // Workers will be registered on the app
添加客户端生成器
// arri.config.ts
import { defineConfig, servers, generators } from "arri";
export default defineConfig({
server: servers.tsServer(),
generators: [
generators.typescriptClient({...}),
generators.dartClient({...}),
generators.kotlinClient({...})
generators.someGenerator({...})
]
});
有关现有发电机的信息见 这里
要了解如何创建自己的生成器,请查看 @ arrirpc/ codegen- utils
关键概念
Arri 定义文件
服务器生成一个 __definition.json 用于设计 API 中的所有程序和模型。 默认情况下, 此方案可以从 /__definition 当服务器运行时,但可以修改. 终点也与 rpcRoutePrefix 选项.
看起来是这样的:
{
"procedures": {
"sayHello": {
"transport": "http",
"path": "/say-hello",
"method": "post",
"params": "SayHelloParams",
"response": "SayHelloResponse"
}
// rest of procedures
},
"definitions": {
"SayHelloParams": {
"properties": {
"name": {
"type": "string"
}
}
},
"SayHelloResponse": {
"properties": {
"message": {
"type": "string"
}
}
}
// rest of models
}
}
Arri能够使用这个schema文件自动生成多种语言的客户端. 这样它的工作方式类似于一个开放的API schema,但得到了更好的代码生成支持. 在设计这个计划时,我做了许多刻意的选择,使代码生成更容易,语言之间更加一致. 例如,Arri schemas使用一个超级集,即: JSON 类型定义 他们的模型而不是JSON Schema.
如何将程序映射到终点
每个程序都根据它的名字 绘制到一个不同的url 例如,以下文件结构:
|--src
|--procedures
|--getStatus.rpc.ts
|--users
|--getUser.rpc.ts
|--updateUser.rpc.ts
我们将获得以下终点:
POST /get-status
POST /users/get-user
POST /users/update-user
(Note: these will always be relative to the `rpcRoutePrefix` option)
默认情况下,所有程序都会成为员额请求,但在创建程序时可以更改:
// procedures/users/getUser.rpc.ts
export default defineRpc({
method: 'get',
// rest of config
});
支持的HTTP方法如下:
- 员额
- 起来
- 删除
- 补丁
- 设置
当使用获取方法时, RPC 参数将被映射为查询参数,该参数将使用 a.coerce 方法从 arri-validate。获取方法支持所有基本的 scalar 类型,无论阵列和嵌入对象不支持.
H3 支助
阿里建在顶部 H3级 因此适用于H3的许多概念也适用于Arri.
上网公用事业
Arri重新运送所有H3公用事业.
import { getRequestIP, setResponseHeader } from '@arrirpc/server';
访问 H3 事件
您可以通过内部程序处理器访问 H3 事件 .
defineRpc({
params: undefined,
response: undefined,
handler(_, event) {
getRequestIP(event);
},
});
defineEventStreamRpc({
params: undefined,
response: undefined,
handler(_, event) {
getRequestIP(event);
},
});
手动启动 Arri 服务器
Arri服务器只是引擎盖下的一个H3应用程序,这样你就可以以同样的方式启动H3应用程序. 尽管您应该注意的是,目前基于备案的路由器只在使用Arri CLI时才有效.
import { createServer } from 'node:http';
import { ArriApp, toNodeListener } from '@arrirpc/server';
const app = new ArriApp();
createServer(toNodeListener(app.h3App)).listen(process.env.PORT || 3000);
阿里·CLI
# start the dev server
arri dev [flags]
# create a production build
arri build [flags]
# create a new project
arri init [dir]
# run codegen
arri codegen [path-to-definition-file]
发展
大楼
运行 nx build arri-rpc 来建造图书馆.
运行单元测试
运行 nx test arri-rpc 通过Vitest执行单元测试.