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.
Files changed (137) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +319 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +263 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/config/index.d.ts +41 -0
  8. package/dist/config/index.d.ts.map +1 -0
  9. package/dist/config/index.js +234 -0
  10. package/dist/config/index.js.map +1 -0
  11. package/dist/core/analyzer.d.ts +63 -0
  12. package/dist/core/analyzer.d.ts.map +1 -0
  13. package/dist/core/analyzer.js +676 -0
  14. package/dist/core/analyzer.js.map +1 -0
  15. package/dist/core/discovery.d.ts +85 -0
  16. package/dist/core/discovery.d.ts.map +1 -0
  17. package/dist/core/discovery.js +344 -0
  18. package/dist/core/discovery.js.map +1 -0
  19. package/dist/core/generator.d.ts +37 -0
  20. package/dist/core/generator.d.ts.map +1 -0
  21. package/dist/core/generator.js +368 -0
  22. package/dist/core/generator.js.map +1 -0
  23. package/dist/core/mapper.d.ts +40 -0
  24. package/dist/core/mapper.d.ts.map +1 -0
  25. package/dist/core/mapper.js +401 -0
  26. package/dist/core/mapper.js.map +1 -0
  27. package/dist/core/validation.d.ts +79 -0
  28. package/dist/core/validation.d.ts.map +1 -0
  29. package/dist/core/validation.js +486 -0
  30. package/dist/core/validation.js.map +1 -0
  31. package/dist/index.d.ts +26 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +29 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/runtime/base.d.ts +12 -0
  36. package/dist/runtime/base.d.ts.map +1 -0
  37. package/dist/runtime/base.js +6 -0
  38. package/dist/runtime/base.js.map +1 -0
  39. package/dist/runtime/errors.d.ts +17 -0
  40. package/dist/runtime/errors.d.ts.map +1 -0
  41. package/dist/runtime/errors.js +21 -0
  42. package/dist/runtime/errors.js.map +1 -0
  43. package/dist/runtime/http.d.ts +22 -0
  44. package/dist/runtime/http.d.ts.map +1 -0
  45. package/dist/runtime/http.js +79 -0
  46. package/dist/runtime/http.js.map +1 -0
  47. package/dist/runtime/index.d.ts +8 -0
  48. package/dist/runtime/index.d.ts.map +1 -0
  49. package/dist/runtime/index.js +17 -0
  50. package/dist/runtime/index.js.map +1 -0
  51. package/dist/runtime/node.d.ts +48 -0
  52. package/dist/runtime/node.d.ts.map +1 -0
  53. package/dist/runtime/node.js +328 -0
  54. package/dist/runtime/node.js.map +1 -0
  55. package/dist/runtime/optimized-node.d.ts +131 -0
  56. package/dist/runtime/optimized-node.d.ts.map +1 -0
  57. package/dist/runtime/optimized-node.js +622 -0
  58. package/dist/runtime/optimized-node.js.map +1 -0
  59. package/dist/runtime/protocol.d.ts +3 -0
  60. package/dist/runtime/protocol.d.ts.map +1 -0
  61. package/dist/runtime/protocol.js +3 -0
  62. package/dist/runtime/protocol.js.map +1 -0
  63. package/dist/runtime/pyodide.d.ts +27 -0
  64. package/dist/runtime/pyodide.d.ts.map +1 -0
  65. package/dist/runtime/pyodide.js +191 -0
  66. package/dist/runtime/pyodide.js.map +1 -0
  67. package/dist/tools/python_suite.js +173 -0
  68. package/dist/tools/python_suite.js.map +1 -0
  69. package/dist/types/index.d.ts +299 -0
  70. package/dist/types/index.d.ts.map +1 -0
  71. package/dist/types/index.js +5 -0
  72. package/dist/types/index.js.map +1 -0
  73. package/dist/tywrap.d.ts +29 -0
  74. package/dist/tywrap.d.ts.map +1 -0
  75. package/dist/tywrap.js +589 -0
  76. package/dist/tywrap.js.map +1 -0
  77. package/dist/utils/bundle-optimizer.d.ts +182 -0
  78. package/dist/utils/bundle-optimizer.d.ts.map +1 -0
  79. package/dist/utils/bundle-optimizer.js +680 -0
  80. package/dist/utils/bundle-optimizer.js.map +1 -0
  81. package/dist/utils/cache.d.ts +149 -0
  82. package/dist/utils/cache.d.ts.map +1 -0
  83. package/dist/utils/cache.js +492 -0
  84. package/dist/utils/cache.js.map +1 -0
  85. package/dist/utils/codec.d.ts +94 -0
  86. package/dist/utils/codec.d.ts.map +1 -0
  87. package/dist/utils/codec.js +150 -0
  88. package/dist/utils/codec.js.map +1 -0
  89. package/dist/utils/logger.d.ts +42 -0
  90. package/dist/utils/logger.d.ts.map +1 -0
  91. package/dist/utils/logger.js +140 -0
  92. package/dist/utils/logger.js.map +1 -0
  93. package/dist/utils/memory-profiler.d.ts +123 -0
  94. package/dist/utils/memory-profiler.d.ts.map +1 -0
  95. package/dist/utils/memory-profiler.js +426 -0
  96. package/dist/utils/memory-profiler.js.map +1 -0
  97. package/dist/utils/parallel-processor.d.ts +146 -0
  98. package/dist/utils/parallel-processor.d.ts.map +1 -0
  99. package/dist/utils/parallel-processor.js +636 -0
  100. package/dist/utils/parallel-processor.js.map +1 -0
  101. package/dist/utils/python.d.ts +8 -0
  102. package/dist/utils/python.d.ts.map +1 -0
  103. package/dist/utils/python.js +56 -0
  104. package/dist/utils/python.js.map +1 -0
  105. package/dist/utils/runtime.d.ts +153 -0
  106. package/dist/utils/runtime.d.ts.map +1 -0
  107. package/dist/utils/runtime.js +519 -0
  108. package/dist/utils/runtime.js.map +1 -0
  109. package/package.json +136 -0
  110. package/runtime/python_bridge.py +476 -0
  111. package/src/cli.ts +331 -0
  112. package/src/config/index.ts +289 -0
  113. package/src/core/analyzer.ts +788 -0
  114. package/src/core/discovery.ts +415 -0
  115. package/src/core/generator.ts +447 -0
  116. package/src/core/mapper.ts +504 -0
  117. package/src/core/validation.ts +693 -0
  118. package/src/index.ts +83 -0
  119. package/src/runtime/base.ts +32 -0
  120. package/src/runtime/errors.ts +19 -0
  121. package/src/runtime/http.ts +132 -0
  122. package/src/runtime/index.ts +24 -0
  123. package/src/runtime/node.ts +431 -0
  124. package/src/runtime/optimized-node.ts +838 -0
  125. package/src/runtime/protocol.ts +2 -0
  126. package/src/runtime/pyodide.ts +228 -0
  127. package/src/types/global.d.ts +60 -0
  128. package/src/types/index.ts +410 -0
  129. package/src/tywrap.ts +669 -0
  130. package/src/utils/bundle-optimizer.ts +876 -0
  131. package/src/utils/cache.ts +634 -0
  132. package/src/utils/codec.ts +275 -0
  133. package/src/utils/logger.ts +201 -0
  134. package/src/utils/memory-profiler.ts +582 -0
  135. package/src/utils/parallel-processor.ts +879 -0
  136. package/src/utils/python.ts +79 -0
  137. 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
+ }