@deepseek-ai/dsh-typert-protocol 0.1.6-alpha.1 → 0.1.7-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +11 -4
- package/README.zh.md +11 -4
- package/lib/index.js +97 -3
- package/lib/types/index.d.ts +10 -4
- package/lib/types/index.js +21 -2
- package/lib/types/json-value.d.ts +20 -0
- package/lib/types/json-value.js +61 -0
- package/lib/types/owned-value.d.ts +22 -0
- package/lib/types/owned-value.js +32 -0
- package/lib/types/types.d.ts +132 -5
- package/package.json +6 -3
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/typert/protocol/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: 8a97bb339d462c8dbea7666c7855a7e51ae9b113
|
|
6
|
+
README.zh.md: 9bb2cae765ee5747bf11a92d44ec2bb201b0285d
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
|
|
|
9
9
|
|
|
10
10
|
## Summary
|
|
11
11
|
|
|
12
|
-
With `dsh-typert-protocol`, business packages can expose Host methods to Remote clients: mark a method with `@Remote` (or `@RemoteScope` for scoped receivers), bind the service to a wire namespace, and associate Host objects and scoped Contexts with wire identities through the merge-extensible protocol maps. Generated artifacts, the Host Gateway, and the Client API consume the same invocation descriptors, codecs, and provider contracts
|
|
12
|
+
With `dsh-typert-protocol`, business packages can expose Host methods to Remote clients: mark a method with `@Remote` (or `@RemoteScope` for scoped receivers), bind the service to a wire namespace, and associate Host objects and scoped Contexts with wire identities through the merge-extensible protocol maps. Generated artifacts, the Host Gateway, and the Client API consume the same invocation descriptors, codecs, and provider contracts. Invocation-owned values transfer cleanup to Gateway without adding a reference count. The package registers no Cordis service and runs no TypeScript analysis.
|
|
13
13
|
|
|
14
14
|
## Table of Contents
|
|
15
15
|
|
|
@@ -44,10 +44,16 @@ export class GoalService extends TypertRemoteService {
|
|
|
44
44
|
|
|
45
45
|
Generation turns the method into a wire endpoint under the service's namespace; Clients call it as a typed method through `ctx.remote` (see the [API Gateway reference](../../../docs/api-gateway.md)). A method opts into cooperative cancellation by declaring `signal: AbortSignal` as its final parameter — the signal is injected, never a JSON parameter or lookup field.
|
|
46
46
|
|
|
47
|
+
A unary method can return `Uint8Array` directly or within nested objects, arrays, tuples, optional fields, unions, and recursive types. Generation supplies optional result codec `encode()` and `decode()` functions: encoding visits only subtrees whose types can contain bytes, while decoding validates reconstructed values; Client declarations use `Uint8Array<ArrayBuffer>` at every byte position while retaining other field types. Pure JSON results pass through without Host byte detection or Client parsing. Parameters, events, and stream items remain JSON-only; runtime object cycles are unsupported.
|
|
48
|
+
|
|
49
|
+
A stream method (`@Remote({ mode: 'stream' })`) returns `Iterable`, `AsyncIterable`, or `RemoteStream<Out, In>`. `In` declares the items the Client may send back on the same logical stream; the method reads them through `this.ctx.invocation.uplink<In>()`, and the descriptor carries their codec. `RemoteInvocation` also names the receiving `service`, the calling `peer` (a `PeerScope` the connection layer admitted), and the carrier `signal`; `ctx.invocation` is `undefined` on a Context no Remote call derived: the first `bindTypertRemote()` binding in a tree, which every `TypertRemoteService` constructor makes, registers that accessor on the root. A generated Client stream method returns `RemoteStreamHandle<Out, In>`: the handle with `send`, `end`, and `dispose` beside the downlink iteration. Uplink items are validated one by one at the Host because they arrive from the browser; downlink items are values the Host method produced and pass through.
|
|
50
|
+
|
|
47
51
|
### Associating Host objects and Contexts with wire identities
|
|
48
52
|
|
|
49
53
|
Complex Host objects cannot cross the wire directly. A business package declares the association through the merge-extensible `TypertLookupMap` and `TypertContextMap`. A Host Context adapter owns the stable wire declaration and resolves wire identities to live Contexts. A Client Context adapter maps in both directions because scoped calls originate from a Client Context and forwarded Host events resolve their explicit wire identity there. Host composition may override its synchronous or asynchronous resolver. A resolver that refuses on policy grounds throws `RemoteError` with its own code, which reaches the caller unchanged.
|
|
50
54
|
|
|
55
|
+
Client Context resolution is synchronous. `typertOwnedValue(value, release)` transfers a non-throwing, idempotent cleanup to the invocation owner; Gateway calls it after handler and reply settlement. A borrowed Context requires no cleanup wrapper. The shared `TYPERT_OWNED_VALUE` symbol and `isTypertOwnedValue` recognizer work across independently bundled providers and Gateway; the wrapper itself does not retain a resource.
|
|
56
|
+
|
|
51
57
|
### Reporting and reading a Remote failure
|
|
52
58
|
|
|
53
59
|
One class carries every Remote failure: `RemoteError`, holding a stable `<domain>/<reason>` code and the details typed for that code. This package declares the universal carrier codes (`gateway/bad-request`, `gateway/cancelled`, `gateway/internal`) and owns `RemoteErrorDetailsMap`, the merge-extensible table every other package extends beside its own throwing code:
|
|
@@ -87,19 +93,20 @@ The package keeps strict reflection in the compiler: decorator initializers reta
|
|
|
87
93
|
|
|
88
94
|
### Protocol maps and descriptors
|
|
89
95
|
|
|
90
|
-
The merge-extensible protocol maps keep static associations in the type system, while runtime providers register resolution with `ctx.typert`; the map names and shapes live in [`src/types.ts`](src/types.ts). `InvocationDescriptor` is the shared runtime form consumed by the registry, the Gateway, and the Client Remote, covering direct and Context receivers, JSON and lookup parameters, scope projections, cancellation, and result codecs.
|
|
96
|
+
The merge-extensible protocol maps keep static associations in the type system, while runtime providers register resolution with `ctx.typert`; the map names and shapes live in [`src/types.ts`](src/types.ts). `InvocationDescriptor` is the shared runtime form consumed by the registry, the Gateway, and the Client Remote, covering direct and Context receivers, JSON and lookup parameters, scope projections, the uplink codec, cancellation, and result codecs.
|
|
91
97
|
|
|
92
98
|
### Wire identity grammar
|
|
93
99
|
|
|
94
|
-
Every namespace, method, lookup, and Context segment must satisfy `isTypertRemoteSegment()`, so generated names cross the shared RPC carrier unchanged. Strict codecs carry generated
|
|
100
|
+
Every namespace, method, lookup, and Context segment must satisfy `isTypertRemoteSegment()`, so generated names cross the shared RPC carrier unchanged. Strict codecs carry generated schema factories; `src-json` codecs identify the weaker source-launch path.
|
|
95
101
|
|
|
96
102
|
### Source map
|
|
97
103
|
|
|
98
104
|
| File | Role |
|
|
99
105
|
|---|---|
|
|
100
106
|
| [`src/index.ts`](src/index.ts) | Decorators, Gateway bindings, `remoteMethods`, segment validation |
|
|
107
|
+
| [`src/json-value.ts`](src/json-value.ts) | `isRemoteJsonValue` and `isRemoteUplinkItem`, the lossless JSON checks every carrier shares |
|
|
101
108
|
| [`src/remote-error.ts`](src/remote-error.ts) | `RemoteError` and the structural `remoteErrorOf` recognizer |
|
|
102
|
-
| [`src/types.ts`](src/types.ts) | Protocol maps, `RemoteErrorDetailsMap`, `RemoteResult`, `InvocationDescriptor`, codecs, provider contracts, registry interfaces, `TypertClientRemote` |
|
|
109
|
+
| [`src/types.ts`](src/types.ts) | Protocol maps, `RemoteErrorDetailsMap`, `RemoteResult`, `RemoteStream`, `RemoteStreamHandle`, `PeerScope`, `RemoteInvocation`, `InvocationDescriptor`, codecs, provider contracts, registry interfaces, `TypertClientRemote` |
|
|
103
110
|
| — | No runtime invariant companion is published; decorators retain private immutable declarations and bindings are frozen values with no independent event stream to cross-check. |
|
|
104
111
|
|
|
105
112
|
</details>
|
package/README.zh.md
CHANGED
|
@@ -9,7 +9,7 @@ kind: "package-library"
|
|
|
9
9
|
|
|
10
10
|
## 概述
|
|
11
11
|
|
|
12
|
-
借助 `dsh-typert-protocol`,业务包可以向 Remote 客户端暴露 Host 方法:用 `@Remote`(作用域接收者用 `@RemoteScope`)标记方法,把服务绑定到 wire 命名空间,并通过可合并扩展的协议映射把 Host 对象与作用域 Context 关联到 wire identity。生成产物、Host Gateway 与 Client API
|
|
12
|
+
借助 `dsh-typert-protocol`,业务包可以向 Remote 客户端暴露 Host 方法:用 `@Remote`(作用域接收者用 `@RemoteScope`)标记方法,把服务绑定到 wire 命名空间,并通过可合并扩展的协议映射把 Host 对象与作用域 Context 关联到 wire identity。生成产物、Host Gateway 与 Client API 消费同一套调用描述符、编解码器与提供方约定。调用持有的值把清理责任交给 Gateway,不另增引用计数。本包不注册任何 Cordis 服务,也不运行 TypeScript 分析。
|
|
13
13
|
|
|
14
14
|
## 目录
|
|
15
15
|
|
|
@@ -44,10 +44,16 @@ export class GoalService extends TypertRemoteService {
|
|
|
44
44
|
|
|
45
45
|
生成会把方法变为服务命名空间下的 wire 端点;Client 通过 `ctx.remote` 以类型化方法调用它(见 [API Gateway 参考](../../../docs/api-gateway.zh.md))。方法把 `signal: AbortSignal` 声明为最后一个参数即可选择协作式取消——该信号是注入的,绝不会成为 JSON 参数或查找字段。
|
|
46
46
|
|
|
47
|
+
一元方法可以直接返回 `Uint8Array`,也可以将其放在嵌套对象、数组、元组、可选字段、联合类型和递归类型中。生成器提供可选的结果 codec `encode()` 和 `decode()`:编码仅访问类型可能包含字节的子树,解码则校验还原后的值;Client 声明在每个字节位置使用 `Uint8Array<ArrayBuffer>`,同时保留其他字段类型。纯 JSON 结果不经 Host 字节识别或 Client 解析直接传递。参数、事件与流条目仍仅支持 JSON;不支持运行时对象循环。
|
|
48
|
+
|
|
49
|
+
流方法(`@Remote({ mode: 'stream' })`)返回 `Iterable`、`AsyncIterable` 或 `RemoteStream<Out, In>`。`In` 声明 Client 可以在同一条逻辑流上回送的项;方法通过 `this.ctx.invocation.uplink<In>()` 读取它们,描述符携带其 codec。`RemoteInvocation` 还给出接收服务 `service`、发起调用的 `peer`(连接层接纳的一个 `PeerScope`)与载体 `signal`;非 Remote 调用派生的 Context 上 `ctx.invocation` 为 `undefined`:树中第一个 `bindTypertRemote()` 绑定(每个 `TypertRemoteService` 构造函数都会建立一个)在根上注册该 accessor。生成的 Client 流方法返回 `RemoteStreamHandle<Out, In>`:在下行迭代之外提供 `send`、`end` 与 `dispose` 的句柄。上行项在 Host 逐项校验,因为它们来自浏览器;下行项是 Host 方法产出的值,原样透传。
|
|
50
|
+
|
|
47
51
|
### 把 Host 对象与 Context 关联到 wire identity
|
|
48
52
|
|
|
49
53
|
复杂的 Host 对象不能直接跨 wire 传输。业务包通过可合并扩展的 `TypertLookupMap` 与 `TypertContextMap` 声明关联。Host Context 适配器拥有稳定 wire 声明,并把 wire identity 解析为活跃 Context。Client Context 适配器需要双向映射,因为作用域调用从 Client Context 发起,而转发的 Host 事件要在 Client 侧解析其显式 wire identity。Host 组合可以覆盖其同步或异步解析器。因策略原因拒绝解析的解析器会抛出带有自身错误码的 `RemoteError`,该码原样到达调用方。
|
|
50
54
|
|
|
55
|
+
Client Context 解析保持同步。`typertOwnedValue(value, release)` 把不抛异常、幂等的清理交给调用 owner;Gateway 在处理器和回复均结束后调用它。借用的 Context 不需要清理包装层。共享的 `TYPERT_OWNED_VALUE` symbol 与 `isTypertOwnedValue` 识别函数可跨独立打包的提供方与 Gateway 使用;包装层自身不会 retain 资源。
|
|
56
|
+
|
|
51
57
|
### 报告与读取 Remote 失败
|
|
52
58
|
|
|
53
59
|
所有 Remote 失败都由一个类承载:`RemoteError`,携带稳定的 `<domain>/<reason>` 码,以及按该码定型的 details。本包声明通用载体码(`gateway/bad-request`、`gateway/cancelled`、`gateway/internal`),并拥有 `RemoteErrorDetailsMap`——可合并扩展的码表,其他每个包都在自己的抛出点旁扩展它:
|
|
@@ -87,19 +93,20 @@ Host 装配以转发给消费方的 Cordis 事件扩展 `TypertRemoteEventSelect
|
|
|
87
93
|
|
|
88
94
|
### 协议映射与描述符
|
|
89
95
|
|
|
90
|
-
可合并扩展的协议映射在类型系统中保留静态关联,运行时提供方则向 `ctx.typert` 注册解析;映射的名称与形状见 [`src/types.ts`](src/types.ts)。`InvocationDescriptor` 是注册表、Gateway 与 Client Remote 共同消费的共享运行时形式,涵盖直接与 Context 接收者、JSON
|
|
96
|
+
可合并扩展的协议映射在类型系统中保留静态关联,运行时提供方则向 `ctx.typert` 注册解析;映射的名称与形状见 [`src/types.ts`](src/types.ts)。`InvocationDescriptor` 是注册表、Gateway 与 Client Remote 共同消费的共享运行时形式,涵盖直接与 Context 接收者、JSON 与查找参数、作用域投影、上行编解码器、取消与结果编解码器。
|
|
91
97
|
|
|
92
98
|
### Wire 标识文法
|
|
93
99
|
|
|
94
|
-
每个命名空间、方法、查找与 Context 段都必须满足 `isTypertRemoteSegment()`,生成的名字才能原样跨共享 RPC 载体传输。严格编解码器携带生成的 schema;`src-json` 编解码器标识约束更弱的源码启动路径。
|
|
100
|
+
每个命名空间、方法、查找与 Context 段都必须满足 `isTypertRemoteSegment()`,生成的名字才能原样跨共享 RPC 载体传输。严格编解码器携带生成的 schema factory;`src-json` 编解码器标识约束更弱的源码启动路径。
|
|
95
101
|
|
|
96
102
|
### 源码地图
|
|
97
103
|
|
|
98
104
|
| 文件 | 职责 |
|
|
99
105
|
|---|---|
|
|
100
106
|
| [`src/index.ts`](src/index.ts) | 装饰器、Gateway 绑定、`remoteMethods`、段校验 |
|
|
107
|
+
| [`src/json-value.ts`](src/json-value.ts) | 各载体共享的无损 JSON 校验 `isRemoteJsonValue` 与 `isRemoteUplinkItem` |
|
|
101
108
|
| [`src/remote-error.ts`](src/remote-error.ts) | `RemoteError` 与结构式识别函数 `remoteErrorOf` |
|
|
102
|
-
| [`src/types.ts`](src/types.ts) | 协议映射、`RemoteErrorDetailsMap`、`RemoteResult`、`InvocationDescriptor`、编解码器、提供方约定、注册表接口、`TypertClientRemote` |
|
|
109
|
+
| [`src/types.ts`](src/types.ts) | 协议映射、`RemoteErrorDetailsMap`、`RemoteResult`、`RemoteStream`、`RemoteStreamHandle`、`PeerScope`、`RemoteInvocation`、`InvocationDescriptor`、编解码器、提供方约定、注册表接口、`TypertClientRemote` |
|
|
103
110
|
| — | 不发布运行时不变量伴生入口;decorator 只保留私有不可变声明,binding 也是冻结值,没有可供交叉核对的独立事件流。 |
|
|
104
111
|
|
|
105
112
|
</details>
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Service } from "@deepseek-ai/cordis";
|
|
1
|
+
import { Context, Service } from "@deepseek-ai/cordis";
|
|
2
2
|
//#region lib/types/remote-error.js
|
|
3
3
|
/** The one Remote failure class shared by owners, the Gateway, and consumers. */
|
|
4
4
|
/**
|
|
@@ -37,6 +37,85 @@ function remoteErrorOf(value) {
|
|
|
37
37
|
if (typeof value === "object" && value !== null && value.isDSHRemoteError === true && typeof value.code === "string") return value;
|
|
38
38
|
}
|
|
39
39
|
//#endregion
|
|
40
|
+
//#region lib/types/owned-value.js
|
|
41
|
+
/** Generic invocation-owned values returned by synchronous Client Context resolvers. */
|
|
42
|
+
/** Shared identity across independently bundled Context providers and Gateway. */
|
|
43
|
+
const TYPERT_OWNED_VALUE = Symbol.for("dsh.typert.owned-value");
|
|
44
|
+
/**
|
|
45
|
+
* Transfer cleanup ownership without adding another resource reference count.
|
|
46
|
+
* @param value - resolved payload passed to the invocation.
|
|
47
|
+
* @param release - non-throwing synchronous release, called at most once.
|
|
48
|
+
* @returns an owned payload disposed after invocation and reply settlement.
|
|
49
|
+
*/
|
|
50
|
+
function typertOwnedValue(value, release) {
|
|
51
|
+
let active = true;
|
|
52
|
+
return {
|
|
53
|
+
[TYPERT_OWNED_VALUE]: true,
|
|
54
|
+
value,
|
|
55
|
+
[Symbol.dispose]() {
|
|
56
|
+
if (!active) return;
|
|
57
|
+
active = false;
|
|
58
|
+
release();
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Identify invocation-owned values using the shared marker.
|
|
64
|
+
* @param value - borrowed or owned resolver result.
|
|
65
|
+
* @returns whether the result carries invocation cleanup.
|
|
66
|
+
*/
|
|
67
|
+
function isTypertOwnedValue(value) {
|
|
68
|
+
return typeof value === "object" && value !== null && TYPERT_OWNED_VALUE in value && value[TYPERT_OWNED_VALUE] === true;
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region lib/types/json-value.js
|
|
72
|
+
/**
|
|
73
|
+
* Lossless JSON checks every Remote carrier shares: the Client handle before it
|
|
74
|
+
* queues an uplink item, the Gateway at its wire and codec-less uplink
|
|
75
|
+
* boundaries, and the in-process mock.
|
|
76
|
+
*/
|
|
77
|
+
/**
|
|
78
|
+
* Test whether a value crosses JSON transport without coercion or omission.
|
|
79
|
+
* @param value - candidate boundary value.
|
|
80
|
+
* @returns whether the value is losslessly JSON-compatible.
|
|
81
|
+
*/
|
|
82
|
+
function isRemoteJsonValue(value) {
|
|
83
|
+
return visitJsonValue(value, /* @__PURE__ */ new Set());
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Test whether a value may travel as one uplink item: a lossless JSON value, or
|
|
87
|
+
* a top-level `undefined`, which the wire carries as an `item` frame without
|
|
88
|
+
* `value`. Nested `undefined`, `NaN`, and infinities stay rejected.
|
|
89
|
+
* @param value - candidate uplink item.
|
|
90
|
+
* @returns whether the item crosses every carrier unchanged.
|
|
91
|
+
*/
|
|
92
|
+
function isRemoteUplinkItem(value) {
|
|
93
|
+
return value === void 0 || isRemoteJsonValue(value);
|
|
94
|
+
}
|
|
95
|
+
function visitJsonValue(value, ancestors) {
|
|
96
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
97
|
+
if (typeof value === "number") return Number.isFinite(value) && !Object.is(value, -0);
|
|
98
|
+
if (typeof value !== "object") return false;
|
|
99
|
+
if (ancestors.has(value)) return false;
|
|
100
|
+
ancestors.add(value);
|
|
101
|
+
try {
|
|
102
|
+
if (Array.isArray(value)) {
|
|
103
|
+
if (Object.getPrototypeOf(value) !== Array.prototype || Reflect.ownKeys(value).length !== value.length + 1) return false;
|
|
104
|
+
for (let index = 0; index < value.length; index++) if (!Object.hasOwn(value, index) || !visitJsonValue(value[index], ancestors)) return false;
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
const prototype = Object.getPrototypeOf(value);
|
|
108
|
+
if (prototype !== Object.prototype && prototype !== null) return false;
|
|
109
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
110
|
+
if (typeof key !== "string") return false;
|
|
111
|
+
if (Object.getOwnPropertyDescriptor(value, key)?.enumerable !== true || !visitJsonValue(Reflect.get(value, key), ancestors)) return false;
|
|
112
|
+
}
|
|
113
|
+
return true;
|
|
114
|
+
} finally {
|
|
115
|
+
ancestors.delete(value);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
//#endregion
|
|
40
119
|
//#region lib/types/index.js
|
|
41
120
|
/**
|
|
42
121
|
* Remote decorators and explicit Gateway bindings backed by versioned
|
|
@@ -55,7 +134,10 @@ function isTypertRemoteSegment(value) {
|
|
|
55
134
|
}
|
|
56
135
|
const REMOTE_METHOD_DESCRIPTOR = "@deepseek-ai/dsh-typert-protocol/remote-methods";
|
|
57
136
|
/**
|
|
58
|
-
* Bind one visible Service field to a Cordis key and Remote namespace.
|
|
137
|
+
* Bind one visible Service field to a Cordis key and Remote namespace. A
|
|
138
|
+
* service that owns a Cordis Context also gives its tree `ctx.invocation`,
|
|
139
|
+
* `undefined` outside a Remote call, so no `TypertRemoteService` is needed for
|
|
140
|
+
* a Host composition to read it.
|
|
59
141
|
* @param service - owning Service instance, normally `this`.
|
|
60
142
|
* @param serviceKey - exact Cordis service key.
|
|
61
143
|
* @param options - optional distinct wire namespace.
|
|
@@ -65,6 +147,8 @@ function bindTypertRemote(service, serviceKey, options = {}) {
|
|
|
65
147
|
validateName("service key", serviceKey);
|
|
66
148
|
const namespace = options.namespace ?? serviceKey;
|
|
67
149
|
validateName("namespace", namespace);
|
|
150
|
+
const ctx = Reflect.get(service, "ctx");
|
|
151
|
+
if (ctx instanceof Context) provideInvocationAccessor(ctx);
|
|
68
152
|
return Object.freeze({
|
|
69
153
|
service,
|
|
70
154
|
serviceKey,
|
|
@@ -86,6 +170,16 @@ var TypertRemoteService = class extends Service {
|
|
|
86
170
|
this.typertRemote = bindTypertRemote(this, this.name, options);
|
|
87
171
|
}
|
|
88
172
|
};
|
|
173
|
+
/**
|
|
174
|
+
* Make `ctx.invocation` read as `undefined` outside a Remote call instead of the
|
|
175
|
+
* reflect service's "cannot get property" error; a call-derived Context shadows
|
|
176
|
+
* the accessor with its own property. The first Remote Service constructed in a
|
|
177
|
+
* tree registers it on the root, where it outlives any one Service.
|
|
178
|
+
*/
|
|
179
|
+
function provideInvocationAccessor(ctx) {
|
|
180
|
+
if (Object.hasOwn(ctx.root.reflect.props, "invocation")) return;
|
|
181
|
+
ctx.root.accessor("invocation", { get: () => void 0 });
|
|
182
|
+
}
|
|
89
183
|
function Remote(methodExportOrOptions, context) {
|
|
90
184
|
if (typeof methodExportOrOptions === "string") {
|
|
91
185
|
validateName("Remote export name", methodExportOrOptions);
|
|
@@ -181,4 +275,4 @@ function validateName(subject, value) {
|
|
|
181
275
|
if (!isTypertRemoteSegment(value)) throw new TypeError(`typert-protocol: ${subject} must contain only RPC endpoint segment characters`);
|
|
182
276
|
}
|
|
183
277
|
//#endregion
|
|
184
|
-
export { Remote, RemoteError, RemoteScope, TypertRemoteService, bindTypertRemote, isTypertRemoteSegment, remoteErrorOf, remoteMethods };
|
|
278
|
+
export { Remote, RemoteError, RemoteScope, TYPERT_OWNED_VALUE, TypertRemoteService, bindTypertRemote, isRemoteJsonValue, isRemoteUplinkItem, isTypertOwnedValue, isTypertRemoteSegment, remoteErrorOf, remoteMethods, typertOwnedValue };
|
package/lib/types/index.d.ts
CHANGED
|
@@ -4,16 +4,19 @@
|
|
|
4
4
|
* remains a Typert compiler responsibility.
|
|
5
5
|
* @module @deepseek-ai/dsh-typert-protocol
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { Context, Service } from '@deepseek-ai/cordis';
|
|
8
8
|
import type { TypertContextMap } from './types.ts';
|
|
9
9
|
export { RemoteError, remoteErrorOf } from './remote-error.ts';
|
|
10
|
+
export { TYPERT_OWNED_VALUE, isTypertOwnedValue, typertOwnedValue } from './owned-value.ts';
|
|
11
|
+
export type { TypertOwnedValue } from './owned-value.ts';
|
|
12
|
+
export { isRemoteJsonValue, isRemoteUplinkItem } from './json-value.ts';
|
|
10
13
|
/**
|
|
11
14
|
* Test one generated Remote name against the Connection endpoint grammar.
|
|
12
15
|
* @param value - namespace, method, lookup, or Context segment.
|
|
13
16
|
* @returns whether the value can cross the shared RPC carrier unchanged.
|
|
14
17
|
*/
|
|
15
18
|
export declare function isTypertRemoteSegment(value: string): boolean;
|
|
16
|
-
export type { InvocationDescriptor, InvocationParameterDescriptor, InvocationSourceLocation, RemoteErrorCode, RemoteErrorDetailsMap, RemoteFailure, RemoteResult, TypertClientEventListener, TypertClientRemote, TypertClientContextAdapter, TypertCodec, TypertContext, TypertContextMap, TypertContextRegistry, TypertContextWire, TypertDisposer, TypertForwardableEvent, TypertForwardableEventEntry, TypertHostContextAdapter, TypertHostContextResolver, TypertLocalRegistry, TypertLookup, TypertLookupDefinition, TypertLookupHost, TypertLookupMap, TypertLookupProvider, TypertLookupResolver, TypertLookupRegistry, TypertLookupWire, TypertRemoteScopeApi, TypertRemoteScopeMap, TypertRemoteScopeNamespace, TypertRemoteContribution, TypertRemoteEvent, TypertRemoteEventSelection, TypertRemoteMap, TypertRemoteNamespace, TypertRemoteNamespaceMap, TypertRemoteRegistry, TypertRegistryChange, TypertRegistryListener, TypertSchema, TypertRegistryContract, } from './types.ts';
|
|
19
|
+
export type { InvocationDescriptor, InvocationParameterDescriptor, InvocationSourceLocation, PeerId, PeerScope, RemoteErrorCode, RemoteErrorDetailsMap, RemoteFailure, RemoteInvocation, RemoteResult, RemoteStream, RemoteStreamHandle, TypertClientEventListener, TypertClientRemote, TypertClientContextAdapter, TypertCodec, TypertContext, TypertContextMap, TypertContextRegistry, TypertContextWire, TypertDisposer, TypertForwardableEvent, TypertForwardableEventEntry, TypertHostContextAdapter, TypertHostContextResolver, TypertLocalRegistry, TypertLookup, TypertLookupDefinition, TypertLookupHost, TypertLookupMap, TypertLookupProvider, TypertLookupResolver, TypertLookupRegistry, TypertLookupWire, TypertRemoteScopeApi, TypertRemoteScopeMap, TypertRemoteScopeNamespace, TypertRemoteContribution, TypertRemoteEvent, TypertRemoteEventSelection, TypertRemoteMap, TypertRemoteNamespace, TypertRemoteNamespaceMap, TypertRemoteRegistry, TypertRegistryChange, TypertRegistryListener, TypertSchema, TypertRegistryContract, } from './types.ts';
|
|
17
20
|
/** Options for an explicit Service-to-Gateway binding. */
|
|
18
21
|
export interface TypertGatewayBindingOptions {
|
|
19
22
|
/** Wire namespace; defaults to the Cordis service key. */
|
|
@@ -44,12 +47,15 @@ export interface RemoteMethodMarker {
|
|
|
44
47
|
}
|
|
45
48
|
/** Options for a non-unary Remote method. */
|
|
46
49
|
export interface RemoteMethodOptions {
|
|
47
|
-
/**
|
|
50
|
+
/** `stream`: deliver each Iterable item over the shared logical-stream carrier. */
|
|
48
51
|
readonly mode: 'stream';
|
|
49
52
|
}
|
|
50
53
|
type RemoteMethodDecorator = <This extends object, Args extends unknown[], Result>(method: (this: This, ...args: Args) => Result, context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Result>) => void;
|
|
51
54
|
/**
|
|
52
|
-
* Bind one visible Service field to a Cordis key and Remote namespace.
|
|
55
|
+
* Bind one visible Service field to a Cordis key and Remote namespace. A
|
|
56
|
+
* service that owns a Cordis Context also gives its tree `ctx.invocation`,
|
|
57
|
+
* `undefined` outside a Remote call, so no `TypertRemoteService` is needed for
|
|
58
|
+
* a Host composition to read it.
|
|
53
59
|
* @param service - owning Service instance, normally `this`.
|
|
54
60
|
* @param serviceKey - exact Cordis service key.
|
|
55
61
|
* @param options - optional distinct wire namespace.
|
package/lib/types/index.js
CHANGED
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* remains a Typert compiler responsibility.
|
|
5
5
|
* @module @deepseek-ai/dsh-typert-protocol
|
|
6
6
|
*/
|
|
7
|
-
import { Service } from '@deepseek-ai/cordis';
|
|
7
|
+
import { Context, Service } from '@deepseek-ai/cordis';
|
|
8
8
|
export { RemoteError, remoteErrorOf } from "./remote-error.js";
|
|
9
|
+
export { TYPERT_OWNED_VALUE, isTypertOwnedValue, typertOwnedValue } from "./owned-value.js";
|
|
10
|
+
export { isRemoteJsonValue, isRemoteUplinkItem } from "./json-value.js";
|
|
9
11
|
const TYPERT_REMOTE_SEGMENT_PATTERN = /^[A-Za-z0-9_$.-]+$/;
|
|
10
12
|
/**
|
|
11
13
|
* Test one generated Remote name against the Connection endpoint grammar.
|
|
@@ -17,7 +19,10 @@ export function isTypertRemoteSegment(value) {
|
|
|
17
19
|
}
|
|
18
20
|
const REMOTE_METHOD_DESCRIPTOR = '@deepseek-ai/dsh-typert-protocol/remote-methods';
|
|
19
21
|
/**
|
|
20
|
-
* Bind one visible Service field to a Cordis key and Remote namespace.
|
|
22
|
+
* Bind one visible Service field to a Cordis key and Remote namespace. A
|
|
23
|
+
* service that owns a Cordis Context also gives its tree `ctx.invocation`,
|
|
24
|
+
* `undefined` outside a Remote call, so no `TypertRemoteService` is needed for
|
|
25
|
+
* a Host composition to read it.
|
|
21
26
|
* @param service - owning Service instance, normally `this`.
|
|
22
27
|
* @param serviceKey - exact Cordis service key.
|
|
23
28
|
* @param options - optional distinct wire namespace.
|
|
@@ -27,6 +32,9 @@ export function bindTypertRemote(service, serviceKey, options = {}) {
|
|
|
27
32
|
validateName('service key', serviceKey);
|
|
28
33
|
const namespace = options.namespace ?? serviceKey;
|
|
29
34
|
validateName('namespace', namespace);
|
|
35
|
+
const ctx = Reflect.get(service, 'ctx');
|
|
36
|
+
if (ctx instanceof Context)
|
|
37
|
+
provideInvocationAccessor(ctx);
|
|
30
38
|
return Object.freeze({ service, serviceKey, namespace });
|
|
31
39
|
}
|
|
32
40
|
/** Cordis Service base that exposes its registered name through Typert Gateway. */
|
|
@@ -44,6 +52,17 @@ export class TypertRemoteService extends Service {
|
|
|
44
52
|
this.typertRemote = bindTypertRemote(this, this.name, options);
|
|
45
53
|
}
|
|
46
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Make `ctx.invocation` read as `undefined` outside a Remote call instead of the
|
|
57
|
+
* reflect service's "cannot get property" error; a call-derived Context shadows
|
|
58
|
+
* the accessor with its own property. The first Remote Service constructed in a
|
|
59
|
+
* tree registers it on the root, where it outlives any one Service.
|
|
60
|
+
*/
|
|
61
|
+
function provideInvocationAccessor(ctx) {
|
|
62
|
+
if (Object.hasOwn(ctx.root.reflect.props, 'invocation'))
|
|
63
|
+
return;
|
|
64
|
+
ctx.root.accessor('invocation', { get: () => undefined });
|
|
65
|
+
}
|
|
47
66
|
export function Remote(methodExportOrOptions, context) {
|
|
48
67
|
if (typeof methodExportOrOptions === 'string') {
|
|
49
68
|
validateName('Remote export name', methodExportOrOptions);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lossless JSON checks every Remote carrier shares: the Client handle before it
|
|
3
|
+
* queues an uplink item, the Gateway at its wire and codec-less uplink
|
|
4
|
+
* boundaries, and the in-process mock.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Test whether a value crosses JSON transport without coercion or omission.
|
|
8
|
+
* @param value - candidate boundary value.
|
|
9
|
+
* @returns whether the value is losslessly JSON-compatible.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isRemoteJsonValue(value: unknown): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Test whether a value may travel as one uplink item: a lossless JSON value, or
|
|
14
|
+
* a top-level `undefined`, which the wire carries as an `item` frame without
|
|
15
|
+
* `value`. Nested `undefined`, `NaN`, and infinities stay rejected.
|
|
16
|
+
* @param value - candidate uplink item.
|
|
17
|
+
* @returns whether the item crosses every carrier unchanged.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isRemoteUplinkItem(value: unknown): boolean;
|
|
20
|
+
//# sourceMappingURL=json-value.d.ts.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lossless JSON checks every Remote carrier shares: the Client handle before it
|
|
3
|
+
* queues an uplink item, the Gateway at its wire and codec-less uplink
|
|
4
|
+
* boundaries, and the in-process mock.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Test whether a value crosses JSON transport without coercion or omission.
|
|
8
|
+
* @param value - candidate boundary value.
|
|
9
|
+
* @returns whether the value is losslessly JSON-compatible.
|
|
10
|
+
*/
|
|
11
|
+
export function isRemoteJsonValue(value) {
|
|
12
|
+
return visitJsonValue(value, new Set());
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Test whether a value may travel as one uplink item: a lossless JSON value, or
|
|
16
|
+
* a top-level `undefined`, which the wire carries as an `item` frame without
|
|
17
|
+
* `value`. Nested `undefined`, `NaN`, and infinities stay rejected.
|
|
18
|
+
* @param value - candidate uplink item.
|
|
19
|
+
* @returns whether the item crosses every carrier unchanged.
|
|
20
|
+
*/
|
|
21
|
+
export function isRemoteUplinkItem(value) {
|
|
22
|
+
return value === undefined || isRemoteJsonValue(value);
|
|
23
|
+
}
|
|
24
|
+
function visitJsonValue(value, ancestors) {
|
|
25
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean')
|
|
26
|
+
return true;
|
|
27
|
+
if (typeof value === 'number')
|
|
28
|
+
return Number.isFinite(value) && !Object.is(value, -0);
|
|
29
|
+
if (typeof value !== 'object')
|
|
30
|
+
return false;
|
|
31
|
+
if (ancestors.has(value))
|
|
32
|
+
return false;
|
|
33
|
+
ancestors.add(value);
|
|
34
|
+
try {
|
|
35
|
+
if (Array.isArray(value)) {
|
|
36
|
+
if (Object.getPrototypeOf(value) !== Array.prototype
|
|
37
|
+
|| Reflect.ownKeys(value).length !== value.length + 1)
|
|
38
|
+
return false;
|
|
39
|
+
for (let index = 0; index < value.length; index++) {
|
|
40
|
+
if (!Object.hasOwn(value, index) || !visitJsonValue(value[index], ancestors))
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
const prototype = Object.getPrototypeOf(value);
|
|
46
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
47
|
+
return false;
|
|
48
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
49
|
+
if (typeof key !== 'string')
|
|
50
|
+
return false;
|
|
51
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
52
|
+
if (descriptor?.enumerable !== true || !visitJsonValue(Reflect.get(value, key), ancestors))
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
ancestors.delete(value);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=json-value.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Generic invocation-owned values returned by synchronous Client Context resolvers. */
|
|
2
|
+
/** Shared identity across independently bundled Context providers and Gateway. */
|
|
3
|
+
export declare const TYPERT_OWNED_VALUE: unique symbol;
|
|
4
|
+
/** A borrowed payload paired with the invocation owner's idempotent cleanup. */
|
|
5
|
+
export interface TypertOwnedValue<Value> extends Disposable {
|
|
6
|
+
readonly [TYPERT_OWNED_VALUE]: true;
|
|
7
|
+
readonly value: Value;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Transfer cleanup ownership without adding another resource reference count.
|
|
11
|
+
* @param value - resolved payload passed to the invocation.
|
|
12
|
+
* @param release - non-throwing synchronous release, called at most once.
|
|
13
|
+
* @returns an owned payload disposed after invocation and reply settlement.
|
|
14
|
+
*/
|
|
15
|
+
export declare function typertOwnedValue<Value>(value: Value, release: () => void): TypertOwnedValue<Value>;
|
|
16
|
+
/**
|
|
17
|
+
* Identify invocation-owned values using the shared marker.
|
|
18
|
+
* @param value - borrowed or owned resolver result.
|
|
19
|
+
* @returns whether the result carries invocation cleanup.
|
|
20
|
+
*/
|
|
21
|
+
export declare function isTypertOwnedValue(value: unknown): value is TypertOwnedValue<unknown>;
|
|
22
|
+
//# sourceMappingURL=owned-value.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Generic invocation-owned values returned by synchronous Client Context resolvers. */
|
|
2
|
+
/** Shared identity across independently bundled Context providers and Gateway. */
|
|
3
|
+
export const TYPERT_OWNED_VALUE = Symbol.for('dsh.typert.owned-value');
|
|
4
|
+
/**
|
|
5
|
+
* Transfer cleanup ownership without adding another resource reference count.
|
|
6
|
+
* @param value - resolved payload passed to the invocation.
|
|
7
|
+
* @param release - non-throwing synchronous release, called at most once.
|
|
8
|
+
* @returns an owned payload disposed after invocation and reply settlement.
|
|
9
|
+
*/
|
|
10
|
+
export function typertOwnedValue(value, release) {
|
|
11
|
+
let active = true;
|
|
12
|
+
return {
|
|
13
|
+
[TYPERT_OWNED_VALUE]: true,
|
|
14
|
+
value,
|
|
15
|
+
[Symbol.dispose]() {
|
|
16
|
+
if (!active)
|
|
17
|
+
return;
|
|
18
|
+
active = false;
|
|
19
|
+
release();
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Identify invocation-owned values using the shared marker.
|
|
25
|
+
* @param value - borrowed or owned resolver result.
|
|
26
|
+
* @returns whether the result carries invocation cleanup.
|
|
27
|
+
*/
|
|
28
|
+
export function isTypertOwnedValue(value) {
|
|
29
|
+
return typeof value === 'object' && value !== null
|
|
30
|
+
&& TYPERT_OWNED_VALUE in value && value[TYPERT_OWNED_VALUE] === true;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=owned-value.js.map
|
package/lib/types/types.d.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* @module @deepseek-ai/dsh-typert-protocol/types
|
|
5
5
|
*/
|
|
6
6
|
import type { Context, Events } from '@deepseek-ai/cordis';
|
|
7
|
+
import type { Branded } from '@deepseek-ai/dsh-brand';
|
|
8
|
+
import type { TypertOwnedValue } from './owned-value.ts';
|
|
7
9
|
declare const LOOKUP_HOST: unique symbol;
|
|
8
10
|
declare const LOOKUP_WIRE: unique symbol;
|
|
9
11
|
declare const CONTEXT_WIRE: unique symbol;
|
|
@@ -69,6 +71,51 @@ export type RemoteResult<T> = {
|
|
|
69
71
|
readonly ok: false;
|
|
70
72
|
readonly error: RemoteFailure;
|
|
71
73
|
};
|
|
74
|
+
declare const STREAM_UPLINK: unique symbol;
|
|
75
|
+
/**
|
|
76
|
+
* One Remote stream as a Host method returns it: the items it yields to the
|
|
77
|
+
* Client, iterated as a plain `AsyncIterable<Out>`. `In` is the type of the
|
|
78
|
+
* items the Client may send back on the same logical stream, read through
|
|
79
|
+
* `RemoteInvocation.uplink()`; it is carried only as a type-level marker. The
|
|
80
|
+
* default `never` declares a method that reads none, and its descriptor
|
|
81
|
+
* carries no uplink codec. A generated Client stream method returns the same
|
|
82
|
+
* stream as a `RemoteStreamHandle<Out, In>`.
|
|
83
|
+
* @template Out - item type the Host method yields.
|
|
84
|
+
* @template In - item type the Client may send; `never` when the method reads none.
|
|
85
|
+
*/
|
|
86
|
+
export type RemoteStream<Out, In = never> = AsyncIterable<Out> & {
|
|
87
|
+
readonly [STREAM_UPLINK]?: In;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* One open Remote stream as the Client holds it: the downlink items as an
|
|
91
|
+
* `AsyncIterable`, plus the uplink and cancellation of the same logical
|
|
92
|
+
* stream. A generated Client stream method returns it, and calling that
|
|
93
|
+
* method opens the stream: a holder that neither iterates nor disposes the
|
|
94
|
+
* handle keeps the Host stream alive. A handle stands for one generation:
|
|
95
|
+
* when the carrier is lost, iteration fails with the carrier error and the
|
|
96
|
+
* handle is finished.
|
|
97
|
+
* @template Out - item type the Host method yields.
|
|
98
|
+
* @template In - item type the Client may send; `never` when the method reads none.
|
|
99
|
+
*/
|
|
100
|
+
export interface RemoteStreamHandle<Out, In> extends AsyncIterable<Out> {
|
|
101
|
+
/**
|
|
102
|
+
* Send one uplink item. Items sent before the stream has opened are queued
|
|
103
|
+
* and sent once the `open` frame is on the wire. A top-level `undefined`
|
|
104
|
+
* travels as an `item` frame without `value`.
|
|
105
|
+
* @param item - item the Host validates against the method's uplink codec.
|
|
106
|
+
* @throws {Error} when the item is not a lossless JSON value, when `end()`
|
|
107
|
+
* was called, or once the stream has terminated.
|
|
108
|
+
*/
|
|
109
|
+
send(item: In): void;
|
|
110
|
+
/** Half-close the uplink: the Host's `uplink()` iteration ends. Idempotent; ignored after termination. */
|
|
111
|
+
end(): void;
|
|
112
|
+
/**
|
|
113
|
+
* Cancel the logical stream: send `cancel` unless a terminal frame has
|
|
114
|
+
* arrived, and end the downlink iterator quietly. Breaking out of
|
|
115
|
+
* `for await` early does the same.
|
|
116
|
+
*/
|
|
117
|
+
dispose(): void;
|
|
118
|
+
}
|
|
72
119
|
/** Merge-extensible scoped Remote method signatures generated for consumers. */
|
|
73
120
|
export interface TypertRemoteScopeMap {
|
|
74
121
|
}
|
|
@@ -152,7 +199,21 @@ export interface TypertSchema<Output = unknown> {
|
|
|
152
199
|
export type TypertCodec = {
|
|
153
200
|
readonly mode: 'strict';
|
|
154
201
|
readonly typeSymbol: string;
|
|
155
|
-
|
|
202
|
+
/** Materialize and return the process-realm schema on first boundary use. */
|
|
203
|
+
readonly create: () => TypertSchema;
|
|
204
|
+
/**
|
|
205
|
+
* Decode a unary result whose fields require type-specific handling.
|
|
206
|
+
* @param value - result reconstructed by the RPC carrier.
|
|
207
|
+
* @returns the validated result, retaining native byte views.
|
|
208
|
+
*/
|
|
209
|
+
readonly decode?: (value: unknown) => unknown;
|
|
210
|
+
/**
|
|
211
|
+
* Project typed binary fields into RPC result attachments.
|
|
212
|
+
* @param value - native unary result.
|
|
213
|
+
* @param writeBytes - records a byte view at its result-relative path and returns its JSON placeholder.
|
|
214
|
+
* @returns JSON metadata with untouched JSON subtrees retained.
|
|
215
|
+
*/
|
|
216
|
+
readonly encode?: (value: unknown, writeBytes: (bytes: Uint8Array, path: readonly (string | number)[]) => null) => unknown;
|
|
156
217
|
} | {
|
|
157
218
|
readonly mode: 'src-json';
|
|
158
219
|
};
|
|
@@ -189,7 +250,7 @@ export interface InvocationDescriptor {
|
|
|
189
250
|
readonly method: string;
|
|
190
251
|
/** Service member invoked when the exported method name is an alias. */
|
|
191
252
|
readonly implementation?: string;
|
|
192
|
-
/** Absent for unary calls; stream calls
|
|
253
|
+
/** Absent for unary calls; stream calls deliver every yielded item as the Host produced it. */
|
|
193
254
|
readonly mode?: 'stream';
|
|
194
255
|
/** Receiver selection mode. */
|
|
195
256
|
readonly invocation: {
|
|
@@ -209,6 +270,16 @@ export interface InvocationDescriptor {
|
|
|
209
270
|
};
|
|
210
271
|
/** Ordered business parameters. */
|
|
211
272
|
readonly parameters: readonly InvocationParameterDescriptor[];
|
|
273
|
+
/**
|
|
274
|
+
* Client-to-Host items of the same logical stream, generated from the `In`
|
|
275
|
+
* type argument of the method's `RemoteStream<Out, In>` return type; absent
|
|
276
|
+
* when `In` is `never`. The method reads the items through
|
|
277
|
+
* `RemoteInvocation.uplink()`, so nothing enters the parameter list.
|
|
278
|
+
*/
|
|
279
|
+
readonly uplink?: {
|
|
280
|
+
/** Codec validating every uplink item before `uplink()` delivers it. */
|
|
281
|
+
readonly codec: TypertCodec;
|
|
282
|
+
};
|
|
212
283
|
/** Transport cancellation injected after business parameters instead of entering wire args. */
|
|
213
284
|
readonly cancellation?: {
|
|
214
285
|
/** Reserved final Host method parameter. */
|
|
@@ -219,6 +290,56 @@ export interface InvocationDescriptor {
|
|
|
219
290
|
/** Source declaration used only for diagnostics. */
|
|
220
291
|
readonly sourceLocation?: InvocationSourceLocation;
|
|
221
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* Opaque identity of one Peer: a party admitted to this Host by the connection
|
|
295
|
+
* layer. "Peer" is a connection-layer word; the browser application keeps the
|
|
296
|
+
* word "Client".
|
|
297
|
+
*/
|
|
298
|
+
export type PeerId = Branded<'PeerId'>;
|
|
299
|
+
/**
|
|
300
|
+
* One Peer's session on this Host. Connection owns it: `ctx` is the Cordis
|
|
301
|
+
* scope that owns connection-lifetime registrations and is disposed with the
|
|
302
|
+
* Peer. Who the Peer is and what it may do are not recorded here.
|
|
303
|
+
*/
|
|
304
|
+
export interface PeerScope {
|
|
305
|
+
readonly id: PeerId;
|
|
306
|
+
readonly ctx: Context;
|
|
307
|
+
/**
|
|
308
|
+
* Tear down every registration made through `ctx`.
|
|
309
|
+
* @returns settles once the scope has quiesced; racing calls share one completion.
|
|
310
|
+
*/
|
|
311
|
+
dispose(): Promise<void>;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* The context of one Remote call, reachable inside the receiving method as
|
|
315
|
+
* `this.ctx.invocation`. The Gateway derives the receiver from a Context that
|
|
316
|
+
* carries it, so no parameter is injected and nothing crosses the wire.
|
|
317
|
+
*/
|
|
318
|
+
export interface RemoteInvocation {
|
|
319
|
+
readonly request: {
|
|
320
|
+
readonly namespace: string;
|
|
321
|
+
readonly method: string;
|
|
322
|
+
readonly args: Readonly<Record<string, unknown>>;
|
|
323
|
+
};
|
|
324
|
+
/** Cordis service key of the receiving Service. */
|
|
325
|
+
readonly service: string;
|
|
326
|
+
/** Peer the call speaks for; an in-process carrier speaks for the operator. */
|
|
327
|
+
readonly peer: PeerScope;
|
|
328
|
+
/** Carrier cancellation: Client cancel, socket close, or an uplink failure. */
|
|
329
|
+
readonly signal: AbortSignal;
|
|
330
|
+
/**
|
|
331
|
+
* The Client's uplink items for this call. Available once; a second call
|
|
332
|
+
* throws. With an uplink codec on the descriptor every item is decoded to
|
|
333
|
+
* `In`; without one items arrive as `unknown` after a JSON-safety check.
|
|
334
|
+
* Iteration ends when the Client ends its uplink; when the method finishes
|
|
335
|
+
* its downlink the Gateway calls the iterator's `return()` and unread items
|
|
336
|
+
* are dropped. `In` is the caller's assertion: the runtime decodes by the
|
|
337
|
+
* descriptor and does not cross-check it.
|
|
338
|
+
* @template In - item type the caller reads; the descriptor codec decides what arrives.
|
|
339
|
+
* @returns the single-consumer uplink iterable.
|
|
340
|
+
*/
|
|
341
|
+
uplink<In = unknown>(): AsyncIterable<In>;
|
|
342
|
+
}
|
|
222
343
|
/** Generated Host contract selected explicitly by a Client assembly. */
|
|
223
344
|
export interface TypertRemoteContribution {
|
|
224
345
|
/** npm package that owns the Remote methods. */
|
|
@@ -305,11 +426,11 @@ export interface TypertClientContextAdapter<Wire = unknown> {
|
|
|
305
426
|
*/
|
|
306
427
|
identity(ctx: Context): Wire | undefined;
|
|
307
428
|
/**
|
|
308
|
-
* Resolve a
|
|
429
|
+
* Resolve a validated identity synchronously for one Client invocation.
|
|
309
430
|
* @param id - validated wire identity.
|
|
310
|
-
* @returns
|
|
431
|
+
* @returns a borrowed or invocation-owned Client Context, or undefined when unavailable.
|
|
311
432
|
*/
|
|
312
|
-
resolve(id: Wire): Context | undefined;
|
|
433
|
+
resolve(id: Wire): Context | TypertOwnedValue<Context> | undefined;
|
|
313
434
|
}
|
|
314
435
|
/** Notification emitted after a Typert runtime registry changes. */
|
|
315
436
|
export interface TypertRegistryChange {
|
|
@@ -451,6 +572,12 @@ export interface TypertRegistryContract {
|
|
|
451
572
|
declare module '@deepseek-ai/cordis' {
|
|
452
573
|
interface Context {
|
|
453
574
|
typert: TypertRegistryContract;
|
|
575
|
+
/**
|
|
576
|
+
* The Remote call this Context was derived for, or `undefined` on a
|
|
577
|
+
* Context no Remote call derived. A Service method reads it as
|
|
578
|
+
* `this.ctx.invocation`.
|
|
579
|
+
*/
|
|
580
|
+
readonly invocation: RemoteInvocation | undefined;
|
|
454
581
|
}
|
|
455
582
|
}
|
|
456
583
|
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-typert-protocol",
|
|
3
3
|
"description": "Compiler-independent Remote metadata and Typert provider protocols",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.7-alpha.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -31,10 +31,13 @@
|
|
|
31
31
|
"lib/types/**/*.d.ts"
|
|
32
32
|
],
|
|
33
33
|
"license": "MIT",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@deepseek-ai/dsh-brand": "^0.1.7-alpha.1"
|
|
36
|
+
},
|
|
34
37
|
"peerDependencies": {
|
|
35
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
38
|
+
"@deepseek-ai/cordis": "^4.0.3"
|
|
36
39
|
},
|
|
37
40
|
"devDependencies": {
|
|
38
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
41
|
+
"@deepseek-ai/cordis": "^4.0.3"
|
|
39
42
|
}
|
|
40
43
|
}
|