tywrap 0.1.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/LICENSE +21 -0
- package/README.md +319 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +263 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/index.d.ts +41 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +234 -0
- package/dist/config/index.js.map +1 -0
- package/dist/core/analyzer.d.ts +63 -0
- package/dist/core/analyzer.d.ts.map +1 -0
- package/dist/core/analyzer.js +676 -0
- package/dist/core/analyzer.js.map +1 -0
- package/dist/core/discovery.d.ts +85 -0
- package/dist/core/discovery.d.ts.map +1 -0
- package/dist/core/discovery.js +344 -0
- package/dist/core/discovery.js.map +1 -0
- package/dist/core/generator.d.ts +37 -0
- package/dist/core/generator.d.ts.map +1 -0
- package/dist/core/generator.js +368 -0
- package/dist/core/generator.js.map +1 -0
- package/dist/core/mapper.d.ts +40 -0
- package/dist/core/mapper.d.ts.map +1 -0
- package/dist/core/mapper.js +401 -0
- package/dist/core/mapper.js.map +1 -0
- package/dist/core/validation.d.ts +79 -0
- package/dist/core/validation.d.ts.map +1 -0
- package/dist/core/validation.js +486 -0
- package/dist/core/validation.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/base.d.ts +12 -0
- package/dist/runtime/base.d.ts.map +1 -0
- package/dist/runtime/base.js +6 -0
- package/dist/runtime/base.js.map +1 -0
- package/dist/runtime/errors.d.ts +17 -0
- package/dist/runtime/errors.d.ts.map +1 -0
- package/dist/runtime/errors.js +21 -0
- package/dist/runtime/errors.js.map +1 -0
- package/dist/runtime/http.d.ts +22 -0
- package/dist/runtime/http.d.ts.map +1 -0
- package/dist/runtime/http.js +79 -0
- package/dist/runtime/http.js.map +1 -0
- package/dist/runtime/index.d.ts +8 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +17 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/node.d.ts +48 -0
- package/dist/runtime/node.d.ts.map +1 -0
- package/dist/runtime/node.js +328 -0
- package/dist/runtime/node.js.map +1 -0
- package/dist/runtime/optimized-node.d.ts +131 -0
- package/dist/runtime/optimized-node.d.ts.map +1 -0
- package/dist/runtime/optimized-node.js +622 -0
- package/dist/runtime/optimized-node.js.map +1 -0
- package/dist/runtime/protocol.d.ts +3 -0
- package/dist/runtime/protocol.d.ts.map +1 -0
- package/dist/runtime/protocol.js +3 -0
- package/dist/runtime/protocol.js.map +1 -0
- package/dist/runtime/pyodide.d.ts +27 -0
- package/dist/runtime/pyodide.d.ts.map +1 -0
- package/dist/runtime/pyodide.js +191 -0
- package/dist/runtime/pyodide.js.map +1 -0
- package/dist/tools/python_suite.js +173 -0
- package/dist/tools/python_suite.js.map +1 -0
- package/dist/types/index.d.ts +299 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/tywrap.d.ts +29 -0
- package/dist/tywrap.d.ts.map +1 -0
- package/dist/tywrap.js +589 -0
- package/dist/tywrap.js.map +1 -0
- package/dist/utils/bundle-optimizer.d.ts +182 -0
- package/dist/utils/bundle-optimizer.d.ts.map +1 -0
- package/dist/utils/bundle-optimizer.js +680 -0
- package/dist/utils/bundle-optimizer.js.map +1 -0
- package/dist/utils/cache.d.ts +149 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +492 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/codec.d.ts +94 -0
- package/dist/utils/codec.d.ts.map +1 -0
- package/dist/utils/codec.js +150 -0
- package/dist/utils/codec.js.map +1 -0
- package/dist/utils/logger.d.ts +42 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +140 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/memory-profiler.d.ts +123 -0
- package/dist/utils/memory-profiler.d.ts.map +1 -0
- package/dist/utils/memory-profiler.js +426 -0
- package/dist/utils/memory-profiler.js.map +1 -0
- package/dist/utils/parallel-processor.d.ts +146 -0
- package/dist/utils/parallel-processor.d.ts.map +1 -0
- package/dist/utils/parallel-processor.js +636 -0
- package/dist/utils/parallel-processor.js.map +1 -0
- package/dist/utils/python.d.ts +8 -0
- package/dist/utils/python.d.ts.map +1 -0
- package/dist/utils/python.js +56 -0
- package/dist/utils/python.js.map +1 -0
- package/dist/utils/runtime.d.ts +153 -0
- package/dist/utils/runtime.d.ts.map +1 -0
- package/dist/utils/runtime.js +519 -0
- package/dist/utils/runtime.js.map +1 -0
- package/package.json +136 -0
- package/runtime/python_bridge.py +476 -0
- package/src/cli.ts +331 -0
- package/src/config/index.ts +289 -0
- package/src/core/analyzer.ts +788 -0
- package/src/core/discovery.ts +415 -0
- package/src/core/generator.ts +447 -0
- package/src/core/mapper.ts +504 -0
- package/src/core/validation.ts +693 -0
- package/src/index.ts +83 -0
- package/src/runtime/base.ts +32 -0
- package/src/runtime/errors.ts +19 -0
- package/src/runtime/http.ts +132 -0
- package/src/runtime/index.ts +24 -0
- package/src/runtime/node.ts +431 -0
- package/src/runtime/optimized-node.ts +838 -0
- package/src/runtime/protocol.ts +2 -0
- package/src/runtime/pyodide.ts +228 -0
- package/src/types/global.d.ts +60 -0
- package/src/types/index.ts +410 -0
- package/src/tywrap.ts +669 -0
- package/src/utils/bundle-optimizer.ts +876 -0
- package/src/utils/cache.ts +634 -0
- package/src/utils/codec.ts +275 -0
- package/src/utils/logger.ts +201 -0
- package/src/utils/memory-profiler.ts +582 -0
- package/src/utils/parallel-processor.ts +879 -0
- package/src/utils/python.ts +79 -0
- package/src/utils/runtime.ts +616 -0
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CodeGenerator - TypeScript wrapper generation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
PythonFunction,
|
|
7
|
+
PythonClass,
|
|
8
|
+
PythonModule,
|
|
9
|
+
GeneratedCode,
|
|
10
|
+
TypescriptType,
|
|
11
|
+
} from '../types/index.js';
|
|
12
|
+
import { globalCache } from '../utils/cache.js';
|
|
13
|
+
|
|
14
|
+
import { TypeMapper } from './mapper.js';
|
|
15
|
+
|
|
16
|
+
export class CodeGenerator {
|
|
17
|
+
private readonly mapper: TypeMapper;
|
|
18
|
+
private readonly reservedTsIdentifiers = new Set([
|
|
19
|
+
'default',
|
|
20
|
+
'delete',
|
|
21
|
+
'new',
|
|
22
|
+
'class',
|
|
23
|
+
'function',
|
|
24
|
+
'var',
|
|
25
|
+
'let',
|
|
26
|
+
'const',
|
|
27
|
+
'enum',
|
|
28
|
+
'export',
|
|
29
|
+
'import',
|
|
30
|
+
'return',
|
|
31
|
+
'extends',
|
|
32
|
+
'implements',
|
|
33
|
+
'interface',
|
|
34
|
+
'package',
|
|
35
|
+
'private',
|
|
36
|
+
'protected',
|
|
37
|
+
'public',
|
|
38
|
+
'static',
|
|
39
|
+
'yield',
|
|
40
|
+
'await',
|
|
41
|
+
'async',
|
|
42
|
+
'null',
|
|
43
|
+
'true',
|
|
44
|
+
'false',
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
constructor(mapper: TypeMapper = new TypeMapper()) {
|
|
48
|
+
this.mapper = mapper;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Convert Python snake_case to TypeScript camelCase and escape reserved words
|
|
53
|
+
*/
|
|
54
|
+
private escapeIdentifier(name: string, options: { preserveCase?: boolean } = {}): string {
|
|
55
|
+
if (!name) {
|
|
56
|
+
return '_';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// First, normalize unicode characters
|
|
60
|
+
let safe = this.normalizeUnicode(name);
|
|
61
|
+
|
|
62
|
+
// Then handle special characters and make it a valid identifier
|
|
63
|
+
safe = safe.replace(/[^a-zA-Z0-9_]/g, '_');
|
|
64
|
+
if (/^[0-9]/.test(safe)) {
|
|
65
|
+
safe = `_${safe}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Convert snake_case to camelCase unless preserveCase is true
|
|
69
|
+
if (!options.preserveCase) {
|
|
70
|
+
safe = this.toCamelCase(safe);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Check for reserved words after conversion
|
|
74
|
+
if (this.reservedTsIdentifiers.has(safe)) {
|
|
75
|
+
return `_${safe}_`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return safe;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Convert snake_case to camelCase
|
|
83
|
+
*/
|
|
84
|
+
private toCamelCase(str: string): string {
|
|
85
|
+
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Convert unicode characters to ASCII equivalents for better compatibility
|
|
90
|
+
*/
|
|
91
|
+
private normalizeUnicode(str: string): string {
|
|
92
|
+
// Basic unicode normalization - convert accented characters to ASCII equivalents
|
|
93
|
+
return str
|
|
94
|
+
.normalize('NFD')
|
|
95
|
+
.replace(/[\u0300-\u036f]/g, '') // Remove diacritics
|
|
96
|
+
.replace(/[^\x00-\x7F]/g, char => {
|
|
97
|
+
// Map common unicode characters to ASCII equivalents
|
|
98
|
+
const unicodeMap = new Map([
|
|
99
|
+
['ñ', 'n'],
|
|
100
|
+
['ü', 'u'],
|
|
101
|
+
['ß', 'ss'],
|
|
102
|
+
['æ', 'ae'],
|
|
103
|
+
['œ', 'oe'],
|
|
104
|
+
['ø', 'o'],
|
|
105
|
+
['€', 'euro'],
|
|
106
|
+
// Add more mappings as needed
|
|
107
|
+
]);
|
|
108
|
+
return unicodeMap.get(char) ?? char.charCodeAt(0).toString(16);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
generateFunctionWrapper(
|
|
113
|
+
func: PythonFunction,
|
|
114
|
+
moduleName?: string,
|
|
115
|
+
annotatedJSDoc = false
|
|
116
|
+
): GeneratedCode {
|
|
117
|
+
const jsdoc = this.generateJsDoc(
|
|
118
|
+
func.docstring,
|
|
119
|
+
annotatedJSDoc ? func.parameters.map(p => String(p.type)) : undefined
|
|
120
|
+
);
|
|
121
|
+
const filteredParams = func.parameters.filter(p => p.name !== 'self' && p.name !== 'cls');
|
|
122
|
+
const renderParam = (p: (typeof filteredParams)[number]): string => {
|
|
123
|
+
const pname = this.escapeIdentifier(p.name);
|
|
124
|
+
if (p.varArgs) {
|
|
125
|
+
return `...${pname}: unknown[]`;
|
|
126
|
+
}
|
|
127
|
+
if (p.kwArgs) {
|
|
128
|
+
return `kwargs?: Record<string, unknown>`;
|
|
129
|
+
}
|
|
130
|
+
return `${pname}${p.optional ? '?' : ''}: ${this.typeToTs(
|
|
131
|
+
this.mapper.mapPythonType(p.type, 'value')
|
|
132
|
+
)}`;
|
|
133
|
+
};
|
|
134
|
+
const paramDecl = filteredParams.map(renderParam).join(', ');
|
|
135
|
+
const positionalParams = filteredParams.filter(p => !p.kwArgs);
|
|
136
|
+
const callArgs = positionalParams
|
|
137
|
+
.map(p => (p.varArgs ? `...${this.escapeIdentifier(p.name)}` : this.escapeIdentifier(p.name)))
|
|
138
|
+
.join(', ');
|
|
139
|
+
const hasKwArgs = filteredParams.some(p => p.kwArgs);
|
|
140
|
+
const returnType = this.typeToTs(this.mapper.mapPythonType(func.returnType, 'return'));
|
|
141
|
+
const fname = this.escapeIdentifier(func.name);
|
|
142
|
+
const moduleId = moduleName ?? '__main__';
|
|
143
|
+
|
|
144
|
+
// Overloads: generate trailing optional parameter drop variants (exclude *args/**kwargs)
|
|
145
|
+
const positional = filteredParams.filter(p => !p.varArgs && !p.kwArgs);
|
|
146
|
+
const firstOptionalIndex = positional.findIndex(p => p.optional);
|
|
147
|
+
const overloads: string[] = [];
|
|
148
|
+
if (firstOptionalIndex >= 0) {
|
|
149
|
+
for (let i = firstOptionalIndex; i < positional.length; i++) {
|
|
150
|
+
const head = positional.slice(0, i);
|
|
151
|
+
const rest = filteredParams.filter(p => p.varArgs || p.kwArgs);
|
|
152
|
+
const sigParams = [...head, ...rest].map(renderParam).join(', ');
|
|
153
|
+
overloads.push(`export function ${fname}(${sigParams}): Promise<${returnType}>;`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const overloadDecl = overloads.length > 0 ? `${overloads.join('\n')}\n` : '';
|
|
158
|
+
const ts = `${jsdoc}${overloadDecl}export async function ${fname}(${paramDecl}): Promise<${returnType}> {
|
|
159
|
+
return getRuntimeBridge().call('${moduleId}', '${func.name}', [${callArgs}]${hasKwArgs ? ', kwargs' : ''});
|
|
160
|
+
}
|
|
161
|
+
`;
|
|
162
|
+
|
|
163
|
+
return this.wrap(ts, [func.name]);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
generateClassWrapper(
|
|
167
|
+
cls: PythonClass,
|
|
168
|
+
moduleName?: string,
|
|
169
|
+
_annotatedJSDoc = false
|
|
170
|
+
): GeneratedCode {
|
|
171
|
+
const jsdoc = this.generateJsDoc(cls.docstring);
|
|
172
|
+
// Structural type aliases for special kinds
|
|
173
|
+
if (cls.decorators.includes('__typed_dict__') || cls.kind === 'typed_dict') {
|
|
174
|
+
const props = cls.properties
|
|
175
|
+
.map(p => {
|
|
176
|
+
const pname = this.escapeIdentifier(p.name);
|
|
177
|
+
const opt = (p as unknown as { optional?: boolean }).optional === true ? '?' : '';
|
|
178
|
+
const t = this.typeToTs(this.mapper.mapPythonType(p.type, 'value'));
|
|
179
|
+
return `${pname}${opt}: ${t};`;
|
|
180
|
+
})
|
|
181
|
+
.join(' ');
|
|
182
|
+
const cname = this.escapeIdentifier(cls.name);
|
|
183
|
+
const ts = `${jsdoc}export type ${cname} = { ${props} }\n`;
|
|
184
|
+
return this.wrap(ts, [cls.name]);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (cls.kind === 'namedtuple') {
|
|
188
|
+
// NamedTuple -> readonly tuple type alias `[T1, T2, ...]`
|
|
189
|
+
const elements = cls.properties.map(p =>
|
|
190
|
+
this.typeToTs(this.mapper.mapPythonType(p.type, 'value'))
|
|
191
|
+
);
|
|
192
|
+
const cname = this.escapeIdentifier(cls.name);
|
|
193
|
+
const ts = `${jsdoc}export type ${cname} = readonly [${elements.join(', ')}]\n`;
|
|
194
|
+
return this.wrap(ts, [cls.name]);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (cls.kind === 'protocol') {
|
|
198
|
+
// Protocol -> structural interface-like type alias for attributes and callables (subset)
|
|
199
|
+
const props = cls.properties
|
|
200
|
+
.map(
|
|
201
|
+
p =>
|
|
202
|
+
`${this.escapeIdentifier(p.name)}: ${this.typeToTs(this.mapper.mapPythonType(p.type, 'value'))};`
|
|
203
|
+
)
|
|
204
|
+
.join(' ');
|
|
205
|
+
const methods = cls.methods
|
|
206
|
+
.map(m => {
|
|
207
|
+
const fparams = m.parameters.filter(p => p.name !== 'self' && p.name !== 'cls');
|
|
208
|
+
const paramsDecl = fparams
|
|
209
|
+
.map(
|
|
210
|
+
p =>
|
|
211
|
+
`${this.escapeIdentifier(p.name)}${p.optional ? '?' : ''}: ${this.typeToTs(this.mapper.mapPythonType(p.type, 'value'))}`
|
|
212
|
+
)
|
|
213
|
+
.join(', ');
|
|
214
|
+
const returnType = this.typeToTs(this.mapper.mapPythonType(m.returnType, 'return'));
|
|
215
|
+
return `${this.escapeIdentifier(m.name)}: (${paramsDecl}) => ${returnType};`;
|
|
216
|
+
})
|
|
217
|
+
.join(' ');
|
|
218
|
+
const cname = this.escapeIdentifier(cls.name);
|
|
219
|
+
const ts = `${jsdoc}export type ${cname} = { ${props} ${methods} }\n`;
|
|
220
|
+
return this.wrap(ts, [cls.name]);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (cls.kind === 'dataclass' || cls.kind === 'pydantic') {
|
|
224
|
+
// Data containers -> object type alias
|
|
225
|
+
const props = cls.properties
|
|
226
|
+
.map(p => {
|
|
227
|
+
const pname = this.escapeIdentifier(p.name);
|
|
228
|
+
const opt = (p as unknown as { optional?: boolean }).optional === true ? '?' : '';
|
|
229
|
+
const t = this.typeToTs(this.mapper.mapPythonType(p.type, 'value'));
|
|
230
|
+
return `${pname}${opt}: ${t};`;
|
|
231
|
+
})
|
|
232
|
+
.join(' ');
|
|
233
|
+
const cname = this.escapeIdentifier(cls.name);
|
|
234
|
+
const ts = `${jsdoc}export type ${cname} = { ${props} }\n`;
|
|
235
|
+
return this.wrap(ts, [cls.name]);
|
|
236
|
+
}
|
|
237
|
+
const sortedMethods = [...cls.methods].sort((a, b) => a.name.localeCompare(b.name));
|
|
238
|
+
const methodBodies = sortedMethods
|
|
239
|
+
.filter(m => m.name !== '__init__')
|
|
240
|
+
.map(m => {
|
|
241
|
+
const fparams = m.parameters.filter(p => p.name !== 'self' && p.name !== 'cls');
|
|
242
|
+
const paramsDecl = fparams
|
|
243
|
+
.map(p =>
|
|
244
|
+
p.varArgs
|
|
245
|
+
? `...${this.escapeIdentifier(p.name)}: unknown[]`
|
|
246
|
+
: p.kwArgs
|
|
247
|
+
? `kwargs?: Record<string, unknown>`
|
|
248
|
+
: `${this.escapeIdentifier(p.name)}${p.optional ? '?' : ''}: ${this.typeToTs(this.mapper.mapPythonType(p.type, 'value'))}`
|
|
249
|
+
)
|
|
250
|
+
.join(', ');
|
|
251
|
+
const positionalParams = fparams.filter(p => !p.kwArgs);
|
|
252
|
+
const callPassthrough = positionalParams
|
|
253
|
+
.map(p =>
|
|
254
|
+
p.varArgs ? `...${this.escapeIdentifier(p.name)}` : this.escapeIdentifier(p.name)
|
|
255
|
+
)
|
|
256
|
+
.join(', ');
|
|
257
|
+
const hasKwArgs = fparams.some(p => p.kwArgs);
|
|
258
|
+
const returnType = this.typeToTs(this.mapper.mapPythonType(m.returnType, 'return'));
|
|
259
|
+
return ` async ${this.escapeIdentifier(m.name)}(${paramsDecl}): Promise<${returnType}> { return getRuntimeBridge().callMethod(this.__handle, '${m.name}', [${callPassthrough}]${hasKwArgs ? ', kwargs' : ''}); }`;
|
|
260
|
+
})
|
|
261
|
+
.join('\n');
|
|
262
|
+
|
|
263
|
+
// Constructor typing from __init__
|
|
264
|
+
const init = cls.methods.find(m => m.name === '__init__');
|
|
265
|
+
const ctorParams = init
|
|
266
|
+
? init.parameters
|
|
267
|
+
.filter(p => p.name !== 'self' && p.name !== 'cls')
|
|
268
|
+
.map(p =>
|
|
269
|
+
p.varArgs
|
|
270
|
+
? `...${this.escapeIdentifier(p.name)}: unknown[]`
|
|
271
|
+
: p.kwArgs
|
|
272
|
+
? `kwargs?: Record<string, unknown>`
|
|
273
|
+
: `${this.escapeIdentifier(p.name)}${p.optional ? '?' : ''}: ${this.typeToTs(this.mapper.mapPythonType(p.type, 'value'))}`
|
|
274
|
+
)
|
|
275
|
+
.join(', ')
|
|
276
|
+
: `...args: unknown[]`;
|
|
277
|
+
|
|
278
|
+
const ctorHasKwArgs = init
|
|
279
|
+
? init.parameters.some(p => p.name !== 'self' && p.name !== 'cls' && p.kwArgs)
|
|
280
|
+
: false;
|
|
281
|
+
|
|
282
|
+
const ctorArgsPassthrough = init
|
|
283
|
+
? init.parameters
|
|
284
|
+
.filter(p => p.name !== 'self' && p.name !== 'cls' && !p.kwArgs)
|
|
285
|
+
.map(p =>
|
|
286
|
+
p.varArgs ? `...${this.escapeIdentifier(p.name)}` : this.escapeIdentifier(p.name)
|
|
287
|
+
)
|
|
288
|
+
.join(', ')
|
|
289
|
+
: '...args';
|
|
290
|
+
|
|
291
|
+
const cname = this.escapeIdentifier(cls.name);
|
|
292
|
+
const moduleId = moduleName ?? '__main__';
|
|
293
|
+
const methodsSection = methodBodies ? `\n${methodBodies}\n` : '\n';
|
|
294
|
+
const ts = `${jsdoc}export class ${cname} {
|
|
295
|
+
private readonly __handle: string;
|
|
296
|
+
private constructor(handle: string) { this.__handle = handle; }
|
|
297
|
+
static async create(${ctorParams}): Promise<${cname}> {
|
|
298
|
+
const handle = await getRuntimeBridge().instantiate<string>('${moduleId}', '${cls.name}', [${ctorArgsPassthrough}]${ctorHasKwArgs ? ', kwargs' : ''});
|
|
299
|
+
return new ${cname}(handle);
|
|
300
|
+
}
|
|
301
|
+
static fromHandle(handle: string): ${cname} { return new ${cname}(handle); }${methodsSection} async disposeHandle(): Promise<void> { await getRuntimeBridge().disposeInstance(this.__handle); }
|
|
302
|
+
}
|
|
303
|
+
`;
|
|
304
|
+
|
|
305
|
+
return this.wrap(ts, [cls.name]);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Generate TypeScript wrapper for Python module with caching
|
|
310
|
+
*/
|
|
311
|
+
async generateModule(
|
|
312
|
+
module: PythonModule,
|
|
313
|
+
options: { moduleName: string; exportAll?: boolean; annotatedJSDoc?: boolean } = {
|
|
314
|
+
moduleName: 'unknown',
|
|
315
|
+
}
|
|
316
|
+
): Promise<GeneratedCode> {
|
|
317
|
+
// Check cache first
|
|
318
|
+
const cached = await globalCache.getCachedGeneration(module, options);
|
|
319
|
+
if (cached) {
|
|
320
|
+
return cached;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const startTime = performance.now();
|
|
324
|
+
const result = this.generateModuleDefinition(module, options.annotatedJSDoc);
|
|
325
|
+
const computeTime = performance.now() - startTime;
|
|
326
|
+
|
|
327
|
+
// Cache the result
|
|
328
|
+
await globalCache.setCachedGeneration(module, options, result, computeTime);
|
|
329
|
+
|
|
330
|
+
return result;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
generateModuleDefinition(module: PythonModule, annotatedJSDoc = false): GeneratedCode {
|
|
334
|
+
const functionCodes = [...module.functions]
|
|
335
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
336
|
+
.map(f => this.generateFunctionWrapper(f, module.name, annotatedJSDoc).typescript)
|
|
337
|
+
.join('\n');
|
|
338
|
+
const classCodes = [...module.classes]
|
|
339
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
340
|
+
.map(c => this.generateClassWrapper(c, module.name, annotatedJSDoc).typescript)
|
|
341
|
+
.join('\n');
|
|
342
|
+
|
|
343
|
+
const header = `// Generated by tywrap\n// Module: ${module.name}\n// DO NOT EDIT MANUALLY\n\n`;
|
|
344
|
+
const hasRuntimeClasses = module.classes.some(c => {
|
|
345
|
+
const kind = c.kind ?? 'class';
|
|
346
|
+
return kind === 'class' && !c.decorators.includes('__typed_dict__');
|
|
347
|
+
});
|
|
348
|
+
const needsRuntime = module.functions.length > 0 || hasRuntimeClasses;
|
|
349
|
+
const bridgeDecl = needsRuntime ? `import { getRuntimeBridge } from 'tywrap/runtime';\n\n` : '';
|
|
350
|
+
|
|
351
|
+
const ts = `${header + bridgeDecl + functionCodes}\n${classCodes}`;
|
|
352
|
+
return this.wrap(ts, [module.name]);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
private generateJsDoc(doc?: string, paramAnnotations?: readonly string[]): string {
|
|
356
|
+
const lines: string[] = [];
|
|
357
|
+
if (doc) {
|
|
358
|
+
lines.push(...doc.split('\n').map(l => ` * ${l}`));
|
|
359
|
+
}
|
|
360
|
+
if (paramAnnotations && paramAnnotations.length > 0) {
|
|
361
|
+
// Lightweight inclusion of annotation strings when enabled
|
|
362
|
+
paramAnnotations.forEach((ann, idx) => {
|
|
363
|
+
lines.push(` * @param arg${idx} ${ann}`);
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
if (lines.length === 0) {
|
|
367
|
+
return '';
|
|
368
|
+
}
|
|
369
|
+
return `/**\n${lines.join('\n')}\n */\n`;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
private wrap(typescript: string, _sources: string[]): GeneratedCode {
|
|
373
|
+
return {
|
|
374
|
+
typescript,
|
|
375
|
+
declaration: '',
|
|
376
|
+
sourceMap: undefined,
|
|
377
|
+
metadata: {
|
|
378
|
+
generatedAt: new Date(),
|
|
379
|
+
sourceFiles: [],
|
|
380
|
+
runtime: 'auto',
|
|
381
|
+
optimizations: [],
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
private typeToTs(type: TypescriptType): string {
|
|
387
|
+
switch (type.kind) {
|
|
388
|
+
case 'primitive':
|
|
389
|
+
return type.name;
|
|
390
|
+
case 'array':
|
|
391
|
+
return `${this.typeToTs(type.elementType)}[]`;
|
|
392
|
+
case 'tuple': {
|
|
393
|
+
const t = type as { kind: 'tuple'; elementTypes: TypescriptType[] };
|
|
394
|
+
const parts = t.elementTypes.map(e => this.typeToTs(e)).join(', ');
|
|
395
|
+
return `[${parts}]`;
|
|
396
|
+
}
|
|
397
|
+
case 'object': {
|
|
398
|
+
const t = type;
|
|
399
|
+
// If it's a simple Record<string, T> pattern, use that syntax
|
|
400
|
+
if (t.properties.length === 0 && t.indexSignature) {
|
|
401
|
+
const keyType = this.typeToTs(t.indexSignature.keyType);
|
|
402
|
+
const valueType = this.typeToTs(t.indexSignature.valueType);
|
|
403
|
+
if (keyType === 'string') {
|
|
404
|
+
return `Record<string, ${valueType}>`;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const props = t.properties
|
|
409
|
+
.map(
|
|
410
|
+
p =>
|
|
411
|
+
`${p.readonly ? 'readonly ' : ''}${p.name}${p.optional ? '?' : ''}: ${this.typeToTs(p.type)};`
|
|
412
|
+
)
|
|
413
|
+
.join(' ');
|
|
414
|
+
const indexSig = t.indexSignature
|
|
415
|
+
? `[key: ${this.typeToTs(t.indexSignature.keyType)}]: ${this.typeToTs(t.indexSignature.valueType)};`
|
|
416
|
+
: '';
|
|
417
|
+
return `{ ${props} ${indexSig} }`;
|
|
418
|
+
}
|
|
419
|
+
case 'union':
|
|
420
|
+
return type.types.map(t => this.typeToTs(t)).join(' | ');
|
|
421
|
+
case 'function': {
|
|
422
|
+
const ft = type;
|
|
423
|
+
const params = ft.parameters
|
|
424
|
+
.map(
|
|
425
|
+
p => `${p.rest ? '...' : ''}${p.name}${p.optional ? '?' : ''}: ${this.typeToTs(p.type)}`
|
|
426
|
+
)
|
|
427
|
+
.join(', ');
|
|
428
|
+
return `(${params}) => ${this.typeToTs(ft.returnType)}`;
|
|
429
|
+
}
|
|
430
|
+
case 'generic': {
|
|
431
|
+
const g = type as { kind: 'generic'; name: string; typeArgs: TypescriptType[] };
|
|
432
|
+
const args = g.typeArgs.map(a => this.typeToTs(a)).join(', ');
|
|
433
|
+
return `${g.name}<${args}>`;
|
|
434
|
+
}
|
|
435
|
+
case 'custom': {
|
|
436
|
+
const c = type as { kind: 'custom'; name: string };
|
|
437
|
+
return c.name;
|
|
438
|
+
}
|
|
439
|
+
case 'literal': {
|
|
440
|
+
const l = type as { kind: 'literal'; value: unknown };
|
|
441
|
+
return JSON.stringify(l.value);
|
|
442
|
+
}
|
|
443
|
+
default:
|
|
444
|
+
return 'unknown';
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|