@deepseek-ai/dsh-typert-generator 0.0.1-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +28 -0
- package/README.i18n.yaml +6 -0
- package/README.md +41 -0
- package/README.zh.md +41 -0
- package/lib/index.js +3922 -0
- package/lib/invariant.js +24 -0
- package/lib/types/analyzer.d.ts +143 -0
- package/lib/types/analyzer.js +2723 -0
- package/lib/types/cordis-catalog.d.ts +148 -0
- package/lib/types/cordis-catalog.js +695 -0
- package/lib/types/emitter.d.ts +55 -0
- package/lib/types/emitter.js +745 -0
- package/lib/types/index.d.ts +16 -0
- package/lib/types/index.js +12 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/invariant.js +23 -0
- package/lib/types/model.d.ts +409 -0
- package/lib/types/model.js +48 -0
- package/lib/types/renderer.d.ts +90 -0
- package/lib/types/renderer.js +351 -0
- package/lib/types/tsdown-plugin.d.ts +34 -0
- package/lib/types/tsdown-plugin.js +136 -0
- package/lib/types/workspace.d.ts +36 -0
- package/lib/types/workspace.js +114 -0
- package/package.json +54 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,3922 @@
|
|
|
1
|
+
import { existsSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, extname, join, posix, relative, resolve, sep } from "node:path";
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
import { Buffer } from "node:buffer";
|
|
5
|
+
import { GenMapping, addMapping, toEncodedMap } from "@jridgewell/gen-mapping";
|
|
6
|
+
//#region lib/types/analyzer.js
|
|
7
|
+
/**
|
|
8
|
+
* TypeScript project analyzer for the compiler-independent Typert model.
|
|
9
|
+
* Programs, symbols, and syntax nodes remain extraction-only implementation
|
|
10
|
+
* details; callers receive only the model declared in {@link ./model.ts}.
|
|
11
|
+
* @module @deepseek-ai/dsh-typert-generator/analyzer
|
|
12
|
+
*/
|
|
13
|
+
/** Analysis failure with a source-oriented diagnostic. */
|
|
14
|
+
var TypertAnalysisError = class extends Error {
|
|
15
|
+
name = "TypertAnalysisError";
|
|
16
|
+
};
|
|
17
|
+
var SourceEditQueued = class extends Error {};
|
|
18
|
+
const EMPTY_DOCUMENTATION = { tags: [] };
|
|
19
|
+
/**
|
|
20
|
+
* Process-wide parse cache for the bundled TypeScript default libraries.
|
|
21
|
+
* `typescript/lib/lib.*.d.ts` content is immutable for the process lifetime,
|
|
22
|
+
* so parses are shared across every {@link WorkspaceCaches} instance; the key
|
|
23
|
+
* carries the parse-affecting settings, keeping reuse exact.
|
|
24
|
+
*/
|
|
25
|
+
const defaultLibraryParses = /* @__PURE__ */ new Map();
|
|
26
|
+
function defaultLibraryKey(fileName, languageVersionOrOptions) {
|
|
27
|
+
const options = typeof languageVersionOrOptions === "object" ? languageVersionOrOptions : { languageVersion: languageVersionOrOptions };
|
|
28
|
+
return [
|
|
29
|
+
fileName,
|
|
30
|
+
String(options.languageVersion),
|
|
31
|
+
String(options.impliedNodeFormat ?? ""),
|
|
32
|
+
String(options.jsDocParsingMode ?? "")
|
|
33
|
+
].join("\0");
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Shared memo over one immutable workspace snapshot. Passing one instance to
|
|
37
|
+
* several analyzers (the batched and write-mode children reuse their parent's
|
|
38
|
+
* automatically) reuses parsed tsconfigs, the registration inventory, and
|
|
39
|
+
* per-face compiler hosts whose parsed and bound source files and module
|
|
40
|
+
* resolutions carry across programs. Callers that mutate workspace files
|
|
41
|
+
* between analyses must start from a fresh instance; write-mode source edits
|
|
42
|
+
* invalidate themselves through {@link invalidate}.
|
|
43
|
+
*/
|
|
44
|
+
var WorkspaceCaches = class {
|
|
45
|
+
/** Parsed tsconfig files by absolute config path. */
|
|
46
|
+
configs = /* @__PURE__ */ new Map();
|
|
47
|
+
/** Registration inventories keyed by root and aggregate config paths. */
|
|
48
|
+
registrations = /* @__PURE__ */ new Map();
|
|
49
|
+
hosts = /* @__PURE__ */ new Map();
|
|
50
|
+
/**
|
|
51
|
+
* Parse one tsconfig once per workspace snapshot.
|
|
52
|
+
* @param path - absolute config path.
|
|
53
|
+
* @returns the memoized parse result.
|
|
54
|
+
*/
|
|
55
|
+
config(path) {
|
|
56
|
+
let parsed = this.configs.get(path);
|
|
57
|
+
if (parsed === void 0) {
|
|
58
|
+
parsed = parseConfig(path);
|
|
59
|
+
this.configs.set(path, parsed);
|
|
60
|
+
}
|
|
61
|
+
return parsed;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Return the shared compiler host for one face. Every program of one face
|
|
65
|
+
* is built from the same aggregate compiler options (the first call wins),
|
|
66
|
+
* so parsed source files, binder state, and module resolutions are safe to
|
|
67
|
+
* reuse across the face's batched programs.
|
|
68
|
+
* @param face - the face whose programs share this host.
|
|
69
|
+
* @param options - the face's effective compiler options.
|
|
70
|
+
* @returns a compiler host with source-file and module-resolution caches.
|
|
71
|
+
*/
|
|
72
|
+
programHost(face, options) {
|
|
73
|
+
let entry = this.hosts.get(face);
|
|
74
|
+
if (entry === void 0) {
|
|
75
|
+
const host = ts.createCompilerHost(options);
|
|
76
|
+
const files = /* @__PURE__ */ new Map();
|
|
77
|
+
const resolutionCache = ts.createModuleResolutionCache(host.getCurrentDirectory(), (fileName) => host.getCanonicalFileName(fileName), options);
|
|
78
|
+
const base = host.getSourceFile.bind(host);
|
|
79
|
+
host.getSourceFile = (fileName, languageVersionOrOptions, onError) => {
|
|
80
|
+
if (isStandardLibraryFile(fileName)) {
|
|
81
|
+
const key = defaultLibraryKey(fileName, languageVersionOrOptions);
|
|
82
|
+
if (!defaultLibraryParses.has(key)) defaultLibraryParses.set(key, base(fileName, languageVersionOrOptions, onError));
|
|
83
|
+
return defaultLibraryParses.get(key);
|
|
84
|
+
}
|
|
85
|
+
if (!files.has(fileName)) files.set(fileName, base(fileName, languageVersionOrOptions, onError));
|
|
86
|
+
return files.get(fileName);
|
|
87
|
+
};
|
|
88
|
+
host.getModuleResolutionCache = () => resolutionCache;
|
|
89
|
+
entry = {
|
|
90
|
+
host,
|
|
91
|
+
files
|
|
92
|
+
};
|
|
93
|
+
this.hosts.set(face, entry);
|
|
94
|
+
}
|
|
95
|
+
return entry.host;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Drop cached parses of one edited source file so the next analysis reads
|
|
99
|
+
* the written content.
|
|
100
|
+
* @param file - path of the edited file.
|
|
101
|
+
*/
|
|
102
|
+
invalidate(file) {
|
|
103
|
+
const target = realPath(file);
|
|
104
|
+
for (const { files } of this.hosts.values()) for (const key of [...files.keys()]) if (realPath(key) === target) files.delete(key);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
/** Analyze host and client as independent TypeScript programs. */
|
|
108
|
+
var WorkspaceAnalyzer = class WorkspaceAnalyzer {
|
|
109
|
+
options;
|
|
110
|
+
queuedEdit;
|
|
111
|
+
crossFaceLinks = /* @__PURE__ */ new Map();
|
|
112
|
+
checkedProjects = /* @__PURE__ */ new Set();
|
|
113
|
+
registrations = [];
|
|
114
|
+
caches;
|
|
115
|
+
constructor(options) {
|
|
116
|
+
this.options = {
|
|
117
|
+
root: realPath(options.root),
|
|
118
|
+
hostConfig: options.hostConfig ?? "tsconfig.host.json",
|
|
119
|
+
clientConfig: options.clientConfig ?? "tsconfig.client.json",
|
|
120
|
+
faces: options.faces ?? ["host", "client"],
|
|
121
|
+
checkDiagnostics: options.checkDiagnostics ?? true,
|
|
122
|
+
mode: options.mode ?? "check",
|
|
123
|
+
...options.packages === void 0 ? {} : { packages: options.packages }
|
|
124
|
+
};
|
|
125
|
+
this.caches = options.caches ?? new WorkspaceCaches();
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Build the workspace model. Write mode applies inferred annotations and then
|
|
129
|
+
* returns a fresh check-mode analysis of the edited projects.
|
|
130
|
+
* @returns the independent face models and their explicit cross-face links.
|
|
131
|
+
*/
|
|
132
|
+
analyze() {
|
|
133
|
+
this.registrations = this.loadRegistrations();
|
|
134
|
+
const selected = this.options.packages === void 0 ? void 0 : new Set(this.options.packages);
|
|
135
|
+
const faces = [];
|
|
136
|
+
try {
|
|
137
|
+
for (const face of this.options.faces) {
|
|
138
|
+
const registrations = this.registrations.filter((registration) => registration.face === face && (selected === void 0 || selected.has(registration.name)));
|
|
139
|
+
if (registrations.length === 0) continue;
|
|
140
|
+
if (this.options.checkDiagnostics) for (const registration of registrations) this.checkProject(registration);
|
|
141
|
+
const aggregatePath = resolve(this.options.root, face === "host" ? this.options.hostConfig : this.options.clientConfig);
|
|
142
|
+
const aggregate = this.caches.config(aggregatePath);
|
|
143
|
+
const rootNames = [...new Set(registrations.flatMap((registration) => registration.config.parsed.fileNames))];
|
|
144
|
+
const options = {
|
|
145
|
+
...aggregate.parsed.options,
|
|
146
|
+
composite: false,
|
|
147
|
+
incremental: false,
|
|
148
|
+
noEmit: true
|
|
149
|
+
};
|
|
150
|
+
const program = ts.createProgram({
|
|
151
|
+
rootNames,
|
|
152
|
+
options,
|
|
153
|
+
host: this.caches.programHost(face, options)
|
|
154
|
+
});
|
|
155
|
+
faces.push(new FaceAnalyzer({
|
|
156
|
+
root: this.options.root,
|
|
157
|
+
face,
|
|
158
|
+
program,
|
|
159
|
+
registrations,
|
|
160
|
+
allRegistrations: this.registrations,
|
|
161
|
+
mode: this.options.mode,
|
|
162
|
+
queueEdit: (edit) => {
|
|
163
|
+
this.queueEdit(edit);
|
|
164
|
+
},
|
|
165
|
+
crossFaceLinks: this.crossFaceLinks
|
|
166
|
+
}).analyze());
|
|
167
|
+
}
|
|
168
|
+
} catch (error) {
|
|
169
|
+
if (!(error instanceof SourceEditQueued) || this.options.mode !== "write" || this.queuedEdit === void 0) throw error;
|
|
170
|
+
}
|
|
171
|
+
if (this.queuedEdit !== void 0) {
|
|
172
|
+
this.applyEdit(this.queuedEdit);
|
|
173
|
+
return new WorkspaceAnalyzer({
|
|
174
|
+
...this.options,
|
|
175
|
+
caches: this.caches,
|
|
176
|
+
mode: "write"
|
|
177
|
+
}).analyze();
|
|
178
|
+
}
|
|
179
|
+
if (this.options.mode === "write") return new WorkspaceAnalyzer({
|
|
180
|
+
...this.options,
|
|
181
|
+
caches: this.caches,
|
|
182
|
+
mode: "check"
|
|
183
|
+
}).analyze();
|
|
184
|
+
return {
|
|
185
|
+
faces,
|
|
186
|
+
crossFaceLinks: [...this.crossFaceLinks.values()].sort(compareCrossFaceLinks)
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Analyze an explicit package selection through bounded compiler programs.
|
|
191
|
+
* The resulting model is identical in shape to {@link analyze}; stable graph
|
|
192
|
+
* ids let repeated dependency declarations merge without flattening types.
|
|
193
|
+
* @param batchSize - maximum selected packages in one face program.
|
|
194
|
+
* @returns one merged workspace model.
|
|
195
|
+
*/
|
|
196
|
+
analyzeInBatches(batchSize = 8) {
|
|
197
|
+
if (this.options.packages === void 0) throw new TypertAnalysisError("typert: batched analysis requires an explicit package selection");
|
|
198
|
+
if (!Number.isInteger(batchSize) || batchSize < 1) throw new TypertAnalysisError(`typert: batch size must be a positive integer, received ${String(batchSize)}`);
|
|
199
|
+
const batches = [];
|
|
200
|
+
for (let index = 0; index < this.options.packages.length; index += batchSize) batches.push(new WorkspaceAnalyzer({
|
|
201
|
+
...this.options,
|
|
202
|
+
caches: this.caches,
|
|
203
|
+
packages: this.options.packages.slice(index, index + batchSize)
|
|
204
|
+
}).analyze());
|
|
205
|
+
return mergeWorkspaceModels(batches);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Discover package faces from public-export-reachable Cordis augmentations
|
|
209
|
+
* and explicit `@typert` roots without constructing a type-checker program.
|
|
210
|
+
* @returns contributors grouped by package with deterministic face order.
|
|
211
|
+
*/
|
|
212
|
+
discoverPackages() {
|
|
213
|
+
const registrations = this.loadRegistrations().filter((registration) => this.options.faces.includes(registration.face)).filter((registration) => this.registrationHasSurface(registration));
|
|
214
|
+
const packages = /* @__PURE__ */ new Map();
|
|
215
|
+
for (const registration of registrations) {
|
|
216
|
+
const current = packages.get(registration.name) ?? {
|
|
217
|
+
root: slash(relative(this.options.root, registration.root)),
|
|
218
|
+
faces: /* @__PURE__ */ new Set()
|
|
219
|
+
};
|
|
220
|
+
current.faces.add(registration.face);
|
|
221
|
+
packages.set(registration.name, current);
|
|
222
|
+
}
|
|
223
|
+
return [...packages].map(([packageName, value]) => ({
|
|
224
|
+
package: packageName,
|
|
225
|
+
root: value.root,
|
|
226
|
+
faces: [...value.faces].sort()
|
|
227
|
+
})).sort((left, right) => left.package.localeCompare(right.package));
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Index top-level exported type declarations without promoting them to graph
|
|
231
|
+
* roots. Consumers use this lexical index for ambiguity checks while all
|
|
232
|
+
* semantic traversal continues through {@link TypeGraph}.
|
|
233
|
+
* @returns declarations from the selected faces and package projects.
|
|
234
|
+
*/
|
|
235
|
+
indexSourceDeclarations() {
|
|
236
|
+
const selected = this.options.packages === void 0 ? void 0 : new Set(this.options.packages);
|
|
237
|
+
const declarations = [];
|
|
238
|
+
for (const registration of this.loadRegistrations()) {
|
|
239
|
+
if (!this.options.faces.includes(registration.face) || selected !== void 0 && !selected.has(registration.name)) continue;
|
|
240
|
+
for (const file of registration.config.parsed.fileNames) {
|
|
241
|
+
const relativeFile = slash(relative(this.options.root, file));
|
|
242
|
+
if (!existsSync(file) || !isWithin(realPath(file), join(registration.root, "src")) || !/\.(?:cts|mts|ts)$/.test(file)) continue;
|
|
243
|
+
const sourceFile = ts.createSourceFile(file, readFileSync(file, "utf8"), ts.ScriptTarget.Latest, true);
|
|
244
|
+
for (const statement of sourceFile.statements) {
|
|
245
|
+
if (!isTypeDeclaration(statement) || statement.name === void 0 || !hasModifier(statement, ts.SyntaxKind.ExportKeyword)) continue;
|
|
246
|
+
const position = sourceFile.getLineAndCharacterOfPosition(statement.getStart(sourceFile));
|
|
247
|
+
declarations.push({
|
|
248
|
+
face: registration.face,
|
|
249
|
+
package: registration.name,
|
|
250
|
+
name: statement.name.text,
|
|
251
|
+
kind: ts.isClassDeclaration(statement) ? "class" : ts.isInterfaceDeclaration(statement) ? "interface" : ts.isTypeAliasDeclaration(statement) ? "alias" : "enum",
|
|
252
|
+
location: {
|
|
253
|
+
file: relativeFile,
|
|
254
|
+
line: position.line + 1,
|
|
255
|
+
column: position.character + 1
|
|
256
|
+
},
|
|
257
|
+
text: declarationText(statement)
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return uniqueBy(declarations, (declaration) => `${declaration.face}\0${declaration.location.file}\0${String(declaration.location.line)}\0${declaration.name}`).sort((left, right) => left.face.localeCompare(right.face) || left.location.file.localeCompare(right.location.file) || left.location.line - right.location.line);
|
|
263
|
+
}
|
|
264
|
+
loadRegistrations() {
|
|
265
|
+
const inventoryKey = `${this.options.root}\0${this.options.hostConfig}\0${this.options.clientConfig}`;
|
|
266
|
+
const cached = this.caches.registrations.get(inventoryKey);
|
|
267
|
+
if (cached !== void 0) return cached;
|
|
268
|
+
const registrations = [];
|
|
269
|
+
for (const face of ["host", "client"]) {
|
|
270
|
+
const aggregatePath = resolve(this.options.root, face === "host" ? this.options.hostConfig : this.options.clientConfig);
|
|
271
|
+
if (!existsSync(aggregatePath)) continue;
|
|
272
|
+
const aggregate = this.caches.config(aggregatePath);
|
|
273
|
+
for (const reference of aggregate.parsed.projectReferences ?? []) {
|
|
274
|
+
const configPath = projectConfigPath(reference.path);
|
|
275
|
+
const packageRoot = dirname(configPath);
|
|
276
|
+
if (!isWithin(realPath(packageRoot), join(this.options.root, "packages"))) continue;
|
|
277
|
+
const manifestPath = join(packageRoot, "package.json");
|
|
278
|
+
if (!existsSync(manifestPath)) continue;
|
|
279
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
280
|
+
if (typeof manifest.name !== "string") continue;
|
|
281
|
+
const registration = {
|
|
282
|
+
face,
|
|
283
|
+
name: manifest.name,
|
|
284
|
+
root: realPath(packageRoot),
|
|
285
|
+
config: this.caches.config(configPath),
|
|
286
|
+
manifest
|
|
287
|
+
};
|
|
288
|
+
if (!isDualFacePackage(manifest)) registrations.push(registration);
|
|
289
|
+
else if (configPath === join(packageRoot, "tsconfig.json")) registrations.push({
|
|
290
|
+
...registration,
|
|
291
|
+
face: "host",
|
|
292
|
+
exportSubpaths: hostExportSubpaths(manifest)
|
|
293
|
+
}, {
|
|
294
|
+
...registration,
|
|
295
|
+
face: "client",
|
|
296
|
+
exportSubpaths: clientExportSubpaths(manifest)
|
|
297
|
+
});
|
|
298
|
+
else registrations.push({
|
|
299
|
+
...registration,
|
|
300
|
+
exportSubpaths: face === "host" ? hostExportSubpaths(manifest) : clientExportSubpaths(manifest)
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const inventory = uniqueBy(registrations, (registration) => `${registration.face}\0${registration.name}`).sort((left, right) => left.face.localeCompare(right.face) || left.name.localeCompare(right.name));
|
|
305
|
+
this.caches.registrations.set(inventoryKey, inventory);
|
|
306
|
+
return inventory;
|
|
307
|
+
}
|
|
308
|
+
entrySourcePaths(registration) {
|
|
309
|
+
return packageExportTargets(registration.manifest).filter(([subpath, target]) => (registration.exportSubpaths === void 0 || registration.exportSubpaths.includes(subpath)) && !target.includes("*") && subpath !== "./package.json" && subpath !== "./typert" && subpath !== "./client/typert" && subpath !== "./remote" && !target.endsWith(".json")).map(([, target]) => sourcePathForExport(registration.root, target)).filter(existsSync);
|
|
310
|
+
}
|
|
311
|
+
registrationHasSurface(registration) {
|
|
312
|
+
const seen = /* @__PURE__ */ new Set();
|
|
313
|
+
const queue = this.entrySourcePaths(registration);
|
|
314
|
+
while (queue.length > 0) {
|
|
315
|
+
const file = realPath(queue.shift());
|
|
316
|
+
if (seen.has(file) || !isWithin(file, registration.root)) continue;
|
|
317
|
+
seen.add(file);
|
|
318
|
+
const source = readFileSync(file, "utf8");
|
|
319
|
+
if (sourceFileHasSurface(ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true))) return true;
|
|
320
|
+
for (const imported of ts.preProcessFile(source).importedFiles) {
|
|
321
|
+
const resolved = ts.resolveModuleName(imported.fileName, file, registration.config.parsed.options, ts.sys).resolvedModule;
|
|
322
|
+
if (resolved !== void 0 && isWithin(resolved.resolvedFileName, registration.root)) queue.push(resolved.resolvedFileName);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
checkProject(registration) {
|
|
328
|
+
if (this.checkedProjects.has(registration.config.path)) return;
|
|
329
|
+
this.checkedProjects.add(registration.config.path);
|
|
330
|
+
const program = ts.createProgram({
|
|
331
|
+
rootNames: registration.config.parsed.fileNames,
|
|
332
|
+
options: {
|
|
333
|
+
...registration.config.parsed.options,
|
|
334
|
+
composite: false,
|
|
335
|
+
incremental: false,
|
|
336
|
+
noEmit: true,
|
|
337
|
+
rootDir: this.options.root
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
const diagnostics = [...program.getSyntacticDiagnostics(), ...program.getSemanticDiagnostics()].filter((diagnostic) => diagnostic.file !== void 0 && diagnostic.start !== void 0 && isWithin(diagnostic.file.fileName, registration.root));
|
|
341
|
+
if (diagnostics.length === 0) return;
|
|
342
|
+
throw new TypertAnalysisError(diagnostics.map((diagnostic) => formatProgramDiagnostic(this.options.root, registration.face, diagnostic)).join("\n"));
|
|
343
|
+
}
|
|
344
|
+
queueEdit(edit) {
|
|
345
|
+
this.queuedEdit = edit;
|
|
346
|
+
}
|
|
347
|
+
applyEdit(edit) {
|
|
348
|
+
const source = readFileSync(edit.file, "utf8");
|
|
349
|
+
writeFileSync(edit.file, source.slice(0, edit.position) + edit.text + source.slice(edit.position));
|
|
350
|
+
this.caches.invalidate(edit.file);
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
var FaceAnalyzer = class {
|
|
354
|
+
root;
|
|
355
|
+
face;
|
|
356
|
+
program;
|
|
357
|
+
checker;
|
|
358
|
+
registrations;
|
|
359
|
+
allRegistrations;
|
|
360
|
+
mode;
|
|
361
|
+
queueEdit;
|
|
362
|
+
crossFaceLinks;
|
|
363
|
+
sourceFiles = /* @__PURE__ */ new Map();
|
|
364
|
+
declarations = /* @__PURE__ */ new Map();
|
|
365
|
+
declarationStates = /* @__PURE__ */ new Set();
|
|
366
|
+
nodes = /* @__PURE__ */ new Map();
|
|
367
|
+
exportsByPackage = /* @__PURE__ */ new Map();
|
|
368
|
+
nodeOrdinals = /* @__PURE__ */ new Map();
|
|
369
|
+
staticLookups;
|
|
370
|
+
staticContexts;
|
|
371
|
+
constructor(options) {
|
|
372
|
+
this.root = options.root;
|
|
373
|
+
this.face = options.face;
|
|
374
|
+
this.program = options.program;
|
|
375
|
+
this.checker = options.program.getTypeChecker();
|
|
376
|
+
this.registrations = options.registrations;
|
|
377
|
+
this.allRegistrations = options.allRegistrations;
|
|
378
|
+
this.mode = options.mode;
|
|
379
|
+
this.queueEdit = options.queueEdit;
|
|
380
|
+
this.crossFaceLinks = options.crossFaceLinks;
|
|
381
|
+
for (const sourceFile of this.program.getSourceFiles()) this.sourceFiles.set(realPath(sourceFile.fileName), sourceFile);
|
|
382
|
+
}
|
|
383
|
+
analyze() {
|
|
384
|
+
for (const registration of this.registrations) this.exportsByPackage.set(registration.name, this.collectExports(registration));
|
|
385
|
+
const packages = this.registrations.map((registration) => this.analyzePackage(registration)).filter(hasPackageSurface);
|
|
386
|
+
this.validateInvocationIdentity(packages);
|
|
387
|
+
return {
|
|
388
|
+
face: this.face,
|
|
389
|
+
packages,
|
|
390
|
+
graph: {
|
|
391
|
+
declarations: [...this.declarations.values()].sort((left, right) => left.id.localeCompare(right.id)),
|
|
392
|
+
nodes: [...this.nodes.values()].sort((left, right) => left.id.localeCompare(right.id))
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
analyzePackage(registration) {
|
|
397
|
+
const records = this.exportsByPackage.get(registration.name);
|
|
398
|
+
const reachable = this.reachableFiles(registration, records.map((record) => record.sourceFile));
|
|
399
|
+
const services = [];
|
|
400
|
+
const events = [];
|
|
401
|
+
for (const sourceFile of reachable) for (const statement of sourceFile.statements) {
|
|
402
|
+
if (!ts.isModuleDeclaration(statement) || !ts.isStringLiteral(statement.name) || statement.name.text !== "@deepseek-ai/cordis" || statement.body === void 0 || !ts.isModuleBlock(statement.body)) continue;
|
|
403
|
+
for (const member of statement.body.statements) {
|
|
404
|
+
if (!ts.isInterfaceDeclaration(member)) continue;
|
|
405
|
+
if (member.name.text === "Context") services.push(...this.collectServices(member, records));
|
|
406
|
+
else if (member.name.text === "Events") events.push(...this.collectEvents(member));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
const explicitServices = this.collectExplicitServices(records);
|
|
410
|
+
const objects = [];
|
|
411
|
+
const schemas = [];
|
|
412
|
+
const seenBusinessSymbols = /* @__PURE__ */ new Set();
|
|
413
|
+
for (const record of records) {
|
|
414
|
+
const declaration = record.declaration;
|
|
415
|
+
if (!isTypeDeclaration(declaration)) continue;
|
|
416
|
+
if (this.registrationForFile(declaration.getSourceFile().fileName) === void 0) continue;
|
|
417
|
+
const symbol = this.resolveSymbol(record.symbol);
|
|
418
|
+
const symbolId = this.symbolId(symbol);
|
|
419
|
+
if (seenBusinessSymbols.has(symbolId)) continue;
|
|
420
|
+
const mode = typertMode(declaration);
|
|
421
|
+
if (mode !== "object" && mode !== "schema") continue;
|
|
422
|
+
seenBusinessSymbols.add(symbolId);
|
|
423
|
+
this.ensureDeclaration(symbol, declaration);
|
|
424
|
+
const documentation = documentationOf(declaration);
|
|
425
|
+
if (mode === "object") objects.push({
|
|
426
|
+
...documentation,
|
|
427
|
+
export: record.model,
|
|
428
|
+
symbol: symbolId,
|
|
429
|
+
passing: "reference"
|
|
430
|
+
});
|
|
431
|
+
else schemas.push({
|
|
432
|
+
...documentation,
|
|
433
|
+
export: record.model,
|
|
434
|
+
symbol: symbolId,
|
|
435
|
+
type: this.referenceNode(symbol, declaration)
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
return {
|
|
439
|
+
name: registration.name,
|
|
440
|
+
root: slash(relative(this.root, registration.root)),
|
|
441
|
+
exports: records.map((record) => record.model).sort((left, right) => left.subpath.localeCompare(right.subpath) || left.name.localeCompare(right.name)),
|
|
442
|
+
services: uniqueBy([...explicitServices, ...services], (service) => service.key).sort((left, right) => left.key.localeCompare(right.key)),
|
|
443
|
+
events: uniqueBy(events, (event) => event.name).sort((left, right) => left.name.localeCompare(right.name)),
|
|
444
|
+
objects: objects.sort((left, right) => left.export.name.localeCompare(right.export.name)),
|
|
445
|
+
schemas: schemas.sort((left, right) => left.export.name.localeCompare(right.export.name)),
|
|
446
|
+
invocations: this.face === "host" ? this.collectInvocations(registration, reachable).sort((left, right) => left.id.localeCompare(right.id)) : []
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
collectExports(registration) {
|
|
450
|
+
const targets = packageExportTargets(registration.manifest).filter(([subpath]) => registration.exportSubpaths === void 0 || registration.exportSubpaths.includes(subpath));
|
|
451
|
+
const records = [];
|
|
452
|
+
for (const [subpath, target] of targets) {
|
|
453
|
+
if (target.includes("*") || subpath === "./package.json" || subpath === "./typert" || subpath === "./client/typert" || subpath === "./remote" || target.endsWith(".json") || target.endsWith(".yml") || target.endsWith(".yaml")) continue;
|
|
454
|
+
const sourcePath = sourcePathForExport(registration.root, target);
|
|
455
|
+
const sourceFile = this.sourceFiles.get(realPath(sourcePath));
|
|
456
|
+
if (sourceFile === void 0) throw new TypertAnalysisError(`typert(${this.face}): ${registration.name} export ${subpath} resolves to missing source ${sourcePath}`);
|
|
457
|
+
const moduleSymbol = this.checker.getSymbolAtLocation(sourceFile);
|
|
458
|
+
if (moduleSymbol === void 0) continue;
|
|
459
|
+
for (const exported of this.checker.getExportsOfModule(moduleSymbol)) {
|
|
460
|
+
const symbol = this.resolveSymbol(exported);
|
|
461
|
+
const declaration = preferredDeclaration(symbol);
|
|
462
|
+
const aliases = exported === symbol || exported.name === symbol.name ? [exported.name] : [exported.name, symbol.name];
|
|
463
|
+
records.push({
|
|
464
|
+
model: {
|
|
465
|
+
subpath,
|
|
466
|
+
name: exported.name,
|
|
467
|
+
symbol: this.symbolId(symbol),
|
|
468
|
+
aliases
|
|
469
|
+
},
|
|
470
|
+
symbol,
|
|
471
|
+
declaration,
|
|
472
|
+
sourceFile
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const unique = uniqueBy(records, (record) => `${record.model.subpath}\0${record.model.name}`);
|
|
477
|
+
this.collectCrossFaceReExports(registration, unique);
|
|
478
|
+
return unique;
|
|
479
|
+
}
|
|
480
|
+
collectCrossFaceReExports(registration, records) {
|
|
481
|
+
const publicSymbols = new Set(records.map((record) => record.symbol));
|
|
482
|
+
const entryFiles = uniqueBy(records, (record) => record.sourceFile.fileName).map((record) => record.sourceFile);
|
|
483
|
+
for (const sourceFile of this.reachableFiles(registration, entryFiles)) for (const statement of sourceFile.statements) {
|
|
484
|
+
if (!ts.isExportDeclaration(statement) || statement.moduleSpecifier === void 0 || !ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
485
|
+
const module = moduleIdentity(statement.moduleSpecifier.text);
|
|
486
|
+
if (module === void 0) continue;
|
|
487
|
+
const toFace = this.allRegistrations.find((candidate) => candidate.name === module.package && candidate.face !== this.face)?.face;
|
|
488
|
+
if (toFace === void 0) continue;
|
|
489
|
+
if (statement.exportClause !== void 0 && ts.isNamespaceExport(statement.exportClause)) {
|
|
490
|
+
const namespace = this.resolveSymbol(this.checker.getSymbolAtLocation(statement.exportClause.name));
|
|
491
|
+
if (publicSymbols.has(namespace)) this.fail(statement.exportClause, "cross-face namespace re-exports are not supported");
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
const exports = statement.exportClause === void 0 ? this.moduleExports(statement.moduleSpecifier).map((symbol) => ({
|
|
495
|
+
symbol: this.resolveSymbol(symbol),
|
|
496
|
+
requestedName: symbol.name,
|
|
497
|
+
site: statement
|
|
498
|
+
})) : statement.exportClause.elements.map((element) => ({
|
|
499
|
+
symbol: this.resolveSymbol(this.checker.getSymbolAtLocation(element.name)),
|
|
500
|
+
requestedName: element.propertyName?.text ?? element.name.text,
|
|
501
|
+
site: element
|
|
502
|
+
}));
|
|
503
|
+
for (const exported of exports) {
|
|
504
|
+
if (!publicSymbols.has(exported.symbol)) continue;
|
|
505
|
+
const name = this.packageExportName(module, exported.symbol, toFace, exported.requestedName);
|
|
506
|
+
if (name === void 0) this.fail(exported.site, `cross-face re-export ${exported.requestedName} is not exported by ${module.package} at ${module.subpath}`);
|
|
507
|
+
this.recordCrossFaceLink(registration.name, toFace, module, name);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
moduleExports(moduleSpecifier) {
|
|
512
|
+
/* v8 ignore next -- a semantically valid export declaration from a resolved module always has a module symbol. */
|
|
513
|
+
const moduleSymbol = this.checker.getSymbolAtLocation(moduleSpecifier);
|
|
514
|
+
return this.checker.getExportsOfModule(moduleSymbol);
|
|
515
|
+
}
|
|
516
|
+
reachableFiles(registration, entryFiles) {
|
|
517
|
+
const reachable = /* @__PURE__ */ new Map();
|
|
518
|
+
const queue = [...entryFiles];
|
|
519
|
+
while (queue.length > 0) {
|
|
520
|
+
const sourceFile = queue.shift();
|
|
521
|
+
const fileName = realPath(sourceFile.fileName);
|
|
522
|
+
if (reachable.has(fileName) || !isWithin(fileName, registration.root)) continue;
|
|
523
|
+
reachable.set(fileName, sourceFile);
|
|
524
|
+
for (const statement of sourceFile.statements) {
|
|
525
|
+
if (!ts.isImportDeclaration(statement) && !ts.isExportDeclaration(statement) || statement.moduleSpecifier === void 0 || !ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
526
|
+
const resolved = ts.resolveModuleName(statement.moduleSpecifier.text, sourceFile.fileName, this.program.getCompilerOptions(), ts.sys).resolvedModule;
|
|
527
|
+
if (resolved === void 0) continue;
|
|
528
|
+
const resolvedPath = realPath(resolved.resolvedFileName);
|
|
529
|
+
if (!isWithin(resolvedPath, registration.root)) continue;
|
|
530
|
+
queue.push(this.sourceFiles.get(resolvedPath));
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return [...reachable.values()].sort((left, right) => left.fileName.localeCompare(right.fileName));
|
|
534
|
+
}
|
|
535
|
+
collectServices(context, records) {
|
|
536
|
+
const bySymbol = /* @__PURE__ */ new Map();
|
|
537
|
+
for (const record of records) {
|
|
538
|
+
const id = this.symbolId(record.symbol);
|
|
539
|
+
const matches = bySymbol.get(id) ?? [];
|
|
540
|
+
matches.push(record);
|
|
541
|
+
bySymbol.set(id, matches);
|
|
542
|
+
}
|
|
543
|
+
const result = [];
|
|
544
|
+
for (const member of context.members) {
|
|
545
|
+
if (!ts.isPropertySignature(member) || member.type === void 0) continue;
|
|
546
|
+
const symbol = this.symbolAtType(member.type);
|
|
547
|
+
if (symbol === void 0) continue;
|
|
548
|
+
const symbolId = this.symbolId(symbol);
|
|
549
|
+
const exported = bySymbol.get(symbolId)?.find((record) => record.model.name === symbol.name) ?? bySymbol.get(symbolId)?.find((record) => record.model.name !== "default") ?? bySymbol.get(symbolId)?.[0];
|
|
550
|
+
if (exported === void 0) continue;
|
|
551
|
+
const declaration = preferredDeclaration(symbol);
|
|
552
|
+
if (declaration === void 0 || !ts.isClassDeclaration(declaration) && !ts.isInterfaceDeclaration(declaration)) this.fail(member, `service ${memberName(member.name)} does not resolve to an exported class or interface`);
|
|
553
|
+
const exposed = this.ensureDeclaration(symbol, declaration).members.filter(exposableMember).map((publicMember) => publicMember.id);
|
|
554
|
+
result.push({
|
|
555
|
+
...documentationOf(declaration),
|
|
556
|
+
key: memberName(member.name),
|
|
557
|
+
symbol: symbolId,
|
|
558
|
+
export: exported.model,
|
|
559
|
+
members: exposed,
|
|
560
|
+
location: this.location(member)
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
return result;
|
|
564
|
+
}
|
|
565
|
+
collectExplicitServices(records) {
|
|
566
|
+
const result = [];
|
|
567
|
+
const seen = /* @__PURE__ */ new Set();
|
|
568
|
+
for (const record of records) {
|
|
569
|
+
const tag = typertServiceTag(record.declaration);
|
|
570
|
+
if (tag === void 0) continue;
|
|
571
|
+
const words = (ts.getTextOfJSDocComment(tag.comment) ?? "").trim().split(/\s+/);
|
|
572
|
+
if (words.length !== 2 || !isRemoteSegment(words[1] ?? "")) this.fail(tag, "@typert service requires exactly one nonempty Cordis service key without \"/\"");
|
|
573
|
+
if (!ts.isClassDeclaration(record.declaration)) this.fail(record.declaration, "@typert service requires an exported class");
|
|
574
|
+
const symbol = this.resolveSymbol(record.symbol);
|
|
575
|
+
const symbolId = this.symbolId(symbol);
|
|
576
|
+
if (seen.has(symbolId)) continue;
|
|
577
|
+
seen.add(symbolId);
|
|
578
|
+
const model = this.ensureDeclaration(symbol, record.declaration);
|
|
579
|
+
result.push({
|
|
580
|
+
...documentationOf(record.declaration),
|
|
581
|
+
key: words[1],
|
|
582
|
+
symbol: symbolId,
|
|
583
|
+
export: record.model,
|
|
584
|
+
members: model.members.filter(exposableMember).map((member) => member.id),
|
|
585
|
+
location: this.location(record.declaration)
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
return result;
|
|
589
|
+
}
|
|
590
|
+
collectInvocations(registration, reachable) {
|
|
591
|
+
const result = [];
|
|
592
|
+
for (const sourceFile of reachable) for (const statement of sourceFile.statements) {
|
|
593
|
+
if (!ts.isClassDeclaration(statement)) continue;
|
|
594
|
+
const marked = statement.members.flatMap((member) => {
|
|
595
|
+
const invocation = this.remoteMarker(member);
|
|
596
|
+
if (invocation === void 0) return [];
|
|
597
|
+
if (!ts.isMethodDeclaration(member)) this.fail(member, "Remote decorators require a public instance method");
|
|
598
|
+
return [{
|
|
599
|
+
method: member,
|
|
600
|
+
invocation
|
|
601
|
+
}];
|
|
602
|
+
});
|
|
603
|
+
const first = marked[0];
|
|
604
|
+
if (first === void 0) continue;
|
|
605
|
+
const binding = this.gatewayBinding(statement);
|
|
606
|
+
if (binding === void 0) this.fail(first.method, "Remote methods require GatewayService or readonly typertGateway = bindTypeRTGateway(this, serviceKey)");
|
|
607
|
+
for (const { method, invocation } of marked) result.push(this.invocationModel(registration, binding, method, invocation));
|
|
608
|
+
}
|
|
609
|
+
return result;
|
|
610
|
+
}
|
|
611
|
+
invocationModel(registration, binding, method, invocation) {
|
|
612
|
+
if (visibilityOf(method) !== "public" || hasModifier(method, ts.SyntaxKind.StaticKeyword)) this.fail(method, "Remote decorators require a public instance method");
|
|
613
|
+
if (hasModifier(method, ts.SyntaxKind.AbstractKeyword) || method.body === void 0) this.fail(method, "Remote methods must have a concrete implementation");
|
|
614
|
+
if (!ts.isIdentifier(method.name)) this.fail(method, "Remote method names must be identifiers");
|
|
615
|
+
if ((method.typeParameters?.length ?? 0) > 0) this.fail(method, "generic Remote methods are not supported");
|
|
616
|
+
const methodName = method.name.text;
|
|
617
|
+
const exportedMethod = invocation.exportName ?? methodName;
|
|
618
|
+
const lookups = this.lookupDeclarations();
|
|
619
|
+
const lookupByHost = new Map(lookups.map((lookup) => [lookup.hostSymbol, lookup]));
|
|
620
|
+
const parameters = [];
|
|
621
|
+
let cancellation;
|
|
622
|
+
const wires = /* @__PURE__ */ new Set();
|
|
623
|
+
for (const [parameterIndex, parameter] of method.parameters.entries()) {
|
|
624
|
+
if (!ts.isIdentifier(parameter.name)) this.fail(parameter, "Remote parameters must use identifier bindings");
|
|
625
|
+
if (parameter.dotDotDotToken !== void 0) this.fail(parameter, "Remote parameters cannot be rest parameters");
|
|
626
|
+
if (parameter.initializer !== void 0) this.fail(parameter, "Remote parameters cannot have default values");
|
|
627
|
+
if (parameter.questionToken !== void 0) this.fail(parameter, "Remote parameters cannot be optional");
|
|
628
|
+
if (parameter.name.text === "this") this.fail(parameter, "Remote methods cannot declare an explicit this parameter");
|
|
629
|
+
const authoredType = this.requiredType(parameter, parameter.type, "parameter");
|
|
630
|
+
const cancellationName = parameter.name.text === "signal";
|
|
631
|
+
const cancellationType = this.isGlobalAbortSignal(authoredType);
|
|
632
|
+
if (cancellationName || cancellationType) {
|
|
633
|
+
if (!cancellationName || !cancellationType) this.fail(parameter, "Remote cancellation must use a parameter named signal with the global AbortSignal type");
|
|
634
|
+
if (parameterIndex !== method.parameters.length - 1) this.fail(parameter, "Remote cancellation signal must be the final parameter");
|
|
635
|
+
cancellation = { parameter: "signal" };
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
const hostSymbol = this.symbolAtType(authoredType);
|
|
639
|
+
const lookup = hostSymbol === void 0 ? void 0 : lookupByHost.get(this.symbolId(hostSymbol));
|
|
640
|
+
let modeled;
|
|
641
|
+
if (lookup !== void 0) {
|
|
642
|
+
if (parameter.name.text !== lookup.key) this.fail(parameter, `lookup parameter for ${lookup.key} must also be named ${lookup.key}`);
|
|
643
|
+
const boundary = this.remoteBoundary(lookup.wireType, `${registration.name}#${binding.namespace}/${exportedMethod}:${lookup.key}Id`, true);
|
|
644
|
+
modeled = {
|
|
645
|
+
name: parameter.name.text,
|
|
646
|
+
wire: `${lookup.key}Id`,
|
|
647
|
+
source: "lookup",
|
|
648
|
+
lookup: lookup.key,
|
|
649
|
+
boundary
|
|
650
|
+
};
|
|
651
|
+
} else {
|
|
652
|
+
if (hostSymbol !== void 0 && this.isWorkspaceClass(hostSymbol)) this.fail(parameter, `non-JSON class parameter ${hostSymbol.name} requires a TypeRTLookupMap entry`);
|
|
653
|
+
modeled = {
|
|
654
|
+
name: parameter.name.text,
|
|
655
|
+
wire: parameter.name.text,
|
|
656
|
+
source: "json",
|
|
657
|
+
boundary: this.remoteBoundary(authoredType, `${registration.name}#${binding.namespace}/${exportedMethod}:${parameter.name.text}`, false)
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
if (wires.has(modeled.wire)) this.fail(parameter, `duplicate Remote wire field ${modeled.wire}`);
|
|
661
|
+
wires.add(modeled.wire);
|
|
662
|
+
parameters.push(modeled);
|
|
663
|
+
}
|
|
664
|
+
let receiver = { kind: "direct" };
|
|
665
|
+
if (invocation.kind === "context") {
|
|
666
|
+
const context = this.contextDeclarations().get(invocation.context);
|
|
667
|
+
if (context === void 0) this.fail(method, `Remote Scope ${invocation.context} has no TypeRTContextMap entry`);
|
|
668
|
+
const wire = `${invocation.context}Id`;
|
|
669
|
+
if (wires.has(wire)) this.fail(method, `Remote Scope wire field ${wire} conflicts with a method parameter`);
|
|
670
|
+
receiver = {
|
|
671
|
+
kind: "context",
|
|
672
|
+
context: invocation.context,
|
|
673
|
+
wire,
|
|
674
|
+
boundary: this.remoteBoundary(context.wireType, `${registration.name}#${binding.namespace}/${exportedMethod}:${wire}`, true)
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
let scope;
|
|
678
|
+
if (invocation.kind === "direct") {
|
|
679
|
+
const lookupParameters = parameters.filter((parameter) => parameter.source === "lookup");
|
|
680
|
+
const parameter = lookupParameters.length === 1 ? lookupParameters[0] : void 0;
|
|
681
|
+
const context = parameter?.lookup === void 0 ? void 0 : this.contextDeclarations().get(parameter.lookup);
|
|
682
|
+
if (parameter !== void 0 && context !== void 0) {
|
|
683
|
+
const contextBoundary = this.remoteBoundary(context.wireType, `${registration.name}#${binding.namespace}/${exportedMethod}:scope:${context.key}`, true);
|
|
684
|
+
if (contextBoundary.typeSymbol !== parameter.boundary.typeSymbol) this.fail(method, `Remote scope ${context.key} wire type ${contextBoundary.typeSymbol} does not match lookup wire type ${parameter.boundary.typeSymbol}`);
|
|
685
|
+
scope = {
|
|
686
|
+
context: context.key,
|
|
687
|
+
wire: parameter.wire
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
const resultType = this.remoteResultType(method);
|
|
692
|
+
return {
|
|
693
|
+
id: `${registration.name}#${binding.namespace}/${exportedMethod}`,
|
|
694
|
+
service: binding.service,
|
|
695
|
+
namespace: binding.namespace,
|
|
696
|
+
method: exportedMethod,
|
|
697
|
+
...exportedMethod === methodName ? {} : { implementation: methodName },
|
|
698
|
+
invocation: receiver,
|
|
699
|
+
...scope === void 0 ? {} : { scope },
|
|
700
|
+
parameters,
|
|
701
|
+
...cancellation === void 0 ? {} : { cancellation },
|
|
702
|
+
result: this.remoteBoundary(resultType, `${registration.name}#${binding.namespace}/${exportedMethod}:result`, false),
|
|
703
|
+
location: this.location(method.name)
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
gatewayBinding(declaration) {
|
|
707
|
+
const field = this.gatewayFieldBinding(declaration);
|
|
708
|
+
const base = this.gatewayServiceBinding(declaration);
|
|
709
|
+
if (field !== void 0 && base !== void 0) this.fail(field.site, "GatewayService subclasses must not declare a second typertGateway binding");
|
|
710
|
+
return field ?? base;
|
|
711
|
+
}
|
|
712
|
+
gatewayFieldBinding(declaration) {
|
|
713
|
+
const [property, duplicate] = declaration.members.filter((member) => ts.isPropertyDeclaration(member) && memberName(member.name) === "typertGateway");
|
|
714
|
+
if (property === void 0) return void 0;
|
|
715
|
+
if (duplicate !== void 0) this.fail(duplicate, "Service has more than one typertGateway field");
|
|
716
|
+
if (visibilityOf(property) !== "public" || hasModifier(property, ts.SyntaxKind.StaticKeyword) || !hasModifier(property, ts.SyntaxKind.ReadonlyKeyword)) this.fail(property, "typertGateway must be a public readonly instance field");
|
|
717
|
+
if (property.initializer === void 0 || !ts.isCallExpression(property.initializer) || !this.isTypeMetaSymbol(property.initializer.expression, "bindTypeRTGateway")) this.fail(property, "typertGateway must call bindTypeRTGateway()");
|
|
718
|
+
const call = property.initializer;
|
|
719
|
+
if (call.arguments.length < 2 || call.arguments.length > 3) this.fail(call, "bindTypeRTGateway() requires this, service key, and an optional options object");
|
|
720
|
+
if (call.arguments[0]?.kind !== ts.SyntaxKind.ThisKeyword) this.fail(call.arguments[0] ?? call, "bindTypeRTGateway() first argument must be this");
|
|
721
|
+
return this.gatewayBindingArguments(call, property);
|
|
722
|
+
}
|
|
723
|
+
gatewayServiceBinding(declaration) {
|
|
724
|
+
const heritage = (declaration.heritageClauses ?? []).filter((clause) => clause.token === ts.SyntaxKind.ExtendsKeyword).flatMap((clause) => [...clause.types]).find((type) => this.isTypeMetaSymbol(type.expression, "GatewayService"));
|
|
725
|
+
if (heritage === void 0) return void 0;
|
|
726
|
+
const constructor = declaration.members.find(ts.isConstructorDeclaration);
|
|
727
|
+
if (constructor?.body === void 0) this.fail(heritage, "GatewayService subclasses must declare a constructor with super(ctx, serviceKey)");
|
|
728
|
+
const call = constructor.body.statements.flatMap((statement) => {
|
|
729
|
+
if (!ts.isExpressionStatement(statement) || !ts.isCallExpression(statement.expression)) return [];
|
|
730
|
+
return statement.expression.expression.kind === ts.SyntaxKind.SuperKeyword ? [statement.expression] : [];
|
|
731
|
+
})[0];
|
|
732
|
+
if (call === void 0) this.fail(constructor, "GatewayService constructor must call super(ctx, serviceKey) directly");
|
|
733
|
+
if (call.arguments.length < 2 || call.arguments.length > 3) this.fail(call, "GatewayService super() requires context, service key, and an optional options object");
|
|
734
|
+
return this.gatewayBindingArguments(call, heritage);
|
|
735
|
+
}
|
|
736
|
+
gatewayBindingArguments(call, site) {
|
|
737
|
+
const serviceArgument = call.arguments[1];
|
|
738
|
+
if (serviceArgument === void 0) this.fail(call, "Gateway service key must be a string literal");
|
|
739
|
+
const service = stringLiteralValue(serviceArgument);
|
|
740
|
+
if (service === void 0) this.fail(serviceArgument, "Gateway service key must be a string literal");
|
|
741
|
+
let namespace = service;
|
|
742
|
+
const options = call.arguments[2];
|
|
743
|
+
if (options !== void 0) {
|
|
744
|
+
if (!ts.isObjectLiteralExpression(options)) this.fail(options, "bindTypeRTGateway() options must be an object literal");
|
|
745
|
+
for (const propertyOption of options.properties) {
|
|
746
|
+
if (!ts.isPropertyAssignment(propertyOption) || memberName(propertyOption.name) !== "namespace") this.fail(propertyOption, "bindTypeRTGateway() only supports a namespace option");
|
|
747
|
+
const value = stringLiteralValue(propertyOption.initializer);
|
|
748
|
+
if (value === void 0) this.fail(propertyOption.initializer, "Gateway namespace must be a string literal");
|
|
749
|
+
namespace = value;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
if (!isRemoteSegment(service)) this.fail(serviceArgument, "Gateway service key must contain only RPC endpoint segment characters");
|
|
753
|
+
if (!isRemoteSegment(namespace)) this.fail(options ?? call, "Gateway namespace must contain only RPC endpoint segment characters");
|
|
754
|
+
return {
|
|
755
|
+
service,
|
|
756
|
+
namespace,
|
|
757
|
+
site
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
remoteMarker(member) {
|
|
761
|
+
let found;
|
|
762
|
+
for (const decorator of ts.canHaveDecorators(member) ? ts.getDecorators(member) ?? [] : []) {
|
|
763
|
+
const expression = decorator.expression;
|
|
764
|
+
let marker;
|
|
765
|
+
if (this.isTypeMetaSymbol(expression, "Remote")) marker = { kind: "direct" };
|
|
766
|
+
else if (ts.isCallExpression(expression) && this.isTypeMetaSymbol(expression.expression, "Remote")) {
|
|
767
|
+
if (expression.arguments.length !== 1) this.fail(expression, "Remote() requires one exported method name");
|
|
768
|
+
const exportName = stringLiteralValue(expression.arguments[0]);
|
|
769
|
+
if (exportName === void 0 || !isRemoteSegment(exportName)) this.fail(expression.arguments[0] ?? expression, "Remote() name must be a string literal containing only RPC endpoint segment characters");
|
|
770
|
+
marker = {
|
|
771
|
+
kind: "direct",
|
|
772
|
+
exportName
|
|
773
|
+
};
|
|
774
|
+
} else if (ts.isCallExpression(expression) && this.isTypeMetaSymbol(expression.expression, "RemoteScope")) {
|
|
775
|
+
if (expression.arguments.length < 1 || expression.arguments.length > 2) this.fail(expression, "RemoteScope() requires a Context key and optional exported method name");
|
|
776
|
+
const context = stringLiteralValue(expression.arguments[0]);
|
|
777
|
+
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");
|
|
778
|
+
const exportArgument = expression.arguments[1];
|
|
779
|
+
const exportName = exportArgument === void 0 ? void 0 : stringLiteralValue(exportArgument);
|
|
780
|
+
if (exportArgument !== void 0 && (exportName === void 0 || !isRemoteSegment(exportName))) this.fail(exportArgument, "RemoteScope() name must be a string literal containing only RPC endpoint segment characters");
|
|
781
|
+
marker = {
|
|
782
|
+
kind: "context",
|
|
783
|
+
context,
|
|
784
|
+
...exportName === void 0 ? {} : { exportName }
|
|
785
|
+
};
|
|
786
|
+
} else continue;
|
|
787
|
+
if (found !== void 0) this.fail(decorator, "a method can have only one Remote invocation decorator");
|
|
788
|
+
found = marker;
|
|
789
|
+
}
|
|
790
|
+
return found;
|
|
791
|
+
}
|
|
792
|
+
remoteResultType(method) {
|
|
793
|
+
const authored = this.requiredType(method, method.type, "return");
|
|
794
|
+
if (!ts.isTypeReferenceNode(authored)) return authored;
|
|
795
|
+
const symbol = this.checker.getSymbolAtLocation(authored.typeName);
|
|
796
|
+
const resolved = symbol === void 0 ? void 0 : this.resolveSymbol(symbol);
|
|
797
|
+
const resultType = authored.typeArguments?.[0];
|
|
798
|
+
if (resolved?.name !== "Promise" || resultType === void 0 || authored.typeArguments?.length !== 1) return authored;
|
|
799
|
+
const declaration = preferredDeclaration(resolved);
|
|
800
|
+
if (declaration === void 0 || !isStandardLibraryFile(declaration.getSourceFile().fileName)) return authored;
|
|
801
|
+
return resultType;
|
|
802
|
+
}
|
|
803
|
+
isGlobalAbortSignal(type) {
|
|
804
|
+
const symbol = this.symbolAtType(type);
|
|
805
|
+
if (symbol?.name !== "AbortSignal") return false;
|
|
806
|
+
return symbol.declarations?.some((declaration) => isStandardLibraryFile(declaration.getSourceFile().fileName)) === true;
|
|
807
|
+
}
|
|
808
|
+
lookupDeclarations() {
|
|
809
|
+
if (this.staticLookups !== void 0) return this.staticLookups;
|
|
810
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
811
|
+
const byHost = /* @__PURE__ */ new Map();
|
|
812
|
+
for (const declaration of this.typeMetaMapMembers("TypeRTLookupMap")) {
|
|
813
|
+
if (!ts.isPropertySignature(declaration) || declaration.type === void 0) this.fail(declaration, "TypeRTLookupMap entries must be required properties");
|
|
814
|
+
const key = memberName(declaration.name);
|
|
815
|
+
if (!isRemoteSegment(key)) this.fail(declaration.name, "TypeRTLookupMap key must contain only RPC endpoint segment characters");
|
|
816
|
+
if (!ts.isTypeReferenceNode(declaration.type) || !this.isTypeMetaSymbol(declaration.type.typeName, "TypeRTLookup") || declaration.type.typeArguments?.length !== 2) this.fail(declaration.type, "TypeRTLookupMap values must be TypeRTLookup<Host, Wire>");
|
|
817
|
+
const hostType = declaration.type.typeArguments[0];
|
|
818
|
+
const wireType = declaration.type.typeArguments[1];
|
|
819
|
+
if (hostType === void 0 || wireType === void 0) this.fail(declaration.type, "TypeRTLookupMap values must be TypeRTLookup<Host, Wire>");
|
|
820
|
+
const host = this.symbolAtType(hostType);
|
|
821
|
+
if (host === void 0) this.fail(hostType, "TypeRTLookup Host must be a named type");
|
|
822
|
+
const entry = {
|
|
823
|
+
key,
|
|
824
|
+
hostSymbol: this.symbolId(host),
|
|
825
|
+
wireType,
|
|
826
|
+
site: declaration
|
|
827
|
+
};
|
|
828
|
+
if (byKey.has(key)) this.fail(declaration, `duplicate TypeRTLookupMap key ${key}`);
|
|
829
|
+
if (byHost.has(entry.hostSymbol)) this.fail(declaration, `Host type ${host.name} has more than one TypeRT lookup`);
|
|
830
|
+
byKey.set(key, entry);
|
|
831
|
+
byHost.set(entry.hostSymbol, entry);
|
|
832
|
+
}
|
|
833
|
+
this.staticLookups = [...byKey.values()];
|
|
834
|
+
return this.staticLookups;
|
|
835
|
+
}
|
|
836
|
+
contextDeclarations() {
|
|
837
|
+
if (this.staticContexts !== void 0) return this.staticContexts;
|
|
838
|
+
const result = /* @__PURE__ */ new Map();
|
|
839
|
+
for (const declaration of this.typeMetaMapMembers("TypeRTContextMap")) {
|
|
840
|
+
if (!ts.isPropertySignature(declaration) || declaration.type === void 0) this.fail(declaration, "TypeRTContextMap entries must be required properties");
|
|
841
|
+
const key = memberName(declaration.name);
|
|
842
|
+
if (!isRemoteSegment(key)) this.fail(declaration.name, "TypeRTContextMap key must contain only RPC endpoint segment characters");
|
|
843
|
+
if (!ts.isTypeReferenceNode(declaration.type) || !this.isTypeMetaSymbol(declaration.type.typeName, "TypeRTContext") || declaration.type.typeArguments?.length !== 1) this.fail(declaration.type, "TypeRTContextMap values must be TypeRTContext<Wire>");
|
|
844
|
+
if (result.has(key)) this.fail(declaration, `duplicate TypeRTContextMap key ${key}`);
|
|
845
|
+
const wireType = declaration.type.typeArguments[0];
|
|
846
|
+
if (wireType === void 0) this.fail(declaration.type, "TypeRTContextMap values must be TypeRTContext<Wire>");
|
|
847
|
+
result.set(key, {
|
|
848
|
+
key,
|
|
849
|
+
wireType,
|
|
850
|
+
site: declaration
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
this.staticContexts = result;
|
|
854
|
+
return result;
|
|
855
|
+
}
|
|
856
|
+
typeMetaMapMembers(name) {
|
|
857
|
+
const result = [];
|
|
858
|
+
for (const sourceFile of this.program.getSourceFiles()) for (const statement of sourceFile.statements) {
|
|
859
|
+
if (!ts.isModuleDeclaration(statement) || !ts.isStringLiteral(statement.name) || statement.name.text !== "@deepseek-ai/dsh-type-meta" || statement.body === void 0 || !ts.isModuleBlock(statement.body)) continue;
|
|
860
|
+
for (const nested of statement.body.statements) if (ts.isInterfaceDeclaration(nested) && nested.name.text === name) result.push(...nested.members);
|
|
861
|
+
}
|
|
862
|
+
return result;
|
|
863
|
+
}
|
|
864
|
+
remoteBoundary(authoredType, fallbackTypeSymbol, requireNamed) {
|
|
865
|
+
const type = this.convertType(authoredType);
|
|
866
|
+
const codecType = this.resolvedRemoteCodecType(authoredType);
|
|
867
|
+
const rootSymbol = this.namedWorkspaceType(authoredType);
|
|
868
|
+
const imports = /* @__PURE__ */ new Map();
|
|
869
|
+
const visit = (node) => {
|
|
870
|
+
if (ts.isTypeReferenceNode(node) || ts.isImportTypeNode(node)) {
|
|
871
|
+
const symbol = ts.isTypeReferenceNode(node) ? this.checker.getSymbolAtLocation(node.typeName) : node.qualifier === void 0 ? void 0 : this.checker.getSymbolAtLocation(node.qualifier);
|
|
872
|
+
if (symbol !== void 0) {
|
|
873
|
+
const resolved = this.resolveSymbol(symbol);
|
|
874
|
+
const declaration = preferredDeclaration(resolved);
|
|
875
|
+
if (declaration !== void 0 && !isStandardLibraryFile(declaration.getSourceFile().fileName) && this.registrationForFile(declaration.getSourceFile().fileName) !== void 0) {
|
|
876
|
+
const imported = this.publicRemoteType(resolved, node);
|
|
877
|
+
imports.set(imported.symbol, imported);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
ts.forEachChild(node, visit);
|
|
882
|
+
};
|
|
883
|
+
visit(authoredType);
|
|
884
|
+
if (rootSymbol !== void 0) {
|
|
885
|
+
const imported = this.publicRemoteType(rootSymbol, authoredType);
|
|
886
|
+
return {
|
|
887
|
+
type,
|
|
888
|
+
codecType,
|
|
889
|
+
typeSymbol: `${imported.specifier}#${imported.name}`,
|
|
890
|
+
imports: [...imports.values()].sort((left, right) => left.specifier.localeCompare(right.specifier) || left.name.localeCompare(right.name))
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
if (requireNamed) this.fail(authoredType, "lookup and Context wire types must be named public types");
|
|
894
|
+
return {
|
|
895
|
+
type,
|
|
896
|
+
codecType,
|
|
897
|
+
typeSymbol: fallbackTypeSymbol,
|
|
898
|
+
imports: [...imports.values()].sort((left, right) => left.specifier.localeCompare(right.specifier) || left.name.localeCompare(right.name))
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Project one authored Remote boundary through the complete face Program.
|
|
903
|
+
* Consumer declarations retain the authored alias, while codecs use this
|
|
904
|
+
* concrete graph so declaration-merged mapped and conditional types are
|
|
905
|
+
* validated without teaching the compiler-independent emitter TypeScript's
|
|
906
|
+
* type evaluator.
|
|
907
|
+
*/
|
|
908
|
+
resolvedRemoteCodecType(authoredType) {
|
|
909
|
+
const resolvedType = this.checker.getTypeFromTypeNode(authoredType);
|
|
910
|
+
this.assertRemoteJsonType(resolvedType, authoredType, /* @__PURE__ */ new Set(), false);
|
|
911
|
+
const completed = /* @__PURE__ */ new Map();
|
|
912
|
+
const active = /* @__PURE__ */ new Map();
|
|
913
|
+
const recursiveDeclarations = /* @__PURE__ */ new Map();
|
|
914
|
+
const convert = (type) => {
|
|
915
|
+
const cached = completed.get(type);
|
|
916
|
+
if (cached !== void 0) return cached;
|
|
917
|
+
const activeId = active.get(type);
|
|
918
|
+
if (activeId !== void 0) {
|
|
919
|
+
if (this.checker.isArrayType(type) || this.checker.isArrayLikeType(type)) {
|
|
920
|
+
const element = this.checker.getIndexTypeOfType(type, ts.IndexKind.Number);
|
|
921
|
+
const elementId = element === void 0 ? void 0 : active.get(element);
|
|
922
|
+
if (element !== void 0 && elementId !== void 0) return this.addNode(authoredType, {
|
|
923
|
+
kind: "array",
|
|
924
|
+
element: this.resolvedCycleReference(element, authoredType, elementId, recursiveDeclarations)
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
return this.resolvedCycleReference(type, authoredType, activeId, recursiveDeclarations);
|
|
928
|
+
}
|
|
929
|
+
const id = this.allocateNodeId(authoredType);
|
|
930
|
+
active.set(type, id);
|
|
931
|
+
try {
|
|
932
|
+
const add = (model) => {
|
|
933
|
+
this.nodes.set(id, {
|
|
934
|
+
id,
|
|
935
|
+
...model
|
|
936
|
+
});
|
|
937
|
+
completed.set(type, id);
|
|
938
|
+
return id;
|
|
939
|
+
};
|
|
940
|
+
const flags = type.flags;
|
|
941
|
+
if ((flags & ts.TypeFlags.Any) !== 0) return add({
|
|
942
|
+
kind: "keyword",
|
|
943
|
+
name: "any"
|
|
944
|
+
});
|
|
945
|
+
if ((flags & ts.TypeFlags.Unknown) !== 0) return add({
|
|
946
|
+
kind: "keyword",
|
|
947
|
+
name: "unknown"
|
|
948
|
+
});
|
|
949
|
+
if ((flags & ts.TypeFlags.Never) !== 0) return add({
|
|
950
|
+
kind: "keyword",
|
|
951
|
+
name: "never"
|
|
952
|
+
});
|
|
953
|
+
if ((flags & ts.TypeFlags.String) !== 0) return add({
|
|
954
|
+
kind: "keyword",
|
|
955
|
+
name: "string"
|
|
956
|
+
});
|
|
957
|
+
if ((flags & ts.TypeFlags.Number) !== 0) return add({
|
|
958
|
+
kind: "keyword",
|
|
959
|
+
name: "number"
|
|
960
|
+
});
|
|
961
|
+
if ((flags & ts.TypeFlags.BigInt) !== 0) return add({
|
|
962
|
+
kind: "keyword",
|
|
963
|
+
name: "bigint"
|
|
964
|
+
});
|
|
965
|
+
if ((flags & ts.TypeFlags.Boolean) !== 0) return add({
|
|
966
|
+
kind: "keyword",
|
|
967
|
+
name: "boolean"
|
|
968
|
+
});
|
|
969
|
+
if ((flags & ts.TypeFlags.ESSymbol) !== 0) return add({
|
|
970
|
+
kind: "keyword",
|
|
971
|
+
name: "symbol"
|
|
972
|
+
});
|
|
973
|
+
if ((flags & ts.TypeFlags.Undefined) !== 0) return add({
|
|
974
|
+
kind: "keyword",
|
|
975
|
+
name: "undefined"
|
|
976
|
+
});
|
|
977
|
+
if ((flags & ts.TypeFlags.Void) !== 0) return add({
|
|
978
|
+
kind: "keyword",
|
|
979
|
+
name: "void"
|
|
980
|
+
});
|
|
981
|
+
if ((flags & ts.TypeFlags.Null) !== 0) return add({
|
|
982
|
+
kind: "literal",
|
|
983
|
+
value: null,
|
|
984
|
+
text: "null"
|
|
985
|
+
});
|
|
986
|
+
if ((flags & ts.TypeFlags.StringLiteral) !== 0) {
|
|
987
|
+
const value = type.value;
|
|
988
|
+
return add({
|
|
989
|
+
kind: "literal",
|
|
990
|
+
value,
|
|
991
|
+
text: JSON.stringify(value)
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
if ((flags & ts.TypeFlags.NumberLiteral) !== 0) {
|
|
995
|
+
const value = type.value;
|
|
996
|
+
return add({
|
|
997
|
+
kind: "literal",
|
|
998
|
+
value,
|
|
999
|
+
text: String(value)
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
if ((flags & ts.TypeFlags.BigIntLiteral) !== 0) {
|
|
1003
|
+
const value = type.value;
|
|
1004
|
+
const text = `${value.negative ? "-" : ""}${value.base10Value}n`;
|
|
1005
|
+
return add({
|
|
1006
|
+
kind: "literal",
|
|
1007
|
+
value: BigInt(`${value.negative ? "-" : ""}${value.base10Value}`),
|
|
1008
|
+
text
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
if ((flags & ts.TypeFlags.BooleanLiteral) !== 0) {
|
|
1012
|
+
const value = type.intrinsicName === "true";
|
|
1013
|
+
return add({
|
|
1014
|
+
kind: "literal",
|
|
1015
|
+
value,
|
|
1016
|
+
text: String(value)
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
if (type.isUnionOrIntersection()) return add({
|
|
1020
|
+
kind: (flags & ts.TypeFlags.Union) !== 0 ? "union" : "intersection",
|
|
1021
|
+
types: type.types.map(convert)
|
|
1022
|
+
});
|
|
1023
|
+
if ((flags & ts.TypeFlags.TypeParameter) !== 0) this.fail(authoredType, "Remote codec contains an unresolved type parameter");
|
|
1024
|
+
if ((flags & ts.TypeFlags.Object) === 0) this.fail(authoredType, `Remote codec type ${this.checker.typeToString(type, authoredType, ts.TypeFormatFlags.NoTruncation)} has no concrete Zod projection`);
|
|
1025
|
+
if (this.checker.isTupleType(type)) {
|
|
1026
|
+
const reference = type;
|
|
1027
|
+
const target = reference.target;
|
|
1028
|
+
return add({
|
|
1029
|
+
kind: "tuple",
|
|
1030
|
+
elements: this.checker.getTypeArguments(reference).map((argument, index) => {
|
|
1031
|
+
const elementFlags = target.elementFlags[index] ?? ts.ElementFlags.Required;
|
|
1032
|
+
return {
|
|
1033
|
+
type: convert(argument),
|
|
1034
|
+
optional: (elementFlags & ts.ElementFlags.Optional) !== 0,
|
|
1035
|
+
rest: (elementFlags & (ts.ElementFlags.Rest | ts.ElementFlags.Variadic)) !== 0
|
|
1036
|
+
};
|
|
1037
|
+
})
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
if (this.checker.isArrayType(type) || this.checker.isArrayLikeType(type)) {
|
|
1041
|
+
const element = this.checker.getIndexTypeOfType(type, ts.IndexKind.Number);
|
|
1042
|
+
if (element === void 0) this.fail(authoredType, "Remote codec array has no element type");
|
|
1043
|
+
return add({
|
|
1044
|
+
kind: "array",
|
|
1045
|
+
element: convert(element)
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
if (type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0) this.fail(authoredType, "Remote codec cannot contain callable or constructable values");
|
|
1049
|
+
const members = [];
|
|
1050
|
+
for (const property of this.checker.getPropertiesOfType(type)) {
|
|
1051
|
+
const declaration = property.valueDeclaration ?? property.declarations?.[0];
|
|
1052
|
+
const propertyType = this.checker.getTypeOfSymbolAtLocation(property, declaration ?? authoredType);
|
|
1053
|
+
const symbolKey = property.getName();
|
|
1054
|
+
members.push({
|
|
1055
|
+
...EMPTY_DOCUMENTATION,
|
|
1056
|
+
id: `${id}#${symbolKey}`,
|
|
1057
|
+
name: symbolKey,
|
|
1058
|
+
...symbolKey.startsWith("__@") ? { computed: "symbol" } : {},
|
|
1059
|
+
optional: (property.flags & ts.SymbolFlags.Optional) !== 0,
|
|
1060
|
+
readonly: declaration !== void 0 && hasModifier(declaration, ts.SyntaxKind.ReadonlyKeyword),
|
|
1061
|
+
async: false,
|
|
1062
|
+
abstract: false,
|
|
1063
|
+
static: false,
|
|
1064
|
+
visibility: "public",
|
|
1065
|
+
location: this.location(authoredType),
|
|
1066
|
+
text: "",
|
|
1067
|
+
kind: "property",
|
|
1068
|
+
type: convert(propertyType)
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
for (const [index, info] of this.checker.getIndexInfosOfType(type).entries()) members.push({
|
|
1072
|
+
...EMPTY_DOCUMENTATION,
|
|
1073
|
+
id: `${id}#index:${String(index)}`,
|
|
1074
|
+
name: "(index)",
|
|
1075
|
+
optional: false,
|
|
1076
|
+
readonly: info.isReadonly,
|
|
1077
|
+
async: false,
|
|
1078
|
+
abstract: false,
|
|
1079
|
+
static: false,
|
|
1080
|
+
visibility: "public",
|
|
1081
|
+
location: this.location(authoredType),
|
|
1082
|
+
text: "",
|
|
1083
|
+
kind: "index",
|
|
1084
|
+
signature: {
|
|
1085
|
+
typeParameters: [],
|
|
1086
|
+
parameters: [{
|
|
1087
|
+
name: "key",
|
|
1088
|
+
binding: "identifier",
|
|
1089
|
+
type: convert(info.keyType),
|
|
1090
|
+
optional: false,
|
|
1091
|
+
rest: false,
|
|
1092
|
+
receiver: false
|
|
1093
|
+
}],
|
|
1094
|
+
returns: convert(info.type)
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
return add({
|
|
1098
|
+
kind: "object",
|
|
1099
|
+
members
|
|
1100
|
+
});
|
|
1101
|
+
} finally {
|
|
1102
|
+
active.delete(type);
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
return convert(resolvedType);
|
|
1106
|
+
}
|
|
1107
|
+
assertRemoteJsonType(type, site, active, allowUndefined) {
|
|
1108
|
+
const flags = type.flags;
|
|
1109
|
+
if ((flags & ts.TypeFlags.Undefined) !== 0 && allowUndefined) return;
|
|
1110
|
+
if ((flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) this.fail(site, `Remote boundary contains unconstrained ${this.checker.typeToString(type)} data`);
|
|
1111
|
+
if ((flags & (ts.TypeFlags.BigIntLike | ts.TypeFlags.ESSymbolLike | ts.TypeFlags.Undefined | ts.TypeFlags.Void)) !== 0) this.fail(site, `Remote boundary contains non-JSON type ${this.checker.typeToString(type)}`);
|
|
1112
|
+
if ((flags & (ts.TypeFlags.StringLike | ts.TypeFlags.NumberLike | ts.TypeFlags.BooleanLike | ts.TypeFlags.Null | ts.TypeFlags.Never)) !== 0) return;
|
|
1113
|
+
if (type.isUnion()) {
|
|
1114
|
+
for (const member of type.types) this.assertRemoteJsonType(member, site, active, allowUndefined);
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
if (type.isIntersection()) {
|
|
1118
|
+
const material = type.types.filter((member) => !this.isRemotePhantomConstraint(member));
|
|
1119
|
+
if (material.length === 0) this.fail(site, "Remote boundary contains a symbol-only object");
|
|
1120
|
+
for (const member of material) this.assertRemoteJsonType(member, site, active, false);
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
if ((flags & ts.TypeFlags.TypeParameter) !== 0) this.fail(site, "Remote boundary contains an unresolved type parameter");
|
|
1124
|
+
if ((flags & ts.TypeFlags.Object) === 0) this.fail(site, `Remote boundary contains non-JSON type ${this.checker.typeToString(type)}`);
|
|
1125
|
+
const symbol = type.getSymbol();
|
|
1126
|
+
const declaration = symbol?.valueDeclaration ?? symbol?.declarations?.[0];
|
|
1127
|
+
if (declaration !== void 0 && (ts.isClassDeclaration(declaration) || ts.isClassExpression(declaration))) this.fail(site, `Remote boundary contains class instance ${symbol?.name ?? this.checker.typeToString(type)}`);
|
|
1128
|
+
if (type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0) this.fail(site, "Remote boundary contains callable or constructable data");
|
|
1129
|
+
if (active.has(type)) return;
|
|
1130
|
+
active.add(type);
|
|
1131
|
+
try {
|
|
1132
|
+
if (this.checker.isTupleType(type)) {
|
|
1133
|
+
const reference = type;
|
|
1134
|
+
const target = reference.target;
|
|
1135
|
+
this.checker.getTypeArguments(reference).forEach((argument, index) => {
|
|
1136
|
+
const elementFlags = target.elementFlags[index] ?? ts.ElementFlags.Required;
|
|
1137
|
+
this.assertRemoteJsonType(argument, site, active, (elementFlags & ts.ElementFlags.Optional) !== 0);
|
|
1138
|
+
});
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
if (this.checker.isArrayType(type) || this.checker.isArrayLikeType(type)) {
|
|
1142
|
+
const element = this.checker.getIndexTypeOfType(type, ts.IndexKind.Number);
|
|
1143
|
+
if (element === void 0) this.fail(site, "Remote boundary array has no element type");
|
|
1144
|
+
this.assertRemoteJsonType(element, site, active, false);
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
const properties = this.checker.getPropertiesOfType(type);
|
|
1148
|
+
if (properties.some((property) => property.getName().startsWith("__@"))) this.fail(site, "Remote boundary contains a symbol-keyed property");
|
|
1149
|
+
for (const property of properties) {
|
|
1150
|
+
const propertyDeclaration = property.valueDeclaration ?? property.declarations?.[0];
|
|
1151
|
+
const propertyType = this.checker.getTypeOfSymbolAtLocation(property, propertyDeclaration ?? site);
|
|
1152
|
+
this.assertRemoteJsonType(propertyType, site, active, (property.flags & ts.SymbolFlags.Optional) !== 0);
|
|
1153
|
+
}
|
|
1154
|
+
for (const info of this.checker.getIndexInfosOfType(type)) {
|
|
1155
|
+
if ((info.keyType.flags & ts.TypeFlags.ESSymbolLike) !== 0) this.fail(site, "Remote boundary contains a symbol index signature");
|
|
1156
|
+
this.assertRemoteJsonType(info.type, site, active, false);
|
|
1157
|
+
}
|
|
1158
|
+
} finally {
|
|
1159
|
+
active.delete(type);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
isRemotePhantomConstraint(type) {
|
|
1163
|
+
if ((type.flags & ts.TypeFlags.Unknown) !== 0) return true;
|
|
1164
|
+
if ((type.flags & ts.TypeFlags.Any) !== 0 || (type.flags & ts.TypeFlags.Object) === 0) return false;
|
|
1165
|
+
if (type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0) return false;
|
|
1166
|
+
if (this.checker.getIndexInfosOfType(type).length > 0) return false;
|
|
1167
|
+
return this.checker.getPropertiesOfType(type).every((property) => property.getName().startsWith("__@"));
|
|
1168
|
+
}
|
|
1169
|
+
resolvedCycleReference(type, site, resolvedType, recursiveDeclarations) {
|
|
1170
|
+
const symbol = type.aliasSymbol ?? type.getSymbol();
|
|
1171
|
+
if (symbol === void 0) this.fail(site, "Remote codec contains an unnamed recursive type");
|
|
1172
|
+
const resolved = this.resolveSymbol(symbol);
|
|
1173
|
+
const declaration = preferredDeclaration(resolved);
|
|
1174
|
+
if (declaration === void 0 || isStandardLibraryFile(declaration.getSourceFile().fileName)) this.fail(site, `Remote codec recursive type ${resolved.name} has no workspace declaration`);
|
|
1175
|
+
const owner = this.registrationForFile(declaration.getSourceFile().fileName);
|
|
1176
|
+
if (owner === void 0) this.fail(site, `Remote codec recursive type ${resolved.name} is not owned by this face`);
|
|
1177
|
+
let id = recursiveDeclarations.get(type);
|
|
1178
|
+
if (id === void 0) {
|
|
1179
|
+
id = `${this.symbolId(resolved)}#remote-codec:${resolvedType}`;
|
|
1180
|
+
recursiveDeclarations.set(type, id);
|
|
1181
|
+
this.declarations.set(id, {
|
|
1182
|
+
...EMPTY_DOCUMENTATION,
|
|
1183
|
+
id,
|
|
1184
|
+
package: owner.name,
|
|
1185
|
+
name: `${resolved.name}RemoteCodec`,
|
|
1186
|
+
kind: "alias",
|
|
1187
|
+
abstract: false,
|
|
1188
|
+
exported: false,
|
|
1189
|
+
location: this.location(declaration),
|
|
1190
|
+
text: "",
|
|
1191
|
+
typeParameters: [],
|
|
1192
|
+
extends: [],
|
|
1193
|
+
implements: [],
|
|
1194
|
+
members: [],
|
|
1195
|
+
type: resolvedType
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
return this.addNode(site, {
|
|
1199
|
+
kind: "reference",
|
|
1200
|
+
name: `${resolved.name}RemoteCodec`,
|
|
1201
|
+
target: {
|
|
1202
|
+
kind: "declaration",
|
|
1203
|
+
symbol: id
|
|
1204
|
+
},
|
|
1205
|
+
arguments: []
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
namedWorkspaceType(node) {
|
|
1209
|
+
if (!ts.isTypeReferenceNode(node) && !ts.isImportTypeNode(node)) return void 0;
|
|
1210
|
+
const symbol = ts.isTypeReferenceNode(node) ? this.checker.getSymbolAtLocation(node.typeName) : node.qualifier === void 0 ? void 0 : this.checker.getSymbolAtLocation(node.qualifier);
|
|
1211
|
+
if (symbol === void 0) return void 0;
|
|
1212
|
+
const resolved = this.resolveSymbol(symbol);
|
|
1213
|
+
const declaration = preferredDeclaration(resolved);
|
|
1214
|
+
if (declaration === void 0 || isStandardLibraryFile(declaration.getSourceFile().fileName) || this.registrationForFile(declaration.getSourceFile().fileName) === void 0) return void 0;
|
|
1215
|
+
return resolved;
|
|
1216
|
+
}
|
|
1217
|
+
publicRemoteType(symbol, site) {
|
|
1218
|
+
const declaration = preferredDeclaration(symbol);
|
|
1219
|
+
if (declaration === void 0) this.fail(site, `type ${symbol.name} has no declaration`);
|
|
1220
|
+
const registration = this.registrationForFile(declaration.getSourceFile().fileName);
|
|
1221
|
+
if (registration === void 0) this.fail(site, `type ${symbol.name} is not owned by a workspace package`);
|
|
1222
|
+
const candidates = [];
|
|
1223
|
+
for (const [subpath, target] of packageExportTargets(registration.manifest)) {
|
|
1224
|
+
if (subpath === "." || subpath === "./package.json" || subpath === "./typert" || subpath === "./client/typert" || subpath === "./remote" || target.includes("*")) continue;
|
|
1225
|
+
const sourceFile = this.sourceFiles.get(realPath(sourcePathForExport(registration.root, target)));
|
|
1226
|
+
if (sourceFile === void 0) continue;
|
|
1227
|
+
const moduleSymbol = this.checker.getSymbolAtLocation(sourceFile);
|
|
1228
|
+
if (moduleSymbol === void 0) continue;
|
|
1229
|
+
for (const exported of this.checker.getExportsOfModule(moduleSymbol)) {
|
|
1230
|
+
if (this.resolveSymbol(exported) !== symbol) continue;
|
|
1231
|
+
candidates.push({
|
|
1232
|
+
symbol: this.symbolId(symbol),
|
|
1233
|
+
specifier: packageExportSpecifier$1(registration.name, subpath),
|
|
1234
|
+
name: exported.name
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
const selected = candidates.sort((left, right) => left.specifier.localeCompare(right.specifier) || left.name.localeCompare(right.name))[0];
|
|
1239
|
+
if (selected === void 0) this.fail(site, `Remote boundary type ${symbol.name} must be exported from a public non-root type subpath`);
|
|
1240
|
+
return selected;
|
|
1241
|
+
}
|
|
1242
|
+
isWorkspaceClass(symbol) {
|
|
1243
|
+
const declaration = preferredDeclaration(symbol);
|
|
1244
|
+
return declaration !== void 0 && ts.isClassDeclaration(declaration) && this.registrationForFile(declaration.getSourceFile().fileName) !== void 0;
|
|
1245
|
+
}
|
|
1246
|
+
isTypeMetaSymbol(node, name) {
|
|
1247
|
+
const symbol = this.checker.getSymbolAtLocation(node);
|
|
1248
|
+
if (symbol === void 0) return false;
|
|
1249
|
+
const resolved = this.resolveSymbol(symbol);
|
|
1250
|
+
if (resolved.name !== name) return false;
|
|
1251
|
+
const declaration = preferredDeclaration(resolved);
|
|
1252
|
+
if (declaration === void 0) return false;
|
|
1253
|
+
if (this.registrationForFile(declaration.getSourceFile().fileName)?.name === "@deepseek-ai/dsh-type-meta") return true;
|
|
1254
|
+
for (let current = declaration; current !== void 0; current = optionalParent(current)) if (ts.isModuleDeclaration(current) && ts.isStringLiteral(current.name) && current.name.text === "@deepseek-ai/dsh-type-meta") return true;
|
|
1255
|
+
return false;
|
|
1256
|
+
}
|
|
1257
|
+
validateInvocationIdentity(packages) {
|
|
1258
|
+
const endpoints = /* @__PURE__ */ new Map();
|
|
1259
|
+
const ids = /* @__PURE__ */ new Map();
|
|
1260
|
+
for (const invocation of packages.flatMap((packageModel) => packageModel.invocations)) {
|
|
1261
|
+
const endpoint = `${invocation.namespace}/${invocation.method}`;
|
|
1262
|
+
const existingEndpoint = endpoints.get(endpoint);
|
|
1263
|
+
if (existingEndpoint !== void 0) throw new TypertAnalysisError(`typert(${this.face}): ${invocation.location.file}:${String(invocation.location.line)}:${String(invocation.location.column)}: Remote endpoint ${endpoint} conflicts with ${existingEndpoint.id}`);
|
|
1264
|
+
const existingId = ids.get(invocation.id);
|
|
1265
|
+
if (existingId !== void 0) throw new TypertAnalysisError(`typert(${this.face}): ${invocation.location.file}:${String(invocation.location.line)}:${String(invocation.location.column)}: Remote invocation id ${invocation.id} conflicts with ${existingId.id}`);
|
|
1266
|
+
endpoints.set(endpoint, invocation);
|
|
1267
|
+
ids.set(invocation.id, invocation);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
collectEvents(events) {
|
|
1271
|
+
const result = [];
|
|
1272
|
+
for (const member of events.members) {
|
|
1273
|
+
const documentation = documentationOf(member);
|
|
1274
|
+
const mode = documentation.tags.find((tag) => tag.name === "mode")?.comment?.trim();
|
|
1275
|
+
if (ts.isMethodSignature(member)) {
|
|
1276
|
+
const signature = this.signature(member, member.type);
|
|
1277
|
+
result.push({
|
|
1278
|
+
...documentation,
|
|
1279
|
+
name: memberName(member.name),
|
|
1280
|
+
signature: this.addNode(member, {
|
|
1281
|
+
kind: "function",
|
|
1282
|
+
signature
|
|
1283
|
+
}),
|
|
1284
|
+
text: memberText(member),
|
|
1285
|
+
...mode === void 0 ? {} : { mode },
|
|
1286
|
+
location: this.location(member)
|
|
1287
|
+
});
|
|
1288
|
+
} else if (ts.isPropertySignature(member) && member.type !== void 0) result.push({
|
|
1289
|
+
...documentation,
|
|
1290
|
+
name: memberName(member.name),
|
|
1291
|
+
signature: this.convertType(member.type),
|
|
1292
|
+
text: memberText(member),
|
|
1293
|
+
...mode === void 0 ? {} : { mode },
|
|
1294
|
+
location: this.location(member)
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
return result;
|
|
1298
|
+
}
|
|
1299
|
+
ensureDeclaration(symbol, selected) {
|
|
1300
|
+
const resolved = this.resolveSymbol(symbol);
|
|
1301
|
+
const id = this.symbolId(resolved);
|
|
1302
|
+
const existing = this.declarations.get(id);
|
|
1303
|
+
if (existing !== void 0) return existing;
|
|
1304
|
+
const declarationParts = resolved.declarations.filter(isTypeDeclaration);
|
|
1305
|
+
if (declarationParts.length > 1 && !declarationParts.every(ts.isInterfaceDeclaration)) this.fail(selected, `merged ${ts.SyntaxKind[selected.kind]} declaration ${resolved.name} is not supported`);
|
|
1306
|
+
if (selected.name === void 0) this.fail(selected, `anonymous ${ts.SyntaxKind[selected.kind]} cannot be represented as a named type declaration`);
|
|
1307
|
+
const owner = this.registrationForFile(selected.getSourceFile().fileName);
|
|
1308
|
+
this.declarationStates.add(id);
|
|
1309
|
+
if (declarationParts.length > 1) {
|
|
1310
|
+
const analyzedParts = declarationParts.map((declarationPart) => {
|
|
1311
|
+
const part = declarationPart;
|
|
1312
|
+
const partOwner = this.registrationForFile(part.getSourceFile().fileName);
|
|
1313
|
+
if (partOwner === void 0) this.fail(part, `merged interface ${resolved.name} contains a declaration outside this face`);
|
|
1314
|
+
const typeParameters = this.typeParameters(part.typeParameters);
|
|
1315
|
+
const heritage = this.heritage(part);
|
|
1316
|
+
const members = this.members(part.members, id);
|
|
1317
|
+
return {
|
|
1318
|
+
typeParameters,
|
|
1319
|
+
heritage,
|
|
1320
|
+
members,
|
|
1321
|
+
model: {
|
|
1322
|
+
...documentationOf(part),
|
|
1323
|
+
package: partOwner.name,
|
|
1324
|
+
location: this.location(part),
|
|
1325
|
+
typeParameters,
|
|
1326
|
+
extends: heritage.extends,
|
|
1327
|
+
members: members.map((member) => member.id)
|
|
1328
|
+
}
|
|
1329
|
+
};
|
|
1330
|
+
});
|
|
1331
|
+
const parameters = this.mergeTypeParameters(analyzedParts.map((part) => part.typeParameters), selected, resolved.name);
|
|
1332
|
+
const model = {
|
|
1333
|
+
...documentationOf(selected),
|
|
1334
|
+
id,
|
|
1335
|
+
package: owner.name,
|
|
1336
|
+
name: declarationName(selected),
|
|
1337
|
+
kind: "interface",
|
|
1338
|
+
abstract: false,
|
|
1339
|
+
exported: hasModifier(selected, ts.SyntaxKind.ExportKeyword),
|
|
1340
|
+
location: this.location(selected),
|
|
1341
|
+
text: declarationText(selected),
|
|
1342
|
+
typeParameters: parameters,
|
|
1343
|
+
extends: analyzedParts.flatMap((part) => part.heritage.extends),
|
|
1344
|
+
implements: [],
|
|
1345
|
+
members: analyzedParts.flatMap((part) => part.members),
|
|
1346
|
+
parts: analyzedParts.map((part) => part.model)
|
|
1347
|
+
};
|
|
1348
|
+
this.declarations.set(id, model);
|
|
1349
|
+
this.declarationStates.delete(id);
|
|
1350
|
+
return model;
|
|
1351
|
+
}
|
|
1352
|
+
const parameters = ts.isEnumDeclaration(selected) ? [] : this.typeParameters(selected.typeParameters);
|
|
1353
|
+
const heritage = ts.isTypeAliasDeclaration(selected) || ts.isEnumDeclaration(selected) ? {
|
|
1354
|
+
extends: [],
|
|
1355
|
+
implements: []
|
|
1356
|
+
} : this.heritage(selected);
|
|
1357
|
+
const kind = ts.isClassDeclaration(selected) ? "class" : ts.isInterfaceDeclaration(selected) ? "interface" : ts.isTypeAliasDeclaration(selected) ? "alias" : "enum";
|
|
1358
|
+
const model = {
|
|
1359
|
+
...documentationOf(selected),
|
|
1360
|
+
id,
|
|
1361
|
+
package: owner.name,
|
|
1362
|
+
name: declarationName(selected),
|
|
1363
|
+
kind,
|
|
1364
|
+
abstract: hasModifier(selected, ts.SyntaxKind.AbstractKeyword),
|
|
1365
|
+
exported: hasModifier(selected, ts.SyntaxKind.ExportKeyword),
|
|
1366
|
+
location: this.location(selected),
|
|
1367
|
+
text: declarationText(selected),
|
|
1368
|
+
typeParameters: parameters,
|
|
1369
|
+
extends: heritage.extends,
|
|
1370
|
+
implements: heritage.implements,
|
|
1371
|
+
members: ts.isTypeAliasDeclaration(selected) || ts.isEnumDeclaration(selected) ? [] : this.members(selected.members, id),
|
|
1372
|
+
...ts.isTypeAliasDeclaration(selected) ? { type: this.convertType(selected.type) } : {},
|
|
1373
|
+
...ts.isEnumDeclaration(selected) ? { enumMembers: this.enumMembers(selected) } : {}
|
|
1374
|
+
};
|
|
1375
|
+
this.declarations.set(id, model);
|
|
1376
|
+
this.declarationStates.delete(id);
|
|
1377
|
+
return model;
|
|
1378
|
+
}
|
|
1379
|
+
enumMembers(declaration) {
|
|
1380
|
+
return declaration.members.map((member) => ({
|
|
1381
|
+
...documentationOf(member),
|
|
1382
|
+
name: memberName(member.name),
|
|
1383
|
+
...member.initializer === void 0 ? {} : { initializer: member.initializer.getText() },
|
|
1384
|
+
location: this.location(member)
|
|
1385
|
+
}));
|
|
1386
|
+
}
|
|
1387
|
+
heritage(declaration) {
|
|
1388
|
+
const result = {
|
|
1389
|
+
extends: [],
|
|
1390
|
+
implements: []
|
|
1391
|
+
};
|
|
1392
|
+
for (const clause of declaration.heritageClauses ?? []) {
|
|
1393
|
+
const target = clause.token === ts.SyntaxKind.ExtendsKeyword ? result.extends : result.implements;
|
|
1394
|
+
for (const type of clause.types) target.push(this.convertHeritage(type));
|
|
1395
|
+
}
|
|
1396
|
+
return result;
|
|
1397
|
+
}
|
|
1398
|
+
convertHeritage(node) {
|
|
1399
|
+
const symbol = this.checker.getSymbolAtLocation(node.expression);
|
|
1400
|
+
return this.addNode(node, {
|
|
1401
|
+
kind: "reference",
|
|
1402
|
+
name: node.expression.getText(),
|
|
1403
|
+
target: this.targetForReference(this.resolveSymbol(symbol), node),
|
|
1404
|
+
arguments: node.typeArguments?.map((argument) => this.convertType(argument)) ?? []
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1407
|
+
members(members, ownerId) {
|
|
1408
|
+
const result = [];
|
|
1409
|
+
for (const member of members) {
|
|
1410
|
+
if (ts.isPropertyDeclaration(member) && memberName(member.name) === "typertGateway" && member.initializer !== void 0 && ts.isCallExpression(member.initializer) && this.isTypeMetaSymbol(member.initializer.expression, "bindTypeRTGateway")) continue;
|
|
1411
|
+
const visibility = visibilityOf(member);
|
|
1412
|
+
const isStatic = hasModifier(member, ts.SyntaxKind.StaticKeyword);
|
|
1413
|
+
if (visibility !== "public" || isStatic || ts.isConstructorDeclaration(member)) continue;
|
|
1414
|
+
const base = this.memberBase(member, ownerId, visibility, isStatic);
|
|
1415
|
+
if (ts.isPropertySignature(member) || ts.isPropertyDeclaration(member)) {
|
|
1416
|
+
const type = this.requiredType(member, member.type, "property");
|
|
1417
|
+
result.push({
|
|
1418
|
+
...base,
|
|
1419
|
+
kind: "property",
|
|
1420
|
+
type: this.convertType(type)
|
|
1421
|
+
});
|
|
1422
|
+
} else if (ts.isMethodSignature(member) || ts.isMethodDeclaration(member)) result.push({
|
|
1423
|
+
...base,
|
|
1424
|
+
kind: "method",
|
|
1425
|
+
signature: this.signature(member, member.type)
|
|
1426
|
+
});
|
|
1427
|
+
else if (ts.isGetAccessorDeclaration(member)) result.push({
|
|
1428
|
+
...base,
|
|
1429
|
+
kind: "getter",
|
|
1430
|
+
signature: this.signature(member, member.type)
|
|
1431
|
+
});
|
|
1432
|
+
else if (ts.isSetAccessorDeclaration(member)) result.push({
|
|
1433
|
+
...base,
|
|
1434
|
+
kind: "setter",
|
|
1435
|
+
signature: this.signature(member, member.type)
|
|
1436
|
+
});
|
|
1437
|
+
else if (ts.isCallSignatureDeclaration(member)) result.push({
|
|
1438
|
+
...base,
|
|
1439
|
+
kind: "call",
|
|
1440
|
+
signature: this.signature(member, member.type)
|
|
1441
|
+
});
|
|
1442
|
+
else if (ts.isConstructSignatureDeclaration(member)) result.push({
|
|
1443
|
+
...base,
|
|
1444
|
+
kind: "construct",
|
|
1445
|
+
signature: this.signature(member, member.type)
|
|
1446
|
+
});
|
|
1447
|
+
else if (ts.isIndexSignatureDeclaration(member)) result.push({
|
|
1448
|
+
...base,
|
|
1449
|
+
kind: "index",
|
|
1450
|
+
signature: this.signature(member, member.type)
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
return result;
|
|
1454
|
+
}
|
|
1455
|
+
memberBase(member, ownerId, visibility, isStatic) {
|
|
1456
|
+
const identity = member.name !== void 0 ? this.memberIdentity(member.name) : { name: ts.isCallSignatureDeclaration(member) ? "(call)" : ts.isConstructSignatureDeclaration(member) ? "(construct)" : "(index)" };
|
|
1457
|
+
return {
|
|
1458
|
+
...documentationOf(member),
|
|
1459
|
+
id: `${ownerId}#${identity.name}@${String(member.getStart())}`,
|
|
1460
|
+
...identity,
|
|
1461
|
+
optional: "questionToken" in member && member.questionToken !== void 0,
|
|
1462
|
+
readonly: hasModifier(member, ts.SyntaxKind.ReadonlyKeyword),
|
|
1463
|
+
async: hasModifier(member, ts.SyntaxKind.AsyncKeyword),
|
|
1464
|
+
abstract: hasModifier(member, ts.SyntaxKind.AbstractKeyword),
|
|
1465
|
+
static: isStatic,
|
|
1466
|
+
visibility,
|
|
1467
|
+
location: this.location(member),
|
|
1468
|
+
text: memberText(member)
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
memberIdentity(name) {
|
|
1472
|
+
if (!ts.isComputedPropertyName(name)) return { name: memberName(name) };
|
|
1473
|
+
const expression = name.expression;
|
|
1474
|
+
if (ts.isStringLiteral(expression) || ts.isNumericLiteral(expression) || ts.isNoSubstitutionTemplateLiteral(expression)) return {
|
|
1475
|
+
name: memberName(name),
|
|
1476
|
+
jsonName: expression.text
|
|
1477
|
+
};
|
|
1478
|
+
const type = this.checker.getTypeAtLocation(expression);
|
|
1479
|
+
return {
|
|
1480
|
+
name: memberName(name),
|
|
1481
|
+
computed: (type.flags & ts.TypeFlags.UniqueESSymbol) !== 0 ? "symbol" : "dynamic"
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
signature(node, explicitReturn) {
|
|
1485
|
+
const parameters = node.parameters.map((parameter) => ({
|
|
1486
|
+
name: memberName(parameter.name),
|
|
1487
|
+
binding: ts.isIdentifier(parameter.name) ? "identifier" : ts.isObjectBindingPattern(parameter.name) ? "object" : "array",
|
|
1488
|
+
type: this.convertType(this.requiredType(parameter, parameter.type, "parameter")),
|
|
1489
|
+
optional: parameter.questionToken !== void 0 || parameter.initializer !== void 0,
|
|
1490
|
+
rest: parameter.dotDotDotToken !== void 0,
|
|
1491
|
+
receiver: ts.isIdentifier(parameter.name) && parameter.name.text === "this",
|
|
1492
|
+
...parameter.initializer === void 0 ? {} : { initializer: parameter.initializer.getText() }
|
|
1493
|
+
}));
|
|
1494
|
+
return {
|
|
1495
|
+
typeParameters: this.typeParameters(node.typeParameters),
|
|
1496
|
+
parameters,
|
|
1497
|
+
returns: ts.isSetAccessorDeclaration(node) ? this.addNode(node, {
|
|
1498
|
+
kind: "keyword",
|
|
1499
|
+
name: "void"
|
|
1500
|
+
}) : this.convertType(this.requiredType(node, explicitReturn, "return"))
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
typeParameters(parameters) {
|
|
1504
|
+
return parameters?.map((parameter) => ({
|
|
1505
|
+
id: `${this.locationKey(parameter)}#${parameter.name.text}`,
|
|
1506
|
+
name: parameter.name.text,
|
|
1507
|
+
const: hasModifier(parameter, ts.SyntaxKind.ConstKeyword),
|
|
1508
|
+
...parameter.constraint === void 0 ? {} : { constraint: this.convertType(parameter.constraint) },
|
|
1509
|
+
...parameter.default === void 0 ? {} : { default: this.convertType(parameter.default) },
|
|
1510
|
+
...hasModifier(parameter, ts.SyntaxKind.InKeyword) && hasModifier(parameter, ts.SyntaxKind.OutKeyword) ? { variance: "in-out" } : hasModifier(parameter, ts.SyntaxKind.InKeyword) ? { variance: "in" } : hasModifier(parameter, ts.SyntaxKind.OutKeyword) ? { variance: "out" } : {}
|
|
1511
|
+
})) ?? [];
|
|
1512
|
+
}
|
|
1513
|
+
mergeTypeParameters(parts, site, declarationName) {
|
|
1514
|
+
return parts[0].map((parameter, index) => {
|
|
1515
|
+
const peers = parts.map((part) => part[index]);
|
|
1516
|
+
const constraint = peers.find((peer) => peer.constraint !== void 0)?.constraint;
|
|
1517
|
+
const fallback = peers.find((peer) => peer.default !== void 0)?.default;
|
|
1518
|
+
const variances = [...new Set(peers.flatMap((peer) => peer.variance === void 0 ? [] : [peer.variance]))];
|
|
1519
|
+
if (variances.length > 1) this.fail(site, `merged interface ${declarationName} has incompatible variance modifiers`);
|
|
1520
|
+
return {
|
|
1521
|
+
id: parameter.id,
|
|
1522
|
+
name: parameter.name,
|
|
1523
|
+
const: peers.some((peer) => peer.const),
|
|
1524
|
+
...constraint === void 0 ? {} : { constraint },
|
|
1525
|
+
...fallback === void 0 ? {} : { default: fallback },
|
|
1526
|
+
...variances[0] === void 0 ? {} : { variance: variances[0] }
|
|
1527
|
+
};
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
1530
|
+
requiredType(owner, type, purpose) {
|
|
1531
|
+
if (type !== void 0) return type;
|
|
1532
|
+
if (this.mode === "check") this.fail(owner, `public ${purpose} is missing an explicit type annotation`);
|
|
1533
|
+
const inferred = this.inferType(owner, purpose);
|
|
1534
|
+
const rendered = ts.createPrinter().printNode(ts.EmitHint.Unspecified, inferred, owner.getSourceFile());
|
|
1535
|
+
const position = annotationPosition(owner, purpose);
|
|
1536
|
+
this.queueEdit({
|
|
1537
|
+
file: realPath(owner.getSourceFile().fileName),
|
|
1538
|
+
position,
|
|
1539
|
+
text: `: ${rendered}`
|
|
1540
|
+
});
|
|
1541
|
+
throw new SourceEditQueued();
|
|
1542
|
+
}
|
|
1543
|
+
inferType(owner, purpose) {
|
|
1544
|
+
let type;
|
|
1545
|
+
if (purpose === "return") {
|
|
1546
|
+
const signature = this.checker.getSignatureFromDeclaration(owner);
|
|
1547
|
+
type = this.checker.getReturnTypeOfSignature(signature);
|
|
1548
|
+
} else type = this.checker.getTypeAtLocation(owner);
|
|
1549
|
+
return this.checker.typeToTypeNode(type, owner, ts.NodeBuilderFlags.NoTruncation | ts.NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope);
|
|
1550
|
+
}
|
|
1551
|
+
convertType(node) {
|
|
1552
|
+
const id = this.allocateNodeId(node);
|
|
1553
|
+
const add = (model) => {
|
|
1554
|
+
this.nodes.set(id, {
|
|
1555
|
+
id,
|
|
1556
|
+
...model
|
|
1557
|
+
});
|
|
1558
|
+
return id;
|
|
1559
|
+
};
|
|
1560
|
+
const keyword = keywordName(node.kind);
|
|
1561
|
+
if (keyword !== void 0) return add({
|
|
1562
|
+
kind: "keyword",
|
|
1563
|
+
name: keyword
|
|
1564
|
+
});
|
|
1565
|
+
if (ts.isParenthesizedTypeNode(node)) return add({
|
|
1566
|
+
kind: "parenthesized",
|
|
1567
|
+
type: this.convertType(node.type)
|
|
1568
|
+
});
|
|
1569
|
+
if (ts.isLiteralTypeNode(node)) return add(literalModel(node));
|
|
1570
|
+
if (ts.isTypeReferenceNode(node)) {
|
|
1571
|
+
const symbol = this.checker.getSymbolAtLocation(node.typeName);
|
|
1572
|
+
return add({
|
|
1573
|
+
kind: "reference",
|
|
1574
|
+
name: node.typeName.getText(),
|
|
1575
|
+
target: this.targetForReference(this.resolveSymbol(symbol), node),
|
|
1576
|
+
arguments: node.typeArguments?.map((argument) => this.convertType(argument)) ?? []
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
if (ts.isUnionTypeNode(node) || ts.isIntersectionTypeNode(node)) return add({
|
|
1580
|
+
kind: ts.isUnionTypeNode(node) ? "union" : "intersection",
|
|
1581
|
+
types: node.types.map((type) => this.convertType(type))
|
|
1582
|
+
});
|
|
1583
|
+
if (ts.isArrayTypeNode(node)) return add({
|
|
1584
|
+
kind: "array",
|
|
1585
|
+
element: this.convertType(node.elementType)
|
|
1586
|
+
});
|
|
1587
|
+
if (ts.isTupleTypeNode(node)) return add({
|
|
1588
|
+
kind: "tuple",
|
|
1589
|
+
elements: node.elements.map((element) => {
|
|
1590
|
+
const named = ts.isNamedTupleMember(element) ? element : void 0;
|
|
1591
|
+
const raw = named?.type ?? element;
|
|
1592
|
+
const optional = named?.questionToken !== void 0 || ts.isOptionalTypeNode(raw);
|
|
1593
|
+
const rest = named?.dotDotDotToken !== void 0 || ts.isRestTypeNode(raw);
|
|
1594
|
+
const type = ts.isOptionalTypeNode(raw) || ts.isRestTypeNode(raw) ? raw.type : raw;
|
|
1595
|
+
return {
|
|
1596
|
+
...named === void 0 ? {} : { name: named.name.text },
|
|
1597
|
+
type: this.convertType(type),
|
|
1598
|
+
optional,
|
|
1599
|
+
rest
|
|
1600
|
+
};
|
|
1601
|
+
})
|
|
1602
|
+
});
|
|
1603
|
+
if (ts.isTypeLiteralNode(node)) return add({
|
|
1604
|
+
kind: "object",
|
|
1605
|
+
members: this.members(node.members, id)
|
|
1606
|
+
});
|
|
1607
|
+
if (ts.isFunctionTypeNode(node)) return add({
|
|
1608
|
+
kind: "function",
|
|
1609
|
+
signature: this.signature(node, node.type)
|
|
1610
|
+
});
|
|
1611
|
+
if (ts.isConstructorTypeNode(node)) return add({
|
|
1612
|
+
kind: "constructor",
|
|
1613
|
+
abstract: hasModifier(node, ts.SyntaxKind.AbstractKeyword),
|
|
1614
|
+
signature: this.signature(node, node.type)
|
|
1615
|
+
});
|
|
1616
|
+
if (ts.isIndexedAccessTypeNode(node)) return add({
|
|
1617
|
+
kind: "indexed-access",
|
|
1618
|
+
object: this.convertType(node.objectType),
|
|
1619
|
+
index: this.convertType(node.indexType)
|
|
1620
|
+
});
|
|
1621
|
+
if (ts.isTypeOperatorNode(node)) return add({
|
|
1622
|
+
kind: "operator",
|
|
1623
|
+
operator: ts.tokenToString(node.operator),
|
|
1624
|
+
type: this.convertType(node.type)
|
|
1625
|
+
});
|
|
1626
|
+
if (ts.isConditionalTypeNode(node)) return add({
|
|
1627
|
+
kind: "conditional",
|
|
1628
|
+
check: this.convertType(node.checkType),
|
|
1629
|
+
extends: this.convertType(node.extendsType),
|
|
1630
|
+
whenTrue: this.convertType(node.trueType),
|
|
1631
|
+
whenFalse: this.convertType(node.falseType)
|
|
1632
|
+
});
|
|
1633
|
+
if (ts.isInferTypeNode(node)) return add({
|
|
1634
|
+
kind: "infer",
|
|
1635
|
+
parameter: this.typeParameters(ts.factory.createNodeArray([node.typeParameter]))[0]
|
|
1636
|
+
});
|
|
1637
|
+
if (ts.isMappedTypeNode(node)) {
|
|
1638
|
+
const parameter = this.typeParameters(ts.factory.createNodeArray([node.typeParameter]))[0];
|
|
1639
|
+
return add({
|
|
1640
|
+
kind: "mapped",
|
|
1641
|
+
parameter,
|
|
1642
|
+
...node.nameType === void 0 ? {} : { nameType: this.convertType(node.nameType) },
|
|
1643
|
+
...node.type === void 0 ? {} : { value: this.convertType(node.type) },
|
|
1644
|
+
readonly: modifierMode(node.readonlyToken),
|
|
1645
|
+
optional: modifierMode(node.questionToken)
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
if (ts.isTemplateLiteralTypeNode(node)) return add({
|
|
1649
|
+
kind: "template-literal",
|
|
1650
|
+
head: node.head.text,
|
|
1651
|
+
spans: node.templateSpans.map((span) => ({
|
|
1652
|
+
type: this.convertType(span.type),
|
|
1653
|
+
text: span.literal.text
|
|
1654
|
+
}))
|
|
1655
|
+
});
|
|
1656
|
+
if (ts.isTypeQueryNode(node)) return add({
|
|
1657
|
+
kind: "type-query",
|
|
1658
|
+
expression: node.exprName.getText(),
|
|
1659
|
+
arguments: node.typeArguments?.map((argument) => this.convertType(argument)) ?? []
|
|
1660
|
+
});
|
|
1661
|
+
if (ts.isImportTypeNode(node)) {
|
|
1662
|
+
const argument = node.argument;
|
|
1663
|
+
const symbol = node.qualifier === void 0 ? void 0 : this.checker.getSymbolAtLocation(node.qualifier);
|
|
1664
|
+
return add({
|
|
1665
|
+
kind: "import-type",
|
|
1666
|
+
module: argument.literal.text,
|
|
1667
|
+
...node.qualifier === void 0 ? {} : { qualifier: node.qualifier.getText() },
|
|
1668
|
+
arguments: node.typeArguments?.map((argument) => this.convertType(argument)) ?? [],
|
|
1669
|
+
typeof: node.isTypeOf,
|
|
1670
|
+
...node.attributes === void 0 ? {} : { attributes: importTypeAttributesText(node) },
|
|
1671
|
+
...symbol === void 0 ? {} : { target: this.targetForReference(this.resolveSymbol(symbol), node) }
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1674
|
+
if (ts.isTypePredicateNode(node)) return add({
|
|
1675
|
+
kind: "predicate",
|
|
1676
|
+
asserts: node.assertsModifier !== void 0,
|
|
1677
|
+
parameter: node.parameterName.getText(),
|
|
1678
|
+
...node.type === void 0 ? {} : { type: this.convertType(node.type) }
|
|
1679
|
+
});
|
|
1680
|
+
/* v8 ignore else -- every source TypeNode kind accepted by TypeScript is handled above; this arm keeps
|
|
1681
|
+
* future compiler kinds fail-loud. */
|
|
1682
|
+
if (ts.isThisTypeNode(node)) return add({ kind: "this" });
|
|
1683
|
+
/* v8 ignore next -- paired with the exhaustive TypeNode guard above. */
|
|
1684
|
+
this.fail(node, `unsupported TypeScript type node ${ts.SyntaxKind[node.kind]}`);
|
|
1685
|
+
}
|
|
1686
|
+
addNode(site, model) {
|
|
1687
|
+
const id = this.allocateNodeId(site);
|
|
1688
|
+
this.nodes.set(id, {
|
|
1689
|
+
id,
|
|
1690
|
+
...model
|
|
1691
|
+
});
|
|
1692
|
+
return id;
|
|
1693
|
+
}
|
|
1694
|
+
referenceNode(symbol, site) {
|
|
1695
|
+
return this.addNode(site, {
|
|
1696
|
+
kind: "reference",
|
|
1697
|
+
name: symbol.name,
|
|
1698
|
+
target: {
|
|
1699
|
+
kind: "declaration",
|
|
1700
|
+
symbol: this.symbolId(symbol)
|
|
1701
|
+
},
|
|
1702
|
+
arguments: []
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1705
|
+
targetForReference(symbol, site) {
|
|
1706
|
+
const declaration = preferredDeclaration(symbol);
|
|
1707
|
+
/* v8 ignore next -- a symbol from a semantically valid source type reference always has a declaration. */
|
|
1708
|
+
if (declaration === void 0) this.fail(site, `type symbol ${symbol.name} has no declaration`);
|
|
1709
|
+
if (ts.isTypeParameterDeclaration(declaration)) return {
|
|
1710
|
+
kind: "type-parameter",
|
|
1711
|
+
parameter: `${this.locationKey(declaration)}#${declaration.name.text}`
|
|
1712
|
+
};
|
|
1713
|
+
if (isStandardLibraryFile(declaration.getSourceFile().fileName)) return {
|
|
1714
|
+
kind: "standard",
|
|
1715
|
+
name: symbol.name
|
|
1716
|
+
};
|
|
1717
|
+
const moduleSpecifier = moduleSpecifierOf(site);
|
|
1718
|
+
const module = moduleSpecifier === void 0 ? void 0 : moduleIdentity(moduleSpecifier);
|
|
1719
|
+
const from = this.registrationForFile(site.getSourceFile().fileName);
|
|
1720
|
+
const owner = this.registrationForFile(declaration.getSourceFile().fileName);
|
|
1721
|
+
if (owner !== void 0) {
|
|
1722
|
+
if (owner.name !== from.name) {
|
|
1723
|
+
if (module === void 0) this.fail(site, `reference to ${symbol.name} crosses a package without an explicit package import`);
|
|
1724
|
+
const exportName = authoredExportName(site, moduleSpecifier);
|
|
1725
|
+
if (this.packageExportName(module, symbol, owner.face, exportName) === void 0) this.fail(site, `package reference ${exportName} is not exported by ${module.package} at ${module.subpath}`);
|
|
1726
|
+
}
|
|
1727
|
+
const typeDeclaration = declaration;
|
|
1728
|
+
if (!this.declarationStates.has(this.symbolId(symbol))) this.ensureDeclaration(symbol, typeDeclaration);
|
|
1729
|
+
return {
|
|
1730
|
+
kind: "declaration",
|
|
1731
|
+
symbol: this.symbolId(symbol)
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
const otherFace = (module === void 0 ? [] : [...new Set(this.allRegistrations.filter((candidate) => candidate.name === module.package).map((candidate) => candidate.face))]).find((face) => face !== this.face);
|
|
1735
|
+
if (otherFace !== void 0 && module !== void 0) {
|
|
1736
|
+
const requestedName = authoredExportName(site, moduleSpecifier);
|
|
1737
|
+
const exportName = this.packageExportName(module, symbol, otherFace, requestedName);
|
|
1738
|
+
if (exportName === void 0) this.fail(site, `cross-face reference ${requestedName} is not exported by ${module.package} at ${module.subpath}`);
|
|
1739
|
+
this.recordCrossFaceLink(from.name, otherFace, module, exportName);
|
|
1740
|
+
return {
|
|
1741
|
+
kind: "cross-face",
|
|
1742
|
+
face: otherFace,
|
|
1743
|
+
package: module.package,
|
|
1744
|
+
subpath: module.subpath,
|
|
1745
|
+
name: exportName
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
if (module !== void 0) return {
|
|
1749
|
+
kind: "external",
|
|
1750
|
+
module: module.package,
|
|
1751
|
+
subpath: module.subpath,
|
|
1752
|
+
name: symbol.name
|
|
1753
|
+
};
|
|
1754
|
+
const external = externalModuleIdentityForFile(declaration.getSourceFile().fileName);
|
|
1755
|
+
if (external !== void 0) return {
|
|
1756
|
+
kind: "external",
|
|
1757
|
+
module: external.package,
|
|
1758
|
+
subpath: external.subpath,
|
|
1759
|
+
name: symbol.name
|
|
1760
|
+
};
|
|
1761
|
+
this.fail(site, `reference to ${symbol.name} crosses a package or face without an explicit import`);
|
|
1762
|
+
}
|
|
1763
|
+
recordCrossFaceLink(fromPackage, toFace, module, name) {
|
|
1764
|
+
const link = {
|
|
1765
|
+
fromFace: this.face,
|
|
1766
|
+
fromPackage,
|
|
1767
|
+
toFace,
|
|
1768
|
+
toPackage: module.package,
|
|
1769
|
+
subpath: module.subpath,
|
|
1770
|
+
name
|
|
1771
|
+
};
|
|
1772
|
+
const key = [
|
|
1773
|
+
link.fromFace,
|
|
1774
|
+
link.fromPackage,
|
|
1775
|
+
link.toFace,
|
|
1776
|
+
link.toPackage,
|
|
1777
|
+
link.subpath,
|
|
1778
|
+
link.name
|
|
1779
|
+
].join("\0");
|
|
1780
|
+
this.crossFaceLinks.set(key, link);
|
|
1781
|
+
}
|
|
1782
|
+
packageExportName(module, symbol, face, requestedName) {
|
|
1783
|
+
const registration = this.allRegistrations.find((candidate) => candidate.face === face && candidate.name === module.package);
|
|
1784
|
+
const target = packageExportTargets(registration.manifest).find(([subpath]) => subpath === module.subpath)?.[1];
|
|
1785
|
+
if (target === void 0) return void 0;
|
|
1786
|
+
const sourceFile = this.sourceFiles.get(realPath(sourcePathForExport(registration.root, target)));
|
|
1787
|
+
const moduleSymbol = this.checker.getSymbolAtLocation(sourceFile);
|
|
1788
|
+
return this.checker.getExportsOfModule(moduleSymbol).find((candidate) => candidate.name === requestedName && this.resolveSymbol(candidate) === symbol)?.name;
|
|
1789
|
+
}
|
|
1790
|
+
symbolAtType(node) {
|
|
1791
|
+
if (ts.isTypeReferenceNode(node)) return this.resolveSymbol(this.checker.getSymbolAtLocation(node.typeName));
|
|
1792
|
+
const type = this.checker.getTypeAtLocation(node);
|
|
1793
|
+
const symbol = type.aliasSymbol ?? type.getSymbol();
|
|
1794
|
+
return symbol === void 0 ? void 0 : this.resolveSymbol(symbol);
|
|
1795
|
+
}
|
|
1796
|
+
resolveSymbol(symbol) {
|
|
1797
|
+
return (symbol.flags & ts.SymbolFlags.Alias) === 0 ? symbol : this.checker.getAliasedSymbol(symbol);
|
|
1798
|
+
}
|
|
1799
|
+
symbolId(symbol) {
|
|
1800
|
+
const declaration = preferredDeclaration(symbol);
|
|
1801
|
+
if (declaration === void 0) return `symbol:${symbol.name}`;
|
|
1802
|
+
const location = this.location(declaration);
|
|
1803
|
+
return `${this.packageNameForFile(declaration.getSourceFile().fileName)}:${location.file}#${symbol.name}`;
|
|
1804
|
+
}
|
|
1805
|
+
registrationForFile(file) {
|
|
1806
|
+
const path = realPath(file);
|
|
1807
|
+
return this.allRegistrations.find((registration) => registration.face === this.face && isWithin(path, registration.root));
|
|
1808
|
+
}
|
|
1809
|
+
packageNameForFile(file) {
|
|
1810
|
+
const path = realPath(file);
|
|
1811
|
+
return this.allRegistrations.find((registration) => isWithin(path, registration.root))?.name ?? "<external>";
|
|
1812
|
+
}
|
|
1813
|
+
allocateNodeId(site) {
|
|
1814
|
+
const location = this.locationKey(site);
|
|
1815
|
+
const ordinal = (this.nodeOrdinals.get(location) ?? 0) + 1;
|
|
1816
|
+
this.nodeOrdinals.set(location, ordinal);
|
|
1817
|
+
return `type:${location}#${String(ordinal)}`;
|
|
1818
|
+
}
|
|
1819
|
+
locationKey(node) {
|
|
1820
|
+
const location = this.location(node);
|
|
1821
|
+
return `${location.file}:${String(location.line)}:${String(location.column)}`;
|
|
1822
|
+
}
|
|
1823
|
+
location(node) {
|
|
1824
|
+
const sourceFile = node.getSourceFile();
|
|
1825
|
+
const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
1826
|
+
return {
|
|
1827
|
+
file: slash(relative(this.root, sourceFile.fileName)),
|
|
1828
|
+
line: position.line + 1,
|
|
1829
|
+
column: position.character + 1
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1832
|
+
fail(node, message) {
|
|
1833
|
+
const location = this.location(node);
|
|
1834
|
+
throw new TypertAnalysisError(`typert(${this.face}): ${location.file}:${String(location.line)}:${String(location.column)}: ${message}`);
|
|
1835
|
+
}
|
|
1836
|
+
};
|
|
1837
|
+
function mergeWorkspaceModels(models) {
|
|
1838
|
+
const faces = /* @__PURE__ */ new Map();
|
|
1839
|
+
const links = /* @__PURE__ */ new Map();
|
|
1840
|
+
for (const model of models) {
|
|
1841
|
+
for (const face of model.faces) {
|
|
1842
|
+
const merged = faces.get(face.face) ?? {
|
|
1843
|
+
packages: /* @__PURE__ */ new Map(),
|
|
1844
|
+
declarations: /* @__PURE__ */ new Map(),
|
|
1845
|
+
nodes: /* @__PURE__ */ new Map()
|
|
1846
|
+
};
|
|
1847
|
+
for (const packageModel of face.packages) merged.packages.set(packageModel.name, packageModel);
|
|
1848
|
+
for (const declaration of face.graph.declarations) if (!merged.declarations.has(declaration.id)) merged.declarations.set(declaration.id, declaration);
|
|
1849
|
+
for (const node of face.graph.nodes) if (!merged.nodes.has(node.id)) merged.nodes.set(node.id, node);
|
|
1850
|
+
faces.set(face.face, merged);
|
|
1851
|
+
}
|
|
1852
|
+
for (const link of model.crossFaceLinks) links.set([
|
|
1853
|
+
link.fromFace,
|
|
1854
|
+
link.fromPackage,
|
|
1855
|
+
link.toFace,
|
|
1856
|
+
link.toPackage,
|
|
1857
|
+
link.subpath,
|
|
1858
|
+
link.name
|
|
1859
|
+
].join("\0"), link);
|
|
1860
|
+
}
|
|
1861
|
+
return {
|
|
1862
|
+
faces: [...faces].sort(([left], [right]) => (left === "host" ? 0 : 1) - (right === "host" ? 0 : 1)).map(([face, model]) => ({
|
|
1863
|
+
face,
|
|
1864
|
+
packages: [...model.packages.values()].sort((left, right) => left.name.localeCompare(right.name)),
|
|
1865
|
+
graph: {
|
|
1866
|
+
declarations: [...model.declarations.values()].sort((left, right) => left.id.localeCompare(right.id)),
|
|
1867
|
+
nodes: [...model.nodes.values()].sort((left, right) => left.id.localeCompare(right.id))
|
|
1868
|
+
}
|
|
1869
|
+
})),
|
|
1870
|
+
crossFaceLinks: [...links.values()].sort(compareCrossFaceLinks)
|
|
1871
|
+
};
|
|
1872
|
+
}
|
|
1873
|
+
function parseConfig(path) {
|
|
1874
|
+
const compilerPath = path.split(sep).join("/");
|
|
1875
|
+
const read = ts.readConfigFile(compilerPath, (file) => ts.sys.readFile(file));
|
|
1876
|
+
if (read.error !== void 0) throw new TypertAnalysisError(formatDiagnostic(read.error));
|
|
1877
|
+
const parsed = ts.parseJsonConfigFileContent(read.config, ts.sys, dirname(compilerPath), void 0, compilerPath);
|
|
1878
|
+
if (parsed.errors.length > 0) throw new TypertAnalysisError(parsed.errors.map(formatDiagnostic).join("\n"));
|
|
1879
|
+
return {
|
|
1880
|
+
path,
|
|
1881
|
+
parsed
|
|
1882
|
+
};
|
|
1883
|
+
}
|
|
1884
|
+
function projectConfigPath(path) {
|
|
1885
|
+
if (extname(path) === ".json") return path;
|
|
1886
|
+
return join(path, "tsconfig.json");
|
|
1887
|
+
}
|
|
1888
|
+
function sourceFileHasSurface(sourceFile) {
|
|
1889
|
+
for (const statement of sourceFile.statements) {
|
|
1890
|
+
if ((ts.isClassDeclaration(statement) || ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement) || ts.isEnumDeclaration(statement)) && (typertMode(statement) !== void 0 || typertServiceTag(statement) !== void 0)) return true;
|
|
1891
|
+
if (ts.isClassDeclaration(statement)) for (const member of statement.members) {
|
|
1892
|
+
if (ts.isPropertyDeclaration(member) && memberName(member.name) === "typertGateway" && member.initializer !== void 0 && ts.isCallExpression(member.initializer) && expressionName(member.initializer.expression) === "bindTypeRTGateway") return true;
|
|
1893
|
+
for (const decorator of ts.canHaveDecorators(member) ? ts.getDecorators(member) ?? [] : []) {
|
|
1894
|
+
const name = expressionName(ts.isCallExpression(decorator.expression) ? decorator.expression.expression : decorator.expression);
|
|
1895
|
+
if (name === "Remote" || name === "RemoteScope") return true;
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
if (!ts.isModuleDeclaration(statement) || !ts.isStringLiteral(statement.name) || statement.name.text !== "@deepseek-ai/cordis" || statement.body === void 0 || !ts.isModuleBlock(statement.body)) continue;
|
|
1899
|
+
if (statement.body.statements.some((member) => ts.isInterfaceDeclaration(member) && (member.name.text === "Context" || member.name.text === "Events") && member.members.length > 0)) return true;
|
|
1900
|
+
}
|
|
1901
|
+
return false;
|
|
1902
|
+
}
|
|
1903
|
+
function hasPackageSurface(model) {
|
|
1904
|
+
return model.services.length > 0 || model.events.length > 0 || model.objects.length > 0 || model.schemas.length > 0 || model.invocations.length > 0;
|
|
1905
|
+
}
|
|
1906
|
+
function isDualFacePackage(manifest) {
|
|
1907
|
+
const dsh = manifest.dsh;
|
|
1908
|
+
const client = dsh !== null && typeof dsh === "object" ? dsh.client : void 0;
|
|
1909
|
+
return client !== null && typeof client === "object" && clientExportSubpaths(manifest).length > 0;
|
|
1910
|
+
}
|
|
1911
|
+
function hostExportSubpaths(manifest) {
|
|
1912
|
+
return packageExportTargets(manifest).map(([subpath]) => subpath).filter((subpath) => subpath !== "./client" && !subpath.startsWith("./client/") && subpath !== "./remote");
|
|
1913
|
+
}
|
|
1914
|
+
function clientExportSubpaths(manifest) {
|
|
1915
|
+
return packageExportTargets(manifest).map(([subpath]) => subpath).filter((subpath) => subpath === "./client" || subpath.startsWith("./client/"));
|
|
1916
|
+
}
|
|
1917
|
+
function packageExportTargets(manifest) {
|
|
1918
|
+
const exportsField = manifest.exports;
|
|
1919
|
+
if (typeof exportsField === "string") return [[".", exportsField]];
|
|
1920
|
+
if (exportsField === null || typeof exportsField !== "object") {
|
|
1921
|
+
const types = manifest.types;
|
|
1922
|
+
return typeof types === "string" ? [[".", types]] : [];
|
|
1923
|
+
}
|
|
1924
|
+
if (Array.isArray(exportsField) || !Object.keys(exportsField).some((key) => key.startsWith("."))) {
|
|
1925
|
+
const target = exportTarget(exportsField);
|
|
1926
|
+
return target === void 0 ? [] : [[".", target]];
|
|
1927
|
+
}
|
|
1928
|
+
const result = [];
|
|
1929
|
+
for (const [subpath, value] of Object.entries(exportsField)) {
|
|
1930
|
+
if (!subpath.startsWith(".")) continue;
|
|
1931
|
+
const target = exportTarget(value);
|
|
1932
|
+
if (target !== void 0) result.push([subpath, target]);
|
|
1933
|
+
}
|
|
1934
|
+
return result.sort(([left], [right]) => left.localeCompare(right));
|
|
1935
|
+
}
|
|
1936
|
+
function exportTarget(value) {
|
|
1937
|
+
if (typeof value === "string") return value;
|
|
1938
|
+
if (Array.isArray(value)) {
|
|
1939
|
+
for (const candidate of value) {
|
|
1940
|
+
const target = exportTarget(candidate);
|
|
1941
|
+
if (target !== void 0) return target;
|
|
1942
|
+
}
|
|
1943
|
+
return;
|
|
1944
|
+
}
|
|
1945
|
+
if (value === null || typeof value !== "object") return void 0;
|
|
1946
|
+
const conditions = value;
|
|
1947
|
+
for (const key of [
|
|
1948
|
+
"types",
|
|
1949
|
+
"import",
|
|
1950
|
+
"default"
|
|
1951
|
+
]) {
|
|
1952
|
+
const target = exportTarget(conditions[key]);
|
|
1953
|
+
if (target !== void 0) return target;
|
|
1954
|
+
}
|
|
1955
|
+
for (const candidate of Object.values(conditions)) {
|
|
1956
|
+
const target = exportTarget(candidate);
|
|
1957
|
+
if (target !== void 0) return target;
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
function sourcePathForExport(packageRoot, target) {
|
|
1961
|
+
const normalized = target.replace(/^\.\//, "");
|
|
1962
|
+
if (normalized.startsWith("lib/types/")) return resolve(packageRoot, "src", normalized.slice(10).replace(/\.d\.(?:mts|cts|ts)$/, ".ts"));
|
|
1963
|
+
if (normalized.startsWith("lib/")) return resolve(packageRoot, "src", normalized.slice(4).replace(/\.(?:mjs|cjs|js|d\.ts)$/, ".ts"));
|
|
1964
|
+
return resolve(packageRoot, normalized);
|
|
1965
|
+
}
|
|
1966
|
+
function preferredDeclaration(symbol) {
|
|
1967
|
+
return symbol.declarations?.find(isTypeDeclaration) ?? symbol.valueDeclaration ?? symbol.declarations?.[0];
|
|
1968
|
+
}
|
|
1969
|
+
function optionalParent(node) {
|
|
1970
|
+
return node.parent;
|
|
1971
|
+
}
|
|
1972
|
+
function isTypeDeclaration(node) {
|
|
1973
|
+
return ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node);
|
|
1974
|
+
}
|
|
1975
|
+
function declarationName(declaration) {
|
|
1976
|
+
return declaration.name.text;
|
|
1977
|
+
}
|
|
1978
|
+
function memberText(member) {
|
|
1979
|
+
const sourceFile = member.getSourceFile();
|
|
1980
|
+
const full = member.getText(sourceFile);
|
|
1981
|
+
const body = member.body;
|
|
1982
|
+
return (body === void 0 ? full : full.slice(0, full.length - body.getText(sourceFile).length)).replace(/\s*;?\s*$/, "").replace(/\s+/g, " ").trim();
|
|
1983
|
+
}
|
|
1984
|
+
function declarationText(declaration) {
|
|
1985
|
+
const printer = ts.createPrinter({ removeComments: true });
|
|
1986
|
+
const projected = ts.isClassDeclaration(declaration) ? classShape(declaration) : declaration;
|
|
1987
|
+
return printer.printNode(ts.EmitHint.Unspecified, projected, declaration.getSourceFile()).replace(/\r/g, "");
|
|
1988
|
+
}
|
|
1989
|
+
function classShape(node) {
|
|
1990
|
+
const nonPublic = (member) => (ts.canHaveModifiers(member) ? ts.getModifiers(member) : void 0)?.some((modifier) => modifier.kind === ts.SyntaxKind.PrivateKeyword || modifier.kind === ts.SyntaxKind.ProtectedKeyword) ?? false;
|
|
1991
|
+
const members = node.members.flatMap((member) => {
|
|
1992
|
+
if (nonPublic(member) || ts.isPropertyDeclaration(member) && ts.isPrivateIdentifier(member.name)) return [];
|
|
1993
|
+
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)];
|
|
1994
|
+
if (ts.isConstructorDeclaration(member)) return [ts.factory.updateConstructorDeclaration(member, member.modifiers, member.parameters, void 0)];
|
|
1995
|
+
if (ts.isGetAccessorDeclaration(member)) return [ts.factory.updateGetAccessorDeclaration(member, member.modifiers, member.name, member.parameters, member.type, void 0)];
|
|
1996
|
+
if (ts.isSetAccessorDeclaration(member)) return [ts.factory.updateSetAccessorDeclaration(member, member.modifiers, member.name, member.parameters, void 0)];
|
|
1997
|
+
if (ts.isPropertyDeclaration(member)) return [ts.factory.updatePropertyDeclaration(member, member.modifiers, member.name, member.questionToken ?? member.exclamationToken, member.type, void 0)];
|
|
1998
|
+
return [member];
|
|
1999
|
+
});
|
|
2000
|
+
return ts.factory.updateClassDeclaration(node, node.modifiers, node.name, node.typeParameters, node.heritageClauses, members);
|
|
2001
|
+
}
|
|
2002
|
+
function documentationOf(node) {
|
|
2003
|
+
const block = ts.getJSDocCommentsAndTags(node).filter(ts.isJSDoc).at(-1);
|
|
2004
|
+
if (block === void 0) return EMPTY_DOCUMENTATION;
|
|
2005
|
+
const description = normalizedDocText(ts.getTextOfJSDocComment(block.comment));
|
|
2006
|
+
const tags = ts.getJSDocTags(node).map((tag) => {
|
|
2007
|
+
const named = tag;
|
|
2008
|
+
const comment = normalizedDocText(ts.getTextOfJSDocComment(tag.comment));
|
|
2009
|
+
return {
|
|
2010
|
+
name: tag.tagName.text,
|
|
2011
|
+
...named.name === void 0 ? {} : { argument: named.name.getText() },
|
|
2012
|
+
...comment === void 0 ? {} : { comment },
|
|
2013
|
+
text: tag.getText(tag.getSourceFile()).trim()
|
|
2014
|
+
};
|
|
2015
|
+
});
|
|
2016
|
+
return {
|
|
2017
|
+
...description === void 0 ? {} : {
|
|
2018
|
+
description,
|
|
2019
|
+
summary: firstSentence$1(description)
|
|
2020
|
+
},
|
|
2021
|
+
tags,
|
|
2022
|
+
jsDoc: rawJsDoc(node)
|
|
2023
|
+
};
|
|
2024
|
+
}
|
|
2025
|
+
function normalizedDocText(value) {
|
|
2026
|
+
if (value === void 0) return void 0;
|
|
2027
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
2028
|
+
/* v8 ignore next -- TypeScript represents whitespace-only JSDoc as undefined before this helper is called. */
|
|
2029
|
+
return normalized.length === 0 ? void 0 : normalized;
|
|
2030
|
+
}
|
|
2031
|
+
function firstSentence$1(value) {
|
|
2032
|
+
return (/^(.*?[.!?])(?:\s|$)/.exec(value)?.[1] ?? value).trim();
|
|
2033
|
+
}
|
|
2034
|
+
function rawJsDoc(node) {
|
|
2035
|
+
const sourceFile = node.getSourceFile();
|
|
2036
|
+
const source = sourceFile.getFullText();
|
|
2037
|
+
const range = ts.getLeadingCommentRanges(source, node.getFullStart()).filter((candidate) => source.slice(candidate.pos, candidate.pos + 3) === "/**").at(-1);
|
|
2038
|
+
const raw = source.slice(range.pos, range.end);
|
|
2039
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(range.pos);
|
|
2040
|
+
const lineStart = sourceFile.getPositionOfLineAndCharacter(line, 0);
|
|
2041
|
+
const indent = source.slice(lineStart, range.pos);
|
|
2042
|
+
return raw.split("\n").map((text, index) => index > 0 && text.startsWith(indent) ? text.slice(indent.length) : text).join("\n");
|
|
2043
|
+
}
|
|
2044
|
+
function typertMode(node) {
|
|
2045
|
+
for (const tag of ts.getJSDocTags(node)) {
|
|
2046
|
+
if (tag.tagName.text !== "typert") continue;
|
|
2047
|
+
const mode = (ts.getTextOfJSDocComment(tag.comment) ?? "").trim().split(/\s+/, 1)[0];
|
|
2048
|
+
if (mode === "object") return "object";
|
|
2049
|
+
if (mode === "" || mode === "schema" || mode === "type") return "schema";
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
function typertServiceTag(node) {
|
|
2053
|
+
return ts.getJSDocTags(node).find((tag) => tag.tagName.text === "typert" && (ts.getTextOfJSDocComment(tag.comment) ?? "").trim().split(/\s+/, 1)[0] === "service");
|
|
2054
|
+
}
|
|
2055
|
+
function memberName(name) {
|
|
2056
|
+
if (ts.isIdentifier(name) || ts.isPrivateIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name) || ts.isNoSubstitutionTemplateLiteral(name)) return name.text;
|
|
2057
|
+
if (ts.isComputedPropertyName(name)) return `[${name.expression.getText()}]`;
|
|
2058
|
+
return name.getText();
|
|
2059
|
+
}
|
|
2060
|
+
function stringLiteralValue(node) {
|
|
2061
|
+
return node !== void 0 && (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) ? node.text : void 0;
|
|
2062
|
+
}
|
|
2063
|
+
function isRemoteSegment(value) {
|
|
2064
|
+
return value !== "." && value !== ".." && /^[A-Za-z0-9_$.-]+$/.test(value);
|
|
2065
|
+
}
|
|
2066
|
+
function expressionName(node) {
|
|
2067
|
+
if (ts.isIdentifier(node)) return node.text;
|
|
2068
|
+
if (ts.isPropertyAccessExpression(node)) return node.name.text;
|
|
2069
|
+
}
|
|
2070
|
+
function packageExportSpecifier$1(packageName, subpath) {
|
|
2071
|
+
return subpath === "." ? packageName : `${packageName}${subpath.slice(1)}`;
|
|
2072
|
+
}
|
|
2073
|
+
function visibilityOf(node) {
|
|
2074
|
+
if ("name" in node && node.name !== void 0 && ts.isPrivateIdentifier(node.name)) return "private";
|
|
2075
|
+
if (hasModifier(node, ts.SyntaxKind.PrivateKeyword)) return "private";
|
|
2076
|
+
if (hasModifier(node, ts.SyntaxKind.ProtectedKeyword)) return "protected";
|
|
2077
|
+
return "public";
|
|
2078
|
+
}
|
|
2079
|
+
function hasModifier(node, kind) {
|
|
2080
|
+
return (ts.canHaveModifiers(node) ? ts.getModifiers(node) : void 0)?.some((modifier) => modifier.kind === kind) ?? false;
|
|
2081
|
+
}
|
|
2082
|
+
function exposableMember(member) {
|
|
2083
|
+
return member.visibility === "public" && !member.static;
|
|
2084
|
+
}
|
|
2085
|
+
function keywordName(kind) {
|
|
2086
|
+
switch (kind) {
|
|
2087
|
+
case ts.SyntaxKind.AnyKeyword: return "any";
|
|
2088
|
+
case ts.SyntaxKind.BigIntKeyword: return "bigint";
|
|
2089
|
+
case ts.SyntaxKind.BooleanKeyword: return "boolean";
|
|
2090
|
+
case ts.SyntaxKind.NeverKeyword: return "never";
|
|
2091
|
+
case ts.SyntaxKind.NumberKeyword: return "number";
|
|
2092
|
+
case ts.SyntaxKind.ObjectKeyword: return "object";
|
|
2093
|
+
case ts.SyntaxKind.StringKeyword: return "string";
|
|
2094
|
+
case ts.SyntaxKind.SymbolKeyword: return "symbol";
|
|
2095
|
+
case ts.SyntaxKind.UndefinedKeyword: return "undefined";
|
|
2096
|
+
case ts.SyntaxKind.UnknownKeyword: return "unknown";
|
|
2097
|
+
case ts.SyntaxKind.VoidKeyword: return "void";
|
|
2098
|
+
default: return;
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
function literalModel(node) {
|
|
2102
|
+
const literal = node.literal;
|
|
2103
|
+
if (ts.isStringLiteral(literal)) return {
|
|
2104
|
+
kind: "literal",
|
|
2105
|
+
value: literal.text,
|
|
2106
|
+
text: literal.getText()
|
|
2107
|
+
};
|
|
2108
|
+
if (ts.isNoSubstitutionTemplateLiteral(literal)) return {
|
|
2109
|
+
kind: "literal",
|
|
2110
|
+
value: literal.text,
|
|
2111
|
+
text: literal.getText()
|
|
2112
|
+
};
|
|
2113
|
+
if (ts.isNumericLiteral(literal)) return {
|
|
2114
|
+
kind: "literal",
|
|
2115
|
+
value: Number(literal.text),
|
|
2116
|
+
text: literal.getText()
|
|
2117
|
+
};
|
|
2118
|
+
if (ts.isBigIntLiteral(literal)) return {
|
|
2119
|
+
kind: "literal",
|
|
2120
|
+
value: BigInt(literal.text.slice(0, -1)),
|
|
2121
|
+
text: literal.getText()
|
|
2122
|
+
};
|
|
2123
|
+
if (literal.kind === ts.SyntaxKind.TrueKeyword) return {
|
|
2124
|
+
kind: "literal",
|
|
2125
|
+
value: true,
|
|
2126
|
+
text: "true"
|
|
2127
|
+
};
|
|
2128
|
+
if (literal.kind === ts.SyntaxKind.FalseKeyword) return {
|
|
2129
|
+
kind: "literal",
|
|
2130
|
+
value: false,
|
|
2131
|
+
text: "false"
|
|
2132
|
+
};
|
|
2133
|
+
if (literal.kind === ts.SyntaxKind.NullKeyword) return {
|
|
2134
|
+
kind: "literal",
|
|
2135
|
+
value: null,
|
|
2136
|
+
text: "null"
|
|
2137
|
+
};
|
|
2138
|
+
/* v8 ignore else -- all remaining LiteralTypeNode syntax is a signed numeric or bigint literal. */
|
|
2139
|
+
if (ts.isPrefixUnaryExpression(literal) && (ts.isNumericLiteral(literal.operand) || ts.isBigIntLiteral(literal.operand))) return {
|
|
2140
|
+
kind: "literal",
|
|
2141
|
+
value: ts.isBigIntLiteral(literal.operand) ? BigInt(literal.getText().slice(0, -1)) : Number(literal.getText()),
|
|
2142
|
+
text: literal.getText()
|
|
2143
|
+
};
|
|
2144
|
+
/* v8 ignore next -- TypeScript's LiteralTypeNode grammar is exhausted above; this contains future compiler syntax. */
|
|
2145
|
+
throw new TypertAnalysisError(`typert: unsupported literal type ${literal.getText()}`);
|
|
2146
|
+
}
|
|
2147
|
+
function modifierMode(token) {
|
|
2148
|
+
if (token?.kind === ts.SyntaxKind.PlusToken) return "add";
|
|
2149
|
+
if (token?.kind === ts.SyntaxKind.MinusToken) return "remove";
|
|
2150
|
+
return token === void 0 ? "preserve" : "add";
|
|
2151
|
+
}
|
|
2152
|
+
function annotationPosition(node, purpose) {
|
|
2153
|
+
if (purpose === "return") return node.parameters.end + 1;
|
|
2154
|
+
return node.name.end;
|
|
2155
|
+
}
|
|
2156
|
+
function moduleSpecifierOf(node) {
|
|
2157
|
+
if (ts.isImportTypeNode(node)) return node.argument.literal.text;
|
|
2158
|
+
const symbol = ts.isTypeReferenceNode(node) ? node.typeName : node.expression;
|
|
2159
|
+
const sourceFile = node.getSourceFile();
|
|
2160
|
+
const first = ts.isIdentifier(symbol) ? symbol.text : symbol.getFirstToken(sourceFile)?.getText(sourceFile);
|
|
2161
|
+
for (const statement of sourceFile.statements) {
|
|
2162
|
+
if (!ts.isImportDeclaration(statement) || statement.importClause === void 0 || !ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
2163
|
+
if (statement.importClause.name?.text === first) return statement.moduleSpecifier.text;
|
|
2164
|
+
const bindings = statement.importClause.namedBindings;
|
|
2165
|
+
if (bindings !== void 0 && ts.isNamespaceImport(bindings) && bindings.name.text === first) return statement.moduleSpecifier.text;
|
|
2166
|
+
if (bindings !== void 0 && ts.isNamedImports(bindings) && bindings.elements.some((element) => element.name.text === first)) return statement.moduleSpecifier.text;
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
function authoredExportName(node, moduleSpecifier) {
|
|
2170
|
+
if (ts.isImportTypeNode(node)) return node.qualifier.getText().split(".")[0];
|
|
2171
|
+
const referenced = ts.isTypeReferenceNode(node) ? node.typeName.getText().split(".") : node.expression.getText().split(".");
|
|
2172
|
+
const localName = referenced[0];
|
|
2173
|
+
for (const statement of node.getSourceFile().statements) {
|
|
2174
|
+
if (!ts.isImportDeclaration(statement) || statement.importClause === void 0 || !ts.isStringLiteral(statement.moduleSpecifier) || statement.moduleSpecifier.text !== moduleSpecifier) continue;
|
|
2175
|
+
if (statement.importClause.name?.text === localName) return "default";
|
|
2176
|
+
const bindings = statement.importClause.namedBindings;
|
|
2177
|
+
if (bindings !== void 0 && ts.isNamedImports(bindings)) {
|
|
2178
|
+
const imported = bindings.elements.find((element) => element.name.text === localName);
|
|
2179
|
+
if (imported !== void 0) return imported.propertyName?.text ?? imported.name.text;
|
|
2180
|
+
}
|
|
2181
|
+
if (bindings !== void 0 && ts.isNamespaceImport(bindings) && bindings.name.text === localName) return referenced[1];
|
|
2182
|
+
}
|
|
2183
|
+
/* v8 ignore next -- moduleSpecifierOf returns only the matching import inspected by this loop. */
|
|
2184
|
+
throw new TypertAnalysisError(`typert: cannot recover export name for ${localName} from ${moduleSpecifier}`);
|
|
2185
|
+
}
|
|
2186
|
+
function importTypeAttributesText(node) {
|
|
2187
|
+
const sourceFile = node.getSourceFile();
|
|
2188
|
+
const children = node.getChildren(sourceFile);
|
|
2189
|
+
const comma = children.find((child) => child.kind === ts.SyntaxKind.CommaToken);
|
|
2190
|
+
const close = children.find((child) => child.kind === ts.SyntaxKind.CloseParenToken);
|
|
2191
|
+
return sourceFile.text.slice(comma.end, close.pos).trim();
|
|
2192
|
+
}
|
|
2193
|
+
function moduleIdentity(specifier) {
|
|
2194
|
+
if (specifier.startsWith(".") || specifier.startsWith("/")) return void 0;
|
|
2195
|
+
const parts = specifier.split("/");
|
|
2196
|
+
const packageLength = specifier.startsWith("@") ? 2 : 1;
|
|
2197
|
+
const packageName = parts.slice(0, packageLength).join("/");
|
|
2198
|
+
const rest = parts.slice(packageLength).join("/");
|
|
2199
|
+
return {
|
|
2200
|
+
package: packageName,
|
|
2201
|
+
subpath: rest.length === 0 ? "." : `./${rest}`
|
|
2202
|
+
};
|
|
2203
|
+
}
|
|
2204
|
+
function externalModuleIdentityForFile(file) {
|
|
2205
|
+
const normalized = slash(file);
|
|
2206
|
+
const index = normalized.lastIndexOf("/node_modules/");
|
|
2207
|
+
if (index < 0) return void 0;
|
|
2208
|
+
const parts = normalized.slice(index + 14).split("/");
|
|
2209
|
+
const packageLength = parts[0].startsWith("@") ? 2 : 1;
|
|
2210
|
+
return {
|
|
2211
|
+
package: parts.slice(0, packageLength).join("/"),
|
|
2212
|
+
subpath: "."
|
|
2213
|
+
};
|
|
2214
|
+
}
|
|
2215
|
+
function isStandardLibraryFile(file) {
|
|
2216
|
+
const base = file.replaceAll("\\", "/");
|
|
2217
|
+
return /\/typescript\/lib\/lib\.[^/]+\.d\.ts$/.test(base);
|
|
2218
|
+
}
|
|
2219
|
+
function formatDiagnostic(diagnostic) {
|
|
2220
|
+
return ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
|
2221
|
+
}
|
|
2222
|
+
function formatProgramDiagnostic(root, face, diagnostic) {
|
|
2223
|
+
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
|
2224
|
+
const position = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
|
2225
|
+
return `typert(${face}): ${slash(relative(root, diagnostic.file.fileName))}:${String(position.line + 1)}:${String(position.character + 1)}: TypeScript TS${String(diagnostic.code)}: ${message}`;
|
|
2226
|
+
}
|
|
2227
|
+
const realPathCache = /* @__PURE__ */ new Map();
|
|
2228
|
+
function realPath(path) {
|
|
2229
|
+
const absolute = resolve(path);
|
|
2230
|
+
const cached = realPathCache.get(absolute);
|
|
2231
|
+
if (cached !== void 0) return cached;
|
|
2232
|
+
if (!existsSync(absolute)) return absolute;
|
|
2233
|
+
const resolved = realpathSync(absolute);
|
|
2234
|
+
realPathCache.set(absolute, resolved);
|
|
2235
|
+
return resolved;
|
|
2236
|
+
}
|
|
2237
|
+
function isWithin(path, root) {
|
|
2238
|
+
const absolute = realPath(path);
|
|
2239
|
+
const parent = realPath(root);
|
|
2240
|
+
return absolute === parent || absolute.startsWith(parent + sep);
|
|
2241
|
+
}
|
|
2242
|
+
function slash(value) {
|
|
2243
|
+
return value.replaceAll("\\", "/");
|
|
2244
|
+
}
|
|
2245
|
+
function uniqueBy(values, key) {
|
|
2246
|
+
const result = /* @__PURE__ */ new Map();
|
|
2247
|
+
for (const value of values) if (!result.has(key(value))) result.set(key(value), value);
|
|
2248
|
+
return [...result.values()];
|
|
2249
|
+
}
|
|
2250
|
+
function compareCrossFaceLinks(left, right) {
|
|
2251
|
+
return left.fromFace.localeCompare(right.fromFace) || left.fromPackage.localeCompare(right.fromPackage) || left.toFace.localeCompare(right.toFace) || left.toPackage.localeCompare(right.toPackage) || left.subpath.localeCompare(right.subpath) || left.name.localeCompare(right.name);
|
|
2252
|
+
}
|
|
2253
|
+
//#endregion
|
|
2254
|
+
//#region lib/types/model.js
|
|
2255
|
+
/**
|
|
2256
|
+
* Compiler-independent Typert analysis model. TypeScript nodes and checker
|
|
2257
|
+
* objects are extraction inputs only; emitters consume this graph.
|
|
2258
|
+
* @module @deepseek-ai/dsh-typert-generator/model
|
|
2259
|
+
*/
|
|
2260
|
+
/**
|
|
2261
|
+
* Return the direct type-expression edges owned by one node.
|
|
2262
|
+
* @param node - compiler-independent type node to inspect.
|
|
2263
|
+
* @returns graph-local ids of its direct child type nodes.
|
|
2264
|
+
*/
|
|
2265
|
+
function childTypeNodeIds(node) {
|
|
2266
|
+
switch (node.kind) {
|
|
2267
|
+
case "parenthesized":
|
|
2268
|
+
case "operator": return [node.type];
|
|
2269
|
+
case "reference": return [...node.arguments];
|
|
2270
|
+
case "union":
|
|
2271
|
+
case "intersection": return [...node.types];
|
|
2272
|
+
case "array": return [node.element];
|
|
2273
|
+
case "tuple": return node.elements.map((element) => element.type);
|
|
2274
|
+
case "indexed-access": return [node.object, node.index];
|
|
2275
|
+
case "conditional": return [
|
|
2276
|
+
node.check,
|
|
2277
|
+
node.extends,
|
|
2278
|
+
node.whenTrue,
|
|
2279
|
+
node.whenFalse
|
|
2280
|
+
];
|
|
2281
|
+
case "mapped": return [
|
|
2282
|
+
...node.parameter.constraint === void 0 ? [] : [node.parameter.constraint],
|
|
2283
|
+
...node.parameter.default === void 0 ? [] : [node.parameter.default],
|
|
2284
|
+
...node.nameType === void 0 ? [] : [node.nameType],
|
|
2285
|
+
...node.value === void 0 ? [] : [node.value]
|
|
2286
|
+
];
|
|
2287
|
+
case "template-literal": return node.spans.map((span) => span.type);
|
|
2288
|
+
case "type-query":
|
|
2289
|
+
case "import-type": return [...node.arguments];
|
|
2290
|
+
case "predicate": return node.type === void 0 ? [] : [node.type];
|
|
2291
|
+
case "infer": return [...node.parameter.constraint === void 0 ? [] : [node.parameter.constraint], ...node.parameter.default === void 0 ? [] : [node.parameter.default]];
|
|
2292
|
+
case "keyword":
|
|
2293
|
+
case "literal":
|
|
2294
|
+
case "object":
|
|
2295
|
+
case "function":
|
|
2296
|
+
case "constructor":
|
|
2297
|
+
case "this": return [];
|
|
2298
|
+
default: return assertNever$1(node);
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
function assertNever$1(value) {
|
|
2302
|
+
throw new Error(`unsupported model variant ${JSON.stringify(value)}`);
|
|
2303
|
+
}
|
|
2304
|
+
//#endregion
|
|
2305
|
+
//#region lib/types/renderer.js
|
|
2306
|
+
/**
|
|
2307
|
+
* Rendering and traversal over the compiler-independent TypeGraph. Emitters
|
|
2308
|
+
* use this module instead of reaching back into TypeScript AST nodes.
|
|
2309
|
+
* @module @deepseek-ai/dsh-typert-generator/renderer
|
|
2310
|
+
*/
|
|
2311
|
+
/** Failure to render or traverse an internally inconsistent TypeGraph. */
|
|
2312
|
+
var TypeGraphRenderError = class extends Error {
|
|
2313
|
+
name = "TypeGraphRenderError";
|
|
2314
|
+
};
|
|
2315
|
+
/** Read and render one TypeGraph without compiler objects. */
|
|
2316
|
+
var TypeGraphRenderer = class {
|
|
2317
|
+
graph;
|
|
2318
|
+
nodes;
|
|
2319
|
+
declarations;
|
|
2320
|
+
members;
|
|
2321
|
+
parameterNames = /* @__PURE__ */ new Map();
|
|
2322
|
+
/**
|
|
2323
|
+
* Index one complete graph.
|
|
2324
|
+
* @param graph - compiler-independent graph to render.
|
|
2325
|
+
*/
|
|
2326
|
+
constructor(graph) {
|
|
2327
|
+
this.graph = graph;
|
|
2328
|
+
this.nodes = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
2329
|
+
this.declarations = new Map(graph.declarations.map((declaration) => [declaration.id, declaration]));
|
|
2330
|
+
this.members = new Map(graph.declarations.flatMap((declaration) => declaration.members.map((member) => [member.id, member])));
|
|
2331
|
+
for (const declaration of graph.declarations) {
|
|
2332
|
+
this.indexParameters(declaration.typeParameters);
|
|
2333
|
+
for (const member of declaration.members) if ("signature" in member) this.indexParameters(member.signature.typeParameters);
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
/**
|
|
2337
|
+
* Resolve a node id or fail with the broken edge.
|
|
2338
|
+
* @param id - graph-local type node id.
|
|
2339
|
+
* @returns the referenced node.
|
|
2340
|
+
*/
|
|
2341
|
+
node(id) {
|
|
2342
|
+
const node = this.nodes.get(id);
|
|
2343
|
+
if (node === void 0) throw new TypeGraphRenderError(`type graph references missing node ${id}`);
|
|
2344
|
+
return node;
|
|
2345
|
+
}
|
|
2346
|
+
/**
|
|
2347
|
+
* Resolve a declaration id or fail with the broken edge.
|
|
2348
|
+
* @param id - workspace symbol id.
|
|
2349
|
+
* @returns the referenced declaration.
|
|
2350
|
+
*/
|
|
2351
|
+
declaration(id) {
|
|
2352
|
+
const declaration = this.declarations.get(id);
|
|
2353
|
+
if (declaration === void 0) throw new TypeGraphRenderError(`type graph references missing declaration ${id}`);
|
|
2354
|
+
return declaration;
|
|
2355
|
+
}
|
|
2356
|
+
/**
|
|
2357
|
+
* Resolve a public member id.
|
|
2358
|
+
* @param id - declaration member id.
|
|
2359
|
+
* @returns the referenced member.
|
|
2360
|
+
*/
|
|
2361
|
+
member(id) {
|
|
2362
|
+
const member = this.members.get(id);
|
|
2363
|
+
if (member === void 0) throw new TypeGraphRenderError(`type graph references missing member ${id}`);
|
|
2364
|
+
return member;
|
|
2365
|
+
}
|
|
2366
|
+
/**
|
|
2367
|
+
* Render one type expression from the retained source structure.
|
|
2368
|
+
* @param id - type node id.
|
|
2369
|
+
* @param references - optional generated names for declaration references.
|
|
2370
|
+
* @returns TypeScript type text.
|
|
2371
|
+
*/
|
|
2372
|
+
renderType(id, references) {
|
|
2373
|
+
const node = this.node(id);
|
|
2374
|
+
switch (node.kind) {
|
|
2375
|
+
case "keyword": return node.name;
|
|
2376
|
+
case "literal": return node.text;
|
|
2377
|
+
case "parenthesized": return `(${this.renderType(node.type, references)})`;
|
|
2378
|
+
case "reference": {
|
|
2379
|
+
const name = node.target.kind === "type-parameter" ? this.parameterNames.get(node.target.parameter) ?? node.name : node.target.kind === "declaration" ? references?.get(node.target.symbol) ?? node.name : node.name;
|
|
2380
|
+
return node.arguments.length === 0 ? name : `${name}<${node.arguments.map((argument) => this.renderType(argument, references)).join(", ")}>`;
|
|
2381
|
+
}
|
|
2382
|
+
case "union": return node.types.map((type) => this.renderType(type, references)).join(" | ");
|
|
2383
|
+
case "intersection": return node.types.map((type) => this.renderType(type, references)).join(" & ");
|
|
2384
|
+
case "array": {
|
|
2385
|
+
const element = this.renderType(node.element, references);
|
|
2386
|
+
return `${needsArrayParentheses(this.node(node.element)) ? `(${element})` : element}[]`;
|
|
2387
|
+
}
|
|
2388
|
+
case "tuple": return `[${node.elements.map((element) => {
|
|
2389
|
+
const type = this.renderType(element.type, references);
|
|
2390
|
+
if (element.name !== void 0) return `${element.rest ? "..." : ""}${element.name}${element.optional ? "?" : ""}: ${type}`;
|
|
2391
|
+
return `${element.rest ? "..." : ""}${type}${element.optional ? "?" : ""}`;
|
|
2392
|
+
}).join(", ")}]`;
|
|
2393
|
+
case "object": return this.renderObject(node.members, references);
|
|
2394
|
+
case "function": return `${this.renderSignatureHead(node.signature, references)} => ${this.renderType(node.signature.returns, references)}`;
|
|
2395
|
+
case "constructor": return `${node.abstract ? "abstract " : ""}new ${this.renderSignatureHead(node.signature, references)} => ${this.renderType(node.signature.returns, references)}`;
|
|
2396
|
+
case "indexed-access": return `${this.renderType(node.object, references)}[${this.renderType(node.index, references)}]`;
|
|
2397
|
+
case "operator": return `${node.operator} ${this.renderType(node.type, references)}`;
|
|
2398
|
+
case "conditional": return `${this.renderType(node.check, references)} extends ${this.renderType(node.extends, references)} ? ${this.renderType(node.whenTrue, references)} : ${this.renderType(node.whenFalse, references)}`;
|
|
2399
|
+
case "infer": return `infer ${this.renderTypeParameter(node.parameter, false, references)}`;
|
|
2400
|
+
case "mapped": {
|
|
2401
|
+
const readonly = node.readonly === "preserve" ? "" : node.readonly === "remove" ? "-readonly " : "readonly ";
|
|
2402
|
+
const optional = node.optional === "preserve" ? "" : node.optional === "remove" ? "-?" : "?";
|
|
2403
|
+
if (node.parameter.constraint === void 0) throw new TypeGraphRenderError(`mapped type parameter ${node.parameter.name} has no constraint`);
|
|
2404
|
+
return `{ ${readonly}[${`${node.parameter.name} in ${this.renderType(node.parameter.constraint, references)}`}${node.nameType === void 0 ? "" : ` as ${this.renderType(node.nameType, references)}`}]${optional}: ${node.value === void 0 ? "unknown" : this.renderType(node.value, references)} }`;
|
|
2405
|
+
}
|
|
2406
|
+
case "template-literal": {
|
|
2407
|
+
const spans = node.spans.map((span) => `\${${this.renderType(span.type, references)}}${escapeTemplate(span.text)}`).join("");
|
|
2408
|
+
return `\`${escapeTemplate(node.head)}${spans}\``;
|
|
2409
|
+
}
|
|
2410
|
+
case "type-query": {
|
|
2411
|
+
const argumentsText = node.arguments.length === 0 ? "" : `<${node.arguments.map((argument) => this.renderType(argument, references)).join(", ")}>`;
|
|
2412
|
+
return `typeof ${node.expression}${argumentsText}`;
|
|
2413
|
+
}
|
|
2414
|
+
case "import-type": {
|
|
2415
|
+
const attributes = node.attributes === void 0 ? "" : `, ${node.attributes}`;
|
|
2416
|
+
const imported = `import(${quote$2(node.module)}${attributes})${node.qualifier === void 0 ? "" : `.${node.qualifier}`}`;
|
|
2417
|
+
const argumentsText = node.arguments.length === 0 ? "" : `<${node.arguments.map((argument) => this.renderType(argument, references)).join(", ")}>`;
|
|
2418
|
+
return `${node.typeof ? "typeof " : ""}${imported}${argumentsText}`;
|
|
2419
|
+
}
|
|
2420
|
+
case "predicate": {
|
|
2421
|
+
const assertion = node.asserts ? "asserts " : "";
|
|
2422
|
+
return node.type === void 0 ? `${assertion}${node.parameter}` : `${assertion}${node.parameter} is ${this.renderType(node.type, references)}`;
|
|
2423
|
+
}
|
|
2424
|
+
case "this": return "this";
|
|
2425
|
+
default: return assertNever(node);
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
/**
|
|
2429
|
+
* Render a callable signature without a member name.
|
|
2430
|
+
* @param signature - modeled signature.
|
|
2431
|
+
* @param references - optional generated names for declaration references.
|
|
2432
|
+
* @returns parameter list and return type.
|
|
2433
|
+
*/
|
|
2434
|
+
renderSignature(signature, references) {
|
|
2435
|
+
return `${this.renderSignatureHead(signature, references)}: ${this.renderType(signature.returns, references)}`;
|
|
2436
|
+
}
|
|
2437
|
+
/**
|
|
2438
|
+
* Render one class/interface member as a body-free declaration.
|
|
2439
|
+
* @param member - modeled member.
|
|
2440
|
+
* @param sourceModifiers - retain source-only modifiers for reflection text.
|
|
2441
|
+
* @param references - optional generated names for declaration references.
|
|
2442
|
+
* @returns one-line TypeScript member text.
|
|
2443
|
+
*/
|
|
2444
|
+
renderMember(member, sourceModifiers = false, references) {
|
|
2445
|
+
if (sourceModifiers) return member.text;
|
|
2446
|
+
const name = renderPropertyName(member.name);
|
|
2447
|
+
const optional = member.optional ? "?" : "";
|
|
2448
|
+
const readonly = member.readonly ? "readonly " : "";
|
|
2449
|
+
const abstract = member.abstract ? "abstract " : "";
|
|
2450
|
+
switch (member.kind) {
|
|
2451
|
+
case "property": return `${abstract}${readonly}${name}${optional}: ${this.renderType(member.type, references)}`;
|
|
2452
|
+
case "method": return `${abstract}${name}${optional}${this.renderSignature(member.signature, references)}`;
|
|
2453
|
+
case "getter": return `${abstract}get ${name}()${this.renderReturn(member.signature, references)}`;
|
|
2454
|
+
case "setter": return `${abstract}set ${name}${this.renderSignatureHead(member.signature, references)}`;
|
|
2455
|
+
case "call": return this.renderSignature(member.signature, references);
|
|
2456
|
+
case "construct": return `new ${this.renderSignature(member.signature, references)}`;
|
|
2457
|
+
case "index": return `${readonly}[${member.signature.parameters.map((parameter) => this.renderParameter(parameter, references)).join(", ")}]: ${this.renderType(member.signature.returns, references)}`;
|
|
2458
|
+
default: return assertNever(member);
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
/**
|
|
2462
|
+
* Render a named declaration without JSDoc.
|
|
2463
|
+
* @param id - declaration symbol id.
|
|
2464
|
+
* @returns exported TypeScript declaration text.
|
|
2465
|
+
*/
|
|
2466
|
+
renderDeclaration(id) {
|
|
2467
|
+
const declaration = this.declaration(id);
|
|
2468
|
+
const parameters = this.renderTypeParameters(declaration.typeParameters);
|
|
2469
|
+
if (declaration.kind === "enum") {
|
|
2470
|
+
const members = declaration.enumMembers?.map((member) => ` ${renderPropertyName(member.name)}${member.initializer === void 0 ? "" : ` = ${member.initializer}`},`) ?? [];
|
|
2471
|
+
return [
|
|
2472
|
+
`export enum ${declaration.name} {`,
|
|
2473
|
+
...members,
|
|
2474
|
+
"}"
|
|
2475
|
+
].join("\n");
|
|
2476
|
+
}
|
|
2477
|
+
if (declaration.kind === "alias") {
|
|
2478
|
+
if (declaration.type === void 0) throw new TypeGraphRenderError(`alias ${id} has no type node`);
|
|
2479
|
+
return `export type ${declaration.name}${parameters} = ${this.renderType(declaration.type)};`;
|
|
2480
|
+
}
|
|
2481
|
+
const extendsTypes = declaration.extends.map((type) => this.renderType(type));
|
|
2482
|
+
const implementsTypes = declaration.implements.map((type) => this.renderType(type));
|
|
2483
|
+
const heritage = [extendsTypes.length === 0 ? "" : ` extends ${extendsTypes.join(", ")}`, implementsTypes.length === 0 ? "" : ` implements ${implementsTypes.join(", ")}`].join("");
|
|
2484
|
+
const prefix = declaration.kind === "class" && declaration.abstract ? "abstract " : "";
|
|
2485
|
+
const members = declaration.members.map((member) => ` ${this.renderMember(member)};`);
|
|
2486
|
+
return [
|
|
2487
|
+
`export ${prefix}${declaration.kind} ${declaration.name}${parameters}${heritage} {`,
|
|
2488
|
+
...members,
|
|
2489
|
+
"}"
|
|
2490
|
+
].join("\n");
|
|
2491
|
+
}
|
|
2492
|
+
/**
|
|
2493
|
+
* Find the transitive declaration closure referenced by members.
|
|
2494
|
+
* @param memberIds - business-surface member ids.
|
|
2495
|
+
* @returns declarations in graph order, excluding no roots implicitly.
|
|
2496
|
+
*/
|
|
2497
|
+
declarationClosureForMembers(memberIds) {
|
|
2498
|
+
return this.declarationClosure(memberIds, []);
|
|
2499
|
+
}
|
|
2500
|
+
/**
|
|
2501
|
+
* Find the transitive declaration closure referenced by type roots.
|
|
2502
|
+
* @param typeIds - graph type roots.
|
|
2503
|
+
* @returns declarations in graph order.
|
|
2504
|
+
*/
|
|
2505
|
+
declarationClosureForTypes(typeIds) {
|
|
2506
|
+
return this.declarationClosure([], typeIds);
|
|
2507
|
+
}
|
|
2508
|
+
declarationClosure(memberIds, typeIds) {
|
|
2509
|
+
const found = /* @__PURE__ */ new Set();
|
|
2510
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
2511
|
+
const visitNode = (id) => {
|
|
2512
|
+
const node = this.node(id);
|
|
2513
|
+
if (node.kind === "reference" && node.target.kind === "declaration") visitDeclaration(node.target.symbol);
|
|
2514
|
+
if (node.kind === "import-type" && node.target?.kind === "declaration") visitDeclaration(node.target.symbol);
|
|
2515
|
+
for (const child of childTypeNodeIds(node)) visitNode(child);
|
|
2516
|
+
for (const signature of nodeSignatures(node)) visitSignature(signature);
|
|
2517
|
+
if (node.kind === "object") for (const member of node.members) visitMember(member);
|
|
2518
|
+
};
|
|
2519
|
+
const visitSignature = (signature) => {
|
|
2520
|
+
for (const parameter of signature.typeParameters) {
|
|
2521
|
+
if (parameter.constraint !== void 0) visitNode(parameter.constraint);
|
|
2522
|
+
if (parameter.default !== void 0) visitNode(parameter.default);
|
|
2523
|
+
}
|
|
2524
|
+
for (const parameter of signature.parameters) visitNode(parameter.type);
|
|
2525
|
+
visitNode(signature.returns);
|
|
2526
|
+
};
|
|
2527
|
+
const visitMember = (member) => {
|
|
2528
|
+
if (member.kind === "property") visitNode(member.type);
|
|
2529
|
+
else visitSignature(member.signature);
|
|
2530
|
+
};
|
|
2531
|
+
const visitDeclaration = (id) => {
|
|
2532
|
+
if (found.has(id) || visiting.has(id)) return;
|
|
2533
|
+
visiting.add(id);
|
|
2534
|
+
const declaration = this.declaration(id);
|
|
2535
|
+
for (const parameter of declaration.typeParameters) {
|
|
2536
|
+
if (parameter.constraint !== void 0) visitNode(parameter.constraint);
|
|
2537
|
+
if (parameter.default !== void 0) visitNode(parameter.default);
|
|
2538
|
+
}
|
|
2539
|
+
for (const type of [...declaration.extends, ...declaration.implements]) visitNode(type);
|
|
2540
|
+
if (declaration.type !== void 0) visitNode(declaration.type);
|
|
2541
|
+
for (const member of declaration.members) visitMember(member);
|
|
2542
|
+
visiting.delete(id);
|
|
2543
|
+
found.add(id);
|
|
2544
|
+
};
|
|
2545
|
+
for (const id of memberIds) visitMember(this.member(id));
|
|
2546
|
+
for (const id of typeIds) visitNode(id);
|
|
2547
|
+
return this.graph.declarations.filter((declaration) => found.has(declaration.id));
|
|
2548
|
+
}
|
|
2549
|
+
renderSignatureHead(signature, references) {
|
|
2550
|
+
return `${this.renderTypeParameters(signature.typeParameters, references)}(${signature.parameters.map((parameter) => this.renderParameter(parameter, references)).join(", ")})`;
|
|
2551
|
+
}
|
|
2552
|
+
renderReturn(signature, references) {
|
|
2553
|
+
return `: ${this.renderType(signature.returns, references)}`;
|
|
2554
|
+
}
|
|
2555
|
+
renderParameter(parameter, references) {
|
|
2556
|
+
const name = parameter.binding === "identifier" ? renderPropertyName(parameter.name) : parameter.name;
|
|
2557
|
+
const optional = parameter.initializer === void 0 && parameter.optional && !parameter.rest ? "?" : "";
|
|
2558
|
+
const initializer = parameter.initializer === void 0 ? "" : ` = ${parameter.initializer}`;
|
|
2559
|
+
return `${parameter.rest ? "..." : ""}${name}${optional}: ${this.renderType(parameter.type, references)}${initializer}`;
|
|
2560
|
+
}
|
|
2561
|
+
renderTypeParameters(parameters, references) {
|
|
2562
|
+
return parameters.length === 0 ? "" : `<${parameters.map((parameter) => this.renderTypeParameter(parameter, true, references)).join(", ")}>`;
|
|
2563
|
+
}
|
|
2564
|
+
renderTypeParameter(parameter, includeDefault, references) {
|
|
2565
|
+
const variance = parameter.variance === void 0 ? "" : `${parameter.variance === "in-out" ? "in out" : parameter.variance} `;
|
|
2566
|
+
const constModifier = parameter.const ? "const " : "";
|
|
2567
|
+
const constraint = parameter.constraint === void 0 ? "" : ` extends ${this.renderType(parameter.constraint, references)}`;
|
|
2568
|
+
const fallback = !includeDefault || parameter.default === void 0 ? "" : ` = ${this.renderType(parameter.default, references)}`;
|
|
2569
|
+
return `${constModifier}${variance}${parameter.name}${constraint}${fallback}`;
|
|
2570
|
+
}
|
|
2571
|
+
renderObject(members, references) {
|
|
2572
|
+
if (members.length === 0) return "{}";
|
|
2573
|
+
return `{ ${members.map((member) => `${this.renderMember(member, false, references)};`).join(" ")} }`;
|
|
2574
|
+
}
|
|
2575
|
+
indexParameters(parameters) {
|
|
2576
|
+
for (const parameter of parameters) this.parameterNames.set(parameter.id, parameter.name);
|
|
2577
|
+
}
|
|
2578
|
+
};
|
|
2579
|
+
function nodeSignatures(node) {
|
|
2580
|
+
return node.kind === "function" || node.kind === "constructor" ? [node.signature] : [];
|
|
2581
|
+
}
|
|
2582
|
+
function needsArrayParentheses(node) {
|
|
2583
|
+
return node.kind === "union" || node.kind === "intersection" || node.kind === "function" || node.kind === "constructor" || node.kind === "conditional";
|
|
2584
|
+
}
|
|
2585
|
+
function renderPropertyName(name) {
|
|
2586
|
+
if (name.startsWith("[") && name.endsWith("]")) return name;
|
|
2587
|
+
if (/^(?:[$A-Z_a-z][$\w]*|\d+)$/u.test(name)) return name;
|
|
2588
|
+
return quote$2(name);
|
|
2589
|
+
}
|
|
2590
|
+
function quote$2(value) {
|
|
2591
|
+
return `'${value.replaceAll("\\", "\\\\").replaceAll("'", "\\'").replaceAll("\n", "\\n")}'`;
|
|
2592
|
+
}
|
|
2593
|
+
function escapeTemplate(value) {
|
|
2594
|
+
return value.replaceAll("\\", "\\\\").replaceAll("`", "\\`").replaceAll("${", "\\${");
|
|
2595
|
+
}
|
|
2596
|
+
function assertNever(value) {
|
|
2597
|
+
throw new TypeGraphRenderError(`unsupported model variant ${JSON.stringify(value)}`);
|
|
2598
|
+
}
|
|
2599
|
+
//#endregion
|
|
2600
|
+
//#region lib/types/emitter.js
|
|
2601
|
+
/**
|
|
2602
|
+
* Model-driven Typert artifact emitter. It consumes only FaceModel and
|
|
2603
|
+
* TypeGraph data; TypeScript compiler nodes are not part of this boundary.
|
|
2604
|
+
* @module @deepseek-ai/dsh-typert-generator/emitter
|
|
2605
|
+
*/
|
|
2606
|
+
/** Failure to project a modeled construct into an emitted artifact. */
|
|
2607
|
+
var TypertEmitError = class extends Error {
|
|
2608
|
+
name = "TypertEmitError";
|
|
2609
|
+
};
|
|
2610
|
+
/** Emit generated runtime and type artifacts from one independently analyzed face. */
|
|
2611
|
+
var FaceModelEmitter = class {
|
|
2612
|
+
face;
|
|
2613
|
+
renderer;
|
|
2614
|
+
/**
|
|
2615
|
+
* Create an emitter for one face graph.
|
|
2616
|
+
* @param face - independently analyzed face.
|
|
2617
|
+
*/
|
|
2618
|
+
constructor(face) {
|
|
2619
|
+
this.face = face;
|
|
2620
|
+
this.renderer = new TypeGraphRenderer(face.graph);
|
|
2621
|
+
}
|
|
2622
|
+
/**
|
|
2623
|
+
* Emit one modeled package.
|
|
2624
|
+
* @param packageName - exact package name in the face model.
|
|
2625
|
+
* @returns executable JavaScript and its precise declaration file.
|
|
2626
|
+
*/
|
|
2627
|
+
emit(packageName) {
|
|
2628
|
+
const packageModel = this.face.packages.find((candidate) => candidate.name === packageName);
|
|
2629
|
+
if (packageModel === void 0) throw new TypertEmitError(`typert emitter(${this.face.face}): package ${packageName} is not modeled on this face`);
|
|
2630
|
+
const schemaArtifact = new SchemaEmitter(this.renderer, packageModel.schemas, invocationBoundaryRoots(packageModel.invocations)).emit();
|
|
2631
|
+
const runtimeModel = this.runtimeModel(packageModel);
|
|
2632
|
+
const js = this.renderJs(packageModel, schemaArtifact, runtimeModel);
|
|
2633
|
+
const dts = this.renderDts(packageModel, schemaArtifact);
|
|
2634
|
+
return {
|
|
2635
|
+
package: packageName,
|
|
2636
|
+
face: this.face.face,
|
|
2637
|
+
exports: packageModel.schemas.map((schema) => schema.export.name),
|
|
2638
|
+
js,
|
|
2639
|
+
dts,
|
|
2640
|
+
...this.face.face === "host" && packageModel.invocations.length > 0 ? { remote: this.emitRemote(packageModel) } : {}
|
|
2641
|
+
};
|
|
2642
|
+
}
|
|
2643
|
+
runtimeModel(packageModel) {
|
|
2644
|
+
return {
|
|
2645
|
+
services: packageModel.services.map((service) => {
|
|
2646
|
+
const members = service.members.map((id) => this.runtimeMember(this.renderer.member(id)));
|
|
2647
|
+
return {
|
|
2648
|
+
...documentationLiteral(service),
|
|
2649
|
+
key: service.key,
|
|
2650
|
+
exportName: service.export.name,
|
|
2651
|
+
members,
|
|
2652
|
+
types: this.runtimeTypes(this.renderer.declarationClosureForMembers(service.members), service.symbol)
|
|
2653
|
+
};
|
|
2654
|
+
}),
|
|
2655
|
+
events: packageModel.events.map((event) => {
|
|
2656
|
+
const node = this.renderer.node(event.signature);
|
|
2657
|
+
if (node.kind !== "function") throw new TypertEmitError(`typert emitter(${this.face.face}): event ${event.name} is not a function type`);
|
|
2658
|
+
return {
|
|
2659
|
+
...documentationLiteral(event),
|
|
2660
|
+
name: event.name,
|
|
2661
|
+
...event.mode === void 0 ? {} : { mode: event.mode },
|
|
2662
|
+
signature: `${quote$1(event.name)}${this.renderer.renderSignature(node.signature)}`
|
|
2663
|
+
};
|
|
2664
|
+
}),
|
|
2665
|
+
objects: packageModel.objects.map((object) => {
|
|
2666
|
+
const declaration = this.renderer.declaration(object.symbol);
|
|
2667
|
+
return {
|
|
2668
|
+
...documentationLiteral(object),
|
|
2669
|
+
name: declaration.name,
|
|
2670
|
+
exportName: object.export.name,
|
|
2671
|
+
members: declaration.members.map((member) => this.runtimeMember(member)),
|
|
2672
|
+
types: this.runtimeTypes(this.renderer.declarationClosureForMembers(declaration.members.map((member) => member.id)), declaration.id)
|
|
2673
|
+
};
|
|
2674
|
+
})
|
|
2675
|
+
};
|
|
2676
|
+
}
|
|
2677
|
+
runtimeMember(member) {
|
|
2678
|
+
return {
|
|
2679
|
+
kind: member.kind,
|
|
2680
|
+
name: member.name,
|
|
2681
|
+
signature: this.renderer.renderMember(member, true),
|
|
2682
|
+
...member.summary === void 0 ? {} : { summary: member.summary },
|
|
2683
|
+
...member.jsDoc === void 0 ? {} : { jsDoc: member.jsDoc }
|
|
2684
|
+
};
|
|
2685
|
+
}
|
|
2686
|
+
runtimeTypes(declarations, root) {
|
|
2687
|
+
return declarations.filter((declaration) => declaration.id !== root).map((declaration) => ({
|
|
2688
|
+
name: declaration.name,
|
|
2689
|
+
declaration: this.renderer.renderDeclaration(declaration.id)
|
|
2690
|
+
})).sort((left, right) => left.name.localeCompare(right.name));
|
|
2691
|
+
}
|
|
2692
|
+
renderJs(packageModel, schemas, runtimeModel) {
|
|
2693
|
+
const lines = ["/* Generated by @deepseek-ai/dsh-typert-generator from FaceModel — do not edit. */"];
|
|
2694
|
+
if (schemas.definitions.length > 0) lines.push("import { z } from 'zod'", "");
|
|
2695
|
+
lines.push(...schemas.definitions);
|
|
2696
|
+
if (schemas.definitions.length > 0) lines.push("");
|
|
2697
|
+
for (const schema of schemas.exports) lines.push(`export const ${schema.exportName} = ${schema.internalName}`);
|
|
2698
|
+
if (schemas.exports.length > 0) lines.push("");
|
|
2699
|
+
const model = JSON.stringify(runtimeModel, null, 2);
|
|
2700
|
+
lines.push("export const TYPERT = {");
|
|
2701
|
+
lines.push(` package: ${quote$1(packageModel.name)},`);
|
|
2702
|
+
lines.push(` face: ${quote$1(this.face.face)},`);
|
|
2703
|
+
lines.push(" schemas: [");
|
|
2704
|
+
for (const schema of schemas.exports) lines.push(` { name: ${quote$1(schema.exportName)}, schema: ${schema.exportName} },`);
|
|
2705
|
+
lines.push(" ],");
|
|
2706
|
+
lines.push(" invocations: [");
|
|
2707
|
+
for (const invocation of packageModel.invocations) lines.push(`${indent(this.invocationLiteral(invocation, schemas), 4)},`);
|
|
2708
|
+
lines.push(" ],");
|
|
2709
|
+
lines.push(` model: ${indent(model, 2).trimStart()},`);
|
|
2710
|
+
lines.push("}");
|
|
2711
|
+
return `${lines.join("\n")}\n`;
|
|
2712
|
+
}
|
|
2713
|
+
renderDts(packageModel, schemas) {
|
|
2714
|
+
const imports = /* @__PURE__ */ new Map();
|
|
2715
|
+
for (const schema of schemas.exports) {
|
|
2716
|
+
const specifier = packageExportSpecifier(packageModel.name, schema.model.export.subpath);
|
|
2717
|
+
const names = imports.get(specifier) ?? [];
|
|
2718
|
+
names.push(`${schema.model.export.name} as ${schema.exportName}$source`);
|
|
2719
|
+
imports.set(specifier, names);
|
|
2720
|
+
}
|
|
2721
|
+
const lines = ["/* Generated by @deepseek-ai/dsh-typert-generator from FaceModel — do not edit. */"];
|
|
2722
|
+
if (schemas.exports.length > 0) lines.splice(1, 0, "import type { z } from 'zod'");
|
|
2723
|
+
for (const [specifier, names] of [...imports].sort(([left], [right]) => left.localeCompare(right))) lines.push(`import type { ${names.sort().join(", ")} } from ${quote$1(specifier)}`);
|
|
2724
|
+
lines.push("");
|
|
2725
|
+
for (const schema of schemas.exports) lines.push(`export declare const ${schema.exportName}: z.ZodType<${schema.exportName}$source>`);
|
|
2726
|
+
if (schemas.exports.length > 0) lines.push("");
|
|
2727
|
+
lines.push("export declare const TYPERT: unknown");
|
|
2728
|
+
return `${lines.join("\n")}\n`;
|
|
2729
|
+
}
|
|
2730
|
+
emitRemote(packageModel) {
|
|
2731
|
+
const schemas = new SchemaEmitter(this.renderer, [], invocationBoundaryRoots(packageModel.invocations)).emit();
|
|
2732
|
+
const lines = ["/* Generated by @deepseek-ai/dsh-typert-generator from the Host FaceModel — do not edit. */"];
|
|
2733
|
+
if (schemas.definitions.length > 0) lines.push("import { z } from 'zod'", "");
|
|
2734
|
+
lines.push(...schemas.definitions);
|
|
2735
|
+
if (schemas.definitions.length > 0) lines.push("");
|
|
2736
|
+
lines.push("export const TYPERT_REMOTE = {");
|
|
2737
|
+
lines.push(` package: ${quote$1(packageModel.name)},`);
|
|
2738
|
+
lines.push(" descriptors: [");
|
|
2739
|
+
for (const invocation of packageModel.invocations) lines.push(`${indent(this.invocationLiteral(invocation, schemas), 4)},`);
|
|
2740
|
+
lines.push(" ],");
|
|
2741
|
+
lines.push("}");
|
|
2742
|
+
lines.push("");
|
|
2743
|
+
lines.push("export default TYPERT_REMOTE");
|
|
2744
|
+
const declaration = this.renderRemoteDts(packageModel);
|
|
2745
|
+
return {
|
|
2746
|
+
js: `${lines.join("\n")}\n`,
|
|
2747
|
+
...declaration
|
|
2748
|
+
};
|
|
2749
|
+
}
|
|
2750
|
+
invocationLiteral(invocation, schemas) {
|
|
2751
|
+
const lines = [
|
|
2752
|
+
"{",
|
|
2753
|
+
` id: ${quote$1(invocation.id)},`,
|
|
2754
|
+
` service: ${quote$1(invocation.service)},`,
|
|
2755
|
+
` namespace: ${quote$1(invocation.namespace)},`,
|
|
2756
|
+
` method: ${quote$1(invocation.method)},`
|
|
2757
|
+
];
|
|
2758
|
+
if (invocation.implementation !== void 0) lines.push(` implementation: ${quote$1(invocation.implementation)},`);
|
|
2759
|
+
if (invocation.invocation.kind === "direct") lines.push(" invocation: { kind: 'direct' },");
|
|
2760
|
+
else {
|
|
2761
|
+
lines.push(" invocation: {");
|
|
2762
|
+
lines.push(" kind: 'context',");
|
|
2763
|
+
lines.push(` context: ${quote$1(invocation.invocation.context)},`);
|
|
2764
|
+
lines.push(` wire: ${quote$1(invocation.invocation.wire)},`);
|
|
2765
|
+
lines.push(` codec: ${indent(strictCodec(invocation.invocation.boundary, schemas.boundary(contextBoundaryKey(invocation))), 4).trimStart()},`);
|
|
2766
|
+
lines.push(" },");
|
|
2767
|
+
}
|
|
2768
|
+
if (invocation.scope !== void 0) {
|
|
2769
|
+
lines.push(" scope: {");
|
|
2770
|
+
lines.push(` context: ${quote$1(invocation.scope.context)},`);
|
|
2771
|
+
lines.push(` wire: ${quote$1(invocation.scope.wire)},`);
|
|
2772
|
+
lines.push(" },");
|
|
2773
|
+
}
|
|
2774
|
+
lines.push(" parameters: [");
|
|
2775
|
+
invocation.parameters.forEach((parameter, index) => {
|
|
2776
|
+
lines.push(" {");
|
|
2777
|
+
lines.push(` name: ${quote$1(parameter.name)},`);
|
|
2778
|
+
lines.push(` wire: ${quote$1(parameter.wire)},`);
|
|
2779
|
+
lines.push(` source: ${quote$1(parameter.source)},`);
|
|
2780
|
+
if (parameter.lookup !== void 0) lines.push(` lookup: ${quote$1(parameter.lookup)},`);
|
|
2781
|
+
lines.push(` codec: ${indent(strictCodec(parameter.boundary, schemas.boundary(parameterBoundaryKey(invocation, index))), 6).trimStart()},`);
|
|
2782
|
+
lines.push(" },");
|
|
2783
|
+
});
|
|
2784
|
+
lines.push(" ],");
|
|
2785
|
+
if (invocation.cancellation !== void 0) lines.push(" cancellation: { parameter: 'signal' },");
|
|
2786
|
+
lines.push(` result: ${indent(strictCodec(invocation.result, schemas.boundary(resultBoundaryKey(invocation))), 2).trimStart()},`);
|
|
2787
|
+
lines.push(` sourceLocation: ${JSON.stringify(invocation.location)},`);
|
|
2788
|
+
lines.push("}");
|
|
2789
|
+
return lines.join("\n");
|
|
2790
|
+
}
|
|
2791
|
+
renderRemoteDts(packageModel) {
|
|
2792
|
+
const imports = remoteImports(packageModel.invocations);
|
|
2793
|
+
const referenceNames = allocateRemoteImportNames(imports);
|
|
2794
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
2795
|
+
for (const imported of imports) {
|
|
2796
|
+
const values = grouped.get(imported.specifier) ?? [];
|
|
2797
|
+
values.push({
|
|
2798
|
+
name: imported.name,
|
|
2799
|
+
local: referenceNames.get(imported.symbol)
|
|
2800
|
+
});
|
|
2801
|
+
grouped.set(imported.specifier, values);
|
|
2802
|
+
}
|
|
2803
|
+
const lines = [
|
|
2804
|
+
"/* Generated by @deepseek-ai/dsh-typert-generator from the Host FaceModel — do not edit. */",
|
|
2805
|
+
"import type {",
|
|
2806
|
+
" TypeRTRemoteContribution,",
|
|
2807
|
+
"} from '@deepseek-ai/dsh-type-meta'"
|
|
2808
|
+
];
|
|
2809
|
+
const sourceMap = new GenMapping({ file: "typert.remote-client.d.ts" });
|
|
2810
|
+
for (const [specifier, values] of [...grouped].sort(([left], [right]) => left.localeCompare(right))) {
|
|
2811
|
+
const names = values.sort((left, right) => left.local.localeCompare(right.local)).map((value) => value.name === value.local ? value.name : `${value.name} as ${value.local}`);
|
|
2812
|
+
lines.push(`import type { ${names.join(", ")} } from ${quote$1(specifier)}`);
|
|
2813
|
+
}
|
|
2814
|
+
lines.push("");
|
|
2815
|
+
lines.push("declare module '@deepseek-ai/dsh-type-meta' {");
|
|
2816
|
+
const direct = packageModel.invocations.filter((invocation) => invocation.invocation.kind === "direct");
|
|
2817
|
+
const scoped = packageModel.invocations.filter((invocation) => invocation.invocation.kind === "context" || invocation.scope !== void 0);
|
|
2818
|
+
if (direct.length > 0) {
|
|
2819
|
+
for (const namespace of uniqueNamespaces(direct)) {
|
|
2820
|
+
lines.push(` interface ${remoteNamespaceInterface(namespace)} {`);
|
|
2821
|
+
for (const invocation of direct.filter((candidate) => candidate.namespace === namespace)) this.pushRemoteNamespaceSignature(lines, sourceMap, packageModel, invocation, referenceNames);
|
|
2822
|
+
lines.push(" }");
|
|
2823
|
+
}
|
|
2824
|
+
lines.push(" interface TypeRTRemoteMap {");
|
|
2825
|
+
for (const invocation of direct) this.pushRemoteSignature(lines, sourceMap, packageModel, invocation, referenceNames, false);
|
|
2826
|
+
lines.push(" }");
|
|
2827
|
+
lines.push(" interface TypeRTRemoteNamespaceMap {");
|
|
2828
|
+
for (const namespace of uniqueNamespaces(direct)) lines.push(` ${quote$1(namespace)}: ${remoteNamespaceInterface(namespace)}`);
|
|
2829
|
+
lines.push(" }");
|
|
2830
|
+
}
|
|
2831
|
+
if (scoped.length > 0) {
|
|
2832
|
+
lines.push(" interface TypeRTRemoteScopeMap {");
|
|
2833
|
+
for (const invocation of scoped) this.pushRemoteSignature(lines, sourceMap, packageModel, invocation, referenceNames, true);
|
|
2834
|
+
lines.push(" }");
|
|
2835
|
+
}
|
|
2836
|
+
lines.push("}");
|
|
2837
|
+
lines.push("");
|
|
2838
|
+
lines.push("export declare const TYPERT_REMOTE: TypeRTRemoteContribution");
|
|
2839
|
+
lines.push("export default TYPERT_REMOTE");
|
|
2840
|
+
lines.push("//# sourceMappingURL=typert.remote-client.d.ts.map");
|
|
2841
|
+
return {
|
|
2842
|
+
dts: `${lines.join("\n")}\n`,
|
|
2843
|
+
dtsMap: `${JSON.stringify(toEncodedMap(sourceMap))}\n`
|
|
2844
|
+
};
|
|
2845
|
+
}
|
|
2846
|
+
pushRemoteSignature(lines, sourceMap, packageModel, invocation, referenceNames, scoped) {
|
|
2847
|
+
const signature = this.remoteSignature(invocation, referenceNames, scoped);
|
|
2848
|
+
const keyLength = signature.indexOf(": (");
|
|
2849
|
+
if (keyLength < 0) throw new TypertEmitError(`Remote signature ${invocation.id} has no property delimiter`);
|
|
2850
|
+
this.pushMappedRemoteSignature(lines, sourceMap, packageModel, invocation, signature, keyLength);
|
|
2851
|
+
}
|
|
2852
|
+
pushRemoteNamespaceSignature(lines, sourceMap, packageModel, invocation, referenceNames) {
|
|
2853
|
+
const key = renderRemotePropertyName(invocation.method);
|
|
2854
|
+
const signature = `${key}: ${this.remoteFunctionType(invocation, referenceNames, false)}`;
|
|
2855
|
+
this.pushMappedRemoteSignature(lines, sourceMap, packageModel, invocation, signature, key.length);
|
|
2856
|
+
}
|
|
2857
|
+
pushMappedRemoteSignature(lines, sourceMap, packageModel, invocation, signature, keyLength) {
|
|
2858
|
+
lines.push(` ${signature}`);
|
|
2859
|
+
const generatedLine = lines.length;
|
|
2860
|
+
const source = remoteDeclarationSource(packageModel, invocation);
|
|
2861
|
+
addMapping(sourceMap, {
|
|
2862
|
+
generated: {
|
|
2863
|
+
line: generatedLine,
|
|
2864
|
+
column: 4
|
|
2865
|
+
},
|
|
2866
|
+
source,
|
|
2867
|
+
original: {
|
|
2868
|
+
line: invocation.location.line,
|
|
2869
|
+
column: invocation.location.column - 1
|
|
2870
|
+
},
|
|
2871
|
+
name: invocation.method
|
|
2872
|
+
});
|
|
2873
|
+
addMapping(sourceMap, { generated: {
|
|
2874
|
+
line: generatedLine,
|
|
2875
|
+
column: 4 + keyLength
|
|
2876
|
+
} });
|
|
2877
|
+
}
|
|
2878
|
+
remoteSignature(invocation, referenceNames, scoped) {
|
|
2879
|
+
const context = invocation.invocation.kind === "context" ? invocation.invocation.context : invocation.scope?.context;
|
|
2880
|
+
return `${quote$1(scoped ? `${context}:${invocation.namespace}/${invocation.method}` : `${invocation.namespace}/${invocation.method}`)}: ${this.remoteFunctionType(invocation, referenceNames, scoped)}`;
|
|
2881
|
+
}
|
|
2882
|
+
remoteFunctionType(invocation, referenceNames, scoped) {
|
|
2883
|
+
const parameters = invocation.parameters.filter((parameter) => !scoped || invocation.invocation.kind === "context" || parameter.wire !== invocation.scope?.wire).map((parameter) => `${safeIdentifier(parameter.wire)}: ${this.renderer.renderType(parameter.boundary.type, referenceNames)}`);
|
|
2884
|
+
if (invocation.cancellation !== void 0) parameters.push("signal?: AbortSignal");
|
|
2885
|
+
const result = this.renderer.renderType(invocation.result.type, referenceNames);
|
|
2886
|
+
return `(${parameters.join(", ")}) => Promise<${result}>`;
|
|
2887
|
+
}
|
|
2888
|
+
};
|
|
2889
|
+
function remoteDeclarationSource(packageModel, invocation) {
|
|
2890
|
+
const relativeSource = posix.relative(packageModel.root, invocation.location.file);
|
|
2891
|
+
if (relativeSource === "" || relativeSource === ".." || relativeSource.startsWith("../") || posix.isAbsolute(relativeSource)) throw new TypertEmitError(`Remote declaration ${invocation.id} is outside its package root ${packageModel.root}`);
|
|
2892
|
+
return posix.join("..", relativeSource);
|
|
2893
|
+
}
|
|
2894
|
+
function uniqueNamespaces(invocations) {
|
|
2895
|
+
return [...new Set(invocations.map((invocation) => invocation.namespace))].sort();
|
|
2896
|
+
}
|
|
2897
|
+
function remoteNamespaceInterface(namespace) {
|
|
2898
|
+
return `TypeRTRemoteNamespace$${Buffer.from(namespace, "utf8").toString("hex")}`;
|
|
2899
|
+
}
|
|
2900
|
+
var SchemaEmitter = class {
|
|
2901
|
+
renderer;
|
|
2902
|
+
schemas;
|
|
2903
|
+
boundaries;
|
|
2904
|
+
names = /* @__PURE__ */ new Map();
|
|
2905
|
+
boundaryNames = /* @__PURE__ */ new Map();
|
|
2906
|
+
declarations;
|
|
2907
|
+
constructor(renderer, schemas, boundaries) {
|
|
2908
|
+
this.renderer = renderer;
|
|
2909
|
+
this.schemas = schemas;
|
|
2910
|
+
this.boundaries = boundaries;
|
|
2911
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
2912
|
+
for (const schema of schemas) for (const declaration of renderer.declarationClosureForTypes([schema.type])) declarations.set(declaration.id, declaration);
|
|
2913
|
+
for (const boundary of boundaries) for (const declaration of renderer.declarationClosureForTypes([boundary.type])) declarations.set(declaration.id, declaration);
|
|
2914
|
+
this.declarations = renderer.graph.declarations.filter((declaration) => declarations.has(declaration.id));
|
|
2915
|
+
const identifiers = /* @__PURE__ */ new Set();
|
|
2916
|
+
for (const declaration of this.declarations) {
|
|
2917
|
+
const base = `${safeIdentifier(declaration.name)}$schema`;
|
|
2918
|
+
let name = base;
|
|
2919
|
+
let suffix = 2;
|
|
2920
|
+
while (identifiers.has(name)) name = `${base}${String(suffix++)}`;
|
|
2921
|
+
identifiers.add(name);
|
|
2922
|
+
this.names.set(declaration.id, name);
|
|
2923
|
+
}
|
|
2924
|
+
for (const boundary of boundaries) {
|
|
2925
|
+
const base = `${safeIdentifier(boundary.key)}$schema`;
|
|
2926
|
+
let name = base;
|
|
2927
|
+
let suffix = 2;
|
|
2928
|
+
while (identifiers.has(name)) name = `${base}${String(suffix++)}`;
|
|
2929
|
+
identifiers.add(name);
|
|
2930
|
+
this.boundaryNames.set(boundary.key, name);
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
emit() {
|
|
2934
|
+
const definitions = this.declarations.map((declaration) => this.declarationDefinition(declaration));
|
|
2935
|
+
for (const boundary of this.boundaries) definitions.push(`const ${this.boundaryName(boundary.key)} = ${this.typeSchema(boundary.type)}`);
|
|
2936
|
+
return {
|
|
2937
|
+
definitions,
|
|
2938
|
+
exports: this.schemas.map((model) => ({
|
|
2939
|
+
model,
|
|
2940
|
+
exportName: safeIdentifier(model.export.name),
|
|
2941
|
+
internalName: this.exportSchemaName(model)
|
|
2942
|
+
})),
|
|
2943
|
+
boundary: (key) => this.boundaryName(key)
|
|
2944
|
+
};
|
|
2945
|
+
}
|
|
2946
|
+
declarationDefinition(declaration) {
|
|
2947
|
+
const name = this.schemaName(declaration.id);
|
|
2948
|
+
if (declaration.typeParameters.length === 0) return `const ${name} = ${this.declarationSchema(declaration, /* @__PURE__ */ new Map())}`;
|
|
2949
|
+
const parameters = declaration.typeParameters.map((parameter, index) => [`type${String(index)}$schema`, parameter.id]);
|
|
2950
|
+
const substitutions = new Map(parameters.map(([schema, id]) => [id, schema]));
|
|
2951
|
+
return `const ${name} = (${parameters.map(([schema]) => schema).join(", ")}) => ${this.declarationSchema(declaration, substitutions)}`;
|
|
2952
|
+
}
|
|
2953
|
+
declarationSchema(declaration, substitutions) {
|
|
2954
|
+
if (declaration.kind === "enum") this.fail(declaration.name, "enum declarations have no Zod projection");
|
|
2955
|
+
if (declaration.kind === "alias") {
|
|
2956
|
+
if (declaration.type === void 0) this.fail(declaration.name, "alias has no modeled type");
|
|
2957
|
+
return this.describe(this.typeSchema(declaration.type, substitutions), declaration);
|
|
2958
|
+
}
|
|
2959
|
+
let result = this.objectSchema(declaration.members, declaration.name, substitutions);
|
|
2960
|
+
for (const heritage of declaration.extends) result = `z.intersection(${this.typeSchema(heritage, substitutions)}, ${result})`;
|
|
2961
|
+
return this.describe(result, declaration);
|
|
2962
|
+
}
|
|
2963
|
+
typeSchema(id, substitutions = /* @__PURE__ */ new Map()) {
|
|
2964
|
+
const node = this.renderer.node(id);
|
|
2965
|
+
switch (node.kind) {
|
|
2966
|
+
case "keyword": return this.keywordSchema(node.name);
|
|
2967
|
+
case "literal": return `z.literal(${node.text})`;
|
|
2968
|
+
case "parenthesized": return this.typeSchema(node.type, substitutions);
|
|
2969
|
+
case "reference": return this.referenceSchema(node, substitutions);
|
|
2970
|
+
case "union":
|
|
2971
|
+
if (node.types.length === 0) return "z.never()";
|
|
2972
|
+
if (node.types.length === 1) return this.typeSchema(node.types[0], substitutions);
|
|
2973
|
+
return `z.union([${node.types.map((type) => this.typeSchema(type, substitutions)).join(", ")}])`;
|
|
2974
|
+
case "intersection": {
|
|
2975
|
+
const [head, ...tail] = node.types;
|
|
2976
|
+
if (head === void 0) return "z.unknown()";
|
|
2977
|
+
return tail.reduce((left, right) => `z.intersection(${left}, ${this.typeSchema(right, substitutions)})`, this.typeSchema(head, substitutions));
|
|
2978
|
+
}
|
|
2979
|
+
case "array": return `z.array(${this.typeSchema(node.element, substitutions)})`;
|
|
2980
|
+
case "tuple": {
|
|
2981
|
+
const fixed = node.elements.filter((element) => !element.rest);
|
|
2982
|
+
const rest = node.elements.find((element) => element.rest);
|
|
2983
|
+
let schema = `z.tuple([${fixed.map((element) => this.optional(this.typeSchema(element.type, substitutions), element.optional)).join(", ")}])`;
|
|
2984
|
+
if (rest !== void 0) schema += `.rest(${this.tupleRestSchema(rest.type, substitutions)})`;
|
|
2985
|
+
return schema;
|
|
2986
|
+
}
|
|
2987
|
+
case "object": return this.objectSchema(node.members, id, substitutions);
|
|
2988
|
+
case "operator":
|
|
2989
|
+
case "indexed-access":
|
|
2990
|
+
case "conditional":
|
|
2991
|
+
case "infer":
|
|
2992
|
+
case "mapped":
|
|
2993
|
+
case "template-literal":
|
|
2994
|
+
case "type-query":
|
|
2995
|
+
case "import-type":
|
|
2996
|
+
case "predicate":
|
|
2997
|
+
case "function":
|
|
2998
|
+
case "constructor":
|
|
2999
|
+
case "this": return this.unsupported(node);
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
referenceSchema(node, substitutions) {
|
|
3003
|
+
if (node.target.kind === "declaration") {
|
|
3004
|
+
const name = this.schemaName(node.target.symbol);
|
|
3005
|
+
const declaration = this.renderer.declaration(node.target.symbol);
|
|
3006
|
+
if (declaration.typeParameters.length === 0) {
|
|
3007
|
+
if (node.arguments.length > 0) this.fail(node.name, `non-generic declaration received ${String(node.arguments.length)} type arguments`);
|
|
3008
|
+
return `z.lazy(() => ${name})`;
|
|
3009
|
+
}
|
|
3010
|
+
return `z.lazy(() => ${name}(${this.declarationArguments(node, declaration, substitutions).join(", ")}))`;
|
|
3011
|
+
}
|
|
3012
|
+
if (node.target.kind === "type-parameter") {
|
|
3013
|
+
if (node.arguments.length > 0) this.fail(node.name, "type parameter reference cannot receive type arguments");
|
|
3014
|
+
const schema = substitutions.get(node.target.parameter);
|
|
3015
|
+
if (schema === void 0) this.fail(node.name, "type parameter has no schema substitution");
|
|
3016
|
+
return schema;
|
|
3017
|
+
}
|
|
3018
|
+
if (node.target.kind === "standard") switch (node.target.name) {
|
|
3019
|
+
case "Array":
|
|
3020
|
+
case "ReadonlyArray": {
|
|
3021
|
+
const element = node.arguments[0];
|
|
3022
|
+
if (element === void 0) this.fail(node.name, "array reference has no element type");
|
|
3023
|
+
return this.readonly(`z.array(${this.typeSchema(element, substitutions)})`, node.target.name === "ReadonlyArray");
|
|
3024
|
+
}
|
|
3025
|
+
case "Record": {
|
|
3026
|
+
const key = node.arguments[0];
|
|
3027
|
+
const value = node.arguments[1];
|
|
3028
|
+
if (key === void 0 || value === void 0) this.fail(node.name, "Record requires key and value types");
|
|
3029
|
+
return `z.record(${this.typeSchema(key, substitutions)}, ${this.typeSchema(value, substitutions)})`;
|
|
3030
|
+
}
|
|
3031
|
+
case "Date": return "z.date()";
|
|
3032
|
+
default: this.fail(node.name, `standard type ${node.target.name} has no Zod projection`);
|
|
3033
|
+
}
|
|
3034
|
+
this.fail(node.name, `${node.target.kind} reference has no Zod projection`);
|
|
3035
|
+
}
|
|
3036
|
+
declarationArguments(node, declaration, substitutions) {
|
|
3037
|
+
if (node.arguments.length > declaration.typeParameters.length) this.fail(node.name, `generic declaration accepts ${String(declaration.typeParameters.length)} type arguments but received ${String(node.arguments.length)}`);
|
|
3038
|
+
const resolved = new Map(substitutions);
|
|
3039
|
+
const arguments_ = [];
|
|
3040
|
+
for (const [index, parameter] of declaration.typeParameters.entries()) {
|
|
3041
|
+
const argument = node.arguments[index];
|
|
3042
|
+
const schema = argument === void 0 ? parameter.default === void 0 ? this.fail(node.name, `missing type argument ${parameter.name}`) : this.typeSchema(parameter.default, resolved) : this.typeSchema(argument, substitutions);
|
|
3043
|
+
arguments_.push(schema);
|
|
3044
|
+
resolved.set(parameter.id, schema);
|
|
3045
|
+
}
|
|
3046
|
+
return arguments_;
|
|
3047
|
+
}
|
|
3048
|
+
tupleRestSchema(id, substitutions) {
|
|
3049
|
+
const node = this.renderer.node(id);
|
|
3050
|
+
if (node.kind === "array") return this.typeSchema(node.element, substitutions);
|
|
3051
|
+
if (node.kind === "reference" && node.target.kind === "standard" && (node.target.name === "Array" || node.target.name === "ReadonlyArray")) {
|
|
3052
|
+
const element = node.arguments[0];
|
|
3053
|
+
if (element === void 0) this.fail(node.name, "tuple rest array has no element type");
|
|
3054
|
+
return this.typeSchema(element, substitutions);
|
|
3055
|
+
}
|
|
3056
|
+
this.fail(id, "tuple rest element must retain an array type");
|
|
3057
|
+
}
|
|
3058
|
+
objectSchema(members, subject, substitutions) {
|
|
3059
|
+
const properties = [];
|
|
3060
|
+
const indices = [];
|
|
3061
|
+
let symbolMembers = 0;
|
|
3062
|
+
for (const member of members) {
|
|
3063
|
+
if (member.static || member.visibility !== "public") continue;
|
|
3064
|
+
if (member.computed === "symbol") {
|
|
3065
|
+
symbolMembers++;
|
|
3066
|
+
continue;
|
|
3067
|
+
}
|
|
3068
|
+
if (member.computed === "dynamic") this.fail(subject, `computed member ${member.name} has no fixed JSON property name`);
|
|
3069
|
+
if (member.kind === "index") {
|
|
3070
|
+
const parameter = member.signature.parameters[0];
|
|
3071
|
+
if (member.signature.parameters.length !== 1 || parameter === void 0) this.fail(subject, "index signature must have exactly one key parameter");
|
|
3072
|
+
indices.push(this.readonly(`z.record(${this.typeSchema(parameter.type, substitutions)}, ${this.typeSchema(member.signature.returns, substitutions)})`, member.readonly));
|
|
3073
|
+
continue;
|
|
3074
|
+
}
|
|
3075
|
+
if (member.kind !== "property") this.fail(subject, `${member.kind} member ${member.name} is not data-schema projectable`);
|
|
3076
|
+
const property = this.describe(this.optional(this.readonly(this.typeSchema(member.type, substitutions), member.readonly), member.optional), member);
|
|
3077
|
+
properties.push(`${quote$1(member.jsonName ?? member.name)}: ${property}`);
|
|
3078
|
+
}
|
|
3079
|
+
if (indices.length > 1) this.fail(subject, "object type has more than one JSON index signature");
|
|
3080
|
+
if (properties.length === 0 && indices.length === 0 && symbolMembers > 0) return "z.unknown()";
|
|
3081
|
+
const object = `z.object({${properties.length === 0 ? "" : `\n${properties.map((property) => ` ${property},`).join("\n")}\n`}})`;
|
|
3082
|
+
const index = indices[0];
|
|
3083
|
+
if (index === void 0) return object;
|
|
3084
|
+
if (properties.length === 0) return index;
|
|
3085
|
+
return `z.intersection(${object}, ${index})`;
|
|
3086
|
+
}
|
|
3087
|
+
exportSchemaName(model) {
|
|
3088
|
+
const name = this.schemaName(model.symbol);
|
|
3089
|
+
if (this.renderer.declaration(model.symbol).typeParameters.length > 0) this.fail(model.export.name, "generic schema exports require a concrete declaration");
|
|
3090
|
+
return name;
|
|
3091
|
+
}
|
|
3092
|
+
keywordSchema(name) {
|
|
3093
|
+
switch (name) {
|
|
3094
|
+
case "any": return "z.any()";
|
|
3095
|
+
case "unknown": return "z.unknown()";
|
|
3096
|
+
case "never": return "z.never()";
|
|
3097
|
+
case "string": return "z.string()";
|
|
3098
|
+
case "number": return "z.number()";
|
|
3099
|
+
case "bigint": return "z.bigint()";
|
|
3100
|
+
case "boolean": return "z.boolean()";
|
|
3101
|
+
case "symbol": return "z.symbol()";
|
|
3102
|
+
case "undefined": return "z.undefined()";
|
|
3103
|
+
case "void": return "z.void()";
|
|
3104
|
+
case "object": return "z.custom((value) => (typeof value === 'object' && value !== null) || typeof value === 'function')";
|
|
3105
|
+
default: this.fail(name, `keyword ${name} has no Zod projection`);
|
|
3106
|
+
}
|
|
3107
|
+
}
|
|
3108
|
+
schemaName(symbol) {
|
|
3109
|
+
const name = this.names.get(symbol);
|
|
3110
|
+
if (name === void 0) this.fail(symbol, "referenced declaration is outside the selected schema closure");
|
|
3111
|
+
return name;
|
|
3112
|
+
}
|
|
3113
|
+
boundaryName(key) {
|
|
3114
|
+
const name = this.boundaryNames.get(key);
|
|
3115
|
+
if (name === void 0) this.fail(key, "invocation boundary is outside the selected schema roots");
|
|
3116
|
+
return name;
|
|
3117
|
+
}
|
|
3118
|
+
describe(schema, documentation) {
|
|
3119
|
+
return documentation.description === void 0 ? schema : `${schema}.describe(${quote$1(documentation.description)})`;
|
|
3120
|
+
}
|
|
3121
|
+
optional(schema, optional) {
|
|
3122
|
+
return optional ? `${schema}.optional()` : schema;
|
|
3123
|
+
}
|
|
3124
|
+
readonly(schema, readonly) {
|
|
3125
|
+
return readonly ? `${schema}.readonly()` : schema;
|
|
3126
|
+
}
|
|
3127
|
+
unsupported(node) {
|
|
3128
|
+
this.fail(node.id, `type node ${node.kind} has no Zod projection`);
|
|
3129
|
+
}
|
|
3130
|
+
fail(subject, message) {
|
|
3131
|
+
throw new TypertEmitError(`typert Zod emitter: ${subject}: ${message}`);
|
|
3132
|
+
}
|
|
3133
|
+
};
|
|
3134
|
+
function documentationLiteral(documentation) {
|
|
3135
|
+
return {
|
|
3136
|
+
...documentation.description === void 0 ? {} : { description: documentation.description },
|
|
3137
|
+
...documentation.summary === void 0 ? {} : { summary: documentation.summary },
|
|
3138
|
+
tags: documentation.tags,
|
|
3139
|
+
...documentation.jsDoc === void 0 ? {} : { jsDoc: documentation.jsDoc }
|
|
3140
|
+
};
|
|
3141
|
+
}
|
|
3142
|
+
function invocationBoundaryRoots(invocations) {
|
|
3143
|
+
const result = [];
|
|
3144
|
+
for (const invocation of invocations) {
|
|
3145
|
+
if (invocation.invocation.kind === "context") result.push({
|
|
3146
|
+
key: contextBoundaryKey(invocation),
|
|
3147
|
+
type: invocation.invocation.boundary.codecType
|
|
3148
|
+
});
|
|
3149
|
+
invocation.parameters.forEach((parameter, index) => {
|
|
3150
|
+
result.push({
|
|
3151
|
+
key: parameterBoundaryKey(invocation, index),
|
|
3152
|
+
type: parameter.boundary.codecType
|
|
3153
|
+
});
|
|
3154
|
+
});
|
|
3155
|
+
result.push({
|
|
3156
|
+
key: resultBoundaryKey(invocation),
|
|
3157
|
+
type: invocation.result.codecType
|
|
3158
|
+
});
|
|
3159
|
+
}
|
|
3160
|
+
return result;
|
|
3161
|
+
}
|
|
3162
|
+
function contextBoundaryKey(invocation) {
|
|
3163
|
+
return `${invocation.id}:context`;
|
|
3164
|
+
}
|
|
3165
|
+
function parameterBoundaryKey(invocation, index) {
|
|
3166
|
+
return `${invocation.id}:parameter:${String(index)}`;
|
|
3167
|
+
}
|
|
3168
|
+
function resultBoundaryKey(invocation) {
|
|
3169
|
+
return `${invocation.id}:result`;
|
|
3170
|
+
}
|
|
3171
|
+
function strictCodec(boundary, schema) {
|
|
3172
|
+
return [
|
|
3173
|
+
"{",
|
|
3174
|
+
" mode: 'strict',",
|
|
3175
|
+
` typeSymbol: ${quote$1(boundary.typeSymbol)},`,
|
|
3176
|
+
` schema: ${schema},`,
|
|
3177
|
+
"}"
|
|
3178
|
+
].join("\n");
|
|
3179
|
+
}
|
|
3180
|
+
function remoteImports(invocations) {
|
|
3181
|
+
const imports = /* @__PURE__ */ new Map();
|
|
3182
|
+
const add = (boundary) => {
|
|
3183
|
+
for (const imported of boundary.imports) {
|
|
3184
|
+
const current = imports.get(imported.symbol);
|
|
3185
|
+
if (current !== void 0 && (current.specifier !== imported.specifier || current.name !== imported.name)) throw new TypertEmitError(`typert Remote emitter: symbol ${imported.symbol} has inconsistent public imports`);
|
|
3186
|
+
imports.set(imported.symbol, imported);
|
|
3187
|
+
}
|
|
3188
|
+
};
|
|
3189
|
+
for (const invocation of invocations) {
|
|
3190
|
+
if (invocation.invocation.kind === "context") add(invocation.invocation.boundary);
|
|
3191
|
+
for (const parameter of invocation.parameters) add(parameter.boundary);
|
|
3192
|
+
add(invocation.result);
|
|
3193
|
+
}
|
|
3194
|
+
return [...imports.values()].sort((left, right) => left.specifier.localeCompare(right.specifier) || left.name.localeCompare(right.name));
|
|
3195
|
+
}
|
|
3196
|
+
function allocateRemoteImportNames(imports) {
|
|
3197
|
+
const used = new Set(["TypeRTRemoteContribution", "TYPERT_REMOTE"]);
|
|
3198
|
+
const names = /* @__PURE__ */ new Map();
|
|
3199
|
+
for (const imported of imports) {
|
|
3200
|
+
const base = safeIdentifier(imported.name);
|
|
3201
|
+
let name = base;
|
|
3202
|
+
let suffix = 2;
|
|
3203
|
+
while (used.has(name)) name = `${base}$remote${String(suffix++)}`;
|
|
3204
|
+
used.add(name);
|
|
3205
|
+
names.set(imported.symbol, name);
|
|
3206
|
+
}
|
|
3207
|
+
return names;
|
|
3208
|
+
}
|
|
3209
|
+
function packageExportSpecifier(packageName, subpath) {
|
|
3210
|
+
return subpath === "." ? packageName : `${packageName}${subpath.slice(1)}`;
|
|
3211
|
+
}
|
|
3212
|
+
function safeIdentifier(name) {
|
|
3213
|
+
const normalized = name.replace(/[^$\w]/gu, "_");
|
|
3214
|
+
if (/^[$A-Z_a-z]/u.test(normalized)) return normalized;
|
|
3215
|
+
return `_${normalized}`;
|
|
3216
|
+
}
|
|
3217
|
+
function renderRemotePropertyName(name) {
|
|
3218
|
+
return /^[$A-Z_a-z][$\w]*$/u.test(name) ? name : quote$1(name);
|
|
3219
|
+
}
|
|
3220
|
+
function quote$1(value) {
|
|
3221
|
+
return `'${value.replaceAll("\\", "\\\\").replaceAll("'", "\\'").replaceAll("\n", "\\n").replaceAll("\r", "\\r")}'`;
|
|
3222
|
+
}
|
|
3223
|
+
function indent(value, spaces) {
|
|
3224
|
+
const prefix = " ".repeat(spaces);
|
|
3225
|
+
return value.split("\n").map((line) => `${prefix}${line}`).join("\n");
|
|
3226
|
+
}
|
|
3227
|
+
//#endregion
|
|
3228
|
+
//#region lib/types/cordis-catalog.js
|
|
3229
|
+
/**
|
|
3230
|
+
* Cordis catalog-specific projection over the compiler-independent Typert
|
|
3231
|
+
* model. This module owns Cordis validation and text projection mechanics;
|
|
3232
|
+
* callers supply repository-specific type classifications and inherited data.
|
|
3233
|
+
* @module @deepseek-ai/dsh-typert-generator
|
|
3234
|
+
*/
|
|
3235
|
+
/** Append fail-closed signature type-link violations from the retained type tree. */
|
|
3236
|
+
function checkTypeLinks(where, names, policy, violations) {
|
|
3237
|
+
for (const name of names) {
|
|
3238
|
+
if (Object.hasOwn(policy.linkedTypePages, name) || policy.foundationTypeNames.has(name) || Object.hasOwn(policy.typeLinkExemptions, name)) continue;
|
|
3239
|
+
violations.push(`${where} references unclassified type '${name}'. Add it to linkedTypePages with its documentation page, to foundationTypeNames if TypeScript or the framework owns it, or to typeLinkExemptions with the non-catalog documentation owner.`);
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
/** Throw one aggregated diagnostic for every unclassified signature type. */
|
|
3243
|
+
function reportTypeLinkViolations(gate, violations) {
|
|
3244
|
+
if (violations.length === 0) return;
|
|
3245
|
+
throw new Error(`${gate}: ${violations.length} signature type-link coverage violation(s):\n` + violations.map((violation) => ` ${violation}`).join("\n"));
|
|
3246
|
+
}
|
|
3247
|
+
/** Repository-specific Cordis validation and projection over one Typert face. */
|
|
3248
|
+
var CordisCatalogProjector = class {
|
|
3249
|
+
face;
|
|
3250
|
+
sourceDeclarations;
|
|
3251
|
+
policy;
|
|
3252
|
+
renderer;
|
|
3253
|
+
/**
|
|
3254
|
+
* @param face - analyzed host face containing package business semantics.
|
|
3255
|
+
* @param sourceDeclarations - exported declarations available to the runtime type closure.
|
|
3256
|
+
* @param policy - caller-owned type classifications and inherited Cordis data.
|
|
3257
|
+
*/
|
|
3258
|
+
constructor(face, sourceDeclarations, policy) {
|
|
3259
|
+
this.face = face;
|
|
3260
|
+
this.sourceDeclarations = sourceDeclarations;
|
|
3261
|
+
this.policy = policy;
|
|
3262
|
+
if (face.face !== "host") throw new Error(`cordis catalog requires the host face, received ${face.face}`);
|
|
3263
|
+
this.renderer = new TypeGraphRenderer(face.graph);
|
|
3264
|
+
}
|
|
3265
|
+
/**
|
|
3266
|
+
* Validate and project the host model's Cordis surface.
|
|
3267
|
+
* @returns every validated service and event projected from the host model.
|
|
3268
|
+
*/
|
|
3269
|
+
project() {
|
|
3270
|
+
return {
|
|
3271
|
+
events: this.collectEvents(),
|
|
3272
|
+
services: this.collectServices()
|
|
3273
|
+
};
|
|
3274
|
+
}
|
|
3275
|
+
/**
|
|
3276
|
+
* Render the model-facing static API consumed by `tool-cordis`.
|
|
3277
|
+
* @param model - validated Cordis catalog projection from this projector.
|
|
3278
|
+
* @returns the model-facing TypeScript catalog source.
|
|
3279
|
+
*/
|
|
3280
|
+
renderRuntimeApi(model) {
|
|
3281
|
+
return renderRuntimeApi(model.services, model.events, this.runtimeTypes(model.services), this.policy.inheritedServices);
|
|
3282
|
+
}
|
|
3283
|
+
collectEvents() {
|
|
3284
|
+
const entries = [];
|
|
3285
|
+
const violations = [];
|
|
3286
|
+
const typeLinkViolations = [];
|
|
3287
|
+
for (const packageModel of this.face.packages) for (const event of packageModel.events) {
|
|
3288
|
+
const source = pointer(event.location);
|
|
3289
|
+
const where = `event '${event.name}' (${source})`;
|
|
3290
|
+
const node = this.renderer.node(event.signature);
|
|
3291
|
+
if (node.kind !== "function") {
|
|
3292
|
+
violations.push(`${where} is not represented by a callable type.`);
|
|
3293
|
+
continue;
|
|
3294
|
+
}
|
|
3295
|
+
checkTypeLinks(where, signatureTypeNames(this.renderer, node.signature), this.policy, typeLinkViolations);
|
|
3296
|
+
const parsed = parseJsDoc(event.jsDoc ?? "");
|
|
3297
|
+
const mode = event.mode;
|
|
3298
|
+
if (!isMode(mode)) violations.push(`${where} is missing an @mode tag. Add '@mode emit|waterfall|parallel|serial' to its JSDoc (see AGENTS.md).`);
|
|
3299
|
+
const last = node.signature.parameters.at(-1);
|
|
3300
|
+
const hasNext = last?.name === "next";
|
|
3301
|
+
if (isMode(mode) && hasNext && mode !== "waterfall") violations.push(`${where} has a trailing 'next' parameter (structurally a waterfall) but is tagged '@mode ${mode}'. Fix the tag or the signature.`);
|
|
3302
|
+
if (isMode(mode) && !hasNext && mode === "waterfall") violations.push(`${where} is tagged '@mode waterfall' but has no trailing 'next' parameter. A waterfall delegates via next().`);
|
|
3303
|
+
if (parsed.doc === "") violations.push(`${where} has no description prose. Say what happened / what a listener may do, above the block tags.`);
|
|
3304
|
+
checkParams(where, "event", node.signature.parameters, parsed.params, (parameter) => parameter.receiver || hasNext && parameter === last, violations);
|
|
3305
|
+
if (isMode(mode)) entries.push({
|
|
3306
|
+
name: event.name,
|
|
3307
|
+
scope: event.name.split("/")[0] ?? event.name,
|
|
3308
|
+
signature: event.text,
|
|
3309
|
+
jsDoc: event.jsDoc ?? "",
|
|
3310
|
+
mode,
|
|
3311
|
+
doc: parsed.doc,
|
|
3312
|
+
source
|
|
3313
|
+
});
|
|
3314
|
+
}
|
|
3315
|
+
reportViolations("gen-cordis-catalog", violations);
|
|
3316
|
+
reportTypeLinkViolations("gen-cordis-catalog", typeLinkViolations);
|
|
3317
|
+
return entries;
|
|
3318
|
+
}
|
|
3319
|
+
collectServices() {
|
|
3320
|
+
const entries = [];
|
|
3321
|
+
const violations = [];
|
|
3322
|
+
const typeLinkViolations = [];
|
|
3323
|
+
for (const packageModel of this.face.packages) for (const service of packageModel.services) {
|
|
3324
|
+
const declaration = this.renderer.declaration(service.symbol);
|
|
3325
|
+
if (declaration.kind !== "class" || !/^packages\/[^/]+\/[^/]+\/src\/[^/]+\.ts$/.test(service.location.file) || declaration.location.file !== service.location.file) continue;
|
|
3326
|
+
const doc = parseJsDoc(declaration.jsDoc ?? "").doc;
|
|
3327
|
+
const source = pointer(declaration.location);
|
|
3328
|
+
if (doc === "") violations.push(`service ctx.${service.key} (${source}): class ${declaration.name} has no JSDoc.`);
|
|
3329
|
+
const methods = [];
|
|
3330
|
+
for (const memberId of service.members) {
|
|
3331
|
+
const member = this.renderer.member(memberId);
|
|
3332
|
+
if (member.kind !== "method" || member.name.startsWith("[")) continue;
|
|
3333
|
+
const where = `service method ctx.${service.key}.${member.name} (${pointer(member.location)})`;
|
|
3334
|
+
checkTypeLinks(where, signatureTypeNames(this.renderer, member.signature), this.policy, typeLinkViolations);
|
|
3335
|
+
methods.push({
|
|
3336
|
+
signature: member.text,
|
|
3337
|
+
jsDoc: member.jsDoc ?? ""
|
|
3338
|
+
});
|
|
3339
|
+
if (member.jsDoc === void 0) {
|
|
3340
|
+
violations.push(`${where} has no JSDoc.`);
|
|
3341
|
+
continue;
|
|
3342
|
+
}
|
|
3343
|
+
const parsed = parseJsDoc(member.jsDoc);
|
|
3344
|
+
if (parsed.doc === "") violations.push(`${where} has no description prose above its block tags.`);
|
|
3345
|
+
checkParams(where, "service", member.signature.parameters, parsed.params, (parameter) => parameter.receiver, violations);
|
|
3346
|
+
checkReturns(where, member.signature, parsed.returns, this.renderer, violations);
|
|
3347
|
+
}
|
|
3348
|
+
entries.push({
|
|
3349
|
+
key: service.key,
|
|
3350
|
+
type: declaration.name,
|
|
3351
|
+
abstract: declaration.abstract,
|
|
3352
|
+
doc,
|
|
3353
|
+
methods,
|
|
3354
|
+
source
|
|
3355
|
+
});
|
|
3356
|
+
}
|
|
3357
|
+
reportViolations("gen-cordis-catalog", violations);
|
|
3358
|
+
reportTypeLinkViolations("gen-cordis-catalog", typeLinkViolations);
|
|
3359
|
+
return entries.sort((left, right) => left.key.localeCompare(right.key));
|
|
3360
|
+
}
|
|
3361
|
+
runtimeTypes(services) {
|
|
3362
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
3363
|
+
const ambiguous = /* @__PURE__ */ new Set();
|
|
3364
|
+
for (const declaration of this.sourceDeclarations) {
|
|
3365
|
+
if (declaration.face !== "host" || declaration.kind === "enum" || !/^packages\/[^/]+\/[^/]+\/src\/[^/]+\.ts$/.test(declaration.location.file)) continue;
|
|
3366
|
+
if (declarations.has(declaration.name)) {
|
|
3367
|
+
ambiguous.add(declaration.name);
|
|
3368
|
+
continue;
|
|
3369
|
+
}
|
|
3370
|
+
declarations.set(declaration.name, declaration.text.length > MAX_DECL_CHARS ? `${declaration.text.slice(0, MAX_DECL_CHARS)} /* …truncated — full shape in source */` : declaration.text);
|
|
3371
|
+
}
|
|
3372
|
+
for (const name of ambiguous) declarations.delete(name);
|
|
3373
|
+
return referencedTypes(services.flatMap((service) => service.methods.map((method) => method.signature)), declarations);
|
|
3374
|
+
}
|
|
3375
|
+
};
|
|
3376
|
+
/**
|
|
3377
|
+
* Analyze the host project once and return both the model and its projection.
|
|
3378
|
+
* @param scanRoot - workspace root containing `tsconfig.host.json`.
|
|
3379
|
+
* @param policy - caller-owned type classifications and inherited Cordis data.
|
|
3380
|
+
* @returns the configured projector and its validated catalog model.
|
|
3381
|
+
*/
|
|
3382
|
+
function projectCordisCatalog(scanRoot, policy) {
|
|
3383
|
+
const caches = new WorkspaceCaches();
|
|
3384
|
+
const face = new WorkspaceAnalyzer({
|
|
3385
|
+
root: scanRoot,
|
|
3386
|
+
faces: ["host"],
|
|
3387
|
+
packages: new WorkspaceAnalyzer({
|
|
3388
|
+
root: scanRoot,
|
|
3389
|
+
faces: ["host"],
|
|
3390
|
+
checkDiagnostics: false,
|
|
3391
|
+
caches
|
|
3392
|
+
}).discoverPackages().filter((candidate) => candidate.faces.includes("host")).map((candidate) => candidate.package),
|
|
3393
|
+
checkDiagnostics: false,
|
|
3394
|
+
caches
|
|
3395
|
+
}).analyzeInBatches().faces.find((candidate) => candidate.face === "host");
|
|
3396
|
+
if (face === void 0) throw new Error("gen-cordis-catalog: Typert produced no host face");
|
|
3397
|
+
const projector = new CordisCatalogProjector(face, new WorkspaceAnalyzer({
|
|
3398
|
+
root: scanRoot,
|
|
3399
|
+
faces: ["host"],
|
|
3400
|
+
checkDiagnostics: false,
|
|
3401
|
+
caches
|
|
3402
|
+
}).indexSourceDeclarations(), policy);
|
|
3403
|
+
return {
|
|
3404
|
+
projector,
|
|
3405
|
+
model: projector.project()
|
|
3406
|
+
};
|
|
3407
|
+
}
|
|
3408
|
+
/**
|
|
3409
|
+
* Collect all modeled events for relationship-document consumers.
|
|
3410
|
+
* @param scanRoot - workspace root containing `tsconfig.host.json`.
|
|
3411
|
+
* @param policy - caller-owned Cordis catalog policy.
|
|
3412
|
+
* @returns all validated event entries.
|
|
3413
|
+
*/
|
|
3414
|
+
function collectEvents(scanRoot, policy) {
|
|
3415
|
+
return [...projectCordisCatalog(scanRoot, policy).model.events];
|
|
3416
|
+
}
|
|
3417
|
+
/**
|
|
3418
|
+
* Collect all modeled services for relationship-document consumers.
|
|
3419
|
+
* @param scanRoot - workspace root containing `tsconfig.host.json`.
|
|
3420
|
+
* @param policy - caller-owned Cordis catalog policy.
|
|
3421
|
+
* @returns all validated service entries.
|
|
3422
|
+
*/
|
|
3423
|
+
function collectServices(scanRoot, policy) {
|
|
3424
|
+
return [...projectCordisCatalog(scanRoot, policy).model.services];
|
|
3425
|
+
}
|
|
3426
|
+
function parseJsDoc(raw) {
|
|
3427
|
+
const lines = raw.replace(/^\/\*\*/, "").replace(/\*\/$/, "").split("\n").map((line) => line.replace(/^\s*\*?\s?/, "").replace(/\s+$/, ""));
|
|
3428
|
+
const blocks = [];
|
|
3429
|
+
let paragraph = [];
|
|
3430
|
+
let list = [];
|
|
3431
|
+
let item = [];
|
|
3432
|
+
let inTags = false;
|
|
3433
|
+
const join = (parts) => parts.join(" ").replace(/\s+/g, " ").trim();
|
|
3434
|
+
const flushItem = () => {
|
|
3435
|
+
if (item.length > 0) list.push(join(item));
|
|
3436
|
+
item = [];
|
|
3437
|
+
};
|
|
3438
|
+
const flushList = () => {
|
|
3439
|
+
flushItem();
|
|
3440
|
+
if (list.length > 0) blocks.push(list.join("\n"));
|
|
3441
|
+
list = [];
|
|
3442
|
+
};
|
|
3443
|
+
const flushParagraph = () => {
|
|
3444
|
+
flushList();
|
|
3445
|
+
if (paragraph.length > 0) blocks.push(join(paragraph));
|
|
3446
|
+
paragraph = [];
|
|
3447
|
+
};
|
|
3448
|
+
for (const line of lines) {
|
|
3449
|
+
if (line.trimStart().startsWith("@")) {
|
|
3450
|
+
flushParagraph();
|
|
3451
|
+
inTags = true;
|
|
3452
|
+
continue;
|
|
3453
|
+
}
|
|
3454
|
+
if (inTags) continue;
|
|
3455
|
+
if (line.trim() === "") {
|
|
3456
|
+
flushParagraph();
|
|
3457
|
+
continue;
|
|
3458
|
+
}
|
|
3459
|
+
if (/^-\s+/.test(line)) {
|
|
3460
|
+
flushItem();
|
|
3461
|
+
if (paragraph.length > 0) {
|
|
3462
|
+
blocks.push(join(paragraph));
|
|
3463
|
+
paragraph = [];
|
|
3464
|
+
}
|
|
3465
|
+
item.push(line);
|
|
3466
|
+
continue;
|
|
3467
|
+
}
|
|
3468
|
+
if (item.length > 0) item.push(line);
|
|
3469
|
+
else paragraph.push(line);
|
|
3470
|
+
}
|
|
3471
|
+
flushParagraph();
|
|
3472
|
+
const params = /* @__PURE__ */ new Map();
|
|
3473
|
+
let returns = null;
|
|
3474
|
+
let sink;
|
|
3475
|
+
for (const line of lines) {
|
|
3476
|
+
const param = /^@param\s+(\[?[\w$]+\]?)\s*(?:[-—–]\s*)?(.*)$/.exec(line);
|
|
3477
|
+
if (param !== null) {
|
|
3478
|
+
const name = (param[1] ?? "").replace(/^\[|\]$/g, "");
|
|
3479
|
+
let value = param[2] ?? "";
|
|
3480
|
+
params.set(name, value);
|
|
3481
|
+
sink = (text) => {
|
|
3482
|
+
value = value === "" ? text : `${value} ${text}`;
|
|
3483
|
+
params.set(name, value);
|
|
3484
|
+
};
|
|
3485
|
+
continue;
|
|
3486
|
+
}
|
|
3487
|
+
const returnsTag = /^@returns?(?:\s+[-—–]?\s*(.*))?$/.exec(line);
|
|
3488
|
+
if (returnsTag !== null) {
|
|
3489
|
+
let value = returnsTag[1] ?? "";
|
|
3490
|
+
returns = value;
|
|
3491
|
+
sink = (text) => {
|
|
3492
|
+
value = value === "" ? text : `${value} ${text}`;
|
|
3493
|
+
returns = value;
|
|
3494
|
+
};
|
|
3495
|
+
continue;
|
|
3496
|
+
}
|
|
3497
|
+
if (line.startsWith("@") || line.trim() === "") sink = void 0;
|
|
3498
|
+
else sink?.(line.trim());
|
|
3499
|
+
}
|
|
3500
|
+
return {
|
|
3501
|
+
doc: blocks.join("\n\n").replace(/\{@link\s+([^}]+)\}/g, "$1").trim(),
|
|
3502
|
+
params,
|
|
3503
|
+
returns
|
|
3504
|
+
};
|
|
3505
|
+
}
|
|
3506
|
+
function checkParams(where, surface, parameters, tags, isExempt, violations) {
|
|
3507
|
+
for (const parameter of parameters) {
|
|
3508
|
+
if (parameter.binding !== "identifier") {
|
|
3509
|
+
violations.push(`${where}: parameter '${parameter.name}' is a binding pattern; the ${surface} surface needs simple identifier parameters so @param can name them.`);
|
|
3510
|
+
continue;
|
|
3511
|
+
}
|
|
3512
|
+
if (isExempt(parameter)) continue;
|
|
3513
|
+
const description = tags.get(parameter.name);
|
|
3514
|
+
if (description === void 0) violations.push(`${where} is missing @param ${parameter.name}.`);
|
|
3515
|
+
else if (description.trim() === "") violations.push(`${where}: @param ${parameter.name} has an empty description.`);
|
|
3516
|
+
}
|
|
3517
|
+
for (const tag of tags.keys()) if (!parameters.some((parameter) => parameter.binding === "identifier" && parameter.name === tag)) violations.push(`${where}: @param ${tag} does not match any parameter (stale tag?).`);
|
|
3518
|
+
}
|
|
3519
|
+
function checkReturns(where, signature, returns, renderer, violations) {
|
|
3520
|
+
const type = renderer.renderType(signature.returns);
|
|
3521
|
+
if (type === "void" || type === "Promise<void>") return;
|
|
3522
|
+
if (returns === null) violations.push(`${where} is missing @returns (return type: ${type}).`);
|
|
3523
|
+
else if (returns.trim() === "") violations.push(`${where}: @returns has an empty description.`);
|
|
3524
|
+
}
|
|
3525
|
+
function reportViolations(gate, violations) {
|
|
3526
|
+
if (violations.length === 0) return;
|
|
3527
|
+
throw new Error(`${gate}: ${String(violations.length)} JSDoc completeness violation(s) (see AGENTS.md):\n` + violations.map((violation) => ` ${violation}`).join("\n"));
|
|
3528
|
+
}
|
|
3529
|
+
function pointer(location) {
|
|
3530
|
+
return `${location.file}:${String(location.line)}`;
|
|
3531
|
+
}
|
|
3532
|
+
function isMode(mode) {
|
|
3533
|
+
return mode === "emit" || mode === "waterfall" || mode === "parallel" || mode === "serial";
|
|
3534
|
+
}
|
|
3535
|
+
function signatureTypeNames(renderer, signature) {
|
|
3536
|
+
const names = /* @__PURE__ */ new Set();
|
|
3537
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3538
|
+
const visitSignature = (current) => {
|
|
3539
|
+
for (const parameter of current.typeParameters) {
|
|
3540
|
+
if (parameter.constraint !== void 0) visit(parameter.constraint);
|
|
3541
|
+
if (parameter.default !== void 0) visit(parameter.default);
|
|
3542
|
+
}
|
|
3543
|
+
for (const parameter of current.parameters) visit(parameter.type);
|
|
3544
|
+
visit(current.returns);
|
|
3545
|
+
};
|
|
3546
|
+
const visitMember = (member) => {
|
|
3547
|
+
if (member.kind === "property") visit(member.type);
|
|
3548
|
+
else visitSignature(member.signature);
|
|
3549
|
+
};
|
|
3550
|
+
const visit = (id) => {
|
|
3551
|
+
if (visited.has(id)) return;
|
|
3552
|
+
visited.add(id);
|
|
3553
|
+
const node = renderer.node(id);
|
|
3554
|
+
if (node.kind === "reference" && node.target.kind !== "type-parameter") names.add(node.name);
|
|
3555
|
+
if (node.kind === "type-query") names.add(node.expression);
|
|
3556
|
+
for (const child of childTypeNodeIds(node)) visit(child);
|
|
3557
|
+
if (node.kind === "object") for (const member of node.members) visitMember(member);
|
|
3558
|
+
if (node.kind === "function" || node.kind === "constructor") visitSignature(node.signature);
|
|
3559
|
+
};
|
|
3560
|
+
visitSignature(signature);
|
|
3561
|
+
return [...names].sort();
|
|
3562
|
+
}
|
|
3563
|
+
/** Declarations longer than this render as a truncated stub. */
|
|
3564
|
+
const MAX_DECL_CHARS = 1500;
|
|
3565
|
+
/** Render one value as a single-quoted TypeScript literal. */
|
|
3566
|
+
function quote(value) {
|
|
3567
|
+
return `'${value.replaceAll("\\", "\\\\").replaceAll("'", "\\'").replaceAll("\n", "\\n")}'`;
|
|
3568
|
+
}
|
|
3569
|
+
/** Resolve and sort the word-bounded transitive type closure referenced by seed text. */
|
|
3570
|
+
function referencedTypes(seeds, declarations) {
|
|
3571
|
+
const included = /* @__PURE__ */ new Map();
|
|
3572
|
+
let frontier = [...seeds];
|
|
3573
|
+
while (frontier.length > 0) {
|
|
3574
|
+
const next = [];
|
|
3575
|
+
for (const [name, declaration] of declarations) {
|
|
3576
|
+
if (included.has(name)) continue;
|
|
3577
|
+
const pattern = new RegExp(`\\b${name}\\b`);
|
|
3578
|
+
if (frontier.some((text) => pattern.test(text))) {
|
|
3579
|
+
included.set(name, declaration);
|
|
3580
|
+
next.push(declaration);
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3583
|
+
frontier = next;
|
|
3584
|
+
}
|
|
3585
|
+
return [...included].map(([name, declaration]) => ({
|
|
3586
|
+
name,
|
|
3587
|
+
declaration
|
|
3588
|
+
})).sort((left, right) => left.name.localeCompare(right.name));
|
|
3589
|
+
}
|
|
3590
|
+
function firstSentence(doc) {
|
|
3591
|
+
const line = doc.split("\n", 1)[0] ?? "";
|
|
3592
|
+
return (/^(.*?[.!?])(?:\s|$)/.exec(line)?.[1] ?? line).trim();
|
|
3593
|
+
}
|
|
3594
|
+
/** Render the byte-compatible model-facing API catalog. */
|
|
3595
|
+
function renderRuntimeApi(services, events, types, inheritedServices) {
|
|
3596
|
+
const lines = [
|
|
3597
|
+
"/**",
|
|
3598
|
+
" * Generated by scripts/gen-cordis-api.ts — do not edit by hand; run",
|
|
3599
|
+
" * `pnpm run gen-cordis-api` to regenerate (freshness-gated by",
|
|
3600
|
+
" * `pnpm run verify-cordis-api` in doc-sync).",
|
|
3601
|
+
" *",
|
|
3602
|
+
" * The machine-readable cordis API catalog `cordis_inspect` serves to the",
|
|
3603
|
+
" * model: harness services (summary + public method signatures/JSDoc),",
|
|
3604
|
+
" * harness events (mode + signature/JSDoc), and the inherited `ctx` surface. Produced by",
|
|
3605
|
+
" * the same AST walk as docs/cordis-catalog, so this data and the rendered",
|
|
3606
|
+
" * docs cannot diverge.",
|
|
3607
|
+
" *",
|
|
3608
|
+
" * @module @deepseek-ai/dsh-tool-cordis/api-catalog",
|
|
3609
|
+
" */",
|
|
3610
|
+
"",
|
|
3611
|
+
"/** One public service method and its source-owned contract. */",
|
|
3612
|
+
"export interface ServiceApiMethod {",
|
|
3613
|
+
" /** Public method signature with its body stripped. */",
|
|
3614
|
+
" signature: string",
|
|
3615
|
+
" /** Original method JSDoc, with only container indentation removed. */",
|
|
3616
|
+
" jsDoc: string",
|
|
3617
|
+
"}",
|
|
3618
|
+
"",
|
|
3619
|
+
"/** One harness `ctx.<key>` service: its one-line summary and public methods. */",
|
|
3620
|
+
"export interface ServiceApiEntry {",
|
|
3621
|
+
" /** The `ctx.<key>` name, e.g. `tools`. */",
|
|
3622
|
+
" key: string",
|
|
3623
|
+
" /** First sentence of the service class JSDoc. */",
|
|
3624
|
+
" summary: string",
|
|
3625
|
+
" /** Public methods, bodies stripped, in source order. */",
|
|
3626
|
+
" methods: readonly ServiceApiMethod[]",
|
|
3627
|
+
"}",
|
|
3628
|
+
"",
|
|
3629
|
+
"/** One harness event: its dispatch mode, exact signature, and one-line summary. */",
|
|
3630
|
+
"export interface EventApiEntry {",
|
|
3631
|
+
" /** The scoped event name, e.g. `agent/status`. */",
|
|
3632
|
+
" name: string",
|
|
3633
|
+
" /** The dispatch mode from the declaration's `@mode` tag. */",
|
|
3634
|
+
" mode: string",
|
|
3635
|
+
" /** The exact listener signature, whitespace-normalized. */",
|
|
3636
|
+
" signature: string",
|
|
3637
|
+
" /** Original event JSDoc, with only container indentation removed. */",
|
|
3638
|
+
" jsDoc: string",
|
|
3639
|
+
" /** First sentence of the event JSDoc. */",
|
|
3640
|
+
" summary: string",
|
|
3641
|
+
"}",
|
|
3642
|
+
"",
|
|
3643
|
+
"/** One inherited (cordis core + loader/hmr/timer) `ctx` member group with its summary. */",
|
|
3644
|
+
"export interface InheritedApiEntry {",
|
|
3645
|
+
" /** The `ctx` member name(s), e.g. `ctx.on / ctx.once`. */",
|
|
3646
|
+
" name: string",
|
|
3647
|
+
" /** One-line summary of what the member does. */",
|
|
3648
|
+
" summary: string",
|
|
3649
|
+
"}",
|
|
3650
|
+
"",
|
|
3651
|
+
"/** One named type shape the service signatures reference. */",
|
|
3652
|
+
"export interface TypeApiEntry {",
|
|
3653
|
+
" /** The exported type/interface name, e.g. `BashRunResult`. */",
|
|
3654
|
+
" name: string",
|
|
3655
|
+
" /** The full declaration text, comments stripped. */",
|
|
3656
|
+
" declaration: string",
|
|
3657
|
+
"}",
|
|
3658
|
+
"",
|
|
3659
|
+
"/** Every harness `ctx.<key>` service, sorted by key. */",
|
|
3660
|
+
"export const SERVICE_API: readonly ServiceApiEntry[] = ["
|
|
3661
|
+
];
|
|
3662
|
+
for (const service of services) {
|
|
3663
|
+
lines.push(" {");
|
|
3664
|
+
lines.push(` key: ${quote(service.key)},`);
|
|
3665
|
+
lines.push(` summary: ${quote(firstSentence(service.doc))},`);
|
|
3666
|
+
if (service.methods.length === 0) lines.push(" methods: [],");
|
|
3667
|
+
else {
|
|
3668
|
+
lines.push(" methods: [");
|
|
3669
|
+
for (const method of service.methods) {
|
|
3670
|
+
lines.push(" {");
|
|
3671
|
+
lines.push(` signature: ${quote(method.signature)},`);
|
|
3672
|
+
lines.push(` jsDoc: ${quote(method.jsDoc)},`);
|
|
3673
|
+
lines.push(" },");
|
|
3674
|
+
}
|
|
3675
|
+
lines.push(" ],");
|
|
3676
|
+
}
|
|
3677
|
+
lines.push(" },");
|
|
3678
|
+
}
|
|
3679
|
+
lines.push("]", "", "/** Every harness event, sorted by name. */", "export const EVENT_API: readonly EventApiEntry[] = [");
|
|
3680
|
+
for (const event of [...events].sort((left, right) => left.name.localeCompare(right.name))) {
|
|
3681
|
+
lines.push(" {");
|
|
3682
|
+
lines.push(` name: ${quote(event.name)},`);
|
|
3683
|
+
lines.push(` mode: ${quote(event.mode)},`);
|
|
3684
|
+
lines.push(` signature: ${quote(event.signature)},`);
|
|
3685
|
+
lines.push(` jsDoc: ${quote(event.jsDoc)},`);
|
|
3686
|
+
lines.push(` summary: ${quote(firstSentence(event.doc))},`);
|
|
3687
|
+
lines.push(" },");
|
|
3688
|
+
}
|
|
3689
|
+
lines.push("]", "", "/** Shapes of every exported type the SERVICE_API signatures reference (transitively), sorted by name. */", "export const TYPE_API: readonly TypeApiEntry[] = [");
|
|
3690
|
+
for (const type of types) {
|
|
3691
|
+
lines.push(" {");
|
|
3692
|
+
lines.push(` name: ${quote(type.name)},`);
|
|
3693
|
+
lines.push(` declaration: ${quote(type.declaration)},`);
|
|
3694
|
+
lines.push(" },");
|
|
3695
|
+
}
|
|
3696
|
+
lines.push("]", "", "/** The inherited `ctx` surface (cordis core + loader/hmr/timer), in curated order. */", "export const INHERITED_CTX_API: readonly InheritedApiEntry[] = [");
|
|
3697
|
+
for (const inherited of inheritedServices) lines.push(` { name: ${quote(inherited.name)}, summary: ${quote(inherited.summary)} },`);
|
|
3698
|
+
lines.push("]", "");
|
|
3699
|
+
return lines.join("\n");
|
|
3700
|
+
}
|
|
3701
|
+
/** Opening region delimiter; injected content lives between the pair and the page owns everything outside. */
|
|
3702
|
+
const REGION_BEGIN = "<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->";
|
|
3703
|
+
/** Closing region delimiter matching {@link REGION_BEGIN}. */
|
|
3704
|
+
const REGION_END = "<!-- END GENERATED cordis-surface -->";
|
|
3705
|
+
/**
|
|
3706
|
+
* Render the cross-link "Types:" line for a signature relative to one
|
|
3707
|
+
* subsystems page, or '' if none apply. A type whose primary page IS the
|
|
3708
|
+
* rendering page would link as a fragmentless self-link readers already sit
|
|
3709
|
+
* on, so it is dropped instead.
|
|
3710
|
+
*/
|
|
3711
|
+
function typeLinks(signature, onPage, linkedTypePages) {
|
|
3712
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3713
|
+
for (const name of Object.keys(linkedTypePages)) if (new RegExp(`\\b${name}\\b`).test(signature)) seen.add(name);
|
|
3714
|
+
const links = [...seen].sort().filter((name) => linkedTypePages[name] !== onPage).map((name) => `[${name}](${linkedTypePages[name]})`);
|
|
3715
|
+
if (links.length === 0) return "";
|
|
3716
|
+
return `Types: ${links.join(" · ")}`;
|
|
3717
|
+
}
|
|
3718
|
+
/**
|
|
3719
|
+
* GitHub's heading-slug algorithm (lowercase; drop everything but letters,
|
|
3720
|
+
* numbers, spaces, hyphens; spaces become hyphens). Region headings carry
|
|
3721
|
+
* backticks and em-dashes, which VitePress slugifies differently, so each
|
|
3722
|
+
* generated heading is preceded by an explicit `<a id>` carrying this slug —
|
|
3723
|
+
* the historical flat-catalog anchor — making `#ctx<key>--<class>` fragments
|
|
3724
|
+
* resolve identically on GitHub and the published site.
|
|
3725
|
+
*/
|
|
3726
|
+
function githubSlug(heading) {
|
|
3727
|
+
return heading.toLowerCase().replace(/[^\p{L}\p{N} -]/gu, "").replaceAll(" ", "-");
|
|
3728
|
+
}
|
|
3729
|
+
/** The explicit-anchor line emitted before one generated heading. */
|
|
3730
|
+
function anchorFor(headingText) {
|
|
3731
|
+
return [`<a id="${githubSlug(headingText)}"></a>`, ""];
|
|
3732
|
+
}
|
|
3733
|
+
/** Render one harness event entry onto its owning page, nested under its scope heading. */
|
|
3734
|
+
function renderEvent(e, onPage, linkedTypePages) {
|
|
3735
|
+
const out = [
|
|
3736
|
+
...anchorFor(`${e.name} — ${e.mode}`),
|
|
3737
|
+
`#### \`${e.name}\` — ${e.mode}`,
|
|
3738
|
+
""
|
|
3739
|
+
];
|
|
3740
|
+
if (e.doc) out.push(e.doc, "");
|
|
3741
|
+
out.push("```ts cordis-catalog", e.jsDoc, e.signature, "```", "");
|
|
3742
|
+
const links = typeLinks(e.signature, onPage, linkedTypePages);
|
|
3743
|
+
if (links) out.push(links, "");
|
|
3744
|
+
out.push(`Source: [\`${e.source}\`](../../${e.source.split(":")[0]})`, "");
|
|
3745
|
+
return out;
|
|
3746
|
+
}
|
|
3747
|
+
/** Render one harness service entry onto its owning page. */
|
|
3748
|
+
function renderService(s, onPage, linkedTypePages) {
|
|
3749
|
+
const kind = s.abstract ? " (abstract seam)" : "";
|
|
3750
|
+
const out = [
|
|
3751
|
+
...anchorFor(`ctx.${s.key} — ${s.type}${kind}`),
|
|
3752
|
+
`### \`ctx.${s.key}\` — \`${s.type}\`${kind}`,
|
|
3753
|
+
""
|
|
3754
|
+
];
|
|
3755
|
+
if (s.doc) out.push(s.doc, "");
|
|
3756
|
+
if (s.methods.length) {
|
|
3757
|
+
const declarations = s.methods.flatMap((method, index) => [
|
|
3758
|
+
...index > 0 ? [""] : [],
|
|
3759
|
+
method.jsDoc,
|
|
3760
|
+
method.signature
|
|
3761
|
+
]);
|
|
3762
|
+
out.push("```ts cordis-catalog", ...declarations, "```", "");
|
|
3763
|
+
const links = typeLinks(s.methods.map((method) => method.signature).join("\n"), onPage, linkedTypePages);
|
|
3764
|
+
if (links) out.push(links, "");
|
|
3765
|
+
}
|
|
3766
|
+
out.push(`Source: [\`${s.source}\`](../../${s.source.split(":")[0]})`, "");
|
|
3767
|
+
return out;
|
|
3768
|
+
}
|
|
3769
|
+
/** The shared generated-file banner comment. */
|
|
3770
|
+
const BANNER = [
|
|
3771
|
+
"<!-- Generated by scripts/gen-cordis-catalog.ts — do not edit by hand.",
|
|
3772
|
+
" Run `pnpm run gen-cordis-catalog` to regenerate. -->",
|
|
3773
|
+
""
|
|
3774
|
+
];
|
|
3775
|
+
/** The shared GENERATED + freshness-gate + fence notice paragraph. */
|
|
3776
|
+
const GATE_NOTICE = "This file is GENERATED from source (`scripts/gen-cordis-catalog.ts`) and verified fresh by `pnpm run verify-cordis-catalog` (part of `doc-sync`) — do not edit it by hand. Signature blocks use a `ts cordis-catalog` fence and include the original source JSDoc immediately before each event or service method. doc-typecheck skips these bare declaration fragments; type names in a signature link to the page that documents them.";
|
|
3777
|
+
/**
|
|
3778
|
+
* Render one page's generated `cordis-surface` region: the services mapped to
|
|
3779
|
+
* the page, then the event scopes mapped to it, markers included. Pure and
|
|
3780
|
+
* deterministic given sorted inputs; identical bytes land in both pair sides.
|
|
3781
|
+
* @param page - the owning `docs/subsystems/` page basename, e.g. `core.md`.
|
|
3782
|
+
* @param services - validated services mapped to this page.
|
|
3783
|
+
* @param events - validated events whose scopes map to this page.
|
|
3784
|
+
* @param policy - type links supplied by the caller.
|
|
3785
|
+
* @returns the complete marker-delimited region text.
|
|
3786
|
+
*/
|
|
3787
|
+
function renderPageRegion(page, services, events, policy) {
|
|
3788
|
+
const lines = [
|
|
3789
|
+
REGION_BEGIN,
|
|
3790
|
+
"",
|
|
3791
|
+
"<a id=\"cordis-surface\"></a>",
|
|
3792
|
+
"",
|
|
3793
|
+
"## Cordis surface",
|
|
3794
|
+
"",
|
|
3795
|
+
"Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md).",
|
|
3796
|
+
""
|
|
3797
|
+
];
|
|
3798
|
+
for (const s of services) lines.push(...renderService(s, page, policy.linkedTypePages));
|
|
3799
|
+
const scopes = [...new Set(events.map((e) => e.scope))].sort();
|
|
3800
|
+
for (const scope of scopes) {
|
|
3801
|
+
lines.push(...anchorFor(`${scope}/* events`), `### \`${scope}/*\` events`, "");
|
|
3802
|
+
for (const e of events.filter((x) => x.scope === scope).sort((a, b) => a.name.localeCompare(b.name))) lines.push(...renderEvent(e, page, policy.linkedTypePages));
|
|
3803
|
+
}
|
|
3804
|
+
while (lines.at(-1) === "") lines.pop();
|
|
3805
|
+
lines.push(REGION_END);
|
|
3806
|
+
return lines.join("\n");
|
|
3807
|
+
}
|
|
3808
|
+
/**
|
|
3809
|
+
* Render the inherited (pinned vendor) tier as its own generated page.
|
|
3810
|
+
* @param policy - inherited events and services supplied by the caller.
|
|
3811
|
+
* @returns the complete generated Markdown document.
|
|
3812
|
+
*/
|
|
3813
|
+
function renderInheritedPage(policy) {
|
|
3814
|
+
const lines = [
|
|
3815
|
+
...BANNER,
|
|
3816
|
+
"# Inherited Cordis Surface",
|
|
3817
|
+
"",
|
|
3818
|
+
"The framework `ctx` members and events every plugin sees beyond the harness tier — pinned vendor source ([vendoring policy](../../vendor/README.md)), summarized tersely so the harness pages stay focused on repository-owned vocabulary. Detailed Context, Fiber, Registry, and Service APIs are generated in [context.md](context.md), [fiber.md](fiber.md), [registry.md](registry.md), and [service.md](service.md); the event-dispatch methods in [events.md](events.md).",
|
|
3819
|
+
"",
|
|
3820
|
+
GATE_NOTICE,
|
|
3821
|
+
"",
|
|
3822
|
+
"## Inherited `ctx` members (cordis core + loader/hmr/timer)",
|
|
3823
|
+
""
|
|
3824
|
+
];
|
|
3825
|
+
for (const s of policy.inheritedServices) lines.push(`- \`${s.name}\` — ${s.summary} ([\`${s.source}\`](../../${s.source.split(":")[0]}))`);
|
|
3826
|
+
lines.push("", "## Inherited events (cordis core + loader/hmr/timer)", "");
|
|
3827
|
+
for (const e of policy.inheritedEvents) lines.push(`- \`${e.name}\` — ${e.summary} ([\`${e.source}\`](../../${e.source.split(":")[0]}))`);
|
|
3828
|
+
lines.push("");
|
|
3829
|
+
return lines.join("\n");
|
|
3830
|
+
}
|
|
3831
|
+
//#endregion
|
|
3832
|
+
//#region lib/types/workspace.js
|
|
3833
|
+
/**
|
|
3834
|
+
* Workspace-level discovery and model-driven Typert generation.
|
|
3835
|
+
* @module @deepseek-ai/dsh-typert-generator/workspace
|
|
3836
|
+
*/
|
|
3837
|
+
/** Discover, analyze, and emit package reflection from independent faces. */
|
|
3838
|
+
var WorkspaceTypertGenerator = class {
|
|
3839
|
+
root;
|
|
3840
|
+
/**
|
|
3841
|
+
* Bind generation to one workspace root.
|
|
3842
|
+
* @param root - directory containing face aggregate tsconfigs.
|
|
3843
|
+
*/
|
|
3844
|
+
constructor(root) {
|
|
3845
|
+
this.root = root;
|
|
3846
|
+
}
|
|
3847
|
+
/**
|
|
3848
|
+
* Find public package faces that contribute Cordis services/events or
|
|
3849
|
+
* explicitly tagged Typert roots.
|
|
3850
|
+
* @param faces - optional independent program faces to inspect.
|
|
3851
|
+
* @returns discovered packages in stable package-name order.
|
|
3852
|
+
*/
|
|
3853
|
+
discover(faces) {
|
|
3854
|
+
return new WorkspaceAnalyzer({
|
|
3855
|
+
root: this.root,
|
|
3856
|
+
...faces === void 0 ? {} : { faces }
|
|
3857
|
+
}).discoverPackages();
|
|
3858
|
+
}
|
|
3859
|
+
/**
|
|
3860
|
+
* Generate all discovered contributors, or an explicit package subset.
|
|
3861
|
+
* @param packages - optional exact package names for a focused pass.
|
|
3862
|
+
* @param faces - optional independent program faces to analyze.
|
|
3863
|
+
* @returns one artifact per package face.
|
|
3864
|
+
*/
|
|
3865
|
+
generate(packages, faces) {
|
|
3866
|
+
const selected = packages ?? this.discover(faces).map((candidate) => candidate.package);
|
|
3867
|
+
const workspace = new WorkspaceAnalyzer({
|
|
3868
|
+
root: this.root,
|
|
3869
|
+
packages: selected,
|
|
3870
|
+
...faces === void 0 ? {} : { faces }
|
|
3871
|
+
}).analyze();
|
|
3872
|
+
const artifacts = [];
|
|
3873
|
+
for (const face of workspace.faces) {
|
|
3874
|
+
const emitter = new FaceModelEmitter(face);
|
|
3875
|
+
for (const packageModel of face.packages) {
|
|
3876
|
+
const artifact = {
|
|
3877
|
+
...emitter.emit(packageModel.name),
|
|
3878
|
+
packageRoot: packageModel.root
|
|
3879
|
+
};
|
|
3880
|
+
this.validateExport(artifact);
|
|
3881
|
+
artifacts.push(artifact);
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3884
|
+
return artifacts;
|
|
3885
|
+
}
|
|
3886
|
+
validateExport(artifact) {
|
|
3887
|
+
const manifestPath = resolve(this.root, artifact.packageRoot, "package.json");
|
|
3888
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
3889
|
+
const subpath = artifact.face === "host" ? "./typert" : "./client/typert";
|
|
3890
|
+
const expected = {
|
|
3891
|
+
types: `./lib/typert.${artifact.face}.d.ts`,
|
|
3892
|
+
default: `./lib/typert.${artifact.face}.js`
|
|
3893
|
+
};
|
|
3894
|
+
if (!sameExport(manifest.exports !== null && typeof manifest.exports === "object" ? manifest.exports[subpath] : void 0, expected)) throw new TypertAnalysisError(`typert(${artifact.face}): ${artifact.package} must export ${subpath} as ${JSON.stringify(expected)}`);
|
|
3895
|
+
const files = Array.isArray(manifest.files) ? manifest.files : [];
|
|
3896
|
+
for (const file of [`lib/typert.${artifact.face}.js`, `lib/typert.${artifact.face}.d.ts`]) if (!files.includes(file)) throw new TypertAnalysisError(`typert(${artifact.face}): ${artifact.package} package files must include ${file}`);
|
|
3897
|
+
if (artifact.face !== "host") return;
|
|
3898
|
+
const remoteExpected = {
|
|
3899
|
+
types: "./lib/typert.remote-client.d.ts",
|
|
3900
|
+
default: "./lib/typert.remote-client.js"
|
|
3901
|
+
};
|
|
3902
|
+
const remoteActual = manifest.exports !== null && typeof manifest.exports === "object" ? manifest.exports["./remote"] : void 0;
|
|
3903
|
+
const remoteFiles = [
|
|
3904
|
+
"lib/typert.remote-client.js",
|
|
3905
|
+
"lib/typert.remote-client.d.ts",
|
|
3906
|
+
"lib/typert.remote-client.d.ts.map"
|
|
3907
|
+
];
|
|
3908
|
+
if (artifact.remote === void 0) {
|
|
3909
|
+
if (remoteActual !== void 0 || remoteFiles.some((file) => files.includes(file))) throw new TypertAnalysisError(`typert(host): ${artifact.package} publishes Remote artifacts but has no Remote methods`);
|
|
3910
|
+
return;
|
|
3911
|
+
}
|
|
3912
|
+
if (!sameExport(remoteActual, remoteExpected)) throw new TypertAnalysisError(`typert(host): ${artifact.package} must export ./remote as ${JSON.stringify(remoteExpected)}`);
|
|
3913
|
+
for (const file of remoteFiles) if (!files.includes(file)) throw new TypertAnalysisError(`typert(host): ${artifact.package} package files must include ${file}`);
|
|
3914
|
+
}
|
|
3915
|
+
};
|
|
3916
|
+
function sameExport(actual, expected) {
|
|
3917
|
+
if (actual === null || typeof actual !== "object" || Array.isArray(actual)) return false;
|
|
3918
|
+
const value = actual;
|
|
3919
|
+
return value.types === expected.types && value.default === expected.default;
|
|
3920
|
+
}
|
|
3921
|
+
//#endregion
|
|
3922
|
+
export { CordisCatalogProjector, FaceModelEmitter, REGION_BEGIN, REGION_END, TypeGraphRenderError, TypeGraphRenderer, TypertAnalysisError, TypertEmitError, WorkspaceAnalyzer, WorkspaceCaches, WorkspaceTypertGenerator, collectEvents, collectServices, projectCordisCatalog, renderInheritedPage, renderPageRegion };
|