@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,85 @@
1
+ /**
2
+ * Pure validators for plugin and TypeInfoApi boundaries. No I/O.
3
+ */
4
+
5
+ const DEFAULT_MAX_LENGTH = 4096;
6
+
7
+ export type ValidationOk<T> = { ok: true; value: T };
8
+ export type ValidationFail = { ok: false; reason: string };
9
+ export type ValidationResult<T> = ValidationOk<T> | ValidationFail;
10
+
11
+ /**
12
+ * Validates a non-empty string (trimmed). Rejects null bytes.
13
+ */
14
+ export function validateNonEmptyString(
15
+ value: unknown,
16
+ name: string,
17
+ maxLength: number = DEFAULT_MAX_LENGTH,
18
+ ): ValidationResult<string> {
19
+ if (typeof value !== "string") {
20
+ return { ok: false, reason: `${name} must be a string` };
21
+ }
22
+ const trimmed = value.trim();
23
+ if (trimmed === "") {
24
+ return { ok: false, reason: `${name} must be non-empty` };
25
+ }
26
+ if (value.includes("\0")) {
27
+ return { ok: false, reason: `${name} must not contain null bytes` };
28
+ }
29
+ if (value.length > maxLength) {
30
+ return { ok: false, reason: `${name} exceeds max length ${maxLength}` };
31
+ }
32
+ return { ok: true, value: trimmed };
33
+ }
34
+
35
+ /**
36
+ * Validates a path-like segment: string, non-empty, no null byte, optional max length.
37
+ * Does not trim so path semantics are preserved.
38
+ */
39
+ export function validatePathSegment(
40
+ value: unknown,
41
+ name: string,
42
+ maxLength: number = DEFAULT_MAX_LENGTH,
43
+ ): ValidationResult<string> {
44
+ if (typeof value !== "string") {
45
+ return { ok: false, reason: `${name} must be a string` };
46
+ }
47
+ if (value.length === 0 || value.trim() === "") {
48
+ return { ok: false, reason: `${name} must be non-empty` };
49
+ }
50
+ if (value.includes("\0")) {
51
+ return { ok: false, reason: `${name} must not contain null bytes` };
52
+ }
53
+ if (value.length > maxLength) {
54
+ return { ok: false, reason: `${name} exceeds max length ${maxLength}` };
55
+ }
56
+ return { ok: true, value };
57
+ }
58
+
59
+ /**
60
+ * Validates relativeGlobs: array or single string; each element non-empty, no null byte,
61
+ * no parent-segment (..) or absolute paths, optional max length.
62
+ * Rejects patterns that could escape the intended base directory.
63
+ */
64
+ export function validateRelativeGlobs(
65
+ value: unknown,
66
+ name: string,
67
+ maxLength: number = DEFAULT_MAX_LENGTH,
68
+ ): ValidationResult<string[]> {
69
+ const arr = Array.isArray(value) ? value : value === undefined || value === null ? [] : [value];
70
+ const result: string[] = [];
71
+ for (let i = 0; i < arr.length; i++) {
72
+ const v = arr[i];
73
+ const r = validatePathSegment(v, `${name}[${i}]`, maxLength);
74
+ if (!r.ok) return r;
75
+ const normalized = (r.value as string).replaceAll("\\", "/");
76
+ if (normalized.includes("..")) {
77
+ return { ok: false, reason: `${name}[${i}] must not contain parent path segments (..)` };
78
+ }
79
+ if (normalized.startsWith("/") || /^[A-Za-z]:/.test(normalized)) {
80
+ return { ok: false, reason: `${name}[${i}] must be a relative glob, not absolute` };
81
+ }
82
+ result.push(r.value);
83
+ }
84
+ return { ok: true, value: result };
85
+ }
@@ -0,0 +1,75 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import type * as ts from "typescript";
4
+ import { createTypeTargetBootstrapContent } from "./TypeInfoApi.js";
5
+ import type { TypeTargetSpec } from "./types.js";
6
+
7
+ /**
8
+ * Canonical relative path for the type-target bootstrap file.
9
+ * Always under node_modules so it is git-ignored and does not pollute the project.
10
+ */
11
+ export const TYPE_TARGET_BOOTSTRAP_RELATIVE = "node_modules/.typed/type-target-bootstrap.ts";
12
+
13
+ /**
14
+ * Returns the canonical path for the type-target bootstrap file.
15
+ * Always under projectRoot/node_modules/.typed/ so it is git-ignored.
16
+ * All consumers (TS plugin, VSCode resolver, VMC, Vite plugin) should use this path.
17
+ */
18
+ export function getTypeTargetBootstrapPath(projectRoot: string): string {
19
+ return join(projectRoot, "node_modules", ".typed", "type-target-bootstrap.ts");
20
+ }
21
+
22
+ export interface EnsureTypeTargetBootstrapFileFs {
23
+ mkdirSync(path: string, options?: { recursive?: boolean }): void;
24
+ writeFile(path: string, content: string): void;
25
+ }
26
+
27
+ const defaultFs: EnsureTypeTargetBootstrapFileFs = {
28
+ mkdirSync(path: string, options?: { recursive?: boolean }) {
29
+ mkdirSync(path, options);
30
+ },
31
+ writeFile(path: string, content: string) {
32
+ writeFileSync(path, content, "utf8");
33
+ },
34
+ };
35
+
36
+ /**
37
+ * Ensures the type-target bootstrap file exists on disk. Writes to
38
+ * projectRoot/node_modules/.typed/type-target-bootstrap.ts (git-ignored via node_modules).
39
+ * Creates node_modules/.typed if needed. Returns the bootstrap path.
40
+ * Use default Node fs unless passing custom fs (e.g. ts.sys for VMC).
41
+ */
42
+ export function ensureTypeTargetBootstrapFile(
43
+ projectRoot: string,
44
+ typeTargetSpecs: readonly TypeTargetSpec[],
45
+ fs: EnsureTypeTargetBootstrapFileFs = defaultFs,
46
+ ): string {
47
+ const bootstrapPath = getTypeTargetBootstrapPath(projectRoot);
48
+ if (typeTargetSpecs.length === 0) return bootstrapPath;
49
+ const dir = dirname(bootstrapPath);
50
+ fs.mkdirSync(dir, { recursive: true });
51
+ fs.writeFile(bootstrapPath, createTypeTargetBootstrapContent(typeTargetSpecs));
52
+ return bootstrapPath;
53
+ }
54
+
55
+ /**
56
+ * Returns a program that includes the type-target bootstrap file in rootNames when
57
+ * typeTargetSpecs is non-empty, so resolveTypeTargetsFromSpecs can find types.
58
+ * If typeTargetSpecs is empty or the program already includes the bootstrap, returns program unchanged.
59
+ */
60
+ export function getProgramWithTypeTargetBootstrap(
61
+ tsMod: typeof import("typescript"),
62
+ program: ts.Program,
63
+ projectRoot: string,
64
+ typeTargetSpecs: readonly TypeTargetSpec[] | undefined,
65
+ ): ts.Program {
66
+ if (!typeTargetSpecs?.length) return program;
67
+ const bootstrapPath = ensureTypeTargetBootstrapFile(projectRoot, typeTargetSpecs);
68
+ const rootNames = program.getRootFileNames();
69
+ const canonicalBootstrap = resolve(bootstrapPath);
70
+ const alreadyHasBootstrap = rootNames.some((p) => resolve(p) === canonicalBootstrap);
71
+ if (alreadyHasBootstrap) return program;
72
+ const opts = program.getCompilerOptions();
73
+ const host = tsMod.createCompilerHost(opts, true);
74
+ return tsMod.createProgram([...rootNames, bootstrapPath], opts, host);
75
+ }
package/src/types.ts ADDED
@@ -0,0 +1,535 @@
1
+ import type * as ts from "typescript";
2
+
3
+ /**
4
+ * Type-safe AST for serialized types. Discriminated union by `kind`.
5
+ * Use `node.kind` to narrow; each variant exposes only its fields (e.g. `UnionTypeNode.elements`, `FunctionTypeNode.parameters`).
6
+ *
7
+ * Forward-compatible consumer guidance: Use a switch with a default branch that handles unknown kinds
8
+ * (e.g. `return node.text` or fallback behavior). New `kind` values may be added over time;
9
+ * consumers that only care about specific variants should explicitly list them and treat others as opaque.
10
+ *
11
+ * @example
12
+ * function visit(node: TypeNode): string {
13
+ * switch (node.kind) {
14
+ * case "union": return node.elements.map(visit).join(" | ");
15
+ * case "function": return `(${node.parameters.map(p => p.name).join(", ")}) => ${visit(node.returnType)}`;
16
+ * case "object": return `{ ${node.properties.map(p => p.name).join(", ")} }`;
17
+ * default: return node.text;
18
+ * }
19
+ * }
20
+ */
21
+ export type TypeNode =
22
+ | LiteralTypeNode
23
+ | PrimitiveTypeNode
24
+ | AnyTypeNode
25
+ | UnknownTypeNode
26
+ | NeverTypeNode
27
+ | UnionTypeNode
28
+ | IntersectionTypeNode
29
+ | TupleTypeNode
30
+ | ArrayTypeNode
31
+ | FunctionTypeNode
32
+ | ReferenceTypeNode
33
+ | ObjectTypeNode
34
+ | ConditionalTypeNode
35
+ | IndexedAccessTypeNode
36
+ | TemplateLiteralTypeNode
37
+ | MappedTypeNode
38
+ | TypeOperatorTypeNode
39
+ | ConstructorTypeNode
40
+ | IndexSignatureTypeNode
41
+ | OverloadSetTypeNode
42
+ | EnumTypeNode;
43
+
44
+ export interface LiteralTypeNode {
45
+ readonly kind: "literal";
46
+ readonly text: string;
47
+ }
48
+
49
+ export interface PrimitiveTypeNode {
50
+ readonly kind: "primitive";
51
+ readonly text: string;
52
+ }
53
+
54
+ export interface AnyTypeNode {
55
+ readonly kind: "any";
56
+ readonly text: string;
57
+ }
58
+
59
+ export interface UnknownTypeNode {
60
+ readonly kind: "unknown";
61
+ readonly text: string;
62
+ }
63
+
64
+ export interface NeverTypeNode {
65
+ readonly kind: "never";
66
+ readonly text: string;
67
+ }
68
+
69
+ export interface UnionTypeNode {
70
+ readonly kind: "union";
71
+ readonly text: string;
72
+ readonly elements: readonly TypeNode[];
73
+ }
74
+
75
+ export interface IntersectionTypeNode {
76
+ readonly kind: "intersection";
77
+ readonly text: string;
78
+ readonly elements: readonly TypeNode[];
79
+ }
80
+
81
+ export interface TupleTypeNode {
82
+ readonly kind: "tuple";
83
+ readonly text: string;
84
+ readonly elements: readonly TypeNode[];
85
+ }
86
+
87
+ export interface ArrayTypeNode {
88
+ readonly kind: "array";
89
+ readonly text: string;
90
+ readonly elements: readonly TypeNode[];
91
+ }
92
+
93
+ export interface TypeParameter {
94
+ readonly name: string;
95
+ readonly optional: boolean;
96
+ readonly type: TypeNode;
97
+ }
98
+
99
+ export interface FunctionTypeNode {
100
+ readonly kind: "function";
101
+ readonly text: string;
102
+ readonly parameters: readonly TypeParameter[];
103
+ readonly returnType: TypeNode;
104
+ }
105
+
106
+ export interface ReferenceTypeNode {
107
+ readonly kind: "reference";
108
+ readonly text: string;
109
+ readonly typeArguments?: readonly TypeNode[];
110
+ }
111
+
112
+ export interface ObjectProperty {
113
+ readonly name: string;
114
+ readonly optional: boolean;
115
+ readonly readonly: boolean;
116
+ readonly type: TypeNode;
117
+ }
118
+
119
+ export interface ObjectTypeNode {
120
+ readonly kind: "object";
121
+ readonly text: string;
122
+ readonly properties: readonly ObjectProperty[];
123
+ /** Optional index signature for `[key: K]: V` when present. */
124
+ readonly indexSignature?: IndexSignatureInfo;
125
+ }
126
+
127
+ export interface IndexSignatureInfo {
128
+ readonly keyType: TypeNode;
129
+ readonly valueType: TypeNode;
130
+ readonly readonly: boolean;
131
+ }
132
+
133
+ export interface ConditionalTypeNode {
134
+ readonly kind: "conditional";
135
+ readonly text: string;
136
+ readonly checkType: TypeNode;
137
+ readonly extendsType: TypeNode;
138
+ readonly trueType: TypeNode;
139
+ readonly falseType: TypeNode;
140
+ }
141
+
142
+ export interface IndexedAccessTypeNode {
143
+ readonly kind: "indexedAccess";
144
+ readonly text: string;
145
+ readonly objectType: TypeNode;
146
+ readonly indexType: TypeNode;
147
+ }
148
+
149
+ export interface TemplateLiteralTypeNode {
150
+ readonly kind: "templateLiteral";
151
+ readonly text: string;
152
+ readonly texts: readonly string[];
153
+ readonly types: readonly TypeNode[];
154
+ }
155
+
156
+ export interface MappedTypeNode {
157
+ readonly kind: "mapped";
158
+ readonly text: string;
159
+ readonly constraintType: TypeNode;
160
+ readonly mappedType: TypeNode;
161
+ readonly readonlyModifier?: "+" | "-" | undefined;
162
+ readonly optionalModifier?: "+" | "-" | undefined;
163
+ }
164
+
165
+ export interface TypeOperatorTypeNode {
166
+ readonly kind: "typeOperator";
167
+ readonly text: string;
168
+ readonly operator: "keyof" | "unique" | "readonly";
169
+ readonly type: TypeNode;
170
+ }
171
+
172
+ export interface ConstructorTypeNode {
173
+ readonly kind: "constructor";
174
+ readonly text: string;
175
+ readonly parameters: readonly TypeParameter[];
176
+ readonly returnType: TypeNode;
177
+ }
178
+
179
+ export interface IndexSignatureTypeNode {
180
+ readonly kind: "indexSignature";
181
+ readonly text: string;
182
+ readonly keyType: TypeNode;
183
+ readonly valueType: TypeNode;
184
+ readonly readonly: boolean;
185
+ }
186
+
187
+ export interface FunctionSignature {
188
+ readonly parameters: readonly TypeParameter[];
189
+ readonly returnType: TypeNode;
190
+ }
191
+
192
+ export interface OverloadSetTypeNode {
193
+ readonly kind: "overloadSet";
194
+ readonly text: string;
195
+ readonly signatures: readonly FunctionSignature[];
196
+ }
197
+
198
+ export interface EnumTypeNode {
199
+ readonly kind: "enum";
200
+ readonly text: string;
201
+ readonly members?: readonly { readonly name: string; readonly value?: string | number }[];
202
+ }
203
+
204
+ export interface ExportedTypeInfo {
205
+ readonly name: string;
206
+ readonly declarationKind?: string;
207
+ readonly declarationText?: string;
208
+ readonly docs?: string;
209
+ readonly type: TypeNode;
210
+ }
211
+
212
+ /**
213
+ * A step that navigates from one type to a sub-type during assignability checking.
214
+ * Steps chain left-to-right when composed in an array.
215
+ */
216
+ export type TypeProjectionStep =
217
+ | { readonly kind: "returnType" }
218
+ | { readonly kind: "param"; readonly index: number }
219
+ | { readonly kind: "typeArg"; readonly index: number }
220
+ | { readonly kind: "property"; readonly name: string }
221
+ | { readonly kind: "ensure"; readonly targetId: string }
222
+ | { readonly kind: "predicate"; readonly fn: (node: TypeNode) => boolean };
223
+
224
+ /**
225
+ * Spec for resolving a type from program imports for structural assignability checks.
226
+ * Used with createTypeInfoApiSession typeTargetSpecs option; resolution happens internally.
227
+ */
228
+ export interface TypeTargetSpec {
229
+ readonly id: string;
230
+ readonly module: string;
231
+ readonly exportName: string;
232
+ /**
233
+ * When the export is a merged interface+namespace with a type member that accepts
234
+ * all generic instantiations (e.g. Route.Any = Route<any, any>), use this to
235
+ * resolve that member's type for assignability checks.
236
+ * Enables Route.Parse("/status") (Route<"/status">) to pass assignableTo.Route
237
+ * by comparing against Route.Any instead of the uninstantiated generic.
238
+ */
239
+ readonly typeMember?: string;
240
+ }
241
+
242
+ /**
243
+ * Import information for cross-file reference resolution.
244
+ * Enables plugins to trace symbols to their source declarations.
245
+ */
246
+ export interface ImportInfo {
247
+ readonly moduleSpecifier: string;
248
+ readonly importedNames?: readonly string[];
249
+ readonly defaultImport?: string;
250
+ readonly namespaceImport?: string;
251
+ /**
252
+ * Resolved absolute path when the module resolves to a project file.
253
+ * Optional; not currently populated by the API. Reserved for future use (e.g. plugin navigation).
254
+ */
255
+ readonly resolvedFilePath?: string;
256
+ }
257
+
258
+ export interface TypeInfoFileSnapshot {
259
+ readonly filePath: string;
260
+ readonly exports: readonly ExportedTypeInfo[];
261
+ /** Imports present in the file for cross-file reference resolution. */
262
+ readonly imports?: readonly ImportInfo[];
263
+ }
264
+
265
+ export interface FileSnapshotSuccess {
266
+ readonly ok: true;
267
+ readonly snapshot: TypeInfoFileSnapshot;
268
+ }
269
+
270
+ export type FileSnapshotErrorCode = "file-not-in-program" | "path-escapes-base" | "invalid-input";
271
+
272
+ export interface FileSnapshotError {
273
+ readonly ok: false;
274
+ readonly error: FileSnapshotErrorCode;
275
+ readonly path?: string;
276
+ }
277
+
278
+ export type FileSnapshotResult = FileSnapshotSuccess | FileSnapshotError;
279
+
280
+ export interface TypeInfoFileQueryOptions {
281
+ readonly baseDir: string;
282
+ readonly watch?: boolean;
283
+ }
284
+
285
+ export interface TypeInfoDirectoryQueryOptions {
286
+ readonly baseDir: string;
287
+ readonly recursive: boolean;
288
+ readonly watch?: boolean;
289
+ }
290
+
291
+ export interface FileWatchDependencyDescriptor {
292
+ readonly type: "file";
293
+ readonly path: string;
294
+ }
295
+
296
+ export interface GlobWatchDependencyDescriptor {
297
+ readonly type: "glob";
298
+ readonly baseDir: string;
299
+ readonly relativeGlobs: readonly string[];
300
+ readonly recursive: boolean;
301
+ }
302
+
303
+ export type WatchDependencyDescriptor =
304
+ | FileWatchDependencyDescriptor
305
+ | GlobWatchDependencyDescriptor;
306
+
307
+ export interface TypeInfoApi {
308
+ file(relativePath: string, options: TypeInfoFileQueryOptions): FileSnapshotResult;
309
+ directory(
310
+ relativeGlobs: string | readonly string[],
311
+ options: TypeInfoDirectoryQueryOptions,
312
+ ): readonly TypeInfoFileSnapshot[];
313
+ /**
314
+ * Resolve an export by name from a file. Use baseDir-relative filePath.
315
+ * Returns undefined when the file is not in the program or the export is not found.
316
+ */
317
+ resolveExport(
318
+ baseDir: string,
319
+ filePath: string,
320
+ exportName: string,
321
+ ): ExportedTypeInfo | undefined;
322
+ /**
323
+ * Dynamic structural assignability check. Looks up the ts.Type backing `node`
324
+ * (registered during serialization), optionally applies projection steps to
325
+ * navigate to a sub-type, then checks assignability against the resolved target.
326
+ *
327
+ * Returns false when: node was not created by this API, projection fails
328
+ * (e.g. returnType on non-function), or targetId was not resolved.
329
+ *
330
+ * Plugins can navigate the TypeNode tree themselves and pass any sub-node
331
+ * (e.g. `functionNode.returnType`, `referenceNode.typeArguments[0]`) since
332
+ * all nodes are registered during serialization.
333
+ */
334
+ isAssignableTo(
335
+ node: TypeNode,
336
+ targetId: string,
337
+ projection?: readonly TypeProjectionStep[],
338
+ ): boolean;
339
+ }
340
+
341
+ export interface TypeInfoApiSession {
342
+ readonly api: TypeInfoApi;
343
+ consumeDependencies(): readonly WatchDependencyDescriptor[];
344
+ }
345
+
346
+ export interface TypeInfoApiFactoryParams {
347
+ readonly id: string;
348
+ readonly importer: string;
349
+ }
350
+
351
+ export type CreateTypeInfoApiSession = (params: TypeInfoApiFactoryParams) => TypeInfoApiSession;
352
+
353
+ /** Result of a successful build; plain string is treated as { sourceText }. */
354
+ export interface VirtualModuleBuildSuccess {
355
+ readonly sourceText: string;
356
+ readonly warnings?: readonly VirtualModuleDiagnostic[];
357
+ }
358
+
359
+ /** Result of a failed build with structured errors. */
360
+ export interface VirtualModuleBuildError {
361
+ readonly errors: readonly VirtualModuleDiagnostic[];
362
+ }
363
+
364
+ export type VirtualModuleBuildResult = string | VirtualModuleBuildSuccess | VirtualModuleBuildError;
365
+
366
+ /** Type guard: true when the build result indicates failure with structured errors. */
367
+ export function isVirtualModuleBuildError(
368
+ result: VirtualModuleBuildResult,
369
+ ): result is VirtualModuleBuildError {
370
+ return (
371
+ typeof result === "object" &&
372
+ result !== null &&
373
+ "errors" in result &&
374
+ Array.isArray((result as VirtualModuleBuildError).errors) &&
375
+ (result as VirtualModuleBuildError).errors.length > 0
376
+ );
377
+ }
378
+
379
+ /** Type guard: true when the build result indicates success with sourceText. */
380
+ export function isVirtualModuleBuildSuccess(
381
+ result: VirtualModuleBuildResult,
382
+ ): result is VirtualModuleBuildSuccess {
383
+ return (
384
+ typeof result === "object" &&
385
+ result !== null &&
386
+ "sourceText" in result &&
387
+ typeof (result as VirtualModuleBuildSuccess).sourceText === "string"
388
+ );
389
+ }
390
+
391
+ export interface VirtualModulePlugin {
392
+ readonly name: string;
393
+ shouldResolve(id: string, importer: string): boolean;
394
+ build(id: string, importer: string, api: TypeInfoApi): VirtualModuleBuildResult;
395
+ /**
396
+ * Optional type target specs for structural assignability checks.
397
+ * Plugins that need assignableTo (e.g. Route, Effect) should declare them here.
398
+ * Specs from all plugins are merged (deduped by id) when creating the TypeInfo session.
399
+ */
400
+ readonly typeTargetSpecs?: readonly TypeTargetSpec[];
401
+ }
402
+
403
+ export interface VirtualModuleDiagnostic {
404
+ readonly code:
405
+ | "plugin-should-resolve-threw"
406
+ | "plugin-build-threw"
407
+ | "session-creation-failed"
408
+ | "invalid-build-output"
409
+ | "invalid-options"
410
+ | "re-entrant-resolution"
411
+ | (string & {}); // allow plugin-specific codes e.g. RVM-GUARD-001
412
+ readonly message: string;
413
+ readonly pluginName: string;
414
+ }
415
+
416
+ export interface VirtualModuleResolved {
417
+ readonly status: "resolved";
418
+ readonly pluginName: string;
419
+ readonly sourceText: string;
420
+ readonly dependencies: readonly WatchDependencyDescriptor[];
421
+ readonly warnings?: readonly VirtualModuleDiagnostic[];
422
+ }
423
+
424
+ export interface VirtualModuleUnresolved {
425
+ readonly status: "unresolved";
426
+ }
427
+
428
+ export interface VirtualModuleError {
429
+ readonly status: "error";
430
+ readonly diagnostic: VirtualModuleDiagnostic;
431
+ }
432
+
433
+ export type VirtualModuleResolution =
434
+ | VirtualModuleResolved
435
+ | VirtualModuleUnresolved
436
+ | VirtualModuleError;
437
+
438
+ export interface ResolveVirtualModuleOptions {
439
+ readonly id: string;
440
+ readonly importer: string;
441
+ readonly createTypeInfoApiSession?: CreateTypeInfoApiSession;
442
+ }
443
+
444
+ export interface VirtualModuleResolver {
445
+ resolveModule(options: ResolveVirtualModuleOptions): VirtualModuleResolution;
446
+ }
447
+
448
+ export interface NodeModulePluginRequest {
449
+ readonly specifier: string;
450
+ readonly baseDir: string;
451
+ }
452
+
453
+ export type NodeModulePluginLoadInput = VirtualModulePlugin | NodeModulePluginRequest;
454
+
455
+ export interface NodeModulePluginLoadSuccess {
456
+ readonly status: "loaded";
457
+ readonly plugin: VirtualModulePlugin;
458
+ readonly resolvedPath: string;
459
+ }
460
+
461
+ export interface NodeModulePluginLoadError {
462
+ readonly status: "error";
463
+ readonly specifier: string;
464
+ readonly baseDir: string;
465
+ readonly code:
466
+ | "module-not-found"
467
+ | "module-load-failed"
468
+ | "invalid-plugin-export"
469
+ | "path-escapes-base"
470
+ | "invalid-request";
471
+ readonly message: string;
472
+ }
473
+
474
+ export type NodeModulePluginLoadResult = NodeModulePluginLoadSuccess | NodeModulePluginLoadError;
475
+
476
+ export interface VirtualModuleRecord {
477
+ readonly key: string;
478
+ readonly id: string;
479
+ readonly importer: string;
480
+ readonly pluginName: string;
481
+ readonly virtualFileName: string;
482
+ readonly sourceText: string;
483
+ readonly dependencies: readonly WatchDependencyDescriptor[];
484
+ readonly diagnostic?: VirtualModuleDiagnostic;
485
+ readonly warnings?: readonly VirtualModuleDiagnostic[];
486
+ readonly version: number;
487
+ readonly stale: boolean;
488
+ }
489
+
490
+ export interface LanguageServiceWatchHost {
491
+ watchFile?: (
492
+ path: string,
493
+ callback: ts.FileWatcherCallback,
494
+ pollingInterval?: number,
495
+ options?: ts.WatchOptions,
496
+ ) => ts.FileWatcher;
497
+ watchDirectory?: (
498
+ path: string,
499
+ callback: ts.DirectoryWatcherCallback,
500
+ recursive?: boolean,
501
+ options?: ts.WatchOptions,
502
+ ) => ts.FileWatcher;
503
+ }
504
+
505
+ export interface LanguageServiceAdapterOptions {
506
+ readonly ts: typeof import("typescript");
507
+ readonly languageService: ts.LanguageService;
508
+ readonly languageServiceHost: ts.LanguageServiceHost;
509
+ readonly resolver: VirtualModuleResolver;
510
+ readonly projectRoot: string;
511
+ readonly createTypeInfoApiSession?: CreateTypeInfoApiSession;
512
+ readonly watchHost?: LanguageServiceWatchHost;
513
+ /** Coalesce rapid watch events (ms). When set, markStale is deferred until after the delay. */
514
+ readonly debounceMs?: number;
515
+ }
516
+
517
+ export interface CompilerHostAdapterOptions {
518
+ readonly ts: typeof import("typescript");
519
+ readonly compilerHost: ts.CompilerHost;
520
+ readonly resolver: VirtualModuleResolver;
521
+ readonly projectRoot: string;
522
+ readonly createTypeInfoApiSession?: CreateTypeInfoApiSession;
523
+ readonly watchHost?: LanguageServiceWatchHost;
524
+ /** Coalesce rapid watch events (ms). When set, markStale is deferred until after the delay. */
525
+ readonly debounceMs?: number;
526
+ /**
527
+ * When provided, called when a virtual module rebuild fails (stale record).
528
+ * Use to surface diagnostics to match LanguageServiceAdapter behavior.
529
+ */
530
+ readonly reportDiagnostic?: ts.DiagnosticReporter;
531
+ }
532
+
533
+ export interface VirtualModuleAdapterHandle {
534
+ dispose(): void;
535
+ }