feat: add TypeRT remote gateway infrastructure

This commit is contained in:
imccyu
2026-08-05 11:17:47 +08:00
parent effd8e1ebd
commit 64a963da0b
98 changed files with 7812 additions and 444 deletions

View File

@@ -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 packages/typert/type-meta/README.md
README.md: 9dd8dadd07b219c7471c8851262958d4d9e96a43
README.zh.md: 5716f56d988c6d2dd9cd237346c3b02ec9ae7c4e

View File

@@ -0,0 +1,33 @@
# @deepseek-ai/dsh-type-meta
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.
## 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.
- `remoteMethods(service)` returns a detached declaration-order snapshot used by the Gateway's SRC fallback.
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.
## TypeRT protocol
Business packages extend `TypeRTLookupMap` and `TypeRTContextMap` to associate Host objects or scoped Contexts with their wire identities. Generated artifacts extend `TypeRTRemoteMap`, `TypeRTRemoteContextMap`, and `TypeRTRemoteNamespaceMap` so Client imports expose only selected Remote methods. `InvocationDescriptor` is the shared runtime form consumed by the registry, Gateway, and Client API.
Lookup and Context packages own both sides of their contract: declaration merging supplies the static association, while runtime providers register identity resolution with `ctx.typert`. Strict codecs carry generated schemas; `src-json` codecs identify the weaker source-launch path.
## Model Experience
None, as this protocol package declares application reflection and registers no model surface.
#### KV Cache effect
No direct effect.
## Known Limitations and Deferred Work
- Decorator markers contain only the method name and direct or Context invocation mode. Parameter, result, lookup, and schema reflection require the TypeRT build pipeline.
- Remote decorators accept only public, non-static instance methods with string names. SRC execution cannot represent overloaded, destructured, defaulted, or rest-parameter signatures.

View File

@@ -0,0 +1,33 @@
# @deepseek-ai/dsh-type-meta
[English](README.md) | 中文
该包提供不依赖编译器的声明,由业务包、生成的 TypeRT 产物、Host Gateway 和 Client API 共享。它负责 Remote 装饰器、显式服务绑定、可通过声明合并扩展的协议映射、调用描述符、编解码器和提供方契约;它不执行 TypeScript 分析,也不提供 Cordis 服务。
## Remote 声明
- `@Remote` 将公开实例方法标记为可在其注册的 Cordis 服务上直接调用。
- `@RemoteContext(key)` 标记接收者选自合并声明的作用域 Context 类型的方法。
- `bindTypeRTGateway(this, serviceKey, options?)` 在服务实例、其准确的 Cordis key 与协议命名空间之间创建可见且冻结的绑定。
- `remoteMethods(service)` 返回按声明顺序排列、与内部状态分离的快照,供 Gateway 的 SRC 回退路径使用。
装饰器初始化器将标记保存在以服务 prototype 为键的模块私有 `WeakMap` 中。它们不会在构造函数上添加 symbol,也不会添加 prototype 属性、参数元数据或运行时反射字段。服务通过自身的 `typertGateway` 绑定字段显式接入。
## TypeRT 协议
业务包扩展 `TypeRTLookupMap` 和 `TypeRTContextMap`,以关联 Host 对象或作用域 Context 与其协议身份。生成的产物扩展 `TypeRTRemoteMap`、`TypeRTRemoteContextMap` 和 `TypeRTRemoteNamespaceMap`,使 Client 导入后仅暴露选定的 Remote 方法。`InvocationDescriptor` 是供注册表、Gateway 和 Client API 使用的共享运行时形式。
查找包与 Context 包同时负责其契约的两侧:声明合并提供静态关联,运行时提供方则向 `ctx.typert` 注册身份解析。严格编解码器携带生成的 schema;`src-json` 编解码器标识约束更弱的源码启动路径。
## 模型体验
无,因为该协议包声明应用反射,不注册任何模型接口。
#### KV Cache 影响
无直接影响。
## 已知限制与延期工作
- 装饰器标记仅包含方法名,以及直接调用或 Context 调用模式。参数、结果、查找和 schema 反射需要 TypeRT 构建流水线。
- Remote 装饰器只接受具有字符串名称的公开、非静态实例方法。SRC 执行无法表示重载签名,以及包含解构参数、默认参数或剩余参数的方法签名。

View File

@@ -0,0 +1,42 @@
{
"name": "@deepseek-ai/dsh-type-meta",
"description": "Compiler-independent Remote metadata and TypeRT provider protocols",
"version": "0.0.1",
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./invariant": {
"types": "./lib/types/invariant.d.ts",
"default": "./lib/invariant.js"
},
"./types": {
"types": "./lib/types/types.d.ts",
"default": "./lib/types/types.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib/index.js",
"lib/invariant.js",
"lib/types/**/*.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",
"peerDependencies": {
"@deepseek-ai/dsh-invariants": "^0.0.1",
"cordis": "^4.0.0-rc.7"
},
"devDependencies": {
"@deepseek-ai/dsh-invariants": "workspace:^",
"cordis": "^4.0.0-rc.7"
}
}

View File

@@ -0,0 +1,223 @@
/**
* Remote decorators and explicit Gateway bindings backed only by private
* module state. Strict reflection remains a TypeRT compiler responsibility.
* @module @deepseek-ai/dsh-type-meta
*/
import type { TypeRTContextMap } from './types.ts'
export type {
InvocationDescriptor,
InvocationParameterDescriptor,
InvocationSourceLocation,
TypeRTClientContextBinder,
TypeRTCodec,
TypeRTContext,
TypeRTContextMap,
TypeRTContextRegistry,
TypeRTContextWire,
TypeRTDisposer,
TypeRTHostContextProvider,
TypeRTLocalRegistry,
TypeRTLookup,
TypeRTLookupHost,
TypeRTLookupMap,
TypeRTLookupProvider,
TypeRTLookupRegistry,
TypeRTLookupWire,
TypeRTRemoteContextApi,
TypeRTRemoteContextMap,
TypeRTRemoteContextNamespace,
TypeRTRemoteContribution,
TypeRTRemoteMap,
TypeRTRemoteNamespace,
TypeRTRemoteNamespaceMap,
TypeRTRemoteRegistry,
TypeRTRegistryChange,
TypeRTRegistryListener,
TypeRTSchema,
TypeRTService,
} from './types.ts'
/** Options for an explicit Service-to-Gateway binding. */
export interface TypeRTGatewayBindingOptions {
/** Wire namespace; defaults to the Cordis service key. */
readonly namespace?: string
}
/** Visible declaration that one Service participates in TypeRT Gateway export. */
export interface TypeRTGatewayBinding<Service extends object = object> {
readonly service: Service
readonly serviceKey: string
readonly namespace: string
}
/** Invocation mode recorded by a Remote method decorator. */
export type RemoteInvocationMarker =
| { readonly kind: 'direct' }
| { readonly kind: 'context'; readonly context: string }
/** One decorator marker discovered for a live Service instance. */
export interface RemoteMethodMarker {
/** Public instance method carrying the implementation. */
readonly method: string
/** Endpoint method when it differs from the implementation member. */
readonly exportName?: string
readonly invocation: RemoteInvocationMarker
}
type RemoteMethodDecorator = <This extends object, Args extends unknown[], Result>(
method: (this: This, ...args: Args) => Result,
context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Result>,
) => void
interface RemoteInitializerContext<This extends object> {
readonly private: boolean
readonly static: boolean
readonly name: string | symbol
addInitializer(initializer: (this: This) => void): void
}
interface StoredRemoteMethodMarker {
readonly exportName?: string
readonly invocation: RemoteInvocationMarker
}
const markers = new WeakMap<object, Map<string, StoredRemoteMethodMarker>>()
/**
* Bind one visible Service field to a Cordis key and Remote namespace.
* @param service - owning Service instance, normally `this`.
* @param serviceKey - exact Cordis service key.
* @param options - optional distinct wire namespace.
* @returns a frozen, inspectable binding with no compiler-injected metadata.
*/
export function bindTypeRTGateway<Service extends object>(
service: Service,
serviceKey: string,
options: TypeRTGatewayBindingOptions = {},
): TypeRTGatewayBinding<Service> {
validateName('service key', serviceKey)
const namespace = options.namespace ?? serviceKey
validateName('namespace', namespace)
return Object.freeze({ service, serviceKey, namespace })
}
/**
* Mark one public instance method as a direct Remote invocation.
* @param _method - decorated method; retained only by the class itself.
* @param context - standard decorator context used to schedule private marking.
*/
export function Remote<This extends object, Args extends unknown[], Result>(
_method: (this: This, ...args: Args) => Result,
context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Result>,
): void
/**
* Mark one public instance method under a distinct exported method name.
* @param exportName - Remote endpoint method, without a namespace or slash.
* @returns a standard method decorator.
*/
export function Remote(exportName: string): RemoteMethodDecorator
export function Remote<This extends object, Args extends unknown[], Result>(
methodOrExportName: string | ((this: This, ...args: Args) => Result),
context?: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Result>,
): void | RemoteMethodDecorator {
if (typeof methodOrExportName === 'string') {
validateName('Remote export name', methodOrExportName)
return function <DecoratorThis extends object, DecoratorArgs extends unknown[], DecoratorResult>(
_method: (this: DecoratorThis, ...args: DecoratorArgs) => DecoratorResult,
decoratorContext: ClassMethodDecoratorContext<
DecoratorThis,
(this: DecoratorThis, ...args: DecoratorArgs) => DecoratorResult
>,
): void {
addMarkerInitializer(decoratorContext, { kind: 'direct' }, methodOrExportName)
}
}
if (context === undefined) throw new TypeError('type-meta: Remote decorator context is missing')
addMarkerInitializer(context, { kind: 'direct' })
}
/**
* Create a decorator for a method resolved from one scoped Remote Context.
* @param key - merge-declared Context key.
* @param exportName - optional Remote export name; defaults to the method name.
* @returns a standard method decorator that records only private module state.
*/
export function RemoteContext(
key: Extract<keyof TypeRTContextMap, string>,
exportName?: string,
): RemoteMethodDecorator {
validateName('Context key', key)
if (exportName !== undefined) validateName('Remote export name', exportName)
return function <This extends object, Args extends unknown[], Result>(
_method: (this: This, ...args: Args) => Result,
context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Result>,
): void {
addMarkerInitializer(context, { kind: 'context', context: key }, exportName)
}
}
/**
* Read Remote markers attached to a live Service by decorator initializers.
* The returned snapshot cannot mutate the private marker table.
* @param service - live Service instance.
* @returns markers in class declaration order.
*/
export function remoteMethods(service: object): readonly RemoteMethodMarker[] {
const prototype = Object.getPrototypeOf(service) as object | null
if (prototype === null) return []
return [...(markers.get(prototype) ?? [])].map(([method, marker]) => ({ method, ...marker }))
}
function addMarkerInitializer<This extends object>(
context: RemoteInitializerContext<This>,
invocation: RemoteInvocationMarker,
exportName?: string,
): void {
if (context.private || context.static || typeof context.name !== 'string') {
throw new TypeError('type-meta: Remote decorators require a public instance method with a string name')
}
const method = context.name
context.addInitializer(function (this: This) {
const prototype = Object.getPrototypeOf(this) as object | null
if (prototype === null) {
throw new TypeError(`type-meta: cannot mark Remote method "${method}" on an object without a prototype`)
}
mark(prototype, method, invocation, exportName)
})
}
function mark(
prototype: object,
method: string,
invocation: RemoteInvocationMarker,
exportName?: string,
): void {
let table = markers.get(prototype)
if (table === undefined) {
table = new Map()
markers.set(prototype, table)
}
const marker: StoredRemoteMethodMarker = {
...(exportName === undefined || exportName === method ? {} : { exportName }),
invocation: Object.freeze(invocation),
}
const current = table.get(method)
if (current !== undefined) {
if (current.exportName === marker.exportName && sameInvocation(current.invocation, invocation)) return
throw new Error(`type-meta: Remote method "${method}" has conflicting invocation markers`)
}
table.set(method, Object.freeze(marker))
}
function sameInvocation(left: RemoteInvocationMarker, right: RemoteInvocationMarker): boolean {
return left.kind === right.kind
&& (left.kind === 'direct' || (right.kind === 'context' && left.context === right.context))
}
function validateName(subject: string, value: string): void {
if (value.length === 0 || value.includes('/')) {
throw new TypeError(`type-meta: ${subject} must be nonempty and must not contain "/"`)
}
}

View File

@@ -0,0 +1,30 @@
/**
* Package-owned invariant companion for `@deepseek-ai/dsh-type-meta`.
* @module @deepseek-ai/dsh-type-meta/invariant
*/
/* jscpd:ignore-start */
import type { Context } from 'cordis'
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
const PACKAGE_NAME = '@deepseek-ai/dsh-type-meta'
/** Cordis companion plugin name. */
export const name = 'type-meta-invariant'
/** Service required before the companion can reserve package ownership. */
export const inject = ['invariants']
/**
* No runtime invariant: decorators retain private immutable declarations and
* bindings are frozen values with no independent event stream to cross-check.
*/
const install: InvariantInstaller = () => {}
/**
* Register this package's invariant companion.
* @param ctx - Cordis context carrying the invariant service.
* @returns the installed registration's disposer after setup succeeds.
*/
export const apply = (ctx: Context): Promise<() => void> =>
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
/* jscpd:ignore-end */

View File

@@ -0,0 +1,358 @@
/**
* Compiler-independent TypeRT protocol shared by business packages, generated
* Remote artifacts, the Host Gateway, and Client API implementations.
* @module @deepseek-ai/dsh-type-meta/types
*/
import type { Context } from 'cordis'
declare const LOOKUP_HOST: unique symbol
declare const LOOKUP_WIRE: unique symbol
declare const CONTEXT_WIRE: unique symbol
/** Type-level association between a Host object and its wire identity. */
export interface TypeRTLookup<Host, Wire> {
readonly [LOOKUP_HOST]: Host
readonly [LOOKUP_WIRE]: Wire
}
/** Extract the Host object associated with one lookup declaration. */
export type TypeRTLookupHost<Lookup> = Lookup extends TypeRTLookup<infer Host, infer _Wire> ? Host : never
/** Extract the wire identity associated with one lookup declaration. */
export type TypeRTLookupWire<Lookup> = Lookup extends TypeRTLookup<infer _Host, infer Wire> ? Wire : never
/** Type-level association between a scoped Context kind and its wire identity. */
export interface TypeRTContext<Wire> {
readonly [CONTEXT_WIRE]: Wire
}
/** Extract the wire identity associated with one scoped Context declaration. */
export type TypeRTContextWire<ContextType> = ContextType extends TypeRTContext<infer Wire> ? Wire : never
/** Merge-extensible Host object lookup declarations. */
export interface TypeRTLookupMap {}
/** Merge-extensible scoped Context declarations. */
export interface TypeRTContextMap {}
/** Merge-extensible direct Remote method signatures generated for consumers. */
export interface TypeRTRemoteMap {}
/** Merge-extensible scoped Remote method signatures generated for consumers. */
export interface TypeRTRemoteContextMap {}
/**
* Resolve one direct Remote namespace from the generated flat endpoint map.
* @template Namespace - wire namespace before the endpoint slash.
*/
export type TypeRTRemoteNamespace<Namespace extends string> = {
[Endpoint in keyof TypeRTRemoteMap as Endpoint extends `${Namespace}/${infer Method}`
? Method
: never]: TypeRTRemoteMap[Endpoint]
}
/**
* Resolve one scoped Remote namespace across every generated Context kind.
* The calling Cordis Context supplies the concrete identity at runtime.
* @template Namespace - wire namespace between the Context prefix and method.
*/
export type TypeRTRemoteContextNamespace<
Namespace extends string,
ContextKey extends string = string,
> = {
[Endpoint in keyof TypeRTRemoteContextMap as Endpoint extends `${ContextKey}:${Namespace}/${infer Method}`
? Method
: never]: TypeRTRemoteContextMap[Endpoint]
}
type TypeRTRemoteContextNamespaceKey<
ContextKey extends string,
Endpoint = keyof TypeRTRemoteContextMap,
> = Endpoint extends `${ContextKey}:${infer Namespace}/${string}` ? Namespace : never
/** Generated scoped Remote namespaces available to one Context kind. */
export type TypeRTRemoteContextApi<ContextKey extends string> = {
[Namespace in TypeRTRemoteContextNamespaceKey<ContextKey>]:
TypeRTRemoteContextNamespace<Namespace, ContextKey>
}
/** Merge-extensible direct namespace surface generated for Client API services. */
export interface TypeRTRemoteNamespaceMap {}
/** Awaitable disposer returned by Cordis-owned TypeRT registrations. */
export type TypeRTDisposer = () => Promise<void>
type StringKeyOf<Value> = Extract<keyof Value, string>
/** Minimal runtime-schema capability carried by strict generated codecs. */
export interface TypeRTSchema<Output = unknown> {
/**
* Parse and validate one boundary value.
* @param value - untrusted boundary value.
* @returns the validated value.
*/
parse(value: unknown): Output
}
/** Codec attached to one invocation parameter or result. */
export type TypeRTCodec =
| {
readonly mode: 'strict'
readonly typeSymbol: string
readonly schema: TypeRTSchema
}
| {
readonly mode: 'src-json'
}
/** One ordered business parameter in a Remote invocation. */
export interface InvocationParameterDescriptor {
/** Source-level parameter name. */
readonly name: string
/** Required key in the wire `args` object. */
readonly wire: string
/** Whether the value is JSON or requires a registered Host lookup. */
readonly source: 'json' | 'lookup'
/** Lookup key when `source` is `lookup`. */
readonly lookup?: string
/** Boundary codec for the wire representation. */
readonly codec: TypeRTCodec
}
/** Source position retained for diagnostics from generated definitions. */
export interface InvocationSourceLocation {
readonly file: string
readonly line: number
readonly column: number
}
/** Carrier-independent description of one exported method invocation. */
export interface InvocationDescriptor {
/** Globally stable generated identity. */
readonly id: string
/** Cordis service key owning the method. */
readonly service: string
/** Wire namespace, defaulting to the service key. */
readonly namespace: string
/** Public instance method name. */
readonly method: string
/** Service member invoked when the exported method name is an alias. */
readonly implementation?: string
/** Receiver selection mode. */
readonly invocation:
| { readonly kind: 'direct' }
| {
readonly kind: 'context'
readonly context: string
readonly wire: string
readonly codec: TypeRTCodec
}
/** Optional consuming-Context projection for one direct lookup parameter. */
readonly scope?: {
/** Context kind whose Client binder supplies the identity. */
readonly context: string
/** Lookup parameter wire field replaced by the Context identity. */
readonly wire: string
}
/** Ordered business parameters. */
readonly parameters: readonly InvocationParameterDescriptor[]
/** Codec for the resolved method result. */
readonly result: TypeRTCodec
/** Source declaration used only for diagnostics. */
readonly sourceLocation?: InvocationSourceLocation
}
/** Generated Host contract selected explicitly by a Client assembly. */
export interface TypeRTRemoteContribution {
/** npm package that owns the Remote methods. */
readonly package: string
/** Consumer-side invocation descriptors generated from that package. */
readonly descriptors: readonly InvocationDescriptor[]
}
/** Runtime resolver for one declared Host object lookup. */
export interface TypeRTLookupProvider<Host = unknown, Wire = unknown> {
/** Source parameter name recognized by the SRC weak parser. */
readonly parameter: string
/** Wire field replacing the Host object parameter. */
readonly wire: string
/** Canonical Host type symbol used by strict generation. */
readonly hostTypeSymbol: string
/** Canonical wire type symbol used by strict generation. */
readonly wireTypeSymbol: string
/**
* Resolve a wire identity to the current live Host object.
* @param id - validated wire identity.
* @returns the live object, or `undefined` when it is unavailable.
*/
resolve(id: Wire): Host | undefined
}
/** Host resolver for one scoped Remote Context kind. */
export interface TypeRTHostContextProvider<Wire = unknown> {
/** Wire field carrying the Context identity. */
readonly wire: string
/** Canonical wire type symbol used by strict generation. */
readonly wireTypeSymbol: string
/**
* Resolve a wire identity to its live scoped Context.
* @param id - validated wire identity.
* @returns the scoped Context, or `undefined` when unavailable.
*/
resolve(id: Wire): Context | undefined
}
/** Client resolver for the identity carried by the calling scoped Context. */
export interface TypeRTClientContextBinder<Wire = unknown> {
/**
* Read the Remote identity represented by a calling Context.
* @param ctx - Context rebound by the Cordis service tracker.
* @returns the wire identity, or `undefined` when the Context has the wrong scope.
*/
identity(ctx: Context): Wire | undefined
}
/** Notification emitted after a TypeRT runtime registry changes. */
export interface TypeRTRegistryChange {
readonly kind: 'local' | 'remote' | 'lookup' | 'host-context' | 'client-context'
readonly key: string
}
/** Listener for one TypeRT runtime registry. */
export type TypeRTRegistryListener = (change: TypeRTRegistryChange) => void
/** Current-environment invocation definitions. */
export interface TypeRTLocalRegistry {
/**
* Look up one invocation by `<namespace>/<method>`.
* @param endpoint - canonical endpoint.
* @returns the live descriptor, or `undefined` when absent.
*/
get(endpoint: string): InvocationDescriptor | undefined
/**
* Report whether a strict definition has existed during this TypeRT Service lifetime.
* @param endpoint - canonical endpoint.
* @returns `true` after the endpoint has been registered at least once, even if withdrawn.
*/
hasSeen(endpoint: string): boolean
/** @returns a registration-order snapshot of local descriptors. */
list(): readonly InvocationDescriptor[]
/**
* Observe later local-definition changes.
* @param listener - synchronous contained observer.
* @returns disposer for this subscription.
*/
subscribe(listener: TypeRTRegistryListener): TypeRTDisposer
}
/** Consumer-selected Remote contribution registry. */
export interface TypeRTRemoteRegistry {
/**
* Register one generated contribution for the calling Cordis fiber.
* @param contribution - generated Remote descriptors.
* @returns disposer withdrawing the exact contribution.
*/
register(contribution: TypeRTRemoteContribution): TypeRTDisposer
/**
* Look up one Remote descriptor by endpoint.
* @param endpoint - canonical endpoint.
* @returns the descriptor, or `undefined` when unmounted.
*/
get(endpoint: string): InvocationDescriptor | undefined
/** @returns a registration-order snapshot of Remote descriptors. */
list(): readonly InvocationDescriptor[]
/**
* Observe later Remote contribution changes.
* @param listener - synchronous contained observer.
* @returns disposer for this subscription.
*/
subscribe(listener: TypeRTRegistryListener): TypeRTDisposer
}
/** Runtime registry for Host object lookup providers. */
export interface TypeRTLookupRegistry {
/**
* Register one provider under its merge-declared key.
* @param key - lookup key.
* @param provider - owning package's live resolver.
* @returns disposer withdrawing the exact provider.
*/
register<K extends StringKeyOf<TypeRTLookupMap>>(
key: K,
provider: TypeRTLookupProvider<
TypeRTLookupHost<TypeRTLookupMap[K]>,
TypeRTLookupWire<TypeRTLookupMap[K]>
>,
): TypeRTDisposer
/**
* Look up one provider by runtime key.
* @param key - descriptor lookup key.
* @returns the live provider, or `undefined` when absent.
*/
get(key: string): TypeRTLookupProvider | undefined
/** @returns a snapshot of registered provider keys. */
keys(): readonly string[]
/**
* Observe later lookup changes.
* @param listener - synchronous contained observer.
* @returns disposer for this subscription.
*/
subscribe(listener: TypeRTRegistryListener): TypeRTDisposer
}
/** Runtime registry for Host Context resolvers and Client Context binders. */
export interface TypeRTContextRegistry {
/**
* Register a Host Context resolver.
* @param key - merge-declared Context key.
* @param provider - owning package's Host resolver.
* @returns disposer withdrawing the exact provider.
*/
registerHost<K extends StringKeyOf<TypeRTContextMap>>(
key: K,
provider: TypeRTHostContextProvider<TypeRTContextWire<TypeRTContextMap[K]>>,
): TypeRTDisposer
/**
* Register a Client Context identity binder.
* @param key - merge-declared Context key.
* @param binder - Client scope identity resolver.
* @returns disposer withdrawing the exact binder.
*/
registerClient<K extends StringKeyOf<TypeRTContextMap>>(
key: K,
binder: TypeRTClientContextBinder<TypeRTContextWire<TypeRTContextMap[K]>>,
): TypeRTDisposer
/**
* Look up a Host Context resolver.
* @param key - descriptor Context key.
* @returns the provider, or `undefined` when absent.
*/
getHost(key: string): TypeRTHostContextProvider | undefined
/**
* Look up a Client Context binder.
* @param key - descriptor Context key.
* @returns the binder, or `undefined` when absent.
*/
getClient(key: string): TypeRTClientContextBinder | undefined
/**
* Observe later Context provider changes.
* @param listener - synchronous contained observer.
* @returns disposer for this subscription.
*/
subscribe(listener: TypeRTRegistryListener): TypeRTDisposer
}
/** Minimal TypeRT runtime consumed through dependency inversion. */
export interface TypeRTService {
readonly local: TypeRTLocalRegistry
readonly remotes: TypeRTRemoteRegistry
readonly lookups: TypeRTLookupRegistry
readonly contexts: TypeRTContextRegistry
}
declare module 'cordis' {
interface Context {
typert: TypeRTService
}
}

View File

@@ -0,0 +1,29 @@
import {
bindTypeRTGateway,
Remote,
RemoteContext,
remoteMethods,
} from '@deepseek-ai/dsh-type-meta'
class Goals {
readonly typertGateway = bindTypeRTGateway(this, 'goals')
@Remote
create(value: string): string {
return value
}
@RemoteContext('agent')
scoped(value: string): string {
return value
}
}
const methods = remoteMethods(new Goals())
const actual = JSON.stringify(methods)
const expected = JSON.stringify([
{ method: 'create', invocation: { kind: 'direct' } },
{ method: 'scoped', invocation: { kind: 'context', context: 'agent' } },
])
if (actual !== expected) throw new Error(`unexpected Remote declarations: ${actual}`)
process.stdout.write(actual)

View File

@@ -0,0 +1,132 @@
import { execFileSync } from 'node:child_process'
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import {
bindTypeRTGateway,
Remote,
RemoteContext,
remoteMethods,
type TypeRTContext,
} from '@deepseek-ai/dsh-type-meta'
declare module '@deepseek-ai/dsh-type-meta' {
interface TypeRTContextMap {
metaFixture: TypeRTContext<string>
}
}
describe('type-meta Remote declarations', () => {
it('executes standard decorator syntax through the Vitest source transform', () => {
class Goals {
readonly typertGateway = bindTypeRTGateway(this, 'goals')
@Remote
create(value: string): string {
return value
}
@RemoteContext('metaFixture')
scoped(value: string): string {
return value
}
}
const goals = new Goals()
expect(remoteMethods(goals)).toEqual([
{ method: 'create', invocation: { kind: 'direct' } },
{ method: 'scoped', invocation: { kind: 'context', context: 'metaFixture' } },
])
})
it('executes standard decorator syntax through the TSX source launcher', () => {
const fixture = fileURLToPath(new URL('./fixtures/source-launch.ts', import.meta.url))
const output = execFileSync(process.execPath, ['--import', 'tsx/esm', fixture], { encoding: 'utf8' })
expect(JSON.parse(output)).toEqual([
{ method: 'create', invocation: { kind: 'direct' } },
{ method: 'scoped', invocation: { kind: 'context', context: 'agent' } },
])
})
it('keeps decorator markers in private module state', () => {
class Goals {
readonly typertGateway = bindTypeRTGateway(this, 'goals')
create(agent: object, request: object): object {
return { agent, request }
}
scoped(request: object): object {
return request
}
}
const initializers: Array<(this: Goals) => void> = []
Remote(
Reflect.get(Goals.prototype, 'create') as (this: Goals, ...args: unknown[]) => unknown,
methodContext('create', initializers),
)
RemoteContext('metaFixture')(
Reflect.get(Goals.prototype, 'scoped') as (this: Goals, ...args: unknown[]) => unknown,
methodContext('scoped', initializers),
)
const goals = new Goals()
for (const initialize of initializers) initialize.call(goals)
expect(goals.typertGateway).toEqual({ service: goals, serviceKey: 'goals', namespace: 'goals' })
expect(Object.isFrozen(goals.typertGateway)).toBe(true)
expect(remoteMethods(goals)).toEqual([
{ method: 'create', invocation: { kind: 'direct' } },
{ method: 'scoped', invocation: { kind: 'context', context: 'metaFixture' } },
])
expect(Reflect.ownKeys(Goals)).toEqual(['length', 'name', 'prototype'])
expect(Reflect.ownKeys(Goals.prototype)).toEqual(['constructor', 'create', 'scoped'])
})
it('keeps markers idempotent across instances and returns detached snapshots', () => {
class Service {
run(value: string): string {
return value
}
}
const initializers: Array<(this: Service) => void> = []
Remote(
Reflect.get(Service.prototype, 'run') as (this: Service, ...args: unknown[]) => unknown,
methodContext('run', initializers),
)
const first = new Service()
const second = new Service()
for (const initialize of initializers) {
initialize.call(first)
initialize.call(second)
}
const snapshot = remoteMethods(first)
expect(remoteMethods(second)).toEqual(snapshot)
;(snapshot as unknown as { method: string }[])[0]!.method = 'changed'
expect(remoteMethods(first)).toEqual([{ method: 'run', invocation: { kind: 'direct' } }])
})
it('rejects ambiguous binding names', () => {
expect(() => bindTypeRTGateway({}, '')).toThrow('service key')
expect(() => bindTypeRTGateway({}, 'goals', { namespace: 'api/goals' })).toThrow('namespace')
})
})
function methodContext<This extends object>(
name: string,
initializers: Array<(this: This) => void>,
): ClassMethodDecoratorContext<This, (this: This, ...args: unknown[]) => unknown> {
return {
kind: 'method',
name,
static: false,
private: false,
metadata: {},
access: {
has: object => name in object,
get: object => (object as Record<string, unknown>)[name] as (this: This, ...args: unknown[]) => unknown,
},
addInitializer: (initializer) => { initializers.push(initializer) },
}
}

View File

@@ -0,0 +1,21 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib/types"
},
"include": [
"src"
],
"references": [
{
"path": "../../../vendor/cosmokit"
},
{
"path": "../../../vendor/cordis"
},
{
"path": "../../support/invariants"
}
]
}