tywrap 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -4
- package/dist/index.d.ts +20 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -2
- package/dist/index.js.map +1 -1
- package/dist/runtime/base.d.ts +17 -7
- package/dist/runtime/base.d.ts.map +1 -1
- package/dist/runtime/base.js +18 -1
- package/dist/runtime/base.js.map +1 -1
- package/dist/runtime/bounded-context.d.ts +252 -0
- package/dist/runtime/bounded-context.d.ts.map +1 -0
- package/dist/runtime/bounded-context.js +454 -0
- package/dist/runtime/bounded-context.js.map +1 -0
- package/dist/runtime/bridge-protocol.d.ts +167 -0
- package/dist/runtime/bridge-protocol.d.ts.map +1 -0
- package/dist/runtime/bridge-protocol.js +247 -0
- package/dist/runtime/bridge-protocol.js.map +1 -0
- package/dist/runtime/disposable.d.ts +40 -0
- package/dist/runtime/disposable.d.ts.map +1 -0
- package/dist/runtime/disposable.js +49 -0
- package/dist/runtime/disposable.js.map +1 -0
- package/dist/runtime/http-io.d.ts +91 -0
- package/dist/runtime/http-io.d.ts.map +1 -0
- package/dist/runtime/http-io.js +195 -0
- package/dist/runtime/http-io.js.map +1 -0
- package/dist/runtime/http.d.ts +47 -13
- package/dist/runtime/http.d.ts.map +1 -1
- package/dist/runtime/http.js +55 -74
- package/dist/runtime/http.js.map +1 -1
- package/dist/runtime/node.d.ts +142 -28
- package/dist/runtime/node.d.ts.map +1 -1
- package/dist/runtime/node.js +321 -168
- package/dist/runtime/node.js.map +1 -1
- package/dist/runtime/optimized-node.d.ts +19 -125
- package/dist/runtime/optimized-node.d.ts.map +1 -1
- package/dist/runtime/optimized-node.js +19 -550
- package/dist/runtime/optimized-node.js.map +1 -1
- package/dist/runtime/pooled-transport.d.ts +131 -0
- package/dist/runtime/pooled-transport.d.ts.map +1 -0
- package/dist/runtime/pooled-transport.js +175 -0
- package/dist/runtime/pooled-transport.js.map +1 -0
- package/dist/runtime/process-io.d.ts +204 -0
- package/dist/runtime/process-io.d.ts.map +1 -0
- package/dist/runtime/process-io.js +695 -0
- package/dist/runtime/process-io.js.map +1 -0
- package/dist/runtime/pyodide-io.d.ts +155 -0
- package/dist/runtime/pyodide-io.d.ts.map +1 -0
- package/dist/runtime/pyodide-io.js +397 -0
- package/dist/runtime/pyodide-io.js.map +1 -0
- package/dist/runtime/pyodide.d.ts +51 -19
- package/dist/runtime/pyodide.d.ts.map +1 -1
- package/dist/runtime/pyodide.js +57 -186
- package/dist/runtime/pyodide.js.map +1 -1
- package/dist/runtime/safe-codec.d.ts +81 -0
- package/dist/runtime/safe-codec.d.ts.map +1 -0
- package/dist/runtime/safe-codec.js +345 -0
- package/dist/runtime/safe-codec.js.map +1 -0
- package/dist/runtime/transport.d.ts +186 -0
- package/dist/runtime/transport.d.ts.map +1 -0
- package/dist/runtime/transport.js +86 -0
- package/dist/runtime/transport.js.map +1 -0
- package/dist/runtime/validators.d.ts +131 -0
- package/dist/runtime/validators.d.ts.map +1 -0
- package/dist/runtime/validators.js +219 -0
- package/dist/runtime/validators.js.map +1 -0
- package/dist/runtime/worker-pool.d.ts +196 -0
- package/dist/runtime/worker-pool.d.ts.map +1 -0
- package/dist/runtime/worker-pool.js +371 -0
- package/dist/runtime/worker-pool.js.map +1 -0
- package/dist/utils/codec.d.ts.map +1 -1
- package/dist/utils/codec.js +120 -1
- package/dist/utils/codec.js.map +1 -1
- package/package.json +2 -2
- package/runtime/python_bridge.py +30 -3
- package/runtime/safe_codec.py +344 -0
- package/src/index.ts +48 -5
- package/src/runtime/base.ts +18 -26
- package/src/runtime/bounded-context.ts +608 -0
- package/src/runtime/bridge-protocol.ts +319 -0
- package/src/runtime/disposable.ts +65 -0
- package/src/runtime/http-io.ts +244 -0
- package/src/runtime/http.ts +71 -117
- package/src/runtime/node.ts +460 -217
- package/src/runtime/optimized-node.ts +19 -761
- package/src/runtime/pooled-transport.ts +252 -0
- package/src/runtime/process-io.ts +902 -0
- package/src/runtime/pyodide-io.ts +485 -0
- package/src/runtime/pyodide.ts +75 -215
- package/src/runtime/safe-codec.ts +443 -0
- package/src/runtime/transport.ts +273 -0
- package/src/runtime/validators.ts +241 -0
- package/src/runtime/worker-pool.ts +498 -0
- package/src/utils/codec.ts +126 -1
package/src/runtime/pyodide.ts
CHANGED
|
@@ -1,228 +1,88 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pyodide runtime bridge
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
28
|
+
/** Timeout in ms for operations. Default: 30000 (30 seconds) */
|
|
29
|
+
timeoutMs?: number;
|
|
13
30
|
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
}
|