Remote 契約
契約描述宿主與實作共用的協定,不包含實作位置。實作位置由宿主項目指定。
用 Contract.service 描述每個跨越邊界的服務:它的鍵、列出各個呼叫的 Effect
RpcGroup,以及兩個函式。
import { Context, Effect, Schema } from "effect"import { Rpc, RpcGroup } from "effect/rpc"
import { Contract } from "@graftland/plugin-remote"
export class GreetingUnavailable extends Schema.TaggedError< GreetingUnavailable, { readonly brand: unique symbol }>()("GreetingUnavailable", { cause: Schema.Defect() }) {}
export class Greeter extends Context.Service< Greeter, { readonly greet: (name: string) => Effect.Effect<string, GreetingUnavailable> }>()("app/Greeter") {}
const api = RpcGroup.make( Contract.operation( "@app/greeter/Greet", 1, Rpc.make("Greet", { payload: { name: Schema.String }, success: Schema.String, error: GreetingUnavailable, }), ),)
const greeter = Contract.service(Greeter, api, { toHandlers: (service) => ({ Greet: ({ name }) => service.greet(name) }), fromClient: (client) => ({ greet: (name) => client .Greet({ name }) .pipe( Effect.catchTag("RpcClientError", (cause) => GreetingUnavailable.make({ cause }), ), ), }),})
export const contract = Contract.make({ name: "app/greeter", config: Schema.Struct({ greeting: Schema.String }), provides: [greeter],})toHandlers 在實作服務的一側執行,fromClient 在使用服務的一側執行。請在
fromClient 中轉換
RpcClientError,讓連線中斷以呼叫端本來就會處理的錯誤出現。Contract.make
再把這些描述彙整成契約:
| 欄位 | 意義 |
|---|---|
name |
外掛名稱。 |
config |
設定 schema,必須能編碼為 JSON。 |
error |
外掛宣告的失敗的 schema,預設為 Schema.Never。 |
inject |
實作使用的宿主服務。 |
provides |
實作發布給宿主的服務。 |
- 契約中的 schema 都不能需要服務才能編碼或解碼。
- 服務鍵在同一份契約內必須唯一;RPC 名稱可以在不同服務間重複。
- 以
contract或預設匯出的形式匯出契約。 - 雙方匯入同一份建置後的契約,所以鍵與 RPC schema 一致。
服務中的每個 RPC 都透過 Contract.operation 取得不可變的 name@version 識別。
| 規則 | 意義 |
|---|---|
| 名稱與版本 | 名稱不得為空或含空白,版本必須是正整數。 |
| 協定變更 | schema、編碼或語意改變時,提升該操作的版本。 |
| 傳輸標籤 | Effect RPC 的標籤保持不變,它不是操作識別。 |
| 服務中繼資料 | service.operations 列出 RPC 群組中的操作識別。缺少識別或同一服務內識別重複會被拒絕。 |
| 相容性 | Contract.compareOperations(required, provided) 檢查精確的集合包含關係。提供方可以有額外操作;新版本不能滿足舊版本的要求。 |
比較是純粹的中繼資料檢查,不比較 schema,也不檢查執行中的實作。套件版本與摘要屬於部署資訊,不決定操作相容性。
import { Array } from "effect"
import { Contract } from "@graftland/plugin-remote"
import { contract } from "./src/contract.ts"
const required = Array.flatMap( contract.provides, (service) => service.operations,)Contract.compareOperations(required, required) // => ({ _tag: "Compatible" })Contract.compareOperations(required, []) // => ({ _tag: "Incompatible", missing: [{ name: "@app/greeter/Greet", version: 1 }] })在 Plugin.make({ provides }) 中宣告服務描述,再在 apply 中用 Graft.provide
發布服務值。overlap: true 宣告服務可以安全地與另一次服務嘗試並行執行,預設為
false。這些宣告不會啟用重疊轉換,也不改變重新啟動行為。
import { Effect } from "effect"
import { Graft, Plugin } from "@graftland/graft"
import { contract, Greeter } from "./contract.ts"
export default Plugin.make({ name: "app/greeter", config: contract.config, provides: contract.provides, overlap: true, apply: ({ greeting }) => Graft.provide(Greeter, { greet: (name) => Effect.succeed(`${greeting}, ${name}!`), }),})投影契約操作
Section titled “投影契約操作”MCP 投影透過注入的服務公開明確註解的操作。Contract.identity(rpc)
讀取操作身分,不解釋 schema。MCP JSON
Schema 只描述工具輸入,絕不參與來源紀錄或相容性。