@deepseek-ai/dsh-typert-generator 0.2.1-alpha.2 → 0.2.1-alpha.4
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 +16 -10
- package/README.md +30 -3
- package/README.zh.md +30 -3
- package/lib/index.js +693 -101
- package/lib/types/analyzer.d.ts +34 -2
- package/lib/types/analyzer.js +176 -45
- package/lib/types/artifact-files.d.ts +30 -0
- package/lib/types/artifact-files.js +57 -0
- package/lib/types/build-output-guard.d.ts +61 -0
- package/lib/types/build-output-guard.js +193 -0
- package/lib/types/emitter.d.ts +3 -0
- package/lib/types/emitter.js +148 -48
- package/lib/types/index.d.ts +3 -2
- package/lib/types/index.js +2 -1
- package/lib/types/model.d.ts +6 -2
- package/lib/types/tool-schema.d.ts +20 -0
- package/lib/types/tool-schema.js +156 -0
- package/lib/types/tsdown-plugin.d.ts +21 -6
- package/lib/types/tsdown-plugin.js +171 -72
- package/lib/types/worker.d.ts +28 -0
- package/lib/types/worker.js +49 -0
- package/lib/types/workspace.d.ts +34 -2
- package/lib/types/workspace.js +55 -7
- package/package.json +7 -6
package/lib/index.js
CHANGED
|
@@ -1,8 +1,177 @@
|
|
|
1
|
-
import { existsSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname, extname, join, posix, relative, resolve, sep } from "node:path";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, extname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
3
3
|
import ts from "typescript";
|
|
4
4
|
import { Buffer } from "node:buffer";
|
|
5
5
|
import { GenMapping, addMapping, toEncodedMap } from "@jridgewell/gen-mapping";
|
|
6
|
+
//#region lib/types/build-output-guard.js
|
|
7
|
+
/**
|
|
8
|
+
* Detection of workspace build outputs loaded into an analyzed face program,
|
|
9
|
+
* and of workspace imports that resolve to nothing. Typert reads workspace
|
|
10
|
+
* packages only from source when a caller rejects build outputs, so its
|
|
11
|
+
* result cannot depend on whether, or when, package compilation ran: an
|
|
12
|
+
* import that reaches `lib/` on a compiled tree resolves to nothing on a
|
|
13
|
+
* clean one, and both are rejected.
|
|
14
|
+
* @module @deepseek-ai/dsh-typert-generator/build-output-guard
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* List the program files that a workspace package holds under its `lib/`
|
|
18
|
+
* directory. A package is the nearest directory with a `package.json` at or
|
|
19
|
+
* below `root`; files under `node_modules` are external. Importers are found
|
|
20
|
+
* by resolving each program file's import specifiers again, so this pass
|
|
21
|
+
* costs extra only when an output is present.
|
|
22
|
+
* @param program - the analyzed face program.
|
|
23
|
+
* @param root - workspace root.
|
|
24
|
+
* @param host - module resolution host of the program.
|
|
25
|
+
* @returns loads in file-path order; empty when every workspace file comes from source.
|
|
26
|
+
*/
|
|
27
|
+
function findBuildOutputLoads(program, root, host) {
|
|
28
|
+
const classifier = new BuildOutputClassifier(root);
|
|
29
|
+
const outputs = /* @__PURE__ */ new Map();
|
|
30
|
+
for (const sourceFile of program.getSourceFiles()) if (classifier.isBuildOutput(sourceFile.fileName)) outputs.set(classifier.canonical(sourceFile.fileName), []);
|
|
31
|
+
if (outputs.size === 0) return [];
|
|
32
|
+
const options = program.getCompilerOptions();
|
|
33
|
+
for (const sourceFile of program.getSourceFiles()) {
|
|
34
|
+
if (!classifier.isWorkspaceFile(sourceFile.fileName) || classifier.isBuildOutput(sourceFile.fileName)) continue;
|
|
35
|
+
for (const { fileName: specifier } of ts.preProcessFile(sourceFile.text, true, true).importedFiles) {
|
|
36
|
+
const resolved = ts.resolveModuleName(specifier, sourceFile.fileName, options, host).resolvedModule;
|
|
37
|
+
(resolved === void 0 ? void 0 : outputs.get(classifier.canonical(resolved.resolvedFileName)))?.push({
|
|
38
|
+
file: classifier.display(sourceFile.fileName),
|
|
39
|
+
specifier
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return [...outputs].sort(([left], [right]) => left.localeCompare(right)).map(([file, importers]) => ({
|
|
44
|
+
file: classifier.display(file),
|
|
45
|
+
importers,
|
|
46
|
+
source: classifier.sourceFor(file)
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* List the imports in workspace program files that name a workspace package
|
|
51
|
+
* and resolve to nothing. A workspace package is one whose `node_modules` link,
|
|
52
|
+
* found from the importing file's directories, leads inside `root` outside any
|
|
53
|
+
* `node_modules`; without that link or a `paths` alias the package cannot be
|
|
54
|
+
* identified, and type checking reports the import. The source of a subpath is
|
|
55
|
+
* derived from the package's `exports` `types` condition under `lib/types/`.
|
|
56
|
+
* @param program - the analyzed face program.
|
|
57
|
+
* @param root - workspace root.
|
|
58
|
+
* @param host - module resolution host of the program.
|
|
59
|
+
* @returns unresolved imports in file and specifier order.
|
|
60
|
+
*/
|
|
61
|
+
function findUnresolvedWorkspaceImports(program, root, host) {
|
|
62
|
+
const classifier = new BuildOutputClassifier(root);
|
|
63
|
+
const options = program.getCompilerOptions();
|
|
64
|
+
const cache = ts.createModuleResolutionCache(root, (file) => file, options);
|
|
65
|
+
const found = [];
|
|
66
|
+
for (const sourceFile of program.getSourceFiles()) {
|
|
67
|
+
if (!classifier.isWorkspaceFile(sourceFile.fileName) || classifier.isBuildOutput(sourceFile.fileName)) continue;
|
|
68
|
+
for (const { fileName: specifier } of ts.preProcessFile(sourceFile.text, true, true).importedFiles) {
|
|
69
|
+
if (specifier.startsWith(".") || isAbsolute(specifier) || specifier.includes(":")) continue;
|
|
70
|
+
if (ts.resolveModuleName(specifier, sourceFile.fileName, options, host, cache).resolvedModule !== void 0) continue;
|
|
71
|
+
const packageRoot = classifier.linkedWorkspacePackage(sourceFile.fileName, packageNameOf(specifier));
|
|
72
|
+
if (packageRoot === void 0) continue;
|
|
73
|
+
const source = exportSource(packageRoot, specifier);
|
|
74
|
+
found.push({
|
|
75
|
+
file: classifier.display(sourceFile.fileName),
|
|
76
|
+
specifier,
|
|
77
|
+
source: source === void 0 ? void 0 : classifier.display(source)
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return found.sort((left, right) => left.file.localeCompare(right.file) || left.specifier.localeCompare(right.specifier));
|
|
82
|
+
}
|
|
83
|
+
function packageNameOf(specifier) {
|
|
84
|
+
return specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
|
|
85
|
+
}
|
|
86
|
+
function exportSource(packageRoot, specifier) {
|
|
87
|
+
const packageName = packageNameOf(specifier);
|
|
88
|
+
const subpath = specifier === packageName ? "." : `.${specifier.slice(packageName.length)}`;
|
|
89
|
+
let manifest;
|
|
90
|
+
try {
|
|
91
|
+
manifest = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
|
|
92
|
+
} catch (_unreadableManifest) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const entry = property(property(manifest, "exports"), subpath);
|
|
96
|
+
const types = typeof entry === "string" ? entry : property(entry, "types");
|
|
97
|
+
if (typeof types !== "string" || !types.startsWith("./lib/types/")) return void 0;
|
|
98
|
+
const stem = join(packageRoot, "src", types.slice(12).replace(/\.d\.([cm]?)ts$/u, ".$1ts"));
|
|
99
|
+
return [stem, `${stem}x`].find((candidate) => existsSync(candidate));
|
|
100
|
+
}
|
|
101
|
+
function property(value, key) {
|
|
102
|
+
return typeof value === "object" && value !== null ? value[key] : void 0;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Classifies program files by their real paths, because module resolution
|
|
106
|
+
* reports symbolic-link targets and Windows long names while the workspace
|
|
107
|
+
* root may be spelled through a link or an 8.3 short name; reported paths are
|
|
108
|
+
* spelled under the caller's root.
|
|
109
|
+
*/
|
|
110
|
+
var BuildOutputClassifier = class {
|
|
111
|
+
root;
|
|
112
|
+
packageRoots = /* @__PURE__ */ new Map();
|
|
113
|
+
realPaths = /* @__PURE__ */ new Map();
|
|
114
|
+
realRoot;
|
|
115
|
+
constructor(root) {
|
|
116
|
+
this.root = root;
|
|
117
|
+
this.realRoot = this.canonical(root);
|
|
118
|
+
}
|
|
119
|
+
canonical(file) {
|
|
120
|
+
const absolute = resolve(file);
|
|
121
|
+
const cached = this.realPaths.get(absolute);
|
|
122
|
+
if (cached !== void 0) return cached;
|
|
123
|
+
let real = absolute;
|
|
124
|
+
try {
|
|
125
|
+
real = realpathSync.native(absolute);
|
|
126
|
+
} catch (_missingPath) {}
|
|
127
|
+
this.realPaths.set(absolute, real);
|
|
128
|
+
return real;
|
|
129
|
+
}
|
|
130
|
+
display(file) {
|
|
131
|
+
const path = relative(this.realRoot, this.canonical(file));
|
|
132
|
+
return path.startsWith("..") || isAbsolute(path) ? resolve(file) : join(this.root, path);
|
|
133
|
+
}
|
|
134
|
+
isWorkspaceFile(file) {
|
|
135
|
+
const path = relative(this.realRoot, this.canonical(file));
|
|
136
|
+
return path !== "" && !path.startsWith("..") && !isAbsolute(path) && !path.split(sep).includes("node_modules");
|
|
137
|
+
}
|
|
138
|
+
isBuildOutput(file) {
|
|
139
|
+
if (!this.isWorkspaceFile(file)) return false;
|
|
140
|
+
const real = this.canonical(file);
|
|
141
|
+
const packageRoot = this.packageRoot(dirname(real));
|
|
142
|
+
return packageRoot !== void 0 && relative(packageRoot, real).split(sep)[0] === "lib";
|
|
143
|
+
}
|
|
144
|
+
sourceFor(file) {
|
|
145
|
+
const real = this.canonical(file);
|
|
146
|
+
const packageRoot = this.packageRoot(dirname(real));
|
|
147
|
+
if (packageRoot === void 0) return void 0;
|
|
148
|
+
const [lib, types, ...rest] = relative(packageRoot, real).split(sep);
|
|
149
|
+
const last = rest.at(-1);
|
|
150
|
+
if (lib !== "lib" || types !== "types" || last === void 0) return void 0;
|
|
151
|
+
const stem = join(packageRoot, "src", ...rest.slice(0, -1), last.replace(/\.d\.([cm]?)ts$/u, ".$1ts"));
|
|
152
|
+
const source = [stem, `${stem}x`].find((candidate) => existsSync(candidate));
|
|
153
|
+
return source === void 0 ? void 0 : this.display(source);
|
|
154
|
+
}
|
|
155
|
+
linkedWorkspacePackage(importer, packageName) {
|
|
156
|
+
let directory = dirname(resolve(importer));
|
|
157
|
+
for (;;) {
|
|
158
|
+
const link = join(directory, "node_modules", packageName);
|
|
159
|
+
if (existsSync(link)) {
|
|
160
|
+
const target = this.canonical(link);
|
|
161
|
+
return this.isWorkspaceFile(join(target, "package.json")) ? target : void 0;
|
|
162
|
+
}
|
|
163
|
+
if (this.canonical(directory) === this.realRoot || dirname(directory) === directory) return void 0;
|
|
164
|
+
directory = dirname(directory);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
packageRoot(directory) {
|
|
168
|
+
if (this.packageRoots.has(directory)) return this.packageRoots.get(directory);
|
|
169
|
+
const found = existsSync(join(directory, "package.json")) ? directory : directory === this.realRoot || dirname(directory) === directory ? void 0 : this.packageRoot(dirname(directory));
|
|
170
|
+
this.packageRoots.set(directory, found);
|
|
171
|
+
return found;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
//#endregion
|
|
6
175
|
//#region lib/types/analyzer.js
|
|
7
176
|
/**
|
|
8
177
|
* TypeScript project analyzer for the compiler-independent Typert model.
|
|
@@ -47,6 +216,7 @@ var WorkspaceCaches = class {
|
|
|
47
216
|
configs = /* @__PURE__ */ new Map();
|
|
48
217
|
/** Registration inventories keyed by root and aggregate config paths. */
|
|
49
218
|
registrations = /* @__PURE__ */ new Map();
|
|
219
|
+
aggregates = /* @__PURE__ */ new Map();
|
|
50
220
|
hosts = /* @__PURE__ */ new Map();
|
|
51
221
|
/**
|
|
52
222
|
* Parse one tsconfig once per workspace snapshot.
|
|
@@ -62,6 +232,26 @@ var WorkspaceCaches = class {
|
|
|
62
232
|
return parsed;
|
|
63
233
|
}
|
|
64
234
|
/**
|
|
235
|
+
* Read one face aggregate tsconfig once per workspace snapshot. An aggregate
|
|
236
|
+
* that declares `references` is parsed without expanding its `include`
|
|
237
|
+
* patterns, which affect neither its options nor its references.
|
|
238
|
+
* @param path - absolute aggregate config path.
|
|
239
|
+
* @returns the memoized compiler options and project references.
|
|
240
|
+
*/
|
|
241
|
+
aggregate(path) {
|
|
242
|
+
let aggregate = this.aggregates.get(path);
|
|
243
|
+
if (aggregate === void 0) {
|
|
244
|
+
const { parsed } = parseConfig(path, true);
|
|
245
|
+
aggregate = {
|
|
246
|
+
path,
|
|
247
|
+
options: parsed.options,
|
|
248
|
+
projectReferences: parsed.projectReferences ?? []
|
|
249
|
+
};
|
|
250
|
+
this.aggregates.set(path, aggregate);
|
|
251
|
+
}
|
|
252
|
+
return aggregate;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
65
255
|
* Return the shared compiler host for one face. Every program of one face
|
|
66
256
|
* is built from the same aggregate compiler options (the first call wins),
|
|
67
257
|
* so parsed source files, binder state, and module resolutions are safe to
|
|
@@ -121,6 +311,7 @@ var WorkspaceAnalyzer = class WorkspaceAnalyzer {
|
|
|
121
311
|
faces: options.faces ?? ["host", "client"],
|
|
122
312
|
checkDiagnostics: options.checkDiagnostics ?? true,
|
|
123
313
|
mode: options.mode ?? "check",
|
|
314
|
+
rejectBuildOutputs: options.rejectBuildOutputs ?? false,
|
|
124
315
|
...options.packages === void 0 ? {} : { packages: options.packages }
|
|
125
316
|
};
|
|
126
317
|
this.caches = options.caches ?? new WorkspaceCaches();
|
|
@@ -140,10 +331,10 @@ var WorkspaceAnalyzer = class WorkspaceAnalyzer {
|
|
|
140
331
|
if (registrations.length === 0) continue;
|
|
141
332
|
if (this.options.checkDiagnostics) for (const registration of registrations) this.checkProject(registration);
|
|
142
333
|
const aggregatePath = resolve(this.options.root, face === "host" ? this.options.hostConfig : this.options.clientConfig);
|
|
143
|
-
const aggregate = this.caches.
|
|
334
|
+
const aggregate = this.caches.aggregate(aggregatePath);
|
|
144
335
|
const rootNames = [...new Set(registrations.flatMap((registration) => registration.config.parsed.fileNames))];
|
|
145
336
|
const options = {
|
|
146
|
-
...aggregate.
|
|
337
|
+
...aggregate.options,
|
|
147
338
|
composite: false,
|
|
148
339
|
incremental: false,
|
|
149
340
|
noEmit: true
|
|
@@ -154,6 +345,7 @@ var WorkspaceAnalyzer = class WorkspaceAnalyzer {
|
|
|
154
345
|
options,
|
|
155
346
|
host
|
|
156
347
|
});
|
|
348
|
+
if (this.options.rejectBuildOutputs) rejectBuildOutputLoads(face, this.options.root, aggregatePath, findBuildOutputLoads(program, this.options.root, host), findUnresolvedWorkspaceImports(program, this.options.root, host));
|
|
157
349
|
faces.push(new FaceAnalyzer({
|
|
158
350
|
root: this.options.root,
|
|
159
351
|
face,
|
|
@@ -210,10 +402,11 @@ var WorkspaceAnalyzer = class WorkspaceAnalyzer {
|
|
|
210
402
|
/**
|
|
211
403
|
* Discover package faces from public-export-reachable Cordis augmentations
|
|
212
404
|
* and explicit `@typert` roots without constructing a type-checker program.
|
|
405
|
+
* @param includeManifest - optional `package.json` predicate; a rejected package's sources and project config are not read.
|
|
213
406
|
* @returns contributors grouped by package with deterministic face order.
|
|
214
407
|
*/
|
|
215
|
-
discoverPackages() {
|
|
216
|
-
const registrations = this.loadRegistrations().filter((registration) => this.options.faces.includes(registration.face)).filter((registration) => this.registrationHasSurface(registration));
|
|
408
|
+
discoverPackages(includeManifest) {
|
|
409
|
+
const registrations = this.loadRegistrations().filter((registration) => this.options.faces.includes(registration.face) && (includeManifest === void 0 || includeManifest(registration.manifest))).filter((registration) => this.registrationHasSurface(registration));
|
|
217
410
|
const packages = /* @__PURE__ */ new Map();
|
|
218
411
|
for (const registration of registrations) {
|
|
219
412
|
const current = packages.get(registration.name) ?? {
|
|
@@ -272,36 +465,31 @@ var WorkspaceAnalyzer = class WorkspaceAnalyzer {
|
|
|
272
465
|
for (const face of ["host", "client"]) {
|
|
273
466
|
const aggregatePath = resolve(this.options.root, face === "host" ? this.options.hostConfig : this.options.clientConfig);
|
|
274
467
|
if (!existsSync(aggregatePath)) continue;
|
|
275
|
-
const aggregate = this.caches.
|
|
276
|
-
for (const reference of aggregate.
|
|
468
|
+
const aggregate = this.caches.aggregate(aggregatePath);
|
|
469
|
+
for (const reference of aggregate.projectReferences) {
|
|
277
470
|
const configPath = projectConfigPath(reference.path);
|
|
278
471
|
const packageRoot = dirname(configPath);
|
|
279
472
|
if (!isWithin(realPath(packageRoot), join(this.options.root, "packages")) && !(includeVendor && isWithin(realPath(packageRoot), join(this.options.root, "vendor")))) continue;
|
|
280
473
|
const manifestPath = join(packageRoot, "package.json");
|
|
281
474
|
if (!existsSync(manifestPath)) continue;
|
|
282
475
|
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
exportSubpaths:
|
|
296
|
-
}, {
|
|
297
|
-
...registration,
|
|
298
|
-
face: "client",
|
|
299
|
-
exportSubpaths: clientExportSubpaths(manifest)
|
|
300
|
-
});
|
|
301
|
-
else registrations.push({
|
|
302
|
-
...registration,
|
|
303
|
-
exportSubpaths: face === "host" ? hostExportSubpaths(manifest) : clientExportSubpaths(manifest)
|
|
476
|
+
const name = manifest.name;
|
|
477
|
+
if (typeof name !== "string") continue;
|
|
478
|
+
const root = realPath(packageRoot);
|
|
479
|
+
const caches = this.caches;
|
|
480
|
+
const register = (registrationFace, exportSubpaths) => ({
|
|
481
|
+
face: registrationFace,
|
|
482
|
+
name,
|
|
483
|
+
root,
|
|
484
|
+
get config() {
|
|
485
|
+
return caches.config(configPath);
|
|
486
|
+
},
|
|
487
|
+
manifest,
|
|
488
|
+
...exportSubpaths === void 0 ? {} : { exportSubpaths }
|
|
304
489
|
});
|
|
490
|
+
if (!isDualFacePackage(manifest)) registrations.push(register(face));
|
|
491
|
+
else if (configPath === join(packageRoot, "tsconfig.json")) registrations.push(register("host", hostExportSubpaths(manifest)), register("client", clientExportSubpaths(manifest)));
|
|
492
|
+
else registrations.push(register(face, face === "host" ? hostExportSubpaths(manifest) : clientExportSubpaths(manifest)));
|
|
305
493
|
}
|
|
306
494
|
}
|
|
307
495
|
const inventory = uniqueBy(registrations, (registration) => `${registration.face}\0${registration.name}`).sort((left, right) => left.face.localeCompare(right.face) || left.name.localeCompare(right.name));
|
|
@@ -699,6 +887,7 @@ var FaceAnalyzer = class {
|
|
|
699
887
|
if (hasModifier(method, ts.SyntaxKind.AbstractKeyword) || method.body === void 0) this.fail(method, "Remote methods must have a concrete implementation");
|
|
700
888
|
if (!ts.isIdentifier(method.name)) this.fail(method, "Remote method names must be identifiers");
|
|
701
889
|
if ((method.typeParameters?.length ?? 0) > 0) this.fail(method, "generic Remote methods are not supported");
|
|
890
|
+
const documentation = documentationOf(method);
|
|
702
891
|
const methodName = method.name.text;
|
|
703
892
|
const exportedMethod = invocation.exportName ?? methodName;
|
|
704
893
|
const lookups = this.lookupDeclarations();
|
|
@@ -747,7 +936,11 @@ var FaceAnalyzer = class {
|
|
|
747
936
|
}
|
|
748
937
|
if (wires.has(modeled.wire)) this.fail(parameter, `duplicate Remote wire field ${modeled.wire}`);
|
|
749
938
|
wires.add(modeled.wire);
|
|
750
|
-
|
|
939
|
+
const description = documentation.tags.find((tag) => tag.name === "param" && tag.argument === modeled.name)?.comment?.replace(/^[-—–]\s*/, "");
|
|
940
|
+
parameters.push({
|
|
941
|
+
...modeled,
|
|
942
|
+
...description === void 0 ? {} : { description }
|
|
943
|
+
});
|
|
751
944
|
}
|
|
752
945
|
let receiver = { kind: "direct" };
|
|
753
946
|
if (invocation.kind === "context") {
|
|
@@ -780,6 +973,7 @@ var FaceAnalyzer = class {
|
|
|
780
973
|
const { result: resultType, uplink: uplinkType } = this.remoteResultType(method, mode);
|
|
781
974
|
const uplink = uplinkType === void 0 ? void 0 : { boundary: this.remoteBoundary(uplinkType, `${registration.name}#${binding.namespace}/${exportedMethod}:uplink`, false, "undefined") };
|
|
782
975
|
return {
|
|
976
|
+
...documentation,
|
|
783
977
|
id: `${registration.name}#${binding.namespace}/${exportedMethod}`,
|
|
784
978
|
service: binding.service,
|
|
785
979
|
namespace: binding.namespace,
|
|
@@ -866,28 +1060,27 @@ var FaceAnalyzer = class {
|
|
|
866
1060
|
kind: "direct",
|
|
867
1061
|
exportName
|
|
868
1062
|
};
|
|
869
|
-
} else {
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
const mode = ts.isPropertyAssignment(property) && memberName(property.name) === "mode" ? stringLiteralValue(property.initializer) : void 0;
|
|
874
|
-
if (mode !== "stream") this.fail(property, "Remote() options must contain exactly mode: \"stream\"");
|
|
875
|
-
marker = {
|
|
876
|
-
kind: "direct",
|
|
877
|
-
mode
|
|
878
|
-
};
|
|
879
|
-
}
|
|
1063
|
+
} else marker = {
|
|
1064
|
+
kind: "direct",
|
|
1065
|
+
...this.remoteOptions(argument, "Remote")
|
|
1066
|
+
};
|
|
880
1067
|
} else if (ts.isCallExpression(expression) && this.isTypeMetaSymbol(expression.expression, "RemoteScope")) {
|
|
881
|
-
if (expression.arguments.length < 1 || expression.arguments.length > 2) this.fail(expression, "RemoteScope() requires a Context key and optional exported method name");
|
|
1068
|
+
if (expression.arguments.length < 1 || expression.arguments.length > 2) this.fail(expression, "RemoteScope() requires a Context key and an optional exported method name or options object");
|
|
882
1069
|
const context = stringLiteralValue(expression.arguments[0]);
|
|
883
1070
|
if (context === void 0 || !isRemoteSegment(context)) this.fail(expression.arguments[0] ?? expression, "RemoteScope() key must be a string literal containing only RPC endpoint segment characters");
|
|
884
|
-
const
|
|
885
|
-
const exportName =
|
|
886
|
-
if (
|
|
887
|
-
|
|
1071
|
+
const option = expression.arguments[1];
|
|
1072
|
+
const exportName = option === void 0 ? void 0 : stringLiteralValue(option);
|
|
1073
|
+
if (option === void 0 || exportName !== void 0) {
|
|
1074
|
+
if (exportName !== void 0 && !isRemoteSegment(exportName)) this.fail(option ?? expression, "RemoteScope() name must be a string literal containing only RPC endpoint segment characters");
|
|
1075
|
+
marker = {
|
|
1076
|
+
kind: "context",
|
|
1077
|
+
context,
|
|
1078
|
+
...exportName === void 0 ? {} : { exportName }
|
|
1079
|
+
};
|
|
1080
|
+
} else marker = {
|
|
888
1081
|
kind: "context",
|
|
889
1082
|
context,
|
|
890
|
-
...
|
|
1083
|
+
...this.remoteOptions(option, "RemoteScope")
|
|
891
1084
|
};
|
|
892
1085
|
} else continue;
|
|
893
1086
|
if (found !== void 0) this.fail(decorator, "a method can have only one Remote invocation decorator");
|
|
@@ -896,6 +1089,34 @@ var FaceAnalyzer = class {
|
|
|
896
1089
|
return found;
|
|
897
1090
|
}
|
|
898
1091
|
/**
|
|
1092
|
+
* Read the options object of `@Remote` or `@RemoteScope`. `resources`
|
|
1093
|
+
* accepts any expression: its readers run only at runtime, where the
|
|
1094
|
+
* Gateway reads them from the prototype descriptor, so generation records
|
|
1095
|
+
* nothing for them.
|
|
1096
|
+
*/
|
|
1097
|
+
remoteOptions(argument, decorator) {
|
|
1098
|
+
const requirement = decorator === "Remote" ? "Remote() options require name, mode: \"stream\", or resources" : "RemoteScope() options require name or resources";
|
|
1099
|
+
if (!ts.isObjectLiteralExpression(argument) || argument.properties.length === 0) this.fail(argument, requirement);
|
|
1100
|
+
let options = {};
|
|
1101
|
+
for (const property of argument.properties) {
|
|
1102
|
+
if (!ts.isPropertyAssignment(property)) this.fail(property, `${decorator}() options must use named values`);
|
|
1103
|
+
const name = memberName(property.name);
|
|
1104
|
+
if (decorator === "Remote" && name === "mode" && stringLiteralValue(property.initializer) === "stream") options = {
|
|
1105
|
+
...options,
|
|
1106
|
+
mode: "stream"
|
|
1107
|
+
};
|
|
1108
|
+
else if (name === "name") {
|
|
1109
|
+
const value = stringLiteralValue(property.initializer);
|
|
1110
|
+
if (value === void 0 || !isRemoteSegment(value)) this.fail(property, `${decorator}() name must be an endpoint segment`);
|
|
1111
|
+
options = {
|
|
1112
|
+
...options,
|
|
1113
|
+
exportName: value
|
|
1114
|
+
};
|
|
1115
|
+
} else if (name !== "resources") this.fail(property, requirement);
|
|
1116
|
+
}
|
|
1117
|
+
return options;
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
899
1120
|
* The item types a Remote method's authored return type declares. Unary
|
|
900
1121
|
* methods unwrap `Promise<T>`; stream methods unwrap `Iterable<Out>`,
|
|
901
1122
|
* `AsyncIterable<Out>`, or the protocol's `RemoteStream<Out, In>`, whose
|
|
@@ -1064,6 +1285,13 @@ var FaceAnalyzer = class {
|
|
|
1064
1285
|
completed.set(type, id);
|
|
1065
1286
|
return id;
|
|
1066
1287
|
};
|
|
1288
|
+
const alias = type.aliasSymbol;
|
|
1289
|
+
const aliasDeclaration = alias === void 0 ? void 0 : preferredDeclaration(this.resolveSymbol(alias));
|
|
1290
|
+
if (alias?.name === "JsonValue" && aliasDeclaration !== void 0 && this.registrationForFile(aliasDeclaration.getSourceFile().fileName)?.name === "@deepseek-ai/dsh-util-values") return add({
|
|
1291
|
+
kind: "keyword",
|
|
1292
|
+
name: "unknown",
|
|
1293
|
+
json: true
|
|
1294
|
+
});
|
|
1067
1295
|
const flags = type.flags;
|
|
1068
1296
|
if (this.isRemoteByteArray(type)) return add({
|
|
1069
1297
|
kind: "reference",
|
|
@@ -1152,8 +1380,19 @@ var FaceAnalyzer = class {
|
|
|
1152
1380
|
text: String(value)
|
|
1153
1381
|
});
|
|
1154
1382
|
}
|
|
1155
|
-
if (type.
|
|
1156
|
-
|
|
1383
|
+
if (type.isIntersection()) {
|
|
1384
|
+
const material = type.types.filter((member) => !this.isRemotePhantomConstraint(member));
|
|
1385
|
+
if (material.length === 1) {
|
|
1386
|
+
const projected = convert(material[0]);
|
|
1387
|
+
const { id: _projectedId, ...value } = this.nodes.get(projected);
|
|
1388
|
+
return add(value);
|
|
1389
|
+
}
|
|
1390
|
+
if (!material.every((member) => (member.flags & ts.TypeFlags.Object) !== 0 && !this.checker.isArrayType(member) && !this.checker.isTupleType(member))) return add({
|
|
1391
|
+
kind: "intersection",
|
|
1392
|
+
types: material.map(convert)
|
|
1393
|
+
});
|
|
1394
|
+
} else if (type.isUnion()) return add({
|
|
1395
|
+
kind: "union",
|
|
1157
1396
|
types: type.types.map(convert)
|
|
1158
1397
|
});
|
|
1159
1398
|
if ((flags & ts.TypeFlags.TypeParameter) !== 0) this.fail(authoredType, "Remote codec contains an unresolved type parameter");
|
|
@@ -1191,7 +1430,7 @@ var FaceAnalyzer = class {
|
|
|
1191
1430
|
const propertyType = this.checker.getTypeOfSymbolAtLocation(property, declaration ?? authoredType);
|
|
1192
1431
|
const symbolKey = property.getName();
|
|
1193
1432
|
members.push({
|
|
1194
|
-
...EMPTY_DOCUMENTATION,
|
|
1433
|
+
...declaration === void 0 ? EMPTY_DOCUMENTATION : documentationOf(declaration),
|
|
1195
1434
|
id: `${id}#${symbolKey}`,
|
|
1196
1435
|
name: symbolKey,
|
|
1197
1436
|
...symbolKey.startsWith("__@") ? { computed: "symbol" } : {},
|
|
@@ -2031,11 +2270,20 @@ function mergeWorkspaceModels(models) {
|
|
|
2031
2270
|
crossFaceLinks: [...links.values()].sort(compareCrossFaceLinks)
|
|
2032
2271
|
};
|
|
2033
2272
|
}
|
|
2034
|
-
|
|
2273
|
+
/** {@link ts.sys} config reads without directory listing, so `include` patterns match no files. */
|
|
2274
|
+
const UNLISTED_CONFIG_HOST = {
|
|
2275
|
+
useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
|
|
2276
|
+
readDirectory: () => [],
|
|
2277
|
+
fileExists: (file) => ts.sys.fileExists(file),
|
|
2278
|
+
readFile: (file) => ts.sys.readFile(file)
|
|
2279
|
+
};
|
|
2280
|
+
function parseConfig(path, aggregate = false) {
|
|
2035
2281
|
const compilerPath = path.split(sep).join("/");
|
|
2036
2282
|
const read = ts.readConfigFile(compilerPath, (file) => ts.sys.readFile(file));
|
|
2037
2283
|
if (read.error !== void 0) throw new TypertAnalysisError(formatDiagnostic(read.error));
|
|
2038
|
-
const
|
|
2284
|
+
const raw = read.config;
|
|
2285
|
+
const host = aggregate && typeof raw === "object" && raw !== null && Object.hasOwn(raw, "references") ? UNLISTED_CONFIG_HOST : ts.sys;
|
|
2286
|
+
const parsed = ts.parseJsonConfigFileContent(read.config, host, dirname(compilerPath), void 0, compilerPath);
|
|
2039
2287
|
if (parsed.errors.length > 0) throw new TypertAnalysisError(parsed.errors.map(formatDiagnostic).join("\n"));
|
|
2040
2288
|
return {
|
|
2041
2289
|
path,
|
|
@@ -2417,6 +2665,33 @@ function isWithin(path, root) {
|
|
|
2417
2665
|
function slash(value) {
|
|
2418
2666
|
return value.replaceAll("\\", "/");
|
|
2419
2667
|
}
|
|
2668
|
+
function rejectBuildOutputLoads(face, root, aggregatePath, loads, unresolved) {
|
|
2669
|
+
if (loads.length === 0 && unresolved.length === 0) return;
|
|
2670
|
+
const path = (file) => slash(relative(root, file));
|
|
2671
|
+
const config = path(aggregatePath);
|
|
2672
|
+
const lines = [];
|
|
2673
|
+
const addAlias = (specifier, source) => {
|
|
2674
|
+
lines.push(` add ${JSON.stringify(specifier)}: [${JSON.stringify(`./${path(source)}`)}] to the compilerOptions.paths of ${config} or the config it extends`);
|
|
2675
|
+
};
|
|
2676
|
+
if (unresolved.length > 0) {
|
|
2677
|
+
lines.push(`typert(${face}): the ${face} program cannot resolve workspace imports, which would analyze as unknown types; map each import below to source:`);
|
|
2678
|
+
for (const entry of unresolved) {
|
|
2679
|
+
lines.push(` '${entry.specifier}' imported by ${path(entry.file)}`);
|
|
2680
|
+
if (entry.source === void 0) lines.push(` add a compilerOptions.paths alias for it to ${config} or the config it extends`);
|
|
2681
|
+
else addAlias(entry.specifier, entry.source);
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
if (loads.length > 0) lines.push(`typert(${face}): the ${face} program loads workspace build outputs instead of source; resolve each import below to source so that generation does not depend on package compilation:`);
|
|
2685
|
+
for (const load of loads) {
|
|
2686
|
+
lines.push(` ${path(load.file)}`);
|
|
2687
|
+
if (load.importers.length === 0) lines.push(" no program source imports it directly; another build output or a reference directive loads it");
|
|
2688
|
+
for (const importer of load.importers) lines.push(` imported as '${importer.specifier}' by ${path(importer.file)}`);
|
|
2689
|
+
const specifiers = [...new Set(load.importers.map((importer) => importer.specifier))];
|
|
2690
|
+
if (load.source === void 0) lines.push(" no workspace source compiles to it; import a source module instead");
|
|
2691
|
+
else for (const specifier of specifiers) addAlias(specifier, load.source);
|
|
2692
|
+
}
|
|
2693
|
+
throw new TypertAnalysisError(lines.join("\n"));
|
|
2694
|
+
}
|
|
2420
2695
|
function uniqueBy(values, key) {
|
|
2421
2696
|
const result = /* @__PURE__ */ new Map();
|
|
2422
2697
|
for (const value of values) if (!result.has(key(value))) result.set(key(value), value);
|
|
@@ -2772,6 +3047,148 @@ function assertNever(value) {
|
|
|
2772
3047
|
throw new TypeGraphRenderError(`unsupported model variant ${JSON.stringify(value)}`);
|
|
2773
3048
|
}
|
|
2774
3049
|
//#endregion
|
|
3050
|
+
//#region lib/types/tool-schema.js
|
|
3051
|
+
/** Lossless projection into the harness tool-schema subset. */
|
|
3052
|
+
/** A resolved codec whose type the tool-schema subset cannot represent without weakening constraints. */
|
|
3053
|
+
var ToolSchemaError = class extends Error {
|
|
3054
|
+
name = "ToolSchemaError";
|
|
3055
|
+
};
|
|
3056
|
+
/**
|
|
3057
|
+
* Project one resolved type into the tool-schema subset that `@deepseek-ai/dsh-tools`
|
|
3058
|
+
* enforces on tool parameters, tool output, and the generated PTC SDK. A type the
|
|
3059
|
+
* subset cannot express without weakening constraints raises {@link ToolSchemaError},
|
|
3060
|
+
* which callers report as an absent projection rather than a narrower schema.
|
|
3061
|
+
* @param renderer - complete analyzed type graph.
|
|
3062
|
+
* @param type - resolved wire type.
|
|
3063
|
+
* @param closedObjects - close declared request objects with `additionalProperties: false`.
|
|
3064
|
+
* @param optional - omit a top-level undefined alternative supplied by an optional argument.
|
|
3065
|
+
* @returns an acyclic schema in the tool-schema subset.
|
|
3066
|
+
*/
|
|
3067
|
+
function projectToolSchema(renderer, type, closedObjects = false, optional = false) {
|
|
3068
|
+
const active = /* @__PURE__ */ new Set();
|
|
3069
|
+
const fail = (subject) => {
|
|
3070
|
+
throw new ToolSchemaError(`typert tool schema: ${subject}`);
|
|
3071
|
+
};
|
|
3072
|
+
const project = (id, optional = false) => {
|
|
3073
|
+
const node = renderer.node(id);
|
|
3074
|
+
if (node.json) return {};
|
|
3075
|
+
if (active.has(id)) return fail("recursive selected type requires a supported JSON value declaration");
|
|
3076
|
+
active.add(id);
|
|
3077
|
+
try {
|
|
3078
|
+
switch (node.kind) {
|
|
3079
|
+
case "keyword":
|
|
3080
|
+
if ([
|
|
3081
|
+
"string",
|
|
3082
|
+
"number",
|
|
3083
|
+
"boolean"
|
|
3084
|
+
].includes(node.name)) return { type: node.name };
|
|
3085
|
+
return fail(`unsupported ${node.name} value`);
|
|
3086
|
+
case "literal":
|
|
3087
|
+
if (typeof node.value === "bigint") return fail("bigint is not JSON");
|
|
3088
|
+
return {
|
|
3089
|
+
type: node.value === null ? "null" : typeof node.value,
|
|
3090
|
+
const: node.value
|
|
3091
|
+
};
|
|
3092
|
+
case "parenthesized": return project(node.type, optional);
|
|
3093
|
+
case "array": return {
|
|
3094
|
+
type: "array",
|
|
3095
|
+
items: project(node.element)
|
|
3096
|
+
};
|
|
3097
|
+
case "object": return object(node.members);
|
|
3098
|
+
case "reference": {
|
|
3099
|
+
if (node.target.kind !== "declaration" || node.arguments.length !== 0) return fail(`unsupported selected reference ${node.name}`);
|
|
3100
|
+
const declaration = renderer.declaration(node.target.symbol);
|
|
3101
|
+
const schema = declaration.kind === "alias" && declaration.type !== void 0 ? project(declaration.type, optional) : object(declaration.members);
|
|
3102
|
+
if (declaration.extends.length > 0) return fail("unresolved inherited selected type");
|
|
3103
|
+
return declaration.description === void 0 ? schema : {
|
|
3104
|
+
...schema,
|
|
3105
|
+
description: declaration.description
|
|
3106
|
+
};
|
|
3107
|
+
}
|
|
3108
|
+
case "union": return union(node.types.filter((item) => {
|
|
3109
|
+
const branch = renderer.node(item);
|
|
3110
|
+
return !(optional && branch.kind === "keyword" && branch.name === "undefined");
|
|
3111
|
+
}).map((item) => project(item)));
|
|
3112
|
+
default: return fail(`unsupported selected ${node.kind} type`);
|
|
3113
|
+
}
|
|
3114
|
+
} finally {
|
|
3115
|
+
active.delete(id);
|
|
3116
|
+
}
|
|
3117
|
+
};
|
|
3118
|
+
const object = (members) => {
|
|
3119
|
+
const properties = {};
|
|
3120
|
+
const required = [];
|
|
3121
|
+
let indexed = false;
|
|
3122
|
+
for (const member of members) {
|
|
3123
|
+
if (member.static || member.visibility !== "public" || member.computed === "symbol") continue;
|
|
3124
|
+
if (member.kind === "index") {
|
|
3125
|
+
const key = member.signature.parameters[0];
|
|
3126
|
+
if (key === void 0 || project(key.type).type !== "string" || Object.keys(project(member.signature.returns)).length !== 0) return fail("selected dictionaries require string keys and unconstrained JSON values");
|
|
3127
|
+
indexed = true;
|
|
3128
|
+
continue;
|
|
3129
|
+
}
|
|
3130
|
+
if (member.kind !== "property" || member.computed === "dynamic") return fail(`unsupported member ${member.name}`);
|
|
3131
|
+
const name = member.jsonName ?? member.name;
|
|
3132
|
+
const schema = project(member.type, member.optional);
|
|
3133
|
+
properties[name] = member.description === void 0 ? schema : {
|
|
3134
|
+
...schema,
|
|
3135
|
+
description: member.description
|
|
3136
|
+
};
|
|
3137
|
+
if (!member.optional) required.push(name);
|
|
3138
|
+
}
|
|
3139
|
+
return {
|
|
3140
|
+
type: "object",
|
|
3141
|
+
properties,
|
|
3142
|
+
required,
|
|
3143
|
+
additionalProperties: indexed || !closedObjects
|
|
3144
|
+
};
|
|
3145
|
+
};
|
|
3146
|
+
const union = (input) => {
|
|
3147
|
+
if (input.some((branch) => Object.keys(branch).length === 0)) return {};
|
|
3148
|
+
const branches = [];
|
|
3149
|
+
for (const branch of input) {
|
|
3150
|
+
const previous = branch.type !== void 0 && !["object", "array"].includes(branch.type) ? branches.find((candidate) => candidate.type === branch.type) : void 0;
|
|
3151
|
+
if (previous === void 0) {
|
|
3152
|
+
branches.push(branch);
|
|
3153
|
+
continue;
|
|
3154
|
+
}
|
|
3155
|
+
const values = literals(previous);
|
|
3156
|
+
const next = literals(branch);
|
|
3157
|
+
if (values === void 0 || next === void 0) {
|
|
3158
|
+
delete previous.const;
|
|
3159
|
+
delete previous.enum;
|
|
3160
|
+
} else {
|
|
3161
|
+
delete previous.const;
|
|
3162
|
+
previous.enum = [...new Set([...values, ...next])];
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
if (branches.length === 1) return branches[0];
|
|
3166
|
+
if (branches.length === 0) return fail("empty selected union");
|
|
3167
|
+
for (const [index, left] of branches.entries()) for (const right of branches.slice(index + 1)) if (!disjoint(left, right)) return fail("selected union branches overlap or have no required discriminator");
|
|
3168
|
+
return { oneOf: branches };
|
|
3169
|
+
};
|
|
3170
|
+
return project(type, optional);
|
|
3171
|
+
}
|
|
3172
|
+
function literals(schema) {
|
|
3173
|
+
if (schema.const !== void 0) return [schema.const];
|
|
3174
|
+
return schema.enum;
|
|
3175
|
+
}
|
|
3176
|
+
function disjoint(left, right) {
|
|
3177
|
+
if (left.oneOf !== void 0) return left.oneOf.every((branch) => disjoint(branch, right));
|
|
3178
|
+
if (right.oneOf !== void 0) return right.oneOf.every((branch) => disjoint(left, branch));
|
|
3179
|
+
if (left.type !== right.type) return left.type !== void 0 && right.type !== void 0;
|
|
3180
|
+
if (left.type !== "object" || right.type !== "object") return false;
|
|
3181
|
+
return (left.required ?? []).some((name) => {
|
|
3182
|
+
if (!right.required?.includes(name)) return false;
|
|
3183
|
+
const a = left.properties?.[name];
|
|
3184
|
+
const b = right.properties?.[name];
|
|
3185
|
+
if (a === void 0 || b === void 0) return false;
|
|
3186
|
+
const first = literals(a);
|
|
3187
|
+
const second = literals(b);
|
|
3188
|
+
return first !== void 0 && second !== void 0 && first.every((value) => !second.includes(value));
|
|
3189
|
+
});
|
|
3190
|
+
}
|
|
3191
|
+
//#endregion
|
|
2775
3192
|
//#region lib/types/emitter.js
|
|
2776
3193
|
/**
|
|
2777
3194
|
* Model-driven Typert artifact emitter. It consumes only FaceModel and
|
|
@@ -2802,7 +3219,7 @@ var FaceModelEmitter = class {
|
|
|
2802
3219
|
emit(packageName) {
|
|
2803
3220
|
const packageModel = this.face.packages.find((candidate) => candidate.name === packageName);
|
|
2804
3221
|
if (packageModel === void 0) throw new TypertEmitError(`typert emitter(${this.face.face}): package ${packageName} is not modeled on this face`);
|
|
2805
|
-
const schemaArtifact =
|
|
3222
|
+
const schemaArtifact = invocationSchemas(this.renderer, packageModel.schemas, invocationBoundaryRoots(packageModel.invocations));
|
|
2806
3223
|
const runtimeModel = this.runtimeModel(packageModel);
|
|
2807
3224
|
const js = this.renderJs(packageModel, schemaArtifact, runtimeModel);
|
|
2808
3225
|
const dts = this.renderDts(packageModel, schemaArtifact);
|
|
@@ -2867,10 +3284,12 @@ var FaceModelEmitter = class {
|
|
|
2867
3284
|
renderJs(packageModel, schemas, runtimeModel) {
|
|
2868
3285
|
const lines = ["/* Generated by @deepseek-ai/dsh-typert-generator from FaceModel — do not edit. */"];
|
|
2869
3286
|
if (schemas.definitions.length > 0) lines.push("import { z } from 'zod'", "");
|
|
3287
|
+
if (packageModel.invocations.length > 0) lines.push("import { createTypertRemoteInfo } from '@deepseek-ai/dsh-typert-protocol/remote-info'", "");
|
|
2870
3288
|
lines.push(...schemas.definitions);
|
|
2871
3289
|
if (schemas.definitions.length > 0) lines.push("");
|
|
2872
3290
|
for (const schema of schemas.exports) lines.push(`export const ${schema.exportName} = ${schema.internalName}`);
|
|
2873
3291
|
if (schemas.exports.length > 0) lines.push("");
|
|
3292
|
+
if (packageModel.invocations.length > 0) lines.push(`const $remote = ${this.remoteInfoLiteral(packageModel, schemas)}`, "");
|
|
2874
3293
|
const model = JSON.stringify(runtimeModel, null, 2);
|
|
2875
3294
|
lines.push("export const TYPERT = {");
|
|
2876
3295
|
lines.push(` package: ${quote$1(packageModel.name)},`);
|
|
@@ -2878,9 +3297,8 @@ var FaceModelEmitter = class {
|
|
|
2878
3297
|
lines.push(" schemas: [");
|
|
2879
3298
|
for (const schema of schemas.exports) lines.push(` { name: ${quote$1(schema.exportName)}, create: ${schema.exportName} },`);
|
|
2880
3299
|
lines.push(" ],");
|
|
2881
|
-
lines.push(" invocations
|
|
2882
|
-
|
|
2883
|
-
lines.push(" ],");
|
|
3300
|
+
if (packageModel.invocations.length > 0) lines.push(" get invocations() { return $remote.descriptors },");
|
|
3301
|
+
else lines.push(" invocations: [", " ],");
|
|
2884
3302
|
lines.push(` model: ${indent(model, 2).trimStart()},`);
|
|
2885
3303
|
lines.push("}");
|
|
2886
3304
|
return `${lines.join("\n")}\n`;
|
|
@@ -2903,32 +3321,47 @@ var FaceModelEmitter = class {
|
|
|
2903
3321
|
return `${lines.join("\n")}\n`;
|
|
2904
3322
|
}
|
|
2905
3323
|
emitRemote(packageModel) {
|
|
2906
|
-
const schemas =
|
|
3324
|
+
const schemas = invocationSchemas(this.renderer, [], invocationBoundaryRoots(packageModel.invocations));
|
|
2907
3325
|
const lines = ["/* Generated by @deepseek-ai/dsh-typert-generator from the Host FaceModel — do not edit. */"];
|
|
2908
3326
|
if (schemas.definitions.length > 0) lines.push("import { z } from 'zod'", "");
|
|
3327
|
+
lines.push("import { createTypertRemoteInfo } from '@deepseek-ai/dsh-typert-protocol/remote-info'", "");
|
|
2909
3328
|
lines.push(...schemas.definitions);
|
|
2910
3329
|
if (schemas.definitions.length > 0) lines.push("");
|
|
2911
|
-
lines.push(
|
|
2912
|
-
lines.push(` package: ${quote$1(packageModel.name)},`);
|
|
2913
|
-
lines.push(" descriptors: [");
|
|
2914
|
-
for (const invocation of packageModel.invocations) lines.push(`${indent(this.invocationLiteral(invocation, schemas), 4)},`);
|
|
2915
|
-
lines.push(" ],");
|
|
2916
|
-
lines.push("}");
|
|
2917
|
-
lines.push("");
|
|
3330
|
+
lines.push(`export const TYPERT_REMOTE = ${this.remoteInfoLiteral(packageModel, schemas)}`, "");
|
|
2918
3331
|
lines.push("export default TYPERT_REMOTE");
|
|
2919
|
-
const declaration = this.renderRemoteDts(packageModel);
|
|
2920
3332
|
return {
|
|
2921
3333
|
js: `${lines.join("\n")}\n`,
|
|
2922
|
-
...
|
|
3334
|
+
...this.renderRemoteDts(packageModel)
|
|
2923
3335
|
};
|
|
2924
3336
|
}
|
|
3337
|
+
remoteMethodTypes(packageModel) {
|
|
3338
|
+
const names = allocateRemoteImportNames(remoteImports(packageModel.invocations));
|
|
3339
|
+
return `{\n${packageModel.invocations.filter((invocation) => invocation.invocation.kind === "direct").map((invocation) => {
|
|
3340
|
+
const input = this.remoteInputType(invocation, names);
|
|
3341
|
+
const result = this.renderer.renderType(invocation.result.type, names);
|
|
3342
|
+
const uplink = invocation.uplink === void 0 ? "" : `, ${this.renderer.renderType(invocation.uplink.boundary.type, names)}`;
|
|
3343
|
+
const mode = invocation.mode === "stream" ? `, ${quote$1("stream")}${uplink}` : containsBytes(this.renderer, invocation.result.codecType) ? `, 'unary', never, RemoteDecoded<${result}>` : "";
|
|
3344
|
+
return ` readonly ${quote$1(`${invocation.namespace}/${invocation.method}`)}: TypertRemoteMethod<${input}, ${result}${mode}>`;
|
|
3345
|
+
}).join("\n")}\n}`;
|
|
3346
|
+
}
|
|
3347
|
+
remoteInputType(invocation, names) {
|
|
3348
|
+
return `{ ${invocation.parameters.map((parameter) => `readonly ${quote$1(parameter.wire)}${parameter.boundary.acceptsUndefined ? "?" : ""}: ${this.renderer.renderType(parameter.boundary.type, names)}`).join("; ")} }`;
|
|
3349
|
+
}
|
|
3350
|
+
remoteInfoLiteral(packageModel, schemas) {
|
|
3351
|
+
return [
|
|
3352
|
+
"createTypertRemoteInfo({",
|
|
3353
|
+
` package: ${quote$1(packageModel.name)},`,
|
|
3354
|
+
" methods: {",
|
|
3355
|
+
...packageModel.invocations.map((invocation) => ` ${quote$1(`${invocation.namespace}/${invocation.method}`)}: ${indent(this.invocationLiteral(invocation, schemas), 4).trimStart()},`),
|
|
3356
|
+
" },",
|
|
3357
|
+
"}, z)"
|
|
3358
|
+
].join("\n");
|
|
3359
|
+
}
|
|
2925
3360
|
invocationLiteral(invocation, schemas) {
|
|
2926
3361
|
const lines = [
|
|
2927
3362
|
"{",
|
|
2928
|
-
` id: ${quote$1(invocation.id)},`,
|
|
2929
3363
|
` service: ${quote$1(invocation.service)},`,
|
|
2930
|
-
`
|
|
2931
|
-
` method: ${quote$1(invocation.method)},`
|
|
3364
|
+
...invocation.description === void 0 ? [] : [` description: ${quote$1(invocation.description)},`]
|
|
2932
3365
|
];
|
|
2933
3366
|
if (invocation.implementation !== void 0) lines.push(` implementation: ${quote$1(invocation.implementation)},`);
|
|
2934
3367
|
if (invocation.mode !== void 0) lines.push(` mode: ${quote$1(invocation.mode)},`);
|
|
@@ -2951,11 +3384,12 @@ var FaceModelEmitter = class {
|
|
|
2951
3384
|
invocation.parameters.forEach((parameter, index) => {
|
|
2952
3385
|
lines.push(" {");
|
|
2953
3386
|
lines.push(` name: ${quote$1(parameter.name)},`);
|
|
2954
|
-
lines.push(` wire: ${quote$1(parameter.wire)},`);
|
|
3387
|
+
if (parameter.wire !== parameter.name) lines.push(` wire: ${quote$1(parameter.wire)},`);
|
|
3388
|
+
if (parameter.description !== void 0) lines.push(` description: ${quote$1(parameter.description)},`);
|
|
2955
3389
|
lines.push(` source: ${quote$1(parameter.source)},`);
|
|
2956
3390
|
if (parameter.lookup !== void 0) lines.push(` lookup: ${quote$1(parameter.lookup)},`);
|
|
2957
3391
|
if (parameter.boundary.acceptsUndefined) lines.push(" acceptsUndefined: true,");
|
|
2958
|
-
lines.push(` codec: ${indent(strictCodec(parameter.boundary, schemas.boundary(parameterBoundaryKey(invocation, index))), 6).trimStart()},`);
|
|
3392
|
+
for (const strict of [false, true]) lines.push(` ${strict ? "strictCodec" : "codec"}: ${indent(strictCodec(parameter.boundary, schemas.boundary(parameterBoundaryKey(invocation, index, strict)), void 0, toolProjection(() => projectToolSchema(this.renderer, parameter.boundary.codecType, strict, parameter.boundary.acceptsUndefined))), 6).trimStart()},`);
|
|
2959
3393
|
lines.push(" },");
|
|
2960
3394
|
});
|
|
2961
3395
|
lines.push(" ],");
|
|
@@ -2965,7 +3399,8 @@ var FaceModelEmitter = class {
|
|
|
2965
3399
|
lines.push(" },");
|
|
2966
3400
|
}
|
|
2967
3401
|
if (invocation.cancellation !== void 0) lines.push(" cancellation: { parameter: 'signal' },");
|
|
2968
|
-
|
|
3402
|
+
const returns = invocation.tags.find((tag) => tag.name === "returns" || tag.name === "return")?.comment?.replace(/^[-—–]\s*/, "");
|
|
3403
|
+
lines.push(` result: ${indent(strictCodec(invocation.result, schemas.boundary(resultBoundaryKey(invocation)), schemas.encoder(resultBoundaryKey(invocation)), toolProjection(() => projectToolSchema(this.renderer, invocation.result.codecType)), returns), 2).trimStart()},`);
|
|
2969
3404
|
lines.push(` sourceLocation: ${JSON.stringify(invocation.location)},`);
|
|
2970
3405
|
lines.push("}");
|
|
2971
3406
|
return lines.join("\n");
|
|
@@ -2985,9 +3420,11 @@ var FaceModelEmitter = class {
|
|
|
2985
3420
|
const lines = [
|
|
2986
3421
|
"/* Generated by @deepseek-ai/dsh-typert-generator from the Host FaceModel — do not edit. */",
|
|
2987
3422
|
"import type {",
|
|
3423
|
+
...packageModel.invocations.some((invocation) => containsBytes(this.renderer, invocation.result.codecType)) ? [" RemoteDecoded,"] : [],
|
|
2988
3424
|
" RemoteResult,",
|
|
2989
3425
|
...packageModel.invocations.some((invocation) => invocation.mode === "stream") ? [" RemoteStreamHandle,"] : [],
|
|
2990
|
-
"
|
|
3426
|
+
" TypertRemoteInfo,",
|
|
3427
|
+
...packageModel.invocations.some((invocation) => invocation.invocation.kind === "direct") ? [" TypertRemoteMethod,"] : [],
|
|
2991
3428
|
"} from '@deepseek-ai/dsh-typert-protocol'"
|
|
2992
3429
|
];
|
|
2993
3430
|
const sourceMap = new GenMapping({ file: "typert.remote-client.d.ts" });
|
|
@@ -2996,12 +3433,6 @@ var FaceModelEmitter = class {
|
|
|
2996
3433
|
lines.push(`import type { ${names.join(", ")} } from ${quote$1(specifier)}`);
|
|
2997
3434
|
}
|
|
2998
3435
|
lines.push("");
|
|
2999
|
-
if (packageModel.invocations.some((invocation) => containsBytes(this.renderer, invocation.result.codecType))) {
|
|
3000
|
-
lines.push("type RemoteDecoded<Value> = Value extends Uint8Array ? Uint8Array<ArrayBuffer>");
|
|
3001
|
-
lines.push(" : Value extends string | number | boolean | null | undefined ? Value");
|
|
3002
|
-
lines.push(" : { [Key in keyof Value]: RemoteDecoded<Value[Key]> }");
|
|
3003
|
-
lines.push("");
|
|
3004
|
-
}
|
|
3005
3436
|
lines.push("declare module '@deepseek-ai/dsh-typert-protocol' {");
|
|
3006
3437
|
const direct = packageModel.invocations.filter((invocation) => invocation.invocation.kind === "direct");
|
|
3007
3438
|
const scoped = packageModel.invocations.filter((invocation) => invocation.invocation.kind === "context" || invocation.scope !== void 0);
|
|
@@ -3025,7 +3456,7 @@ var FaceModelEmitter = class {
|
|
|
3025
3456
|
}
|
|
3026
3457
|
lines.push("}");
|
|
3027
3458
|
lines.push("");
|
|
3028
|
-
lines.push(
|
|
3459
|
+
lines.push(`export declare const TYPERT_REMOTE: TypertRemoteInfo<${this.remoteMethodTypes(packageModel)}>`);
|
|
3029
3460
|
lines.push("export default TYPERT_REMOTE");
|
|
3030
3461
|
lines.push("//# sourceMappingURL=typert.remote-client.d.ts.map");
|
|
3031
3462
|
return {
|
|
@@ -3092,6 +3523,27 @@ function uniqueNamespaces(invocations) {
|
|
|
3092
3523
|
function remoteNamespaceInterface(namespace) {
|
|
3093
3524
|
return `TypertRemoteNamespace$${Buffer.from(namespace, "utf8").toString("hex")}`;
|
|
3094
3525
|
}
|
|
3526
|
+
function toolProjection(project) {
|
|
3527
|
+
try {
|
|
3528
|
+
return `jsonSchema: ${JSON.stringify(project())}`;
|
|
3529
|
+
} catch (error) {
|
|
3530
|
+
if (!(error instanceof ToolSchemaError)) throw error;
|
|
3531
|
+
return `jsonSchemaError: ${quote$1(error.message)}`;
|
|
3532
|
+
}
|
|
3533
|
+
}
|
|
3534
|
+
function invocationSchemas(renderer, schemas, roots) {
|
|
3535
|
+
const identifiers = /* @__PURE__ */ new Set();
|
|
3536
|
+
const ordinary = new SchemaEmitter(renderer, schemas, roots.filter((root) => !root.closedObjects), {}, identifiers).emit();
|
|
3537
|
+
const closedRoots = roots.filter((root) => root.closedObjects);
|
|
3538
|
+
const closed = new SchemaEmitter(renderer, [], closedRoots, { closedObjects: true }, identifiers).emit();
|
|
3539
|
+
const keys = new Set(closedRoots.map((root) => root.key));
|
|
3540
|
+
return {
|
|
3541
|
+
definitions: [...ordinary.definitions, ...closed.definitions],
|
|
3542
|
+
exports: ordinary.exports,
|
|
3543
|
+
boundary: (key) => keys.has(key) ? closed.boundary(key) : ordinary.boundary(key),
|
|
3544
|
+
encoder: (key) => keys.has(key) ? closed.encoder(key) : ordinary.encoder(key)
|
|
3545
|
+
};
|
|
3546
|
+
}
|
|
3095
3547
|
var ResultEncoderEmitter = class {
|
|
3096
3548
|
renderer;
|
|
3097
3549
|
names = /* @__PURE__ */ new Map();
|
|
@@ -3190,20 +3642,21 @@ var SchemaEmitter = class {
|
|
|
3190
3642
|
renderer;
|
|
3191
3643
|
schemas;
|
|
3192
3644
|
boundaries;
|
|
3645
|
+
options;
|
|
3193
3646
|
names = /* @__PURE__ */ new Map();
|
|
3194
3647
|
boundaryNames = /* @__PURE__ */ new Map();
|
|
3195
3648
|
declarations;
|
|
3196
|
-
constructor(renderer, schemas, boundaries) {
|
|
3649
|
+
constructor(renderer, schemas, boundaries, options = {}, identifiers = /* @__PURE__ */ new Set()) {
|
|
3197
3650
|
this.renderer = renderer;
|
|
3198
3651
|
this.schemas = schemas;
|
|
3199
3652
|
this.boundaries = boundaries;
|
|
3653
|
+
this.options = options;
|
|
3200
3654
|
const declarations = /* @__PURE__ */ new Map();
|
|
3201
3655
|
for (const schema of schemas) for (const declaration of renderer.declarationClosureForTypes([schema.type])) declarations.set(declaration.id, declaration);
|
|
3202
3656
|
for (const boundary of boundaries) for (const declaration of renderer.declarationClosureForTypes([boundary.type])) declarations.set(declaration.id, declaration);
|
|
3203
3657
|
this.declarations = renderer.graph.declarations.filter((declaration) => declarations.has(declaration.id));
|
|
3204
|
-
const identifiers = /* @__PURE__ */ new Set();
|
|
3205
3658
|
for (const declaration of this.declarations) {
|
|
3206
|
-
const base = `${safeIdentifier(declaration.name)}$schema`;
|
|
3659
|
+
const base = `${options.closedObjects ? "closed$" : ""}${safeIdentifier(declaration.name)}$schema`;
|
|
3207
3660
|
let name = base;
|
|
3208
3661
|
let suffix = 2;
|
|
3209
3662
|
while (identifiers.has(name)) name = `${base}${String(suffix++)}`;
|
|
@@ -3227,7 +3680,7 @@ var SchemaEmitter = class {
|
|
|
3227
3680
|
const encode = encoder.reference(boundary.type);
|
|
3228
3681
|
if (encode !== void 0) encoders.set(boundary.key, encode);
|
|
3229
3682
|
const name = this.boundaryName(boundary.key);
|
|
3230
|
-
definitions.push(
|
|
3683
|
+
definitions.push(this.cachedFactory(name, this.typeSchema(boundary.type)));
|
|
3231
3684
|
}
|
|
3232
3685
|
definitions.push(...encoder.definitions());
|
|
3233
3686
|
return {
|
|
@@ -3243,11 +3696,14 @@ var SchemaEmitter = class {
|
|
|
3243
3696
|
}
|
|
3244
3697
|
declarationDefinition(declaration) {
|
|
3245
3698
|
const name = this.schemaName(declaration.id);
|
|
3246
|
-
if (declaration.typeParameters.length === 0) return
|
|
3699
|
+
if (declaration.typeParameters.length === 0) return this.cachedFactory(name, this.declarationSchema(declaration, /* @__PURE__ */ new Map()));
|
|
3247
3700
|
const parameters = declaration.typeParameters.map((parameter, index) => [`type${String(index)}$schema`, parameter.id]);
|
|
3248
3701
|
const substitutions = new Map(parameters.map(([schema, id]) => [id, schema]));
|
|
3249
3702
|
return `const ${name} = (${parameters.map(([schema]) => schema).join(", ")}) => ${this.declarationSchema(declaration, substitutions)}`;
|
|
3250
3703
|
}
|
|
3704
|
+
cachedFactory(name, expression) {
|
|
3705
|
+
return `let ${name}$value\nconst ${name} = () => (${name}$value ??= ${expression})`;
|
|
3706
|
+
}
|
|
3251
3707
|
declarationSchema(declaration, substitutions) {
|
|
3252
3708
|
if (declaration.kind === "enum") this.fail(declaration.name, "enum declarations have no Zod projection");
|
|
3253
3709
|
if (declaration.kind === "alias") {
|
|
@@ -3260,6 +3716,7 @@ var SchemaEmitter = class {
|
|
|
3260
3716
|
}
|
|
3261
3717
|
typeSchema(id, substitutions = /* @__PURE__ */ new Map()) {
|
|
3262
3718
|
const node = this.renderer.node(id);
|
|
3719
|
+
if (node.json) return "z.json()";
|
|
3263
3720
|
switch (node.kind) {
|
|
3264
3721
|
case "keyword": return this.keywordSchema(node.name);
|
|
3265
3722
|
case "literal": return `z.literal(${node.text})`;
|
|
@@ -3357,6 +3814,8 @@ var SchemaEmitter = class {
|
|
|
3357
3814
|
objectSchema(members, subject, substitutions) {
|
|
3358
3815
|
const properties = [];
|
|
3359
3816
|
const indices = [];
|
|
3817
|
+
let stringIndexValue;
|
|
3818
|
+
let stringIndexType;
|
|
3360
3819
|
let symbolMembers = 0;
|
|
3361
3820
|
for (const member of members) {
|
|
3362
3821
|
if (member.static || member.visibility !== "public") continue;
|
|
@@ -3368,22 +3827,53 @@ var SchemaEmitter = class {
|
|
|
3368
3827
|
if (member.kind === "index") {
|
|
3369
3828
|
const parameter = member.signature.parameters[0];
|
|
3370
3829
|
if (member.signature.parameters.length !== 1 || parameter === void 0) this.fail(subject, "index signature must have exactly one key parameter");
|
|
3371
|
-
|
|
3830
|
+
const key = this.typeSchema(parameter.type, substitutions);
|
|
3831
|
+
const value = this.typeSchema(member.signature.returns, substitutions);
|
|
3832
|
+
if (key === "z.string()") {
|
|
3833
|
+
stringIndexValue = value;
|
|
3834
|
+
stringIndexType = member.signature.returns;
|
|
3835
|
+
}
|
|
3836
|
+
indices.push(this.readonly(`z.record(${key}, ${value})`, member.readonly));
|
|
3372
3837
|
continue;
|
|
3373
3838
|
}
|
|
3374
3839
|
if (member.kind !== "property") this.fail(subject, `${member.kind} member ${member.name} is not data-schema projectable`);
|
|
3375
|
-
|
|
3376
|
-
const property = this.describe(this.optional(schema, member.optional), member);
|
|
3377
|
-
properties.push(`${quote$1(member.jsonName ?? member.name)}: ${property}`);
|
|
3840
|
+
properties.push(member);
|
|
3378
3841
|
}
|
|
3379
3842
|
if (indices.length > 1) this.fail(subject, "object type has more than one JSON index signature");
|
|
3380
3843
|
if (properties.length === 0 && indices.length === 0 && symbolMembers > 0) return "z.unknown()";
|
|
3381
|
-
const
|
|
3844
|
+
const fields = properties.map((member) => {
|
|
3845
|
+
const type = this.options.closedObjects && stringIndexType !== void 0 ? this.indexedPropertySchema(member.type, stringIndexType, substitutions) : this.typeSchema(member.type, substitutions);
|
|
3846
|
+
const schema = this.readonly(type, member.readonly && !containsBytes(this.renderer, member.type, false));
|
|
3847
|
+
return `${quote$1(member.jsonName ?? member.name)}: ${this.describe(this.optional(schema, member.optional), member)}`;
|
|
3848
|
+
});
|
|
3849
|
+
const object = `z.${this.options.closedObjects && indices.length === 0 ? "strictObject" : "object"}({${fields.length === 0 ? "" : `\n${fields.map((field) => ` ${field},`).join("\n")}\n`}})`;
|
|
3382
3850
|
const index = indices[0];
|
|
3383
3851
|
if (index === void 0) return object;
|
|
3384
3852
|
if (properties.length === 0) return index;
|
|
3853
|
+
if (this.options.closedObjects && stringIndexValue !== void 0) return `${object}.catchall(${stringIndexValue})`;
|
|
3385
3854
|
return `z.intersection(${object}, ${index})`;
|
|
3386
3855
|
}
|
|
3856
|
+
indexedPropertySchema(declared, indexed, substitutions) {
|
|
3857
|
+
if (declared === indexed) return this.typeSchema(declared, substitutions);
|
|
3858
|
+
const own = this.renderer.node(declared);
|
|
3859
|
+
const value = this.renderer.node(indexed);
|
|
3860
|
+
if (own.kind === "union") return `z.union([${own.types.map((type) => this.indexedPropertySchema(type, indexed, substitutions)).join(", ")}])`;
|
|
3861
|
+
if (value.kind === "union") return `z.union([${value.types.map((type) => this.indexedPropertySchema(declared, type, substitutions)).join(", ")}])`;
|
|
3862
|
+
const ownProperties = own.kind === "object" ? own.members.filter((member) => member.kind === "property") : [];
|
|
3863
|
+
const valueProperties = value.kind === "object" ? value.members.filter((member) => member.kind === "property") : [];
|
|
3864
|
+
if (own.kind === "object" && value.kind === "object" && !own.json && !value.json && ownProperties.length === own.members.length && valueProperties.length === value.members.length && [...ownProperties, ...valueProperties].every((member) => !member.static && member.visibility === "public" && member.computed === void 0)) {
|
|
3865
|
+
const indexedFields = new Map(valueProperties.map((member) => [member.jsonName ?? member.name, member]));
|
|
3866
|
+
const overlaps = ownProperties.flatMap((member) => {
|
|
3867
|
+
const inherited = indexedFields.get(member.jsonName ?? member.name);
|
|
3868
|
+
if (inherited === void 0) return [];
|
|
3869
|
+
const combined = this.indexedPropertySchema(member.type, inherited.type, substitutions);
|
|
3870
|
+
const schema = this.readonly(this.optional(combined, member.optional && inherited.optional), (member.readonly || inherited.readonly) && !containsBytes(this.renderer, member.type, false));
|
|
3871
|
+
return [`${quote$1(member.jsonName ?? member.name)}: ${this.describe(schema, member)}`];
|
|
3872
|
+
});
|
|
3873
|
+
return `z.strictObject({ ...${this.typeSchema(indexed, substitutions)}.shape, ...${this.typeSchema(declared, substitutions)}.shape${overlaps.length === 0 ? "" : `, ${overlaps.join(", ")}`} })`;
|
|
3874
|
+
}
|
|
3875
|
+
return `z.intersection(${this.typeSchema(declared, substitutions)}, ${this.typeSchema(indexed, substitutions)})`;
|
|
3876
|
+
}
|
|
3387
3877
|
exportSchemaName(model) {
|
|
3388
3878
|
const name = this.schemaName(model.symbol);
|
|
3389
3879
|
if (this.renderer.declaration(model.symbol).typeParameters.length > 0) this.fail(model.export.name, "generic schema exports require a concrete declaration");
|
|
@@ -3451,6 +3941,11 @@ function invocationBoundaryRoots(invocations) {
|
|
|
3451
3941
|
key: parameterBoundaryKey(invocation, index),
|
|
3452
3942
|
type: parameter.boundary.codecType
|
|
3453
3943
|
});
|
|
3944
|
+
result.push({
|
|
3945
|
+
key: parameterBoundaryKey(invocation, index, true),
|
|
3946
|
+
type: parameter.boundary.codecType,
|
|
3947
|
+
closedObjects: true
|
|
3948
|
+
});
|
|
3454
3949
|
});
|
|
3455
3950
|
if (invocation.uplink !== void 0) result.push({
|
|
3456
3951
|
key: uplinkBoundaryKey(invocation),
|
|
@@ -3469,8 +3964,8 @@ function contextBoundaryKey(invocation) {
|
|
|
3469
3964
|
function uplinkBoundaryKey(invocation) {
|
|
3470
3965
|
return `${invocation.id}:uplink`;
|
|
3471
3966
|
}
|
|
3472
|
-
function parameterBoundaryKey(invocation, index) {
|
|
3473
|
-
return `${invocation.id}:parameter:${String(index)}`;
|
|
3967
|
+
function parameterBoundaryKey(invocation, index, strict = false) {
|
|
3968
|
+
return `${invocation.id}:parameter:${String(index)}${strict ? ":strict" : ""}`;
|
|
3474
3969
|
}
|
|
3475
3970
|
function resultBoundaryKey(invocation) {
|
|
3476
3971
|
return `${invocation.id}:result`;
|
|
@@ -3491,12 +3986,14 @@ function containsBytes(renderer, id, nested = true, visited = /* @__PURE__ */ ne
|
|
|
3491
3986
|
if (node.kind === "object" && node.members.some((member) => containsBytes(renderer, member.kind === "property" ? member.type : member.signature.returns, nested, visited))) return true;
|
|
3492
3987
|
return childTypeNodeIds(node).some((child) => containsBytes(renderer, child, nested, visited));
|
|
3493
3988
|
}
|
|
3494
|
-
function strictCodec(boundary, schema, encoder) {
|
|
3989
|
+
function strictCodec(boundary, schema, encoder, projection, description) {
|
|
3495
3990
|
return [
|
|
3496
3991
|
"{",
|
|
3497
3992
|
" mode: 'strict',",
|
|
3498
3993
|
` typeSymbol: ${quote$1(boundary.typeSymbol)},`,
|
|
3499
3994
|
` create: ${schema},`,
|
|
3995
|
+
...description === void 0 ? [] : [` description: ${quote$1(description)},`],
|
|
3996
|
+
...projection === void 0 ? [] : [` ${projection},`],
|
|
3500
3997
|
...encoder === void 0 ? [] : [` decode: value => ${schema}().parse(value),`, ` encode: (value, writeBytes) => ${encoder}(value, writeBytes, [], new Map()),`],
|
|
3501
3998
|
"}"
|
|
3502
3999
|
].join("\n");
|
|
@@ -3523,7 +4020,8 @@ function allocateRemoteImportNames(imports) {
|
|
|
3523
4020
|
"RemoteDecoded",
|
|
3524
4021
|
"RemoteResult",
|
|
3525
4022
|
"RemoteStreamHandle",
|
|
3526
|
-
"
|
|
4023
|
+
"TypertRemoteInfo",
|
|
4024
|
+
"TypertRemoteMethod",
|
|
3527
4025
|
"TYPERT_REMOTE"
|
|
3528
4026
|
]);
|
|
3529
4027
|
const names = /* @__PURE__ */ new Map();
|
|
@@ -4291,34 +4789,38 @@ var WorkspaceTypertGenerator = class {
|
|
|
4291
4789
|
* Find public package faces that contribute Cordis services/events or
|
|
4292
4790
|
* explicitly tagged Typert roots.
|
|
4293
4791
|
* @param faces - optional independent program faces to inspect.
|
|
4792
|
+
* @param includeManifest - optional `package.json` predicate; a rejected package's sources and project config are not read.
|
|
4294
4793
|
* @returns discovered packages in stable package-name order.
|
|
4295
4794
|
*/
|
|
4296
|
-
discover(faces) {
|
|
4795
|
+
discover(faces, includeManifest) {
|
|
4297
4796
|
return new WorkspaceAnalyzer({
|
|
4298
4797
|
root: this.root,
|
|
4299
4798
|
caches: this.caches,
|
|
4300
4799
|
...faces === void 0 ? {} : { faces }
|
|
4301
|
-
}).discoverPackages();
|
|
4800
|
+
}).discoverPackages(includeManifest);
|
|
4302
4801
|
}
|
|
4303
4802
|
/**
|
|
4304
4803
|
* Generate all discovered contributors, or an explicit package subset.
|
|
4305
|
-
* @param packages - optional exact package names
|
|
4804
|
+
* @param packages - optional exact package names to emit; every pass includes the same face-wide declaration merges.
|
|
4306
4805
|
* @param faces - optional independent program faces to analyze.
|
|
4307
4806
|
* @returns one artifact per package face.
|
|
4308
4807
|
*/
|
|
4309
4808
|
generate(packages, faces) {
|
|
4310
|
-
const
|
|
4809
|
+
const contributors = this.discover(faces).map((candidate) => candidate.package);
|
|
4810
|
+
const selected = new Set(packages ?? contributors);
|
|
4311
4811
|
const workspace = new WorkspaceAnalyzer({
|
|
4312
4812
|
root: this.root,
|
|
4313
|
-
packages:
|
|
4813
|
+
packages: contributors,
|
|
4314
4814
|
caches: this.caches,
|
|
4315
4815
|
...faces === void 0 ? {} : { faces },
|
|
4316
|
-
...this.options.checkDiagnostics === void 0 ? {} : { checkDiagnostics: this.options.checkDiagnostics }
|
|
4816
|
+
...this.options.checkDiagnostics === void 0 ? {} : { checkDiagnostics: this.options.checkDiagnostics },
|
|
4817
|
+
...this.options.rejectBuildOutputs === void 0 ? {} : { rejectBuildOutputs: this.options.rejectBuildOutputs }
|
|
4317
4818
|
}).analyze();
|
|
4318
4819
|
const artifacts = [];
|
|
4319
4820
|
for (const face of workspace.faces) {
|
|
4320
4821
|
const emitter = new FaceModelEmitter(face);
|
|
4321
4822
|
for (const packageModel of face.packages) {
|
|
4823
|
+
if (!selected.has(packageModel.name)) continue;
|
|
4322
4824
|
const artifact = {
|
|
4323
4825
|
...emitter.emit(packageModel.name),
|
|
4324
4826
|
packageRoot: packageModel.root
|
|
@@ -4329,6 +4831,16 @@ var WorkspaceTypertGenerator = class {
|
|
|
4329
4831
|
}
|
|
4330
4832
|
return artifacts;
|
|
4331
4833
|
}
|
|
4834
|
+
/**
|
|
4835
|
+
* Generate every explicit contributor: a package whose manifest declares a
|
|
4836
|
+
* Typert artifact export and whose public graph has a Typert surface.
|
|
4837
|
+
* @param faces - optional independent program faces to analyze.
|
|
4838
|
+
* @returns one artifact per contributor face.
|
|
4839
|
+
*/
|
|
4840
|
+
generateContributors(faces) {
|
|
4841
|
+
const packages = this.discover(faces, (manifest) => hasTypertExport(manifest.exports)).map((candidate) => candidate.package);
|
|
4842
|
+
return packages.length === 0 ? [] : this.generate(packages, faces);
|
|
4843
|
+
}
|
|
4332
4844
|
validateExport(artifact) {
|
|
4333
4845
|
const manifestPath = resolve(this.root, artifact.packageRoot, "package.json");
|
|
4334
4846
|
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
@@ -4355,10 +4867,90 @@ var WorkspaceTypertGenerator = class {
|
|
|
4355
4867
|
for (const file of remoteFiles) if (!files.includes(file)) throw new TypertAnalysisError(`typert(host): ${artifact.package} package files must include ${file}`);
|
|
4356
4868
|
}
|
|
4357
4869
|
};
|
|
4870
|
+
/**
|
|
4871
|
+
* Find the workspace root that Typert generation runs against: the nearest
|
|
4872
|
+
* directory at or above `start` that holds both `tsconfig.host.json` and
|
|
4873
|
+
* `packages/`. The analyzer registers projects under `<root>/packages`, so a
|
|
4874
|
+
* package's own `tsconfig.host.json` face leaf does not qualify.
|
|
4875
|
+
* @param start - directory to search from, such as a bundle output or a working directory.
|
|
4876
|
+
* @returns the absolute workspace root.
|
|
4877
|
+
* @throws If no ancestor of `start` qualifies.
|
|
4878
|
+
*/
|
|
4879
|
+
function findWorkspaceRoot(start) {
|
|
4880
|
+
let current = resolve(start);
|
|
4881
|
+
while (!existsSync(join(current, "tsconfig.host.json")) || !existsSync(join(current, "packages"))) {
|
|
4882
|
+
const parent = dirname(current);
|
|
4883
|
+
if (parent === current) throw new Error(`typert-generator: cannot find workspace root above ${start}`);
|
|
4884
|
+
current = parent;
|
|
4885
|
+
}
|
|
4886
|
+
return current;
|
|
4887
|
+
}
|
|
4888
|
+
/**
|
|
4889
|
+
* Whether a `package.json#exports` field declares a Typert artifact subpath
|
|
4890
|
+
* (`./typert`, `./client/typert`, or `./remote`).
|
|
4891
|
+
* @param exportsField - the manifest's `exports` value.
|
|
4892
|
+
* @returns true when the package opts in to Typert publication.
|
|
4893
|
+
*/
|
|
4894
|
+
function hasTypertExport(exportsField) {
|
|
4895
|
+
if (exportsField === null || typeof exportsField !== "object" || Array.isArray(exportsField)) return false;
|
|
4896
|
+
return Object.hasOwn(exportsField, "./typert") || Object.hasOwn(exportsField, "./client/typert") || Object.hasOwn(exportsField, "./remote");
|
|
4897
|
+
}
|
|
4358
4898
|
function sameExport(actual, expected) {
|
|
4359
4899
|
if (actual === null || typeof actual !== "object" || Array.isArray(actual)) return false;
|
|
4360
4900
|
const value = actual;
|
|
4361
4901
|
return value.types === expected.types && value.default === expected.default;
|
|
4362
4902
|
}
|
|
4363
4903
|
//#endregion
|
|
4364
|
-
|
|
4904
|
+
//#region lib/types/artifact-files.js
|
|
4905
|
+
/**
|
|
4906
|
+
* Package-local files of generated Typert artifacts. The tsdown plugin and
|
|
4907
|
+
* standalone workspace generation both write through this module, so a pass
|
|
4908
|
+
* over the same artifacts leaves the same bytes on disk.
|
|
4909
|
+
* @module @deepseek-ai/dsh-typert-generator/artifact-files
|
|
4910
|
+
*/
|
|
4911
|
+
const REMOTE_CLIENT_FILES = [
|
|
4912
|
+
"typert.remote-client.js",
|
|
4913
|
+
"typert.remote-client.d.ts",
|
|
4914
|
+
"typert.remote-client.d.ts.map"
|
|
4915
|
+
];
|
|
4916
|
+
/**
|
|
4917
|
+
* Write one package's artifacts under `<packageDir>/lib`: `typert.<face>.js`
|
|
4918
|
+
* and `typert.<face>.d.ts` per artifact, plus the three
|
|
4919
|
+
* `typert.remote-client.*` files when an artifact carries Remote methods. A
|
|
4920
|
+
* Host artifact without Remote methods removes any Remote client files left
|
|
4921
|
+
* by an earlier pass. Files are written one by one, so a failed write can
|
|
4922
|
+
* leave this package's outputs partially replaced.
|
|
4923
|
+
* @param packageDir - absolute package root.
|
|
4924
|
+
* @param artifacts - artifacts of that package only.
|
|
4925
|
+
* @throws A file-system error when the output directory or a file cannot be written.
|
|
4926
|
+
*/
|
|
4927
|
+
function writePackageArtifacts(packageDir, artifacts) {
|
|
4928
|
+
const output = join(packageDir, "lib");
|
|
4929
|
+
mkdirSync(output, { recursive: true });
|
|
4930
|
+
let emittedRemote = false;
|
|
4931
|
+
for (const artifact of artifacts) {
|
|
4932
|
+
writeFileSync(join(output, `typert.${artifact.face}.js`), artifact.js);
|
|
4933
|
+
writeFileSync(join(output, `typert.${artifact.face}.d.ts`), artifact.dts);
|
|
4934
|
+
if (artifact.remote !== void 0) {
|
|
4935
|
+
emittedRemote = true;
|
|
4936
|
+
writeFileSync(join(output, "typert.remote-client.js"), artifact.remote.js);
|
|
4937
|
+
writeFileSync(join(output, "typert.remote-client.d.ts"), artifact.remote.dts);
|
|
4938
|
+
writeFileSync(join(output, "typert.remote-client.d.ts.map"), artifact.remote.dtsMap);
|
|
4939
|
+
}
|
|
4940
|
+
}
|
|
4941
|
+
if (!emittedRemote && artifacts.some((artifact) => artifact.face === "host")) for (const file of REMOTE_CLIENT_FILES) rmSync(join(output, file), { force: true });
|
|
4942
|
+
}
|
|
4943
|
+
/**
|
|
4944
|
+
* Write every artifact of one workspace pass into its own package, in
|
|
4945
|
+
* artifact order, with {@link writePackageArtifacts}. A failed write stops
|
|
4946
|
+
* the pass; earlier packages keep their new files and later ones keep their
|
|
4947
|
+
* previous files.
|
|
4948
|
+
* @param root - workspace root that each artifact's `packageRoot` is relative to.
|
|
4949
|
+
* @param artifacts - artifacts returned by one `WorkspaceTypertGenerator` pass.
|
|
4950
|
+
* @throws A file-system error when an output directory or file cannot be written.
|
|
4951
|
+
*/
|
|
4952
|
+
function writeWorkspaceArtifacts(root, artifacts) {
|
|
4953
|
+
for (const artifact of artifacts) writePackageArtifacts(join(root, artifact.packageRoot), [artifact]);
|
|
4954
|
+
}
|
|
4955
|
+
//#endregion
|
|
4956
|
+
export { CordisCatalogProjector, FaceModelEmitter, REGION_BEGIN, REGION_END, TypeGraphRenderError, TypeGraphRenderer, TypertAnalysisError, TypertEmitError, WorkspaceAnalyzer, WorkspaceCaches, WorkspaceTypertGenerator, collectEvents, collectServices, findWorkspaceRoot, projectCordisCatalog, renderInheritedPage, renderPageRegion, writePackageArtifacts, writeWorkspaceArtifacts };
|