From 2f619b1b88ebd0ffc054ff24852c8775d98946a1 Mon Sep 17 00:00:00 2001 From: imccyu <276526105+imccyu@users.noreply.github.com> Date: Fri, 7 Aug 2026 01:59:34 +0800 Subject: [PATCH] docs: document TypeRT API-Gateway --- docs/api-gateway.i18n.yaml | 6 ++ docs/api-gateway.md | 157 ++++++++++++++++++++++++++++++++++++ docs/api-gateway.zh.md | 157 ++++++++++++++++++++++++++++++++++++ docs/architecture.i18n.yaml | 4 +- docs/architecture.md | 1 + docs/architecture.zh.md | 1 + docs/development.i18n.yaml | 4 +- docs/development.md | 2 + docs/development.zh.md | 2 + 9 files changed, 330 insertions(+), 4 deletions(-) create mode 100644 docs/api-gateway.i18n.yaml create mode 100644 docs/api-gateway.md create mode 100644 docs/api-gateway.zh.md diff --git a/docs/api-gateway.i18n.yaml b/docs/api-gateway.i18n.yaml new file mode 100644 index 0000000000..87abb10c88 --- /dev/null +++ b/docs/api-gateway.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/api-gateway.md +api-gateway.md: 76af93880d278a17dc46370fd5065fdcdadb9fb6 +api-gateway.zh.md: d447cea6b64bf88084f86a210a5f654bd9445d6c diff --git a/docs/api-gateway.md b/docs/api-gateway.md new file mode 100644 index 0000000000..76af93880d --- /dev/null +++ b/docs/api-gateway.md @@ -0,0 +1,157 @@ +# API Gateway + +English | [中文](api-gateway.zh.md) + +This is the current-state reference for the TypeRT API Gateway. It describes how business services declare unary Remote methods, how the build generates Host and Client contracts, and how calls reuse the Connection RPC and `/api` route. Session events, incremental data, and other streaming protocols are outside this document's scope; they may use the same Connection but do not use Remote method descriptors. + +## Programming model + +Business services use `@Remote` or `@RemoteContext` to select the methods exposed to the Client. Unmarked methods do not enter the generated Client types or runtime contributions and cannot be called through `ctx.api`. + +`@Remote` denotes calling a Cordis service registered on the root Host Context. Complex Host objects cannot cross the wire directly; the business package must declare their association with a wire identity through `TypeRTLookupMap` and register a resolution provider with `ctx.typert.lookups` at runtime. For example, an `Agent` parameter named `agent` in the Host signature produces an `agentId` wire field, and the Gateway resolves that id to the current live object before invoking the business method. + +`@RemoteContext(key)` first resolves an identity to a scoped Context through `ctx.typert.contexts`, then obtains the service from that Context and invokes the method. It applies when the method itself depends on scoped composition and does not need to receive objects such as `Agent` explicitly. + +Services normally extend `GatewayService` so the constructor explicitly binds the Cordis service key and default Remote namespace. A service that already has another base class can instead declare `readonly typertGateway = bindTypeRTGateway(this, serviceKey)`; both forms leave an inspectable public binding and do not depend on the compiler injecting a symbol into the constructor. + +```ts +import type { Agent } from '@deepseek-ai/dsh-agent' +import { GatewayService, Remote, RemoteContext } from '@deepseek-ai/dsh-type-meta' +import type { Context } from 'cordis' + +export interface CreateGoalRequest { + objective: string +} + +export interface CreateGoalResult { + accepted: boolean +} + +export class GoalService extends GatewayService { + constructor(ctx: Context) { + super(ctx, 'goals') + } + + @Remote('create') + createForClient( + agent: Agent, + request: CreateGoalRequest, + signal: AbortSignal, + ): CreateGoalResult { + signal.throwIfAborted() + return this.create(agent, request) + } + + @RemoteContext('agent', 'current') + currentForClient(): CreateGoalResult { + return { accepted: true } + } + + private create(_agent: Agent, request: CreateGoalRequest): CreateGoalResult { + return { accepted: request.objective.length > 0 } + } +} +``` + +Remote methods may return a value synchronously or return a Promise. For cooperative cancellation, the final parameter in the Host signature must be `signal: AbortSignal` using the global type; it is recorded in the descriptor instead of entering `args`, while the generated Client method accepts an optional final `AbortSignal`. + +The Client uses concrete functions on ordinary objects, not a JavaScript Proxy. Direct Remotes appear under `ctx.api.`; when an `@Remote` method has exactly one lookup parameter and a same-named `TypeRTContextMap` uses the same wire identity, the generator also projects the method without that identity parameter onto the corresponding scoped Context. `@RemoteContext` generates only the scoped invocation interface. + +```ts +import type { SessionId } from '@deepseek-ai/dsh-session/types' +import type { AgentContext } from '@deepseek-ai/dsh-client-runtime/client' +import type { Context } from 'cordis' +import type {} from '@deepseek-ai/dsh-client-remotes/client' + +declare const ctx: Context +declare const agentCtx: AgentContext +declare const agentId: SessionId + +await ctx.api.goals.create(agentId, { objective: 'ship it' }) +await agentCtx.goals.create({ objective: 'ship it' }) +``` + +Client applications assemble only `@deepseek-ai/dsh-client-remotes`. That package imports the `/remote` subpaths of selected business packages as runtime values, mounts their contributions on `ctx.api`, and re-exports the declaration merges from the same files. Adding a Host Remote package is an explicit choice by the Client composition owner; business components do not need to load the Host API Gateway or the business package's Remote JS separately. + +A future TUI can assemble the same React-independent `client-remotes` and `ctx.api` contract, so the Host methods visible to it are likewise limited to the Remote methods selected at generation time. This document does not define or implement the TUI composition. + +## Component responsibilities + +| Location | Package or entry | Responsibility | +|---|---|---| +| Shared | `@deepseek-ai/dsh-type-meta` | Declares decorators, Gateway bindings, merge-extensible protocol maps, invocation descriptors, and provider types; starts no TypeScript analysis and registers no Cordis services | +| Build | `@deepseek-ai/dsh-typert-generator` | Strictly analyzes Remote signatures, the type graph, lookups, Contexts, and source locations from the Host `ts.Program`, then generates Host and Host-for-Client artifacts | +| Host | `@deepseek-ai/dsh-typert-registry` and Loader | Places generated Host descriptors, schemas, and business-package registrations in `ctx.typert`, and holds lookup and Context providers | +| Host | `@deepseek-ai/dsh-host-api-gateway` | Provides `ctx.typertGateway`, claims Remote endpoints, resolves objects or Contexts, invokes live Cordis services, and validates boundaries | +| Client | `@deepseek-ai/dsh-host-api-gateway/client` | Provides `ctx.api`, mounts generated descriptors as concrete methods, and initiates, validates, and cancels calls through the Connection | +| Client | `@deepseek-ai/dsh-client-remotes/client` | Explicitly selects and mounts the `/remote` contributions allowed by the application and brings the corresponding declaration merges into business code | +| Both | `@deepseek-ai/dsh-client-connection` | Provides the RPC carrier, request correlation, trust boundary, cancellation, response envelope, and current `/api` HTTP bridge | + +The Host API Gateway package owns the Host dispatcher and Client API as peer entries, but the two builds never enter the same `ts.Program`. The Host entry does not import the Client Cordis `Context` merge, and the Client entry does not import the Host Gateway service. + +## Strict generation pipeline + +The root build orders `build:lib:host`, `build:lib:client`, and `build:web`. The Host lib build first runs `build:lib:contracts`: it compiles the TypeRT generator, then starts a Host `ts.Program` through `tsdown.typert-host.config.ts` with `tsconfig.host.json` as its seed. The generator does not put the Host and Client aggregates in the same program, so it does not trigger conflicts between the two Cordis `Context` declaration merges. + +Each contributing business package writes generated files to its own `lib/` directory, not to its source directory: + +| File | Consumer | Contents | +|---|---|---| +| `typert.host.js` | Host Loader | Runtime reflection for the Host face, strict invocation descriptors, and schema registration values | +| `typert.host.d.ts` | Host type system | Generated declarations for the Host face | +| `typert.remote-client.js` | `client-remotes` | A mountable `TypeRTRemoteContribution` containing strict descriptors and runtime codecs | +| `typert.remote-client.d.ts` | Client type system | Declaration merges for `TypeRTRemoteNamespaceMap` and `TypeRTRemoteContextMap`, plus Client-safe type references | +| `typert.remote-client.d.ts.map` | Editor | Maps generated method properties back to Remote method declarations in the Host package | + +Business packages expose the Host Loader entry through `./typert` and the Host-for-Client entry through `./remote`. The generator also validates these package exports and published-file lists; it generates artifacts only for explicit contribution packages that provide the corresponding entry. + +Parameter names in Remote Client declarations come from wire fields, while parameter and return types reference Client-safe types exported by the original business package. The declaration map resolves the generated property behind `ctx.api.goals.create` back to the Host source method marked with `@Remote`, so editors that support declaration maps can navigate from a Client call to the real implementation instead of stopping at the generated `.d.ts`. + +Strict analysis requires a Remote to be a public, non-static instance method with a concrete implementation. The method cannot be generic; parameters must be required, named simple identifiers and cannot use destructuring, default values, rest parameters, or optional parameters. TypeRT generates strict schemas for ordinary JSON-representable types; complex objects such as workspace classes must have a unique `TypeRTLookupMap` declaration. Lookup and Context packages are responsible for both static declaration merges and runtime provider registration; if either side is missing, the build or earliest resolvable runtime boundary fails. + +## Runtime invocation + +Remote and API Proxy currently share the Connection's `/api` route; there is no separate `/api2` server or second Connection. The Client API calls `connection.rpc.call('/api', '/', { args }, signal)`; the current HTTP carrier maps this to `POST /api//`, with a payload containing only a named `args` object. + +The Connection performs the unified trust check for `/api` before the HTTP bridge, then dispatches inside the shared FetchHandler in interceptor order. The TypeRT Gateway claims only two-segment endpoints that have a strict descriptor or active SRC marker; unclaimed requests fall back to the existing API Proxy. The Connection owns transport, RPC ids, response envelopes, and request cancellation, while the Gateway owns only the Remote data protocol and business dispatch. Replacing the Connection carrier in the future does not require changes to Remote descriptors or the Client programming interface. + +For every call, the Gateway resolves the descriptor and live service from the current registries instead of caching business objects. It requires the fields in `args` to match the descriptor exactly, validates wire values with codecs, resolves objects or receivers through registered lookup or Context providers, invokes the service method targeted by the binding, and validates the return value. A missing provider, unknown identity, binding mismatch, missing or extra argument, schema failure, or missing method fails at the boundary before entering or after leaving business code. + +Unloading a Client contribution removes its descriptors and concrete methods together, aborts its in-flight calls, and makes stale method handles retained by external code reject further calls. A strict endpoint withdrawn on the Host also does not degrade to SRC inference, preventing a hot unload from silently weakening validation. + +## SRC development fallback + +When the Host starts from source through `node --import tsx/esm`, it does not execute the TypeRT compiler plugin. Standard decorator initializers still record the method name and invocation mode in a module-private `WeakMap`, while `GatewayService` or `bindTypeRTGateway()` supplies the explicit service binding; the Gateway can therefore construct a weaker temporary descriptor without starting a `ts.Program`. + +The SRC fallback parses simple parameter names from the live function. When a parameter name matches the `parameter` of a registered lookup, such as `agent` or `session`, it uses the lookup's `agentId` or `sessionId` wire field and resolves the object on the Host; other parameters are checked only for cycle-free, JSON-safe data with no special prototype. `@RemoteContext` directly uses the wire field of a registered Host Context provider. SRC does not read TypeScript types, generate Zod schemas, infer optional parameters, or support destructuring, default values, rest parameters, or duplicate parameter names. + +SRC solves only dispatch for a Host process running from source. The Client does not discover decorators from the running Host, and the Client API refuses to mount SRC descriptors that lack strict codecs; its types, codecs, and Remote registration values always come from the most recently generated `lib/typert.remote-client.*` artifacts. + +## Development mode + +A complete build generates Host contracts before compiling the Host, Client, and Web, so it is the deterministic entry for creating or refreshing all artifacts: + +```sh +pnpm run build +``` + +Web development normally starts the source Host after one complete build and runs the Client plugin watcher in another terminal: + +```sh +pnpm run dsh -- web --dev +pnpm run dev:web +``` + +`dsh` starts the Host source through tsx, so the Host can use the SRC fallback; `dev:web` watches only Client plugins with a `dshClient` declaration and rewrites their `lib/client.js`. It does not analyze Host decorators or generate Remote Client DTS. + +Changing only a Remote method's implementation body without changing its contract does not require regenerating the TypeRT files. After adding or removing a decorator or changing an export name, namespace, parameter, return value, lookup, Context, or cancellation signature, regenerate the strict contracts before the Client bundle consumes the new artifacts: + +```sh +pnpm run build:lib:contracts +``` + +The running Client watcher consumes these generated files when it rebundles; without a watcher, run `pnpm run build:lib:client`. Recompiling only the frontend source cannot infer new types from Host decorators. `pnpm run typecheck` includes `build:lib:contracts` as a prerequisite, and CI and release builds also use the strict generation pipeline. + +## Boundaries + +Remote handles only unary method calls with one request and one result. Session event streams, pagination, incremental reduce, projection, and entity substreams require a separate data protocol and registration model; even when they reuse the Connection, they must not masquerade as Remote methods or enter invocation descriptors. diff --git a/docs/api-gateway.zh.md b/docs/api-gateway.zh.md new file mode 100644 index 0000000000..d447cea6b6 --- /dev/null +++ b/docs/api-gateway.zh.md @@ -0,0 +1,157 @@ +# API Gateway + +[English](api-gateway.md) | 中文 + +本文是 TypeRT API Gateway 的当前状态参考。它描述业务 Service 如何声明一元 Remote 方法、构建如何生成 Host 与 Client 契约,以及调用如何复用 Connection 的 RPC 与 `/api` 路由。会话事件、增量数据和其他流协议不属于本文范围;它们可以使用同一个 Connection,但不使用 Remote 方法描述符。 + +## 编程模型 + +业务 Service 通过 `@Remote` 或 `@RemoteContext` 选择对 Client 开放的方法。未标记的方法不会进入生成的 Client 类型或运行时贡献,也不能通过 `ctx.api` 调用。 + +`@Remote` 表示调用根 Host Context 中注册的 Cordis Service。复杂的 Host 对象不能直接跨 wire 传输;业务包必须通过 `TypeRTLookupMap` 声明它与 wire identity 的关联,并在运行时向 `ctx.typert.lookups` 注册解析提供方。例如 `Agent` 参数在 Host 签名中名为 `agent`,生成的 wire 字段为 `agentId`,Gateway 在调用业务方法前将 id 解析为当前的实时对象。 + +`@RemoteContext(key)` 表示先通过 `ctx.typert.contexts` 把 identity 解析为一个作用域 Context,再从该 Context 取得 Service 并调用方法。它适用于方法本身依赖作用域组合、而不需要显式接收 `Agent` 等对象的情形。 + +Service 通常继承 `GatewayService`,让 Cordis service key 与默认 Remote namespace 在构造器中显式绑定。已有其他基类的 Service 可以改为声明 `readonly typertGateway = bindTypeRTGateway(this, serviceKey)`;两种方式都会留下可检查的公开 binding,不依赖编译器向构造函数注入 symbol。 + +```ts +import type { Agent } from '@deepseek-ai/dsh-agent' +import { GatewayService, Remote, RemoteContext } from '@deepseek-ai/dsh-type-meta' +import type { Context } from 'cordis' + +export interface CreateGoalRequest { + objective: string +} + +export interface CreateGoalResult { + accepted: boolean +} + +export class GoalService extends GatewayService { + constructor(ctx: Context) { + super(ctx, 'goals') + } + + @Remote('create') + createForClient( + agent: Agent, + request: CreateGoalRequest, + signal: AbortSignal, + ): CreateGoalResult { + signal.throwIfAborted() + return this.create(agent, request) + } + + @RemoteContext('agent', 'current') + currentForClient(): CreateGoalResult { + return { accepted: true } + } + + private create(_agent: Agent, request: CreateGoalRequest): CreateGoalResult { + return { accepted: request.objective.length > 0 } + } +} +``` + +Remote 方法可以同步返回或返回 Promise。若需要协作式取消,Host 签名的最后一个参数必须是全局类型的 `signal: AbortSignal`;它记录在描述符中而不是进入 `args`,Client 生成的方法则接受最后一个可选的 `AbortSignal`。 + +Client 使用普通对象上的具体函数,不使用 JavaScript Proxy。直接 Remote 出现在 `ctx.api.`;当一个 `@Remote` 方法恰好有一个 lookup 参数、且同名 `TypeRTContextMap` 使用相同 wire identity 时,生成器还会把去掉该 identity 参数后的方法投影到对应作用域 Context。`@RemoteContext` 只生成作用域调用界面。 + +```ts +import type { SessionId } from '@deepseek-ai/dsh-session/types' +import type { AgentContext } from '@deepseek-ai/dsh-client-runtime/client' +import type { Context } from 'cordis' +import type {} from '@deepseek-ai/dsh-client-remotes/client' + +declare const ctx: Context +declare const agentCtx: AgentContext +declare const agentId: SessionId + +await ctx.api.goals.create(agentId, { objective: 'ship it' }) +await agentCtx.goals.create({ objective: 'ship it' }) +``` + +Client 应用只装配 `@deepseek-ai/dsh-client-remotes`。该包以运行时值导入被选业务包的 `/remote` 子路径,并向 `ctx.api` 挂载贡献,同时重新导出相同文件中的声明合并。增加一个 Host Remote 包是 Client 组合所有者的显式选择;业务组件不需要分别加载 Host API Gateway 或业务包的 Remote JS。 + +未来的 TUI 可以装配同一个不依赖 React 的 `client-remotes` 与 `ctx.api` 契约,因此它能看到的 Host 方法同样只限于生成时选择的 Remote 方法。本文不定义或实现 TUI 组合。 + +## 组件职责 + +| 位置 | 包或入口 | 职责 | +|---|---|---| +| 共享 | `@deepseek-ai/dsh-type-meta` | 声明 decorator、Gateway binding、可合并协议映射、调用描述符及提供方类型;不启动 TypeScript 分析,也不注册 Cordis 服务 | +| 构建 | `@deepseek-ai/dsh-typert-generator` | 从 Host `ts.Program` 严格分析 Remote 签名、类型图、lookup、Context 与源码位置,并生成 Host 和 Host-for-Client 产物 | +| Host | `@deepseek-ai/dsh-typert-registry` 与 Loader | 把生成的 Host 描述符、schema 及业务包注册项放入 `ctx.typert`,并持有 lookup 与 Context 提供方 | +| Host | `@deepseek-ai/dsh-host-api-gateway` | 提供 `ctx.typertGateway`,认领 Remote endpoint,解析对象或 Context,调用实时 Cordis Service 并校验边界 | +| Client | `@deepseek-ai/dsh-host-api-gateway/client` | 提供 `ctx.api`,把生成的描述符挂成具体方法,并通过 Connection 发起、校验和取消调用 | +| Client | `@deepseek-ai/dsh-client-remotes/client` | 显式选择并挂载本应用允许使用的 `/remote` 贡献,向业务代码带入对应的声明合并 | +| 双侧 | `@deepseek-ai/dsh-client-connection` | 提供 RPC carrier、请求关联、信任边界、取消、响应 envelope 与当前 `/api` HTTP bridge | + +Host API Gateway 包同时拥有 Host dispatcher 与 Client API 两个对等入口,但两侧构建不会进入同一个 `ts.Program`。Host 入口不导入 Client 的 Cordis `Context` 合并,Client 入口也不导入 Host Gateway 服务。 + +## 严格生成链路 + +根构建按 `build:lib:host`、`build:lib:client`、`build:web` 排序。Host lib 构建首先运行 `build:lib:contracts`:它先编译 TypeRT generator,再通过 `tsdown.typert-host.config.ts` 以 `tsconfig.host.json` 为种子启动 Host `ts.Program`。生成器不会把 Host 与 Client 聚合放入同一个 program,因而不会触发两侧 Cordis `Context` 声明合并冲突。 + +每个贡献业务包把生成文件写入自己的 `lib/`,而不是源码目录: + +| 文件 | 消费方 | 内容 | +|---|---|---| +| `typert.host.js` | Host Loader | Host face 的运行时反射、严格调用描述符和 schema 注册值 | +| `typert.host.d.ts` | Host 类型系统 | Host face 的生成声明 | +| `typert.remote-client.js` | `client-remotes` | 可挂载的 `TypeRTRemoteContribution`,包含严格描述符与运行时 codec | +| `typert.remote-client.d.ts` | Client 类型系统 | `TypeRTRemoteNamespaceMap` 与 `TypeRTRemoteContextMap` 的声明合并及 Client-safe 类型引用 | +| `typert.remote-client.d.ts.map` | 编辑器 | 将生成的方法属性映射回 Host 包中的 Remote 方法声明 | + +业务包通过 `./typert` 暴露 Host Loader 入口,通过 `./remote` 暴露 Host-for-Client 入口。生成器同时校验这些 package export 及发布文件清单;只有具备相应入口的显式贡献包才会生成产物。 + +Remote Client 声明中的参数名来自 wire 字段,参数和返回类型则引用原业务包导出的 Client-safe 类型。声明 map 把 `ctx.api.goals.create` 最终解析到的生成属性映射到带 `@Remote` 的 Host 源方法,因此支持 declaration-map 的编辑器可以从 Client 调用跳到真实实现,而不是停在生成的 `.d.ts`。 + +严格分析要求 Remote 是公开、非静态、有具体实现的实例方法。方法不能是泛型;参数必须是具名且必填的简单标识符,不能使用解构、默认值、rest 或可选参数。可 JSON 表示的普通类型由 TypeRT 生成严格 schema;工作区 class 等复杂对象必须具有唯一的 `TypeRTLookupMap` 声明。lookup 与 Context 包同时负责静态声明合并和运行时提供方注册,缺少任一侧都会在构建或最早可解析的运行时边界报错。 + +## 运行时调用 + +当前 Remote 与 API Proxy 共用 Connection 的 `/api` 路由,不存在独立 `/api2` server 或第二套 Connection。Client API 调用 `connection.rpc.call('/api', '/', { args }, signal)`;当前 HTTP carrier 对应 `POST /api//`,payload 只包含一个具名 `args` 对象。 + +Connection 在 HTTP bridge 之前执行 `/api` 的统一信任检查,再在共享 FetchHandler 内按 interceptor 顺序分发。TypeRT Gateway 只认领存在严格描述符或活跃 SRC marker 的两段式 endpoint;未认领的请求回退到既有 API Proxy。Connection 拥有传输、RPC id、响应 envelope 和 request cancellation,Gateway 只拥有 Remote 数据协议和业务分发。未来替换 Connection carrier 不要求改变 Remote 描述符或 Client 编程界面。 + +Gateway 每次调用都从当前注册表解析描述符和实时 Service,不缓存业务对象。它要求 `args` 的字段集合与描述符完全一致,先用 codec 校验 wire 值,再通过注册的 lookup 或 Context provider 解析对象或接收者,最后调用 binding 指向的 Service 方法并校验返回值。缺少 provider、identity 未命中、binding 不一致、参数多缺、schema 失败和方法不存在都在进入或离开业务边界时失败。 + +Client 卸载一个贡献时会一起移除描述符和具体方法,中止其进行中的调用,并使外部仍持有的旧方法句柄拒绝继续调用。Host 上已经注册过的严格 endpoint 被撤回后也不会降级到 SRC 推断,以免热卸载悄然降低校验强度。 + +## SRC 开发回退 + +Host 通过 `node --import tsx/esm` 从源码启动时不会执行 TypeRT 编译插件。标准 decorator 初始化器仍会把方法名和调用模式记录到模块私有 `WeakMap`,`GatewayService` 或 `bindTypeRTGateway()` 则提供显式 service binding;Gateway 因而可以在不启动 `ts.Program` 的情况下构造一个较弱的临时描述符。 + +SRC 回退从运行中函数解析简单参数名。参数名与某个已注册 lookup 的 `parameter` 相同,例如 `agent` 或 `session`,就使用其 `agentId` 或 `sessionId` wire 字段并在 Host 解析对象;其他参数只检查值是否为无循环、无特殊 prototype 的 JSON-safe 数据。`@RemoteContext` 直接使用已注册 Host Context provider 的 wire 字段。SRC 不读取 TypeScript 类型,不生成 Zod schema,不推断可选参数,也不支持解构、默认值、rest 或重复参数名。 + +SRC 只解决 Host 源码进程的分发问题。Client 不会从运行中的 Host 发现 decorator,Client API 也拒绝挂载缺少严格 codec 的 SRC 描述符;其类型、codec 和 Remote 注册值始终来自最近一次生成的 `lib/typert.remote-client.*`。 + +## 开发模式 + +完整构建会先生成 Host 契约,再编译 Host、Client 与 Web,因此是建立或刷新所有产物的确定性入口: + +```sh +pnpm run build +``` + +Web 开发通常在完成一次构建后启动源码 Host,并在另一个终端运行 Client plugin watcher: + +```sh +pnpm run dsh -- web --dev +pnpm run dev:web +``` + +`dsh` 通过 tsx 启动 Host 源码,所以 Host 可以使用 SRC 回退;`dev:web` 只监听带 `dshClient` 声明的 Client plugin 并重写其 `lib/client.js`,它不会分析 Host decorator,也不会生成 Remote Client DTS。 + +只修改 Remote 方法实现体而不改变契约时,无需重新生成 TypeRT 文件。新增或删除 decorator、修改导出名、namespace、参数、返回值、lookup、Context 或取消签名时,先重新生成严格契约,再让 Client bundle 使用新的产物: + +```sh +pnpm run build:lib:contracts +``` + +运行中的 Client watcher 会在重新打包时消费这些生成文件;没有 watcher 时运行 `pnpm run build:lib:client`。仅重新编译前端源码不能从 Host decorator 推导新类型。`pnpm run typecheck` 自带 `build:lib:contracts` 前置步骤,CI 与发布构建也使用严格生成链路。 + +## 边界 + +Remote 只处理有单个请求与单个结果的一元方法调用。Session event stream、分页、增量 reduce、projection 和实体子流需要独立的数据协议与注册模型;即使它们复用 Connection,也不应伪装成 Remote 方法或放入调用描述符。 diff --git a/docs/architecture.i18n.yaml b/docs/architecture.i18n.yaml index 8daacae254..774bc296b1 100644 --- a/docs/architecture.i18n.yaml +++ b/docs/architecture.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/architecture.md -architecture.md: 81464d9c8800556565c84d33239882dc750180a8 -architecture.zh.md: c02bca4f12c3758723b0fc818c89080dccf435d9 +architecture.md: db5991d98dfbc6b04992d62d5a465c375c9a78b8 +architecture.zh.md: 2eb8c3834a6ffc3283c8aa669be481b534bb5914 diff --git a/docs/architecture.md b/docs/architecture.md index 81464d9c88..db5991d98d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -48,6 +48,7 @@ Harnesses are [Cordis](cordis-primer.md) contexts; packages contribute services, | `ctx.credentials` | [`credentials/`](../packages/credentials/README.md) | named secret references resolved per operation, never inlined in configuration | | `ctx.directoryPicker` | [`host/directory-picker`](../packages/host/directory-picker/README.md) | GUI-host directory picking (`native`/`browse` interactions) | | `ctx.typert` | [`typert/registry`](../packages/typert/registry/README.md) | runtime registry for generated package reflection and live Zod schemas | +| `ctx.typertGateway` | [`host/api-gateway`](../packages/host/api-gateway/README.md) | dispatches TypeRT Remote unary calls through the [API Gateway](api-gateway.md) | | `ctx.invariants` | [`support/invariants`](../packages/support/invariants/README.md) | package-name-selected registry of package-owned runtime checks | ## Event diff --git a/docs/architecture.zh.md b/docs/architecture.zh.md index c02bca4f12..2eb8c3834a 100644 --- a/docs/architecture.zh.md +++ b/docs/architecture.zh.md @@ -48,6 +48,7 @@ | `ctx.credentials` | [`credentials/`](../packages/credentials/README.md) | 具名密钥引用,按操作解析,绝不内联进配置 | | `ctx.directoryPicker` | [`host/directory-picker`](../packages/host/directory-picker/README.md) | GUI 宿主目录选取(`native`/`browse` 交互) | | `ctx.typert` | [`typert/registry`](../packages/typert/registry/README.md) | 生成的包反射和实时 Zod schema 的运行时注册表 | +| `ctx.typertGateway` | [`host/api-gateway`](../packages/host/api-gateway/README.md) | 通过 [API Gateway](api-gateway.md) 分发 TypeRT Remote 一元调用 | | `ctx.invariants` | [`support/invariants`](../packages/support/invariants/README.md) | 按包名筛选包自有运行时检查的注册表 | ## 事件 diff --git a/docs/development.i18n.yaml b/docs/development.i18n.yaml index 95ed34cce0..2ea336b1f0 100644 --- a/docs/development.i18n.yaml +++ b/docs/development.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/development.md -development.md: 30a2bd0a2c97df8d3d75ec50f47b861b3a65590e -development.zh.md: 5582a85429c97c3e31517a495c69392b80885f7d +development.md: d480f548dd24ea81d132e4b4c0cc364ce1b0cd53 +development.zh.md: 08ef7fd2d3da7db83eb3ca4dff9f9c85f6d7cb5e diff --git a/docs/development.md b/docs/development.md index 30a2bd0a2c..d480f548dd 100644 --- a/docs/development.md +++ b/docs/development.md @@ -62,6 +62,8 @@ Host and client stay two aggregate programs because both sides declaration-merge Static analysis and tests resolve workspace imports through the base `paths` map to `src` and must pass on a clean tree; gates that consume built `lib/` output declare that dependency explicitly. Decision record: [solution-root note](../.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md); the tsc-first emit pipeline is the [ts-build-config note](../.agents/notes/implemented/process/2026-06-17-ts-build-config.md). +Business services declare callable methods on the Host with `@Remote` or `@RemoteContext`; the Host build generates Host-for-Client types and runtime contributions, and the Client's `client-remotes` composition loads those contributions and mounts concrete methods on `ctx.api` or the corresponding scoped Context. See [API Gateway](api-gateway.md) for the generated artifacts on both sides, their assembly relationships, the SRC development fallback, and the Web build order. + If a relevant local check consumes built package output, build once first: ```sh diff --git a/docs/development.zh.md b/docs/development.zh.md index 5582a85429..08ef7fd2d3 100644 --- a/docs/development.zh.md +++ b/docs/development.zh.md @@ -62,6 +62,8 @@ host 与 client 保持两个聚合 program,是因为两侧在相同键下以 静态分析和测试通过 base 的 `paths` 映射把工作区 import 解析到 `src`,且必须在干净树上通过;消费构建产物 `lib/` 的门禁显式声明该依赖。决策记录:[solution-root note](../.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md);tsc-first 发射管线见 [ts-build-config note](../.agents/notes/implemented/process/2026-06-17-ts-build-config.md)。 +业务 Service 在 Host 使用 `@Remote` 或 `@RemoteContext` 声明可调用方法;Host 构建生成 Host-for-Client 类型与运行时贡献,Client 的 `client-remotes` 组合加载这些贡献并将具体方法挂载到 `ctx.api` 或对应的作用域 Context。两侧的生成产物、装配关系、SRC 开发回退和 Web 构建顺序见 [API Gateway](api-gateway.md)。 + 如果相关的本地检查需要使用构建后的包产物,请先构建一次: ```sh