tywrap 0.2.0 → 0.3.0
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 +65 -31
- package/dist/cli.js +49 -2
- package/dist/cli.js.map +1 -1
- package/dist/config/index.d.ts +11 -7
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +43 -7
- package/dist/config/index.js.map +1 -1
- package/dist/core/analyzer.d.ts +1 -0
- package/dist/core/analyzer.d.ts.map +1 -1
- package/dist/core/analyzer.js +47 -12
- package/dist/core/analyzer.js.map +1 -1
- package/dist/core/annotation-parser.d.ts +8 -0
- package/dist/core/annotation-parser.d.ts.map +1 -0
- package/dist/core/annotation-parser.js +409 -0
- package/dist/core/annotation-parser.js.map +1 -0
- package/dist/core/discovery.d.ts +1 -0
- package/dist/core/discovery.d.ts.map +1 -1
- package/dist/core/discovery.js +9 -9
- package/dist/core/discovery.js.map +1 -1
- package/dist/core/generator.d.ts +9 -1
- package/dist/core/generator.d.ts.map +1 -1
- package/dist/core/generator.js +738 -104
- package/dist/core/generator.js.map +1 -1
- package/dist/core/mapper.d.ts +9 -7
- package/dist/core/mapper.d.ts.map +1 -1
- package/dist/core/mapper.js +129 -44
- package/dist/core/mapper.js.map +1 -1
- package/dist/index.d.ts +23 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -3
- package/dist/index.js.map +1 -1
- package/dist/runtime/base.d.ts +17 -7
- package/dist/runtime/base.d.ts.map +1 -1
- package/dist/runtime/base.js +18 -1
- package/dist/runtime/base.js.map +1 -1
- package/dist/runtime/bounded-context.d.ts +252 -0
- package/dist/runtime/bounded-context.d.ts.map +1 -0
- package/dist/runtime/bounded-context.js +454 -0
- package/dist/runtime/bounded-context.js.map +1 -0
- package/dist/runtime/bridge-core.d.ts +1 -0
- package/dist/runtime/bridge-core.d.ts.map +1 -1
- package/dist/runtime/bridge-core.js +52 -17
- package/dist/runtime/bridge-core.js.map +1 -1
- package/dist/runtime/bridge-protocol.d.ts +184 -0
- package/dist/runtime/bridge-protocol.d.ts.map +1 -0
- package/dist/runtime/bridge-protocol.js +344 -0
- package/dist/runtime/bridge-protocol.js.map +1 -0
- package/dist/runtime/disposable.d.ts +40 -0
- package/dist/runtime/disposable.d.ts.map +1 -0
- package/dist/runtime/disposable.js +49 -0
- package/dist/runtime/disposable.js.map +1 -0
- package/dist/runtime/errors.d.ts +10 -0
- package/dist/runtime/errors.d.ts.map +1 -1
- package/dist/runtime/errors.js +9 -0
- package/dist/runtime/errors.js.map +1 -1
- package/dist/runtime/http-io.d.ts +91 -0
- package/dist/runtime/http-io.d.ts.map +1 -0
- package/dist/runtime/http-io.js +195 -0
- package/dist/runtime/http-io.js.map +1 -0
- package/dist/runtime/http.d.ts +48 -13
- package/dist/runtime/http.d.ts.map +1 -1
- package/dist/runtime/http.js +60 -73
- package/dist/runtime/http.js.map +1 -1
- package/dist/runtime/node.d.ts +97 -130
- package/dist/runtime/node.d.ts.map +1 -1
- package/dist/runtime/node.js +392 -521
- package/dist/runtime/node.js.map +1 -1
- package/dist/runtime/optimized-node.d.ts +1 -1
- package/dist/runtime/optimized-node.d.ts.map +1 -1
- package/dist/runtime/optimized-node.js +1 -1
- package/dist/runtime/optimized-node.js.map +1 -1
- package/dist/runtime/pooled-transport.d.ts +135 -0
- package/dist/runtime/pooled-transport.d.ts.map +1 -0
- package/dist/runtime/pooled-transport.js +177 -0
- package/dist/runtime/pooled-transport.js.map +1 -0
- package/dist/runtime/process-io.d.ts +209 -0
- package/dist/runtime/process-io.d.ts.map +1 -0
- package/dist/runtime/process-io.js +729 -0
- package/dist/runtime/process-io.js.map +1 -0
- package/dist/runtime/pyodide-io.d.ts +154 -0
- package/dist/runtime/pyodide-io.d.ts.map +1 -0
- package/dist/runtime/pyodide-io.js +459 -0
- package/dist/runtime/pyodide-io.js.map +1 -0
- package/dist/runtime/pyodide.d.ts +51 -19
- package/dist/runtime/pyodide.d.ts.map +1 -1
- package/dist/runtime/pyodide.js +57 -186
- package/dist/runtime/pyodide.js.map +1 -1
- package/dist/runtime/safe-codec.d.ts +103 -0
- package/dist/runtime/safe-codec.d.ts.map +1 -0
- package/dist/runtime/safe-codec.js +519 -0
- package/dist/runtime/safe-codec.js.map +1 -0
- package/dist/runtime/transport.d.ts +191 -0
- package/dist/runtime/transport.d.ts.map +1 -0
- package/dist/runtime/transport.js +86 -0
- package/dist/runtime/transport.js.map +1 -0
- package/dist/runtime/validators.d.ts +120 -0
- package/dist/runtime/validators.d.ts.map +1 -0
- package/dist/runtime/validators.js +229 -0
- package/dist/runtime/validators.js.map +1 -0
- package/dist/runtime/worker-pool.d.ts +208 -0
- package/dist/runtime/worker-pool.d.ts.map +1 -0
- package/dist/runtime/worker-pool.js +419 -0
- package/dist/runtime/worker-pool.js.map +1 -0
- package/dist/types/index.d.ts +51 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/tywrap.d.ts.map +1 -1
- package/dist/tywrap.js +227 -328
- package/dist/tywrap.js.map +1 -1
- package/dist/utils/cache.d.ts.map +1 -1
- package/dist/utils/cache.js +53 -5
- package/dist/utils/cache.js.map +1 -1
- package/dist/utils/codec.d.ts.map +1 -1
- package/dist/utils/codec.js +114 -1
- package/dist/utils/codec.js.map +1 -1
- package/dist/utils/ir-cache.d.ts +28 -0
- package/dist/utils/ir-cache.d.ts.map +1 -0
- package/dist/utils/ir-cache.js +29 -0
- package/dist/utils/ir-cache.js.map +1 -0
- package/dist/utils/parallel-processor.d.ts +1 -1
- package/dist/utils/parallel-processor.d.ts.map +1 -1
- package/dist/utils/parallel-processor.js +111 -40
- package/dist/utils/parallel-processor.js.map +1 -1
- package/dist/utils/runtime.d.ts +5 -1
- package/dist/utils/runtime.d.ts.map +1 -1
- package/dist/utils/runtime.js +112 -18
- package/dist/utils/runtime.js.map +1 -1
- package/package.json +40 -18
- package/runtime/python_bridge.py +108 -15
- package/runtime/safe_codec.py +344 -0
- package/src/cli.ts +61 -2
- package/src/config/index.ts +63 -17
- package/src/core/analyzer.ts +52 -15
- package/src/core/annotation-parser.ts +500 -0
- package/src/core/discovery.ts +26 -12
- package/src/core/generator.ts +956 -116
- package/src/core/mapper.ts +158 -51
- package/src/index.ts +78 -6
- package/src/runtime/base.ts +18 -26
- package/src/runtime/bounded-context.ts +608 -0
- package/src/runtime/bridge-core.ts +58 -18
- package/src/runtime/bridge-protocol.ts +483 -0
- package/src/runtime/disposable.ts +65 -0
- package/src/runtime/errors.ts +14 -0
- package/src/runtime/http-io.ts +245 -0
- package/src/runtime/http.ts +76 -115
- package/src/runtime/node.ts +563 -676
- package/src/runtime/optimized-node.ts +4 -1
- package/src/runtime/pooled-transport.ts +263 -0
- package/src/runtime/process-io.ts +930 -0
- package/src/runtime/pyodide-io.ts +551 -0
- package/src/runtime/pyodide.ts +75 -215
- package/src/runtime/safe-codec.ts +651 -0
- package/src/runtime/transport.ts +278 -0
- package/src/runtime/validators.ts +252 -0
- package/src/runtime/worker-pool.ts +559 -0
- package/src/types/global.d.ts +11 -1
- package/src/types/index.ts +63 -0
- package/src/tywrap.ts +279 -358
- package/src/utils/cache.ts +59 -5
- package/src/utils/codec.ts +122 -1
- package/src/utils/ir-cache.ts +51 -0
- package/src/utils/parallel-processor.ts +119 -43
- package/src/utils/runtime.ts +129 -18
package/src/tywrap.ts
CHANGED
|
@@ -4,19 +4,25 @@
|
|
|
4
4
|
|
|
5
5
|
import { CodeGenerator } from './core/generator.js';
|
|
6
6
|
import { TypeMapper } from './core/mapper.js';
|
|
7
|
+
import { parseAnnotationToPythonType } from './core/annotation-parser.js';
|
|
7
8
|
import { createConfig } from './config/index.js';
|
|
8
9
|
import type {
|
|
9
10
|
TywrapOptions,
|
|
11
|
+
PythonGenericParameter,
|
|
10
12
|
PythonFunction,
|
|
11
13
|
PythonModule as TSPythonModule,
|
|
12
14
|
PythonClass,
|
|
15
|
+
PythonTypeAlias,
|
|
13
16
|
Parameter,
|
|
14
17
|
PythonType,
|
|
15
18
|
} from './types/index.js';
|
|
16
|
-
import { fsUtils, pathUtils, processUtils,
|
|
19
|
+
import { fsUtils, pathUtils, processUtils, isWindows } from './utils/runtime.js';
|
|
17
20
|
import { globalCache } from './utils/cache.js';
|
|
18
21
|
import { globalParallelProcessor } from './utils/parallel-processor.js';
|
|
19
22
|
import { resolvePythonExecutable } from './utils/python.js';
|
|
23
|
+
import { computeIrCacheFilename } from './utils/ir-cache.js';
|
|
24
|
+
|
|
25
|
+
const TYWRAP_IR_VERSION = '0.2.0';
|
|
20
26
|
|
|
21
27
|
// Collect unknown typing constructs encountered during annotation parsing (per-generate run)
|
|
22
28
|
let unknownTypeNamesCollector: Map<string, number> = new Map();
|
|
@@ -138,13 +144,14 @@ export async function generate(
|
|
|
138
144
|
const modules = resolvedOptions.pythonModules ?? {};
|
|
139
145
|
for (const entry of Object.entries(modules)) {
|
|
140
146
|
const moduleKey = entry[0];
|
|
147
|
+
const moduleConfig = entry[1];
|
|
141
148
|
// reset collector for each module
|
|
142
149
|
unknownTypeNamesCollector = new Map();
|
|
143
150
|
// Prefer Python IR extractor over TS analyzer with optional cache
|
|
144
|
-
const cacheKey = await computeCacheKey(moduleKey,
|
|
151
|
+
const cacheKey = await computeCacheKey(moduleKey, resolvedOptions);
|
|
145
152
|
let ir: unknown | null = null;
|
|
146
153
|
let irError: string | undefined;
|
|
147
|
-
if (caching && fsUtils.isAvailable()) {
|
|
154
|
+
if (caching && fsUtils.isAvailable() && !checkMode) {
|
|
148
155
|
try {
|
|
149
156
|
const cached = await fsUtils.readFile(pathUtils.join(cacheDir, cacheKey));
|
|
150
157
|
ir = JSON.parse(cached);
|
|
@@ -153,7 +160,10 @@ export async function generate(
|
|
|
153
160
|
}
|
|
154
161
|
}
|
|
155
162
|
if (!ir) {
|
|
156
|
-
const fetchResult = await fetchPythonIr(moduleKey, pythonPath
|
|
163
|
+
const fetchResult = await fetchPythonIr(moduleKey, pythonPath, {
|
|
164
|
+
timeoutMs: resolvedOptions.runtime?.node?.timeout,
|
|
165
|
+
pythonImportPath: resolvedOptions.pythonImportPath,
|
|
166
|
+
});
|
|
157
167
|
ir = fetchResult.ir;
|
|
158
168
|
irError = fetchResult.error;
|
|
159
169
|
if (ir && caching && fsUtils.isAvailable() && !checkMode) {
|
|
@@ -169,6 +179,78 @@ export async function generate(
|
|
|
169
179
|
|
|
170
180
|
const moduleModel = transformIrToTsModel(ir);
|
|
171
181
|
|
|
182
|
+
// Apply module-level export filtering (functions/classes + excludes).
|
|
183
|
+
{
|
|
184
|
+
const builtInDefaultExcludes = new Set([
|
|
185
|
+
'dataclass',
|
|
186
|
+
'property',
|
|
187
|
+
'staticmethod',
|
|
188
|
+
'classmethod',
|
|
189
|
+
'abstractmethod',
|
|
190
|
+
'cached_property',
|
|
191
|
+
]);
|
|
192
|
+
|
|
193
|
+
const excludeExact = new Set((moduleConfig.exclude ?? []).map(String));
|
|
194
|
+
const excludeRegexes: RegExp[] = [];
|
|
195
|
+
for (const pattern of moduleConfig.excludePatterns ?? []) {
|
|
196
|
+
try {
|
|
197
|
+
excludeRegexes.push(new RegExp(String(pattern)));
|
|
198
|
+
} catch (err: unknown) {
|
|
199
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
200
|
+
warnings.push(
|
|
201
|
+
`Module ${moduleKey}: invalid excludePatterns regex "${String(pattern)}": ${message}`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const shouldExclude = (name: string, applyBuiltInDefaults: boolean): boolean => {
|
|
207
|
+
if (excludeExact.has(name)) {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
if (excludeRegexes.some(r => r.test(name))) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
if (applyBuiltInDefaults && builtInDefaultExcludes.has(name)) {
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
return false;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
if (Array.isArray(moduleConfig.functions)) {
|
|
220
|
+
const allow = new Set(moduleConfig.functions.map(String));
|
|
221
|
+
for (const requested of allow) {
|
|
222
|
+
if (!moduleModel.functions.some(f => f.name === requested)) {
|
|
223
|
+
warnings.push(
|
|
224
|
+
`Module ${moduleKey}: configured function "${requested}" not found in IR`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
moduleModel.functions = moduleModel.functions.filter(
|
|
229
|
+
f => allow.has(f.name) && !shouldExclude(f.name, false)
|
|
230
|
+
);
|
|
231
|
+
} else {
|
|
232
|
+
moduleModel.functions = moduleModel.functions.filter(f => !shouldExclude(f.name, true));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (Array.isArray(moduleConfig.classes)) {
|
|
236
|
+
const allow = new Set(moduleConfig.classes.map(String));
|
|
237
|
+
for (const requested of allow) {
|
|
238
|
+
if (!moduleModel.classes.some(c => c.name === requested)) {
|
|
239
|
+
warnings.push(`Module ${moduleKey}: configured class "${requested}" not found in IR`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
moduleModel.classes = moduleModel.classes.filter(
|
|
243
|
+
c => allow.has(c.name) && !shouldExclude(c.name, false)
|
|
244
|
+
);
|
|
245
|
+
} else {
|
|
246
|
+
moduleModel.classes = moduleModel.classes.filter(c => !shouldExclude(c.name, true));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
moduleModel.typeAliases = (moduleModel.typeAliases ?? []).filter(
|
|
250
|
+
alias => !shouldExclude(alias.name, false)
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
172
254
|
// Generate module code
|
|
173
255
|
const annotatedJSDoc = Boolean(resolvedOptions.output?.annotatedJSDoc);
|
|
174
256
|
const gen = instance.generator.generateModuleDefinition(moduleModel, annotatedJSDoc);
|
|
@@ -182,7 +264,7 @@ export async function generate(
|
|
|
182
264
|
if (resolvedOptions.output?.declaration) {
|
|
183
265
|
filesToEmit.push({
|
|
184
266
|
path: pathUtils.join(outputDir, `${baseName}.generated.d.ts`),
|
|
185
|
-
content:
|
|
267
|
+
content: gen.declaration,
|
|
186
268
|
});
|
|
187
269
|
}
|
|
188
270
|
|
|
@@ -253,19 +335,28 @@ export async function generate(
|
|
|
253
335
|
*/
|
|
254
336
|
async function fetchPythonIr(
|
|
255
337
|
moduleName: string,
|
|
256
|
-
pythonPath: string
|
|
338
|
+
pythonPath: string,
|
|
339
|
+
options: { timeoutMs?: number; pythonImportPath?: string[] } = {}
|
|
257
340
|
): Promise<{ ir: unknown | null; error?: string }> {
|
|
258
341
|
if (!processUtils.isAvailable()) {
|
|
259
342
|
return { ir: null, error: 'Subprocess operations not available in this runtime' };
|
|
260
343
|
}
|
|
344
|
+
const delimiter = isWindows() ? ';' : ':';
|
|
345
|
+
const extraPaths = (options.pythonImportPath ?? []).filter(Boolean);
|
|
346
|
+
const existingPyPath =
|
|
347
|
+
typeof process !== 'undefined' && typeof process.env === 'object' && process.env
|
|
348
|
+
? process.env.PYTHONPATH
|
|
349
|
+
: undefined;
|
|
350
|
+
const mergedPyPath = [...extraPaths, ...(existingPyPath ? [existingPyPath] : [])]
|
|
351
|
+
.filter(Boolean)
|
|
352
|
+
.join(delimiter);
|
|
353
|
+
const env = mergedPyPath ? { PYTHONPATH: mergedPyPath } : undefined;
|
|
261
354
|
try {
|
|
262
|
-
const result = await processUtils.exec(
|
|
263
|
-
|
|
264
|
-
'tywrap_ir',
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
'--no-pretty',
|
|
268
|
-
]);
|
|
355
|
+
const result = await processUtils.exec(
|
|
356
|
+
pythonPath,
|
|
357
|
+
['-m', 'tywrap_ir', '--module', moduleName, '--ir-version', TYWRAP_IR_VERSION, '--no-pretty'],
|
|
358
|
+
{ timeoutMs: options.timeoutMs, env }
|
|
359
|
+
);
|
|
269
360
|
if (result.code === 0) {
|
|
270
361
|
try {
|
|
271
362
|
return { ir: JSON.parse(result.stdout) };
|
|
@@ -277,18 +368,40 @@ async function fetchPythonIr(
|
|
|
277
368
|
}
|
|
278
369
|
}
|
|
279
370
|
|
|
280
|
-
|
|
371
|
+
const stderrText = result.stderr.trim();
|
|
372
|
+
const isTywrapIrMissing =
|
|
373
|
+
stderrText.includes('No module named') && stderrText.includes('tywrap_ir');
|
|
374
|
+
if (!isTywrapIrMissing) {
|
|
375
|
+
return { ir: null, error: `tywrap_ir failed. stderr: ${stderrText || 'empty'}` };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Fallback to invoking local __main__.py (useful when running from the repo).
|
|
281
379
|
const localMain = pathUtils.join(process.cwd(), 'tywrap_ir', 'tywrap_ir', '__main__.py');
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
380
|
+
let localMainExists = false;
|
|
381
|
+
if (fsUtils.isAvailable()) {
|
|
382
|
+
try {
|
|
383
|
+
await fsUtils.readFile(localMain);
|
|
384
|
+
localMainExists = true;
|
|
385
|
+
} catch {
|
|
386
|
+
localMainExists = false;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (!localMainExists) {
|
|
390
|
+
return {
|
|
391
|
+
ir: null,
|
|
392
|
+
error: `tywrap_ir not found on PYTHONPATH. stderr: ${stderrText || 'empty'}`,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const fallback = await processUtils.exec(
|
|
397
|
+
pythonPath,
|
|
398
|
+
[localMain, '--module', moduleName, '--ir-version', TYWRAP_IR_VERSION, '--no-pretty'],
|
|
399
|
+
{ timeoutMs: options.timeoutMs, env }
|
|
400
|
+
);
|
|
288
401
|
if (fallback.code !== 0) {
|
|
289
402
|
return {
|
|
290
403
|
ir: null,
|
|
291
|
-
error: `tywrap_ir failed. stderr: ${fallback.stderr.trim() ||
|
|
404
|
+
error: `tywrap_ir failed. stderr: ${fallback.stderr.trim() || stderrText || 'empty'}`,
|
|
292
405
|
};
|
|
293
406
|
}
|
|
294
407
|
try {
|
|
@@ -307,73 +420,161 @@ async function fetchPythonIr(
|
|
|
307
420
|
/**
|
|
308
421
|
* Convert JSON IR from Python into the internal TypeScript model used by the generator.
|
|
309
422
|
*/
|
|
423
|
+
function collectModuleTypeVarNames(obj: Record<string, unknown>): Set<string> {
|
|
424
|
+
const names = new Set<string>();
|
|
425
|
+
const constants = Array.isArray(obj.constants) ? (obj.constants as unknown[]) : [];
|
|
426
|
+
|
|
427
|
+
for (const constant of constants) {
|
|
428
|
+
const entry = constant as Record<string, unknown>;
|
|
429
|
+
const name = typeof entry.name === 'string' ? entry.name.trim() : '';
|
|
430
|
+
if (!name) {
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const valueRepr = typeof entry.value_repr === 'string' ? entry.value_repr.trim() : '';
|
|
435
|
+
if (/^~?[A-Za-z_][A-Za-z0-9_]*$/.test(valueRepr) && valueRepr.replace(/^~/, '') === name) {
|
|
436
|
+
names.add(name);
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const annotation = typeof entry.annotation === 'string' ? entry.annotation.trim() : '';
|
|
441
|
+
if (/(^|\.)(TypeVar|ParamSpec|TypeVarTuple)(\[|\(|$)/.test(annotation)) {
|
|
442
|
+
names.add(name);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return names;
|
|
447
|
+
}
|
|
448
|
+
|
|
310
449
|
function transformIrToTsModel(ir: unknown): TSPythonModule {
|
|
311
450
|
const obj: Record<string, unknown> =
|
|
312
451
|
typeof ir === 'object' && ir !== null ? (ir as Record<string, unknown>) : {};
|
|
313
452
|
const functions = (obj.functions as unknown[]) ?? [];
|
|
314
453
|
const classes = (obj.classes as unknown[]) ?? [];
|
|
315
|
-
const
|
|
454
|
+
const aliases = (obj.type_aliases as unknown[]) ?? [];
|
|
455
|
+
const moduleTypeVarNames = collectModuleTypeVarNames(obj);
|
|
456
|
+
const parseType = (
|
|
457
|
+
annotation: unknown,
|
|
458
|
+
typeParameters: readonly PythonGenericParameter[] = []
|
|
459
|
+
): PythonType =>
|
|
460
|
+
parseAnnotationToPythonType(annotation, {
|
|
461
|
+
onUnknownTypeName: recordUnknown,
|
|
462
|
+
knownTypeVarNames: moduleTypeVarNames,
|
|
463
|
+
typeParameters,
|
|
464
|
+
});
|
|
465
|
+
const mapTypeParameters = (value: Record<string, unknown>): PythonGenericParameter[] =>
|
|
466
|
+
Array.isArray(value.type_params)
|
|
467
|
+
? (value.type_params as unknown[]).map(v => {
|
|
468
|
+
const param = (v ?? {}) as Record<string, unknown>;
|
|
469
|
+
return {
|
|
470
|
+
name: String(param.name ?? ''),
|
|
471
|
+
kind: String(param.kind ?? 'typevar') as PythonGenericParameter['kind'],
|
|
472
|
+
bound: param.bound ? parseType(param.bound) : undefined,
|
|
473
|
+
constraints: Array.isArray(param.constraints)
|
|
474
|
+
? (param.constraints as unknown[]).map(item => parseType(item))
|
|
475
|
+
: undefined,
|
|
476
|
+
variance:
|
|
477
|
+
param.variance === 'covariant' ||
|
|
478
|
+
param.variance === 'contravariant' ||
|
|
479
|
+
param.variance === 'invariant'
|
|
480
|
+
? param.variance
|
|
481
|
+
: undefined,
|
|
482
|
+
} satisfies PythonGenericParameter;
|
|
483
|
+
})
|
|
484
|
+
: [];
|
|
485
|
+
const mapParam = (
|
|
486
|
+
p: Record<string, unknown>,
|
|
487
|
+
typeParameters: readonly PythonGenericParameter[] = []
|
|
488
|
+
): Parameter => ({
|
|
316
489
|
name: String(p.name ?? ''),
|
|
317
|
-
type:
|
|
490
|
+
type: parseType(p.annotation, typeParameters),
|
|
318
491
|
optional: Boolean(p.default),
|
|
319
492
|
varArgs: p.kind === 'VAR_POSITIONAL',
|
|
320
493
|
kwArgs: p.kind === 'VAR_KEYWORD',
|
|
494
|
+
positionalOnly: p.kind === 'POSITIONAL_ONLY',
|
|
495
|
+
keywordOnly: p.kind === 'KEYWORD_ONLY',
|
|
321
496
|
});
|
|
322
497
|
|
|
323
|
-
const mapFunc = (
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
498
|
+
const mapFunc = (
|
|
499
|
+
f: Record<string, unknown>,
|
|
500
|
+
inheritedTypeParameters: readonly PythonGenericParameter[] = []
|
|
501
|
+
): PythonFunction => {
|
|
502
|
+
const localTypeParameters = mapTypeParameters(f);
|
|
503
|
+
const annotationTypeParameters = [...inheritedTypeParameters, ...localTypeParameters];
|
|
504
|
+
return {
|
|
505
|
+
name: String(f.name ?? ''),
|
|
506
|
+
signature: {
|
|
507
|
+
parameters: Array.isArray(f.parameters)
|
|
508
|
+
? (f.parameters as unknown[]).map(v =>
|
|
509
|
+
mapParam((v ?? {}) as Record<string, unknown>, annotationTypeParameters)
|
|
510
|
+
)
|
|
511
|
+
: [],
|
|
512
|
+
returnType: parseType(f.returns, annotationTypeParameters),
|
|
513
|
+
isAsync: Boolean(f.is_async),
|
|
514
|
+
isGenerator: Boolean(f.is_generator),
|
|
515
|
+
},
|
|
516
|
+
docstring: (f.docstring as string | undefined) ?? undefined,
|
|
517
|
+
decorators: [],
|
|
330
518
|
isAsync: Boolean(f.is_async),
|
|
331
519
|
isGenerator: Boolean(f.is_generator),
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
});
|
|
520
|
+
typeParameters: [...localTypeParameters],
|
|
521
|
+
returnType: parseType(f.returns, annotationTypeParameters),
|
|
522
|
+
parameters: Array.isArray(f.parameters)
|
|
523
|
+
? (f.parameters as unknown[]).map(v =>
|
|
524
|
+
mapParam((v ?? {}) as Record<string, unknown>, annotationTypeParameters)
|
|
525
|
+
)
|
|
526
|
+
: [],
|
|
527
|
+
};
|
|
528
|
+
};
|
|
342
529
|
|
|
343
|
-
const mapClass = (c: Record<string, unknown>): PythonClass =>
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
? (c.
|
|
348
|
-
:
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
: (c.
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
530
|
+
const mapClass = (c: Record<string, unknown>): PythonClass => {
|
|
531
|
+
const classTypeParameters = mapTypeParameters(c);
|
|
532
|
+
return {
|
|
533
|
+
name: String(c.name ?? ''),
|
|
534
|
+
bases: Array.isArray(c.bases) ? (c.bases as string[]) : [],
|
|
535
|
+
methods: Array.isArray(c.methods)
|
|
536
|
+
? (c.methods as unknown[]).map(v =>
|
|
537
|
+
mapFunc((v ?? {}) as Record<string, unknown>, classTypeParameters)
|
|
538
|
+
)
|
|
539
|
+
: [],
|
|
540
|
+
properties: Array.isArray(c.fields)
|
|
541
|
+
? (c.fields as unknown[]).map(v => {
|
|
542
|
+
const p = (v ?? {}) as Record<string, unknown>;
|
|
543
|
+
return {
|
|
544
|
+
name: String(p.name ?? ''),
|
|
545
|
+
type: parseType(p.annotation, classTypeParameters),
|
|
546
|
+
readonly: false,
|
|
547
|
+
setter: false,
|
|
548
|
+
getter: true,
|
|
549
|
+
optional: Boolean(p.default),
|
|
550
|
+
};
|
|
551
|
+
})
|
|
552
|
+
: [],
|
|
553
|
+
docstring: (c.docstring as string | undefined) ?? undefined,
|
|
554
|
+
decorators: (c.typed_dict as boolean) ? ['__typed_dict__'] : [],
|
|
555
|
+
kind: (c.typed_dict as boolean)
|
|
556
|
+
? 'typed_dict'
|
|
557
|
+
: (c.is_protocol as boolean)
|
|
558
|
+
? 'protocol'
|
|
559
|
+
: (c.is_namedtuple as boolean)
|
|
560
|
+
? 'namedtuple'
|
|
561
|
+
: (c.is_dataclass as boolean)
|
|
562
|
+
? 'dataclass'
|
|
563
|
+
: (c.is_pydantic as boolean)
|
|
564
|
+
? 'pydantic'
|
|
565
|
+
: 'class',
|
|
566
|
+
typeParameters: classTypeParameters,
|
|
567
|
+
};
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
const mapTypeAlias = (alias: Record<string, unknown>): PythonTypeAlias => {
|
|
571
|
+
const typeParameters = mapTypeParameters(alias);
|
|
572
|
+
return {
|
|
573
|
+
name: String(alias.name ?? ''),
|
|
574
|
+
type: parseType(alias.definition, typeParameters),
|
|
575
|
+
typeParameters,
|
|
576
|
+
};
|
|
577
|
+
};
|
|
377
578
|
|
|
378
579
|
const moduleModel: TSPythonModule = {
|
|
379
580
|
name: (obj.module as string) ?? 'module',
|
|
@@ -384,255 +585,13 @@ function transformIrToTsModel(ir: unknown): TSPythonModule {
|
|
|
384
585
|
: undefined,
|
|
385
586
|
functions: functions.map(v => mapFunc((v ?? {}) as Record<string, unknown>)),
|
|
386
587
|
classes: classes.map(v => mapClass((v ?? {}) as Record<string, unknown>)),
|
|
588
|
+
typeAliases: aliases.map(v => mapTypeAlias((v ?? {}) as Record<string, unknown>)),
|
|
387
589
|
imports: [],
|
|
388
590
|
exports: [],
|
|
389
591
|
};
|
|
390
592
|
return moduleModel;
|
|
391
593
|
}
|
|
392
594
|
|
|
393
|
-
function unknownType(): PythonType {
|
|
394
|
-
return { kind: 'custom', name: 'Any', module: 'typing' };
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Parse Python IR annotation string -> PythonType
|
|
398
|
-
function parseAnnotationToPythonType(annotation: unknown, depth = 0): PythonType {
|
|
399
|
-
if (annotation === null || annotation === undefined) {
|
|
400
|
-
return unknownType();
|
|
401
|
-
}
|
|
402
|
-
if (depth > 100) {
|
|
403
|
-
return unknownType();
|
|
404
|
-
}
|
|
405
|
-
const raw = String(annotation).trim();
|
|
406
|
-
|
|
407
|
-
// Handle built-in class repr: <class 'int'>
|
|
408
|
-
const classMatch = raw.match(/^<class ['"][^'"]+['"]>$/);
|
|
409
|
-
if (classMatch) {
|
|
410
|
-
const inner = (raw.match(/^<class ['"]([^'"]+)['"]>$/) ?? [])[1] ?? '';
|
|
411
|
-
const name = (inner.split('.').pop() ?? '').toString();
|
|
412
|
-
return mapSimpleName(name);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// PEP 604 unions: int | str | None
|
|
416
|
-
if (raw.includes(' | ')) {
|
|
417
|
-
const parts = splitTopLevel(raw, '|');
|
|
418
|
-
const types = parts.map(p => parseAnnotationToPythonType(p.trim(), depth + 1));
|
|
419
|
-
return { kind: 'union', types };
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// typing.Union[...]
|
|
423
|
-
if (raw.startsWith('typing.Union[') || raw.startsWith('Union[')) {
|
|
424
|
-
const inner = raw.slice(raw.indexOf('[') + 1, raw.lastIndexOf(']'));
|
|
425
|
-
const parts = splitTopLevel(inner, ',');
|
|
426
|
-
const types = parts.map(p => parseAnnotationToPythonType(p.trim(), depth + 1));
|
|
427
|
-
return { kind: 'union', types };
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// Optional[T]
|
|
431
|
-
if (raw.startsWith('typing.Optional[') || raw.startsWith('Optional[')) {
|
|
432
|
-
const inner = raw.slice(raw.indexOf('[') + 1, raw.lastIndexOf(']'));
|
|
433
|
-
const base = parseAnnotationToPythonType(inner, depth + 1);
|
|
434
|
-
return { kind: 'optional', type: base };
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// Literal[...] -> literal values union
|
|
438
|
-
if (raw.startsWith('typing.Literal[') || raw.startsWith('Literal[')) {
|
|
439
|
-
const inner = raw.slice(raw.indexOf('[') + 1, raw.lastIndexOf(']'));
|
|
440
|
-
const parts = splitTopLevel(inner, ',');
|
|
441
|
-
if (parts.length === 1) {
|
|
442
|
-
return mapLiteral(String(parts[0] ?? '').trim());
|
|
443
|
-
}
|
|
444
|
-
return { kind: 'union', types: parts.map(p => mapLiteral(String(p).trim())) } as PythonType;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// typing_extensions wrappers: ClassVar[T], Final[T], Required[T], NotRequired[T]
|
|
448
|
-
const extMatch = raw.match(
|
|
449
|
-
/^(typing\.|typing_extensions\.)?(ClassVar|Final|Required|NotRequired)\[(.*)\]$/
|
|
450
|
-
);
|
|
451
|
-
if (extMatch) {
|
|
452
|
-
const inner = extMatch[3] ?? '';
|
|
453
|
-
const base = parseAnnotationToPythonType(inner, depth + 1);
|
|
454
|
-
return base;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
// LiteralString
|
|
458
|
-
if (raw === 'typing.LiteralString' || raw === 'LiteralString') {
|
|
459
|
-
return { kind: 'primitive', name: 'str' } as PythonType;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// Callable[[...], R]
|
|
463
|
-
if (raw.startsWith('typing.Callable[') || raw.startsWith('Callable[')) {
|
|
464
|
-
const inner = raw.slice(raw.indexOf('[') + 1, raw.lastIndexOf(']'));
|
|
465
|
-
const parts = splitTopLevel(inner, ',');
|
|
466
|
-
if (parts.length >= 2) {
|
|
467
|
-
const paramsPart = (parts[0] ?? '').trim();
|
|
468
|
-
const returnPart = parts.slice(1).join(',').trim();
|
|
469
|
-
const paramInner =
|
|
470
|
-
paramsPart.startsWith('[') && paramsPart.endsWith(']') ? paramsPart.slice(1, -1) : '';
|
|
471
|
-
const paramTypes = ((): PythonType[] => {
|
|
472
|
-
const trimmed = paramInner.trim();
|
|
473
|
-
if (trimmed === '...' || trimmed === 'Ellipsis') {
|
|
474
|
-
return [{ kind: 'custom', name: '...' } as PythonType];
|
|
475
|
-
}
|
|
476
|
-
return trimmed
|
|
477
|
-
? splitTopLevel(trimmed, ',').map(p => parseAnnotationToPythonType(p.trim(), depth + 1))
|
|
478
|
-
: [];
|
|
479
|
-
})();
|
|
480
|
-
const returnType = parseAnnotationToPythonType(returnPart, depth + 1);
|
|
481
|
-
return { kind: 'callable', parameters: paramTypes, returnType } as PythonType;
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// Mapping[K, V] / Dict[K, V] normalization
|
|
486
|
-
if (raw.startsWith('typing.Mapping[') || raw.startsWith('Mapping[')) {
|
|
487
|
-
const inner = raw.slice(raw.indexOf('[') + 1, raw.lastIndexOf(']'));
|
|
488
|
-
const parts = splitTopLevel(inner, ',');
|
|
489
|
-
const itemTypes = parts.map(p => parseAnnotationToPythonType(p.trim(), depth + 1));
|
|
490
|
-
return { kind: 'collection', name: 'dict', itemTypes } as PythonType;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
// Annotated[T, ...] -> annotated node with base and metadata
|
|
494
|
-
if (raw.startsWith('typing.Annotated[') || raw.startsWith('Annotated[')) {
|
|
495
|
-
const inner = raw.slice(raw.indexOf('[') + 1, raw.lastIndexOf(']'));
|
|
496
|
-
const parts = splitTopLevel(inner, ',');
|
|
497
|
-
if (parts.length > 0) {
|
|
498
|
-
const base = parseAnnotationToPythonType((parts[0] ?? '').trim(), depth + 1);
|
|
499
|
-
const metaParts = parts.slice(1).map(p => String(p).trim());
|
|
500
|
-
return { kind: 'annotated', base, metadata: metaParts } as PythonType;
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
// Collections: list[T], dict[K,V], tuple[...], set[T]
|
|
505
|
-
const coll = normalizeCollectionName(raw);
|
|
506
|
-
if (coll) {
|
|
507
|
-
const { name, inner } = coll;
|
|
508
|
-
const itemParts =
|
|
509
|
-
name === 'dict' ? splitTopLevel(inner ?? '', ',') : splitTopLevel(inner ?? '', ',');
|
|
510
|
-
const itemTypes = (inner ? itemParts : []).map(p =>
|
|
511
|
-
parseAnnotationToPythonType(p.trim(), depth + 1)
|
|
512
|
-
);
|
|
513
|
-
return { kind: 'collection', name, itemTypes };
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// typing.Callable[...] unlikely for now - treat as custom
|
|
517
|
-
|
|
518
|
-
// Bare names like int, str, float, bool, bytes, None
|
|
519
|
-
return mapSimpleName(raw);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
function mapSimpleName(name: string): PythonType {
|
|
523
|
-
const n = name.replace(/^typing\./, '').trim();
|
|
524
|
-
if (n === 'int' || n === 'float' || n === 'str' || n === 'bool' || n === 'bytes') {
|
|
525
|
-
// cast to the specific union type without using any
|
|
526
|
-
return { kind: 'primitive', name: n };
|
|
527
|
-
}
|
|
528
|
-
// Track unknown typing-ish names for diagnostics
|
|
529
|
-
if (
|
|
530
|
-
n === 'Any' ||
|
|
531
|
-
n === 'Never' ||
|
|
532
|
-
n === 'LiteralString' ||
|
|
533
|
-
n === 'ClassVar' ||
|
|
534
|
-
n === 'Final' ||
|
|
535
|
-
n === 'TypeAlias' ||
|
|
536
|
-
n === 'Required' ||
|
|
537
|
-
n === 'NotRequired'
|
|
538
|
-
) {
|
|
539
|
-
try {
|
|
540
|
-
recordUnknown(n);
|
|
541
|
-
} catch {}
|
|
542
|
-
}
|
|
543
|
-
if (n === 'None' || n.toLowerCase() === 'nonetype') {
|
|
544
|
-
return { kind: 'primitive', name: 'None' };
|
|
545
|
-
}
|
|
546
|
-
if (n === 'list' || n === 'List') {
|
|
547
|
-
return { kind: 'collection', name: 'list', itemTypes: [] };
|
|
548
|
-
}
|
|
549
|
-
if (n === 'dict' || n === 'Dict') {
|
|
550
|
-
return { kind: 'collection', name: 'dict', itemTypes: [] };
|
|
551
|
-
}
|
|
552
|
-
if (n === 'tuple' || n === 'Tuple') {
|
|
553
|
-
return { kind: 'collection', name: 'tuple', itemTypes: [] };
|
|
554
|
-
}
|
|
555
|
-
if (n === 'set' || n === 'Set') {
|
|
556
|
-
return { kind: 'collection', name: 'set', itemTypes: [] };
|
|
557
|
-
}
|
|
558
|
-
return { kind: 'custom', name: n };
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
function normalizeCollectionName(
|
|
562
|
-
raw: string
|
|
563
|
-
): { name: 'list' | 'dict' | 'tuple' | 'set' | 'frozenset'; inner?: string } | null {
|
|
564
|
-
const m = raw.match(/^(typing\.)?(List|Dict|Tuple|Set|list|dict|tuple|set)\[(.*)\]$/);
|
|
565
|
-
if (!m) {
|
|
566
|
-
return null;
|
|
567
|
-
}
|
|
568
|
-
const nameRaw = String(m[2] ?? '');
|
|
569
|
-
const name =
|
|
570
|
-
nameRaw.toLowerCase() === 'list'
|
|
571
|
-
? 'list'
|
|
572
|
-
: nameRaw.toLowerCase() === 'dict'
|
|
573
|
-
? 'dict'
|
|
574
|
-
: nameRaw.toLowerCase() === 'tuple'
|
|
575
|
-
? 'tuple'
|
|
576
|
-
: 'set';
|
|
577
|
-
return { name, inner: String(m[3] ?? '') };
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
function splitTopLevel(input: string, sep: '|' | ','): string[] {
|
|
581
|
-
const results: string[] = [];
|
|
582
|
-
let level = 0;
|
|
583
|
-
let cur = '';
|
|
584
|
-
let guard = 0;
|
|
585
|
-
for (let i = 0; i < input.length; i++) {
|
|
586
|
-
// Depth/length guard to avoid pathological recursion/loops
|
|
587
|
-
guard++;
|
|
588
|
-
if (guard > 20000) {
|
|
589
|
-
// Bail out safely; push remaining and break
|
|
590
|
-
if (cur.trim()) {
|
|
591
|
-
results.push(cur.trim());
|
|
592
|
-
}
|
|
593
|
-
break;
|
|
594
|
-
}
|
|
595
|
-
const ch = input.charAt(i);
|
|
596
|
-
if (ch === '[' || ch === '(') {
|
|
597
|
-
level++;
|
|
598
|
-
}
|
|
599
|
-
if (ch === ']' || ch === ')') {
|
|
600
|
-
level = Math.max(0, level - 1);
|
|
601
|
-
}
|
|
602
|
-
if (level === 0 && input.slice(i, i + sep.length) === sep) {
|
|
603
|
-
results.push(cur.trim());
|
|
604
|
-
cur = '';
|
|
605
|
-
i += sep.length - 1;
|
|
606
|
-
continue;
|
|
607
|
-
}
|
|
608
|
-
cur += ch;
|
|
609
|
-
}
|
|
610
|
-
if (cur.trim()) {
|
|
611
|
-
results.push(cur.trim());
|
|
612
|
-
}
|
|
613
|
-
return results;
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
function mapLiteral(text: string): PythonType {
|
|
617
|
-
// strip quotes if present
|
|
618
|
-
const t = text.trim();
|
|
619
|
-
if ((t.startsWith("'") && t.endsWith("'")) || (t.startsWith('"') && t.endsWith('"'))) {
|
|
620
|
-
return { kind: 'literal', value: t.slice(1, -1) } as PythonType;
|
|
621
|
-
}
|
|
622
|
-
if (t === 'True' || t === 'False') {
|
|
623
|
-
return { kind: 'literal', value: t === 'True' } as PythonType;
|
|
624
|
-
}
|
|
625
|
-
if (t === 'None') {
|
|
626
|
-
return { kind: 'literal', value: null } as PythonType;
|
|
627
|
-
}
|
|
628
|
-
const num = Number(t);
|
|
629
|
-
if (!Number.isNaN(num)) {
|
|
630
|
-
return { kind: 'literal', value: num } as PythonType;
|
|
631
|
-
}
|
|
632
|
-
// fallback
|
|
633
|
-
return { kind: 'custom', name: t } as PythonType;
|
|
634
|
-
}
|
|
635
|
-
|
|
636
595
|
/**
|
|
637
596
|
* Compute a stable cache key filename for a module and options
|
|
638
597
|
*/
|
|
@@ -650,6 +609,8 @@ async function computeCacheKey(
|
|
|
650
609
|
const keyObject = {
|
|
651
610
|
module: moduleName,
|
|
652
611
|
moduleVersion: moduleConfig?.version ?? null,
|
|
612
|
+
irVersion: TYWRAP_IR_VERSION,
|
|
613
|
+
pythonImportPath: options.pythonImportPath ?? [],
|
|
653
614
|
runtime: {
|
|
654
615
|
pythonPath: runtimePython,
|
|
655
616
|
virtualEnv: options.runtime?.node?.virtualEnv ?? null,
|
|
@@ -665,47 +626,7 @@ async function computeCacheKey(
|
|
|
665
626
|
},
|
|
666
627
|
typeHints: moduleConfig?.typeHints ?? 'strict',
|
|
667
628
|
} as const;
|
|
668
|
-
|
|
669
|
-
const digest = await hashUtils.sha256Hex(normalized);
|
|
670
|
-
return `ir_${moduleName}_${digest.slice(0, 16)}.json`;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
/**
|
|
674
|
-
* Very lightweight .d.ts emitter derived from generated TS wrappers
|
|
675
|
-
* This is intentionally minimal and stable for our wrappers shape.
|
|
676
|
-
*/
|
|
677
|
-
function renderDts(generatedTs: string): string {
|
|
678
|
-
const header = `// Generated by tywrap\n// Type Declarations\n// DO NOT EDIT MANUALLY\n\n`;
|
|
679
|
-
const lines: string[] = [header];
|
|
680
|
-
// Extract function exports
|
|
681
|
-
const funcRegex = /export\s+async\s+function\s+(\w+)\s*\(([^)]*)\)\s*:\s*Promise<([^>]+)>/g;
|
|
682
|
-
let m: RegExpExecArray | null;
|
|
683
|
-
while ((m = funcRegex.exec(generatedTs)) !== null) {
|
|
684
|
-
const name = m[1];
|
|
685
|
-
const params = m[2];
|
|
686
|
-
const ret = m[3];
|
|
687
|
-
lines.push(`export function ${name}(${params}): Promise<${ret}>;`);
|
|
688
|
-
}
|
|
689
|
-
// Extract class exports and methods
|
|
690
|
-
const classRegex = /export\s+class\s+(\w+)\s*\{([\s\S]*?)\n\}/g;
|
|
691
|
-
while ((m = classRegex.exec(generatedTs)) !== null) {
|
|
692
|
-
const className = String(m[1] ?? '');
|
|
693
|
-
const body = String(m[2] ?? '');
|
|
694
|
-
// Constructor is always variadic unknown[] in current generator
|
|
695
|
-
const methods: string[] = [];
|
|
696
|
-
const methodRegex = /\n\s+async\s+(\w+)\s*\(([^)]*)\)\s*:\s*Promise<([^>]+)>/g;
|
|
697
|
-
let mm: RegExpExecArray | null;
|
|
698
|
-
while ((mm = methodRegex.exec(body)) !== null) {
|
|
699
|
-
methods.push(` ${mm[1]}(${mm[2]}): Promise<${mm[3]}>;`);
|
|
700
|
-
}
|
|
701
|
-
lines.push(`export class ${className} {`);
|
|
702
|
-
lines.push(` constructor(...args: unknown[]);`);
|
|
703
|
-
if (methods.length > 0) {
|
|
704
|
-
lines.push(methods.join('\n'));
|
|
705
|
-
}
|
|
706
|
-
lines.push('}');
|
|
707
|
-
}
|
|
708
|
-
return `${lines.join('\n')}\n`;
|
|
629
|
+
return await computeIrCacheFilename(keyObject);
|
|
709
630
|
}
|
|
710
631
|
|
|
711
632
|
/**
|