nodepyx 1.0.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 (184) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +399 -0
  3. package/binding.gyp +73 -0
  4. package/dist/core/PyCallable.d.ts +65 -0
  5. package/dist/core/PyCallable.d.ts.map +1 -0
  6. package/dist/core/PyCallable.js +109 -0
  7. package/dist/core/PyCallable.js.map +1 -0
  8. package/dist/core/PyContext.d.ts +76 -0
  9. package/dist/core/PyContext.d.ts.map +1 -0
  10. package/dist/core/PyContext.js +228 -0
  11. package/dist/core/PyContext.js.map +1 -0
  12. package/dist/core/PyIterator.d.ts +84 -0
  13. package/dist/core/PyIterator.d.ts.map +1 -0
  14. package/dist/core/PyIterator.js +243 -0
  15. package/dist/core/PyIterator.js.map +1 -0
  16. package/dist/core/PyModule.d.ts +55 -0
  17. package/dist/core/PyModule.d.ts.map +1 -0
  18. package/dist/core/PyModule.js +172 -0
  19. package/dist/core/PyModule.js.map +1 -0
  20. package/dist/core/PyProxy.d.ts +65 -0
  21. package/dist/core/PyProxy.d.ts.map +1 -0
  22. package/dist/core/PyProxy.js +483 -0
  23. package/dist/core/PyProxy.js.map +1 -0
  24. package/dist/core/PyRuntime.d.ts +105 -0
  25. package/dist/core/PyRuntime.d.ts.map +1 -0
  26. package/dist/core/PyRuntime.js +438 -0
  27. package/dist/core/PyRuntime.js.map +1 -0
  28. package/dist/env/CondaManager.d.ts +118 -0
  29. package/dist/env/CondaManager.d.ts.map +1 -0
  30. package/dist/env/CondaManager.js +401 -0
  31. package/dist/env/CondaManager.js.map +1 -0
  32. package/dist/env/PackageInstaller.d.ts +233 -0
  33. package/dist/env/PackageInstaller.d.ts.map +1 -0
  34. package/dist/env/PackageInstaller.js +609 -0
  35. package/dist/env/PackageInstaller.js.map +1 -0
  36. package/dist/env/PythonDetector.d.ts +103 -0
  37. package/dist/env/PythonDetector.d.ts.map +1 -0
  38. package/dist/env/PythonDetector.js +381 -0
  39. package/dist/env/PythonDetector.js.map +1 -0
  40. package/dist/env/VenvManager.d.ts +117 -0
  41. package/dist/env/VenvManager.d.ts.map +1 -0
  42. package/dist/env/VenvManager.js +331 -0
  43. package/dist/env/VenvManager.js.map +1 -0
  44. package/dist/index.d.ts +169 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +393 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/plugins/Plugin.interface.d.ts +41 -0
  49. package/dist/plugins/Plugin.interface.d.ts.map +1 -0
  50. package/dist/plugins/Plugin.interface.js +12 -0
  51. package/dist/plugins/Plugin.interface.js.map +1 -0
  52. package/dist/plugins/PluginManager.d.ts +26 -0
  53. package/dist/plugins/PluginManager.d.ts.map +1 -0
  54. package/dist/plugins/PluginManager.js +174 -0
  55. package/dist/plugins/PluginManager.js.map +1 -0
  56. package/dist/plugins/builtin/NumpyPlugin.d.ts +17 -0
  57. package/dist/plugins/builtin/NumpyPlugin.d.ts.map +1 -0
  58. package/dist/plugins/builtin/NumpyPlugin.js +41 -0
  59. package/dist/plugins/builtin/NumpyPlugin.js.map +1 -0
  60. package/dist/plugins/builtin/PandasPlugin.d.ts +19 -0
  61. package/dist/plugins/builtin/PandasPlugin.d.ts.map +1 -0
  62. package/dist/plugins/builtin/PandasPlugin.js +57 -0
  63. package/dist/plugins/builtin/PandasPlugin.js.map +1 -0
  64. package/dist/plugins/builtin/TorchPlugin.d.ts +23 -0
  65. package/dist/plugins/builtin/TorchPlugin.d.ts.map +1 -0
  66. package/dist/plugins/builtin/TorchPlugin.js +50 -0
  67. package/dist/plugins/builtin/TorchPlugin.js.map +1 -0
  68. package/dist/plugins/index.d.ts +7 -0
  69. package/dist/plugins/index.d.ts.map +1 -0
  70. package/dist/plugins/index.js +12 -0
  71. package/dist/plugins/index.js.map +1 -0
  72. package/dist/serialization/DataFrameBridge.d.ts +141 -0
  73. package/dist/serialization/DataFrameBridge.d.ts.map +1 -0
  74. package/dist/serialization/DataFrameBridge.js +355 -0
  75. package/dist/serialization/DataFrameBridge.js.map +1 -0
  76. package/dist/serialization/MsgPackSerializer.d.ts +45 -0
  77. package/dist/serialization/MsgPackSerializer.d.ts.map +1 -0
  78. package/dist/serialization/MsgPackSerializer.js +242 -0
  79. package/dist/serialization/MsgPackSerializer.js.map +1 -0
  80. package/dist/serialization/NumpyBridge.d.ts +96 -0
  81. package/dist/serialization/NumpyBridge.d.ts.map +1 -0
  82. package/dist/serialization/NumpyBridge.js +323 -0
  83. package/dist/serialization/NumpyBridge.js.map +1 -0
  84. package/dist/serialization/Serializer.d.ts +78 -0
  85. package/dist/serialization/Serializer.d.ts.map +1 -0
  86. package/dist/serialization/Serializer.js +281 -0
  87. package/dist/serialization/Serializer.js.map +1 -0
  88. package/dist/types/PythonTypeMapper.d.ts +87 -0
  89. package/dist/types/PythonTypeMapper.d.ts.map +1 -0
  90. package/dist/types/PythonTypeMapper.js +449 -0
  91. package/dist/types/PythonTypeMapper.js.map +1 -0
  92. package/dist/types/StubCache.d.ts +109 -0
  93. package/dist/types/StubCache.d.ts.map +1 -0
  94. package/dist/types/StubCache.js +333 -0
  95. package/dist/types/StubCache.js.map +1 -0
  96. package/dist/types/TypeGenerator.d.ts +139 -0
  97. package/dist/types/TypeGenerator.d.ts.map +1 -0
  98. package/dist/types/TypeGenerator.js +372 -0
  99. package/dist/types/TypeGenerator.js.map +1 -0
  100. package/dist/types/addon.d.ts +114 -0
  101. package/dist/types/addon.d.ts.map +1 -0
  102. package/dist/types/addon.js +32 -0
  103. package/dist/types/addon.js.map +1 -0
  104. package/dist/types/config.d.ts +175 -0
  105. package/dist/types/config.d.ts.map +1 -0
  106. package/dist/types/config.js +35 -0
  107. package/dist/types/config.js.map +1 -0
  108. package/dist/types/index.d.ts +10 -0
  109. package/dist/types/index.d.ts.map +1 -0
  110. package/dist/types/index.js +12 -0
  111. package/dist/types/index.js.map +1 -0
  112. package/dist/types/python.d.ts +235 -0
  113. package/dist/types/python.d.ts.map +1 -0
  114. package/dist/types/python.js +7 -0
  115. package/dist/types/python.js.map +1 -0
  116. package/dist/utils/ErrorTranslator.d.ts +83 -0
  117. package/dist/utils/ErrorTranslator.d.ts.map +1 -0
  118. package/dist/utils/ErrorTranslator.js +210 -0
  119. package/dist/utils/ErrorTranslator.js.map +1 -0
  120. package/dist/utils/Logger.d.ts +27 -0
  121. package/dist/utils/Logger.d.ts.map +1 -0
  122. package/dist/utils/Logger.js +115 -0
  123. package/dist/utils/Logger.js.map +1 -0
  124. package/dist/utils/MemoryMonitor.d.ts +44 -0
  125. package/dist/utils/MemoryMonitor.d.ts.map +1 -0
  126. package/dist/utils/MemoryMonitor.js +143 -0
  127. package/dist/utils/MemoryMonitor.js.map +1 -0
  128. package/package.json +177 -0
  129. package/python/error_handler.py +433 -0
  130. package/python/nodepyx_runtime.py +575 -0
  131. package/python/serializer.py +379 -0
  132. package/python/type_inspector.py +288 -0
  133. package/scripts/build-native.js +68 -0
  134. package/scripts/download-prebuilds.js +99 -0
  135. package/scripts/generate-stubs.js +405 -0
  136. package/scripts/install.js +260 -0
  137. package/src/core/PyCallable.ts +137 -0
  138. package/src/core/PyContext.ts +296 -0
  139. package/src/core/PyIterator.ts +294 -0
  140. package/src/core/PyModule.ts +194 -0
  141. package/src/core/PyProxy.ts +605 -0
  142. package/src/core/PyRuntime.ts +504 -0
  143. package/src/env/CondaManager.ts +451 -0
  144. package/src/env/PackageInstaller.ts +738 -0
  145. package/src/env/PythonDetector.ts +414 -0
  146. package/src/env/VenvManager.ts +396 -0
  147. package/src/index.ts +425 -0
  148. package/src/native/gil_guard.cpp +26 -0
  149. package/src/native/gil_guard.h +175 -0
  150. package/src/native/nodepyx_addon.cpp +886 -0
  151. package/src/native/python_bridge.cpp +790 -0
  152. package/src/native/python_bridge.h +257 -0
  153. package/src/native/thread_pool.cpp +336 -0
  154. package/src/native/thread_pool.h +175 -0
  155. package/src/native/type_converter.cpp +901 -0
  156. package/src/native/type_converter.h +272 -0
  157. package/src/nextjs/PyProvider.tsx +123 -0
  158. package/src/nextjs/index.ts +21 -0
  159. package/src/nextjs/usePython.ts +106 -0
  160. package/src/nextjs/withnodepyx.ts +88 -0
  161. package/src/plugins/Plugin.interface.ts +51 -0
  162. package/src/plugins/PluginManager.ts +155 -0
  163. package/src/plugins/builtin/NumpyPlugin.ts +36 -0
  164. package/src/plugins/builtin/PandasPlugin.ts +49 -0
  165. package/src/plugins/builtin/TorchPlugin.ts +56 -0
  166. package/src/plugins/index.ts +7 -0
  167. package/src/serialization/DataFrameBridge.ts +398 -0
  168. package/src/serialization/MsgPackSerializer.ts +220 -0
  169. package/src/serialization/NumpyBridge.ts +332 -0
  170. package/src/serialization/Serializer.ts +320 -0
  171. package/src/types/PythonTypeMapper.ts +495 -0
  172. package/src/types/StubCache.ts +340 -0
  173. package/src/types/TypeGenerator.ts +491 -0
  174. package/src/types/addon.ts +170 -0
  175. package/src/types/config.ts +226 -0
  176. package/src/types/index.ts +55 -0
  177. package/src/types/python.ts +309 -0
  178. package/src/types/stubs/numpy.d.ts +441 -0
  179. package/src/types/stubs/pandas.d.ts +575 -0
  180. package/src/types/stubs/sklearn.d.ts +728 -0
  181. package/src/types/stubs/torch.d.ts +694 -0
  182. package/src/utils/ErrorTranslator.ts +220 -0
  183. package/src/utils/Logger.ts +119 -0
  184. package/src/utils/MemoryMonitor.ts +175 -0
@@ -0,0 +1,495 @@
1
+ /**
2
+ * nodepyx — PythonTypeMapper
3
+ * Maps Python type annotation strings → TypeScript type strings.
4
+ *
5
+ * Handles:
6
+ * - Python built-in types (str, int, float, bool, …)
7
+ * - typing module generics (List[X], Dict[K,V], Optional[X], Union[A,B], …)
8
+ * - numpy / pandas / torch special types
9
+ * - Forward references (strings in quotes)
10
+ * - PEP 604 union syntax (X | Y)
11
+ */
12
+
13
+ export type TsType = string;
14
+
15
+ /**
16
+ * Mapping entry — maps a Python canonical name to a TS type.
17
+ */
18
+ export interface TypeMapEntry {
19
+ /** TypeScript type string */
20
+ tsType: TsType;
21
+ /** Whether this type needs an import statement */
22
+ needsImport?: boolean;
23
+ /** The import path (e.g. './DataFrameBridge') */
24
+ importFrom?: string;
25
+ }
26
+
27
+ /** Context passed to mapType for resolving generic parameters */
28
+ export interface TypeMapContext {
29
+ /** Known class names from the current module */
30
+ knownClasses?: Set<string>;
31
+ /** Module name being inspected */
32
+ moduleName?: string;
33
+ /** Whether to emit strict types (never use `unknown`) */
34
+ strict?: boolean;
35
+ }
36
+
37
+ // ─── Core mapping table ────────────────────────────────────────────────────
38
+
39
+ const BUILTIN_MAP: Readonly<Record<string, TypeMapEntry>> = {
40
+ // ── Primitives ──────────────────────────────────────────────────────────
41
+ 'str': { tsType: 'string' },
42
+ 'int': { tsType: 'number' },
43
+ 'float': { tsType: 'number' },
44
+ 'complex': { tsType: '{ real: number; imag: number }' },
45
+ 'bool': { tsType: 'boolean' },
46
+ 'bytes': { tsType: 'Uint8Array' },
47
+ 'bytearray': { tsType: 'Uint8Array' },
48
+ 'memoryview': { tsType: 'Uint8Array' },
49
+
50
+ // ── None / null-like ────────────────────────────────────────────────────
51
+ 'None': { tsType: 'null' },
52
+ 'NoneType': { tsType: 'null' },
53
+ 'type(None)': { tsType: 'null' },
54
+
55
+ // ── Collections (unparameterized) ───────────────────────────────────────
56
+ 'list': { tsType: 'unknown[]' },
57
+ 'tuple': { tsType: 'readonly unknown[]' },
58
+ 'dict': { tsType: 'Record<string, unknown>' },
59
+ 'set': { tsType: 'Set<unknown>' },
60
+ 'frozenset': { tsType: 'ReadonlySet<unknown>' },
61
+
62
+ // ── typing module ───────────────────────────────────────────────────────
63
+ 'Any': { tsType: 'unknown' },
64
+ 'NoReturn': { tsType: 'never' },
65
+ 'Never': { tsType: 'never' },
66
+ 'ClassVar': { tsType: 'unknown' },
67
+ 'Final': { tsType: 'unknown' },
68
+ 'Literal': { tsType: 'unknown' },
69
+
70
+ // ── IO / streams ────────────────────────────────────────────────────────
71
+ 'IO': { tsType: 'unknown' },
72
+ 'TextIO': { tsType: 'string' },
73
+ 'BinaryIO': { tsType: 'Uint8Array' },
74
+
75
+ // ── Callable ────────────────────────────────────────────────────────────
76
+ 'Callable': { tsType: '(...args: unknown[]) => unknown' },
77
+
78
+ // ── Numeric tower ───────────────────────────────────────────────────────
79
+ 'Number': { tsType: 'number' },
80
+ 'Integer': { tsType: 'number' },
81
+ 'Float': { tsType: 'number' },
82
+ 'Decimal': { tsType: 'number' },
83
+ 'Fraction': { tsType: 'number' },
84
+
85
+ // ── datetime ────────────────────────────────────────────────────────────
86
+ 'datetime': { tsType: 'Date' },
87
+ 'date': { tsType: 'Date' },
88
+ 'time': { tsType: 'Date' },
89
+ 'timedelta': { tsType: 'number' },
90
+ 'datetime.datetime': { tsType: 'Date' },
91
+ 'datetime.date': { tsType: 'Date' },
92
+ 'datetime.time': { tsType: 'Date' },
93
+ 'datetime.timedelta': { tsType: 'number' },
94
+
95
+ // ── pathlib ─────────────────────────────────────────────────────────────
96
+ 'Path': { tsType: 'string' },
97
+ 'PurePath': { tsType: 'string' },
98
+ 'PosixPath': { tsType: 'string' },
99
+ 'WindowsPath': { tsType: 'string' },
100
+ 'pathlib.Path': { tsType: 'string' },
101
+
102
+ // ── uuid ─────────────────────────────────────────────────────────────────
103
+ 'UUID': { tsType: 'string' },
104
+ 'uuid.UUID': { tsType: 'string' },
105
+
106
+ // ── NumPy ────────────────────────────────────────────────────────────────
107
+ 'ndarray': { tsType: 'Float64Array | Float32Array | Int32Array | Uint8Array' },
108
+ 'np.ndarray': { tsType: 'Float64Array | Float32Array | Int32Array | Uint8Array' },
109
+ 'numpy.ndarray': { tsType: 'Float64Array | Float32Array | Int32Array | Uint8Array' },
110
+ 'np.float32': { tsType: 'Float32Array' },
111
+ 'np.float64': { tsType: 'Float64Array' },
112
+ 'np.int32': { tsType: 'Int32Array' },
113
+ 'np.int64': { tsType: 'BigInt64Array' },
114
+ 'np.uint8': { tsType: 'Uint8Array' },
115
+ 'np.uint16': { tsType: 'Uint16Array' },
116
+ 'np.uint32': { tsType: 'Uint32Array' },
117
+ 'np.bool_': { tsType: 'Uint8Array' },
118
+ 'numpy.float64': { tsType: 'number' },
119
+ 'numpy.float32': { tsType: 'number' },
120
+ 'numpy.int64': { tsType: 'number' },
121
+ 'numpy.int32': { tsType: 'number' },
122
+
123
+ // ── Pandas ───────────────────────────────────────────────────────────────
124
+ 'DataFrame': { tsType: 'DataFrameResult' },
125
+ 'pd.DataFrame': { tsType: 'DataFrameResult' },
126
+ 'pandas.DataFrame': { tsType: 'DataFrameResult' },
127
+ 'Series': { tsType: 'SeriesResult' },
128
+ 'pd.Series': { tsType: 'SeriesResult' },
129
+ 'pandas.Series': { tsType: 'SeriesResult' },
130
+ 'Index': { tsType: 'unknown[]' },
131
+ 'pd.Index': { tsType: 'unknown[]' },
132
+ 'MultiIndex': { tsType: 'unknown[][]' },
133
+
134
+ // ── PyTorch ───────────────────────────────────────────────────────────────
135
+ 'Tensor': { tsType: 'Float32Array | Float64Array' },
136
+ 'torch.Tensor': { tsType: 'Float32Array | Float64Array' },
137
+ 'torch.nn.Module': { tsType: 'PyProxy' },
138
+ 'torch.optim.Optimizer': { tsType: 'PyProxy' },
139
+
140
+ // ── sklearn ───────────────────────────────────────────────────────────────
141
+ 'BaseEstimator': { tsType: 'PyProxy' },
142
+ 'ClassifierMixin': { tsType: 'PyProxy' },
143
+ 'RegressorMixin': { tsType: 'PyProxy' },
144
+ 'TransformerMixin': { tsType: 'PyProxy' },
145
+
146
+ // ── fallback ─────────────────────────────────────────────────────────────
147
+ 'object': { tsType: 'unknown' },
148
+ 'type': { tsType: 'PyProxy' },
149
+ 'module': { tsType: 'PyProxy' },
150
+ };
151
+
152
+ // ─── Generic type constructors ────────────────────────────────────────────
153
+
154
+ /** Patterns for parameterized generics like List[str], Dict[str, int], … */
155
+ const GENERIC_PATTERNS: Array<{
156
+ re: RegExp;
157
+ map: (params: string[], ctx: TypeMapContext) => TsType;
158
+ }> = [
159
+ // Iterator[T]
160
+ {
161
+ re: /^Iterator\[(.+)\]$/i,
162
+ map: ([inner], ctx) => `AsyncIterator<${mapType(inner!, ctx)}>`,
163
+ },
164
+ // Set[T]
165
+ {
166
+ re: /^Set\[(.+)\]$/i,
167
+ map: ([inner], ctx) => `Set<${mapType(inner!, ctx)}>`,
168
+ },
169
+ // FrozenSet[T]
170
+ {
171
+ re: /^FrozenSet\[(.+)\]$/i,
172
+ map: ([inner], ctx) => `ReadonlySet<${mapType(inner!, ctx)}>`,
173
+ },
174
+ // Generator[YieldType, ...] — handled before the generic catch-all
175
+ {
176
+ re: /^Generator\[(.+)\]$/i,
177
+ map: ([inner], ctx) => {
178
+ const parts = splitGenericParams(inner!);
179
+ return `AsyncGenerator<${mapType(parts[0]!, ctx)}>`;
180
+ },
181
+ },
182
+ // List[T] / Sequence[T] / Iterable[T] / MutableSequence[T] / Collection[T] / etc.
183
+ {
184
+ re: /^(?:List|Sequence|Iterable|MutableSequence|Collection|AbstractSet|MutableSet|Deque|Queue)\[(.+)\]$/i,
185
+ map: ([inner], ctx) => `Array<${mapType(inner!, ctx)}>`,
186
+ },
187
+ // Tuple[A, B, C]
188
+ {
189
+ re: /^(?:Tuple)\[(.+)\]$/i,
190
+ map: ([inner], ctx) => {
191
+ if (inner === '...') {return `unknown[]`;}
192
+ const parts = splitGenericParams(inner!);
193
+ if (parts.length === 2 && parts[1] === '...') {
194
+ return `Array<${mapType(parts[0]!, ctx)}>`;
195
+ }
196
+ return `[${parts.map(p => mapType(p, ctx)).join(', ')}]`;
197
+ },
198
+ },
199
+ // Dict[K, V] / Mapping[K, V] / MutableMapping[K, V]
200
+ {
201
+ re: /^(?:Dict|Mapping|MutableMapping|OrderedDict|DefaultDict|ChainMap)\[(.+)\]$/i,
202
+ map: ([inner], ctx) => {
203
+ const parts = splitGenericParams(inner!);
204
+ if (parts.length === 2) {
205
+ return `Record<${mapType(parts[0]!, ctx)}, ${mapType(parts[1]!, ctx)}>`;
206
+ }
207
+ return `Record<string, unknown>`;
208
+ },
209
+ },
210
+ // Optional[T] → T | null
211
+ {
212
+ re: /^Optional\[(.+)\]$/i,
213
+ map: ([inner], ctx) => `${mapType(inner!, ctx)} | null`,
214
+ },
215
+ // Union[A, B, C]
216
+ {
217
+ re: /^Union\[(.+)\]$/i,
218
+ map: ([inner], ctx) => {
219
+ const parts = splitGenericParams(inner!);
220
+ return parts.map(p => mapType(p, ctx)).join(' | ');
221
+ },
222
+ },
223
+ // PEP 604 — A | B | C (raw union)
224
+ // Note: params[0] is the first capture group; we reconstruct the full string
225
+ // by combining all capture groups back with ' | ' to split properly.
226
+ {
227
+ re: /^(.+)\s*\|\s*(.+)$/,
228
+ map: (params: string[], ctx: TypeMapContext) => {
229
+ // params[0..n] are capture groups; rejoin and re-split at top-level pipes
230
+ const full = params.join(' | ');
231
+ const parts = splitPipeUnion(full);
232
+ return parts.map(p => mapType(p.trim(), ctx)).join(' | ');
233
+ },
234
+ },
235
+ // Callable[[A, B], R]
236
+ {
237
+ re: /^Callable\[\[(.*)?\],\s*(.+)\]$/i,
238
+ map: ([params, ret], ctx) => {
239
+ if (!params) {return `() => ${mapType(ret!, ctx)}`;}
240
+ const paramTypes = splitGenericParams(params).map((p, i) => `arg${i}: ${mapType(p, ctx)}`);
241
+ return `(${paramTypes.join(', ')}) => ${mapType(ret!, ctx)}`;
242
+ },
243
+ },
244
+ // Type[T] / ClassVar[T] / Final[T]
245
+ {
246
+ re: /^(?:Type|ClassVar|Final)\[(.+)\]$/i,
247
+ map: ([inner], ctx) => mapType(inner!, ctx),
248
+ },
249
+ // Awaitable[T] / Coroutine[…, T]
250
+ {
251
+ re: /^(?:Awaitable|Coroutine)\[.*?,?\s*(.+)\]$/i,
252
+ map: ([ret], ctx) => `Promise<${mapType(ret!, ctx)}>`,
253
+ },
254
+ // AsyncIterator[T] / AsyncIterable[T] / AsyncGenerator[T]
255
+ {
256
+ re: /^(?:AsyncIterator|AsyncIterable|AsyncGenerator)\[(.+)\]$/i,
257
+ map: ([inner], ctx) => `AsyncIterable<${mapType(inner!, ctx)}>`,
258
+ },
259
+ ];
260
+
261
+ // ─── Main mapType function ────────────────────────────────────────────────
262
+
263
+ /**
264
+ * Map a Python type annotation string to a TypeScript type string.
265
+ *
266
+ * @example
267
+ * ```typescript
268
+ * mapType('List[int]') // → 'Array<number>'
269
+ * mapType('Dict[str, float]') // → 'Record<string, number>'
270
+ * mapType('Optional[DataFrame]') // → 'DataFrameResult | null'
271
+ * mapType('Union[str, int, None]') // → 'string | number | null'
272
+ * mapType('Tuple[int, str, bool]') // → '[number, string, boolean]'
273
+ * ```
274
+ */
275
+ export function mapType(
276
+ pythonType: string,
277
+ ctx: TypeMapContext = {},
278
+ ): TsType {
279
+ if (!pythonType) {return 'unknown';}
280
+
281
+ const trimmed = pythonType.trim();
282
+
283
+ // ── Direct lookup ───────────────────────────────────────────────────────
284
+ const direct = BUILTIN_MAP[trimmed];
285
+ if (direct) {return direct.tsType;}
286
+
287
+ // ── Strip module qualifiers for a second lookup ─────────────────────────
288
+ const unqualified = trimmed.includes('.')
289
+ ? trimmed.split('.').pop()!
290
+ : trimmed;
291
+ const unqualLookup = BUILTIN_MAP[unqualified];
292
+ if (unqualLookup) {return unqualLookup.tsType;}
293
+
294
+ // ── Generic patterns ────────────────────────────────────────────────────
295
+ for (const pattern of GENERIC_PATTERNS) {
296
+ const match = trimmed.match(pattern.re);
297
+ if (match) {
298
+ const capturedGroups = match.slice(1);
299
+ // Pass the full raw string for PEP-604 union
300
+ return pattern.map(capturedGroups, ctx);
301
+ }
302
+ }
303
+
304
+ // ── Known class from current module ─────────────────────────────────────
305
+ if (ctx.knownClasses?.has(trimmed) || ctx.knownClasses?.has(unqualified)) {
306
+ return 'PyProxy';
307
+ }
308
+
309
+ // ── String forward reference (PEP 484) ──────────────────────────────────
310
+ if (
311
+ (trimmed.startsWith('"') && trimmed.endsWith('"')) ||
312
+ (trimmed.startsWith("'") && trimmed.endsWith("'"))
313
+ ) {
314
+ return mapType(trimmed.slice(1, -1), ctx);
315
+ }
316
+
317
+ // ── Fallback ─────────────────────────────────────────────────────────────
318
+ // Unknown types always map to 'unknown' (not PyProxy) for better type safety
319
+ return 'unknown';
320
+ }
321
+
322
+ // ─── mapReturnType (adds Promise<> wrapping) ────────────────────────────────
323
+
324
+ /**
325
+ * Map a Python return type annotation.
326
+ * Automatically wraps the result in `Promise<>` since all Python calls
327
+ * are asynchronous from the JavaScript side.
328
+ *
329
+ * Generator / Iterator returns are mapped to `AsyncGenerator`.
330
+ * None → Promise<void>
331
+ */
332
+ export function mapReturnType(
333
+ pythonReturnType: string,
334
+ ctx: TypeMapContext = {},
335
+ ): TsType {
336
+ if (!pythonReturnType || pythonReturnType === 'None' || pythonReturnType === 'NoneType') {
337
+ return 'Promise<void>';
338
+ }
339
+
340
+ if (pythonReturnType === 'NoReturn' || pythonReturnType === 'Never') {
341
+ return 'never';
342
+ }
343
+
344
+ // Generator → AsyncGenerator
345
+ if (/^Generator\[/.test(pythonReturnType)) {
346
+ const inner = pythonReturnType.match(/^Generator\[(.+)\]/)?.[1] ?? 'unknown';
347
+ const parts = splitGenericParams(inner);
348
+ return `AsyncGenerator<${mapType(parts[0]!, ctx)}>`;
349
+ }
350
+
351
+ // Coroutine / Awaitable → strip wrapper, it becomes the resolved type
352
+ const awaitMatch = pythonReturnType.match(/^(?:Awaitable|Coroutine)\[.*?,?\s*(.+)\]$/i);
353
+ if (awaitMatch) {
354
+ return `Promise<${mapType(awaitMatch[1]!, ctx)}>`;
355
+ }
356
+
357
+ const inner = mapType(pythonReturnType, ctx);
358
+ return `Promise<${inner}>`;
359
+ }
360
+
361
+ // ─── Parameter default value helper ──────────────────────────────────────────
362
+
363
+ /**
364
+ * Convert a Python default value representation to a TypeScript literal.
365
+ */
366
+ export function mapDefaultValue(pythonDefault: string): string {
367
+ if (pythonDefault === 'None') {return 'null';}
368
+ if (pythonDefault === 'True') {return 'true';}
369
+ if (pythonDefault === 'False') {return 'false';}
370
+ if (/^-?\d+(\.\d+)?$/.test(pythonDefault)) {return pythonDefault;}
371
+ if (pythonDefault.startsWith("'") || pythonDefault.startsWith('"')) {
372
+ return pythonDefault.replace(/^'|'$/g, '"');
373
+ }
374
+ return `undefined /* ${pythonDefault} */`;
375
+ }
376
+
377
+ // ─── Splitting helpers ─────────────────────────────────────────────────────
378
+
379
+ /**
380
+ * Split generic parameters at the top level (respects nested brackets).
381
+ * e.g. "str, Dict[str, int], bool" → ["str", "Dict[str, int]", "bool"]
382
+ */
383
+ export function splitGenericParams(params: string): string[] {
384
+ const result: string[] = [];
385
+ let depth = 0;
386
+ let current = '';
387
+
388
+ for (const ch of params) {
389
+ if (ch === '[') {
390
+ depth++;
391
+ current += ch;
392
+ } else if (ch === ']') {
393
+ depth--;
394
+ current += ch;
395
+ } else if (ch === ',' && depth === 0) {
396
+ result.push(current.trim());
397
+ current = '';
398
+ } else {
399
+ current += ch;
400
+ }
401
+ }
402
+
403
+ if (current.trim()) {
404
+ result.push(current.trim());
405
+ }
406
+
407
+ return result;
408
+ }
409
+
410
+ /**
411
+ * Split a PEP 604 union "A | B | C" at top-level pipes.
412
+ */
413
+ function splitPipeUnion(type: string): string[] {
414
+ const result: string[] = [];
415
+ let depth = 0;
416
+ let current = '';
417
+
418
+ for (const ch of type) {
419
+ if (ch === '[') { depth++; current += ch; }
420
+ else if (ch === ']') { depth--; current += ch; }
421
+ else if (ch === '|' && depth === 0) {
422
+ result.push(current.trim());
423
+ current = '';
424
+ } else {
425
+ current += ch;
426
+ }
427
+ }
428
+
429
+ if (current.trim()) {result.push(current.trim());}
430
+ return result;
431
+ }
432
+
433
+ // ─── PythonTypeMapper class (stateful, module-aware) ──────────────────────
434
+
435
+ /**
436
+ * Stateful type mapper that accumulates required imports
437
+ * and knows which class names belong to the current module.
438
+ *
439
+ * @example
440
+ * ```typescript
441
+ * const mapper = new PythonTypeMapper({ moduleName: 'sklearn.linear_model' });
442
+ * mapper.registerClass('LinearRegression');
443
+ *
444
+ * mapper.map('LinearRegression') // → 'LinearRegression' (local class ref)
445
+ * mapper.map('DataFrame') // → 'DataFrameResult'
446
+ * ```
447
+ */
448
+ export class PythonTypeMapper {
449
+ private readonly _ctx: TypeMapContext;
450
+ private readonly _imports = new Map<string, Set<string>>();
451
+
452
+ constructor(ctx: TypeMapContext = {}) {
453
+ this._ctx = {
454
+ ...ctx,
455
+ knownClasses: ctx.knownClasses ?? new Set(),
456
+ };
457
+ }
458
+
459
+ registerClass(name: string): void {
460
+ this._ctx.knownClasses!.add(name);
461
+ }
462
+
463
+ map(pyType: string): TsType {
464
+ const result = mapType(pyType, this._ctx);
465
+ // Auto-track imports for well-known result types
466
+ if (result === 'DataFrameResult') {
467
+ this.addImport('DataFrameResult', 'nodepyx');
468
+ } else if (result === 'SeriesResult') {
469
+ this.addImport('SeriesResult', 'nodepyx');
470
+ } else if (result === 'NumPyArrayResult') {
471
+ this.addImport('NumPyArrayResult', 'nodepyx');
472
+ }
473
+ return result;
474
+ }
475
+
476
+ mapReturn(pyType: string): TsType {
477
+ return mapReturnType(pyType, this._ctx);
478
+ }
479
+
480
+ addImport(symbol: string, from: string): void {
481
+ if (!this._imports.has(from)) {
482
+ this._imports.set(from, new Set());
483
+ }
484
+ this._imports.get(from)!.add(symbol);
485
+ }
486
+
487
+ generateImportBlock(): string {
488
+ const lines: string[] = [];
489
+ for (const [from, symbols] of this._imports) {
490
+ lines.push(`import type { ${[...symbols].join(', ')} } from '${from}';`);
491
+ }
492
+ return lines.join('\n');
493
+ }
494
+ }
495
+