@typed/virtual-modules 1.0.0-beta.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.
Files changed (82) hide show
  1. package/README.md +135 -0
  2. package/dist/CompilerHostAdapter.d.ts +3 -0
  3. package/dist/CompilerHostAdapter.d.ts.map +1 -0
  4. package/dist/CompilerHostAdapter.js +160 -0
  5. package/dist/LanguageServiceAdapter.d.ts +3 -0
  6. package/dist/LanguageServiceAdapter.d.ts.map +1 -0
  7. package/dist/LanguageServiceAdapter.js +488 -0
  8. package/dist/LanguageServiceSession.d.ts +16 -0
  9. package/dist/LanguageServiceSession.d.ts.map +1 -0
  10. package/dist/LanguageServiceSession.js +122 -0
  11. package/dist/NodeModulePluginLoader.d.ts +8 -0
  12. package/dist/NodeModulePluginLoader.d.ts.map +1 -0
  13. package/dist/NodeModulePluginLoader.js +175 -0
  14. package/dist/PluginManager.d.ts +10 -0
  15. package/dist/PluginManager.d.ts.map +1 -0
  16. package/dist/PluginManager.js +151 -0
  17. package/dist/TypeInfoApi.d.ts +71 -0
  18. package/dist/TypeInfoApi.d.ts.map +1 -0
  19. package/dist/TypeInfoApi.js +855 -0
  20. package/dist/VmcConfigLoader.d.ts +31 -0
  21. package/dist/VmcConfigLoader.d.ts.map +1 -0
  22. package/dist/VmcConfigLoader.js +231 -0
  23. package/dist/VmcResolverLoader.d.ts +30 -0
  24. package/dist/VmcResolverLoader.d.ts.map +1 -0
  25. package/dist/VmcResolverLoader.js +71 -0
  26. package/dist/collectTypeTargetSpecs.d.ts +6 -0
  27. package/dist/collectTypeTargetSpecs.d.ts.map +1 -0
  28. package/dist/collectTypeTargetSpecs.js +19 -0
  29. package/dist/index.d.ts +13 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +12 -0
  32. package/dist/internal/VirtualRecordStore.d.ts +60 -0
  33. package/dist/internal/VirtualRecordStore.d.ts.map +1 -0
  34. package/dist/internal/VirtualRecordStore.js +199 -0
  35. package/dist/internal/materializeVirtualFile.d.ts +12 -0
  36. package/dist/internal/materializeVirtualFile.d.ts.map +1 -0
  37. package/dist/internal/materializeVirtualFile.js +28 -0
  38. package/dist/internal/path.d.ts +40 -0
  39. package/dist/internal/path.d.ts.map +1 -0
  40. package/dist/internal/path.js +71 -0
  41. package/dist/internal/sanitize.d.ts +6 -0
  42. package/dist/internal/sanitize.d.ts.map +1 -0
  43. package/dist/internal/sanitize.js +15 -0
  44. package/dist/internal/tsInternal.d.ts +65 -0
  45. package/dist/internal/tsInternal.d.ts.map +1 -0
  46. package/dist/internal/tsInternal.js +99 -0
  47. package/dist/internal/validation.d.ts +28 -0
  48. package/dist/internal/validation.d.ts.map +1 -0
  49. package/dist/internal/validation.js +66 -0
  50. package/dist/typeTargetBootstrap.d.ts +33 -0
  51. package/dist/typeTargetBootstrap.d.ts.map +1 -0
  52. package/dist/typeTargetBootstrap.js +57 -0
  53. package/dist/types.d.ts +405 -0
  54. package/dist/types.d.ts.map +1 -0
  55. package/dist/types.js +15 -0
  56. package/package.json +38 -0
  57. package/src/CompilerHostAdapter.test.ts +180 -0
  58. package/src/CompilerHostAdapter.ts +316 -0
  59. package/src/LanguageServiceAdapter.test.ts +521 -0
  60. package/src/LanguageServiceAdapter.ts +631 -0
  61. package/src/LanguageServiceSession.ts +160 -0
  62. package/src/LanguageServiceTester.integration.test.ts +268 -0
  63. package/src/NodeModulePluginLoader.test.ts +170 -0
  64. package/src/NodeModulePluginLoader.ts +268 -0
  65. package/src/PluginManager.test.ts +178 -0
  66. package/src/PluginManager.ts +218 -0
  67. package/src/TypeInfoApi.test.ts +1211 -0
  68. package/src/TypeInfoApi.ts +1228 -0
  69. package/src/VmcConfigLoader.test.ts +108 -0
  70. package/src/VmcConfigLoader.ts +297 -0
  71. package/src/VmcResolverLoader.test.ts +181 -0
  72. package/src/VmcResolverLoader.ts +116 -0
  73. package/src/collectTypeTargetSpecs.ts +22 -0
  74. package/src/index.ts +35 -0
  75. package/src/internal/VirtualRecordStore.ts +304 -0
  76. package/src/internal/materializeVirtualFile.ts +38 -0
  77. package/src/internal/path.ts +106 -0
  78. package/src/internal/sanitize.ts +16 -0
  79. package/src/internal/tsInternal.ts +127 -0
  80. package/src/internal/validation.ts +85 -0
  81. package/src/typeTargetBootstrap.ts +75 -0
  82. package/src/types.ts +535 -0
@@ -0,0 +1,855 @@
1
+ import { relative } from "node:path";
2
+ import { getAliasedSymbol as resolveAliasedSymbol, getBaseTypes as getBaseTypesInternal, getIndexInfosOfType, getSymbolExports, getTypeReferenceTarget, getTypeSymbol, FALLBACK_OBJECT_FLAGS_MAPPED, } from "./internal/tsInternal.js";
3
+ import { dedupeSorted, pathIsUnderBase, resolvePathUnderBase, resolveRelativePath, toPosixPath, } from "./internal/path.js";
4
+ import { validatePathSegment, validateRelativeGlobs } from "./internal/validation.js";
5
+ const TYPE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".d.ts"];
6
+ const DEFAULT_MAX_DEPTH = 6;
7
+ const compareByName = (a, b) => a.name.localeCompare(b.name);
8
+ const toOptionalFlag = (symbol, tsMod) => (symbol.flags & tsMod.SymbolFlags.Optional) !== 0;
9
+ const hasReadonlyModifier = (declaration, tsMod) => {
10
+ if (!declaration || !tsMod.canHaveModifiers(declaration)) {
11
+ return false;
12
+ }
13
+ const modifiers = tsMod.getModifiers(declaration);
14
+ return modifiers?.some((modifier) => modifier.kind === tsMod.SyntaxKind.ReadonlyKeyword) ?? false;
15
+ };
16
+ const asLiteral = (type, checker, tsMod) => {
17
+ const text = checker.typeToString(type);
18
+ if ((type.flags & tsMod.TypeFlags.StringLiteral) !== 0 ||
19
+ (type.flags & tsMod.TypeFlags.NumberLiteral) !== 0 ||
20
+ (type.flags & tsMod.TypeFlags.BooleanLiteral) !== 0 ||
21
+ (type.flags & tsMod.TypeFlags.BigIntLiteral) !== 0) {
22
+ return { kind: "literal", text };
23
+ }
24
+ return undefined;
25
+ };
26
+ const serializeFunctionSignature = (signature, checker, tsMod, depth, maxDepth, visited, onInternalError, registry) => {
27
+ const parameters = signature.getParameters().map((parameter) => {
28
+ const declaration = parameter.valueDeclaration ?? parameter.declarations?.[0];
29
+ const parameterType = declaration
30
+ ? checker.getTypeOfSymbolAtLocation(parameter, declaration)
31
+ : checker.getDeclaredTypeOfSymbol(parameter);
32
+ return {
33
+ name: parameter.getName(),
34
+ optional: toOptionalFlag(parameter, tsMod),
35
+ type: serializeTypeNode(parameterType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
36
+ };
37
+ });
38
+ const returnType = serializeTypeNode(checker.getReturnTypeOfSignature(signature), checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry);
39
+ return {
40
+ parameters: parameters.sort(compareByName),
41
+ returnType,
42
+ };
43
+ };
44
+ const serializeObjectProperties = (type, checker, tsMod, depth, maxDepth, visited, onInternalError, registry) => {
45
+ const properties = checker.getPropertiesOfType(type);
46
+ const snapshots = properties.map((property) => {
47
+ const declaration = property.valueDeclaration ?? property.declarations?.[0];
48
+ const propertyType = declaration
49
+ ? checker.getTypeOfSymbolAtLocation(property, declaration)
50
+ : checker.getDeclaredTypeOfSymbol(property);
51
+ return {
52
+ name: property.getName(),
53
+ optional: toOptionalFlag(property, tsMod),
54
+ readonly: hasReadonlyModifier(declaration, tsMod),
55
+ type: serializeTypeNode(propertyType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
56
+ };
57
+ });
58
+ return snapshots.sort(compareByName);
59
+ };
60
+ const serializeIndexSignature = (type, checker, tsMod, depth, maxDepth, visited, onInternalError, registry) => {
61
+ const infos = getIndexInfosOfType(type, checker);
62
+ if (!infos || infos.length === 0)
63
+ return undefined;
64
+ const first = infos[0];
65
+ if (!first)
66
+ return undefined;
67
+ return {
68
+ keyType: serializeTypeNode(first.keyType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
69
+ valueType: serializeTypeNode(first.type, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
70
+ readonly: first.isReadonly ?? false,
71
+ };
72
+ };
73
+ const primitiveTypeNode = (type, checker, tsMod) => {
74
+ const text = checker.typeToString(type);
75
+ if ((type.flags & tsMod.TypeFlags.Any) !== 0) {
76
+ return { kind: "any", text };
77
+ }
78
+ if ((type.flags & tsMod.TypeFlags.Unknown) !== 0) {
79
+ return { kind: "unknown", text };
80
+ }
81
+ if ((type.flags & tsMod.TypeFlags.Never) !== 0) {
82
+ return { kind: "never", text };
83
+ }
84
+ if ((type.flags & tsMod.TypeFlags.StringLike) !== 0 ||
85
+ (type.flags & tsMod.TypeFlags.NumberLike) !== 0 ||
86
+ (type.flags & tsMod.TypeFlags.BooleanLike) !== 0 ||
87
+ (type.flags & tsMod.TypeFlags.BigIntLike) !== 0 ||
88
+ (type.flags & tsMod.TypeFlags.ESSymbolLike) !== 0 ||
89
+ (type.flags & tsMod.TypeFlags.Null) !== 0 ||
90
+ (type.flags & tsMod.TypeFlags.Undefined) !== 0 ||
91
+ (type.flags & tsMod.TypeFlags.Void) !== 0) {
92
+ return { kind: "primitive", text };
93
+ }
94
+ return undefined;
95
+ };
96
+ const UNKNOWN_NODE = { kind: "unknown", text: "unknown" };
97
+ const serializeTypeNode = (type, checker, tsMod, depth, maxDepth, visited, onInternalError, registry) => {
98
+ const text = checker.typeToString(type);
99
+ const reg = (node) => {
100
+ registry?.set(node, type);
101
+ return node;
102
+ };
103
+ const typeId = `${type.id ?? "anon"}:${text}`;
104
+ if (depth > maxDepth || visited.has(typeId)) {
105
+ return reg({ kind: "reference", text });
106
+ }
107
+ visited.add(typeId);
108
+ const literal = asLiteral(type, checker, tsMod);
109
+ if (literal) {
110
+ return reg(literal);
111
+ }
112
+ if (type.isUnion()) {
113
+ const union = {
114
+ kind: "union",
115
+ text,
116
+ elements: type.types.map((value) => serializeTypeNode(value, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)),
117
+ };
118
+ return reg(union);
119
+ }
120
+ if (type.isIntersection()) {
121
+ const intersection = {
122
+ kind: "intersection",
123
+ text,
124
+ elements: type.types.map((value) => serializeTypeNode(value, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)),
125
+ };
126
+ return reg(intersection);
127
+ }
128
+ const primitive = primitiveTypeNode(type, checker, tsMod);
129
+ if (primitive) {
130
+ return reg(primitive);
131
+ }
132
+ if ((type.flags & tsMod.TypeFlags.Enum) !== 0) {
133
+ const members = [];
134
+ try {
135
+ const props = checker.getPropertiesOfType(type);
136
+ for (const sym of props) {
137
+ const name = sym.name;
138
+ let value;
139
+ const decl = sym.valueDeclaration;
140
+ if (decl && tsMod.isEnumMember?.(decl) && decl.initializer) {
141
+ const init = decl.initializer;
142
+ if (tsMod.isNumericLiteral?.(init)) {
143
+ value = parseInt(init.text, 10);
144
+ }
145
+ else if (tsMod.isStringLiteral?.(init) || tsMod.isNoSubstitutionTemplateLiteral?.(init)) {
146
+ value = init.text;
147
+ }
148
+ else {
149
+ const cv = checker.getConstantValue?.(init);
150
+ if (cv !== undefined)
151
+ value = cv;
152
+ }
153
+ }
154
+ members.push(value !== undefined ? { name, value } : { name });
155
+ }
156
+ }
157
+ catch (err) {
158
+ onInternalError?.(err, "enum-members");
159
+ }
160
+ const enumNode = {
161
+ kind: "enum",
162
+ text,
163
+ members: members.length > 0 ? members : undefined,
164
+ };
165
+ return reg(enumNode);
166
+ }
167
+ if ((type.flags & tsMod.TypeFlags.Conditional) !== 0) {
168
+ const ct = type;
169
+ const trueType = ct.resolvedTrueType ??
170
+ (ct.root?.node?.trueType
171
+ ? checker.getTypeFromTypeNode?.(ct.root.node.trueType)
172
+ : undefined);
173
+ const falseType = ct.resolvedFalseType ??
174
+ (ct.root?.node?.falseType
175
+ ? checker.getTypeFromTypeNode?.(ct.root.node.falseType)
176
+ : undefined);
177
+ const conditional = {
178
+ kind: "conditional",
179
+ text,
180
+ checkType: serializeTypeNode(ct.checkType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
181
+ extendsType: serializeTypeNode(ct.extendsType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
182
+ trueType: trueType
183
+ ? serializeTypeNode(trueType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)
184
+ : UNKNOWN_NODE,
185
+ falseType: falseType
186
+ ? serializeTypeNode(falseType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)
187
+ : UNKNOWN_NODE,
188
+ };
189
+ return reg(conditional);
190
+ }
191
+ if ((type.flags & tsMod.TypeFlags.IndexedAccess) !== 0) {
192
+ const iat = type;
193
+ const indexed = {
194
+ kind: "indexedAccess",
195
+ text,
196
+ objectType: serializeTypeNode(iat.objectType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
197
+ indexType: serializeTypeNode(iat.indexType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
198
+ };
199
+ return reg(indexed);
200
+ }
201
+ if ((type.flags & tsMod.TypeFlags.TemplateLiteral) !== 0) {
202
+ const tlt = type;
203
+ const texts = tlt.texts ?? [];
204
+ const types = tlt.types ?? [];
205
+ const template = {
206
+ kind: "templateLiteral",
207
+ text,
208
+ texts,
209
+ types: types.map((t) => serializeTypeNode(t, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)),
210
+ };
211
+ return reg(template);
212
+ }
213
+ const objType = type;
214
+ const mappedFlag = tsMod.ObjectFlags?.Mapped ?? FALLBACK_OBJECT_FLAGS_MAPPED;
215
+ if (objType.objectFlags !== undefined && (objType.objectFlags & mappedFlag) !== 0) {
216
+ const mt = type;
217
+ const constraintType = mt.constraint ?? mt.typeParameter?.constraint;
218
+ const mappedType = mt.templateType ?? mt.mappedType;
219
+ const mapped = {
220
+ kind: "mapped",
221
+ text,
222
+ constraintType: constraintType
223
+ ? serializeTypeNode(constraintType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)
224
+ : UNKNOWN_NODE,
225
+ mappedType: mappedType
226
+ ? serializeTypeNode(mappedType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)
227
+ : UNKNOWN_NODE,
228
+ };
229
+ return reg(mapped);
230
+ }
231
+ if ((type.flags & tsMod.TypeFlags.Index) !== 0) {
232
+ const idxType = type;
233
+ const typeOperator = {
234
+ kind: "typeOperator",
235
+ text,
236
+ operator: "keyof",
237
+ type: serializeTypeNode(idxType.type, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
238
+ };
239
+ return reg(typeOperator);
240
+ }
241
+ if (checker.isTupleType(type)) {
242
+ const typeArguments = checker.getTypeArguments(type);
243
+ const tuple = {
244
+ kind: "tuple",
245
+ text,
246
+ elements: typeArguments.map((value) => serializeTypeNode(value, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)),
247
+ };
248
+ return reg(tuple);
249
+ }
250
+ if (checker.isArrayType(type)) {
251
+ const typeArguments = checker.getTypeArguments(type);
252
+ const element = typeArguments[0];
253
+ const array = {
254
+ kind: "array",
255
+ text,
256
+ elements: element
257
+ ? [serializeTypeNode(element, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)]
258
+ : [UNKNOWN_NODE],
259
+ };
260
+ return reg(array);
261
+ }
262
+ const referenceArguments = checker.getTypeArguments(type);
263
+ if (referenceArguments.length > 0) {
264
+ const ref = {
265
+ kind: "reference",
266
+ text,
267
+ typeArguments: referenceArguments.map((value) => serializeTypeNode(value, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)),
268
+ };
269
+ return reg(ref);
270
+ }
271
+ const constructSignatures = checker.getSignaturesOfType(type, tsMod.SignatureKind.Construct);
272
+ if (constructSignatures.length > 0) {
273
+ const sig = serializeFunctionSignature(constructSignatures[0], checker, tsMod, depth, maxDepth, visited, onInternalError, registry);
274
+ const ctor = {
275
+ kind: "constructor",
276
+ text,
277
+ parameters: sig.parameters,
278
+ returnType: sig.returnType,
279
+ };
280
+ return reg(ctor);
281
+ }
282
+ const callSignatures = checker.getSignaturesOfType(type, tsMod.SignatureKind.Call);
283
+ if (callSignatures.length > 0) {
284
+ if (callSignatures.length > 1) {
285
+ const signatures = callSignatures.map((s) => {
286
+ const serialized = serializeFunctionSignature(s, checker, tsMod, depth, maxDepth, visited, onInternalError, registry);
287
+ return { parameters: serialized.parameters, returnType: serialized.returnType };
288
+ });
289
+ const overload = { kind: "overloadSet", text, signatures };
290
+ return reg(overload);
291
+ }
292
+ const signature = serializeFunctionSignature(callSignatures[0], checker, tsMod, depth, maxDepth, visited, onInternalError, registry);
293
+ const fn = {
294
+ kind: "function",
295
+ text,
296
+ parameters: signature.parameters,
297
+ returnType: signature.returnType,
298
+ };
299
+ return reg(fn);
300
+ }
301
+ const indexSig = serializeIndexSignature(type, checker, tsMod, depth, maxDepth, visited, onInternalError, registry);
302
+ const object = {
303
+ kind: "object",
304
+ text,
305
+ properties: serializeObjectProperties(type, checker, tsMod, depth, maxDepth, visited, onInternalError, registry),
306
+ ...(indexSig !== undefined && { indexSignature: indexSig }),
307
+ };
308
+ return reg(object);
309
+ };
310
+ /**
311
+ * Serialize a TypeScript type to TypeNode. For testing serializer branches (typeOperator, enum, etc).
312
+ * @internal
313
+ */
314
+ export function serializeTypeForTest(type, checker, tsMod, maxDepth = 6) {
315
+ return serializeTypeNode(type, checker, tsMod, 0, maxDepth, new Set());
316
+ }
317
+ /**
318
+ * Generate bootstrap file content that imports all modules from typeTargetSpecs.
319
+ * Include this file in the program's rootNames so resolveTypeTargetsFromSpecs can
320
+ * find types without requiring user source to import them.
321
+ *
322
+ * @example
323
+ * ```ts
324
+ * const bootstrapContent = createTypeTargetBootstrapContent(HTTPAPI_TYPE_TARGET_SPECS);
325
+ * const bootstrapPath = join(tmpDir, "__typeTargetBootstrap.ts");
326
+ * writeFileSync(bootstrapPath, bootstrapContent);
327
+ * const program = ts.createProgram([...rootFiles, bootstrapPath], options, host);
328
+ * ```
329
+ */
330
+ export function createTypeTargetBootstrapContent(specs) {
331
+ const seen = new Set();
332
+ const lines = [
333
+ "/**",
334
+ " * Auto-generated bootstrap for type target resolution.",
335
+ " * Imports canonical modules so resolveTypeTargetsFromSpecs can find types.",
336
+ " * Include in program rootNames when using typeTargetSpecs without user imports.",
337
+ " */",
338
+ ];
339
+ for (const spec of specs) {
340
+ if (seen.has(spec.module))
341
+ continue;
342
+ seen.add(spec.module);
343
+ const id = spec.module.replace(/\W+/g, "_").replace(/_+/g, "_") || "m";
344
+ lines.push(`import * as _${id} from "${spec.module}";`);
345
+ lines.push(`void _${id};`);
346
+ }
347
+ lines.push("export {};");
348
+ return lines.join("\n");
349
+ }
350
+ /**
351
+ * Resolve type targets from program imports using the given specs.
352
+ * Scans source files for imports from the specified modules and extracts types.
353
+ * Requires the program to include files that import from these modules (e.g.
354
+ * use createTypeTargetBootstrapContent and add to rootNames).
355
+ */
356
+ export function resolveTypeTargetsFromSpecs(program, tsMod, specs) {
357
+ const checker = program.getTypeChecker();
358
+ const found = new Map();
359
+ const getTypeFromSymbol = (symbol) => {
360
+ const aliased = resolveAliasedSymbol(symbol, checker, tsMod);
361
+ const decls = aliased.declarations ?? (aliased.valueDeclaration ? [aliased.valueDeclaration] : []);
362
+ const typeDecl = decls.find((d) => tsMod.isTypeAliasDeclaration(d) ||
363
+ tsMod.isInterfaceDeclaration(d) ||
364
+ tsMod.isTypeParameterDeclaration(d));
365
+ const decl = typeDecl ?? decls[0];
366
+ const useDeclaredType = decl &&
367
+ (tsMod.isTypeAliasDeclaration(decl) ||
368
+ tsMod.isInterfaceDeclaration(decl) ||
369
+ tsMod.isTypeParameterDeclaration(decl));
370
+ const type = useDeclaredType
371
+ ? checker.getDeclaredTypeOfSymbol(aliased)
372
+ : decl
373
+ ? checker.getTypeOfSymbolAtLocation(aliased, decl)
374
+ : checker.getDeclaredTypeOfSymbol(aliased);
375
+ if (!type || (type.flags & tsMod.TypeFlags.Any) !== 0)
376
+ return undefined;
377
+ return type;
378
+ };
379
+ const getTypeFromSpec = (symbol, spec) => {
380
+ const primaryType = getTypeFromSymbol(symbol);
381
+ if (!primaryType || !spec.typeMember)
382
+ return primaryType;
383
+ const aliased = resolveAliasedSymbol(symbol, checker, tsMod);
384
+ const exports = getSymbolExports(aliased);
385
+ if (!exports)
386
+ return primaryType;
387
+ const memberSymbol = Array.from(exports.values()).find((s) => s.getName() === spec.typeMember);
388
+ if (!memberSymbol)
389
+ return primaryType;
390
+ const memberType = checker.getDeclaredTypeOfSymbol(memberSymbol);
391
+ if (!memberType || (memberType.flags & tsMod.TypeFlags.Any) !== 0)
392
+ return primaryType;
393
+ return memberType;
394
+ };
395
+ const addFromModuleSymbol = (moduleSymbol, moduleSpec) => {
396
+ if (!moduleSymbol)
397
+ return;
398
+ const exportsByName = new Map(checker.getExportsOfModule(moduleSymbol).map((moduleExport) => [
399
+ moduleExport.getName(),
400
+ moduleExport,
401
+ ]));
402
+ for (const spec of specs) {
403
+ if (spec.module !== moduleSpec || found.has(spec.id))
404
+ continue;
405
+ const moduleExport = exportsByName.get(spec.exportName);
406
+ if (!moduleExport)
407
+ continue;
408
+ const type = getTypeFromSpec(moduleExport, spec);
409
+ if (type)
410
+ found.set(spec.id, type);
411
+ }
412
+ };
413
+ for (const sourceFile of program.getSourceFiles()) {
414
+ if (sourceFile.isDeclarationFile)
415
+ continue;
416
+ tsMod.forEachChild(sourceFile, (node) => {
417
+ if (!tsMod.isImportDeclaration(node))
418
+ return;
419
+ const moduleSpecifier = node.moduleSpecifier;
420
+ if (!tsMod.isStringLiteral(moduleSpecifier))
421
+ return;
422
+ const moduleSpec = moduleSpecifier.text;
423
+ const binding = node.importClause;
424
+ if (!binding)
425
+ return;
426
+ const moduleSymbol = checker.getSymbolAtLocation(moduleSpecifier);
427
+ const addNamed = (name) => {
428
+ const exportName = (name.propertyName ?? name.name).getText(sourceFile);
429
+ const spec = specs.find((s) => s.module === moduleSpec && s.exportName === exportName);
430
+ if (!spec || found.has(spec.id))
431
+ return;
432
+ const symbol = checker.getSymbolAtLocation(name.name);
433
+ if (!symbol)
434
+ return;
435
+ const type = getTypeFromSpec(symbol, spec);
436
+ if (type)
437
+ found.set(spec.id, type);
438
+ };
439
+ if (binding.namedBindings) {
440
+ if (tsMod.isNamedImports(binding.namedBindings)) {
441
+ for (const elem of binding.namedBindings.elements) {
442
+ addNamed(elem);
443
+ }
444
+ }
445
+ else if (tsMod.isNamespaceImport(binding.namedBindings)) {
446
+ const nsName = binding.namedBindings.name;
447
+ const symbol = checker.getSymbolAtLocation(nsName);
448
+ const nsType = symbol ? checker.getTypeOfSymbolAtLocation(symbol, nsName) : undefined;
449
+ for (const spec of specs) {
450
+ if (spec.module !== moduleSpec || found.has(spec.id))
451
+ continue;
452
+ const prop = nsType?.getProperty?.(spec.exportName);
453
+ if (!prop)
454
+ continue;
455
+ const type = getTypeFromSpec(prop, spec);
456
+ if (type)
457
+ found.set(spec.id, type);
458
+ }
459
+ addFromModuleSymbol(moduleSymbol, moduleSpec);
460
+ }
461
+ }
462
+ else if (binding.name) {
463
+ const symbol = checker.getSymbolAtLocation(binding.name);
464
+ const nsType = symbol ? checker.getTypeOfSymbolAtLocation(symbol, binding.name) : undefined;
465
+ for (const spec of specs) {
466
+ if (spec.module !== moduleSpec || found.has(spec.id))
467
+ continue;
468
+ const prop = nsType?.getProperty?.(spec.exportName);
469
+ if (!prop)
470
+ continue;
471
+ const type = getTypeFromSpec(prop, spec);
472
+ if (type)
473
+ found.set(spec.id, type);
474
+ }
475
+ addFromModuleSymbol(moduleSymbol, moduleSpec);
476
+ }
477
+ });
478
+ }
479
+ return specs.filter((s) => found.has(s.id)).map((s) => ({ id: s.id, type: found.get(s.id) }));
480
+ }
481
+ /**
482
+ * Resolve an export symbol to the symbol it aliases when present (re-exports and import-then-export),
483
+ * so we derive the type from the target file for cross-file type resolution.
484
+ */
485
+ const resolveExportSymbol = (symbol, checker, tsMod) => resolveAliasedSymbol(symbol, checker, tsMod);
486
+ /** Get the base GenericType from a generic instantiation (TypeReference). */
487
+ const getGenericBase = (type, checker) => getTypeReferenceTarget(type, checker);
488
+ /** Get the symbol representing the "root" type for comparison (handles GenericType and TypeReference). */
489
+ const getComparisonSymbol = (type, checker) => {
490
+ const base = getGenericBase(type, checker);
491
+ if (base?.symbol)
492
+ return base.symbol;
493
+ return getTypeSymbol(type);
494
+ };
495
+ /**
496
+ * Assignability and serialization may use internal TS APIs or heuristics. When those
497
+ * paths throw, errors are caught and behavior degrades (e.g. "not assignable" or a
498
+ * safe fallback node); no errors are thrown to the caller. Use session option
499
+ * onInternalError for observability when provided.
500
+ */
501
+ /**
502
+ * Check assignability. In strict mode, uses only checker.isTypeAssignableTo.
503
+ * In compatibility mode, adds fallbacks for generic instantiations and inheritance
504
+ * when types come from different files. Union sources require ALL constituents to
505
+ * be assignable (sound semantics).
506
+ */
507
+ const isAssignableTo = (source, target, checker, tsMod, mode) => {
508
+ if (checker.isTypeAssignableTo(source, target))
509
+ return true;
510
+ if (mode === "strict")
511
+ return false;
512
+ const tgtBase = getGenericBase(target, checker);
513
+ const tgtSymbol = tgtBase?.symbol ?? getTypeSymbol(target);
514
+ const matchSymbol = (sym) => sym && tgtSymbol && sym === tgtSymbol;
515
+ if (source.isUnion()) {
516
+ const constituents = source.types;
517
+ if (!constituents.every((t) => isAssignableTo(t, target, checker, tsMod, mode)))
518
+ return false;
519
+ return true;
520
+ }
521
+ const srcBase = getGenericBase(source, checker);
522
+ if (srcBase?.symbol && matchSymbol(srcBase.symbol))
523
+ return true;
524
+ const tgtTarget = getTypeReferenceTarget(target, checker);
525
+ if (srcBase?.symbol && tgtTarget?.symbol && srcBase.symbol === tgtTarget.symbol)
526
+ return true;
527
+ const srcSymbol = getComparisonSymbol(source, checker);
528
+ if (matchSymbol(srcSymbol))
529
+ return true;
530
+ if ((source.flags & tsMod.TypeFlags.Object) !== 0) {
531
+ const srcSym = source.symbol;
532
+ const isClassOrInterface = srcSym &&
533
+ (srcSym.flags & (tsMod.SymbolFlags.Class | tsMod.SymbolFlags.Interface)) !== 0;
534
+ if (isClassOrInterface) {
535
+ const bases = getBaseTypesInternal(source, checker);
536
+ const hasMatchingBase = (bases ?? []).some((b) => {
537
+ const bSym = getTypeSymbol(b);
538
+ return bSym && matchSymbol(bSym);
539
+ });
540
+ if (hasMatchingBase)
541
+ return true;
542
+ }
543
+ }
544
+ const tgtName = tgtSymbol?.getName();
545
+ if (tgtName && srcBase?.symbol?.getName() === tgtName)
546
+ return true;
547
+ if (tgtName && srcSymbol?.getName() === tgtName)
548
+ return true;
549
+ return false;
550
+ };
551
+ const serializeExport = (symbol, checker, tsMod, maxDepth, onInternalError, registry) => {
552
+ const resolved = resolveExportSymbol(symbol, checker, tsMod);
553
+ const declaration = resolved.valueDeclaration ?? resolved.declarations?.[0];
554
+ const useDeclaredType = declaration &&
555
+ (tsMod.isTypeAliasDeclaration(declaration) || tsMod.isInterfaceDeclaration(declaration));
556
+ const exportedType = useDeclaredType
557
+ ? checker.getDeclaredTypeOfSymbol(resolved)
558
+ : declaration
559
+ ? checker.getTypeOfSymbolAtLocation(resolved, declaration)
560
+ : checker.getDeclaredTypeOfSymbol(resolved);
561
+ return {
562
+ name: symbol.getName(),
563
+ declarationKind: declaration ? tsMod.SyntaxKind[declaration.kind] : undefined,
564
+ declarationText: declaration ? declaration.getText() : undefined,
565
+ docs: tsMod.displayPartsToString(symbol.getDocumentationComment(checker)) || undefined,
566
+ type: serializeTypeNode(exportedType, checker, tsMod, 0, maxDepth, new Set(), onInternalError, registry),
567
+ };
568
+ };
569
+ const collectImports = (sourceFile, program, tsMod) => {
570
+ const imports = [];
571
+ tsMod.forEachChild(sourceFile, (node) => {
572
+ if (!tsMod.isImportDeclaration(node))
573
+ return;
574
+ const moduleSpecifier = node.moduleSpecifier;
575
+ if (!tsMod.isStringLiteral(moduleSpecifier))
576
+ return;
577
+ const spec = moduleSpecifier.text;
578
+ const binding = node.importClause;
579
+ let info = { moduleSpecifier: spec };
580
+ if (binding) {
581
+ if (binding.namedBindings) {
582
+ if (tsMod.isNamedImports(binding.namedBindings)) {
583
+ info = {
584
+ ...info,
585
+ importedNames: binding.namedBindings.elements.map((el) => (el.propertyName ?? el.name).getText(sourceFile)),
586
+ };
587
+ }
588
+ else if (tsMod.isNamespaceImport(binding.namedBindings)) {
589
+ info = {
590
+ ...info,
591
+ namespaceImport: binding.namedBindings.name.getText(sourceFile),
592
+ };
593
+ }
594
+ }
595
+ if (binding.name) {
596
+ info = { ...info, defaultImport: binding.name.getText(sourceFile) };
597
+ }
598
+ }
599
+ imports.push(info);
600
+ });
601
+ return imports;
602
+ };
603
+ const createFileSnapshot = (sourceFile, checker, program, tsMod, maxDepth, includeImports, onInternalError, registry) => {
604
+ const filePath = sourceFile.fileName;
605
+ const moduleSymbol = checker.getSymbolAtLocation(sourceFile);
606
+ const exports = moduleSymbol
607
+ ? checker
608
+ .getExportsOfModule(moduleSymbol)
609
+ .map((value) => serializeExport(value, checker, tsMod, maxDepth, onInternalError, registry))
610
+ .sort(compareByName)
611
+ : [];
612
+ const imports = includeImports ? collectImports(sourceFile, program, tsMod) : undefined;
613
+ return {
614
+ filePath,
615
+ exports,
616
+ ...(imports !== undefined && imports.length > 0 ? { imports } : {}),
617
+ };
618
+ };
619
+ /**
620
+ * Apply a sequence of projection steps to a ts.Type, navigating to a sub-type.
621
+ * Returns undefined when any step fails (e.g. returnType on non-function).
622
+ */
623
+ const applyProjection = (type, steps, checker, tsMod, opts) => {
624
+ const { onInternalError, targetsByIdMap, assignabilityMode, maxDepth } = opts;
625
+ let current = type;
626
+ for (const step of steps) {
627
+ try {
628
+ switch (step.kind) {
629
+ case "returnType": {
630
+ const sigs = checker.getSignaturesOfType(current, tsMod.SignatureKind.Call);
631
+ if (sigs.length === 0)
632
+ return undefined;
633
+ current = checker.getReturnTypeOfSignature(sigs[0]);
634
+ break;
635
+ }
636
+ case "param": {
637
+ const sigs = checker.getSignaturesOfType(current, tsMod.SignatureKind.Call);
638
+ if (sigs.length === 0)
639
+ return undefined;
640
+ const params = sigs[0].getParameters();
641
+ if (step.index < 0 || step.index >= params.length)
642
+ return undefined;
643
+ const param = params[step.index];
644
+ const decl = param.valueDeclaration ?? param.declarations?.[0];
645
+ current = decl
646
+ ? checker.getTypeOfSymbolAtLocation(param, decl)
647
+ : checker.getDeclaredTypeOfSymbol(param);
648
+ break;
649
+ }
650
+ case "typeArg": {
651
+ const args = checker.getTypeArguments(current);
652
+ if (step.index < 0 || step.index >= args.length)
653
+ return undefined;
654
+ current = args[step.index];
655
+ break;
656
+ }
657
+ case "property": {
658
+ const prop = current.getProperty(step.name);
659
+ if (!prop)
660
+ return undefined;
661
+ const decl = prop.valueDeclaration ?? prop.declarations?.[0];
662
+ current = decl
663
+ ? checker.getTypeOfSymbolAtLocation(prop, decl)
664
+ : checker.getDeclaredTypeOfSymbol(prop);
665
+ break;
666
+ }
667
+ case "ensure": {
668
+ const target = targetsByIdMap.get(step.targetId);
669
+ if (!target)
670
+ return undefined;
671
+ if (!isAssignableTo(current, target, checker, tsMod, assignabilityMode))
672
+ return undefined;
673
+ break;
674
+ }
675
+ case "predicate": {
676
+ const serialized = serializeTypeNode(current, checker, tsMod, 0, maxDepth, new Set(), onInternalError);
677
+ if (!step.fn(serialized))
678
+ return undefined;
679
+ break;
680
+ }
681
+ default:
682
+ return undefined;
683
+ }
684
+ }
685
+ catch (err) {
686
+ onInternalError?.(err, `applyProjection:${step.kind}`);
687
+ return undefined;
688
+ }
689
+ }
690
+ return current;
691
+ };
692
+ export const createTypeInfoApiSession = (options) => {
693
+ let effectiveTypeTargets = options.typeTargets ??
694
+ (options.typeTargetSpecs?.length
695
+ ? resolveTypeTargetsFromSpecs(options.program, options.ts, options.typeTargetSpecs)
696
+ : undefined);
697
+ const failWhenNoTargets = options.failWhenNoTargetsResolved !== false;
698
+ if (failWhenNoTargets &&
699
+ options.typeTargetSpecs &&
700
+ options.typeTargetSpecs.length > 0 &&
701
+ (!effectiveTypeTargets || effectiveTypeTargets.length === 0)) {
702
+ const modules = [...new Set(options.typeTargetSpecs.map((s) => s.module))].join(", ");
703
+ throw new Error(`type targets could not be resolved; ensure program includes imports from: ${modules}. ` +
704
+ "Use createTypeTargetBootstrapContent(typeTargetSpecs) to generate a bootstrap file and add it to program rootNames.");
705
+ }
706
+ const checker = options.program.getTypeChecker();
707
+ const descriptors = new Map();
708
+ const snapshotCache = new Map();
709
+ const directoryCache = new Map();
710
+ const maxDepth = options.maxTypeDepth ?? DEFAULT_MAX_DEPTH;
711
+ const assignabilityMode = options.assignabilityMode ?? "compatibility";
712
+ const typeNodeRegistry = new WeakMap();
713
+ const targetsByIdMap = new Map((effectiveTypeTargets ?? []).map((t) => [t.id, t.type]));
714
+ const registerDescriptor = (descriptor) => {
715
+ if (descriptor.type === "file") {
716
+ descriptors.set(`file:${descriptor.path}`, descriptor);
717
+ return;
718
+ }
719
+ const globs = descriptor.relativeGlobs.join("|");
720
+ descriptors.set(`glob:${descriptor.baseDir}:${descriptor.recursive ? "r" : "nr"}:${globs}`, descriptor);
721
+ };
722
+ const file = (relativePath, queryOptions) => {
723
+ const baseDirResult = validatePathSegment(queryOptions.baseDir, "baseDir");
724
+ if (!baseDirResult.ok)
725
+ return { ok: false, error: "invalid-input" };
726
+ const relativePathResult = validatePathSegment(relativePath, "relativePath");
727
+ if (!relativePathResult.ok)
728
+ return { ok: false, error: "invalid-input" };
729
+ const baseDir = baseDirResult.value;
730
+ const normalizedRelativePath = relativePathResult.value;
731
+ const pathResult = resolvePathUnderBase(baseDir, normalizedRelativePath);
732
+ if (!pathResult.ok) {
733
+ return {
734
+ ok: false,
735
+ error: "path-escapes-base",
736
+ path: resolveRelativePath(baseDir, normalizedRelativePath),
737
+ };
738
+ }
739
+ const absolutePath = pathResult.path;
740
+ try {
741
+ if (!pathIsUnderBase(baseDir, absolutePath)) {
742
+ return { ok: false, error: "path-escapes-base", path: absolutePath };
743
+ }
744
+ }
745
+ catch {
746
+ return { ok: false, error: "path-escapes-base", path: absolutePath };
747
+ }
748
+ if (queryOptions.watch) {
749
+ registerDescriptor({
750
+ type: "file",
751
+ path: absolutePath,
752
+ });
753
+ }
754
+ const sourceFile = options.program.getSourceFile(absolutePath);
755
+ if (!sourceFile) {
756
+ return { ok: false, error: "file-not-in-program", path: absolutePath };
757
+ }
758
+ const cached = snapshotCache.get(absolutePath);
759
+ if (cached !== undefined) {
760
+ return { ok: true, snapshot: cached };
761
+ }
762
+ const snapshot = createFileSnapshot(sourceFile, checker, options.program, options.ts, maxDepth, true, options.onInternalError, typeNodeRegistry);
763
+ snapshotCache.set(absolutePath, snapshot);
764
+ return { ok: true, snapshot };
765
+ };
766
+ const directoryCacheKey = (baseDir, normalizedGlobs, recursive) => `${baseDir}\0${normalizedGlobs.join("\0")}\0${recursive ? "r" : "nr"}`;
767
+ const directory = (relativeGlobs, queryOptions) => {
768
+ const baseDirResult = validatePathSegment(queryOptions.baseDir, "baseDir");
769
+ if (!baseDirResult.ok)
770
+ return [];
771
+ const baseDir = baseDirResult.value;
772
+ const globsResult = validateRelativeGlobs(relativeGlobs, "relativeGlobs");
773
+ if (!globsResult.ok)
774
+ return [];
775
+ const normalizedGlobs = dedupeSorted(globsResult.value);
776
+ const cacheKey = directoryCacheKey(baseDir, normalizedGlobs, queryOptions.recursive ?? false);
777
+ const cached = directoryCache.get(cacheKey);
778
+ if (cached !== undefined)
779
+ return cached;
780
+ if (queryOptions.watch) {
781
+ registerDescriptor({
782
+ type: "glob",
783
+ baseDir: toPosixPath(baseDir),
784
+ relativeGlobs: normalizedGlobs,
785
+ recursive: queryOptions.recursive,
786
+ });
787
+ }
788
+ const depth = queryOptions.recursive ? undefined : 1;
789
+ const includes = queryOptions.recursive && normalizedGlobs.every((g) => !g.includes("**"))
790
+ ? [...normalizedGlobs, ...normalizedGlobs.map((g) => `**/${g}`)]
791
+ : normalizedGlobs;
792
+ const matchedPaths = options.ts.sys.readDirectory(baseDir, TYPE_EXTENSIONS, undefined, includes, depth);
793
+ const normalizedMatches = dedupeSorted(matchedPaths.map((value) => resolveRelativePath(baseDir, value)));
794
+ const underBase = normalizedMatches.filter((filePath) => pathIsUnderBase(baseDir, filePath));
795
+ const filteredMatches = queryOptions.recursive
796
+ ? underBase
797
+ : underBase.filter((filePath) => {
798
+ const relativePath = toPosixPath(relative(baseDir, filePath));
799
+ return !relativePath.includes("/");
800
+ });
801
+ const program = options.program;
802
+ const tsMod = options.ts;
803
+ const snapshots = filteredMatches.flatMap((filePath) => {
804
+ const sf = program.getSourceFile(filePath);
805
+ if (!sf)
806
+ return [];
807
+ try {
808
+ return [
809
+ createFileSnapshot(sf, checker, program, tsMod, maxDepth, true, options.onInternalError, typeNodeRegistry),
810
+ ];
811
+ }
812
+ catch (err) {
813
+ options.onInternalError?.(err, `directory() serialization for ${filePath}`);
814
+ return [];
815
+ }
816
+ });
817
+ directoryCache.set(cacheKey, snapshots);
818
+ return snapshots;
819
+ };
820
+ const resolveExport = (baseDir, filePath, exportName) => {
821
+ const result = file(filePath, { baseDir });
822
+ if (!result.ok)
823
+ return undefined;
824
+ return result.snapshot.exports.find((e) => e.name === exportName);
825
+ };
826
+ const apiIsAssignableTo = (node, targetId, projection) => {
827
+ const sourceType = typeNodeRegistry.get(node);
828
+ if (!sourceType)
829
+ return false;
830
+ const targetType = targetsByIdMap.get(targetId);
831
+ if (!targetType)
832
+ return false;
833
+ const projected = projection?.length
834
+ ? applyProjection(sourceType, projection, checker, options.ts, {
835
+ onInternalError: options.onInternalError,
836
+ targetsByIdMap,
837
+ assignabilityMode,
838
+ maxDepth,
839
+ })
840
+ : sourceType;
841
+ if (!projected)
842
+ return false;
843
+ return isAssignableTo(projected, targetType, checker, options.ts, assignabilityMode);
844
+ };
845
+ return {
846
+ api: {
847
+ file,
848
+ directory,
849
+ resolveExport,
850
+ isAssignableTo: apiIsAssignableTo,
851
+ },
852
+ consumeDependencies: () => [...descriptors.values()],
853
+ };
854
+ };
855
+ export const createTypeInfoApiSessionFactory = (options) => () => createTypeInfoApiSession(options);