tywrap 0.8.0 → 0.10.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 +42 -22
- package/SECURITY.md +38 -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 +254 -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 +3 -3
- package/dist/runtime/bridge-codec.d.ts.map +1 -1
- package/dist/runtime/bridge-codec.js +129 -62
- 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 +50 -0
- package/dist/runtime/validators.d.ts.map +1 -1
- package/dist/runtime/validators.js +154 -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 +17 -0
- package/dist/utils/codec.d.ts.map +1 -1
- package/dist/utils/codec.js +500 -85
- 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.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +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 +515 -187
- package/src/config/index.ts +11 -0
- package/src/core/annotation-parser.ts +7 -4
- package/src/core/generator.ts +330 -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 +158 -74
- 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 +212 -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 +749 -112
- 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/src/core/generator.ts
CHANGED
|
@@ -29,14 +29,44 @@ interface GenericRenderParam {
|
|
|
29
29
|
|
|
30
30
|
interface GenericRenderContext {
|
|
31
31
|
currentModule?: string;
|
|
32
|
+
localDeclaredNames: Set<string>;
|
|
32
33
|
declaration: string;
|
|
33
34
|
typeArguments: string;
|
|
34
35
|
emittedNames: Set<string>;
|
|
35
36
|
emittedParamSpecs: Set<string>;
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
type ReturnDefinitionNames = ReadonlySet<string>;
|
|
40
|
+
|
|
41
|
+
export interface CodeGeneratorOptions {
|
|
42
|
+
/** Reports a generated annotation that cannot be represented by emitted declarations. */
|
|
43
|
+
onTypeDegrade?: (typeName: string) => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
38
46
|
export class CodeGenerator {
|
|
39
47
|
private readonly mapper: TypeMapper;
|
|
48
|
+
private readonly onTypeDegrade?: (typeName: string) => void;
|
|
49
|
+
private readonly builtinGenericNames = new Set([
|
|
50
|
+
'Array',
|
|
51
|
+
'AsyncIterator',
|
|
52
|
+
'Generator',
|
|
53
|
+
'Iterable',
|
|
54
|
+
'Iterator',
|
|
55
|
+
'Promise',
|
|
56
|
+
'Record',
|
|
57
|
+
]);
|
|
58
|
+
// Iterator-protocol objects can never cross the bridge: the Python side
|
|
59
|
+
// rejects them loudly at serialization, so a return typed as one of these
|
|
60
|
+
// could never carry a value. Returns degrade to `unknown` (and count as a
|
|
61
|
+
// degrade for --fail-on-warn). Iterable/Sequence are NOT here — a list
|
|
62
|
+
// satisfies those annotations and decodes to an array, which structurally
|
|
63
|
+
// satisfies the emitted TS type.
|
|
64
|
+
private readonly nonDecodableReturnGenerics = new Set([
|
|
65
|
+
'Generator',
|
|
66
|
+
'AsyncGenerator',
|
|
67
|
+
'Iterator',
|
|
68
|
+
'AsyncIterator',
|
|
69
|
+
]);
|
|
40
70
|
private readonly reservedTsIdentifiers = new Set([
|
|
41
71
|
'default',
|
|
42
72
|
'delete',
|
|
@@ -66,8 +96,9 @@ export class CodeGenerator {
|
|
|
66
96
|
'false',
|
|
67
97
|
]);
|
|
68
98
|
|
|
69
|
-
constructor(mapper: TypeMapper = new TypeMapper()) {
|
|
99
|
+
constructor(mapper: TypeMapper = new TypeMapper(), options: CodeGeneratorOptions = {}) {
|
|
70
100
|
this.mapper = mapper;
|
|
101
|
+
this.onTypeDegrade = options.onTypeDegrade;
|
|
71
102
|
}
|
|
72
103
|
|
|
73
104
|
/**
|
|
@@ -140,7 +171,8 @@ export class CodeGenerator {
|
|
|
140
171
|
private buildGenericRenderContext(
|
|
141
172
|
typeParameters: readonly PythonGenericParameter[],
|
|
142
173
|
types: readonly PythonType[],
|
|
143
|
-
currentModule?: string
|
|
174
|
+
currentModule?: string,
|
|
175
|
+
localDeclaredNames: Set<string> = new Set()
|
|
144
176
|
): GenericRenderContext {
|
|
145
177
|
const callableParamSpecs = new Set<string>();
|
|
146
178
|
types.forEach(type => this.collectCallableParamSpecs(type, callableParamSpecs));
|
|
@@ -170,6 +202,7 @@ export class CodeGenerator {
|
|
|
170
202
|
|
|
171
203
|
return {
|
|
172
204
|
currentModule,
|
|
205
|
+
localDeclaredNames,
|
|
173
206
|
declaration:
|
|
174
207
|
emitted.length > 0 ? `<${emitted.map(param => param.declaration).join(', ')}>` : '',
|
|
175
208
|
typeArguments: emitted.length > 0 ? `<${emitted.map(param => param.name).join(', ')}>` : '',
|
|
@@ -184,6 +217,7 @@ export class CodeGenerator {
|
|
|
184
217
|
): GenericRenderContext {
|
|
185
218
|
return {
|
|
186
219
|
currentModule: inner.currentModule ?? outer.currentModule,
|
|
220
|
+
localDeclaredNames: inner.localDeclaredNames,
|
|
187
221
|
declaration: inner.declaration,
|
|
188
222
|
typeArguments: inner.typeArguments,
|
|
189
223
|
emittedNames: new Set([...outer.emittedNames, ...inner.emittedNames]),
|
|
@@ -308,7 +342,27 @@ export class CodeGenerator {
|
|
|
308
342
|
ctx: GenericRenderContext,
|
|
309
343
|
mappingContext: 'value' | 'return'
|
|
310
344
|
): string {
|
|
311
|
-
return this.typeToTs(
|
|
345
|
+
return this.typeToTs(
|
|
346
|
+
this.mapper.mapPythonType(this.sanitizeType(type, ctx), mappingContext),
|
|
347
|
+
ctx,
|
|
348
|
+
mappingContext
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
private isLocalTypeIdentity(
|
|
353
|
+
type: { name: string; module?: string },
|
|
354
|
+
ctx: GenericRenderContext
|
|
355
|
+
): boolean {
|
|
356
|
+
if (!ctx.localDeclaredNames.has(type.name)) {
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
return type.module === undefined || type.module === ctx.currentModule;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private degradeType(type: { name: string; module?: string }): string {
|
|
363
|
+
const identity = type.module ? `${type.module}.${type.name}` : type.name;
|
|
364
|
+
this.onTypeDegrade?.(identity);
|
|
365
|
+
return 'unknown';
|
|
312
366
|
}
|
|
313
367
|
|
|
314
368
|
private renderLooksLikeKwargsExpr(
|
|
@@ -357,10 +411,170 @@ export class CodeGenerator {
|
|
|
357
411
|
};
|
|
358
412
|
}
|
|
359
413
|
|
|
414
|
+
/**
|
|
415
|
+
* Preserve Python provenance in generated return validators instead of
|
|
416
|
+
* reconstructing a checker from the final TypeScript spelling. In particular
|
|
417
|
+
* pandas/NumPy values retain their marker contract after Arrow decoding.
|
|
418
|
+
*/
|
|
419
|
+
private returnSchema(type: PythonType, definitions: ReturnDefinitionNames = new Set()): unknown {
|
|
420
|
+
// A bare `-> None` return maps to TS void and validates nothing, but a
|
|
421
|
+
// NESTED None (union member, tuple element) is a real wire value — it
|
|
422
|
+
// decodes to null and must be checked as null, or a union containing it
|
|
423
|
+
// would carry an any-member and accept everything.
|
|
424
|
+
if (type.kind === 'primitive' && type.name === 'None') {
|
|
425
|
+
return { kind: 'any' };
|
|
426
|
+
}
|
|
427
|
+
const schema = (current: PythonType): unknown => {
|
|
428
|
+
switch (current.kind) {
|
|
429
|
+
case 'primitive':
|
|
430
|
+
return current.name === 'int' || current.name === 'float'
|
|
431
|
+
? { kind: 'primitive', type: 'number' }
|
|
432
|
+
: current.name === 'str'
|
|
433
|
+
? { kind: 'primitive', type: 'string' }
|
|
434
|
+
: current.name === 'bool'
|
|
435
|
+
? { kind: 'primitive', type: 'boolean' }
|
|
436
|
+
: current.name === 'bytes'
|
|
437
|
+
? { kind: 'primitive', type: 'Uint8Array' }
|
|
438
|
+
: current.name === 'None'
|
|
439
|
+
? { kind: 'primitive', type: 'null' }
|
|
440
|
+
: { kind: 'any' };
|
|
441
|
+
case 'literal':
|
|
442
|
+
return { kind: 'literal', value: current.value };
|
|
443
|
+
case 'annotated':
|
|
444
|
+
return schema(current.base);
|
|
445
|
+
case 'final':
|
|
446
|
+
case 'classvar':
|
|
447
|
+
return schema(current.type);
|
|
448
|
+
case 'optional':
|
|
449
|
+
return {
|
|
450
|
+
kind: 'union',
|
|
451
|
+
options: [schema(current.type), { kind: 'primitive', type: 'null' }],
|
|
452
|
+
};
|
|
453
|
+
case 'union':
|
|
454
|
+
return { kind: 'union', options: current.types.map(schema) };
|
|
455
|
+
case 'collection': {
|
|
456
|
+
if (current.name === 'tuple') {
|
|
457
|
+
if (current.itemTypes[1]?.kind === 'custom' && current.itemTypes[1].name === '...') {
|
|
458
|
+
return {
|
|
459
|
+
kind: 'array',
|
|
460
|
+
element: schema(current.itemTypes[0] ?? { kind: 'custom', name: 'Any' }),
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
return { kind: 'tuple', elements: current.itemTypes.map(schema) };
|
|
464
|
+
}
|
|
465
|
+
if (current.name === 'dict') {
|
|
466
|
+
return {
|
|
467
|
+
kind: 'record',
|
|
468
|
+
values: schema(current.itemTypes[1] ?? { kind: 'custom', name: 'Any' }),
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
return {
|
|
472
|
+
kind: 'array',
|
|
473
|
+
element: schema(current.itemTypes[0] ?? { kind: 'custom', name: 'Any' }),
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
case 'generic': {
|
|
477
|
+
const leaf = current.name.split('.').at(-1) ?? current.name;
|
|
478
|
+
if (leaf === 'NDArray' || leaf === 'ndarray') {
|
|
479
|
+
return { kind: 'marker', marker: 'ndarray' };
|
|
480
|
+
}
|
|
481
|
+
// Iterator/Generator are deliberately absent: those returns emit
|
|
482
|
+
// `unknown` (see nonDecodableReturnGenerics) and validate nothing.
|
|
483
|
+
if (['list', 'List', 'Sequence', 'Iterable', 'set', 'frozenset'].includes(leaf)) {
|
|
484
|
+
return {
|
|
485
|
+
kind: 'array',
|
|
486
|
+
element: schema(current.typeArgs[0] ?? { kind: 'custom', name: 'Any' }),
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
if (['dict', 'Dict', 'Mapping', 'MutableMapping'].includes(leaf)) {
|
|
490
|
+
return {
|
|
491
|
+
kind: 'record',
|
|
492
|
+
values: schema(current.typeArgs[1] ?? { kind: 'custom', name: 'Any' }),
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
return definitions.has(leaf) ? { kind: 'ref', name: leaf } : { kind: 'any' };
|
|
496
|
+
}
|
|
497
|
+
case 'custom': {
|
|
498
|
+
const leaf = current.name.split('.').at(-1) ?? current.name;
|
|
499
|
+
const full = `${current.module ?? ''}.${leaf}`;
|
|
500
|
+
if (leaf === 'None') {
|
|
501
|
+
return { kind: 'primitive', type: 'null' };
|
|
502
|
+
}
|
|
503
|
+
if (leaf === 'Any' || leaf === 'object' || leaf === 'NoReturn' || leaf === 'Never') {
|
|
504
|
+
return { kind: 'any' };
|
|
505
|
+
}
|
|
506
|
+
if (leaf === 'DataFrame' && (!current.module || current.module.startsWith('pandas'))) {
|
|
507
|
+
return { kind: 'marker', marker: 'dataframe' };
|
|
508
|
+
}
|
|
509
|
+
if (leaf === 'Series' && (!current.module || current.module.startsWith('pandas'))) {
|
|
510
|
+
return { kind: 'marker', marker: 'series' };
|
|
511
|
+
}
|
|
512
|
+
if (leaf === 'ndarray' || leaf === 'NDArray' || full.includes('numpy')) {
|
|
513
|
+
return { kind: 'marker', marker: 'ndarray' };
|
|
514
|
+
}
|
|
515
|
+
if (
|
|
516
|
+
['csr_matrix', 'csc_matrix', 'coo_matrix', 'spmatrix'].includes(leaf) &&
|
|
517
|
+
(!current.module || current.module.startsWith('scipy'))
|
|
518
|
+
) {
|
|
519
|
+
return { kind: 'marker', marker: 'scipy.sparse' };
|
|
520
|
+
}
|
|
521
|
+
if (leaf === 'Tensor' && (!current.module || current.module.startsWith('torch'))) {
|
|
522
|
+
return { kind: 'marker', marker: 'torch.tensor' };
|
|
523
|
+
}
|
|
524
|
+
if (
|
|
525
|
+
leaf === 'BaseEstimator' &&
|
|
526
|
+
(!current.module || current.module.startsWith('sklearn'))
|
|
527
|
+
) {
|
|
528
|
+
return { kind: 'marker', marker: 'sklearn.estimator' };
|
|
529
|
+
}
|
|
530
|
+
return definitions.has(leaf) ? { kind: 'ref', name: leaf } : { kind: 'any' };
|
|
531
|
+
}
|
|
532
|
+
case 'typevar':
|
|
533
|
+
case 'paramspec':
|
|
534
|
+
case 'paramspec_args':
|
|
535
|
+
case 'paramspec_kwargs':
|
|
536
|
+
case 'typevartuple':
|
|
537
|
+
case 'unpack':
|
|
538
|
+
case 'callable':
|
|
539
|
+
return { kind: 'any' };
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
return schema(type);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
private returnDefinitions(module: PythonModule): ReturnDefinitionNames {
|
|
546
|
+
return new Set(
|
|
547
|
+
module.classes
|
|
548
|
+
.filter(cls => cls.kind === 'typed_dict' || cls.decorators.includes('__typed_dict__'))
|
|
549
|
+
.map(cls => cls.name)
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
private emitReturnDefinitions(module: PythonModule): string {
|
|
554
|
+
const definitions = this.returnDefinitions(module);
|
|
555
|
+
const entries = module.classes
|
|
556
|
+
.filter(cls => cls.kind === 'typed_dict' || cls.decorators.includes('__typed_dict__'))
|
|
557
|
+
.map(cls => {
|
|
558
|
+
const fields = Object.fromEntries(
|
|
559
|
+
cls.properties.map(property => [
|
|
560
|
+
property.name,
|
|
561
|
+
{
|
|
562
|
+
schema: this.returnSchema(property.type, definitions),
|
|
563
|
+
optional: property.optional === true,
|
|
564
|
+
},
|
|
565
|
+
])
|
|
566
|
+
);
|
|
567
|
+
return [cls.name, { kind: 'record', fields }];
|
|
568
|
+
});
|
|
569
|
+
return `const __tywrapReturnDefinitions: Record<string, ReturnSchema> = ${JSON.stringify(Object.fromEntries(entries))};\n\n`;
|
|
570
|
+
}
|
|
571
|
+
|
|
360
572
|
generateFunctionWrapper(
|
|
361
573
|
func: PythonFunction,
|
|
362
574
|
moduleName?: string,
|
|
363
|
-
annotatedJSDoc = false
|
|
575
|
+
annotatedJSDoc = false,
|
|
576
|
+
localDeclaredNames: Set<string> = new Set(),
|
|
577
|
+
returnDefinitions: ReturnDefinitionNames = new Set()
|
|
364
578
|
): GeneratedCode {
|
|
365
579
|
const jsdoc = this.generateJsDoc(
|
|
366
580
|
func.docstring,
|
|
@@ -379,7 +593,8 @@ export class CodeGenerator {
|
|
|
379
593
|
const genericContext = this.buildGenericRenderContext(
|
|
380
594
|
this.getTypeParameters(func.typeParameters),
|
|
381
595
|
[func.returnType, ...filteredParams.map(param => param.type)],
|
|
382
|
-
moduleName
|
|
596
|
+
moduleName,
|
|
597
|
+
localDeclaredNames
|
|
383
598
|
);
|
|
384
599
|
const typeParamDecl = genericContext.declaration;
|
|
385
600
|
|
|
@@ -447,6 +662,8 @@ export class CodeGenerator {
|
|
|
447
662
|
const returnType = this.typeToTsFromPython(func.returnType, genericContext, 'return');
|
|
448
663
|
const fname = this.escapeIdentifier(func.name);
|
|
449
664
|
const moduleId = moduleName ?? '__main__';
|
|
665
|
+
const validatorName = `__validate${this.escapeIdentifier(func.name, { preserveCase: true })}Result`;
|
|
666
|
+
const returnValidator = `const ${validatorName} = createReturnValidator(${JSON.stringify(this.returnSchema(func.returnType, returnDefinitions))}, ${JSON.stringify(`${moduleId}.${func.name}`)}, __tywrapReturnDefinitions);\n\n`;
|
|
450
667
|
|
|
451
668
|
// Overloads: generate trailing optional parameter drop variants (exclude *args/**kwargs).
|
|
452
669
|
// Why: Python APIs frequently have many optional tail params. TypeScript callers expect
|
|
@@ -535,10 +752,8 @@ export class CodeGenerator {
|
|
|
535
752
|
const callPreludeLines = emitCallPrelude(callDescriptor, this.callEmitHelpers());
|
|
536
753
|
const callPrelude = callPreludeLines.length > 0 ? `${callPreludeLines.join('\n')}\n` : '';
|
|
537
754
|
|
|
538
|
-
const ts = `${jsdoc}${overloadDecl}export async function ${fname}${typeParamDecl}(${paramDecl}): Promise<${returnType}> {
|
|
539
|
-
${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.name}', __args${
|
|
540
|
-
hasKwArgs ? ', __kwargs' : ''
|
|
541
|
-
});
|
|
755
|
+
const ts = `${jsdoc}${returnValidator}${overloadDecl}export async function ${fname}${typeParamDecl}(${paramDecl}): Promise<${returnType}> {
|
|
756
|
+
${callPrelude}${guards} return getRuntimeBridge().call<${returnType}>('${moduleId}', '${func.name}', __args, ${hasKwArgs ? '__kwargs' : 'undefined'}, ${validatorName});
|
|
542
757
|
}
|
|
543
758
|
`;
|
|
544
759
|
|
|
@@ -553,8 +768,12 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
|
|
|
553
768
|
generateClassWrapper(
|
|
554
769
|
cls: PythonClass,
|
|
555
770
|
moduleName?: string,
|
|
556
|
-
_annotatedJSDoc = false
|
|
771
|
+
_annotatedJSDoc = false,
|
|
772
|
+
localDeclaredNames: Set<string> = new Set(),
|
|
773
|
+
returnDefinitions: ReturnDefinitionNames = new Set()
|
|
557
774
|
): GeneratedCode {
|
|
775
|
+
const moduleDeclaredNames = new Set(localDeclaredNames);
|
|
776
|
+
moduleDeclaredNames.add(cls.name);
|
|
558
777
|
const jsdoc = this.generateJsDoc(cls.docstring);
|
|
559
778
|
const classGenericContext = this.buildGenericRenderContext(
|
|
560
779
|
this.getTypeParameters(cls.typeParameters),
|
|
@@ -566,14 +785,11 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
|
|
|
566
785
|
...method.parameters.map(p => p.type),
|
|
567
786
|
]),
|
|
568
787
|
],
|
|
569
|
-
moduleName
|
|
788
|
+
moduleName,
|
|
789
|
+
moduleDeclaredNames
|
|
570
790
|
);
|
|
571
791
|
const classTypeParamDecl = classGenericContext.declaration;
|
|
572
792
|
const cname = this.escapeIdentifier(cls.name);
|
|
573
|
-
const classSelfType = `${cname}${classGenericContext.typeArguments}`;
|
|
574
|
-
const tsValueType = (p: (typeof cls.methods)[number]['parameters'][number]): string =>
|
|
575
|
-
this.typeToTsFromPython(p.type, classGenericContext, 'value');
|
|
576
|
-
|
|
577
793
|
const wrapAlias = (body: string): GeneratedCode => {
|
|
578
794
|
const ts = `${jsdoc}export type ${cname}${classTypeParamDecl} = ${body}\n`;
|
|
579
795
|
return this.wrap(ts, ts, [cls.name]);
|
|
@@ -621,7 +837,8 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
|
|
|
621
837
|
const methodOwnGenericContext = this.buildGenericRenderContext(
|
|
622
838
|
this.getTypeParameters(m.typeParameters),
|
|
623
839
|
[m.returnType, ...fparams.map(param => param.type)],
|
|
624
|
-
moduleName
|
|
840
|
+
moduleName,
|
|
841
|
+
moduleDeclaredNames
|
|
625
842
|
);
|
|
626
843
|
const methodGenericContext = this.mergeGenericRenderContexts(
|
|
627
844
|
classGenericContext,
|
|
@@ -659,19 +876,21 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
|
|
|
659
876
|
const methodDeclarations: string[] = [];
|
|
660
877
|
|
|
661
878
|
sortedMethods
|
|
662
|
-
.filter(
|
|
879
|
+
.filter(
|
|
880
|
+
method =>
|
|
881
|
+
method.name !== '__init__' &&
|
|
882
|
+
(method.methodKind === 'class' || method.methodKind === 'static')
|
|
883
|
+
)
|
|
663
884
|
.forEach(method => {
|
|
664
|
-
//
|
|
665
|
-
// the
|
|
666
|
-
|
|
667
|
-
// their output stays byte-identical.
|
|
668
|
-
const isStatic = method.methodKind === 'class' || method.methodKind === 'static';
|
|
669
|
-
const staticPrefix = isStatic ? 'static ' : '';
|
|
885
|
+
// v0.9 wrappers expose only class/static methods. They route through
|
|
886
|
+
// the ordinary module call path and never retain process-local state.
|
|
887
|
+
const staticPrefix = 'static ';
|
|
670
888
|
const fparams = method.parameters.filter(p => p.name !== 'self' && p.name !== 'cls');
|
|
671
889
|
const methodOwnGenericContext = this.buildGenericRenderContext(
|
|
672
890
|
this.getTypeParameters(method.typeParameters),
|
|
673
891
|
[method.returnType, ...fparams.map(param => param.type)],
|
|
674
|
-
moduleName
|
|
892
|
+
moduleName,
|
|
893
|
+
moduleDeclaredNames
|
|
675
894
|
);
|
|
676
895
|
const methodGenericContext = this.mergeGenericRenderContexts(
|
|
677
896
|
classGenericContext,
|
|
@@ -737,6 +956,8 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
|
|
|
737
956
|
'return'
|
|
738
957
|
);
|
|
739
958
|
const mname = this.escapeIdentifier(method.name);
|
|
959
|
+
const validatorName = `__validate${this.escapeIdentifier(cls.name, { preserveCase: true })}${this.escapeIdentifier(method.name, { preserveCase: true })}Result`;
|
|
960
|
+
const returnValidator = `const ${validatorName} = createReturnValidator(${JSON.stringify(this.returnSchema(method.returnType, returnDefinitions))}, ${JSON.stringify(`${moduleId}.${cls.name}.${method.name}`)}, __tywrapReturnDefinitions);\n\n`;
|
|
740
961
|
|
|
741
962
|
const overloads: string[] = [];
|
|
742
963
|
if (needsKwargsParam && requiredKwOnlyNames.length > 0) {
|
|
@@ -784,193 +1005,49 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
|
|
|
784
1005
|
const guardLines = emitArgGuards(callDescriptor);
|
|
785
1006
|
const guards = guardLines.length > 0 ? `${guardLines.join('\n')}\n` : '';
|
|
786
1007
|
|
|
787
|
-
const callExpr =
|
|
788
|
-
? `getRuntimeBridge().call('${moduleId}', '${cls.name}.${method.name}', __args${
|
|
789
|
-
needsKwargsParam ? ', __kwargs' : ''
|
|
790
|
-
})`
|
|
791
|
-
: `getRuntimeBridge().callMethod(this.__handle, '${method.name}', __args${
|
|
792
|
-
needsKwargsParam ? ', __kwargs' : ''
|
|
793
|
-
})`;
|
|
1008
|
+
const callExpr = `getRuntimeBridge().call<${returnType}>('${moduleId}', '${cls.name}.${method.name}', __args, ${needsKwargsParam ? '__kwargs' : 'undefined'}, ${validatorName})`;
|
|
794
1009
|
methodBodies.push(`${overloadDecl} ${staticPrefix}async ${mname}${methodTypeParamDecl}(${paramsDecl}): Promise<${returnType}> {
|
|
795
|
-
${callPrelude}${guards} return ${callExpr};
|
|
1010
|
+
${returnValidator}${callPrelude}${guards} return ${callExpr};
|
|
796
1011
|
}`);
|
|
797
1012
|
methodDeclarations.push(
|
|
798
1013
|
`${overloadDecl}${overloads.length > 0 ? '' : ` ${staticPrefix}${mname}${methodTypeParamDecl}(${paramsDecl}): Promise<${returnType}>;\n`}`
|
|
799
1014
|
);
|
|
800
1015
|
});
|
|
801
1016
|
|
|
802
|
-
const init = cls.methods.find(m => m.name === '__init__');
|
|
803
|
-
const ctorSpec = (() => {
|
|
804
|
-
if (!init) {
|
|
805
|
-
return {
|
|
806
|
-
overloadDecl: '',
|
|
807
|
-
declaration: ` static create${classTypeParamDecl}(...args: unknown[]): Promise<${classSelfType}>;\n`,
|
|
808
|
-
paramsDecl: `...args: unknown[]`,
|
|
809
|
-
callPrelude: ` const __args: unknown[] = [...args];\n`,
|
|
810
|
-
hasKwargs: false,
|
|
811
|
-
guardLines: [] as string[],
|
|
812
|
-
};
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
const fparams = init.parameters.filter(p => p.name !== 'self' && p.name !== 'cls');
|
|
816
|
-
const keywordOnlyParams = fparams.filter(p => p.keywordOnly);
|
|
817
|
-
const positionalOnlyNames = fparams.filter(p => p.positionalOnly).map(p => p.name);
|
|
818
|
-
const hasVarKwArgs = fparams.some(p => p.kwArgs);
|
|
819
|
-
const needsKwargsParam = keywordOnlyParams.length > 0 || hasVarKwArgs;
|
|
820
|
-
const varArgsParam = fparams.find(p => p.varArgs);
|
|
821
|
-
const needsVarArgsArray = Boolean(varArgsParam) && needsKwargsParam;
|
|
822
|
-
const positionalParams = fparams.filter(p => !p.keywordOnly && !p.varArgs && !p.kwArgs);
|
|
823
|
-
const firstOptionalPosIndex = positionalParams.findIndex(p => p.optional);
|
|
824
|
-
const requiredPosCount =
|
|
825
|
-
firstOptionalPosIndex >= 0 ? firstOptionalPosIndex : positionalParams.length;
|
|
826
|
-
const keywordOnlyNames = keywordOnlyParams.map(p => p.name);
|
|
827
|
-
|
|
828
|
-
const renderPositionalParam = (
|
|
829
|
-
p: (typeof positionalParams)[number],
|
|
830
|
-
forceRequired = false
|
|
831
|
-
): string => {
|
|
832
|
-
const pname = this.escapeIdentifier(p.name);
|
|
833
|
-
const opt = !forceRequired && p.optional ? '?' : '';
|
|
834
|
-
return `${pname}${opt}: ${tsValueType(p)}`;
|
|
835
|
-
};
|
|
836
|
-
|
|
837
|
-
const kwargsType = (() => {
|
|
838
|
-
if (!needsKwargsParam) {
|
|
839
|
-
return '';
|
|
840
|
-
}
|
|
841
|
-
if (keywordOnlyParams.length === 0 && hasVarKwArgs) {
|
|
842
|
-
return 'Record<string, unknown>';
|
|
843
|
-
}
|
|
844
|
-
const props = keywordOnlyParams
|
|
845
|
-
.map(p => `${JSON.stringify(p.name)}${p.optional ? '?' : ''}: ${tsValueType(p)};`)
|
|
846
|
-
.join(' ');
|
|
847
|
-
const obj = `{ ${props} }`;
|
|
848
|
-
return hasVarKwArgs ? `(${obj} & Record<string, unknown>)` : obj;
|
|
849
|
-
})();
|
|
850
|
-
|
|
851
|
-
const paramsDeclParts: string[] = [];
|
|
852
|
-
positionalParams.forEach(p => {
|
|
853
|
-
paramsDeclParts.push(renderPositionalParam(p));
|
|
854
|
-
});
|
|
855
|
-
if (varArgsParam) {
|
|
856
|
-
const vname = this.escapeIdentifier(varArgsParam.name);
|
|
857
|
-
paramsDeclParts.push(needsVarArgsArray ? `${vname}?: unknown[]` : `...${vname}: unknown[]`);
|
|
858
|
-
}
|
|
859
|
-
if (needsKwargsParam) {
|
|
860
|
-
paramsDeclParts.push(`kwargs?: ${kwargsType}`);
|
|
861
|
-
}
|
|
862
|
-
const paramsDecl = paramsDeclParts.join(', ');
|
|
863
|
-
|
|
864
|
-
const requiredKwOnlyNames = keywordOnlyParams.filter(p => !p.optional).map(p => p.name);
|
|
865
|
-
const overloads: string[] = [];
|
|
866
|
-
if (needsKwargsParam && requiredKwOnlyNames.length > 0) {
|
|
867
|
-
const firstOptionalIndex = positionalParams.findIndex(p => p.optional);
|
|
868
|
-
const requiredPosCount =
|
|
869
|
-
firstOptionalIndex >= 0 ? firstOptionalIndex : positionalParams.length;
|
|
870
|
-
for (let i = requiredPosCount; i <= positionalParams.length; i++) {
|
|
871
|
-
const head = positionalParams.slice(0, i).map(p => renderPositionalParam(p, true));
|
|
872
|
-
const rest: string[] = [];
|
|
873
|
-
if (varArgsParam) {
|
|
874
|
-
const vname = this.escapeIdentifier(varArgsParam.name);
|
|
875
|
-
rest.push(
|
|
876
|
-
needsVarArgsArray ? `${vname}: unknown[] | undefined` : `...${vname}: unknown[]`
|
|
877
|
-
);
|
|
878
|
-
}
|
|
879
|
-
rest.push(`kwargs: ${kwargsType}`);
|
|
880
|
-
overloads.push(
|
|
881
|
-
` static create${classTypeParamDecl}(${[...head, ...rest].join(', ')}): Promise<${classSelfType}>;`
|
|
882
|
-
);
|
|
883
|
-
if (varArgsParam && needsVarArgsArray) {
|
|
884
|
-
overloads.push(
|
|
885
|
-
` static create${classTypeParamDecl}(${[...head, `kwargs: ${kwargsType}`].join(', ')}): Promise<${classSelfType}>;`
|
|
886
|
-
);
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
const overloadDecl = overloads.length > 0 ? `${overloads.join('\n')}\n` : '';
|
|
891
|
-
const declaration =
|
|
892
|
-
overloads.length > 0
|
|
893
|
-
? overloadDecl
|
|
894
|
-
: ` static create${classTypeParamDecl}(${paramsDecl}): Promise<${classSelfType}>;\n`;
|
|
895
|
-
|
|
896
|
-
const callDescriptor: CallDescriptor = {
|
|
897
|
-
positionalParams,
|
|
898
|
-
varArgsParam,
|
|
899
|
-
needsVarArgsArray,
|
|
900
|
-
hasKwArgs: needsKwargsParam,
|
|
901
|
-
hasVarKwArgs,
|
|
902
|
-
keywordOnlyNames,
|
|
903
|
-
requiredKwOnlyNames,
|
|
904
|
-
positionalOnlyNames,
|
|
905
|
-
requiredPosCount,
|
|
906
|
-
indent: ' ',
|
|
907
|
-
errorLabel: '__init__',
|
|
908
|
-
};
|
|
909
|
-
const callPreludeLines = emitCallPrelude(callDescriptor, this.callEmitHelpers());
|
|
910
|
-
const callPrelude = callPreludeLines.length > 0 ? `${callPreludeLines.join('\n')}\n` : '';
|
|
911
|
-
const guardLines = emitArgGuards(callDescriptor);
|
|
912
|
-
|
|
913
|
-
return {
|
|
914
|
-
overloadDecl,
|
|
915
|
-
declaration,
|
|
916
|
-
paramsDecl,
|
|
917
|
-
callPrelude,
|
|
918
|
-
hasKwargs: needsKwargsParam,
|
|
919
|
-
guardLines,
|
|
920
|
-
};
|
|
921
|
-
})();
|
|
922
|
-
|
|
923
|
-
// @property / functools.cached_property → TS getters returning Promise<T>.
|
|
924
|
-
// Reading the attribute fires the getter on the Python side; the bridge
|
|
925
|
-
// resolves it via callMethod with no args. Sorted for stable output.
|
|
926
|
-
const accessorBodies: string[] = [];
|
|
927
|
-
const accessorDeclarations: string[] = [];
|
|
928
|
-
const sortedAccessors = [...(cls.accessors ?? [])].sort((a, b) => a.name.localeCompare(b.name));
|
|
929
|
-
sortedAccessors.forEach(accessor => {
|
|
930
|
-
const aname = this.escapeIdentifier(accessor.name);
|
|
931
|
-
const accessorType = this.typeToTsFromPython(accessor.type, classGenericContext, 'return');
|
|
932
|
-
const jsdocAcc = this.generateJsDoc(accessor.docstring);
|
|
933
|
-
accessorBodies.push(
|
|
934
|
-
`${jsdocAcc} get ${aname}(): Promise<${accessorType}> { return getRuntimeBridge().callMethod(this.__handle, '${accessor.name}', []); }`
|
|
935
|
-
);
|
|
936
|
-
accessorDeclarations.push(`${jsdocAcc} get ${aname}(): Promise<${accessorType}>;\n`);
|
|
937
|
-
});
|
|
938
|
-
const accessorsImpl = accessorBodies.length > 0 ? `${accessorBodies.join('\n')}\n` : '';
|
|
939
|
-
const accessorsDecl = accessorDeclarations.length > 0 ? `${accessorDeclarations.join('')}` : '';
|
|
940
|
-
|
|
941
1017
|
const methodsSection = methodBodies.length > 0 ? `\n${methodBodies.join('\n')}\n` : '\n';
|
|
942
1018
|
const declarationMethodsSection =
|
|
943
1019
|
methodDeclarations.length > 0 ? `\n${methodDeclarations.join('')}\n` : '\n';
|
|
944
|
-
|
|
945
|
-
|
|
1020
|
+
// The constructor counts: a class whose only member was __init__ loses
|
|
1021
|
+
// create(), so it needs the migration note as much as one with methods.
|
|
1022
|
+
const omittedInstanceMembers =
|
|
1023
|
+
cls.methods.some(method => method.methodKind !== 'class' && method.methodKind !== 'static') ||
|
|
1024
|
+
(cls.accessors?.length ?? 0) > 0;
|
|
1025
|
+
const migrationNote = omittedInstanceMembers
|
|
1026
|
+
? ' // NOTE: Instance members are not generated in v0.9; migrate this API to value-returning module functions.\n'
|
|
1027
|
+
: '';
|
|
946
1028
|
const ts = `${jsdoc}export class ${cname}${classTypeParamDecl} {
|
|
947
|
-
|
|
948
|
-
private constructor(handle: string) { this.__handle = handle; }
|
|
949
|
-
${ctorSpec.overloadDecl} static async create${classTypeParamDecl}(${ctorSpec.paramsDecl}): Promise<${classSelfType}> {
|
|
950
|
-
${ctorSpec.callPrelude}${ctorGuards} const handle = await getRuntimeBridge().instantiate<string>('${moduleId}', '${cls.name}', __args${
|
|
951
|
-
ctorSpec.hasKwargs ? ', __kwargs' : ''
|
|
952
|
-
});
|
|
953
|
-
return ${newClassExpr};
|
|
954
|
-
}
|
|
955
|
-
static fromHandle${classTypeParamDecl}(handle: string): ${classSelfType} { return ${newClassExpr}; }${methodsSection}${accessorsImpl} async disposeHandle(): Promise<void> { await getRuntimeBridge().disposeInstance(this.__handle); }
|
|
1029
|
+
${migrationNote}${methodsSection}
|
|
956
1030
|
}
|
|
957
1031
|
`;
|
|
958
1032
|
|
|
959
1033
|
const declaration = `${jsdoc}export class ${cname}${classTypeParamDecl} {
|
|
960
|
-
|
|
961
|
-
private constructor(handle: string);
|
|
962
|
-
${ctorSpec.declaration} static fromHandle${classTypeParamDecl}(handle: string): ${classSelfType};${declarationMethodsSection}${accessorsDecl} disposeHandle(): Promise<void>;
|
|
1034
|
+
${migrationNote}${declarationMethodsSection}
|
|
963
1035
|
}
|
|
964
1036
|
`;
|
|
965
1037
|
|
|
966
1038
|
return this.wrap(ts, declaration, [cls.name]);
|
|
967
1039
|
}
|
|
968
1040
|
|
|
969
|
-
generateTypeAlias(
|
|
1041
|
+
generateTypeAlias(
|
|
1042
|
+
alias: PythonTypeAlias,
|
|
1043
|
+
moduleName?: string,
|
|
1044
|
+
localDeclaredNames: Set<string> = new Set()
|
|
1045
|
+
): GeneratedCode {
|
|
970
1046
|
const genericContext = this.buildGenericRenderContext(
|
|
971
1047
|
this.getTypeParameters(alias.typeParameters),
|
|
972
1048
|
[alias.type],
|
|
973
|
-
moduleName
|
|
1049
|
+
moduleName,
|
|
1050
|
+
localDeclaredNames
|
|
974
1051
|
);
|
|
975
1052
|
const aliasName = this.escapeIdentifier(alias.name, { preserveCase: true });
|
|
976
1053
|
const body = this.typeToTsFromPython(alias.type, genericContext, 'value');
|
|
@@ -1004,15 +1081,35 @@ ${ctorSpec.declaration} static fromHandle${classTypeParamDecl}(handle: string):
|
|
|
1004
1081
|
}
|
|
1005
1082
|
|
|
1006
1083
|
generateModuleDefinition(module: PythonModule, annotatedJSDoc = false): GeneratedCode {
|
|
1084
|
+
const localDeclaredNames = new Set([
|
|
1085
|
+
...module.classes.map(cls => cls.name),
|
|
1086
|
+
...(module.typeAliases ?? []).map(alias => alias.name),
|
|
1087
|
+
]);
|
|
1007
1088
|
const functionResults = [...module.functions]
|
|
1008
1089
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
1009
|
-
.map(f =>
|
|
1090
|
+
.map(f =>
|
|
1091
|
+
this.generateFunctionWrapper(
|
|
1092
|
+
f,
|
|
1093
|
+
module.name,
|
|
1094
|
+
annotatedJSDoc,
|
|
1095
|
+
localDeclaredNames,
|
|
1096
|
+
this.returnDefinitions(module)
|
|
1097
|
+
)
|
|
1098
|
+
);
|
|
1010
1099
|
const classResults = [...module.classes]
|
|
1011
1100
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
1012
|
-
.map(c =>
|
|
1101
|
+
.map(c =>
|
|
1102
|
+
this.generateClassWrapper(
|
|
1103
|
+
c,
|
|
1104
|
+
module.name,
|
|
1105
|
+
annotatedJSDoc,
|
|
1106
|
+
localDeclaredNames,
|
|
1107
|
+
this.returnDefinitions(module)
|
|
1108
|
+
)
|
|
1109
|
+
);
|
|
1013
1110
|
const typeAliasResults = [...(module.typeAliases ?? [])]
|
|
1014
1111
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
1015
|
-
.map(alias => this.generateTypeAlias(alias, module.name));
|
|
1112
|
+
.map(alias => this.generateTypeAlias(alias, module.name, localDeclaredNames));
|
|
1016
1113
|
|
|
1017
1114
|
const functionCodes = functionResults.map(result => result.typescript).join('\n');
|
|
1018
1115
|
const classCodes = classResults.map(result => result.typescript).join('\n');
|
|
@@ -1025,7 +1122,9 @@ ${ctorSpec.declaration} static fromHandle${classTypeParamDecl}(handle: string):
|
|
|
1025
1122
|
return kind === 'class' && !c.decorators.includes('__typed_dict__');
|
|
1026
1123
|
});
|
|
1027
1124
|
const needsRuntime = module.functions.length > 0 || hasRuntimeClasses;
|
|
1028
|
-
const bridgeDecl = needsRuntime
|
|
1125
|
+
const bridgeDecl = needsRuntime
|
|
1126
|
+
? `import { createReturnValidator, getRuntimeBridge, type ReturnSchema } from 'tywrap/runtime';\n\n${this.emitReturnDefinitions(module)}`
|
|
1127
|
+
: '';
|
|
1029
1128
|
|
|
1030
1129
|
const ts = `${`${header}${bridgeDecl}${functionCodes}\n${classCodes}\n${typeAliasCodes}`.trimEnd()}\n`;
|
|
1031
1130
|
const declaration = `${`${declarationHeader}${functionResults
|
|
@@ -1067,23 +1166,27 @@ ${ctorSpec.declaration} static fromHandle${classTypeParamDecl}(handle: string):
|
|
|
1067
1166
|
};
|
|
1068
1167
|
}
|
|
1069
1168
|
|
|
1070
|
-
private typeToTs(
|
|
1169
|
+
private typeToTs(
|
|
1170
|
+
type: TypescriptType,
|
|
1171
|
+
ctx?: GenericRenderContext,
|
|
1172
|
+
mappingContext: 'value' | 'return' = 'value'
|
|
1173
|
+
): string {
|
|
1071
1174
|
switch (type.kind) {
|
|
1072
1175
|
case 'primitive':
|
|
1073
1176
|
return type.name;
|
|
1074
1177
|
case 'array':
|
|
1075
|
-
return `${this.typeToTs(type.elementType)}[]`;
|
|
1178
|
+
return `${this.typeToTs(type.elementType, ctx, mappingContext)}[]`;
|
|
1076
1179
|
case 'tuple': {
|
|
1077
1180
|
const t = type as { kind: 'tuple'; elementTypes: TypescriptType[] };
|
|
1078
|
-
const parts = t.elementTypes.map(e => this.typeToTs(e)).join(', ');
|
|
1181
|
+
const parts = t.elementTypes.map(e => this.typeToTs(e, ctx, mappingContext)).join(', ');
|
|
1079
1182
|
return `[${parts}]`;
|
|
1080
1183
|
}
|
|
1081
1184
|
case 'object': {
|
|
1082
1185
|
const t = type;
|
|
1083
1186
|
// If it's a simple Record<string, T> pattern, use that syntax
|
|
1084
1187
|
if (t.properties.length === 0 && t.indexSignature) {
|
|
1085
|
-
const keyType = this.typeToTs(t.indexSignature.keyType);
|
|
1086
|
-
const valueType = this.typeToTs(t.indexSignature.valueType);
|
|
1188
|
+
const keyType = this.typeToTs(t.indexSignature.keyType, ctx, mappingContext);
|
|
1189
|
+
const valueType = this.typeToTs(t.indexSignature.valueType, ctx, mappingContext);
|
|
1087
1190
|
if (keyType === 'string') {
|
|
1088
1191
|
return `Record<string, ${valueType}>`;
|
|
1089
1192
|
}
|
|
@@ -1092,34 +1195,56 @@ ${ctorSpec.declaration} static fromHandle${classTypeParamDecl}(handle: string):
|
|
|
1092
1195
|
const props = t.properties
|
|
1093
1196
|
.map(
|
|
1094
1197
|
p =>
|
|
1095
|
-
`${p.readonly ? 'readonly ' : ''}${p.name}${p.optional ? '?' : ''}: ${this.typeToTs(p.type)};`
|
|
1198
|
+
`${p.readonly ? 'readonly ' : ''}${p.name}${p.optional ? '?' : ''}: ${this.typeToTs(p.type, ctx, mappingContext)};`
|
|
1096
1199
|
)
|
|
1097
1200
|
.join(' ');
|
|
1098
1201
|
const indexSig = t.indexSignature
|
|
1099
|
-
? `[key: ${this.typeToTs(t.indexSignature.keyType)}]: ${this.typeToTs(t.indexSignature.valueType)};`
|
|
1202
|
+
? `[key: ${this.typeToTs(t.indexSignature.keyType, ctx, mappingContext)}]: ${this.typeToTs(t.indexSignature.valueType, ctx, mappingContext)};`
|
|
1100
1203
|
: '';
|
|
1101
1204
|
return `{ ${props} ${indexSig} }`;
|
|
1102
1205
|
}
|
|
1103
1206
|
case 'union':
|
|
1104
|
-
return type.types.map(t => this.typeToTs(t)).join(' | ');
|
|
1207
|
+
return type.types.map(t => this.typeToTs(t, ctx, mappingContext)).join(' | ');
|
|
1105
1208
|
case 'function': {
|
|
1106
1209
|
const ft = type;
|
|
1107
1210
|
const params = ft.parameters
|
|
1108
1211
|
.map(
|
|
1109
|
-
p =>
|
|
1212
|
+
p =>
|
|
1213
|
+
`${p.rest ? '...' : ''}${p.name}${p.optional ? '?' : ''}: ${this.typeToTs(p.type, ctx, mappingContext)}`
|
|
1110
1214
|
)
|
|
1111
1215
|
.join(', ');
|
|
1112
|
-
return `(${params}) => ${this.typeToTs(ft.returnType)}`;
|
|
1216
|
+
return `(${params}) => ${this.typeToTs(ft.returnType, ctx, mappingContext)}`;
|
|
1113
1217
|
}
|
|
1114
1218
|
case 'generic': {
|
|
1115
|
-
const g = type as {
|
|
1116
|
-
|
|
1219
|
+
const g = type as {
|
|
1220
|
+
kind: 'generic';
|
|
1221
|
+
name: string;
|
|
1222
|
+
module?: string;
|
|
1223
|
+
typeArgs: TypescriptType[];
|
|
1224
|
+
};
|
|
1225
|
+
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(g.name)) {
|
|
1226
|
+
return this.degradeType(g);
|
|
1227
|
+
}
|
|
1228
|
+
if (mappingContext === 'return' && this.nonDecodableReturnGenerics.has(g.name)) {
|
|
1229
|
+
return this.degradeType(g);
|
|
1230
|
+
}
|
|
1231
|
+
if (ctx && !this.builtinGenericNames.has(g.name) && !this.isLocalTypeIdentity(g, ctx)) {
|
|
1232
|
+
return this.degradeType(g);
|
|
1233
|
+
}
|
|
1234
|
+
const args = g.typeArgs.map(a => this.typeToTs(a, ctx, mappingContext)).join(', ');
|
|
1117
1235
|
return `${g.name}<${args}>`;
|
|
1118
1236
|
}
|
|
1119
1237
|
case 'custom': {
|
|
1120
|
-
const c = type as { kind: 'custom'; name: string };
|
|
1238
|
+
const c = type as { kind: 'custom'; name: string; module?: string };
|
|
1121
1239
|
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(c.name)) {
|
|
1122
|
-
return
|
|
1240
|
+
return this.degradeType(c);
|
|
1241
|
+
}
|
|
1242
|
+
if (
|
|
1243
|
+
ctx &&
|
|
1244
|
+
!this.isLocalTypeIdentity(c, ctx) &&
|
|
1245
|
+
!(c.module === 'typing' && ctx.emittedNames.has(c.name))
|
|
1246
|
+
) {
|
|
1247
|
+
return this.degradeType(c);
|
|
1123
1248
|
}
|
|
1124
1249
|
return c.name;
|
|
1125
1250
|
}
|