tywrap 0.2.0 → 0.3.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 (163) hide show
  1. package/README.md +65 -31
  2. package/dist/cli.js +49 -2
  3. package/dist/cli.js.map +1 -1
  4. package/dist/config/index.d.ts +11 -7
  5. package/dist/config/index.d.ts.map +1 -1
  6. package/dist/config/index.js +43 -7
  7. package/dist/config/index.js.map +1 -1
  8. package/dist/core/analyzer.d.ts +1 -0
  9. package/dist/core/analyzer.d.ts.map +1 -1
  10. package/dist/core/analyzer.js +47 -12
  11. package/dist/core/analyzer.js.map +1 -1
  12. package/dist/core/annotation-parser.d.ts +8 -0
  13. package/dist/core/annotation-parser.d.ts.map +1 -0
  14. package/dist/core/annotation-parser.js +409 -0
  15. package/dist/core/annotation-parser.js.map +1 -0
  16. package/dist/core/discovery.d.ts +1 -0
  17. package/dist/core/discovery.d.ts.map +1 -1
  18. package/dist/core/discovery.js +9 -9
  19. package/dist/core/discovery.js.map +1 -1
  20. package/dist/core/generator.d.ts +9 -1
  21. package/dist/core/generator.d.ts.map +1 -1
  22. package/dist/core/generator.js +738 -104
  23. package/dist/core/generator.js.map +1 -1
  24. package/dist/core/mapper.d.ts +9 -7
  25. package/dist/core/mapper.d.ts.map +1 -1
  26. package/dist/core/mapper.js +129 -44
  27. package/dist/core/mapper.js.map +1 -1
  28. package/dist/index.d.ts +23 -7
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +23 -3
  31. package/dist/index.js.map +1 -1
  32. package/dist/runtime/base.d.ts +17 -7
  33. package/dist/runtime/base.d.ts.map +1 -1
  34. package/dist/runtime/base.js +18 -1
  35. package/dist/runtime/base.js.map +1 -1
  36. package/dist/runtime/bounded-context.d.ts +252 -0
  37. package/dist/runtime/bounded-context.d.ts.map +1 -0
  38. package/dist/runtime/bounded-context.js +454 -0
  39. package/dist/runtime/bounded-context.js.map +1 -0
  40. package/dist/runtime/bridge-core.d.ts +1 -0
  41. package/dist/runtime/bridge-core.d.ts.map +1 -1
  42. package/dist/runtime/bridge-core.js +52 -17
  43. package/dist/runtime/bridge-core.js.map +1 -1
  44. package/dist/runtime/bridge-protocol.d.ts +184 -0
  45. package/dist/runtime/bridge-protocol.d.ts.map +1 -0
  46. package/dist/runtime/bridge-protocol.js +344 -0
  47. package/dist/runtime/bridge-protocol.js.map +1 -0
  48. package/dist/runtime/disposable.d.ts +40 -0
  49. package/dist/runtime/disposable.d.ts.map +1 -0
  50. package/dist/runtime/disposable.js +49 -0
  51. package/dist/runtime/disposable.js.map +1 -0
  52. package/dist/runtime/errors.d.ts +10 -0
  53. package/dist/runtime/errors.d.ts.map +1 -1
  54. package/dist/runtime/errors.js +9 -0
  55. package/dist/runtime/errors.js.map +1 -1
  56. package/dist/runtime/http-io.d.ts +91 -0
  57. package/dist/runtime/http-io.d.ts.map +1 -0
  58. package/dist/runtime/http-io.js +195 -0
  59. package/dist/runtime/http-io.js.map +1 -0
  60. package/dist/runtime/http.d.ts +48 -13
  61. package/dist/runtime/http.d.ts.map +1 -1
  62. package/dist/runtime/http.js +60 -73
  63. package/dist/runtime/http.js.map +1 -1
  64. package/dist/runtime/node.d.ts +97 -130
  65. package/dist/runtime/node.d.ts.map +1 -1
  66. package/dist/runtime/node.js +392 -521
  67. package/dist/runtime/node.js.map +1 -1
  68. package/dist/runtime/optimized-node.d.ts +1 -1
  69. package/dist/runtime/optimized-node.d.ts.map +1 -1
  70. package/dist/runtime/optimized-node.js +1 -1
  71. package/dist/runtime/optimized-node.js.map +1 -1
  72. package/dist/runtime/pooled-transport.d.ts +135 -0
  73. package/dist/runtime/pooled-transport.d.ts.map +1 -0
  74. package/dist/runtime/pooled-transport.js +177 -0
  75. package/dist/runtime/pooled-transport.js.map +1 -0
  76. package/dist/runtime/process-io.d.ts +209 -0
  77. package/dist/runtime/process-io.d.ts.map +1 -0
  78. package/dist/runtime/process-io.js +729 -0
  79. package/dist/runtime/process-io.js.map +1 -0
  80. package/dist/runtime/pyodide-io.d.ts +154 -0
  81. package/dist/runtime/pyodide-io.d.ts.map +1 -0
  82. package/dist/runtime/pyodide-io.js +459 -0
  83. package/dist/runtime/pyodide-io.js.map +1 -0
  84. package/dist/runtime/pyodide.d.ts +51 -19
  85. package/dist/runtime/pyodide.d.ts.map +1 -1
  86. package/dist/runtime/pyodide.js +57 -186
  87. package/dist/runtime/pyodide.js.map +1 -1
  88. package/dist/runtime/safe-codec.d.ts +103 -0
  89. package/dist/runtime/safe-codec.d.ts.map +1 -0
  90. package/dist/runtime/safe-codec.js +519 -0
  91. package/dist/runtime/safe-codec.js.map +1 -0
  92. package/dist/runtime/transport.d.ts +191 -0
  93. package/dist/runtime/transport.d.ts.map +1 -0
  94. package/dist/runtime/transport.js +86 -0
  95. package/dist/runtime/transport.js.map +1 -0
  96. package/dist/runtime/validators.d.ts +120 -0
  97. package/dist/runtime/validators.d.ts.map +1 -0
  98. package/dist/runtime/validators.js +229 -0
  99. package/dist/runtime/validators.js.map +1 -0
  100. package/dist/runtime/worker-pool.d.ts +208 -0
  101. package/dist/runtime/worker-pool.d.ts.map +1 -0
  102. package/dist/runtime/worker-pool.js +419 -0
  103. package/dist/runtime/worker-pool.js.map +1 -0
  104. package/dist/types/index.d.ts +51 -1
  105. package/dist/types/index.d.ts.map +1 -1
  106. package/dist/tywrap.d.ts.map +1 -1
  107. package/dist/tywrap.js +227 -328
  108. package/dist/tywrap.js.map +1 -1
  109. package/dist/utils/cache.d.ts.map +1 -1
  110. package/dist/utils/cache.js +53 -5
  111. package/dist/utils/cache.js.map +1 -1
  112. package/dist/utils/codec.d.ts.map +1 -1
  113. package/dist/utils/codec.js +114 -1
  114. package/dist/utils/codec.js.map +1 -1
  115. package/dist/utils/ir-cache.d.ts +28 -0
  116. package/dist/utils/ir-cache.d.ts.map +1 -0
  117. package/dist/utils/ir-cache.js +29 -0
  118. package/dist/utils/ir-cache.js.map +1 -0
  119. package/dist/utils/parallel-processor.d.ts +1 -1
  120. package/dist/utils/parallel-processor.d.ts.map +1 -1
  121. package/dist/utils/parallel-processor.js +111 -40
  122. package/dist/utils/parallel-processor.js.map +1 -1
  123. package/dist/utils/runtime.d.ts +5 -1
  124. package/dist/utils/runtime.d.ts.map +1 -1
  125. package/dist/utils/runtime.js +112 -18
  126. package/dist/utils/runtime.js.map +1 -1
  127. package/package.json +40 -18
  128. package/runtime/python_bridge.py +108 -15
  129. package/runtime/safe_codec.py +344 -0
  130. package/src/cli.ts +61 -2
  131. package/src/config/index.ts +63 -17
  132. package/src/core/analyzer.ts +52 -15
  133. package/src/core/annotation-parser.ts +500 -0
  134. package/src/core/discovery.ts +26 -12
  135. package/src/core/generator.ts +956 -116
  136. package/src/core/mapper.ts +158 -51
  137. package/src/index.ts +78 -6
  138. package/src/runtime/base.ts +18 -26
  139. package/src/runtime/bounded-context.ts +608 -0
  140. package/src/runtime/bridge-core.ts +58 -18
  141. package/src/runtime/bridge-protocol.ts +483 -0
  142. package/src/runtime/disposable.ts +65 -0
  143. package/src/runtime/errors.ts +14 -0
  144. package/src/runtime/http-io.ts +245 -0
  145. package/src/runtime/http.ts +76 -115
  146. package/src/runtime/node.ts +563 -676
  147. package/src/runtime/optimized-node.ts +4 -1
  148. package/src/runtime/pooled-transport.ts +263 -0
  149. package/src/runtime/process-io.ts +930 -0
  150. package/src/runtime/pyodide-io.ts +551 -0
  151. package/src/runtime/pyodide.ts +75 -215
  152. package/src/runtime/safe-codec.ts +651 -0
  153. package/src/runtime/transport.ts +278 -0
  154. package/src/runtime/validators.ts +252 -0
  155. package/src/runtime/worker-pool.ts +559 -0
  156. package/src/types/global.d.ts +11 -1
  157. package/src/types/index.ts +63 -0
  158. package/src/tywrap.ts +279 -358
  159. package/src/utils/cache.ts +59 -5
  160. package/src/utils/codec.ts +122 -1
  161. package/src/utils/ir-cache.ts +51 -0
  162. package/src/utils/parallel-processor.ts +119 -43
  163. package/src/utils/runtime.ts +129 -18
@@ -1,228 +1,88 @@
1
1
  /**
2
- * Pyodide runtime bridge (browser)
2
+ * Pyodide runtime bridge for BridgeProtocol.
3
+ *
4
+ * PyodideBridge extends BridgeProtocol and uses PyodideIO transport for
5
+ * in-memory Python execution in browser environments via WebAssembly.
6
+ *
7
+ * @see https://github.com/bbopen/tywrap/issues/149
3
8
  */
4
9
 
5
- import { RuntimeBridge } from './base.js';
10
+ import { BridgeProtocol, type BridgeProtocolOptions } from './bridge-protocol.js';
11
+ import { PyodideIO } from './pyodide-io.js';
12
+ import type { CodecOptions } from './safe-codec.js';
6
13
 
14
+ // =============================================================================
15
+ // OPTIONS
16
+ // =============================================================================
17
+
18
+ /**
19
+ * Configuration options for PyodideBridge.
20
+ */
7
21
  export interface PyodideBridgeOptions {
8
- indexURL: string;
22
+ /** URL for Pyodide CDN. Default: official CDN */
23
+ indexURL?: string;
24
+
25
+ /** Python packages to load during initialization */
9
26
  packages?: string[];
10
- }
11
27
 
12
- type LoadPyodide = (options: { indexURL: string }) => Promise<PyodideInstance>;
28
+ /** Timeout in ms for operations. Default: 30000 (30 seconds) */
29
+ timeoutMs?: number;
13
30
 
14
- interface PyodideInstance {
15
- runPython: (code: string) => unknown;
16
- runPythonAsync: (code: string) => Promise<unknown>;
17
- globals: { get: (key: string) => unknown; set: (k: string, v: unknown) => void };
18
- toPy: (obj: unknown) => unknown;
19
- loadPackage: (name: string | string[]) => Promise<void>;
31
+ /** Codec options for validation/serialization */
32
+ codec?: CodecOptions;
20
33
  }
21
34
 
22
- export class PyodideBridge extends RuntimeBridge {
23
- private readonly indexURL: string;
24
- private readonly packages: readonly string[];
25
- private py?: PyodideInstance;
26
- private initPromise?: Promise<void>;
27
-
28
- constructor(options: PyodideBridgeOptions = { indexURL: 'https://cdn.jsdelivr.net/pyodide/' }) {
29
- super();
30
- this.indexURL = options.indexURL;
31
- this.packages = [...(options.packages ?? [])];
32
- }
33
-
34
- private async ensureReady(): Promise<void> {
35
- if (this.py) {
36
- return;
37
- }
38
- // If already initializing, wait for that promise
39
- if (this.initPromise) {
40
- return this.initPromise;
41
- }
42
- // Start initialization and store the promise to prevent concurrent initialization
43
- this.initPromise = this.doInit();
44
- return this.initPromise;
45
- }
46
-
47
- private async doInit(): Promise<void> {
48
- const loadPyodideFn: LoadPyodide | undefined = await this.resolveLoadPyodide();
49
- if (!loadPyodideFn) {
50
- throw new Error('Pyodide is not available in this environment');
51
- }
52
- this.py = await loadPyodideFn({ indexURL: this.indexURL });
53
- if (this.packages.length > 0) {
54
- await this.py.loadPackage([...this.packages]);
55
- }
56
- await this.bootstrapHelpers();
57
- }
58
-
59
- async init(): Promise<void> {
60
- await this.ensureReady();
61
- }
62
-
63
- private async resolveLoadPyodide(): Promise<LoadPyodide | undefined> {
64
- // Prefer global loadPyodide when present (browser script tag include)
65
- const g = (globalThis as unknown as { loadPyodide?: LoadPyodide }) ?? {};
66
- if (typeof g.loadPyodide === 'function') {
67
- return g.loadPyodide;
68
- }
69
- try {
70
- // Attempt dynamic import if available
71
- const mod = (await import('pyodide')) as unknown as { loadPyodide?: LoadPyodide };
72
- if (typeof mod.loadPyodide === 'function') {
73
- return mod.loadPyodide;
74
- }
75
- } catch {
76
- // Ignore import errors - pyodide may not be installed
77
- // This is expected in most environments
78
- }
79
- return undefined;
80
- }
35
+ // =============================================================================
36
+ // PYODIDE BRIDGE
37
+ // =============================================================================
81
38
 
82
- private async bootstrapHelpers(): Promise<void> {
83
- if (!this.py) {
84
- return;
85
- }
86
- const helper = [
87
- 'import importlib',
88
- '__tywrap_instances = {}',
89
- 'def __tywrap_call(module, function_name, args, kwargs):',
90
- ' mod = importlib.import_module(module)',
91
- ' fn = getattr(mod, function_name)',
92
- ' return fn(*args, **(kwargs or {}))',
93
- 'def __tywrap_instantiate(module, class_name, args, kwargs):',
94
- ' mod = importlib.import_module(module)',
95
- ' cls = getattr(mod, class_name)',
96
- ' obj = cls(*args, **(kwargs or {}))',
97
- ' handle = str(id(obj))',
98
- ' __tywrap_instances[handle] = obj',
99
- ' return handle',
100
- 'def __tywrap_call_method(handle, method_name, args, kwargs):',
101
- ' if handle not in __tywrap_instances:',
102
- ' raise KeyError(f"Unknown handle: {handle}")',
103
- ' obj = __tywrap_instances[handle]',
104
- ' fn = getattr(obj, method_name)',
105
- ' return fn(*args, **(kwargs or {}))',
106
- 'def __tywrap_dispose_instance(handle):',
107
- ' return __tywrap_instances.pop(handle, None) is not None',
108
- ].join('\n');
109
- await this.py.runPythonAsync(helper);
110
- }
111
-
112
- async call<T = unknown>(
113
- module: string,
114
- functionName: string,
115
- args: unknown[],
116
- kwargs?: Record<string, unknown>
117
- ): Promise<T> {
118
- await this.ensureReady();
119
- const py = this.py;
120
- if (!py) {
121
- throw new Error('Pyodide not initialized');
122
- }
123
- const fn = py.globals.get('__tywrap_call');
124
- if (!fn) {
125
- throw new Error('Pyodide helper not initialized');
126
- }
127
- const invoke = fn as (module: string, f: string, a: unknown, k: unknown) => unknown;
128
- const pyArgs = py.toPy(args ?? []);
129
- const pyKwargs = py.toPy(kwargs ?? {});
130
- try {
131
- const out = invoke(module, functionName, pyArgs, pyKwargs);
132
- return out as T;
133
- } finally {
134
- this.destroyPyProxy(pyArgs);
135
- this.destroyPyProxy(pyKwargs);
136
- this.destroyPyProxy(fn);
137
- }
138
- }
139
-
140
- async instantiate<T = unknown>(
141
- module: string,
142
- className: string,
143
- args: unknown[],
144
- kwargs?: Record<string, unknown>
145
- ): Promise<T> {
146
- await this.ensureReady();
147
- const py = this.py;
148
- if (!py) {
149
- throw new Error('Pyodide not initialized');
150
- }
151
- const fn = py.globals.get('__tywrap_instantiate');
152
- if (!fn) {
153
- throw new Error('Pyodide helper not initialized');
154
- }
155
- const invoke = fn as (module: string, c: string, a: unknown, k: unknown) => unknown;
156
- const pyArgs = py.toPy(args ?? []);
157
- const pyKwargs = py.toPy(kwargs ?? {});
158
- try {
159
- const out = invoke(module, className, pyArgs, pyKwargs);
160
- return out as T;
161
- } finally {
162
- this.destroyPyProxy(pyArgs);
163
- this.destroyPyProxy(pyKwargs);
164
- this.destroyPyProxy(fn);
165
- }
166
- }
167
-
168
- async callMethod<T = unknown>(
169
- handle: string,
170
- methodName: string,
171
- args: unknown[],
172
- kwargs?: Record<string, unknown>
173
- ): Promise<T> {
174
- await this.ensureReady();
175
- const py = this.py;
176
- if (!py) {
177
- throw new Error('Pyodide not initialized');
178
- }
179
- const fn = py.globals.get('__tywrap_call_method');
180
- if (!fn) {
181
- throw new Error('Pyodide helper not initialized');
182
- }
183
- const invoke = fn as (h: string, m: string, a: unknown, k: unknown) => unknown;
184
- const pyArgs = py.toPy(args ?? []);
185
- const pyKwargs = py.toPy(kwargs ?? {});
186
- try {
187
- const out = invoke(handle, methodName, pyArgs, pyKwargs);
188
- return out as T;
189
- } finally {
190
- this.destroyPyProxy(pyArgs);
191
- this.destroyPyProxy(pyKwargs);
192
- this.destroyPyProxy(fn);
193
- }
194
- }
195
-
196
- async disposeInstance(handle: string): Promise<void> {
197
- await this.ensureReady();
198
- const py = this.py;
199
- if (!py) {
200
- throw new Error('Pyodide not initialized');
201
- }
202
- const fn = py.globals.get('__tywrap_dispose_instance');
203
- if (!fn) {
204
- throw new Error('Pyodide helper not initialized');
205
- }
206
- const invoke = fn as (h: string) => unknown;
207
- try {
208
- invoke(handle);
209
- } finally {
210
- this.destroyPyProxy(fn);
211
- }
212
- }
213
-
214
- async dispose(): Promise<void> {
215
- // Pyodide has no explicit dispose for instance; rely on GC
216
- this.py = undefined;
217
- }
218
-
219
- private destroyPyProxy(value: unknown): void {
220
- if (value && typeof (value as { destroy?: () => void }).destroy === 'function') {
221
- try {
222
- (value as { destroy: () => void }).destroy();
223
- } catch {
224
- // ignore cleanup failures
225
- }
226
- }
39
+ /**
40
+ * Browser-based runtime bridge for executing Python code via Pyodide.
41
+ *
42
+ * PyodideBridge provides in-memory Python execution using Pyodide (Python
43
+ * compiled to WebAssembly). This enables running Python directly in the
44
+ * browser without a server.
45
+ *
46
+ * Features:
47
+ * - Zero network overhead (in-memory execution)
48
+ * - Automatic Pyodide loading from CDN or module
49
+ * - Python package loading support
50
+ * - Full SafeCodec validation (NaN/Infinity rejection, key validation)
51
+ * - Proper proxy cleanup to prevent memory leaks
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * const bridge = new PyodideBridge({
56
+ * packages: ['numpy'],
57
+ * });
58
+ * await bridge.init();
59
+ *
60
+ * const result = await bridge.call('math', 'sqrt', [16]);
61
+ * console.log(result); // 4.0
62
+ *
63
+ * await bridge.dispose();
64
+ * ```
65
+ */
66
+ export class PyodideBridge extends BridgeProtocol {
67
+ /**
68
+ * Create a new PyodideBridge instance.
69
+ *
70
+ * @param options - Configuration options for the bridge
71
+ */
72
+ constructor(options: PyodideBridgeOptions = {}) {
73
+ // Create Pyodide transport
74
+ const transport = new PyodideIO({
75
+ indexURL: options.indexURL,
76
+ packages: options.packages,
77
+ });
78
+
79
+ // Initialize BridgeProtocol with transport and codec options
80
+ const protocolOptions: BridgeProtocolOptions = {
81
+ transport,
82
+ codec: options.codec,
83
+ defaultTimeoutMs: options.timeoutMs,
84
+ };
85
+
86
+ super(protocolOptions);
227
87
  }
228
88
  }