@deepseek-ai/dsh-typert-generator 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 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/generator/README.md
5
- README.md: 7177e6a34520ab07645727edb15c5a726b9a2a4c
6
- README.zh.md: c1add1907c7c14ee7dd440dfaceee0ece0aa036e
5
+ README.md: 9c207574e9bb576a965ab1a91576a3b5a9db397e
6
+ README.zh.md: 9a6fa9e81d7950efb4d4b76df6921bc5a83b6adf
package/README.md CHANGED
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
9
9
 
10
10
  ## Summary
11
11
 
12
- `dsh-typert-generator` lets maintainers turn public TypeScript types into build artifacts and compiler-independent models. Packages opt in through the `./typert` and optional `./client/typert` exports, and generation rejects declarations, publish lists, Remote exports, or Zod projections that it cannot represent correctly. Repository builds can emit executable schemas and matching declarations, while tools can call `WorkspaceAnalyzer` for inspection or catalog generation without publishing artifacts. Generation runs only at build time and never in a live agent session.
12
+ `dsh-typert-generator` lets maintainers turn public TypeScript types into build artifacts and compiler-independent models. Packages opt in through the `./typert` and optional `./client/typert` exports, and generation rejects declarations, publish lists, Remote exports, or Zod projections that it cannot represent correctly. Repository builds emit executable schema factories and matching declarations, while tools can call `WorkspaceAnalyzer` for inspection or catalog generation without publishing artifacts. Generation runs only at build time and never in a live agent session.
13
13
 
14
14
  ## Table of Contents
15
15
 
@@ -79,14 +79,18 @@ The generator is built on one separation: extraction and emission are decoupled
79
79
 
80
80
  ### Analysis and faces
81
81
 
82
- Host and Client are independent TypeScript programs. Direct project references establish compiler-face membership, while `dsh.client` package subpaths establish runtime-face contribution; `package.json#exports` marks every cross-package public boundary, and imports or re-exports are the only cross-face edges. A relative import that resolves inside the referencing package is followed through that module's re-exports until a package specifier appears, so package-local forwarding modules keep their original declaration references; a relative import that resolves into another package fails. `check` mode fails on syntax or semantic diagnostics, missing public annotations, private cross-package references, and reachable declaration merges the model cannot retain losslessly; `write` mode inserts checker-derived annotations and returns a clean check-mode model. Types owned by NPM dependencies remain `external` references instead of being expanded.
82
+ Host and Client are independent TypeScript programs. Direct project references establish compiler-face membership, while `dsh.client` package subpaths establish runtime-face contribution; `package.json#exports` marks every cross-package public boundary, and imports or re-exports are the only cross-face edges. A relative import that resolves inside the referencing package is followed through that module's re-exports until a package specifier appears, so package-local forwarding modules keep their original declaration references; a relative import that resolves into another package fails. `check` mode fails on syntax or semantic diagnostics, missing public annotations, private cross-package references, and reachable declaration merges the model cannot retain losslessly; `write` mode inserts checker-derived annotations and returns a clean check-mode model. Types owned by NPM dependencies remain `external` references instead of being expanded. A stream method's return type may be `Iterable<Out>`, `AsyncIterable<Out>`, or the protocol's `RemoteStream<Out, In>`, recognized by symbol and declaring package like the standard-library wrappers; a second type argument other than `never` yields the descriptor's uplink codec, and the generated Client signature returns `RemoteStreamHandle<Out, In>`.
83
83
 
84
84
  ### Emission and publication contract
85
85
 
86
- `FaceModelEmitter` emits executable JavaScript containing supported Zod schemas and the `TYPERT` contribution, plus a declaration file whose schemas are typed `z.ZodType<SourceType>` through the package's public export; unsupported Zod projections fail. The Host face with Remote methods additionally emits `typert.remote-client.*` projections of Host Remote contracts for the Client. `WorkspaceTypertGenerator` validates each contributor's `package.json`: `./typert` and `./client/typert` (and `./remote` when Remote methods exist) must point at the exact generated files, and the `files` list must include them.
86
+ `FaceModelEmitter` emits executable JavaScript containing success-cached Zod schema factories and the `TYPERT` contribution, plus a declaration file whose factories return `z.ZodType<SourceType>` through the package's public export; unsupported Zod projections fail. The Host face with Remote methods additionally emits `typert.remote-client.*` projections of Host Remote contracts for the Client. `WorkspaceTypertGenerator` validates each contributor's `package.json`: `./typert` and `./client/typert` (and `./remote` when Remote methods exist) must point at the exact generated files, and the `files` list must include them.
87
+
88
+ [Unary binary results](../protocol/README.md) are recognized after generic type resolution. Their codecs validate the byte-array type and JSON metadata without iterating, copying, or freezing the byte payload; generated Client declarations retain the authored metadata and narrow the byte backing to `ArrayBuffer`.
87
89
 
88
90
  ### Catalog projection
89
91
 
92
+ The runtime type closure indexes exported workspace declarations and referenced framework enums. Vendored declarations stay outside business API discovery; their enums remain available when a public result refers to them.
93
+
90
94
  The root export includes the model-driven extraction, completeness checks, and deterministic text renderers used by this repository's Cordis catalogs. They accept a `CordisCatalogPolicy`; repository-owned type links, foundation and exemption classifications, and inherited Cordis entries stay in `scripts/gen-cordis-catalog.ts` and are passed in explicitly, so this package contains projection mechanics, not a hidden copy of the repository's documentation taxonomy.
91
95
 
92
96
  </details>
package/README.zh.md CHANGED
@@ -9,7 +9,7 @@ kind: "package-library"
9
9
 
10
10
  ## 概述
11
11
 
12
- `dsh-typert-generator` 让维护者把公开的 TypeScript 类型转换为构建产物和与编译器无关的模型。包通过 `./typert` 和可选的 `./client/typert` 导出选择加入;如果声明、发布清单、Remote 导出或 Zod 投影无法被正确表示,生成过程就会失败。仓库构建可以生成可执行 schema 与配套声明,工具也可以调用 `WorkspaceAnalyzer` 完成检查或目录生成而不发布产物。生成过程只在构建时运行,绝不会进入实时 agent(智能体)会话。
12
+ `dsh-typert-generator` 让维护者把公开的 TypeScript 类型转换为构建产物和与编译器无关的模型。包通过 `./typert` 和可选的 `./client/typert` 导出选择加入;如果声明、发布清单、Remote 导出或 Zod 投影无法被正确表示,生成过程就会失败。仓库构建会生成可执行 schema factory 与配套声明,工具也可以调用 `WorkspaceAnalyzer` 完成检查或目录生成而不发布产物。生成过程只在构建时运行,绝不会进入实时 agent(智能体)会话。
13
13
 
14
14
  ## 目录
15
15
 
@@ -79,14 +79,18 @@ files:
79
79
 
80
80
  ### 分析与 face
81
81
 
82
- Host 与 Client 是两个独立的 TypeScript 程序。直接项目引用确定编译器 face 的成员归属,`dsh.client` 包子路径则确定运行时 face 的贡献;`package.json#exports` 划定所有跨包公开边界,跨 face 的边只能来自导入或重新导出。解析到本包内部模块的相对导入会沿该模块的重新导出继续追踪,直到出现包说明符,因此包内转发模块保留原始声明引用;解析到其他包的相对导入会失败。`check` 模式遇到语法或语义诊断、缺失的公开类型标注、跨包私有引用,以及模型无法无损保留的可达声明合并时都会失败;`write` 模式插入类型检查器推导出的标注,并返回无诊断的 check 模式模型。NPM 依赖拥有的类型继续以 `external` 引用表示,不会被展开。
82
+ Host 与 Client 是两个独立的 TypeScript 程序。直接项目引用确定编译器 face 的成员归属,`dsh.client` 包子路径则确定运行时 face 的贡献;`package.json#exports` 划定所有跨包公开边界,跨 face 的边只能来自导入或重新导出。解析到本包内部模块的相对导入会沿该模块的重新导出继续追踪,直到出现包说明符,因此包内转发模块保留原始声明引用;解析到其他包的相对导入会失败。`check` 模式遇到语法或语义诊断、缺失的公开类型标注、跨包私有引用,以及模型无法无损保留的可达声明合并时都会失败;`write` 模式插入类型检查器推导出的标注,并返回无诊断的 check 模式模型。NPM 依赖拥有的类型继续以 `external` 引用表示,不会被展开。流方法的返回类型可以是 `Iterable<Out>`、`AsyncIterable<Out>` 或协议包的 `RemoteStream<Out, In>`,后者与标准库包装器一样按符号名与声明所在包识别;第二个类型参数不为 `never` 时产出描述符的上行编解码器,生成的 Client 签名则返回 `RemoteStreamHandle<Out, In>`。
83
83
 
84
84
  ### 生成与发布约定
85
85
 
86
- `FaceModelEmitter` 输出包含受支持 Zod schema 与 `TYPERT` 贡献的可执行 JavaScript,以及把 schema 通过包的公开导出标注为 `z.ZodType<SourceType>` 的声明文件;不支持的 Zod 投影会失败。含 Remote 方法的 Host face 还会额外为 Client 生成 Host Remote 约定的 `typert.remote-client.*` 投影。`WorkspaceTypertGenerator` 校验每个贡献方的 `package.json`:`./typert` 与 `./client/typert`(存在 Remote 方法时还有 `./remote`)必须指向精确的生成文件,且 `files` 清单必须包含它们。
86
+ `FaceModelEmitter` 输出包含只缓存成功结果的 Zod schema factory 与 `TYPERT` 贡献的可执行 JavaScript,以及把 factory 通过包的公开导出标注为返回 `z.ZodType<SourceType>` 的声明文件;不支持的 Zod 投影会失败。含 Remote 方法的 Host face 还会额外为 Client 生成 Host Remote 约定的 `typert.remote-client.*` 投影。`WorkspaceTypertGenerator` 校验每个贡献方的 `package.json`:`./typert` 与 `./client/typert`(存在 Remote 方法时还有 `./remote`)必须指向精确的生成文件,且 `files` 清单必须包含它们。
87
+
88
+ [一元二进制结果](../protocol/README.zh.md)在泛型类型解析后被识别。其 codec 校验字节数组类型与 JSON 元数据,不遍历、复制或冻结字节载荷;生成的 Client 声明保留原有元数据类型,并将字节的底层缓冲区收窄为 `ArrayBuffer`。
87
89
 
88
90
  ### 目录投影
89
91
 
92
+ 运行时类型闭包索引导出的 workspace 声明和被引用的框架 enum。Vendor 声明不参与业务 API 发现;公共结果引用其中 enum 时,目录仍提供其定义。
93
+
90
94
  根导出包含本仓库 Cordis 目录使用的模型驱动提取逻辑、完整性检查与确定性文本渲染器。它们接受 `CordisCatalogPolicy`;由仓库持有的类型链接、基础类型/豁免分类与继承的 Cordis 条目仍位于 `scripts/gen-cordis-catalog.ts`,由调用方显式传入,因此本包只包含投影机制,不会隐式复制仓库的文档分类体系。
91
95
 
92
96
  </details>
package/lib/index.js CHANGED
@@ -238,7 +238,7 @@ var WorkspaceAnalyzer = class WorkspaceAnalyzer {
238
238
  indexSourceDeclarations() {
239
239
  const selected = this.options.packages === void 0 ? void 0 : new Set(this.options.packages);
240
240
  const declarations = [];
241
- for (const registration of this.loadRegistrations()) {
241
+ for (const registration of this.loadRegistrations(true)) {
242
242
  if (!this.options.faces.includes(registration.face) || selected !== void 0 && !selected.has(registration.name)) continue;
243
243
  for (const file of registration.config.parsed.fileNames) {
244
244
  const relativeFile = slash(relative(this.options.root, file));
@@ -264,8 +264,8 @@ var WorkspaceAnalyzer = class WorkspaceAnalyzer {
264
264
  }
265
265
  return uniqueBy(declarations, (declaration) => `${declaration.face}\0${declaration.location.file}\0${String(declaration.location.line)}\0${declaration.name}`).sort((left, right) => left.face.localeCompare(right.face) || left.location.file.localeCompare(right.location.file) || left.location.line - right.location.line);
266
266
  }
267
- loadRegistrations() {
268
- const inventoryKey = `${this.options.root}\0${this.options.hostConfig}\0${this.options.clientConfig}`;
267
+ loadRegistrations(includeVendor = false) {
268
+ const inventoryKey = `${this.options.root}\0${this.options.hostConfig}\0${this.options.clientConfig}\0${String(includeVendor)}`;
269
269
  const cached = this.caches.registrations.get(inventoryKey);
270
270
  if (cached !== void 0) return cached;
271
271
  const registrations = [];
@@ -276,7 +276,7 @@ var WorkspaceAnalyzer = class WorkspaceAnalyzer {
276
276
  for (const reference of aggregate.parsed.projectReferences ?? []) {
277
277
  const configPath = projectConfigPath(reference.path);
278
278
  const packageRoot = dirname(configPath);
279
- if (!isWithin(realPath(packageRoot), join(this.options.root, "packages"))) continue;
279
+ if (!isWithin(realPath(packageRoot), join(this.options.root, "packages")) && !(includeVendor && isWithin(realPath(packageRoot), join(this.options.root, "vendor")))) continue;
280
280
  const manifestPath = join(packageRoot, "package.json");
281
281
  if (!existsSync(manifestPath)) continue;
282
282
  const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
@@ -774,7 +774,8 @@ var FaceAnalyzer = class {
774
774
  }
775
775
  }
776
776
  const mode = invocation.kind === "direct" ? invocation.mode : void 0;
777
- const resultType = this.remoteResultType(method, mode);
777
+ const { result: resultType, uplink: uplinkType } = this.remoteResultType(method, mode);
778
+ const uplink = uplinkType === void 0 ? void 0 : { boundary: this.remoteBoundary(uplinkType, `${registration.name}#${binding.namespace}/${exportedMethod}:uplink`, false, "undefined") };
778
779
  return {
779
780
  id: `${registration.name}#${binding.namespace}/${exportedMethod}`,
780
781
  service: binding.service,
@@ -785,8 +786,9 @@ var FaceAnalyzer = class {
785
786
  invocation: receiver,
786
787
  ...scope === void 0 ? {} : { scope },
787
788
  parameters,
789
+ ...uplink === void 0 ? {} : { uplink },
788
790
  ...cancellation === void 0 ? {} : { cancellation },
789
- result: this.remoteBoundary(resultType, `${registration.name}#${binding.namespace}/${exportedMethod}:result`, false, "undefined-or-void"),
791
+ result: this.remoteBoundary(resultType, `${registration.name}#${binding.namespace}/${exportedMethod}:result`, false, "undefined-or-void", false, mode === void 0),
790
792
  location: this.location(method.name)
791
793
  };
792
794
  }
@@ -865,10 +867,11 @@ var FaceAnalyzer = class {
865
867
  if (!ts.isObjectLiteralExpression(argument) || argument.properties.length !== 1) this.fail(argument, "Remote() options must contain exactly mode: \"stream\"");
866
868
  const [property] = argument.properties;
867
869
  if (property === void 0) this.fail(argument, "Remote() options must contain exactly mode: \"stream\"");
868
- if (!ts.isPropertyAssignment(property) || memberName(property.name) !== "mode" || stringLiteralValue(property.initializer) !== "stream") this.fail(property, "Remote() options must contain exactly mode: \"stream\"");
870
+ const mode = ts.isPropertyAssignment(property) && memberName(property.name) === "mode" ? stringLiteralValue(property.initializer) : void 0;
871
+ if (mode !== "stream") this.fail(property, "Remote() options must contain exactly mode: \"stream\"");
869
872
  marker = {
870
873
  kind: "direct",
871
- mode: "stream"
874
+ mode
872
875
  };
873
876
  }
874
877
  } else if (ts.isCallExpression(expression) && this.isTypeMetaSymbol(expression.expression, "RemoteScope")) {
@@ -889,18 +892,33 @@ var FaceAnalyzer = class {
889
892
  }
890
893
  return found;
891
894
  }
895
+ /**
896
+ * The item types a Remote method's authored return type declares. Unary
897
+ * methods unwrap `Promise<T>`; stream methods unwrap `Iterable<Out>`,
898
+ * `AsyncIterable<Out>`, or the protocol's `RemoteStream<Out, In>`, whose
899
+ * second type argument is the uplink item type unless it is `never`.
900
+ */
892
901
  remoteResultType(method, mode) {
893
902
  const authored = this.requiredType(method, method.type, "return");
894
903
  if (ts.isTypeReferenceNode(authored)) {
895
904
  const symbol = this.checker.getSymbolAtLocation(authored.typeName);
896
905
  const resolved = symbol === void 0 ? void 0 : this.resolveSymbol(symbol);
897
- const resultType = authored.typeArguments?.[0];
898
- const wrappers = mode === "stream" ? ["Iterable", "AsyncIterable"] : ["Promise"];
899
906
  const declaration = resolved === void 0 ? void 0 : preferredDeclaration(resolved);
900
- if (resolved !== void 0 && wrappers.includes(resolved.name) && resultType !== void 0 && authored.typeArguments?.length === 1 && declaration !== void 0 && isStandardLibraryFile(declaration.getSourceFile().fileName)) return resultType;
907
+ const [result, uplink] = authored.typeArguments ?? [];
908
+ const arity = authored.typeArguments?.length ?? 0;
909
+ if (resolved !== void 0 && declaration !== void 0 && result !== void 0) {
910
+ if (isStandardLibraryFile(declaration.getSourceFile().fileName) && (mode === void 0 ? ["Promise"] : ["Iterable", "AsyncIterable"]).includes(resolved.name) && arity === 1) return { result };
911
+ if (mode !== void 0 && resolved.name === "RemoteStream" && this.isTypeMetaSymbol(authored.typeName, "RemoteStream") && arity <= 2) return uplink === void 0 || this.isNeverType(uplink) ? { result } : {
912
+ result,
913
+ uplink
914
+ };
915
+ }
901
916
  }
902
- if (mode === "stream") this.fail(method, "stream Remote methods must return Iterable<T> or AsyncIterable<T>");
903
- return authored;
917
+ if (mode !== void 0) this.fail(method, "stream Remote methods must return Iterable<Out>, AsyncIterable<Out>, or RemoteStream<Out, In>");
918
+ return { result: authored };
919
+ }
920
+ isNeverType(type) {
921
+ return (this.checker.getTypeFromTypeNode(type).flags & ts.TypeFlags.Never) !== 0;
904
922
  }
905
923
  isGlobalAbortSignal(type) {
906
924
  const symbol = this.symbolAtType(type);
@@ -963,11 +981,11 @@ var FaceAnalyzer = class {
963
981
  }
964
982
  return result;
965
983
  }
966
- remoteBoundary(authoredType, fallbackTypeSymbol, requireNamed, topLevelAbsence = "reject", optional = false) {
984
+ remoteBoundary(authoredType, fallbackTypeSymbol, requireNamed, topLevelAbsence = "reject", optional = false, allowBytes = false) {
967
985
  const type = this.convertType(authoredType);
968
986
  const declaredType = this.checker.getTypeFromTypeNode(authoredType);
969
987
  const resolvedType = optional ? this.checker.getNullableType(declaredType, ts.TypeFlags.Undefined) : declaredType;
970
- const codecType = this.resolvedRemoteCodecType(authoredType, resolvedType, topLevelAbsence);
988
+ const codecType = this.resolvedRemoteCodecType(authoredType, resolvedType, topLevelAbsence, allowBytes);
971
989
  const acceptsUndefined = topLevelAbsence !== "reject" && this.includesRemoteAbsence(resolvedType);
972
990
  const rootSymbol = this.namedWorkspaceType(authoredType);
973
991
  const imports = /* @__PURE__ */ new Map();
@@ -1012,8 +1030,8 @@ var FaceAnalyzer = class {
1012
1030
  * validated without teaching the compiler-independent emitter TypeScript's
1013
1031
  * type evaluator.
1014
1032
  */
1015
- resolvedRemoteCodecType(authoredType, resolvedType, topLevelAbsence) {
1016
- this.assertRemoteJsonType(resolvedType, authoredType, /* @__PURE__ */ new Set(), topLevelAbsence !== "reject", topLevelAbsence === "undefined-or-void");
1033
+ resolvedRemoteCodecType(authoredType, resolvedType, topLevelAbsence, allowBytes) {
1034
+ this.assertRemoteJsonType(resolvedType, authoredType, /* @__PURE__ */ new Set(), topLevelAbsence !== "reject", topLevelAbsence === "undefined-or-void", allowBytes);
1017
1035
  const completed = /* @__PURE__ */ new Map();
1018
1036
  const active = /* @__PURE__ */ new Map();
1019
1037
  const recursiveDeclarations = /* @__PURE__ */ new Map();
@@ -1044,6 +1062,15 @@ var FaceAnalyzer = class {
1044
1062
  return id;
1045
1063
  };
1046
1064
  const flags = type.flags;
1065
+ if (this.isRemoteByteArray(type)) return add({
1066
+ kind: "reference",
1067
+ name: "Uint8Array",
1068
+ target: {
1069
+ kind: "standard",
1070
+ name: "Uint8Array"
1071
+ },
1072
+ arguments: []
1073
+ });
1047
1074
  if ((flags & ts.TypeFlags.Any) !== 0) return add({
1048
1075
  kind: "keyword",
1049
1076
  name: "any"
@@ -1127,7 +1154,7 @@ var FaceAnalyzer = class {
1127
1154
  types: type.types.map(convert)
1128
1155
  });
1129
1156
  if ((flags & ts.TypeFlags.TypeParameter) !== 0) this.fail(authoredType, "Remote codec contains an unresolved type parameter");
1130
- if ((flags & ts.TypeFlags.Object) === 0) this.fail(authoredType, `Remote codec type ${this.checker.typeToString(type, authoredType, ts.TypeFormatFlags.NoTruncation)} has no concrete Zod projection`);
1157
+ if ((flags & (ts.TypeFlags.Object | ts.TypeFlags.Intersection)) === 0) this.fail(authoredType, `Remote codec type ${this.checker.typeToString(type, authoredType, ts.TypeFormatFlags.NoTruncation)} has no concrete Zod projection`);
1131
1158
  if (this.checker.isTupleType(type)) {
1132
1159
  const reference = type;
1133
1160
  const target = reference.target;
@@ -1136,7 +1163,10 @@ var FaceAnalyzer = class {
1136
1163
  elements: this.checker.getTypeArguments(reference).map((argument, index) => {
1137
1164
  const elementFlags = target.elementFlags[index] ?? ts.ElementFlags.Required;
1138
1165
  return {
1139
- type: convert(argument),
1166
+ type: (elementFlags & ts.ElementFlags.Rest) !== 0 ? this.addNode(authoredType, {
1167
+ kind: "array",
1168
+ element: convert(argument)
1169
+ }) : convert(argument),
1140
1170
  optional: (elementFlags & ts.ElementFlags.Optional) !== 0,
1141
1171
  rest: (elementFlags & (ts.ElementFlags.Rest | ts.ElementFlags.Variadic)) !== 0
1142
1172
  };
@@ -1210,25 +1240,29 @@ var FaceAnalyzer = class {
1210
1240
  };
1211
1241
  return convert(resolvedType);
1212
1242
  }
1213
- assertRemoteJsonType(type, site, active, allowUndefined, allowVoid) {
1243
+ assertRemoteJsonType(type, site, active, allowUndefined, allowVoid, allowBytes = false) {
1214
1244
  const flags = type.flags;
1215
1245
  if ((flags & ts.TypeFlags.Undefined) !== 0 && allowUndefined) return;
1216
1246
  if ((flags & ts.TypeFlags.Void) !== 0 && allowVoid) return;
1217
1247
  if ((flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) this.fail(site, `Remote boundary contains unconstrained ${this.checker.typeToString(type)} data`);
1218
1248
  if ((flags & (ts.TypeFlags.BigIntLike | ts.TypeFlags.ESSymbolLike | ts.TypeFlags.Undefined | ts.TypeFlags.Void)) !== 0) this.fail(site, `Remote boundary contains non-JSON type ${this.checker.typeToString(type)}`);
1219
1249
  if ((flags & (ts.TypeFlags.StringLike | ts.TypeFlags.NumberLike | ts.TypeFlags.BooleanLike | ts.TypeFlags.Null | ts.TypeFlags.Never)) !== 0) return;
1250
+ if (this.isRemoteByteArray(type)) {
1251
+ if (allowBytes) return;
1252
+ this.fail(site, "Remote Uint8Array is only supported in unary results");
1253
+ }
1220
1254
  if (type.isUnion()) {
1221
- for (const member of type.types) this.assertRemoteJsonType(member, site, active, allowUndefined, allowVoid);
1255
+ for (const member of type.types) this.assertRemoteJsonType(member, site, active, allowUndefined, allowVoid, allowBytes);
1222
1256
  return;
1223
1257
  }
1224
1258
  if (type.isIntersection()) {
1225
1259
  const material = type.types.filter((member) => !this.isRemotePhantomConstraint(member));
1226
1260
  if (material.length === 0) this.fail(site, "Remote boundary contains a symbol-only object");
1227
- for (const member of material) this.assertRemoteJsonType(member, site, active, false, false);
1261
+ for (const member of material) this.assertRemoteJsonType(member, site, active, false, false, allowBytes);
1228
1262
  return;
1229
1263
  }
1230
1264
  if ((flags & ts.TypeFlags.TypeParameter) !== 0) this.fail(site, "Remote boundary contains an unresolved type parameter");
1231
- if ((flags & ts.TypeFlags.Object) === 0) this.fail(site, `Remote boundary contains non-JSON type ${this.checker.typeToString(type)}`);
1265
+ if ((flags & (ts.TypeFlags.Object | ts.TypeFlags.Intersection)) === 0) this.fail(site, `Remote boundary contains non-JSON type ${this.checker.typeToString(type)}`);
1232
1266
  const symbol = type.getSymbol();
1233
1267
  const declaration = symbol?.valueDeclaration ?? symbol?.declarations?.[0];
1234
1268
  if (declaration !== void 0 && (ts.isClassDeclaration(declaration) || ts.isClassExpression(declaration))) this.fail(site, `Remote boundary contains class instance ${symbol?.name ?? this.checker.typeToString(type)}`);
@@ -1241,14 +1275,14 @@ var FaceAnalyzer = class {
1241
1275
  const target = reference.target;
1242
1276
  this.checker.getTypeArguments(reference).forEach((argument, index) => {
1243
1277
  const elementFlags = target.elementFlags[index] ?? ts.ElementFlags.Required;
1244
- this.assertRemoteJsonType(argument, site, active, (elementFlags & ts.ElementFlags.Optional) !== 0, false);
1278
+ this.assertRemoteJsonType(argument, site, active, (elementFlags & ts.ElementFlags.Optional) !== 0, false, allowBytes);
1245
1279
  });
1246
1280
  return;
1247
1281
  }
1248
1282
  if (this.checker.isArrayType(type) || this.checker.isArrayLikeType(type)) {
1249
1283
  const element = this.checker.getIndexTypeOfType(type, ts.IndexKind.Number);
1250
1284
  if (element === void 0) this.fail(site, "Remote boundary array has no element type");
1251
- this.assertRemoteJsonType(element, site, active, false, false);
1285
+ this.assertRemoteJsonType(element, site, active, false, false, allowBytes);
1252
1286
  return;
1253
1287
  }
1254
1288
  const properties = this.checker.getPropertiesOfType(type);
@@ -1256,16 +1290,20 @@ var FaceAnalyzer = class {
1256
1290
  for (const property of properties) {
1257
1291
  const propertyDeclaration = property.valueDeclaration ?? property.declarations?.[0];
1258
1292
  const propertyType = this.checker.getTypeOfSymbolAtLocation(property, propertyDeclaration ?? site);
1259
- this.assertRemoteJsonType(propertyType, site, active, (property.flags & ts.SymbolFlags.Optional) !== 0, false);
1293
+ this.assertRemoteJsonType(propertyType, site, active, (property.flags & ts.SymbolFlags.Optional) !== 0, false, allowBytes);
1260
1294
  }
1261
1295
  for (const info of this.checker.getIndexInfosOfType(type)) {
1262
1296
  if ((info.keyType.flags & ts.TypeFlags.ESSymbolLike) !== 0) this.fail(site, "Remote boundary contains a symbol index signature");
1263
- this.assertRemoteJsonType(info.type, site, active, false, false);
1297
+ this.assertRemoteJsonType(info.type, site, active, false, false, allowBytes);
1264
1298
  }
1265
1299
  } finally {
1266
1300
  active.delete(type);
1267
1301
  }
1268
1302
  }
1303
+ isRemoteByteArray(type) {
1304
+ const symbol = type.getSymbol();
1305
+ return symbol?.name === "Uint8Array" && symbol.declarations?.some((declaration) => isStandardLibraryFile(declaration.getSourceFile().fileName)) === true;
1306
+ }
1269
1307
  includesRemoteAbsence(type) {
1270
1308
  if ((type.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Void)) !== 0) return true;
1271
1309
  return type.isUnion() && type.types.some((member) => this.includesRemoteAbsence(member));
@@ -2821,7 +2859,7 @@ var FaceModelEmitter = class {
2821
2859
  lines.push(` package: ${quote$1(packageModel.name)},`);
2822
2860
  lines.push(` face: ${quote$1(this.face.face)},`);
2823
2861
  lines.push(" schemas: [");
2824
- for (const schema of schemas.exports) lines.push(` { name: ${quote$1(schema.exportName)}, schema: ${schema.exportName} },`);
2862
+ for (const schema of schemas.exports) lines.push(` { name: ${quote$1(schema.exportName)}, create: ${schema.exportName} },`);
2825
2863
  lines.push(" ],");
2826
2864
  lines.push(" invocations: [");
2827
2865
  for (const invocation of packageModel.invocations) lines.push(`${indent(this.invocationLiteral(invocation, schemas), 4)},`);
@@ -2842,7 +2880,7 @@ var FaceModelEmitter = class {
2842
2880
  if (schemas.exports.length > 0) lines.splice(1, 0, "import type { z } from 'zod'");
2843
2881
  for (const [specifier, names] of [...imports].sort(([left], [right]) => left.localeCompare(right))) lines.push(`import type { ${names.sort().join(", ")} } from ${quote$1(specifier)}`);
2844
2882
  lines.push("");
2845
- for (const schema of schemas.exports) lines.push(`export declare const ${schema.exportName}: z.ZodType<${schema.exportName}$source>`);
2883
+ for (const schema of schemas.exports) lines.push(`export declare const ${schema.exportName}: () => z.ZodType<${schema.exportName}$source>`);
2846
2884
  if (schemas.exports.length > 0) lines.push("");
2847
2885
  lines.push("export declare const TYPERT: unknown");
2848
2886
  return `${lines.join("\n")}\n`;
@@ -2904,8 +2942,13 @@ var FaceModelEmitter = class {
2904
2942
  lines.push(" },");
2905
2943
  });
2906
2944
  lines.push(" ],");
2945
+ if (invocation.uplink !== void 0) {
2946
+ lines.push(" uplink: {");
2947
+ lines.push(` codec: ${indent(strictCodec(invocation.uplink.boundary, schemas.boundary(uplinkBoundaryKey(invocation))), 4).trimStart()},`);
2948
+ lines.push(" },");
2949
+ }
2907
2950
  if (invocation.cancellation !== void 0) lines.push(" cancellation: { parameter: 'signal' },");
2908
- lines.push(` result: ${indent(strictCodec(invocation.result, schemas.boundary(resultBoundaryKey(invocation))), 2).trimStart()},`);
2951
+ lines.push(` result: ${indent(strictCodec(invocation.result, schemas.boundary(resultBoundaryKey(invocation)), schemas.encoder(resultBoundaryKey(invocation))), 2).trimStart()},`);
2909
2952
  lines.push(` sourceLocation: ${JSON.stringify(invocation.location)},`);
2910
2953
  lines.push("}");
2911
2954
  return lines.join("\n");
@@ -2926,6 +2969,7 @@ var FaceModelEmitter = class {
2926
2969
  "/* Generated by @deepseek-ai/dsh-typert-generator from the Host FaceModel — do not edit. */",
2927
2970
  "import type {",
2928
2971
  " RemoteResult,",
2972
+ ...packageModel.invocations.some((invocation) => invocation.mode === "stream") ? [" RemoteStreamHandle,"] : [],
2929
2973
  " TypertRemoteContribution,",
2930
2974
  "} from '@deepseek-ai/dsh-typert-protocol'"
2931
2975
  ];
@@ -2935,6 +2979,12 @@ var FaceModelEmitter = class {
2935
2979
  lines.push(`import type { ${names.join(", ")} } from ${quote$1(specifier)}`);
2936
2980
  }
2937
2981
  lines.push("");
2982
+ if (packageModel.invocations.some((invocation) => containsBytes(this.renderer, invocation.result.codecType))) {
2983
+ lines.push("type RemoteDecoded<Value> = Value extends Uint8Array ? Uint8Array<ArrayBuffer>");
2984
+ lines.push(" : Value extends string | number | boolean | null | undefined ? Value");
2985
+ lines.push(" : { [Key in keyof Value]: RemoteDecoded<Value[Key]> }");
2986
+ lines.push("");
2987
+ }
2938
2988
  lines.push("declare module '@deepseek-ai/dsh-typert-protocol' {");
2939
2989
  const direct = packageModel.invocations.filter((invocation) => invocation.invocation.kind === "direct");
2940
2990
  const scoped = packageModel.invocations.filter((invocation) => invocation.invocation.kind === "context" || invocation.scope !== void 0);
@@ -3005,8 +3055,12 @@ var FaceModelEmitter = class {
3005
3055
  remoteFunctionType(invocation, referenceNames, scoped) {
3006
3056
  const parameters = invocation.parameters.filter((parameter) => !scoped || invocation.invocation.kind === "context" || parameter.wire !== invocation.scope?.wire).map((parameter) => `${safeIdentifier(parameter.wire)}${parameter.optional === true ? "?" : ""}: ${this.renderer.renderType(parameter.boundary.type, referenceNames)}`);
3007
3057
  if (invocation.cancellation !== void 0) parameters.push("signal?: AbortSignal");
3008
- const result = this.renderer.renderType(invocation.result.type, referenceNames);
3009
- if (invocation.mode === "stream") return `(${parameters.join(", ")}) => AsyncIterable<${result}>`;
3058
+ const authoredResult = this.renderer.renderType(invocation.result.type, referenceNames);
3059
+ const result = containsBytes(this.renderer, invocation.result.codecType) ? `RemoteDecoded<${authoredResult}>` : authoredResult;
3060
+ if (invocation.mode === "stream") {
3061
+ const uplink = invocation.uplink === void 0 ? "never" : this.renderer.renderType(invocation.uplink.boundary.type, referenceNames);
3062
+ return `(${parameters.join(", ")}) => RemoteStreamHandle<${result}, ${uplink}>`;
3063
+ }
3010
3064
  return `(${parameters.join(", ")}) => Promise<RemoteResult<${result}>>`;
3011
3065
  }
3012
3066
  };
@@ -3021,6 +3075,100 @@ function uniqueNamespaces(invocations) {
3021
3075
  function remoteNamespaceInterface(namespace) {
3022
3076
  return `TypertRemoteNamespace$${Buffer.from(namespace, "utf8").toString("hex")}`;
3023
3077
  }
3078
+ var ResultEncoderEmitter = class {
3079
+ renderer;
3080
+ names = /* @__PURE__ */ new Map();
3081
+ bodies = /* @__PURE__ */ new Map();
3082
+ constructor(renderer) {
3083
+ this.renderer = renderer;
3084
+ }
3085
+ reference(id) {
3086
+ if (!containsBytes(this.renderer, id)) return void 0;
3087
+ const existing = this.names.get(id);
3088
+ if (existing !== void 0) return existing;
3089
+ const name = `$encode${this.names.size}`;
3090
+ this.names.set(id, name);
3091
+ const node = this.renderer.node(id);
3092
+ const container = node.kind === "object" || node.kind === "array" || node.kind === "tuple";
3093
+ const body = container || node.kind === "union" || node.kind === "intersection" ? `return $resultContainer(value, path, ancestors, value => {\n${indent(this.body(id), 2)}\n}, ${container})` : this.body(id);
3094
+ this.bodies.set(id, `const ${name} = (value, writeBytes, path, ancestors) => {\n${indent(body, 2)}\n}`);
3095
+ return name;
3096
+ }
3097
+ definitions() {
3098
+ return this.bodies.size === 0 ? [] : [RESULT_SNAPSHOT, ...this.bodies.values()];
3099
+ }
3100
+ call(id, value, path = "path") {
3101
+ const reference = this.reference(id);
3102
+ return reference === void 0 ? value : `${reference}(${value}, writeBytes, ${path}, ancestors)`;
3103
+ }
3104
+ body(id) {
3105
+ const node = this.renderer.node(id);
3106
+ switch (node.kind) {
3107
+ case "reference":
3108
+ if (node.target.kind === "standard" && node.target.name === "Uint8Array") return "return value instanceof Uint8Array ? writeBytes(value, path) : value";
3109
+ if (node.target.kind === "declaration") {
3110
+ const type = this.renderer.declaration(node.target.symbol).type;
3111
+ if (type !== void 0) return `return ${this.call(type, "value")}`;
3112
+ }
3113
+ break;
3114
+ case "union":
3115
+ case "intersection": return [...node.types.filter((type) => containsBytes(this.renderer, type)).map((type) => `value = ${this.call(type, "value")}`), "return value"].join("\n");
3116
+ case "array": return [
3117
+ "if (!Array.isArray(value)) return value",
3118
+ `for (let index = 0; index < value.length; index++) value[index] = ${this.call(node.element, "value[index]", "[...path, index]")}`,
3119
+ "return value"
3120
+ ].join("\n");
3121
+ case "tuple": {
3122
+ const lines = ["if (!Array.isArray(value)) return value"];
3123
+ for (const [index, element] of node.elements.entries()) {
3124
+ if (!containsBytes(this.renderer, element.type)) continue;
3125
+ if (element.rest) {
3126
+ const rest = this.renderer.node(element.type);
3127
+ if (rest.kind !== "array") break;
3128
+ lines.push(`for (let index = ${index}; index < value.length; index++) value[index] = ${this.call(rest.element, "value[index]", "[...path, index]")}`);
3129
+ } else lines.push(`if (value.length > ${index}) value[${index}] = ${this.call(element.type, `value[${index}]`, `[...path, ${index}]`)}`);
3130
+ }
3131
+ return [...lines, "return value"].join("\n");
3132
+ }
3133
+ case "object": {
3134
+ const lines = ["if (value === null || typeof value !== 'object' || Array.isArray(value) || value instanceof Uint8Array) return value"];
3135
+ for (const member of node.members) if (member.kind === "property" && containsBytes(this.renderer, member.type)) {
3136
+ const key = quote$1(member.name);
3137
+ lines.push(`if (Object.hasOwn(value, ${key})) value[${key}] = ${this.call(member.type, `value[${key}]`, `[...path, ${key}]`)}`);
3138
+ } else if (member.kind === "index" && containsBytes(this.renderer, member.signature.returns)) lines.push(`for (const key of Object.keys(value)) value[key] = ${this.call(member.signature.returns, "value[key]", "[...path, key]")}`);
3139
+ return [...lines, "return value"].join("\n");
3140
+ }
3141
+ default: break;
3142
+ }
3143
+ throw new TypertEmitError(`typert result encoder: ${node.kind} has no binary field projection`);
3144
+ }
3145
+ };
3146
+ const RESULT_SNAPSHOT = `const $resultSnapshot = (input, path) => {
3147
+ if (input === null || typeof input !== 'object' || input instanceof Uint8Array) return input
3148
+ const toJSON = input.toJSON
3149
+ const value = typeof toJSON === 'function' ? toJSON.call(input, path.at(-1)?.toString() ?? 'value') : input
3150
+ if (value === null || typeof value !== 'object' || value instanceof Uint8Array) return value
3151
+ if (Array.isArray(value)) {
3152
+ const items = []
3153
+ for (let index = 0, length = value.length; index < length; index++) items.push(value[index])
3154
+ return items
3155
+ }
3156
+ const fields = {}
3157
+ for (const key of Object.keys(value)) {
3158
+ const item = key === 'toJSON' && value === input ? toJSON : value[key]
3159
+ if (key === 'toJSON' && typeof item === 'function') continue
3160
+ Object.defineProperty(fields, key, { value: item, enumerable: true, writable: true, configurable: true })
3161
+ }
3162
+ return fields
3163
+ }
3164
+ const $resultContainer = (input, path, ancestors, project, snapshot) => {
3165
+ if (input === null || typeof input !== 'object') return project(input)
3166
+ const owner = !ancestors.has(input)
3167
+ if (!owner && ancestors.get(input) !== path.length) throw new TypeError('Remote result contains a circular object')
3168
+ if (owner) ancestors.set(input, path.length)
3169
+ try { return project(snapshot ? $resultSnapshot(input, path) : input) }
3170
+ finally { if (owner) ancestors.delete(input) }
3171
+ }`;
3024
3172
  var SchemaEmitter = class {
3025
3173
  renderer;
3026
3174
  schemas;
@@ -3056,7 +3204,15 @@ var SchemaEmitter = class {
3056
3204
  }
3057
3205
  emit() {
3058
3206
  const definitions = this.declarations.map((declaration) => this.declarationDefinition(declaration));
3059
- for (const boundary of this.boundaries) definitions.push(`const ${this.boundaryName(boundary.key)} = ${this.typeSchema(boundary.type)}`);
3207
+ const encoder = new ResultEncoderEmitter(this.renderer);
3208
+ const encoders = /* @__PURE__ */ new Map();
3209
+ for (const boundary of this.boundaries) {
3210
+ const encode = encoder.reference(boundary.type);
3211
+ if (encode !== void 0) encoders.set(boundary.key, encode);
3212
+ const name = this.boundaryName(boundary.key);
3213
+ definitions.push(`let ${name}$value\nconst ${name} = () => (${name}$value ??= ${this.typeSchema(boundary.type)})`);
3214
+ }
3215
+ definitions.push(...encoder.definitions());
3060
3216
  return {
3061
3217
  definitions,
3062
3218
  exports: this.schemas.map((model) => ({
@@ -3064,12 +3220,13 @@ var SchemaEmitter = class {
3064
3220
  exportName: safeIdentifier(model.export.name),
3065
3221
  internalName: this.exportSchemaName(model)
3066
3222
  })),
3067
- boundary: (key) => this.boundaryName(key)
3223
+ boundary: (key) => this.boundaryName(key),
3224
+ encoder: (key) => encoders.get(key)
3068
3225
  };
3069
3226
  }
3070
3227
  declarationDefinition(declaration) {
3071
3228
  const name = this.schemaName(declaration.id);
3072
- if (declaration.typeParameters.length === 0) return `const ${name} = ${this.declarationSchema(declaration, /* @__PURE__ */ new Map())}`;
3229
+ if (declaration.typeParameters.length === 0) return `let ${name}$value\nconst ${name} = () => (${name}$value ??= ${this.declarationSchema(declaration, /* @__PURE__ */ new Map())})`;
3073
3230
  const parameters = declaration.typeParameters.map((parameter, index) => [`type${String(index)}$schema`, parameter.id]);
3074
3231
  const substitutions = new Map(parameters.map(([schema, id]) => [id, schema]));
3075
3232
  return `const ${name} = (${parameters.map(([schema]) => schema).join(", ")}) => ${this.declarationSchema(declaration, substitutions)}`;
@@ -3129,7 +3286,7 @@ var SchemaEmitter = class {
3129
3286
  const declaration = this.renderer.declaration(node.target.symbol);
3130
3287
  if (declaration.typeParameters.length === 0) {
3131
3288
  if (node.arguments.length > 0) this.fail(node.name, `non-generic declaration received ${String(node.arguments.length)} type arguments`);
3132
- return `z.lazy(() => ${name})`;
3289
+ return `z.lazy(() => ${name}())`;
3133
3290
  }
3134
3291
  return `z.lazy(() => ${name}(${this.declarationArguments(node, declaration, substitutions).join(", ")}))`;
3135
3292
  }
@@ -3153,6 +3310,7 @@ var SchemaEmitter = class {
3153
3310
  return `z.record(${this.typeSchema(key, substitutions)}, ${this.typeSchema(value, substitutions)})`;
3154
3311
  }
3155
3312
  case "Date": return "z.date()";
3313
+ case "Uint8Array": return "z.instanceof(Uint8Array)";
3156
3314
  default: this.fail(node.name, `standard type ${node.target.name} has no Zod projection`);
3157
3315
  }
3158
3316
  this.fail(node.name, `${node.target.kind} reference has no Zod projection`);
@@ -3197,7 +3355,8 @@ var SchemaEmitter = class {
3197
3355
  continue;
3198
3356
  }
3199
3357
  if (member.kind !== "property") this.fail(subject, `${member.kind} member ${member.name} is not data-schema projectable`);
3200
- const property = this.describe(this.optional(this.readonly(this.typeSchema(member.type, substitutions), member.readonly), member.optional), member);
3358
+ const schema = this.readonly(this.typeSchema(member.type, substitutions), member.readonly && !containsBytes(this.renderer, member.type, false));
3359
+ const property = this.describe(this.optional(schema, member.optional), member);
3201
3360
  properties.push(`${quote$1(member.jsonName ?? member.name)}: ${property}`);
3202
3361
  }
3203
3362
  if (indices.length > 1) this.fail(subject, "object type has more than one JSON index signature");
@@ -3276,6 +3435,10 @@ function invocationBoundaryRoots(invocations) {
3276
3435
  type: parameter.boundary.codecType
3277
3436
  });
3278
3437
  });
3438
+ if (invocation.uplink !== void 0) result.push({
3439
+ key: uplinkBoundaryKey(invocation),
3440
+ type: invocation.uplink.boundary.codecType
3441
+ });
3279
3442
  result.push({
3280
3443
  key: resultBoundaryKey(invocation),
3281
3444
  type: invocation.result.codecType
@@ -3286,18 +3449,38 @@ function invocationBoundaryRoots(invocations) {
3286
3449
  function contextBoundaryKey(invocation) {
3287
3450
  return `${invocation.id}:context`;
3288
3451
  }
3452
+ function uplinkBoundaryKey(invocation) {
3453
+ return `${invocation.id}:uplink`;
3454
+ }
3289
3455
  function parameterBoundaryKey(invocation, index) {
3290
3456
  return `${invocation.id}:parameter:${String(index)}`;
3291
3457
  }
3292
3458
  function resultBoundaryKey(invocation) {
3293
3459
  return `${invocation.id}:result`;
3294
3460
  }
3295
- function strictCodec(boundary, schema) {
3461
+ function containsBytes(renderer, id, nested = true, visited = /* @__PURE__ */ new Set()) {
3462
+ if (visited.has(id)) return false;
3463
+ visited.add(id);
3464
+ const node = renderer.node(id);
3465
+ if (node.kind === "reference") {
3466
+ if (node.target.kind === "standard" && node.target.name === "Uint8Array") return true;
3467
+ if (node.target.kind === "declaration") {
3468
+ const declaration = renderer.declaration(node.target.symbol);
3469
+ if (declaration.type !== void 0 && containsBytes(renderer, declaration.type, nested, visited)) return true;
3470
+ }
3471
+ if (!nested) return false;
3472
+ }
3473
+ if (!nested && node.kind !== "reference" && node.kind !== "parenthesized" && node.kind !== "union" && node.kind !== "intersection") return false;
3474
+ if (node.kind === "object" && node.members.some((member) => containsBytes(renderer, member.kind === "property" ? member.type : member.signature.returns, nested, visited))) return true;
3475
+ return childTypeNodeIds(node).some((child) => containsBytes(renderer, child, nested, visited));
3476
+ }
3477
+ function strictCodec(boundary, schema, encoder) {
3296
3478
  return [
3297
3479
  "{",
3298
3480
  " mode: 'strict',",
3299
3481
  ` typeSymbol: ${quote$1(boundary.typeSymbol)},`,
3300
- ` schema: ${schema},`,
3482
+ ` create: ${schema},`,
3483
+ ...encoder === void 0 ? [] : [` decode: value => ${schema}().parse(value),`, ` encode: (value, writeBytes) => ${encoder}(value, writeBytes, [], new Map()),`],
3301
3484
  "}"
3302
3485
  ].join("\n");
3303
3486
  }
@@ -3313,12 +3496,19 @@ function remoteImports(invocations) {
3313
3496
  for (const invocation of invocations) {
3314
3497
  if (invocation.invocation.kind === "context") add(invocation.invocation.boundary);
3315
3498
  for (const parameter of invocation.parameters) add(parameter.boundary);
3499
+ if (invocation.uplink !== void 0) add(invocation.uplink.boundary);
3316
3500
  add(invocation.result);
3317
3501
  }
3318
3502
  return [...imports.values()].sort((left, right) => left.specifier.localeCompare(right.specifier) || left.name.localeCompare(right.name));
3319
3503
  }
3320
3504
  function allocateRemoteImportNames(imports) {
3321
- const used = new Set(["TypertRemoteContribution", "TYPERT_REMOTE"]);
3505
+ const used = new Set([
3506
+ "RemoteDecoded",
3507
+ "RemoteResult",
3508
+ "RemoteStreamHandle",
3509
+ "TypertRemoteContribution",
3510
+ "TYPERT_REMOTE"
3511
+ ]);
3322
3512
  const names = /* @__PURE__ */ new Map();
3323
3513
  for (const imported of imports) {
3324
3514
  const base = safeIdentifier(imported.name);
@@ -3529,13 +3719,14 @@ var CordisCatalogProjector = class {
3529
3719
  runtimeTypes(services, events) {
3530
3720
  const declarations = /* @__PURE__ */ new Map();
3531
3721
  const ambiguous = /* @__PURE__ */ new Set();
3722
+ const maxDeclarationChars = this.policy.runtimeDeclarationMaxChars ?? DEFAULT_MAX_DECL_CHARS;
3532
3723
  for (const declaration of this.sourceDeclarations) {
3533
- if (declaration.face !== this.face.face || declaration.kind === "enum" || !/^packages\/[^/]+\/[^/]+\/src\/.+\.tsx?$/.test(declaration.location.file)) continue;
3724
+ if (declaration.face !== this.face.face || !/^packages\/[^/]+\/[^/]+\/src\/.+\.tsx?$/.test(declaration.location.file) && !(declaration.kind === "enum" && /^vendor\/[^/]+\/src\/.+\.ts$/.test(declaration.location.file))) continue;
3534
3725
  if (declarations.has(declaration.name)) {
3535
3726
  ambiguous.add(declaration.name);
3536
3727
  continue;
3537
3728
  }
3538
- declarations.set(declaration.name, declaration.text.length > MAX_DECL_CHARS ? `${declaration.text.slice(0, MAX_DECL_CHARS)} /* …truncated — full shape in source */` : declaration.text);
3729
+ declarations.set(declaration.name, declaration.text.length > maxDeclarationChars ? `${declaration.text.slice(0, maxDeclarationChars)} /* …truncated — full shape in source */` : declaration.text);
3539
3730
  }
3540
3731
  for (const name of ambiguous) declarations.delete(name);
3541
3732
  return referencedTypes([...services.flatMap((service) => service.methods.map((method) => method.signature)), ...events.map((event) => event.signature)], declarations);
@@ -3751,7 +3942,7 @@ function signatureTypeNames(renderer, signature) {
3751
3942
  return [...names].sort();
3752
3943
  }
3753
3944
  /** Declarations longer than this render as a truncated stub. */
3754
- const MAX_DECL_CHARS = 1500;
3945
+ const DEFAULT_MAX_DECL_CHARS = 1500;
3755
3946
  /** Render one value as a single-quoted TypeScript literal. */
3756
3947
  function quote(value) {
3757
3948
  return `'${value.replaceAll("\\", "\\\\").replaceAll("'", "\\'").replaceAll("\n", "\\n")}'`;