@deepseek-ai/dsh-typert-generator 0.2.1-alpha.1 → 0.2.1-alpha.3
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 +714 -105
- package/lib/types/analyzer.d.ts +34 -2
- package/lib/types/analyzer.js +201 -49
- 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 +176 -77
- 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));
|
|
@@ -370,6 +558,9 @@ var FaceAnalyzer = class {
|
|
|
370
558
|
nodes = /* @__PURE__ */ new Map();
|
|
371
559
|
exportsByPackage = /* @__PURE__ */ new Map();
|
|
372
560
|
nodeOrdinals = /* @__PURE__ */ new Map();
|
|
561
|
+
/** Registration and package lookups per source path; the registration set is fixed per analyzer. */
|
|
562
|
+
registrationByFile = /* @__PURE__ */ new Map();
|
|
563
|
+
packageNameByFile = /* @__PURE__ */ new Map();
|
|
373
564
|
staticLookups;
|
|
374
565
|
staticContexts;
|
|
375
566
|
constructor(options) {
|
|
@@ -696,6 +887,7 @@ var FaceAnalyzer = class {
|
|
|
696
887
|
if (hasModifier(method, ts.SyntaxKind.AbstractKeyword) || method.body === void 0) this.fail(method, "Remote methods must have a concrete implementation");
|
|
697
888
|
if (!ts.isIdentifier(method.name)) this.fail(method, "Remote method names must be identifiers");
|
|
698
889
|
if ((method.typeParameters?.length ?? 0) > 0) this.fail(method, "generic Remote methods are not supported");
|
|
890
|
+
const documentation = documentationOf(method);
|
|
699
891
|
const methodName = method.name.text;
|
|
700
892
|
const exportedMethod = invocation.exportName ?? methodName;
|
|
701
893
|
const lookups = this.lookupDeclarations();
|
|
@@ -744,7 +936,11 @@ var FaceAnalyzer = class {
|
|
|
744
936
|
}
|
|
745
937
|
if (wires.has(modeled.wire)) this.fail(parameter, `duplicate Remote wire field ${modeled.wire}`);
|
|
746
938
|
wires.add(modeled.wire);
|
|
747
|
-
|
|
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
|
+
});
|
|
748
944
|
}
|
|
749
945
|
let receiver = { kind: "direct" };
|
|
750
946
|
if (invocation.kind === "context") {
|
|
@@ -777,6 +973,7 @@ var FaceAnalyzer = class {
|
|
|
777
973
|
const { result: resultType, uplink: uplinkType } = this.remoteResultType(method, mode);
|
|
778
974
|
const uplink = uplinkType === void 0 ? void 0 : { boundary: this.remoteBoundary(uplinkType, `${registration.name}#${binding.namespace}/${exportedMethod}:uplink`, false, "undefined") };
|
|
779
975
|
return {
|
|
976
|
+
...documentation,
|
|
780
977
|
id: `${registration.name}#${binding.namespace}/${exportedMethod}`,
|
|
781
978
|
service: binding.service,
|
|
782
979
|
namespace: binding.namespace,
|
|
@@ -863,28 +1060,27 @@ var FaceAnalyzer = class {
|
|
|
863
1060
|
kind: "direct",
|
|
864
1061
|
exportName
|
|
865
1062
|
};
|
|
866
|
-
} else {
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
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\"");
|
|
872
|
-
marker = {
|
|
873
|
-
kind: "direct",
|
|
874
|
-
mode
|
|
875
|
-
};
|
|
876
|
-
}
|
|
1063
|
+
} else marker = {
|
|
1064
|
+
kind: "direct",
|
|
1065
|
+
...this.remoteOptions(argument, "Remote")
|
|
1066
|
+
};
|
|
877
1067
|
} else if (ts.isCallExpression(expression) && this.isTypeMetaSymbol(expression.expression, "RemoteScope")) {
|
|
878
|
-
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");
|
|
879
1069
|
const context = stringLiteralValue(expression.arguments[0]);
|
|
880
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");
|
|
881
|
-
const
|
|
882
|
-
const exportName =
|
|
883
|
-
if (
|
|
884
|
-
|
|
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 = {
|
|
885
1081
|
kind: "context",
|
|
886
1082
|
context,
|
|
887
|
-
...
|
|
1083
|
+
...this.remoteOptions(option, "RemoteScope")
|
|
888
1084
|
};
|
|
889
1085
|
} else continue;
|
|
890
1086
|
if (found !== void 0) this.fail(decorator, "a method can have only one Remote invocation decorator");
|
|
@@ -893,6 +1089,34 @@ var FaceAnalyzer = class {
|
|
|
893
1089
|
return found;
|
|
894
1090
|
}
|
|
895
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
|
+
/**
|
|
896
1120
|
* The item types a Remote method's authored return type declares. Unary
|
|
897
1121
|
* methods unwrap `Promise<T>`; stream methods unwrap `Iterable<Out>`,
|
|
898
1122
|
* `AsyncIterable<Out>`, or the protocol's `RemoteStream<Out, In>`, whose
|
|
@@ -1061,6 +1285,13 @@ var FaceAnalyzer = class {
|
|
|
1061
1285
|
completed.set(type, id);
|
|
1062
1286
|
return id;
|
|
1063
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
|
+
});
|
|
1064
1295
|
const flags = type.flags;
|
|
1065
1296
|
if (this.isRemoteByteArray(type)) return add({
|
|
1066
1297
|
kind: "reference",
|
|
@@ -1149,8 +1380,19 @@ var FaceAnalyzer = class {
|
|
|
1149
1380
|
text: String(value)
|
|
1150
1381
|
});
|
|
1151
1382
|
}
|
|
1152
|
-
if (type.
|
|
1153
|
-
|
|
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",
|
|
1154
1396
|
types: type.types.map(convert)
|
|
1155
1397
|
});
|
|
1156
1398
|
if ((flags & ts.TypeFlags.TypeParameter) !== 0) this.fail(authoredType, "Remote codec contains an unresolved type parameter");
|
|
@@ -1188,7 +1430,7 @@ var FaceAnalyzer = class {
|
|
|
1188
1430
|
const propertyType = this.checker.getTypeOfSymbolAtLocation(property, declaration ?? authoredType);
|
|
1189
1431
|
const symbolKey = property.getName();
|
|
1190
1432
|
members.push({
|
|
1191
|
-
...EMPTY_DOCUMENTATION,
|
|
1433
|
+
...declaration === void 0 ? EMPTY_DOCUMENTATION : documentationOf(declaration),
|
|
1192
1434
|
id: `${id}#${symbolKey}`,
|
|
1193
1435
|
name: symbolKey,
|
|
1194
1436
|
...symbolKey.startsWith("__@") ? { computed: "symbol" } : {},
|
|
@@ -1954,12 +2196,19 @@ var FaceAnalyzer = class {
|
|
|
1954
2196
|
return `${this.packageNameForFile(declaration.getSourceFile().fileName)}:${location.file}#${symbol.name}`;
|
|
1955
2197
|
}
|
|
1956
2198
|
registrationForFile(file) {
|
|
2199
|
+
if (this.registrationByFile.has(file)) return this.registrationByFile.get(file);
|
|
1957
2200
|
const path = realPath(file);
|
|
1958
|
-
|
|
2201
|
+
const registration = this.allRegistrations.find((registration) => registration.face === this.face && isWithin(path, registration.root));
|
|
2202
|
+
this.registrationByFile.set(file, registration);
|
|
2203
|
+
return registration;
|
|
1959
2204
|
}
|
|
1960
2205
|
packageNameForFile(file) {
|
|
2206
|
+
const cached = this.packageNameByFile.get(file);
|
|
2207
|
+
if (cached !== void 0) return cached;
|
|
1961
2208
|
const path = realPath(file);
|
|
1962
|
-
|
|
2209
|
+
const name = this.allRegistrations.find((registration) => isWithin(path, registration.root))?.name ?? "<external>";
|
|
2210
|
+
this.packageNameByFile.set(file, name);
|
|
2211
|
+
return name;
|
|
1963
2212
|
}
|
|
1964
2213
|
allocateNodeId(site) {
|
|
1965
2214
|
const location = this.locationKey(site);
|
|
@@ -2021,11 +2270,20 @@ function mergeWorkspaceModels(models) {
|
|
|
2021
2270
|
crossFaceLinks: [...links.values()].sort(compareCrossFaceLinks)
|
|
2022
2271
|
};
|
|
2023
2272
|
}
|
|
2024
|
-
|
|
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) {
|
|
2025
2281
|
const compilerPath = path.split(sep).join("/");
|
|
2026
2282
|
const read = ts.readConfigFile(compilerPath, (file) => ts.sys.readFile(file));
|
|
2027
2283
|
if (read.error !== void 0) throw new TypertAnalysisError(formatDiagnostic(read.error));
|
|
2028
|
-
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);
|
|
2029
2287
|
if (parsed.errors.length > 0) throw new TypertAnalysisError(parsed.errors.map(formatDiagnostic).join("\n"));
|
|
2030
2288
|
return {
|
|
2031
2289
|
path,
|
|
@@ -2140,7 +2398,8 @@ function declarationText(declaration) {
|
|
|
2140
2398
|
function classShape(node) {
|
|
2141
2399
|
const nonPublic = (member) => (ts.canHaveModifiers(member) ? ts.getModifiers(member) : void 0)?.some((modifier) => modifier.kind === ts.SyntaxKind.PrivateKeyword || modifier.kind === ts.SyntaxKind.ProtectedKeyword) ?? false;
|
|
2142
2400
|
const members = node.members.flatMap((member) => {
|
|
2143
|
-
|
|
2401
|
+
const name = ts.getNameOfDeclaration(member);
|
|
2402
|
+
if (ts.isClassStaticBlockDeclaration(member) || nonPublic(member) || name !== void 0 && ts.isPrivateIdentifier(name)) return [];
|
|
2144
2403
|
if (ts.isMethodDeclaration(member)) return [ts.factory.updateMethodDeclaration(member, member.modifiers, member.asteriskToken, member.name, member.questionToken, member.typeParameters, member.parameters, member.type, void 0)];
|
|
2145
2404
|
if (ts.isConstructorDeclaration(member)) return [ts.factory.updateConstructorDeclaration(member, member.modifiers, member.parameters, void 0)];
|
|
2146
2405
|
if (ts.isGetAccessorDeclaration(member)) return [ts.factory.updateGetAccessorDeclaration(member, member.modifiers, member.name, member.parameters, member.type, void 0)];
|
|
@@ -2383,8 +2642,14 @@ function formatProgramDiagnostic(root, face, diagnostic) {
|
|
|
2383
2642
|
return `typert(${face}): ${slash(relative(root, diagnostic.file.fileName))}:${String(position.line + 1)}:${String(position.character + 1)}: TypeScript TS${String(diagnostic.code)}: ${message}`;
|
|
2384
2643
|
}
|
|
2385
2644
|
const realPathCache = /* @__PURE__ */ new Map();
|
|
2645
|
+
/** Normalized form per requested path; `resolve` reads only the process working directory. */
|
|
2646
|
+
const absolutePathCache = /* @__PURE__ */ new Map();
|
|
2386
2647
|
function realPath(path) {
|
|
2387
|
-
|
|
2648
|
+
let absolute = absolutePathCache.get(path);
|
|
2649
|
+
if (absolute === void 0) {
|
|
2650
|
+
absolute = resolve(path);
|
|
2651
|
+
absolutePathCache.set(path, absolute);
|
|
2652
|
+
}
|
|
2388
2653
|
const cached = realPathCache.get(absolute);
|
|
2389
2654
|
if (cached !== void 0) return cached;
|
|
2390
2655
|
if (!existsSync(absolute)) return absolute;
|
|
@@ -2400,6 +2665,33 @@ function isWithin(path, root) {
|
|
|
2400
2665
|
function slash(value) {
|
|
2401
2666
|
return value.replaceAll("\\", "/");
|
|
2402
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
|
+
}
|
|
2403
2695
|
function uniqueBy(values, key) {
|
|
2404
2696
|
const result = /* @__PURE__ */ new Map();
|
|
2405
2697
|
for (const value of values) if (!result.has(key(value))) result.set(key(value), value);
|
|
@@ -2755,6 +3047,148 @@ function assertNever(value) {
|
|
|
2755
3047
|
throw new TypeGraphRenderError(`unsupported model variant ${JSON.stringify(value)}`);
|
|
2756
3048
|
}
|
|
2757
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
|
|
2758
3192
|
//#region lib/types/emitter.js
|
|
2759
3193
|
/**
|
|
2760
3194
|
* Model-driven Typert artifact emitter. It consumes only FaceModel and
|
|
@@ -2785,7 +3219,7 @@ var FaceModelEmitter = class {
|
|
|
2785
3219
|
emit(packageName) {
|
|
2786
3220
|
const packageModel = this.face.packages.find((candidate) => candidate.name === packageName);
|
|
2787
3221
|
if (packageModel === void 0) throw new TypertEmitError(`typert emitter(${this.face.face}): package ${packageName} is not modeled on this face`);
|
|
2788
|
-
const schemaArtifact =
|
|
3222
|
+
const schemaArtifact = invocationSchemas(this.renderer, packageModel.schemas, invocationBoundaryRoots(packageModel.invocations));
|
|
2789
3223
|
const runtimeModel = this.runtimeModel(packageModel);
|
|
2790
3224
|
const js = this.renderJs(packageModel, schemaArtifact, runtimeModel);
|
|
2791
3225
|
const dts = this.renderDts(packageModel, schemaArtifact);
|
|
@@ -2850,10 +3284,12 @@ var FaceModelEmitter = class {
|
|
|
2850
3284
|
renderJs(packageModel, schemas, runtimeModel) {
|
|
2851
3285
|
const lines = ["/* Generated by @deepseek-ai/dsh-typert-generator from FaceModel — do not edit. */"];
|
|
2852
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'", "");
|
|
2853
3288
|
lines.push(...schemas.definitions);
|
|
2854
3289
|
if (schemas.definitions.length > 0) lines.push("");
|
|
2855
3290
|
for (const schema of schemas.exports) lines.push(`export const ${schema.exportName} = ${schema.internalName}`);
|
|
2856
3291
|
if (schemas.exports.length > 0) lines.push("");
|
|
3292
|
+
if (packageModel.invocations.length > 0) lines.push(`const $remote = ${this.remoteInfoLiteral(packageModel, schemas)}`, "");
|
|
2857
3293
|
const model = JSON.stringify(runtimeModel, null, 2);
|
|
2858
3294
|
lines.push("export const TYPERT = {");
|
|
2859
3295
|
lines.push(` package: ${quote$1(packageModel.name)},`);
|
|
@@ -2861,9 +3297,8 @@ var FaceModelEmitter = class {
|
|
|
2861
3297
|
lines.push(" schemas: [");
|
|
2862
3298
|
for (const schema of schemas.exports) lines.push(` { name: ${quote$1(schema.exportName)}, create: ${schema.exportName} },`);
|
|
2863
3299
|
lines.push(" ],");
|
|
2864
|
-
lines.push(" invocations
|
|
2865
|
-
|
|
2866
|
-
lines.push(" ],");
|
|
3300
|
+
if (packageModel.invocations.length > 0) lines.push(" get invocations() { return $remote.descriptors },");
|
|
3301
|
+
else lines.push(" invocations: [", " ],");
|
|
2867
3302
|
lines.push(` model: ${indent(model, 2).trimStart()},`);
|
|
2868
3303
|
lines.push("}");
|
|
2869
3304
|
return `${lines.join("\n")}\n`;
|
|
@@ -2886,32 +3321,47 @@ var FaceModelEmitter = class {
|
|
|
2886
3321
|
return `${lines.join("\n")}\n`;
|
|
2887
3322
|
}
|
|
2888
3323
|
emitRemote(packageModel) {
|
|
2889
|
-
const schemas =
|
|
3324
|
+
const schemas = invocationSchemas(this.renderer, [], invocationBoundaryRoots(packageModel.invocations));
|
|
2890
3325
|
const lines = ["/* Generated by @deepseek-ai/dsh-typert-generator from the Host FaceModel — do not edit. */"];
|
|
2891
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'", "");
|
|
2892
3328
|
lines.push(...schemas.definitions);
|
|
2893
3329
|
if (schemas.definitions.length > 0) lines.push("");
|
|
2894
|
-
lines.push(
|
|
2895
|
-
lines.push(` package: ${quote$1(packageModel.name)},`);
|
|
2896
|
-
lines.push(" descriptors: [");
|
|
2897
|
-
for (const invocation of packageModel.invocations) lines.push(`${indent(this.invocationLiteral(invocation, schemas), 4)},`);
|
|
2898
|
-
lines.push(" ],");
|
|
2899
|
-
lines.push("}");
|
|
2900
|
-
lines.push("");
|
|
3330
|
+
lines.push(`export const TYPERT_REMOTE = ${this.remoteInfoLiteral(packageModel, schemas)}`, "");
|
|
2901
3331
|
lines.push("export default TYPERT_REMOTE");
|
|
2902
|
-
const declaration = this.renderRemoteDts(packageModel);
|
|
2903
3332
|
return {
|
|
2904
3333
|
js: `${lines.join("\n")}\n`,
|
|
2905
|
-
...
|
|
3334
|
+
...this.renderRemoteDts(packageModel)
|
|
2906
3335
|
};
|
|
2907
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
|
+
}
|
|
2908
3360
|
invocationLiteral(invocation, schemas) {
|
|
2909
3361
|
const lines = [
|
|
2910
3362
|
"{",
|
|
2911
|
-
` id: ${quote$1(invocation.id)},`,
|
|
2912
3363
|
` service: ${quote$1(invocation.service)},`,
|
|
2913
|
-
`
|
|
2914
|
-
` method: ${quote$1(invocation.method)},`
|
|
3364
|
+
...invocation.description === void 0 ? [] : [` description: ${quote$1(invocation.description)},`]
|
|
2915
3365
|
];
|
|
2916
3366
|
if (invocation.implementation !== void 0) lines.push(` implementation: ${quote$1(invocation.implementation)},`);
|
|
2917
3367
|
if (invocation.mode !== void 0) lines.push(` mode: ${quote$1(invocation.mode)},`);
|
|
@@ -2934,11 +3384,12 @@ var FaceModelEmitter = class {
|
|
|
2934
3384
|
invocation.parameters.forEach((parameter, index) => {
|
|
2935
3385
|
lines.push(" {");
|
|
2936
3386
|
lines.push(` name: ${quote$1(parameter.name)},`);
|
|
2937
|
-
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)},`);
|
|
2938
3389
|
lines.push(` source: ${quote$1(parameter.source)},`);
|
|
2939
3390
|
if (parameter.lookup !== void 0) lines.push(` lookup: ${quote$1(parameter.lookup)},`);
|
|
2940
3391
|
if (parameter.boundary.acceptsUndefined) lines.push(" acceptsUndefined: true,");
|
|
2941
|
-
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()},`);
|
|
2942
3393
|
lines.push(" },");
|
|
2943
3394
|
});
|
|
2944
3395
|
lines.push(" ],");
|
|
@@ -2948,7 +3399,8 @@ var FaceModelEmitter = class {
|
|
|
2948
3399
|
lines.push(" },");
|
|
2949
3400
|
}
|
|
2950
3401
|
if (invocation.cancellation !== void 0) lines.push(" cancellation: { parameter: 'signal' },");
|
|
2951
|
-
|
|
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()},`);
|
|
2952
3404
|
lines.push(` sourceLocation: ${JSON.stringify(invocation.location)},`);
|
|
2953
3405
|
lines.push("}");
|
|
2954
3406
|
return lines.join("\n");
|
|
@@ -2968,9 +3420,11 @@ var FaceModelEmitter = class {
|
|
|
2968
3420
|
const lines = [
|
|
2969
3421
|
"/* Generated by @deepseek-ai/dsh-typert-generator from the Host FaceModel — do not edit. */",
|
|
2970
3422
|
"import type {",
|
|
3423
|
+
...packageModel.invocations.some((invocation) => containsBytes(this.renderer, invocation.result.codecType)) ? [" RemoteDecoded,"] : [],
|
|
2971
3424
|
" RemoteResult,",
|
|
2972
3425
|
...packageModel.invocations.some((invocation) => invocation.mode === "stream") ? [" RemoteStreamHandle,"] : [],
|
|
2973
|
-
"
|
|
3426
|
+
" TypertRemoteInfo,",
|
|
3427
|
+
...packageModel.invocations.some((invocation) => invocation.invocation.kind === "direct") ? [" TypertRemoteMethod,"] : [],
|
|
2974
3428
|
"} from '@deepseek-ai/dsh-typert-protocol'"
|
|
2975
3429
|
];
|
|
2976
3430
|
const sourceMap = new GenMapping({ file: "typert.remote-client.d.ts" });
|
|
@@ -2979,12 +3433,6 @@ var FaceModelEmitter = class {
|
|
|
2979
3433
|
lines.push(`import type { ${names.join(", ")} } from ${quote$1(specifier)}`);
|
|
2980
3434
|
}
|
|
2981
3435
|
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
|
-
}
|
|
2988
3436
|
lines.push("declare module '@deepseek-ai/dsh-typert-protocol' {");
|
|
2989
3437
|
const direct = packageModel.invocations.filter((invocation) => invocation.invocation.kind === "direct");
|
|
2990
3438
|
const scoped = packageModel.invocations.filter((invocation) => invocation.invocation.kind === "context" || invocation.scope !== void 0);
|
|
@@ -3008,7 +3456,7 @@ var FaceModelEmitter = class {
|
|
|
3008
3456
|
}
|
|
3009
3457
|
lines.push("}");
|
|
3010
3458
|
lines.push("");
|
|
3011
|
-
lines.push(
|
|
3459
|
+
lines.push(`export declare const TYPERT_REMOTE: TypertRemoteInfo<${this.remoteMethodTypes(packageModel)}>`);
|
|
3012
3460
|
lines.push("export default TYPERT_REMOTE");
|
|
3013
3461
|
lines.push("//# sourceMappingURL=typert.remote-client.d.ts.map");
|
|
3014
3462
|
return {
|
|
@@ -3075,6 +3523,27 @@ function uniqueNamespaces(invocations) {
|
|
|
3075
3523
|
function remoteNamespaceInterface(namespace) {
|
|
3076
3524
|
return `TypertRemoteNamespace$${Buffer.from(namespace, "utf8").toString("hex")}`;
|
|
3077
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
|
+
}
|
|
3078
3547
|
var ResultEncoderEmitter = class {
|
|
3079
3548
|
renderer;
|
|
3080
3549
|
names = /* @__PURE__ */ new Map();
|
|
@@ -3173,20 +3642,21 @@ var SchemaEmitter = class {
|
|
|
3173
3642
|
renderer;
|
|
3174
3643
|
schemas;
|
|
3175
3644
|
boundaries;
|
|
3645
|
+
options;
|
|
3176
3646
|
names = /* @__PURE__ */ new Map();
|
|
3177
3647
|
boundaryNames = /* @__PURE__ */ new Map();
|
|
3178
3648
|
declarations;
|
|
3179
|
-
constructor(renderer, schemas, boundaries) {
|
|
3649
|
+
constructor(renderer, schemas, boundaries, options = {}, identifiers = /* @__PURE__ */ new Set()) {
|
|
3180
3650
|
this.renderer = renderer;
|
|
3181
3651
|
this.schemas = schemas;
|
|
3182
3652
|
this.boundaries = boundaries;
|
|
3653
|
+
this.options = options;
|
|
3183
3654
|
const declarations = /* @__PURE__ */ new Map();
|
|
3184
3655
|
for (const schema of schemas) for (const declaration of renderer.declarationClosureForTypes([schema.type])) declarations.set(declaration.id, declaration);
|
|
3185
3656
|
for (const boundary of boundaries) for (const declaration of renderer.declarationClosureForTypes([boundary.type])) declarations.set(declaration.id, declaration);
|
|
3186
3657
|
this.declarations = renderer.graph.declarations.filter((declaration) => declarations.has(declaration.id));
|
|
3187
|
-
const identifiers = /* @__PURE__ */ new Set();
|
|
3188
3658
|
for (const declaration of this.declarations) {
|
|
3189
|
-
const base = `${safeIdentifier(declaration.name)}$schema`;
|
|
3659
|
+
const base = `${options.closedObjects ? "closed$" : ""}${safeIdentifier(declaration.name)}$schema`;
|
|
3190
3660
|
let name = base;
|
|
3191
3661
|
let suffix = 2;
|
|
3192
3662
|
while (identifiers.has(name)) name = `${base}${String(suffix++)}`;
|
|
@@ -3210,7 +3680,7 @@ var SchemaEmitter = class {
|
|
|
3210
3680
|
const encode = encoder.reference(boundary.type);
|
|
3211
3681
|
if (encode !== void 0) encoders.set(boundary.key, encode);
|
|
3212
3682
|
const name = this.boundaryName(boundary.key);
|
|
3213
|
-
definitions.push(
|
|
3683
|
+
definitions.push(this.cachedFactory(name, this.typeSchema(boundary.type)));
|
|
3214
3684
|
}
|
|
3215
3685
|
definitions.push(...encoder.definitions());
|
|
3216
3686
|
return {
|
|
@@ -3226,11 +3696,14 @@ var SchemaEmitter = class {
|
|
|
3226
3696
|
}
|
|
3227
3697
|
declarationDefinition(declaration) {
|
|
3228
3698
|
const name = this.schemaName(declaration.id);
|
|
3229
|
-
if (declaration.typeParameters.length === 0) return
|
|
3699
|
+
if (declaration.typeParameters.length === 0) return this.cachedFactory(name, this.declarationSchema(declaration, /* @__PURE__ */ new Map()));
|
|
3230
3700
|
const parameters = declaration.typeParameters.map((parameter, index) => [`type${String(index)}$schema`, parameter.id]);
|
|
3231
3701
|
const substitutions = new Map(parameters.map(([schema, id]) => [id, schema]));
|
|
3232
3702
|
return `const ${name} = (${parameters.map(([schema]) => schema).join(", ")}) => ${this.declarationSchema(declaration, substitutions)}`;
|
|
3233
3703
|
}
|
|
3704
|
+
cachedFactory(name, expression) {
|
|
3705
|
+
return `let ${name}$value\nconst ${name} = () => (${name}$value ??= ${expression})`;
|
|
3706
|
+
}
|
|
3234
3707
|
declarationSchema(declaration, substitutions) {
|
|
3235
3708
|
if (declaration.kind === "enum") this.fail(declaration.name, "enum declarations have no Zod projection");
|
|
3236
3709
|
if (declaration.kind === "alias") {
|
|
@@ -3243,6 +3716,7 @@ var SchemaEmitter = class {
|
|
|
3243
3716
|
}
|
|
3244
3717
|
typeSchema(id, substitutions = /* @__PURE__ */ new Map()) {
|
|
3245
3718
|
const node = this.renderer.node(id);
|
|
3719
|
+
if (node.json) return "z.json()";
|
|
3246
3720
|
switch (node.kind) {
|
|
3247
3721
|
case "keyword": return this.keywordSchema(node.name);
|
|
3248
3722
|
case "literal": return `z.literal(${node.text})`;
|
|
@@ -3340,6 +3814,8 @@ var SchemaEmitter = class {
|
|
|
3340
3814
|
objectSchema(members, subject, substitutions) {
|
|
3341
3815
|
const properties = [];
|
|
3342
3816
|
const indices = [];
|
|
3817
|
+
let stringIndexValue;
|
|
3818
|
+
let stringIndexType;
|
|
3343
3819
|
let symbolMembers = 0;
|
|
3344
3820
|
for (const member of members) {
|
|
3345
3821
|
if (member.static || member.visibility !== "public") continue;
|
|
@@ -3351,22 +3827,53 @@ var SchemaEmitter = class {
|
|
|
3351
3827
|
if (member.kind === "index") {
|
|
3352
3828
|
const parameter = member.signature.parameters[0];
|
|
3353
3829
|
if (member.signature.parameters.length !== 1 || parameter === void 0) this.fail(subject, "index signature must have exactly one key parameter");
|
|
3354
|
-
|
|
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));
|
|
3355
3837
|
continue;
|
|
3356
3838
|
}
|
|
3357
3839
|
if (member.kind !== "property") this.fail(subject, `${member.kind} member ${member.name} is not data-schema projectable`);
|
|
3358
|
-
|
|
3359
|
-
const property = this.describe(this.optional(schema, member.optional), member);
|
|
3360
|
-
properties.push(`${quote$1(member.jsonName ?? member.name)}: ${property}`);
|
|
3840
|
+
properties.push(member);
|
|
3361
3841
|
}
|
|
3362
3842
|
if (indices.length > 1) this.fail(subject, "object type has more than one JSON index signature");
|
|
3363
3843
|
if (properties.length === 0 && indices.length === 0 && symbolMembers > 0) return "z.unknown()";
|
|
3364
|
-
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`}})`;
|
|
3365
3850
|
const index = indices[0];
|
|
3366
3851
|
if (index === void 0) return object;
|
|
3367
3852
|
if (properties.length === 0) return index;
|
|
3853
|
+
if (this.options.closedObjects && stringIndexValue !== void 0) return `${object}.catchall(${stringIndexValue})`;
|
|
3368
3854
|
return `z.intersection(${object}, ${index})`;
|
|
3369
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
|
+
}
|
|
3370
3877
|
exportSchemaName(model) {
|
|
3371
3878
|
const name = this.schemaName(model.symbol);
|
|
3372
3879
|
if (this.renderer.declaration(model.symbol).typeParameters.length > 0) this.fail(model.export.name, "generic schema exports require a concrete declaration");
|
|
@@ -3434,6 +3941,11 @@ function invocationBoundaryRoots(invocations) {
|
|
|
3434
3941
|
key: parameterBoundaryKey(invocation, index),
|
|
3435
3942
|
type: parameter.boundary.codecType
|
|
3436
3943
|
});
|
|
3944
|
+
result.push({
|
|
3945
|
+
key: parameterBoundaryKey(invocation, index, true),
|
|
3946
|
+
type: parameter.boundary.codecType,
|
|
3947
|
+
closedObjects: true
|
|
3948
|
+
});
|
|
3437
3949
|
});
|
|
3438
3950
|
if (invocation.uplink !== void 0) result.push({
|
|
3439
3951
|
key: uplinkBoundaryKey(invocation),
|
|
@@ -3452,8 +3964,8 @@ function contextBoundaryKey(invocation) {
|
|
|
3452
3964
|
function uplinkBoundaryKey(invocation) {
|
|
3453
3965
|
return `${invocation.id}:uplink`;
|
|
3454
3966
|
}
|
|
3455
|
-
function parameterBoundaryKey(invocation, index) {
|
|
3456
|
-
return `${invocation.id}:parameter:${String(index)}`;
|
|
3967
|
+
function parameterBoundaryKey(invocation, index, strict = false) {
|
|
3968
|
+
return `${invocation.id}:parameter:${String(index)}${strict ? ":strict" : ""}`;
|
|
3457
3969
|
}
|
|
3458
3970
|
function resultBoundaryKey(invocation) {
|
|
3459
3971
|
return `${invocation.id}:result`;
|
|
@@ -3474,12 +3986,14 @@ function containsBytes(renderer, id, nested = true, visited = /* @__PURE__ */ ne
|
|
|
3474
3986
|
if (node.kind === "object" && node.members.some((member) => containsBytes(renderer, member.kind === "property" ? member.type : member.signature.returns, nested, visited))) return true;
|
|
3475
3987
|
return childTypeNodeIds(node).some((child) => containsBytes(renderer, child, nested, visited));
|
|
3476
3988
|
}
|
|
3477
|
-
function strictCodec(boundary, schema, encoder) {
|
|
3989
|
+
function strictCodec(boundary, schema, encoder, projection, description) {
|
|
3478
3990
|
return [
|
|
3479
3991
|
"{",
|
|
3480
3992
|
" mode: 'strict',",
|
|
3481
3993
|
` typeSymbol: ${quote$1(boundary.typeSymbol)},`,
|
|
3482
3994
|
` create: ${schema},`,
|
|
3995
|
+
...description === void 0 ? [] : [` description: ${quote$1(description)},`],
|
|
3996
|
+
...projection === void 0 ? [] : [` ${projection},`],
|
|
3483
3997
|
...encoder === void 0 ? [] : [` decode: value => ${schema}().parse(value),`, ` encode: (value, writeBytes) => ${encoder}(value, writeBytes, [], new Map()),`],
|
|
3484
3998
|
"}"
|
|
3485
3999
|
].join("\n");
|
|
@@ -3506,7 +4020,8 @@ function allocateRemoteImportNames(imports) {
|
|
|
3506
4020
|
"RemoteDecoded",
|
|
3507
4021
|
"RemoteResult",
|
|
3508
4022
|
"RemoteStreamHandle",
|
|
3509
|
-
"
|
|
4023
|
+
"TypertRemoteInfo",
|
|
4024
|
+
"TypertRemoteMethod",
|
|
3510
4025
|
"TYPERT_REMOTE"
|
|
3511
4026
|
]);
|
|
3512
4027
|
const names = /* @__PURE__ */ new Map();
|
|
@@ -4274,34 +4789,38 @@ var WorkspaceTypertGenerator = class {
|
|
|
4274
4789
|
* Find public package faces that contribute Cordis services/events or
|
|
4275
4790
|
* explicitly tagged Typert roots.
|
|
4276
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.
|
|
4277
4793
|
* @returns discovered packages in stable package-name order.
|
|
4278
4794
|
*/
|
|
4279
|
-
discover(faces) {
|
|
4795
|
+
discover(faces, includeManifest) {
|
|
4280
4796
|
return new WorkspaceAnalyzer({
|
|
4281
4797
|
root: this.root,
|
|
4282
4798
|
caches: this.caches,
|
|
4283
4799
|
...faces === void 0 ? {} : { faces }
|
|
4284
|
-
}).discoverPackages();
|
|
4800
|
+
}).discoverPackages(includeManifest);
|
|
4285
4801
|
}
|
|
4286
4802
|
/**
|
|
4287
4803
|
* Generate all discovered contributors, or an explicit package subset.
|
|
4288
|
-
* @param packages - optional exact package names
|
|
4804
|
+
* @param packages - optional exact package names to emit; every pass includes the same face-wide declaration merges.
|
|
4289
4805
|
* @param faces - optional independent program faces to analyze.
|
|
4290
4806
|
* @returns one artifact per package face.
|
|
4291
4807
|
*/
|
|
4292
4808
|
generate(packages, faces) {
|
|
4293
|
-
const
|
|
4809
|
+
const contributors = this.discover(faces).map((candidate) => candidate.package);
|
|
4810
|
+
const selected = new Set(packages ?? contributors);
|
|
4294
4811
|
const workspace = new WorkspaceAnalyzer({
|
|
4295
4812
|
root: this.root,
|
|
4296
|
-
packages:
|
|
4813
|
+
packages: contributors,
|
|
4297
4814
|
caches: this.caches,
|
|
4298
4815
|
...faces === void 0 ? {} : { faces },
|
|
4299
|
-
...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 }
|
|
4300
4818
|
}).analyze();
|
|
4301
4819
|
const artifacts = [];
|
|
4302
4820
|
for (const face of workspace.faces) {
|
|
4303
4821
|
const emitter = new FaceModelEmitter(face);
|
|
4304
4822
|
for (const packageModel of face.packages) {
|
|
4823
|
+
if (!selected.has(packageModel.name)) continue;
|
|
4305
4824
|
const artifact = {
|
|
4306
4825
|
...emitter.emit(packageModel.name),
|
|
4307
4826
|
packageRoot: packageModel.root
|
|
@@ -4312,6 +4831,16 @@ var WorkspaceTypertGenerator = class {
|
|
|
4312
4831
|
}
|
|
4313
4832
|
return artifacts;
|
|
4314
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
|
+
}
|
|
4315
4844
|
validateExport(artifact) {
|
|
4316
4845
|
const manifestPath = resolve(this.root, artifact.packageRoot, "package.json");
|
|
4317
4846
|
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
@@ -4338,10 +4867,90 @@ var WorkspaceTypertGenerator = class {
|
|
|
4338
4867
|
for (const file of remoteFiles) if (!files.includes(file)) throw new TypertAnalysisError(`typert(host): ${artifact.package} package files must include ${file}`);
|
|
4339
4868
|
}
|
|
4340
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
|
+
}
|
|
4341
4898
|
function sameExport(actual, expected) {
|
|
4342
4899
|
if (actual === null || typeof actual !== "object" || Array.isArray(actual)) return false;
|
|
4343
4900
|
const value = actual;
|
|
4344
4901
|
return value.types === expected.types && value.default === expected.default;
|
|
4345
4902
|
}
|
|
4346
4903
|
//#endregion
|
|
4347
|
-
|
|
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 };
|