@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
package/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # @typed/virtual-modules
2
+
3
+ Synchronous, type-safe virtual module primitives for TypeScript Language Service and compiler-host integrations.
4
+
5
+ ## Purpose
6
+
7
+ Virtual modules are module IDs that do not map to files on disk; instead, plugins generate their content when the module is resolved. Use them for type-safe generated modules (e.g. route matchers, API clients), scaffolding from file structure, or config injection. For example, `import { routes } from "router:./routes"` lets a plugin scan the routes directory and generate typed route descriptors from the file layout. This package provides the core primitives: the plugin system, type-aware code generation (via TypeInfoApi), and adapters that integrate with the TypeScript Language Service (editors) and CompilerHost (tsc, vmc).
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pnpm add @typed/virtual-modules
13
+ ```
14
+
15
+ Peer / dev: `typescript` (project supplies its own).
16
+
17
+ ## API overview
18
+
19
+ - **PluginManager** – First-match plugin resolution; `resolveModule(id, importer)` returns resolved / unresolved / error.
20
+ - **VirtualModulePlugin** – Contract: `name`, `shouldResolve(id, importer?)`, `build(id, importer?, api)`; sync only.
21
+ - **TypeInfoApi** – `createTypeInfoApiSession({ ts, program, maxTypeDepth? })` → `api.file(relativePath, options)` returns a `FileSnapshotResult` (check `result.ok` before using `result.snapshot`); `api.directory(relativeGlobs, options)` returns type snapshots. Both support optional watch descriptors.
22
+ - **NodeModulePluginLoader** – Load plugins from disk via Node resolution (sync CJS / preloaded); `load(plugin | { modulePath, baseDir })`.
23
+ - **LanguageServiceAdapter** – `attachLanguageServiceAdapter({ ts, languageService, languageServiceHost, resolver, projectRoot, watchHost?, debounceMs? })`; patches resolution, script snapshot/version, fileExists, readFile, and diagnostics; returns a handle with `dispose()`. Adapter state is bounded by necessary files: records whose importer file no longer exists are evicted.
24
+ - **CompilerHostAdapter** – `attachCompilerHostAdapter({ ts, compilerHost, resolver, projectRoot, watchHost?, debounceMs? })`; patches resolution, getSourceFile/getSourceFileByPath, fileExists, readFile, hasInvalidatedResolutions; returns a handle with `dispose()`. Same eviction of records when the importer file no longer exists.
25
+
26
+ ## Building plugins
27
+
28
+ ### Plugin contract
29
+
30
+ A `VirtualModulePlugin` implements `name`, `shouldResolve(id, importer)` → `boolean`, and `build(id, importer, api)` → `VirtualModuleBuildResult` (sync only). The host (Language Service adapter, CompilerHost adapter, or Vite plugin) provides `api` (TypeInfoApi) when it supplies `createTypeInfoApiSession`; otherwise plugins receive a no-op api that returns safe defaults (`file()` → `{ ok: false }`, `directory()` → `[]`). Hosts should always supply `createTypeInfoApiSession` when plugins use the API for correct behavior. **Rule**: `build()` must not trigger module resolution—re-entrancy is detected and surfaces as diagnostics (see [Production considerations](#production-considerations)).
31
+
32
+ Minimal static plugin (no `api` usage):
33
+
34
+ ```ts
35
+ const configPlugin: VirtualModulePlugin = {
36
+ name: "config",
37
+ shouldResolve(id) {
38
+ return id === "virtual:config";
39
+ },
40
+ build() {
41
+ return `export const env = "dev";`;
42
+ },
43
+ };
44
+ ```
45
+
46
+ ### Type snapshots: `api.file()` and `api.directory()`
47
+
48
+ **`api.file(relativePath, { baseDir, watch? })`** → `FileSnapshotResult`. Always check `result.ok`; on success use `result.snapshot` (`TypeInfoFileSnapshot`: `filePath`, `exports` as `ExportedTypeInfo[]`). On failure, `result.error` is `'file-not-in-program' | 'path-escapes-base' | 'invalid-input'`. Path contract: `baseDir` must be absolute; `relativePath` must not escape it.
49
+
50
+ ```ts
51
+ build(_id, importer, api) {
52
+ const baseDir = dirname(importer);
53
+ const result = api.file("types.ts", { baseDir });
54
+ if (!result.ok) {
55
+ return `export const names = [];`;
56
+ }
57
+ const names = result.snapshot.exports.map((e) => e.name);
58
+ return `export const names = ${JSON.stringify(names)};`;
59
+ }
60
+ ```
61
+
62
+ **`api.directory(relativeGlobs, { baseDir, recursive, watch? })`** → `readonly TypeInfoFileSnapshot[]`. Globs are relative to `baseDir` (e.g. `"*.ts"` or `["**/*.ts", "**/*.tsx"]`).
63
+
64
+ ```ts
65
+ build(_id, importer, api) {
66
+ const baseDir = dirname(importer);
67
+ const snapshots = api.directory("*.ts", { baseDir, recursive: true });
68
+ const paths = snapshots.map((s) => s.filePath);
69
+ return `export const paths = ${JSON.stringify(paths)};`;
70
+ }
71
+ ```
72
+
73
+ `ExportedTypeInfo` has `name`, `type` (serialized `TypeNode` tree), and optional `declarationKind`, `declarationText`, `docs`. The `type` field is used for structural checks below.
74
+
75
+ ### Resolving a single export: `api.resolveExport()`
76
+
77
+ **`api.resolveExport(baseDir, filePath, exportName)`** → `ExportedTypeInfo | undefined`. Use when you know the file and need one export by name without a full snapshot.
78
+
79
+ ```ts
80
+ const foo = api.resolveExport(baseDir, "./mod.ts", "foo");
81
+ ```
82
+
83
+ ### Structural assignability: `api.isAssignableTo()` and type targets
84
+
85
+ **`api.isAssignableTo(node, targetId, projection?)`** → `boolean`. Checks whether the type behind `node` (a serialized `TypeNode` from this API) is assignable to a pre-resolved target. Used to enforce contracts (e.g. "handler return type must be Effect").
86
+
87
+ **TypeProjectionStep** (optional third arg): `{ kind: "returnType" }`, `{ kind: "param", index }`, `{ kind: "typeArg", index }`, `{ kind: "property", name }` to navigate to a sub-type.
88
+
89
+ Assignability only works when the host creates the TypeInfo session with **type targets**:
90
+
91
+ - **typeTargetSpecs**: `{ id, module, exportName, typeMember? }`. Host resolves from the program; module specifier must match user imports exactly.
92
+ - **typeTargets**: pre-resolved `{ id, type: ts.Type }`.
93
+
94
+ Plugins that need assignability declare `typeTargetSpecs` on the plugin; the host merges them (e.g. via `collectTypeTargetSpecs`) and passes them into `createTypeInfoApiSession`. If the program does not import the target modules, use `createTypeTargetBootstrapContent(typeTargetSpecs)` and include the generated file in `rootNames` (see [Production considerations](#production-considerations)).
95
+
96
+ ```ts
97
+ // Validate handler return type is Effect (projection navigates to return type)
98
+ const handlerExport = snapshot.exports.find((e) => e.name === "handler");
99
+ if (handlerExport && !api.isAssignableTo(handlerExport.type, "Effect", [{ kind: "returnType" }])) {
100
+ return { errors: [{ code: "CONTRACT-001", message: "handler must return Effect", pluginName: name }] };
101
+ }
102
+ ```
103
+
104
+ ### Watch descriptors and errors
105
+
106
+ **Watch**: `api.file(..., { watch: true })` or `api.directory(..., { watch: true })` records descriptors; the host calls `session.consumeDependencies()` → `WatchDependencyDescriptor[]` for invalidation.
107
+
108
+ **Build errors**: Return `{ errors: [{ code, message, pluginName }] }` for structured failures. Use `isVirtualModuleBuildError(result)` to detect.
109
+
110
+ ### Reference implementations
111
+
112
+ - **Router plugin** ([`@typed/app` RouterVirtualModulePlugin](../../app/src/RouterVirtualModulePlugin.ts)) — `api.directory()`, type targets (Route, Fx, Effect), assignability for route/entrypoint validation.
113
+ - **HttpApi plugin** ([`@typed/app` HttpApiVirtualModulePlugin](../../app/src/HttpApiVirtualModulePlugin.ts)) — `api.directory()`, `api.isAssignableTo()` for handler/route/success/error contracts.
114
+ - **Tests**: [TypeInfoApi.test.ts](src/TypeInfoApi.test.ts), [vitePlugin.integration.test.ts](../virtual-modules-vite/src/vitePlugin.integration.test.ts) for minimal file/directory plugins.
115
+
116
+ ## Production considerations
117
+
118
+ - **Path contract** – `baseDir` must be an absolute path; `relativePath` (and resolved paths) must not escape `baseDir`. Path containment is enforced: escaping returns an error (`path-escapes-base` or `invalid-input`).
119
+ - **Plugin contract** – `build()` must not trigger module resolution. Re-entrancy is detected and may surface as unresolved resolution or a diagnostic (`re-entrant-resolution`).
120
+ - **TypeScript** – Tested with TypeScript 5.x. The host supplies its own `typescript` instance. TypeInfoApi uses a small set of **internal** TypeScript APIs (see [Internal API usage](.docs/production-readiness.md#internal-api-usage) in production-readiness) for type-target resolution and assignability fallbacks; these are centralized in `internal/tsInternal.ts` and may require adjustment when upgrading TS.
121
+ - **Type targets and resolution** – Type targets (e.g. for `assignableTo`) are resolved from the **same TypeScript program** only: “remote” here means types from other packages already in the program (e.g. in `node_modules`), not from a registry. Resolution matches the **exact module specifier** used in imports; path/alias mapping is not applied. For targets not imported by app code, use `createTypeTargetBootstrapContent(specs)` and include the generated file in the program’s `rootNames`.
122
+ - **Watch debouncing** – Optional `debounceMs` on adapter options coalesces rapid file/glob watch events to avoid recomputation storms.
123
+
124
+ ## Errors and gotchas
125
+
126
+ Full reference: [virtual-modules-errors-and-gotchas](.docs/virtual-modules-errors-and-gotchas.md). Summary:
127
+
128
+ - **`api.directory()`** may throw if TypeScript internal APIs throw during type serialization for a matched file. Not a typed error.
129
+ - **`api.file()`** returns a `FileSnapshotResult`: either `{ ok: true, snapshot }` or `{ ok: false, error, path? }`. Possible `error` values: `'file-not-in-program'` (path not in the program), `'path-escapes-base'` (path would escape baseDir), `'invalid-input'` (e.g. empty baseDir, null bytes, or invalid relativePath). Always check `result.ok` before using `result.snapshot`.
130
+ - **Adapter diagnostics** – The Language Service adapter may attach diagnostics for plugin failures (e.g. `plugin-build-threw`, `plugin-should-resolve-threw`), invalid build output (`invalid-build-output`), invalid options (`invalid-options`), re-entrant resolution (`re-entrant-resolution`), or virtual module rebuild failures. Plugin names and messages are included.
131
+ - **Loader errors** – `NodeModulePluginLoader.load()` returns structured errors with codes such as `module-not-found`, `module-load-failed`, `invalid-plugin-export`, `path-escapes-base`, and `invalid-request` (e.g. empty baseDir or specifier).
132
+
133
+ ## Status
134
+
135
+ Early implementation. Covers plugin manager, TypeInfo API, Node loader, and LS/CompilerHost adapters. See package spec and tests for behavior and testing strategy.
@@ -0,0 +1,3 @@
1
+ import { type CompilerHostAdapterOptions, type VirtualModuleAdapterHandle } from "./types.js";
2
+ export declare const attachCompilerHostAdapter: (options: CompilerHostAdapterOptions) => VirtualModuleAdapterHandle;
3
+ //# sourceMappingURL=CompilerHostAdapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CompilerHostAdapter.d.ts","sourceRoot":"","sources":["../src/CompilerHostAdapter.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,EAEhC,MAAM,YAAY,CAAC;AASpB,eAAO,MAAM,yBAAyB,GACpC,SAAS,0BAA0B,KAClC,0BA2SF,CAAC"}
@@ -0,0 +1,160 @@
1
+ import { rewriteSourceForPreviewLocation } from "./internal/materializeVirtualFile.js";
2
+ import { createVirtualRecordStore, toResolvedModule, } from "./internal/VirtualRecordStore.js";
3
+ import { VIRTUAL_NODE_MODULES_RELATIVE } from "./internal/path.js";
4
+ export const attachCompilerHostAdapter = (options) => {
5
+ if (typeof options.projectRoot !== "string" || options.projectRoot.trim() === "") {
6
+ throw new Error("projectRoot must be a non-empty string");
7
+ }
8
+ const host = options.compilerHost;
9
+ const watchHost = options.watchHost;
10
+ const invalidatedPaths = new Set();
11
+ const originalResolveModuleNameLiterals = host.resolveModuleNameLiterals?.bind(host);
12
+ const originalResolveModuleNames = host.resolveModuleNames?.bind(host);
13
+ const originalGetSourceFile = host.getSourceFile.bind(host);
14
+ const originalGetSourceFileByPath = host.getSourceFileByPath?.bind(host);
15
+ const originalFileExists = host.fileExists.bind(host);
16
+ const originalReadFile = host.readFile.bind(host);
17
+ const originalHasInvalidatedResolutions = host.hasInvalidatedResolutions?.bind(host);
18
+ const store = createVirtualRecordStore({
19
+ projectRoot: options.projectRoot,
20
+ resolver: options.resolver,
21
+ createTypeInfoApiSession: options.createTypeInfoApiSession,
22
+ debounceMs: options.debounceMs,
23
+ watchHost,
24
+ shouldEvictRecord: (record) => !originalFileExists(record.importer),
25
+ onMarkStale: (record) => {
26
+ invalidatedPaths.add(record.importer);
27
+ invalidatedPaths.add(record.virtualFileName);
28
+ },
29
+ onRecordResolved: (record) => {
30
+ invalidatedPaths.delete(record.importer);
31
+ invalidatedPaths.delete(record.virtualFileName);
32
+ },
33
+ onEvictRecord: (record) => {
34
+ invalidatedPaths.delete(record.importer);
35
+ invalidatedPaths.delete(record.virtualFileName);
36
+ },
37
+ });
38
+ const { recordsByVirtualFile } = store;
39
+ const getOrBuildRecord = (id, importer) => {
40
+ const result = store.getOrBuildRecord(id, importer);
41
+ return result.status === "resolved" ? result.record : undefined;
42
+ };
43
+ const ADAPTER_DIAGNOSTIC_CODE = 99001;
44
+ const rebuildIfStale = (record) => {
45
+ if (!record.stale) {
46
+ return record;
47
+ }
48
+ const rebuilt = store.resolveRecord(record.id, record.importer, record);
49
+ if (rebuilt.status === "resolved") {
50
+ return rebuilt.record;
51
+ }
52
+ if (rebuilt.status === "error" && options.reportDiagnostic) {
53
+ const diag = rebuilt.diagnostic;
54
+ const message = `Virtual module rebuild failed: ${diag.message}`;
55
+ options.reportDiagnostic({
56
+ category: options.ts.DiagnosticCategory.Error,
57
+ code: ADAPTER_DIAGNOSTIC_CODE,
58
+ file: undefined,
59
+ start: 0,
60
+ length: 0,
61
+ messageText: message,
62
+ });
63
+ }
64
+ return record;
65
+ };
66
+ const fallbackResolveModule = (moduleName, containingFile, compilerOptions) => {
67
+ const result = options.ts.resolveModuleName(moduleName, containingFile, compilerOptions ?? {}, host);
68
+ return result.resolvedModule;
69
+ };
70
+ host.resolveModuleNames = (moduleNames, containingFile, reusedNames, redirectedReference, compilerOptions, containingSourceFile) => {
71
+ const fallback = originalResolveModuleNames
72
+ ? originalResolveModuleNames(moduleNames, containingFile, reusedNames, redirectedReference, compilerOptions, containingSourceFile)
73
+ : moduleNames.map((moduleName) => fallbackResolveModule(moduleName, containingFile, compilerOptions));
74
+ return moduleNames.map((moduleName, index) => {
75
+ const record = getOrBuildRecord(moduleName, containingFile);
76
+ if (!record) {
77
+ return fallback[index];
78
+ }
79
+ return toResolvedModule(options.ts, record.virtualFileName);
80
+ });
81
+ };
82
+ const assignResolveModuleNameLiterals = (moduleLiterals, containingFile, redirectedReference, compilerOptions, containingSourceFile, reusedNames) => {
83
+ const fallback = originalResolveModuleNameLiterals
84
+ ? originalResolveModuleNameLiterals(moduleLiterals, containingFile, redirectedReference, compilerOptions, containingSourceFile, reusedNames)
85
+ : moduleLiterals.map((moduleLiteral) => ({
86
+ resolvedModule: fallbackResolveModule(moduleLiteral.text, containingFile, compilerOptions),
87
+ }));
88
+ return moduleLiterals.map((moduleLiteral, index) => {
89
+ const record = getOrBuildRecord(moduleLiteral.text, containingFile);
90
+ if (!record) {
91
+ return fallback[index];
92
+ }
93
+ return {
94
+ resolvedModule: toResolvedModule(options.ts, record.virtualFileName),
95
+ };
96
+ });
97
+ };
98
+ host.resolveModuleNameLiterals = assignResolveModuleNameLiterals;
99
+ const getSourceTextForRecord = (record) => {
100
+ const fresh = rebuildIfStale(record);
101
+ if (record.virtualFileName.includes(VIRTUAL_NODE_MODULES_RELATIVE)) {
102
+ return rewriteSourceForPreviewLocation(fresh.sourceText, fresh.importer, record.virtualFileName);
103
+ }
104
+ return fresh.sourceText;
105
+ };
106
+ host.getSourceFile = (fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile) => {
107
+ const record = recordsByVirtualFile.get(fileName);
108
+ if (!record) {
109
+ return originalGetSourceFile(fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile);
110
+ }
111
+ const sourceText = getSourceTextForRecord(record);
112
+ return options.ts.createSourceFile(fileName, sourceText, languageVersionOrOptions, true, options.ts.ScriptKind.TS);
113
+ };
114
+ if (originalGetSourceFileByPath) {
115
+ host.getSourceFileByPath = (fileName, path, languageVersionOrOptions, onError, shouldCreateNewSourceFile) => {
116
+ const record = recordsByVirtualFile.get(fileName);
117
+ if (!record) {
118
+ return originalGetSourceFileByPath(fileName, path, languageVersionOrOptions, onError, shouldCreateNewSourceFile);
119
+ }
120
+ const sourceText = getSourceTextForRecord(record);
121
+ return options.ts.createSourceFile(fileName, sourceText, languageVersionOrOptions, true, options.ts.ScriptKind.TS);
122
+ };
123
+ }
124
+ host.fileExists = (fileName) => {
125
+ if (recordsByVirtualFile.has(fileName)) {
126
+ return true;
127
+ }
128
+ return originalFileExists(fileName);
129
+ };
130
+ host.readFile = (fileName) => {
131
+ const record = recordsByVirtualFile.get(fileName);
132
+ if (!record) {
133
+ return originalReadFile(fileName);
134
+ }
135
+ return getSourceTextForRecord(record);
136
+ };
137
+ if (originalHasInvalidatedResolutions) {
138
+ host.hasInvalidatedResolutions = (...args) => {
139
+ if (invalidatedPaths.size > 0) {
140
+ return true;
141
+ }
142
+ return originalHasInvalidatedResolutions(...args);
143
+ };
144
+ }
145
+ return {
146
+ dispose() {
147
+ host.resolveModuleNameLiterals = originalResolveModuleNameLiterals;
148
+ host.resolveModuleNames = originalResolveModuleNames;
149
+ host.getSourceFile = originalGetSourceFile;
150
+ if (originalGetSourceFileByPath) {
151
+ host.getSourceFileByPath = originalGetSourceFileByPath;
152
+ }
153
+ host.fileExists = originalFileExists;
154
+ host.readFile = originalReadFile;
155
+ host.hasInvalidatedResolutions = originalHasInvalidatedResolutions;
156
+ store.dispose();
157
+ invalidatedPaths.clear();
158
+ },
159
+ };
160
+ };
@@ -0,0 +1,3 @@
1
+ import { type LanguageServiceAdapterOptions, type VirtualModuleAdapterHandle } from "./types.js";
2
+ export declare const attachLanguageServiceAdapter: (options: LanguageServiceAdapterOptions) => VirtualModuleAdapterHandle;
3
+ //# sourceMappingURL=LanguageServiceAdapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LanguageServiceAdapter.d.ts","sourceRoot":"","sources":["../src/LanguageServiceAdapter.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,6BAA6B,EAElC,KAAK,0BAA0B,EAChC,MAAM,YAAY,CAAC;AAiGpB,eAAO,MAAM,4BAA4B,GACvC,SAAS,6BAA6B,KACrC,0BA4gBF,CAAC"}