@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.
- package/README.md +135 -0
- package/dist/CompilerHostAdapter.d.ts +3 -0
- package/dist/CompilerHostAdapter.d.ts.map +1 -0
- package/dist/CompilerHostAdapter.js +160 -0
- package/dist/LanguageServiceAdapter.d.ts +3 -0
- package/dist/LanguageServiceAdapter.d.ts.map +1 -0
- package/dist/LanguageServiceAdapter.js +488 -0
- package/dist/LanguageServiceSession.d.ts +16 -0
- package/dist/LanguageServiceSession.d.ts.map +1 -0
- package/dist/LanguageServiceSession.js +122 -0
- package/dist/NodeModulePluginLoader.d.ts +8 -0
- package/dist/NodeModulePluginLoader.d.ts.map +1 -0
- package/dist/NodeModulePluginLoader.js +175 -0
- package/dist/PluginManager.d.ts +10 -0
- package/dist/PluginManager.d.ts.map +1 -0
- package/dist/PluginManager.js +151 -0
- package/dist/TypeInfoApi.d.ts +71 -0
- package/dist/TypeInfoApi.d.ts.map +1 -0
- package/dist/TypeInfoApi.js +855 -0
- package/dist/VmcConfigLoader.d.ts +31 -0
- package/dist/VmcConfigLoader.d.ts.map +1 -0
- package/dist/VmcConfigLoader.js +231 -0
- package/dist/VmcResolverLoader.d.ts +30 -0
- package/dist/VmcResolverLoader.d.ts.map +1 -0
- package/dist/VmcResolverLoader.js +71 -0
- package/dist/collectTypeTargetSpecs.d.ts +6 -0
- package/dist/collectTypeTargetSpecs.d.ts.map +1 -0
- package/dist/collectTypeTargetSpecs.js +19 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/internal/VirtualRecordStore.d.ts +60 -0
- package/dist/internal/VirtualRecordStore.d.ts.map +1 -0
- package/dist/internal/VirtualRecordStore.js +199 -0
- package/dist/internal/materializeVirtualFile.d.ts +12 -0
- package/dist/internal/materializeVirtualFile.d.ts.map +1 -0
- package/dist/internal/materializeVirtualFile.js +28 -0
- package/dist/internal/path.d.ts +40 -0
- package/dist/internal/path.d.ts.map +1 -0
- package/dist/internal/path.js +71 -0
- package/dist/internal/sanitize.d.ts +6 -0
- package/dist/internal/sanitize.d.ts.map +1 -0
- package/dist/internal/sanitize.js +15 -0
- package/dist/internal/tsInternal.d.ts +65 -0
- package/dist/internal/tsInternal.d.ts.map +1 -0
- package/dist/internal/tsInternal.js +99 -0
- package/dist/internal/validation.d.ts +28 -0
- package/dist/internal/validation.d.ts.map +1 -0
- package/dist/internal/validation.js +66 -0
- package/dist/typeTargetBootstrap.d.ts +33 -0
- package/dist/typeTargetBootstrap.d.ts.map +1 -0
- package/dist/typeTargetBootstrap.js +57 -0
- package/dist/types.d.ts +405 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +15 -0
- package/package.json +38 -0
- package/src/CompilerHostAdapter.test.ts +180 -0
- package/src/CompilerHostAdapter.ts +316 -0
- package/src/LanguageServiceAdapter.test.ts +521 -0
- package/src/LanguageServiceAdapter.ts +631 -0
- package/src/LanguageServiceSession.ts +160 -0
- package/src/LanguageServiceTester.integration.test.ts +268 -0
- package/src/NodeModulePluginLoader.test.ts +170 -0
- package/src/NodeModulePluginLoader.ts +268 -0
- package/src/PluginManager.test.ts +178 -0
- package/src/PluginManager.ts +218 -0
- package/src/TypeInfoApi.test.ts +1211 -0
- package/src/TypeInfoApi.ts +1228 -0
- package/src/VmcConfigLoader.test.ts +108 -0
- package/src/VmcConfigLoader.ts +297 -0
- package/src/VmcResolverLoader.test.ts +181 -0
- package/src/VmcResolverLoader.ts +116 -0
- package/src/collectTypeTargetSpecs.ts +22 -0
- package/src/index.ts +35 -0
- package/src/internal/VirtualRecordStore.ts +304 -0
- package/src/internal/materializeVirtualFile.ts +38 -0
- package/src/internal/path.ts +106 -0
- package/src/internal/sanitize.ts +16 -0
- package/src/internal/tsInternal.ts +127 -0
- package/src/internal/validation.ts +85 -0
- package/src/typeTargetBootstrap.ts +75 -0
- package/src/types.ts +535 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { runInThisContext } from "node:vm";
|
|
5
|
+
import { pathIsUnderBase } from "./internal/path.js";
|
|
6
|
+
import { sanitizeErrorMessage } from "./internal/sanitize.js";
|
|
7
|
+
const toMessage = (error) => {
|
|
8
|
+
if (error instanceof Error) {
|
|
9
|
+
return error.message;
|
|
10
|
+
}
|
|
11
|
+
return String(error);
|
|
12
|
+
};
|
|
13
|
+
const isPluginLike = (value) => {
|
|
14
|
+
if (!value || typeof value !== "object") {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const candidate = value;
|
|
18
|
+
return (typeof candidate.name === "string" &&
|
|
19
|
+
typeof candidate.shouldResolve === "function" &&
|
|
20
|
+
typeof candidate.build === "function");
|
|
21
|
+
};
|
|
22
|
+
const invalidPluginError = (request, message) => ({
|
|
23
|
+
status: "error",
|
|
24
|
+
specifier: request.specifier,
|
|
25
|
+
baseDir: request.baseDir,
|
|
26
|
+
code: "invalid-plugin-export",
|
|
27
|
+
message,
|
|
28
|
+
});
|
|
29
|
+
const loadFailedError = (request, message) => ({
|
|
30
|
+
status: "error",
|
|
31
|
+
specifier: request.specifier,
|
|
32
|
+
baseDir: request.baseDir,
|
|
33
|
+
code: "module-load-failed",
|
|
34
|
+
message,
|
|
35
|
+
});
|
|
36
|
+
const notFoundError = (request, message) => ({
|
|
37
|
+
status: "error",
|
|
38
|
+
specifier: request.specifier,
|
|
39
|
+
baseDir: request.baseDir,
|
|
40
|
+
code: "module-not-found",
|
|
41
|
+
message,
|
|
42
|
+
});
|
|
43
|
+
const pathEscapesError = (request, message) => ({
|
|
44
|
+
status: "error",
|
|
45
|
+
specifier: request.specifier,
|
|
46
|
+
baseDir: request.baseDir,
|
|
47
|
+
code: "path-escapes-base",
|
|
48
|
+
message,
|
|
49
|
+
});
|
|
50
|
+
const MAX_PATH_LENGTH = 4096;
|
|
51
|
+
const invalidRequestError = (message) => ({
|
|
52
|
+
status: "error",
|
|
53
|
+
specifier: "",
|
|
54
|
+
baseDir: "",
|
|
55
|
+
code: "invalid-request",
|
|
56
|
+
message,
|
|
57
|
+
});
|
|
58
|
+
const getErrorCode = (error) => typeof error === "object" && error !== null && "code" in error
|
|
59
|
+
? String(error.code)
|
|
60
|
+
: undefined;
|
|
61
|
+
export class NodeModulePluginLoader {
|
|
62
|
+
load(input) {
|
|
63
|
+
if (isPluginLike(input)) {
|
|
64
|
+
return {
|
|
65
|
+
status: "loaded",
|
|
66
|
+
plugin: input,
|
|
67
|
+
resolvedPath: "<preloaded>",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const request = input;
|
|
71
|
+
if (typeof request.baseDir !== "string" || request.baseDir.trim() === "") {
|
|
72
|
+
return invalidRequestError("baseDir must be a non-empty string");
|
|
73
|
+
}
|
|
74
|
+
if (typeof request.specifier !== "string" || request.specifier.trim() === "") {
|
|
75
|
+
return invalidRequestError("specifier must be a non-empty string");
|
|
76
|
+
}
|
|
77
|
+
if (request.baseDir.length > MAX_PATH_LENGTH || request.specifier.length > MAX_PATH_LENGTH) {
|
|
78
|
+
return invalidRequestError(`baseDir and specifier must be at most ${MAX_PATH_LENGTH} characters`);
|
|
79
|
+
}
|
|
80
|
+
const require = createRequire(resolve(request.baseDir, "__typed_virtual_modules_loader__.cjs"));
|
|
81
|
+
let resolvedPath;
|
|
82
|
+
try {
|
|
83
|
+
resolvedPath = require.resolve(request.specifier, { paths: [request.baseDir] });
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
return notFoundError(request, `Could not resolve plugin "${request.specifier}" from "${request.baseDir}": ${sanitizeErrorMessage(toMessage(error))}`);
|
|
87
|
+
}
|
|
88
|
+
if (!pathIsUnderBase(request.baseDir, resolvedPath)) {
|
|
89
|
+
return pathEscapesError(request, sanitizeErrorMessage(`Resolved plugin path "${resolvedPath}" is not under baseDir "${request.baseDir}"`));
|
|
90
|
+
}
|
|
91
|
+
let mod;
|
|
92
|
+
try {
|
|
93
|
+
mod = require(resolvedPath);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
const errorCode = getErrorCode(error);
|
|
97
|
+
if (errorCode === "ERR_REQUIRE_ASYNC_MODULE") {
|
|
98
|
+
return loadFailedError(request, sanitizeErrorMessage(`Could not load plugin module "${resolvedPath}": plugin module uses top-level await and cannot be loaded synchronously`));
|
|
99
|
+
}
|
|
100
|
+
if (errorCode === "ERR_REQUIRE_ESM") {
|
|
101
|
+
const esmFallback = this.#loadSyncEsmModule(resolvedPath, require);
|
|
102
|
+
if (esmFallback.status === "loaded") {
|
|
103
|
+
mod = esmFallback.moduleExport;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
return loadFailedError(request, esmFallback.message);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
return loadFailedError(request, `Could not load plugin module "${resolvedPath}": ${sanitizeErrorMessage(toMessage(error))}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const normalizedPlugin = this.#normalizeModuleExport(mod);
|
|
114
|
+
if (!normalizedPlugin) {
|
|
115
|
+
return invalidPluginError(request, sanitizeErrorMessage(`Resolved module "${resolvedPath}" does not export a valid VirtualModulePlugin`));
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
status: "loaded",
|
|
119
|
+
plugin: normalizedPlugin,
|
|
120
|
+
resolvedPath,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
loadMany(inputs) {
|
|
124
|
+
return inputs.map((input) => this.load(input));
|
|
125
|
+
}
|
|
126
|
+
#loadSyncEsmModule(resolvedPath, localRequire) {
|
|
127
|
+
let tsMod;
|
|
128
|
+
try {
|
|
129
|
+
tsMod = localRequire("typescript");
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
return loadFailedError({ specifier: resolvedPath, baseDir: dirname(resolvedPath) }, sanitizeErrorMessage(`Could not load sync ESM plugin "${resolvedPath}": failed to load TypeScript for transpilation: ${toMessage(error)}`));
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
const sourceText = readFileSync(resolvedPath, "utf8");
|
|
136
|
+
const transpiled = tsMod.transpileModule(sourceText, {
|
|
137
|
+
fileName: resolvedPath,
|
|
138
|
+
compilerOptions: {
|
|
139
|
+
module: tsMod.ModuleKind.CommonJS,
|
|
140
|
+
target: tsMod.ScriptTarget.ES2020,
|
|
141
|
+
moduleResolution: tsMod.ModuleResolutionKind.NodeNext,
|
|
142
|
+
esModuleInterop: true,
|
|
143
|
+
allowSyntheticDefaultImports: true,
|
|
144
|
+
},
|
|
145
|
+
reportDiagnostics: false,
|
|
146
|
+
}).outputText;
|
|
147
|
+
const module = { exports: {} };
|
|
148
|
+
const evaluate = runInThisContext(`(function (exports, require, module, __filename, __dirname) {${transpiled}\n})`, { filename: resolvedPath });
|
|
149
|
+
evaluate(module.exports, localRequire, module, resolvedPath, dirname(resolvedPath));
|
|
150
|
+
return { status: "loaded", moduleExport: module.exports };
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
return {
|
|
154
|
+
status: "error",
|
|
155
|
+
message: sanitizeErrorMessage(`Could not load sync ESM plugin "${resolvedPath}": ${toMessage(error)}`),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
#normalizeModuleExport(mod) {
|
|
160
|
+
if (isPluginLike(mod)) {
|
|
161
|
+
return mod;
|
|
162
|
+
}
|
|
163
|
+
if (!mod || typeof mod !== "object") {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
const candidate = mod;
|
|
167
|
+
if (isPluginLike(candidate.default)) {
|
|
168
|
+
return candidate.default;
|
|
169
|
+
}
|
|
170
|
+
if (isPluginLike(candidate.plugin)) {
|
|
171
|
+
return candidate.plugin;
|
|
172
|
+
}
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ResolveVirtualModuleOptions, VirtualModulePlugin, VirtualModuleResolution, VirtualModuleResolver } from "./types.js";
|
|
2
|
+
export declare class PluginManager implements VirtualModuleResolver {
|
|
3
|
+
#private;
|
|
4
|
+
constructor(plugins?: readonly VirtualModulePlugin[]);
|
|
5
|
+
get plugins(): readonly VirtualModulePlugin[];
|
|
6
|
+
register(plugin: VirtualModulePlugin): void;
|
|
7
|
+
registerMany(plugins: readonly VirtualModulePlugin[]): void;
|
|
8
|
+
resolveModule(options: ResolveVirtualModuleOptions): VirtualModuleResolution;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=PluginManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PluginManager.d.ts","sourceRoot":"","sources":["../src/PluginManager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,2BAA2B,EAK3B,mBAAmB,EACnB,uBAAuB,EACvB,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAwCpB,qBAAa,aAAc,YAAW,qBAAqB;;gBAG7C,OAAO,GAAE,SAAS,mBAAmB,EAAO;IAIxD,IAAI,OAAO,IAAI,SAAS,mBAAmB,EAAE,CAE5C;IAED,QAAQ,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI;IAI3C,YAAY,CAAC,OAAO,EAAE,SAAS,mBAAmB,EAAE,GAAG,IAAI;IAM3D,aAAa,CAAC,OAAO,EAAE,2BAA2B,GAAG,uBAAuB;CAiJ7E"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { sanitizeErrorMessage } from "./internal/sanitize.js";
|
|
2
|
+
import { validateNonEmptyString } from "./internal/validation.js";
|
|
3
|
+
import { isVirtualModuleBuildError, isVirtualModuleBuildSuccess, } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* TypeInfoApi used when createTypeInfoApiSession is not provided.
|
|
6
|
+
* Returns safe defaults instead of throwing.
|
|
7
|
+
* Hosts should always supply createTypeInfoApiSession when plugins use api.file(), api.directory(), or api.resolveExport() for correct behavior.
|
|
8
|
+
*/
|
|
9
|
+
const noopTypeInfoApi = {
|
|
10
|
+
file: () => ({ ok: false, error: "invalid-input" }),
|
|
11
|
+
directory: () => [],
|
|
12
|
+
resolveExport: () => undefined,
|
|
13
|
+
isAssignableTo: () => false,
|
|
14
|
+
};
|
|
15
|
+
const noopSession = {
|
|
16
|
+
api: noopTypeInfoApi,
|
|
17
|
+
consumeDependencies: () => [],
|
|
18
|
+
};
|
|
19
|
+
const toMessage = (error) => {
|
|
20
|
+
if (error instanceof Error) {
|
|
21
|
+
return error.message;
|
|
22
|
+
}
|
|
23
|
+
return String(error);
|
|
24
|
+
};
|
|
25
|
+
const createDiagnostic = (code, pluginName, message) => ({
|
|
26
|
+
code,
|
|
27
|
+
pluginName,
|
|
28
|
+
message,
|
|
29
|
+
});
|
|
30
|
+
export class PluginManager {
|
|
31
|
+
#plugins;
|
|
32
|
+
constructor(plugins = []) {
|
|
33
|
+
this.#plugins = [...plugins];
|
|
34
|
+
}
|
|
35
|
+
get plugins() {
|
|
36
|
+
return this.#plugins;
|
|
37
|
+
}
|
|
38
|
+
register(plugin) {
|
|
39
|
+
this.#plugins.push(plugin);
|
|
40
|
+
}
|
|
41
|
+
registerMany(plugins) {
|
|
42
|
+
for (const plugin of plugins) {
|
|
43
|
+
this.register(plugin);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
resolveModule(options) {
|
|
47
|
+
const idResult = validateNonEmptyString(options.id, "options.id");
|
|
48
|
+
if (!idResult.ok) {
|
|
49
|
+
return {
|
|
50
|
+
status: "error",
|
|
51
|
+
diagnostic: createDiagnostic("invalid-options", "", idResult.reason),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const importerResult = validateNonEmptyString(options.importer, "options.importer");
|
|
55
|
+
if (!importerResult.ok) {
|
|
56
|
+
return {
|
|
57
|
+
status: "error",
|
|
58
|
+
diagnostic: createDiagnostic("invalid-options", "", importerResult.reason),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const createSession = options.createTypeInfoApiSession;
|
|
62
|
+
for (const plugin of this.#plugins) {
|
|
63
|
+
const nameResult = validateNonEmptyString(plugin.name, "Plugin name");
|
|
64
|
+
if (!nameResult.ok) {
|
|
65
|
+
return {
|
|
66
|
+
status: "error",
|
|
67
|
+
diagnostic: createDiagnostic("invalid-options", "", nameResult.reason),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const shouldResolve = this.#safeShouldResolve(plugin, options.id, options.importer);
|
|
71
|
+
if (shouldResolve.status === "error") {
|
|
72
|
+
return shouldResolve;
|
|
73
|
+
}
|
|
74
|
+
if (!shouldResolve.value) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
let session;
|
|
78
|
+
try {
|
|
79
|
+
session = createSession?.({ id: options.id, importer: options.importer }) ?? noopSession;
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
const msg = toMessage(error);
|
|
83
|
+
// Treat temporary unavailability (e.g. program not loaded) as unresolved so retry can succeed later.
|
|
84
|
+
if (msg.includes("Program not yet available") ||
|
|
85
|
+
msg.includes("TypeInfo session creation failed")) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
status: "error",
|
|
90
|
+
diagnostic: createDiagnostic("session-creation-failed", plugin.name, `Session creation failed: ${sanitizeErrorMessage(msg)}`),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const result = plugin.build(options.id, options.importer, session.api);
|
|
95
|
+
if (typeof result === "string") {
|
|
96
|
+
return {
|
|
97
|
+
status: "resolved",
|
|
98
|
+
pluginName: plugin.name,
|
|
99
|
+
sourceText: result,
|
|
100
|
+
dependencies: session.consumeDependencies(),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (isVirtualModuleBuildError(result)) {
|
|
104
|
+
const first = result.errors[0];
|
|
105
|
+
const diagnostic = first &&
|
|
106
|
+
typeof first === "object" &&
|
|
107
|
+
typeof first.code === "string" &&
|
|
108
|
+
typeof first.message === "string" &&
|
|
109
|
+
typeof first.pluginName === "string"
|
|
110
|
+
? first
|
|
111
|
+
: createDiagnostic("invalid-build-output", plugin.name, `Plugin "${plugin.name}" returned errors with invalid diagnostic shape`);
|
|
112
|
+
return { status: "error", diagnostic };
|
|
113
|
+
}
|
|
114
|
+
if (isVirtualModuleBuildSuccess(result)) {
|
|
115
|
+
return {
|
|
116
|
+
status: "resolved",
|
|
117
|
+
pluginName: plugin.name,
|
|
118
|
+
sourceText: result.sourceText,
|
|
119
|
+
dependencies: session.consumeDependencies(),
|
|
120
|
+
...(result.warnings?.length ? { warnings: result.warnings } : {}),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
status: "error",
|
|
125
|
+
diagnostic: createDiagnostic("invalid-build-output", plugin.name, `Plugin "${plugin.name}" returned a non-string build result`),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
return {
|
|
130
|
+
status: "error",
|
|
131
|
+
diagnostic: createDiagnostic("plugin-build-threw", plugin.name, `Plugin "${plugin.name}" build failed: ${sanitizeErrorMessage(toMessage(error))}`),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return { status: "unresolved" };
|
|
136
|
+
}
|
|
137
|
+
#safeShouldResolve(plugin, id, importer) {
|
|
138
|
+
try {
|
|
139
|
+
return {
|
|
140
|
+
status: "ok",
|
|
141
|
+
value: plugin.shouldResolve(id, importer),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
return {
|
|
146
|
+
status: "error",
|
|
147
|
+
diagnostic: createDiagnostic("plugin-should-resolve-threw", plugin.name, `Plugin "${plugin.name}" shouldResolve failed: ${sanitizeErrorMessage(toMessage(error))}`),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type * as ts from "typescript";
|
|
2
|
+
import { type CreateTypeInfoApiSession, type TypeInfoApiSession } from "./types.js";
|
|
3
|
+
import type { TypeNode, TypeTargetSpec } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Pre-resolved type for structural assignability checks.
|
|
6
|
+
* Host resolves ts.Type from the program (e.g. Fx from @typed/fx) and passes it.
|
|
7
|
+
*/
|
|
8
|
+
export interface ResolvedTypeTarget {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
readonly type: ts.Type;
|
|
11
|
+
}
|
|
12
|
+
export interface CreateTypeInfoApiSessionOptions {
|
|
13
|
+
readonly ts: typeof import("typescript");
|
|
14
|
+
readonly program: ts.Program;
|
|
15
|
+
readonly maxTypeDepth?: number;
|
|
16
|
+
/** Pre-resolved types for assignability checks. Takes precedence over typeTargetSpecs when both exist. */
|
|
17
|
+
readonly typeTargets?: readonly ResolvedTypeTarget[];
|
|
18
|
+
/**
|
|
19
|
+
* Specs to resolve from program imports for assignability checks.
|
|
20
|
+
* Resolution happens internally; use this instead of typeTargets when you have module+export specs.
|
|
21
|
+
*/
|
|
22
|
+
readonly typeTargetSpecs?: readonly TypeTargetSpec[];
|
|
23
|
+
/**
|
|
24
|
+
* When true (default) and typeTargetSpecs are provided but resolution finds zero targets,
|
|
25
|
+
* session creation throws. Set false to allow empty resolution (assignableTo will be undefined for all exports).
|
|
26
|
+
*/
|
|
27
|
+
readonly failWhenNoTargetsResolved?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Assignability check mode.
|
|
30
|
+
* - "strict": Use only checker.isTypeAssignableTo; no heuristic fallbacks. Correct TS semantics.
|
|
31
|
+
* - "compatibility" (default): Use checker.isTypeAssignableTo first, then fallbacks for generic/inheritance
|
|
32
|
+
* when types come from different files (e.g. fixture vs bootstrap). Union sources require ALL
|
|
33
|
+
* constituents to be assignable (sound union semantics).
|
|
34
|
+
*/
|
|
35
|
+
readonly assignabilityMode?: "strict" | "compatibility";
|
|
36
|
+
/**
|
|
37
|
+
* Optional callback when an internal TS API or fallback path catches an error.
|
|
38
|
+
* Assignability and serialization may degrade (e.g. "not assignable" or safe fallback) without throwing.
|
|
39
|
+
* Use for observability in production; default behavior is unchanged when not provided.
|
|
40
|
+
*/
|
|
41
|
+
readonly onInternalError?: (err: unknown, context: string) => void;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Serialize a TypeScript type to TypeNode. For testing serializer branches (typeOperator, enum, etc).
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
export declare function serializeTypeForTest(type: ts.Type, checker: ts.TypeChecker, tsMod: typeof import("typescript"), maxDepth?: number): TypeNode;
|
|
48
|
+
/**
|
|
49
|
+
* Generate bootstrap file content that imports all modules from typeTargetSpecs.
|
|
50
|
+
* Include this file in the program's rootNames so resolveTypeTargetsFromSpecs can
|
|
51
|
+
* find types without requiring user source to import them.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* const bootstrapContent = createTypeTargetBootstrapContent(HTTPAPI_TYPE_TARGET_SPECS);
|
|
56
|
+
* const bootstrapPath = join(tmpDir, "__typeTargetBootstrap.ts");
|
|
57
|
+
* writeFileSync(bootstrapPath, bootstrapContent);
|
|
58
|
+
* const program = ts.createProgram([...rootFiles, bootstrapPath], options, host);
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare function createTypeTargetBootstrapContent(specs: readonly TypeTargetSpec[]): string;
|
|
62
|
+
/**
|
|
63
|
+
* Resolve type targets from program imports using the given specs.
|
|
64
|
+
* Scans source files for imports from the specified modules and extracts types.
|
|
65
|
+
* Requires the program to include files that import from these modules (e.g.
|
|
66
|
+
* use createTypeTargetBootstrapContent and add to rootNames).
|
|
67
|
+
*/
|
|
68
|
+
export declare function resolveTypeTargetsFromSpecs(program: ts.Program, tsMod: typeof import("typescript"), specs: readonly TypeTargetSpec[]): ResolvedTypeTarget[];
|
|
69
|
+
export declare const createTypeInfoApiSession: (options: CreateTypeInfoApiSessionOptions) => TypeInfoApiSession;
|
|
70
|
+
export declare const createTypeInfoApiSessionFactory: (options: CreateTypeInfoApiSessionOptions) => CreateTypeInfoApiSession;
|
|
71
|
+
//# sourceMappingURL=TypeInfoApi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TypeInfoApi.d.ts","sourceRoot":"","sources":["../src/TypeInfoApi.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EACL,KAAK,wBAAwB,EAI7B,KAAK,kBAAkB,EAKxB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAsBV,QAAQ,EAGR,cAAc,EAGf,MAAM,YAAY,CAAC;AAsBpB;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,EAAE,EAAE,cAAc,YAAY,CAAC,CAAC;IACzC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC;IAC7B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,0GAA0G;IAC1G,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACrD;;;OAGG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACrD;;;OAGG;IACH,QAAQ,CAAC,yBAAyB,CAAC,EAAE,OAAO,CAAC;IAC7C;;;;;;OAMG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,QAAQ,GAAG,eAAe,CAAC;IACxD;;;;OAIG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpE;AAkbD;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,OAAO,EAAE,EAAE,CAAC,WAAW,EACvB,KAAK,EAAE,cAAc,YAAY,CAAC,EAClC,QAAQ,SAAI,GACX,QAAQ,CAEV;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,SAAS,cAAc,EAAE,GAC/B,MAAM,CAkBR;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,EAAE,CAAC,OAAO,EACnB,KAAK,EAAE,cAAc,YAAY,CAAC,EAClC,KAAK,EAAE,SAAS,cAAc,EAAE,GAC/B,kBAAkB,EAAE,CAsHtB;AA0RD,eAAO,MAAM,wBAAwB,GACnC,SAAS,+BAA+B,KACvC,kBAkOF,CAAC;AAEF,eAAO,MAAM,+BAA+B,GACzC,SAAS,+BAA+B,KAAG,wBAET,CAAC"}
|