tywrap 0.8.0 → 0.9.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 +22 -7
- package/SECURITY.md +39 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +8 -0
- package/dist/config/index.js.map +1 -1
- package/dist/core/annotation-parser.d.ts +2 -1
- package/dist/core/annotation-parser.d.ts.map +1 -1
- package/dist/core/annotation-parser.js +6 -3
- package/dist/core/annotation-parser.js.map +1 -1
- package/dist/core/generator.d.ts +23 -4
- package/dist/core/generator.d.ts.map +1 -1
- package/dist/core/generator.js +243 -170
- package/dist/core/generator.js.map +1 -1
- package/dist/core/mapper.d.ts +3 -2
- package/dist/core/mapper.d.ts.map +1 -1
- package/dist/core/mapper.js +5 -5
- package/dist/core/mapper.js.map +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/runtime/base-bridge.d.ts +3 -7
- package/dist/runtime/base-bridge.d.ts.map +1 -1
- package/dist/runtime/base-bridge.js +4 -17
- package/dist/runtime/base-bridge.js.map +1 -1
- package/dist/runtime/bounded-context.d.ts +3 -22
- package/dist/runtime/bounded-context.d.ts.map +1 -1
- package/dist/runtime/bounded-context.js +13 -53
- package/dist/runtime/bounded-context.js.map +1 -1
- package/dist/runtime/bridge-codec.d.ts +1 -1
- package/dist/runtime/bridge-codec.d.ts.map +1 -1
- package/dist/runtime/bridge-codec.js +78 -55
- package/dist/runtime/bridge-codec.js.map +1 -1
- package/dist/runtime/errors.d.ts +16 -0
- package/dist/runtime/errors.d.ts.map +1 -1
- package/dist/runtime/errors.js +17 -0
- package/dist/runtime/errors.js.map +1 -1
- package/dist/runtime/http-transport.d.ts +1 -1
- package/dist/runtime/http-transport.d.ts.map +1 -1
- package/dist/runtime/http-transport.js +1 -1
- package/dist/runtime/http-transport.js.map +1 -1
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/node.d.ts +7 -36
- package/dist/runtime/node.d.ts.map +1 -1
- package/dist/runtime/node.js +2 -80
- package/dist/runtime/node.js.map +1 -1
- package/dist/runtime/pooled-transport.d.ts +120 -59
- package/dist/runtime/pooled-transport.d.ts.map +1 -1
- package/dist/runtime/pooled-transport.js +345 -78
- package/dist/runtime/pooled-transport.js.map +1 -1
- package/dist/runtime/pyodide-bootstrap-core.generated.d.ts.map +1 -1
- package/dist/runtime/pyodide-bootstrap-core.generated.js +1 -1
- package/dist/runtime/pyodide-bootstrap-core.generated.js.map +1 -1
- package/dist/runtime/pyodide-transport.d.ts +1 -1
- package/dist/runtime/pyodide-transport.d.ts.map +1 -1
- package/dist/runtime/pyodide-transport.js +2 -3
- package/dist/runtime/pyodide-transport.js.map +1 -1
- package/dist/runtime/rpc-client.d.ts +7 -36
- package/dist/runtime/rpc-client.d.ts.map +1 -1
- package/dist/runtime/rpc-client.js +20 -102
- package/dist/runtime/rpc-client.js.map +1 -1
- package/dist/runtime/subprocess-transport.d.ts +30 -69
- package/dist/runtime/subprocess-transport.d.ts.map +1 -1
- package/dist/runtime/subprocess-transport.js +179 -236
- package/dist/runtime/subprocess-transport.js.map +1 -1
- package/dist/runtime/timed-out-request-tracker.d.ts +2 -1
- package/dist/runtime/timed-out-request-tracker.d.ts.map +1 -1
- package/dist/runtime/transport.d.ts +9 -19
- package/dist/runtime/transport.d.ts.map +1 -1
- package/dist/runtime/transport.js +1 -1
- package/dist/runtime/transport.js.map +1 -1
- package/dist/runtime/validators.d.ts +49 -0
- package/dist/runtime/validators.d.ts.map +1 -1
- package/dist/runtime/validators.js +152 -0
- package/dist/runtime/validators.js.map +1 -1
- package/dist/types/index.d.ts +14 -39
- package/dist/types/index.d.ts.map +1 -1
- package/dist/tywrap.d.ts +3 -2
- package/dist/tywrap.d.ts.map +1 -1
- package/dist/tywrap.js +140 -13
- package/dist/tywrap.js.map +1 -1
- package/dist/utils/cache.d.ts +3 -16
- package/dist/utils/cache.d.ts.map +1 -1
- package/dist/utils/codec.d.ts +1 -0
- package/dist/utils/codec.d.ts.map +1 -1
- package/dist/utils/codec.js +13 -13
- package/dist/utils/codec.js.map +1 -1
- package/dist/utils/ir-cache.d.ts +2 -1
- package/dist/utils/ir-cache.d.ts.map +1 -1
- package/dist/utils/runtime.d.ts +0 -29
- package/dist/utils/runtime.d.ts.map +1 -1
- package/dist/utils/runtime.js +16 -107
- package/dist/utils/runtime.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -6
- package/runtime/frame_codec.py +7 -1
- package/runtime/python_bridge.py +85 -125
- package/runtime/tywrap_bridge_core.py +133 -150
- package/src/config/index.ts +11 -0
- package/src/core/annotation-parser.ts +7 -4
- package/src/core/generator.ts +315 -205
- package/src/core/mapper.ts +8 -8
- package/src/index.ts +7 -4
- package/src/runtime/base-bridge.ts +5 -30
- package/src/runtime/bounded-context.ts +12 -67
- package/src/runtime/bridge-codec.ts +94 -65
- package/src/runtime/errors.ts +21 -0
- package/src/runtime/http-transport.ts +6 -1
- package/src/runtime/index.ts +6 -0
- package/src/runtime/node.ts +9 -120
- package/src/runtime/pooled-transport.ts +424 -90
- package/src/runtime/pyodide-bootstrap-core.generated.ts +1 -1
- package/src/runtime/pyodide-transport.ts +7 -3
- package/src/runtime/rpc-client.ts +37 -139
- package/src/runtime/subprocess-transport.ts +210 -285
- package/src/runtime/timed-out-request-tracker.ts +1 -1
- package/src/runtime/transport.ts +15 -23
- package/src/runtime/validators.ts +204 -0
- package/src/types/index.ts +21 -60
- package/src/tywrap.ts +157 -22
- package/src/utils/cache.ts +3 -3
- package/src/utils/codec.ts +21 -13
- package/src/utils/ir-cache.ts +1 -1
- package/src/utils/runtime.ts +17 -128
- package/src/version.ts +1 -1
- package/dist/core/discovery.d.ts +0 -103
- package/dist/core/discovery.d.ts.map +0 -1
- package/dist/core/discovery.js +0 -380
- package/dist/core/discovery.js.map +0 -1
- package/dist/core/validation.d.ts +0 -102
- package/dist/core/validation.d.ts.map +0 -1
- package/dist/core/validation.js +0 -490
- package/dist/core/validation.js.map +0 -1
- package/dist/runtime/base.d.ts +0 -22
- package/dist/runtime/base.d.ts.map +0 -1
- package/dist/runtime/base.js +0 -23
- package/dist/runtime/base.js.map +0 -1
- package/dist/runtime/transport-pool.d.ts +0 -196
- package/dist/runtime/transport-pool.d.ts.map +0 -1
- package/dist/runtime/transport-pool.js +0 -418
- package/dist/runtime/transport-pool.js.map +0 -1
- package/runtime/__pycache__/_tywrap_conformance_chunking_fixtures.cpython-311.pyc +0 -0
- package/runtime/__pycache__/_tywrap_member_fixtures.cpython-311.pyc +0 -0
- package/runtime/__pycache__/_tywrap_w4_chunking_fixture.cpython-311.pyc +0 -0
- package/runtime/__pycache__/_tywrap_w5_request_chunking_fixture.cpython-311.pyc +0 -0
- package/runtime/__pycache__/_tywrap_w6_pool_chunking_fixture.cpython-311.pyc +0 -0
- package/runtime/__pycache__/frame_codec.cpython-311.pyc +0 -0
- package/runtime/__pycache__/safe_codec.cpython-311.pyc +0 -0
- package/runtime/__pycache__/tywrap_bridge_core.cpython-311.pyc +0 -0
- package/runtime/safe_codec.py +0 -352
- package/src/core/discovery.ts +0 -477
- package/src/core/validation.ts +0 -729
- package/src/runtime/base.ts +0 -24
- package/src/runtime/transport-pool.ts +0 -538
package/dist/core/generator.js
CHANGED
|
@@ -6,6 +6,28 @@ import { emitArgGuards, emitCallPrelude, } from './emit-call.js';
|
|
|
6
6
|
import { TypeMapper } from './mapper.js';
|
|
7
7
|
export class CodeGenerator {
|
|
8
8
|
mapper;
|
|
9
|
+
onTypeDegrade;
|
|
10
|
+
builtinGenericNames = new Set([
|
|
11
|
+
'Array',
|
|
12
|
+
'AsyncIterator',
|
|
13
|
+
'Generator',
|
|
14
|
+
'Iterable',
|
|
15
|
+
'Iterator',
|
|
16
|
+
'Promise',
|
|
17
|
+
'Record',
|
|
18
|
+
]);
|
|
19
|
+
// Iterator-protocol objects can never cross the bridge: the Python side
|
|
20
|
+
// rejects them loudly at serialization, so a return typed as one of these
|
|
21
|
+
// could never carry a value. Returns degrade to `unknown` (and count as a
|
|
22
|
+
// degrade for --fail-on-warn). Iterable/Sequence are NOT here — a list
|
|
23
|
+
// satisfies those annotations and decodes to an array, which structurally
|
|
24
|
+
// satisfies the emitted TS type.
|
|
25
|
+
nonDecodableReturnGenerics = new Set([
|
|
26
|
+
'Generator',
|
|
27
|
+
'AsyncGenerator',
|
|
28
|
+
'Iterator',
|
|
29
|
+
'AsyncIterator',
|
|
30
|
+
]);
|
|
9
31
|
reservedTsIdentifiers = new Set([
|
|
10
32
|
'default',
|
|
11
33
|
'delete',
|
|
@@ -34,8 +56,9 @@ export class CodeGenerator {
|
|
|
34
56
|
'true',
|
|
35
57
|
'false',
|
|
36
58
|
]);
|
|
37
|
-
constructor(mapper = new TypeMapper()) {
|
|
59
|
+
constructor(mapper = new TypeMapper(), options = {}) {
|
|
38
60
|
this.mapper = mapper;
|
|
61
|
+
this.onTypeDegrade = options.onTypeDegrade;
|
|
39
62
|
}
|
|
40
63
|
/**
|
|
41
64
|
* Convert Python snake_case to TypeScript camelCase and escape reserved words
|
|
@@ -93,7 +116,7 @@ export class CodeGenerator {
|
|
|
93
116
|
getTypeParameters(typeParameters) {
|
|
94
117
|
return typeParameters ?? [];
|
|
95
118
|
}
|
|
96
|
-
buildGenericRenderContext(typeParameters, types, currentModule) {
|
|
119
|
+
buildGenericRenderContext(typeParameters, types, currentModule, localDeclaredNames = new Set()) {
|
|
97
120
|
const callableParamSpecs = new Set();
|
|
98
121
|
types.forEach(type => this.collectCallableParamSpecs(type, callableParamSpecs));
|
|
99
122
|
const emitted = [];
|
|
@@ -116,6 +139,7 @@ export class CodeGenerator {
|
|
|
116
139
|
});
|
|
117
140
|
return {
|
|
118
141
|
currentModule,
|
|
142
|
+
localDeclaredNames,
|
|
119
143
|
declaration: emitted.length > 0 ? `<${emitted.map(param => param.declaration).join(', ')}>` : '',
|
|
120
144
|
typeArguments: emitted.length > 0 ? `<${emitted.map(param => param.name).join(', ')}>` : '',
|
|
121
145
|
emittedNames,
|
|
@@ -125,6 +149,7 @@ export class CodeGenerator {
|
|
|
125
149
|
mergeGenericRenderContexts(outer, inner) {
|
|
126
150
|
return {
|
|
127
151
|
currentModule: inner.currentModule ?? outer.currentModule,
|
|
152
|
+
localDeclaredNames: inner.localDeclaredNames,
|
|
128
153
|
declaration: inner.declaration,
|
|
129
154
|
typeArguments: inner.typeArguments,
|
|
130
155
|
emittedNames: new Set([...outer.emittedNames, ...inner.emittedNames]),
|
|
@@ -240,7 +265,18 @@ export class CodeGenerator {
|
|
|
240
265
|
}
|
|
241
266
|
}
|
|
242
267
|
typeToTsFromPython(type, ctx, mappingContext) {
|
|
243
|
-
return this.typeToTs(this.mapper.mapPythonType(this.sanitizeType(type, ctx), mappingContext));
|
|
268
|
+
return this.typeToTs(this.mapper.mapPythonType(this.sanitizeType(type, ctx), mappingContext), ctx, mappingContext);
|
|
269
|
+
}
|
|
270
|
+
isLocalTypeIdentity(type, ctx) {
|
|
271
|
+
if (!ctx.localDeclaredNames.has(type.name)) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
return type.module === undefined || type.module === ctx.currentModule;
|
|
275
|
+
}
|
|
276
|
+
degradeType(type) {
|
|
277
|
+
const identity = type.module ? `${type.module}.${type.name}` : type.name;
|
|
278
|
+
this.onTypeDegrade?.(identity);
|
|
279
|
+
return 'unknown';
|
|
244
280
|
}
|
|
245
281
|
renderLooksLikeKwargsExpr(valueExpr, options) {
|
|
246
282
|
const base = `typeof ${valueExpr} === 'object' && ${valueExpr} !== null && !globalThis.Array.isArray(${valueExpr}) && (Object.getPrototypeOf(${valueExpr}) === Object.prototype || Object.getPrototypeOf(${valueExpr}) === null)`;
|
|
@@ -270,7 +306,143 @@ export class CodeGenerator {
|
|
|
270
306
|
renderLooksLikeKwargsExpr: (valueExpr, options) => this.renderLooksLikeKwargsExpr(valueExpr, options),
|
|
271
307
|
};
|
|
272
308
|
}
|
|
273
|
-
|
|
309
|
+
/**
|
|
310
|
+
* Preserve Python provenance in generated return validators instead of
|
|
311
|
+
* reconstructing a checker from the final TypeScript spelling. In particular
|
|
312
|
+
* pandas/NumPy values retain their marker contract after Arrow decoding.
|
|
313
|
+
*/
|
|
314
|
+
returnSchema(type, definitions = new Set()) {
|
|
315
|
+
// A bare `-> None` return maps to TS void and validates nothing, but a
|
|
316
|
+
// NESTED None (union member, tuple element) is a real wire value — it
|
|
317
|
+
// decodes to null and must be checked as null, or a union containing it
|
|
318
|
+
// would carry an any-member and accept everything.
|
|
319
|
+
if (type.kind === 'primitive' && type.name === 'None') {
|
|
320
|
+
return { kind: 'any' };
|
|
321
|
+
}
|
|
322
|
+
const schema = (current) => {
|
|
323
|
+
switch (current.kind) {
|
|
324
|
+
case 'primitive':
|
|
325
|
+
return current.name === 'int' || current.name === 'float'
|
|
326
|
+
? { kind: 'primitive', type: 'number' }
|
|
327
|
+
: current.name === 'str'
|
|
328
|
+
? { kind: 'primitive', type: 'string' }
|
|
329
|
+
: current.name === 'bool'
|
|
330
|
+
? { kind: 'primitive', type: 'boolean' }
|
|
331
|
+
: current.name === 'bytes'
|
|
332
|
+
? { kind: 'primitive', type: 'Uint8Array' }
|
|
333
|
+
: current.name === 'None'
|
|
334
|
+
? { kind: 'primitive', type: 'null' }
|
|
335
|
+
: { kind: 'any' };
|
|
336
|
+
case 'literal':
|
|
337
|
+
return { kind: 'literal', value: current.value };
|
|
338
|
+
case 'annotated':
|
|
339
|
+
return schema(current.base);
|
|
340
|
+
case 'final':
|
|
341
|
+
case 'classvar':
|
|
342
|
+
return schema(current.type);
|
|
343
|
+
case 'optional':
|
|
344
|
+
return {
|
|
345
|
+
kind: 'union',
|
|
346
|
+
options: [schema(current.type), { kind: 'primitive', type: 'null' }],
|
|
347
|
+
};
|
|
348
|
+
case 'union':
|
|
349
|
+
return { kind: 'union', options: current.types.map(schema) };
|
|
350
|
+
case 'collection': {
|
|
351
|
+
if (current.name === 'tuple') {
|
|
352
|
+
if (current.itemTypes[1]?.kind === 'custom' && current.itemTypes[1].name === '...') {
|
|
353
|
+
return {
|
|
354
|
+
kind: 'array',
|
|
355
|
+
element: schema(current.itemTypes[0] ?? { kind: 'custom', name: 'Any' }),
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
return { kind: 'tuple', elements: current.itemTypes.map(schema) };
|
|
359
|
+
}
|
|
360
|
+
if (current.name === 'dict') {
|
|
361
|
+
return {
|
|
362
|
+
kind: 'record',
|
|
363
|
+
values: schema(current.itemTypes[1] ?? { kind: 'custom', name: 'Any' }),
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
kind: 'array',
|
|
368
|
+
element: schema(current.itemTypes[0] ?? { kind: 'custom', name: 'Any' }),
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
case 'generic': {
|
|
372
|
+
const leaf = current.name.split('.').at(-1) ?? current.name;
|
|
373
|
+
if (leaf === 'NDArray' || leaf === 'ndarray') {
|
|
374
|
+
return { kind: 'marker', marker: 'ndarray' };
|
|
375
|
+
}
|
|
376
|
+
// Iterator/Generator are deliberately absent: those returns emit
|
|
377
|
+
// `unknown` (see nonDecodableReturnGenerics) and validate nothing.
|
|
378
|
+
if (['list', 'List', 'Sequence', 'Iterable', 'set', 'frozenset'].includes(leaf)) {
|
|
379
|
+
return {
|
|
380
|
+
kind: 'array',
|
|
381
|
+
element: schema(current.typeArgs[0] ?? { kind: 'custom', name: 'Any' }),
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
if (['dict', 'Dict', 'Mapping', 'MutableMapping'].includes(leaf)) {
|
|
385
|
+
return {
|
|
386
|
+
kind: 'record',
|
|
387
|
+
values: schema(current.typeArgs[1] ?? { kind: 'custom', name: 'Any' }),
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
return definitions.has(leaf) ? { kind: 'ref', name: leaf } : { kind: 'any' };
|
|
391
|
+
}
|
|
392
|
+
case 'custom': {
|
|
393
|
+
const leaf = current.name.split('.').at(-1) ?? current.name;
|
|
394
|
+
const full = `${current.module ?? ''}.${leaf}`;
|
|
395
|
+
if (leaf === 'None') {
|
|
396
|
+
return { kind: 'primitive', type: 'null' };
|
|
397
|
+
}
|
|
398
|
+
if (leaf === 'Any' || leaf === 'object' || leaf === 'NoReturn' || leaf === 'Never') {
|
|
399
|
+
return { kind: 'any' };
|
|
400
|
+
}
|
|
401
|
+
if (leaf === 'DataFrame' && (!current.module || current.module.startsWith('pandas'))) {
|
|
402
|
+
return { kind: 'marker', marker: 'dataframe' };
|
|
403
|
+
}
|
|
404
|
+
if (leaf === 'Series' && (!current.module || current.module.startsWith('pandas'))) {
|
|
405
|
+
return { kind: 'marker', marker: 'series' };
|
|
406
|
+
}
|
|
407
|
+
if (leaf === 'ndarray' || leaf === 'NDArray' || full.includes('numpy')) {
|
|
408
|
+
return { kind: 'marker', marker: 'ndarray' };
|
|
409
|
+
}
|
|
410
|
+
return definitions.has(leaf) ? { kind: 'ref', name: leaf } : { kind: 'any' };
|
|
411
|
+
}
|
|
412
|
+
case 'typevar':
|
|
413
|
+
case 'paramspec':
|
|
414
|
+
case 'paramspec_args':
|
|
415
|
+
case 'paramspec_kwargs':
|
|
416
|
+
case 'typevartuple':
|
|
417
|
+
case 'unpack':
|
|
418
|
+
case 'callable':
|
|
419
|
+
return { kind: 'any' };
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
return schema(type);
|
|
423
|
+
}
|
|
424
|
+
returnDefinitions(module) {
|
|
425
|
+
return new Set(module.classes
|
|
426
|
+
.filter(cls => cls.kind === 'typed_dict' || cls.decorators.includes('__typed_dict__'))
|
|
427
|
+
.map(cls => cls.name));
|
|
428
|
+
}
|
|
429
|
+
emitReturnDefinitions(module) {
|
|
430
|
+
const definitions = this.returnDefinitions(module);
|
|
431
|
+
const entries = module.classes
|
|
432
|
+
.filter(cls => cls.kind === 'typed_dict' || cls.decorators.includes('__typed_dict__'))
|
|
433
|
+
.map(cls => {
|
|
434
|
+
const fields = Object.fromEntries(cls.properties.map(property => [
|
|
435
|
+
property.name,
|
|
436
|
+
{
|
|
437
|
+
schema: this.returnSchema(property.type, definitions),
|
|
438
|
+
optional: property.optional === true,
|
|
439
|
+
},
|
|
440
|
+
]));
|
|
441
|
+
return [cls.name, { kind: 'record', fields }];
|
|
442
|
+
});
|
|
443
|
+
return `const __tywrapReturnDefinitions: Record<string, ReturnSchema> = ${JSON.stringify(Object.fromEntries(entries))};\n\n`;
|
|
444
|
+
}
|
|
445
|
+
generateFunctionWrapper(func, moduleName, annotatedJSDoc = false, localDeclaredNames = new Set(), returnDefinitions = new Set()) {
|
|
274
446
|
const jsdoc = this.generateJsDoc(func.docstring, annotatedJSDoc ? func.parameters.map(p => String(p.type)) : undefined);
|
|
275
447
|
const filteredParams = func.parameters.filter(p => p.name !== 'self' && p.name !== 'cls');
|
|
276
448
|
const keywordOnlyParams = filteredParams.filter(p => p.keywordOnly);
|
|
@@ -280,7 +452,7 @@ export class CodeGenerator {
|
|
|
280
452
|
const varArgsParam = filteredParams.find(p => p.varArgs);
|
|
281
453
|
const needsVarArgsArray = Boolean(varArgsParam) && needsKwargsParam;
|
|
282
454
|
const positionalParams = filteredParams.filter(p => !p.keywordOnly && !p.varArgs && !p.kwArgs);
|
|
283
|
-
const genericContext = this.buildGenericRenderContext(this.getTypeParameters(func.typeParameters), [func.returnType, ...filteredParams.map(param => param.type)], moduleName);
|
|
455
|
+
const genericContext = this.buildGenericRenderContext(this.getTypeParameters(func.typeParameters), [func.returnType, ...filteredParams.map(param => param.type)], moduleName, localDeclaredNames);
|
|
284
456
|
const typeParamDecl = genericContext.declaration;
|
|
285
457
|
const tsTypeForValue = (p) => this.typeToTsFromPython(p.type, genericContext, 'value');
|
|
286
458
|
const kwargsType = (() => {
|
|
@@ -336,6 +508,8 @@ export class CodeGenerator {
|
|
|
336
508
|
const returnType = this.typeToTsFromPython(func.returnType, genericContext, 'return');
|
|
337
509
|
const fname = this.escapeIdentifier(func.name);
|
|
338
510
|
const moduleId = moduleName ?? '__main__';
|
|
511
|
+
const validatorName = `__validate${this.escapeIdentifier(func.name, { preserveCase: true })}Result`;
|
|
512
|
+
const returnValidator = `const ${validatorName} = createReturnValidator(${JSON.stringify(this.returnSchema(func.returnType, returnDefinitions))}, ${JSON.stringify(`${moduleId}.${func.name}`)}, __tywrapReturnDefinitions);\n\n`;
|
|
339
513
|
// Overloads: generate trailing optional parameter drop variants (exclude *args/**kwargs).
|
|
340
514
|
// Why: Python APIs frequently have many optional tail params. TypeScript callers expect
|
|
341
515
|
// `fn(a)`, `fn(a, b)`, ... all to typecheck. We emit a family of overloads that progressively
|
|
@@ -413,8 +587,8 @@ export class CodeGenerator {
|
|
|
413
587
|
const guards = guardLines.length > 0 ? `${guardLines.join('\n')}\n` : '';
|
|
414
588
|
const callPreludeLines = emitCallPrelude(callDescriptor, this.callEmitHelpers());
|
|
415
589
|
const callPrelude = callPreludeLines.length > 0 ? `${callPreludeLines.join('\n')}\n` : '';
|
|
416
|
-
const ts = `${jsdoc}${overloadDecl}export async function ${fname}${typeParamDecl}(${paramDecl}): Promise<${returnType}> {
|
|
417
|
-
${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.name}', __args${hasKwArgs ? '
|
|
590
|
+
const ts = `${jsdoc}${returnValidator}${overloadDecl}export async function ${fname}${typeParamDecl}(${paramDecl}): Promise<${returnType}> {
|
|
591
|
+
${callPrelude}${guards} return getRuntimeBridge().call<${returnType}>('${moduleId}', '${func.name}', __args, ${hasKwArgs ? '__kwargs' : 'undefined'}, ${validatorName});
|
|
418
592
|
}
|
|
419
593
|
`;
|
|
420
594
|
const declarationBody = overloads.length > 0
|
|
@@ -422,7 +596,9 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
|
|
|
422
596
|
: `export function ${fname}${typeParamDecl}(${paramDecl}): Promise<${returnType}>;\n`;
|
|
423
597
|
return this.wrap(ts, `${jsdoc}${declarationBody}`, [func.name]);
|
|
424
598
|
}
|
|
425
|
-
generateClassWrapper(cls, moduleName, _annotatedJSDoc = false) {
|
|
599
|
+
generateClassWrapper(cls, moduleName, _annotatedJSDoc = false, localDeclaredNames = new Set(), returnDefinitions = new Set()) {
|
|
600
|
+
const moduleDeclaredNames = new Set(localDeclaredNames);
|
|
601
|
+
moduleDeclaredNames.add(cls.name);
|
|
426
602
|
const jsdoc = this.generateJsDoc(cls.docstring);
|
|
427
603
|
const classGenericContext = this.buildGenericRenderContext(this.getTypeParameters(cls.typeParameters), [
|
|
428
604
|
...cls.properties.map(property => property.type),
|
|
@@ -431,11 +607,9 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
|
|
|
431
607
|
method.returnType,
|
|
432
608
|
...method.parameters.map(p => p.type),
|
|
433
609
|
]),
|
|
434
|
-
], moduleName);
|
|
610
|
+
], moduleName, moduleDeclaredNames);
|
|
435
611
|
const classTypeParamDecl = classGenericContext.declaration;
|
|
436
612
|
const cname = this.escapeIdentifier(cls.name);
|
|
437
|
-
const classSelfType = `${cname}${classGenericContext.typeArguments}`;
|
|
438
|
-
const tsValueType = (p) => this.typeToTsFromPython(p.type, classGenericContext, 'value');
|
|
439
613
|
const wrapAlias = (body) => {
|
|
440
614
|
const ts = `${jsdoc}export type ${cname}${classTypeParamDecl} = ${body}\n`;
|
|
441
615
|
return this.wrap(ts, ts, [cls.name]);
|
|
@@ -469,7 +643,7 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
|
|
|
469
643
|
.filter(m => m.name !== '__init__')
|
|
470
644
|
.map(m => {
|
|
471
645
|
const fparams = m.parameters.filter(p => p.name !== 'self' && p.name !== 'cls');
|
|
472
|
-
const methodOwnGenericContext = this.buildGenericRenderContext(this.getTypeParameters(m.typeParameters), [m.returnType, ...fparams.map(param => param.type)], moduleName);
|
|
646
|
+
const methodOwnGenericContext = this.buildGenericRenderContext(this.getTypeParameters(m.typeParameters), [m.returnType, ...fparams.map(param => param.type)], moduleName, moduleDeclaredNames);
|
|
473
647
|
const methodGenericContext = this.mergeGenericRenderContexts(classGenericContext, methodOwnGenericContext);
|
|
474
648
|
const methodTypeParamDecl = methodOwnGenericContext.declaration;
|
|
475
649
|
const paramsDecl = fparams
|
|
@@ -497,16 +671,14 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
|
|
|
497
671
|
const methodBodies = [];
|
|
498
672
|
const methodDeclarations = [];
|
|
499
673
|
sortedMethods
|
|
500
|
-
.filter(method => method.name !== '__init__'
|
|
674
|
+
.filter(method => method.name !== '__init__' &&
|
|
675
|
+
(method.methodKind === 'class' || method.methodKind === 'static'))
|
|
501
676
|
.forEach(method => {
|
|
502
|
-
//
|
|
503
|
-
// the
|
|
504
|
-
|
|
505
|
-
// their output stays byte-identical.
|
|
506
|
-
const isStatic = method.methodKind === 'class' || method.methodKind === 'static';
|
|
507
|
-
const staticPrefix = isStatic ? 'static ' : '';
|
|
677
|
+
// v0.9 wrappers expose only class/static methods. They route through
|
|
678
|
+
// the ordinary module call path and never retain process-local state.
|
|
679
|
+
const staticPrefix = 'static ';
|
|
508
680
|
const fparams = method.parameters.filter(p => p.name !== 'self' && p.name !== 'cls');
|
|
509
|
-
const methodOwnGenericContext = this.buildGenericRenderContext(this.getTypeParameters(method.typeParameters), [method.returnType, ...fparams.map(param => param.type)], moduleName);
|
|
681
|
+
const methodOwnGenericContext = this.buildGenericRenderContext(this.getTypeParameters(method.typeParameters), [method.returnType, ...fparams.map(param => param.type)], moduleName, moduleDeclaredNames);
|
|
510
682
|
const methodGenericContext = this.mergeGenericRenderContexts(classGenericContext, methodOwnGenericContext);
|
|
511
683
|
const methodTypeParamDecl = methodOwnGenericContext.declaration;
|
|
512
684
|
const methodTsValueType = (p) => this.typeToTsFromPython(p.type, methodGenericContext, 'value');
|
|
@@ -553,6 +725,8 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
|
|
|
553
725
|
const requiredKwOnlyNames = keywordOnlyParams.filter(p => !p.optional).map(p => p.name);
|
|
554
726
|
const returnType = this.typeToTsFromPython(method.returnType, methodGenericContext, 'return');
|
|
555
727
|
const mname = this.escapeIdentifier(method.name);
|
|
728
|
+
const validatorName = `__validate${this.escapeIdentifier(cls.name, { preserveCase: true })}${this.escapeIdentifier(method.name, { preserveCase: true })}Result`;
|
|
729
|
+
const returnValidator = `const ${validatorName} = createReturnValidator(${JSON.stringify(this.returnSchema(method.returnType, returnDefinitions))}, ${JSON.stringify(`${moduleId}.${cls.name}.${method.name}`)}, __tywrapReturnDefinitions);\n\n`;
|
|
556
730
|
const overloads = [];
|
|
557
731
|
if (needsKwargsParam && requiredKwOnlyNames.length > 0) {
|
|
558
732
|
const firstOptionalIndex = positionalParams.findIndex(p => p.optional);
|
|
@@ -589,154 +763,33 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
|
|
|
589
763
|
const callPrelude = callPreludeLines.length > 0 ? `${callPreludeLines.join('\n')}\n` : '';
|
|
590
764
|
const guardLines = emitArgGuards(callDescriptor);
|
|
591
765
|
const guards = guardLines.length > 0 ? `${guardLines.join('\n')}\n` : '';
|
|
592
|
-
const callExpr =
|
|
593
|
-
? `getRuntimeBridge().call('${moduleId}', '${cls.name}.${method.name}', __args${needsKwargsParam ? ', __kwargs' : ''})`
|
|
594
|
-
: `getRuntimeBridge().callMethod(this.__handle, '${method.name}', __args${needsKwargsParam ? ', __kwargs' : ''})`;
|
|
766
|
+
const callExpr = `getRuntimeBridge().call<${returnType}>('${moduleId}', '${cls.name}.${method.name}', __args, ${needsKwargsParam ? '__kwargs' : 'undefined'}, ${validatorName})`;
|
|
595
767
|
methodBodies.push(`${overloadDecl} ${staticPrefix}async ${mname}${methodTypeParamDecl}(${paramsDecl}): Promise<${returnType}> {
|
|
596
|
-
${callPrelude}${guards} return ${callExpr};
|
|
768
|
+
${returnValidator}${callPrelude}${guards} return ${callExpr};
|
|
597
769
|
}`);
|
|
598
770
|
methodDeclarations.push(`${overloadDecl}${overloads.length > 0 ? '' : ` ${staticPrefix}${mname}${methodTypeParamDecl}(${paramsDecl}): Promise<${returnType}>;\n`}`);
|
|
599
771
|
});
|
|
600
|
-
const init = cls.methods.find(m => m.name === '__init__');
|
|
601
|
-
const ctorSpec = (() => {
|
|
602
|
-
if (!init) {
|
|
603
|
-
return {
|
|
604
|
-
overloadDecl: '',
|
|
605
|
-
declaration: ` static create${classTypeParamDecl}(...args: unknown[]): Promise<${classSelfType}>;\n`,
|
|
606
|
-
paramsDecl: `...args: unknown[]`,
|
|
607
|
-
callPrelude: ` const __args: unknown[] = [...args];\n`,
|
|
608
|
-
hasKwargs: false,
|
|
609
|
-
guardLines: [],
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
|
-
const fparams = init.parameters.filter(p => p.name !== 'self' && p.name !== 'cls');
|
|
613
|
-
const keywordOnlyParams = fparams.filter(p => p.keywordOnly);
|
|
614
|
-
const positionalOnlyNames = fparams.filter(p => p.positionalOnly).map(p => p.name);
|
|
615
|
-
const hasVarKwArgs = fparams.some(p => p.kwArgs);
|
|
616
|
-
const needsKwargsParam = keywordOnlyParams.length > 0 || hasVarKwArgs;
|
|
617
|
-
const varArgsParam = fparams.find(p => p.varArgs);
|
|
618
|
-
const needsVarArgsArray = Boolean(varArgsParam) && needsKwargsParam;
|
|
619
|
-
const positionalParams = fparams.filter(p => !p.keywordOnly && !p.varArgs && !p.kwArgs);
|
|
620
|
-
const firstOptionalPosIndex = positionalParams.findIndex(p => p.optional);
|
|
621
|
-
const requiredPosCount = firstOptionalPosIndex >= 0 ? firstOptionalPosIndex : positionalParams.length;
|
|
622
|
-
const keywordOnlyNames = keywordOnlyParams.map(p => p.name);
|
|
623
|
-
const renderPositionalParam = (p, forceRequired = false) => {
|
|
624
|
-
const pname = this.escapeIdentifier(p.name);
|
|
625
|
-
const opt = !forceRequired && p.optional ? '?' : '';
|
|
626
|
-
return `${pname}${opt}: ${tsValueType(p)}`;
|
|
627
|
-
};
|
|
628
|
-
const kwargsType = (() => {
|
|
629
|
-
if (!needsKwargsParam) {
|
|
630
|
-
return '';
|
|
631
|
-
}
|
|
632
|
-
if (keywordOnlyParams.length === 0 && hasVarKwArgs) {
|
|
633
|
-
return 'Record<string, unknown>';
|
|
634
|
-
}
|
|
635
|
-
const props = keywordOnlyParams
|
|
636
|
-
.map(p => `${JSON.stringify(p.name)}${p.optional ? '?' : ''}: ${tsValueType(p)};`)
|
|
637
|
-
.join(' ');
|
|
638
|
-
const obj = `{ ${props} }`;
|
|
639
|
-
return hasVarKwArgs ? `(${obj} & Record<string, unknown>)` : obj;
|
|
640
|
-
})();
|
|
641
|
-
const paramsDeclParts = [];
|
|
642
|
-
positionalParams.forEach(p => {
|
|
643
|
-
paramsDeclParts.push(renderPositionalParam(p));
|
|
644
|
-
});
|
|
645
|
-
if (varArgsParam) {
|
|
646
|
-
const vname = this.escapeIdentifier(varArgsParam.name);
|
|
647
|
-
paramsDeclParts.push(needsVarArgsArray ? `${vname}?: unknown[]` : `...${vname}: unknown[]`);
|
|
648
|
-
}
|
|
649
|
-
if (needsKwargsParam) {
|
|
650
|
-
paramsDeclParts.push(`kwargs?: ${kwargsType}`);
|
|
651
|
-
}
|
|
652
|
-
const paramsDecl = paramsDeclParts.join(', ');
|
|
653
|
-
const requiredKwOnlyNames = keywordOnlyParams.filter(p => !p.optional).map(p => p.name);
|
|
654
|
-
const overloads = [];
|
|
655
|
-
if (needsKwargsParam && requiredKwOnlyNames.length > 0) {
|
|
656
|
-
const firstOptionalIndex = positionalParams.findIndex(p => p.optional);
|
|
657
|
-
const requiredPosCount = firstOptionalIndex >= 0 ? firstOptionalIndex : positionalParams.length;
|
|
658
|
-
for (let i = requiredPosCount; i <= positionalParams.length; i++) {
|
|
659
|
-
const head = positionalParams.slice(0, i).map(p => renderPositionalParam(p, true));
|
|
660
|
-
const rest = [];
|
|
661
|
-
if (varArgsParam) {
|
|
662
|
-
const vname = this.escapeIdentifier(varArgsParam.name);
|
|
663
|
-
rest.push(needsVarArgsArray ? `${vname}: unknown[] | undefined` : `...${vname}: unknown[]`);
|
|
664
|
-
}
|
|
665
|
-
rest.push(`kwargs: ${kwargsType}`);
|
|
666
|
-
overloads.push(` static create${classTypeParamDecl}(${[...head, ...rest].join(', ')}): Promise<${classSelfType}>;`);
|
|
667
|
-
if (varArgsParam && needsVarArgsArray) {
|
|
668
|
-
overloads.push(` static create${classTypeParamDecl}(${[...head, `kwargs: ${kwargsType}`].join(', ')}): Promise<${classSelfType}>;`);
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
const overloadDecl = overloads.length > 0 ? `${overloads.join('\n')}\n` : '';
|
|
673
|
-
const declaration = overloads.length > 0
|
|
674
|
-
? overloadDecl
|
|
675
|
-
: ` static create${classTypeParamDecl}(${paramsDecl}): Promise<${classSelfType}>;\n`;
|
|
676
|
-
const callDescriptor = {
|
|
677
|
-
positionalParams,
|
|
678
|
-
varArgsParam,
|
|
679
|
-
needsVarArgsArray,
|
|
680
|
-
hasKwArgs: needsKwargsParam,
|
|
681
|
-
hasVarKwArgs,
|
|
682
|
-
keywordOnlyNames,
|
|
683
|
-
requiredKwOnlyNames,
|
|
684
|
-
positionalOnlyNames,
|
|
685
|
-
requiredPosCount,
|
|
686
|
-
indent: ' ',
|
|
687
|
-
errorLabel: '__init__',
|
|
688
|
-
};
|
|
689
|
-
const callPreludeLines = emitCallPrelude(callDescriptor, this.callEmitHelpers());
|
|
690
|
-
const callPrelude = callPreludeLines.length > 0 ? `${callPreludeLines.join('\n')}\n` : '';
|
|
691
|
-
const guardLines = emitArgGuards(callDescriptor);
|
|
692
|
-
return {
|
|
693
|
-
overloadDecl,
|
|
694
|
-
declaration,
|
|
695
|
-
paramsDecl,
|
|
696
|
-
callPrelude,
|
|
697
|
-
hasKwargs: needsKwargsParam,
|
|
698
|
-
guardLines,
|
|
699
|
-
};
|
|
700
|
-
})();
|
|
701
|
-
// @property / functools.cached_property → TS getters returning Promise<T>.
|
|
702
|
-
// Reading the attribute fires the getter on the Python side; the bridge
|
|
703
|
-
// resolves it via callMethod with no args. Sorted for stable output.
|
|
704
|
-
const accessorBodies = [];
|
|
705
|
-
const accessorDeclarations = [];
|
|
706
|
-
const sortedAccessors = [...(cls.accessors ?? [])].sort((a, b) => a.name.localeCompare(b.name));
|
|
707
|
-
sortedAccessors.forEach(accessor => {
|
|
708
|
-
const aname = this.escapeIdentifier(accessor.name);
|
|
709
|
-
const accessorType = this.typeToTsFromPython(accessor.type, classGenericContext, 'return');
|
|
710
|
-
const jsdocAcc = this.generateJsDoc(accessor.docstring);
|
|
711
|
-
accessorBodies.push(`${jsdocAcc} get ${aname}(): Promise<${accessorType}> { return getRuntimeBridge().callMethod(this.__handle, '${accessor.name}', []); }`);
|
|
712
|
-
accessorDeclarations.push(`${jsdocAcc} get ${aname}(): Promise<${accessorType}>;\n`);
|
|
713
|
-
});
|
|
714
|
-
const accessorsImpl = accessorBodies.length > 0 ? `${accessorBodies.join('\n')}\n` : '';
|
|
715
|
-
const accessorsDecl = accessorDeclarations.length > 0 ? `${accessorDeclarations.join('')}` : '';
|
|
716
772
|
const methodsSection = methodBodies.length > 0 ? `\n${methodBodies.join('\n')}\n` : '\n';
|
|
717
773
|
const declarationMethodsSection = methodDeclarations.length > 0 ? `\n${methodDeclarations.join('')}\n` : '\n';
|
|
718
|
-
|
|
719
|
-
|
|
774
|
+
// The constructor counts: a class whose only member was __init__ loses
|
|
775
|
+
// create(), so it needs the migration note as much as one with methods.
|
|
776
|
+
const omittedInstanceMembers = cls.methods.some(method => method.methodKind !== 'class' && method.methodKind !== 'static') ||
|
|
777
|
+
(cls.accessors?.length ?? 0) > 0;
|
|
778
|
+
const migrationNote = omittedInstanceMembers
|
|
779
|
+
? ' // NOTE: Instance members are not generated in v0.9; migrate this API to value-returning module functions.\n'
|
|
780
|
+
: '';
|
|
720
781
|
const ts = `${jsdoc}export class ${cname}${classTypeParamDecl} {
|
|
721
|
-
|
|
722
|
-
private constructor(handle: string) { this.__handle = handle; }
|
|
723
|
-
${ctorSpec.overloadDecl} static async create${classTypeParamDecl}(${ctorSpec.paramsDecl}): Promise<${classSelfType}> {
|
|
724
|
-
${ctorSpec.callPrelude}${ctorGuards} const handle = await getRuntimeBridge().instantiate<string>('${moduleId}', '${cls.name}', __args${ctorSpec.hasKwargs ? ', __kwargs' : ''});
|
|
725
|
-
return ${newClassExpr};
|
|
726
|
-
}
|
|
727
|
-
static fromHandle${classTypeParamDecl}(handle: string): ${classSelfType} { return ${newClassExpr}; }${methodsSection}${accessorsImpl} async disposeHandle(): Promise<void> { await getRuntimeBridge().disposeInstance(this.__handle); }
|
|
782
|
+
${migrationNote}${methodsSection}
|
|
728
783
|
}
|
|
729
784
|
`;
|
|
730
785
|
const declaration = `${jsdoc}export class ${cname}${classTypeParamDecl} {
|
|
731
|
-
|
|
732
|
-
private constructor(handle: string);
|
|
733
|
-
${ctorSpec.declaration} static fromHandle${classTypeParamDecl}(handle: string): ${classSelfType};${declarationMethodsSection}${accessorsDecl} disposeHandle(): Promise<void>;
|
|
786
|
+
${migrationNote}${declarationMethodsSection}
|
|
734
787
|
}
|
|
735
788
|
`;
|
|
736
789
|
return this.wrap(ts, declaration, [cls.name]);
|
|
737
790
|
}
|
|
738
|
-
generateTypeAlias(alias, moduleName) {
|
|
739
|
-
const genericContext = this.buildGenericRenderContext(this.getTypeParameters(alias.typeParameters), [alias.type], moduleName);
|
|
791
|
+
generateTypeAlias(alias, moduleName, localDeclaredNames = new Set()) {
|
|
792
|
+
const genericContext = this.buildGenericRenderContext(this.getTypeParameters(alias.typeParameters), [alias.type], moduleName, localDeclaredNames);
|
|
740
793
|
const aliasName = this.escapeIdentifier(alias.name, { preserveCase: true });
|
|
741
794
|
const body = this.typeToTsFromPython(alias.type, genericContext, 'value');
|
|
742
795
|
const ts = `export type ${aliasName}${genericContext.declaration} = ${body}\n`;
|
|
@@ -761,15 +814,19 @@ ${ctorSpec.declaration} static fromHandle${classTypeParamDecl}(handle: string):
|
|
|
761
814
|
return result;
|
|
762
815
|
}
|
|
763
816
|
generateModuleDefinition(module, annotatedJSDoc = false) {
|
|
817
|
+
const localDeclaredNames = new Set([
|
|
818
|
+
...module.classes.map(cls => cls.name),
|
|
819
|
+
...(module.typeAliases ?? []).map(alias => alias.name),
|
|
820
|
+
]);
|
|
764
821
|
const functionResults = [...module.functions]
|
|
765
822
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
766
|
-
.map(f => this.generateFunctionWrapper(f, module.name, annotatedJSDoc));
|
|
823
|
+
.map(f => this.generateFunctionWrapper(f, module.name, annotatedJSDoc, localDeclaredNames, this.returnDefinitions(module)));
|
|
767
824
|
const classResults = [...module.classes]
|
|
768
825
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
769
|
-
.map(c => this.generateClassWrapper(c, module.name, annotatedJSDoc));
|
|
826
|
+
.map(c => this.generateClassWrapper(c, module.name, annotatedJSDoc, localDeclaredNames, this.returnDefinitions(module)));
|
|
770
827
|
const typeAliasResults = [...(module.typeAliases ?? [])]
|
|
771
828
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
772
|
-
.map(alias => this.generateTypeAlias(alias, module.name));
|
|
829
|
+
.map(alias => this.generateTypeAlias(alias, module.name, localDeclaredNames));
|
|
773
830
|
const functionCodes = functionResults.map(result => result.typescript).join('\n');
|
|
774
831
|
const classCodes = classResults.map(result => result.typescript).join('\n');
|
|
775
832
|
const typeAliasCodes = typeAliasResults.map(result => result.typescript).join('\n');
|
|
@@ -780,7 +837,9 @@ ${ctorSpec.declaration} static fromHandle${classTypeParamDecl}(handle: string):
|
|
|
780
837
|
return kind === 'class' && !c.decorators.includes('__typed_dict__');
|
|
781
838
|
});
|
|
782
839
|
const needsRuntime = module.functions.length > 0 || hasRuntimeClasses;
|
|
783
|
-
const bridgeDecl = needsRuntime
|
|
840
|
+
const bridgeDecl = needsRuntime
|
|
841
|
+
? `import { createReturnValidator, getRuntimeBridge, type ReturnSchema } from 'tywrap/runtime';\n\n${this.emitReturnDefinitions(module)}`
|
|
842
|
+
: '';
|
|
784
843
|
const ts = `${`${header}${bridgeDecl}${functionCodes}\n${classCodes}\n${typeAliasCodes}`.trimEnd()}\n`;
|
|
785
844
|
const declaration = `${`${declarationHeader}${functionResults
|
|
786
845
|
.map(result => result.declaration)
|
|
@@ -818,53 +877,67 @@ ${ctorSpec.declaration} static fromHandle${classTypeParamDecl}(handle: string):
|
|
|
818
877
|
},
|
|
819
878
|
};
|
|
820
879
|
}
|
|
821
|
-
typeToTs(type) {
|
|
880
|
+
typeToTs(type, ctx, mappingContext = 'value') {
|
|
822
881
|
switch (type.kind) {
|
|
823
882
|
case 'primitive':
|
|
824
883
|
return type.name;
|
|
825
884
|
case 'array':
|
|
826
|
-
return `${this.typeToTs(type.elementType)}[]`;
|
|
885
|
+
return `${this.typeToTs(type.elementType, ctx, mappingContext)}[]`;
|
|
827
886
|
case 'tuple': {
|
|
828
887
|
const t = type;
|
|
829
|
-
const parts = t.elementTypes.map(e => this.typeToTs(e)).join(', ');
|
|
888
|
+
const parts = t.elementTypes.map(e => this.typeToTs(e, ctx, mappingContext)).join(', ');
|
|
830
889
|
return `[${parts}]`;
|
|
831
890
|
}
|
|
832
891
|
case 'object': {
|
|
833
892
|
const t = type;
|
|
834
893
|
// If it's a simple Record<string, T> pattern, use that syntax
|
|
835
894
|
if (t.properties.length === 0 && t.indexSignature) {
|
|
836
|
-
const keyType = this.typeToTs(t.indexSignature.keyType);
|
|
837
|
-
const valueType = this.typeToTs(t.indexSignature.valueType);
|
|
895
|
+
const keyType = this.typeToTs(t.indexSignature.keyType, ctx, mappingContext);
|
|
896
|
+
const valueType = this.typeToTs(t.indexSignature.valueType, ctx, mappingContext);
|
|
838
897
|
if (keyType === 'string') {
|
|
839
898
|
return `Record<string, ${valueType}>`;
|
|
840
899
|
}
|
|
841
900
|
}
|
|
842
901
|
const props = t.properties
|
|
843
|
-
.map(p => `${p.readonly ? 'readonly ' : ''}${p.name}${p.optional ? '?' : ''}: ${this.typeToTs(p.type)};`)
|
|
902
|
+
.map(p => `${p.readonly ? 'readonly ' : ''}${p.name}${p.optional ? '?' : ''}: ${this.typeToTs(p.type, ctx, mappingContext)};`)
|
|
844
903
|
.join(' ');
|
|
845
904
|
const indexSig = t.indexSignature
|
|
846
|
-
? `[key: ${this.typeToTs(t.indexSignature.keyType)}]: ${this.typeToTs(t.indexSignature.valueType)};`
|
|
905
|
+
? `[key: ${this.typeToTs(t.indexSignature.keyType, ctx, mappingContext)}]: ${this.typeToTs(t.indexSignature.valueType, ctx, mappingContext)};`
|
|
847
906
|
: '';
|
|
848
907
|
return `{ ${props} ${indexSig} }`;
|
|
849
908
|
}
|
|
850
909
|
case 'union':
|
|
851
|
-
return type.types.map(t => this.typeToTs(t)).join(' | ');
|
|
910
|
+
return type.types.map(t => this.typeToTs(t, ctx, mappingContext)).join(' | ');
|
|
852
911
|
case 'function': {
|
|
853
912
|
const ft = type;
|
|
854
913
|
const params = ft.parameters
|
|
855
|
-
.map(p => `${p.rest ? '...' : ''}${p.name}${p.optional ? '?' : ''}: ${this.typeToTs(p.type)}`)
|
|
914
|
+
.map(p => `${p.rest ? '...' : ''}${p.name}${p.optional ? '?' : ''}: ${this.typeToTs(p.type, ctx, mappingContext)}`)
|
|
856
915
|
.join(', ');
|
|
857
|
-
return `(${params}) => ${this.typeToTs(ft.returnType)}`;
|
|
916
|
+
return `(${params}) => ${this.typeToTs(ft.returnType, ctx, mappingContext)}`;
|
|
858
917
|
}
|
|
859
918
|
case 'generic': {
|
|
860
919
|
const g = type;
|
|
861
|
-
|
|
920
|
+
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(g.name)) {
|
|
921
|
+
return this.degradeType(g);
|
|
922
|
+
}
|
|
923
|
+
if (mappingContext === 'return' && this.nonDecodableReturnGenerics.has(g.name)) {
|
|
924
|
+
return this.degradeType(g);
|
|
925
|
+
}
|
|
926
|
+
if (ctx && !this.builtinGenericNames.has(g.name) && !this.isLocalTypeIdentity(g, ctx)) {
|
|
927
|
+
return this.degradeType(g);
|
|
928
|
+
}
|
|
929
|
+
const args = g.typeArgs.map(a => this.typeToTs(a, ctx, mappingContext)).join(', ');
|
|
862
930
|
return `${g.name}<${args}>`;
|
|
863
931
|
}
|
|
864
932
|
case 'custom': {
|
|
865
933
|
const c = type;
|
|
866
934
|
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(c.name)) {
|
|
867
|
-
return
|
|
935
|
+
return this.degradeType(c);
|
|
936
|
+
}
|
|
937
|
+
if (ctx &&
|
|
938
|
+
!this.isLocalTypeIdentity(c, ctx) &&
|
|
939
|
+
!(c.module === 'typing' && ctx.emittedNames.has(c.name))) {
|
|
940
|
+
return this.degradeType(c);
|
|
868
941
|
}
|
|
869
942
|
return c.name;
|
|
870
943
|
}
|