refactor(typert): bind remote services through base class

This commit is contained in:
imccyu
2026-08-06 23:04:25 +08:00
parent 1ea5507bf8
commit e8f2ab89bb
17 changed files with 213 additions and 61 deletions

View File

@@ -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 packages/typert/type-meta/README.md
README.md: 95716446c01c7fd510cdf55a82509b5b8af6f3ae
README.zh.md: 0d30b3122265d9bb3caa289345f843fe67377be3
README.md: 245df305efcf711486b2d3f32e40a8b415f2682e
README.zh.md: 592aa5d027a52a7a277a90ba5d51f19101f055f6

View File

@@ -2,18 +2,19 @@
English | [中文](README.zh.md)
Compiler-independent declarations shared by business packages, generated TypeRT artifacts, the Host Gateway, and Client API. This package owns Remote decorators, the explicit Service binding, merge-extensible protocol maps, invocation descriptors, codecs, and provider contracts; it does not run TypeScript analysis or provide a Cordis service.
Compiler-independent declarations shared by business packages, generated TypeRT artifacts, the Host Gateway, and Client API. This package owns the Remote Service base, decorators, explicit binding fallback, merge-extensible protocol maps, invocation descriptors, codecs, and provider contracts; it does not run TypeScript analysis or register a concrete Cordis service.
## Remote declarations
- `@Remote` marks a public instance method for direct invocation on its registered Cordis Service.
- `@RemoteContext(key)` marks a method whose receiver is selected from a merge-declared scoped Context kind.
- `bindTypeRTGateway(this, serviceKey, options?)` creates the visible, frozen binding between a Service instance, its exact Cordis key, and its wire namespace.
- `GatewayService` binds the Cordis key passed to `super(ctx, serviceKey, options?)` to the same default wire namespace.
- `bindTypeRTGateway(this, serviceKey, options?)` provides the same visible, frozen binding for a Service that cannot inherit from `GatewayService`.
- `remoteMethods(service)` returns a detached declaration-order snapshot used by the Gateway's SRC fallback.
A Host method opts into cooperative cancellation by declaring `signal: AbortSignal` as its final parameter. `InvocationDescriptor.cancellation` records that reserved injection point; the signal never becomes a JSON parameter or lookup field. SRC recognizes the final parameter name, while strict generation also verifies the global `AbortSignal` type.
Decorator initializers retain markers in a module-private `WeakMap` keyed by the Service prototype. They do not add constructor symbols, prototype properties, parameter metadata, or runtime reflection fields. The Service opts in explicitly through its `typertGateway` binding field.
Decorator initializers retain markers in a module-private `WeakMap` keyed by the Service prototype. They do not add constructor symbols, prototype properties, parameter metadata, or runtime reflection fields. A `GatewayService` exposes the same public readonly `typertGateway` binding that the explicit helper returns.
## TypeRT protocol

View File

@@ -2,18 +2,19 @@
[English](README.md) | 中文
该包提供不依赖编译器的声明,由业务包、生成的 TypeRT 产物、Host Gateway 和 Client API 共享。它负责 Remote 装饰器、显式服务绑定、可通过声明合并扩展的协议映射、调用描述符、编解码器和提供方契约;它不执行 TypeScript 分析,也不提供 Cordis 服务。
该包提供不依赖编译器的声明,由业务包、生成的 TypeRT 产物、Host Gateway 和 Client API 共享。它负责 Remote Service 基类、装饰器、显式 binding 回退、可通过声明合并扩展的协议映射、调用描述符、编解码器和提供方契约;它不执行 TypeScript 分析,也不注册具体 Cordis 服务。
## Remote 声明
- `@Remote` 将公开实例方法标记为可在其注册的 Cordis 服务上直接调用。
- `@RemoteContext(key)` 标记接收者选自合并声明的作用域 Context 类型的方法。
- `bindTypeRTGateway(this, serviceKey, options?)` 在服务实例、其准确的 Cordis key 与协议命名空间之间创建可见且冻结的绑定。
- `GatewayService` 将 `super(ctx, serviceKey, options?)` 接收的 Cordis key 同时绑定为默认 wire namespace。
- `bindTypeRTGateway(this, serviceKey, options?)` 为无法继承 `GatewayService` 的 Service 提供同样可见且冻结的绑定。
- `remoteMethods(service)` 返回按声明顺序排列、与内部状态分离的快照,供 Gateway 的 SRC 回退路径使用。
Host 方法通过将 `signal: AbortSignal` 声明为最后一个参数来启用协作式取消。`InvocationDescriptor.cancellation` 记录这个保留的注入点;signal 绝不会成为 JSON 参数或 lookup 字段。SRC 识别末位参数名,严格生成还会校验它是否具有全局 `AbortSignal` 类型。
装饰器初始化器将标记保存在以服务 prototype 为键的模块私有 `WeakMap` 中。它们不会在构造函数上添加 symbol,也不会添加 prototype 属性、参数元数据或运行时反射字段。服务通过自身的 `typertGateway` 绑定字段显式接入。
装饰器初始化器将标记保存在以服务 prototype 为键的模块私有 `WeakMap` 中。它们不会在构造函数上添加 symbol,也不会添加 prototype 属性、参数元数据或运行时反射字段。`GatewayService` 会暴露与显式 helper 相同的 public readonly `typertGateway` 绑定。
## TypeRT 协议

View File

@@ -4,6 +4,7 @@
* @module @deepseek-ai/dsh-type-meta
*/
import { Service, type Context } from 'cordis'
import type { TypeRTContextMap } from './types.ts'
export type {
@@ -104,6 +105,23 @@ export function bindTypeRTGateway<Service extends object>(
return Object.freeze({ service, serviceKey, namespace })
}
/** Cordis Service base that exposes its registered name through TypeRT Gateway. */
export abstract class GatewayService<out T = never> extends Service<T> {
/** Visible binding consumed by the Gateway's source-mode discovery. */
readonly typertGateway: TypeRTGatewayBinding<this>
/**
* Register the Service and bind the same key to TypeRT Gateway.
* @param ctx - owning Cordis Context.
* @param serviceKey - exact Cordis service key and default wire namespace.
* @param options - optional distinct wire namespace.
*/
protected constructor(ctx: Context, serviceKey: string, options: TypeRTGatewayBindingOptions = {}) {
super(ctx, serviceKey)
this.typertGateway = bindTypeRTGateway(this, this.name, options)
}
}
/**
* Mark one public instance method as a direct Remote invocation.
* @param _method - decorated method; retained only by the class itself.

View File

@@ -1,12 +1,15 @@
import { Context } from 'cordis'
import {
bindTypeRTGateway,
GatewayService,
Remote,
RemoteContext,
remoteMethods,
} from '@deepseek-ai/dsh-type-meta'
class Goals {
readonly typertGateway = bindTypeRTGateway(this, 'goals')
class Goals extends GatewayService {
constructor(ctx: Context) {
super(ctx, 'goals')
}
@Remote
create(value: string): string {
@@ -19,7 +22,7 @@ class Goals {
}
}
const methods = remoteMethods(new Goals())
const methods = remoteMethods(new Goals(new Context()))
const actual = JSON.stringify(methods)
const expected = JSON.stringify([
{ method: 'create', invocation: { kind: 'direct' } },

View File

@@ -1,8 +1,10 @@
import { execFileSync } from 'node:child_process'
import { fileURLToPath } from 'node:url'
import { Context } from 'cordis'
import { describe, expect, it } from 'vitest'
import {
bindTypeRTGateway,
GatewayService,
Remote,
RemoteContext,
remoteMethods,
@@ -16,9 +18,11 @@ declare module '@deepseek-ai/dsh-type-meta' {
}
describe('type-meta Remote declarations', () => {
it('executes standard decorator syntax through the Vitest source transform', () => {
class Goals {
readonly typertGateway = bindTypeRTGateway(this, 'goals')
it('binds a GatewayService name and executes decorators through the Vitest source transform', async () => {
class Goals extends GatewayService {
constructor(ctx: Context) {
super(ctx, 'goals')
}
@Remote
create(value: string): string {
@@ -31,11 +35,26 @@ describe('type-meta Remote declarations', () => {
}
}
const goals = new Goals()
class NamespacedGoals extends GatewayService {
constructor(ctx: Context) {
super(ctx, 'internalGoals', { namespace: 'goals' })
}
}
const ctx = new Context()
const goals = new Goals(ctx)
const namespaced = new NamespacedGoals(ctx)
expect(goals.typertGateway).toEqual({ service: goals, serviceKey: 'goals', namespace: 'goals' })
expect(namespaced.typertGateway).toEqual({
service: namespaced,
serviceKey: 'internalGoals',
namespace: 'goals',
})
expect(remoteMethods(goals)).toEqual([
{ method: 'create', invocation: { kind: 'direct' } },
{ method: 'scoped', invocation: { kind: 'context', context: 'metaFixture' } },
])
await ctx.fiber.dispose()
})
it('executes standard decorator syntax through the TSX source launcher', () => {