tywrap 0.5.1 → 0.6.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/dist/cli.js +8 -6
- package/dist/cli.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +236 -147
- package/dist/config/index.js.map +1 -1
- package/dist/core/annotation-parser.d.ts.map +1 -1
- package/dist/core/annotation-parser.js +93 -83
- package/dist/core/annotation-parser.js.map +1 -1
- package/dist/core/discovery.d.ts.map +1 -1
- package/dist/core/discovery.js +26 -0
- package/dist/core/discovery.js.map +1 -1
- package/dist/core/emit-call.d.ts +58 -0
- package/dist/core/emit-call.d.ts.map +1 -0
- package/dist/core/emit-call.js +103 -0
- package/dist/core/emit-call.js.map +1 -0
- package/dist/core/generator.d.ts +2 -0
- package/dist/core/generator.d.ts.map +1 -1
- package/dist/core/generator.js +54 -211
- package/dist/core/generator.js.map +1 -1
- package/dist/core/mapper.d.ts.map +1 -1
- package/dist/core/mapper.js +118 -104
- package/dist/core/mapper.js.map +1 -1
- package/dist/index.d.ts +6 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -31
- package/dist/index.js.map +1 -1
- package/dist/runtime/bounded-context.d.ts +2 -2
- package/dist/runtime/bounded-context.js +2 -2
- package/dist/runtime/{safe-codec.d.ts → bridge-codec.d.ts} +11 -11
- package/dist/runtime/bridge-codec.d.ts.map +1 -0
- package/dist/runtime/{safe-codec.js → bridge-codec.js} +26 -26
- package/dist/runtime/bridge-codec.js.map +1 -0
- package/dist/runtime/{http-io.d.ts → http-transport.d.ts} +6 -6
- package/dist/runtime/http-transport.d.ts.map +1 -0
- package/dist/runtime/{http-io.js → http-transport.js} +3 -3
- package/dist/runtime/http-transport.js.map +1 -0
- package/dist/runtime/http.d.ts +3 -3
- package/dist/runtime/http.d.ts.map +1 -1
- package/dist/runtime/http.js +4 -4
- package/dist/runtime/http.js.map +1 -1
- package/dist/runtime/index.d.ts +9 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +9 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/node.d.ts +6 -6
- package/dist/runtime/node.d.ts.map +1 -1
- package/dist/runtime/node.js +7 -7
- package/dist/runtime/node.js.map +1 -1
- package/dist/runtime/pooled-transport.d.ts +11 -11
- package/dist/runtime/pooled-transport.d.ts.map +1 -1
- package/dist/runtime/pooled-transport.js +10 -10
- package/dist/runtime/pooled-transport.js.map +1 -1
- package/dist/runtime/pyodide-bootstrap-core.generated.d.ts.map +1 -1
- package/dist/runtime/pyodide-bootstrap-core.generated.js +1 -1
- package/dist/runtime/pyodide-bootstrap-core.generated.js.map +1 -1
- package/dist/runtime/{pyodide-io.d.ts → pyodide-transport.d.ts} +8 -8
- package/dist/runtime/pyodide-transport.d.ts.map +1 -0
- package/dist/runtime/{pyodide-io.js → pyodide-transport.js} +5 -5
- package/dist/runtime/pyodide-transport.js.map +1 -0
- package/dist/runtime/pyodide.d.ts +3 -3
- package/dist/runtime/pyodide.d.ts.map +1 -1
- package/dist/runtime/pyodide.js +5 -5
- package/dist/runtime/pyodide.js.map +1 -1
- package/dist/runtime/rpc-client.d.ts +5 -5
- package/dist/runtime/rpc-client.d.ts.map +1 -1
- package/dist/runtime/rpc-client.js +6 -7
- package/dist/runtime/rpc-client.js.map +1 -1
- package/dist/runtime/{process-io.d.ts → subprocess-transport.d.ts} +9 -9
- package/dist/runtime/subprocess-transport.d.ts.map +1 -0
- package/dist/runtime/{process-io.js → subprocess-transport.js} +6 -6
- package/dist/runtime/subprocess-transport.js.map +1 -0
- package/dist/runtime/{worker-pool.d.ts → transport-pool.d.ts} +15 -15
- package/dist/runtime/transport-pool.d.ts.map +1 -0
- package/dist/runtime/{worker-pool.js → transport-pool.js} +6 -6
- package/dist/runtime/transport-pool.js.map +1 -0
- package/dist/runtime/transport.d.ts +14 -8
- package/dist/runtime/transport.d.ts.map +1 -1
- package/dist/runtime/transport.js +10 -4
- package/dist/runtime/transport.js.map +1 -1
- package/dist/types/index.d.ts +10 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/cache.d.ts +2 -2
- package/dist/utils/cache.d.ts.map +1 -1
- package/dist/utils/cache.js +2 -2
- package/dist/utils/cache.js.map +1 -1
- package/dist/utils/codec.d.ts +1 -1
- package/dist/utils/codec.d.ts.map +1 -1
- package/dist/utils/codec.js +181 -182
- package/dist/utils/codec.js.map +1 -1
- package/dist/version.d.ts +12 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +12 -0
- package/dist/version.js.map +1 -0
- package/package.json +3 -2
- package/runtime/__pycache__/safe_codec.cpython-311.pyc +0 -0
- package/runtime/__pycache__/tywrap_bridge_core.cpython-311.pyc +0 -0
- package/runtime/python_bridge.py +54 -6
- package/runtime/safe_codec.py +8 -8
- package/runtime/tywrap_bridge_core.py +159 -23
- package/src/cli.ts +8 -12
- package/src/config/index.ts +271 -165
- package/src/core/annotation-parser.ts +112 -105
- package/src/core/discovery.ts +32 -0
- package/src/core/emit-call.ts +171 -0
- package/src/core/generator.ts +67 -263
- package/src/core/mapper.ts +125 -110
- package/src/index.ts +11 -68
- package/src/runtime/bounded-context.ts +2 -2
- package/src/runtime/{safe-codec.ts → bridge-codec.ts} +27 -27
- package/src/runtime/{http-io.ts → http-transport.ts} +5 -5
- package/src/runtime/http.ts +5 -5
- package/src/runtime/index.ts +23 -1
- package/src/runtime/node.ts +15 -15
- package/src/runtime/pooled-transport.ts +15 -15
- package/src/runtime/pyodide-bootstrap-core.generated.ts +1 -1
- package/src/runtime/{pyodide-io.ts → pyodide-transport.ts} +7 -7
- package/src/runtime/pyodide.ts +6 -6
- package/src/runtime/rpc-client.ts +12 -8
- package/src/runtime/{process-io.ts → subprocess-transport.ts} +8 -8
- package/src/runtime/{worker-pool.ts → transport-pool.ts} +26 -26
- package/src/runtime/transport.ts +15 -8
- package/src/types/index.ts +10 -1
- package/src/utils/cache.ts +2 -2
- package/src/utils/codec.ts +217 -196
- package/src/version.ts +12 -0
- package/dist/runtime/http-io.d.ts.map +0 -1
- package/dist/runtime/http-io.js.map +0 -1
- package/dist/runtime/optimized-node.d.ts +0 -23
- package/dist/runtime/optimized-node.d.ts.map +0 -1
- package/dist/runtime/optimized-node.js +0 -23
- package/dist/runtime/optimized-node.js.map +0 -1
- package/dist/runtime/process-io.d.ts.map +0 -1
- package/dist/runtime/process-io.js.map +0 -1
- package/dist/runtime/protocol.d.ts +0 -3
- package/dist/runtime/protocol.d.ts.map +0 -1
- package/dist/runtime/protocol.js +0 -3
- package/dist/runtime/protocol.js.map +0 -1
- package/dist/runtime/pyodide-io.d.ts.map +0 -1
- package/dist/runtime/pyodide-io.js.map +0 -1
- package/dist/runtime/safe-codec.d.ts.map +0 -1
- package/dist/runtime/safe-codec.js.map +0 -1
- package/dist/runtime/worker-pool.d.ts.map +0 -1
- package/dist/runtime/worker-pool.js.map +0 -1
- package/dist/utils/bundle-optimizer.d.ts +0 -182
- package/dist/utils/bundle-optimizer.d.ts.map +0 -1
- package/dist/utils/bundle-optimizer.js +0 -680
- package/dist/utils/bundle-optimizer.js.map +0 -1
- package/dist/utils/memory-profiler.d.ts +0 -123
- package/dist/utils/memory-profiler.d.ts.map +0 -1
- package/dist/utils/memory-profiler.js +0 -426
- package/dist/utils/memory-profiler.js.map +0 -1
- package/src/runtime/optimized-node.ts +0 -25
- package/src/runtime/protocol.ts +0 -2
- package/src/utils/bundle-optimizer.ts +0 -876
- package/src/utils/memory-profiler.ts +0 -582
package/src/index.ts
CHANGED
|
@@ -3,70 +3,20 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @description Build-time code generation system that makes Python libraries
|
|
5
5
|
* feel native in TypeScript with zero runtime overhead
|
|
6
|
+
*
|
|
7
|
+
* This is the package root. It intentionally exposes only the stable,
|
|
8
|
+
* consumer-facing surface. Runtime plumbing (codec, transport, bridge
|
|
9
|
+
* implementations, disposables, validators) lives behind the subpath
|
|
10
|
+
* entrypoints: `tywrap/runtime`, `tywrap/node`, `tywrap/pyodide`, `tywrap/http`.
|
|
6
11
|
*/
|
|
7
12
|
|
|
8
13
|
import { tywrap } from './tywrap.js';
|
|
9
14
|
|
|
15
|
+
// Configuration
|
|
10
16
|
export type { TywrapConfig, ResolvedTywrapConfig } from './config/index.js';
|
|
11
17
|
export { defineConfig, resolveConfig } from './config/index.js';
|
|
12
|
-
// DisposableBase - lifecycle/resource base for cross-boundary components
|
|
13
|
-
export {
|
|
14
|
-
DisposableBase,
|
|
15
|
-
type ContextState,
|
|
16
|
-
type ExecuteOptions,
|
|
17
|
-
} from './runtime/bounded-context.js';
|
|
18
|
-
// RpcClient - the single correlated-RPC client (codec + transport), held by bridges
|
|
19
|
-
export { RpcClient, type RpcClientOptions } from './runtime/rpc-client.js';
|
|
20
|
-
// SafeCodec - validation and serialization for JS<->Python boundary
|
|
21
|
-
export { SafeCodec, type CodecOptions } from './runtime/safe-codec.js';
|
|
22
|
-
// Transport - abstract I/O channel interface
|
|
23
|
-
export type {
|
|
24
|
-
Transport,
|
|
25
|
-
TransportOptions,
|
|
26
|
-
ProtocolMessage,
|
|
27
|
-
ProtocolResponse,
|
|
28
|
-
} from './runtime/transport.js';
|
|
29
|
-
export {
|
|
30
|
-
PROTOCOL_ID,
|
|
31
|
-
isTransport,
|
|
32
|
-
isProtocolMessage,
|
|
33
|
-
isProtocolResponse,
|
|
34
|
-
} from './runtime/transport.js';
|
|
35
|
-
// Transport implementations
|
|
36
|
-
export { ProcessIO, type ProcessIOOptions } from './runtime/process-io.js';
|
|
37
|
-
export { HttpIO, type HttpIOOptions } from './runtime/http-io.js';
|
|
38
|
-
export { PyodideIO, type PyodideIOOptions } from './runtime/pyodide-io.js';
|
|
39
|
-
// WorkerPool - concurrent transport management
|
|
40
|
-
export { WorkerPool, type WorkerPoolOptions, type PooledWorker } from './runtime/worker-pool.js';
|
|
41
|
-
// PooledTransport - Transport adapter that wraps WorkerPool
|
|
42
|
-
export { PooledTransport, type PooledTransportOptions } from './runtime/pooled-transport.js';
|
|
43
|
-
export type { Disposable } from './runtime/disposable.js';
|
|
44
|
-
export { isDisposable, safeDispose, disposeAll } from './runtime/disposable.js';
|
|
45
|
-
export {
|
|
46
|
-
ValidationError,
|
|
47
|
-
isFiniteNumber,
|
|
48
|
-
isPositiveNumber,
|
|
49
|
-
isNonNegativeNumber,
|
|
50
|
-
isNonEmptyString,
|
|
51
|
-
isPlainObject,
|
|
52
|
-
assertFiniteNumber,
|
|
53
|
-
assertPositive,
|
|
54
|
-
assertNonNegative,
|
|
55
|
-
assertString,
|
|
56
|
-
assertNonEmptyString,
|
|
57
|
-
assertArray,
|
|
58
|
-
assertObject,
|
|
59
|
-
containsSpecialFloat,
|
|
60
|
-
assertNoSpecialFloats,
|
|
61
|
-
sanitizeForFilename,
|
|
62
|
-
containsPathTraversal,
|
|
63
|
-
} from './runtime/validators.js';
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* @deprecated Use DisposableBase instead. RuntimeBridge will be removed in the next major version.
|
|
67
|
-
*/
|
|
68
|
-
export { RuntimeBridge } from './runtime/base.js';
|
|
69
18
|
|
|
19
|
+
// Bridge error hierarchy
|
|
70
20
|
export {
|
|
71
21
|
BridgeError,
|
|
72
22
|
BridgeCodecError,
|
|
@@ -75,12 +25,6 @@ export {
|
|
|
75
25
|
BridgeDisposedError,
|
|
76
26
|
BridgeExecutionError,
|
|
77
27
|
} from './runtime/errors.js';
|
|
78
|
-
export { getRuntimeBridge, setRuntimeBridge, clearRuntimeBridge } from './runtime/index.js';
|
|
79
|
-
|
|
80
|
-
// Runtime-specific exports (Bridge facades that HOLD an RpcClient)
|
|
81
|
-
export { NodeBridge, type NodeBridgeOptions } from './runtime/node.js';
|
|
82
|
-
export { PyodideBridge, type PyodideBridgeOptions } from './runtime/pyodide.js';
|
|
83
|
-
export { HttpBridge, type HttpBridgeOptions } from './runtime/http.js';
|
|
84
28
|
|
|
85
29
|
// Core types
|
|
86
30
|
export type {
|
|
@@ -138,6 +82,8 @@ export type { GenerateFailure, GenerateResult, GenerateRunOptions } from './tywr
|
|
|
138
82
|
|
|
139
83
|
// Runtime detection utilities
|
|
140
84
|
export { detectRuntime, isNodejs, isDeno, isBun, isBrowser } from './utils/runtime.js';
|
|
85
|
+
|
|
86
|
+
// Arrow/codec public helpers
|
|
141
87
|
export {
|
|
142
88
|
decodeValue,
|
|
143
89
|
decodeValueAsync,
|
|
@@ -146,11 +92,8 @@ export {
|
|
|
146
92
|
clearArrowDecoder,
|
|
147
93
|
} from './utils/codec.js';
|
|
148
94
|
|
|
149
|
-
// Version info
|
|
150
|
-
export
|
|
95
|
+
// Version info — single-sourced from package.json via scripts/generate-version.mjs
|
|
96
|
+
export { VERSION } from './version.js';
|
|
151
97
|
|
|
152
|
-
/**
|
|
153
|
-
* Quick setup function for getting started
|
|
154
|
-
*/
|
|
155
98
|
// Default export for convenience
|
|
156
99
|
export default tywrap;
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* ownership tracking. It carries ZERO RPC methods by design — the
|
|
7
7
|
* cross-boundary RPC contract (call/instantiate/callMethod/disposeInstance)
|
|
8
8
|
* is PythonRuntime, implemented only by the bridge facades, never by a base
|
|
9
|
-
* class or a transport. This separation is what keeps transports (
|
|
10
|
-
* PooledTransport,
|
|
9
|
+
* class or a transport. This separation is what keeps transports (SubprocessTransport,
|
|
10
|
+
* PooledTransport, TransportPool, PyodideTransport) and RpcClient from having to stub
|
|
11
11
|
* RPC methods they do not implement.
|
|
12
12
|
*
|
|
13
13
|
* Reused by RpcClient and all transports.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* BridgeCodec - Unified validation and serialization for JS<->Python boundary crossing.
|
|
3
3
|
*
|
|
4
4
|
* Provides safe encoding/decoding with configurable guardrails for:
|
|
5
5
|
* - Special float rejection (NaN, Infinity)
|
|
@@ -18,7 +18,7 @@ import { PROTOCOL_ID } from './transport.js';
|
|
|
18
18
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
* Configuration options for
|
|
21
|
+
* Configuration options for BridgeCodec behavior.
|
|
22
22
|
*/
|
|
23
23
|
export interface CodecOptions {
|
|
24
24
|
/** Reject NaN/Infinity in arguments. Default: true */
|
|
@@ -48,7 +48,7 @@ interface NormalizedPythonError {
|
|
|
48
48
|
traceback?: string;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
interface
|
|
51
|
+
interface RpcResponse {
|
|
52
52
|
id: number;
|
|
53
53
|
protocol?: string;
|
|
54
54
|
result?: unknown;
|
|
@@ -167,10 +167,10 @@ function isPythonErrorResponse(value: unknown): value is PythonErrorResponse {
|
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
/**
|
|
170
|
-
* Type guard for
|
|
171
|
-
*
|
|
170
|
+
* Type guard for the RPC response wrapper ({ id, result | error }).
|
|
171
|
+
* Responses must include a numeric id.
|
|
172
172
|
*/
|
|
173
|
-
function
|
|
173
|
+
function isRpcResponse(value: unknown): value is RpcResponse {
|
|
174
174
|
if (value === null || typeof value !== 'object') {
|
|
175
175
|
return false;
|
|
176
176
|
}
|
|
@@ -233,7 +233,7 @@ function summarizePayloadForError(payload: string): string {
|
|
|
233
233
|
|
|
234
234
|
/**
|
|
235
235
|
* Validate the protocol version in a response.
|
|
236
|
-
* Only validates when the response looks like
|
|
236
|
+
* Only validates when the response looks like an RPC response (has 'id' field).
|
|
237
237
|
* Throws if protocol is present but doesn't match expected version.
|
|
238
238
|
* Allows missing protocol for backwards compatibility.
|
|
239
239
|
*/
|
|
@@ -242,7 +242,7 @@ function validateProtocolVersion(value: unknown): void {
|
|
|
242
242
|
return;
|
|
243
243
|
}
|
|
244
244
|
const obj = value as Record<string, unknown>;
|
|
245
|
-
// Only validate protocol on
|
|
245
|
+
// Only validate protocol on RPC responses (responses with 'id' field)
|
|
246
246
|
// This avoids false positives on user data that happens to contain 'protocol' key
|
|
247
247
|
if (!('id' in obj)) {
|
|
248
248
|
return;
|
|
@@ -293,16 +293,16 @@ function findSpecialFloatPath(
|
|
|
293
293
|
}
|
|
294
294
|
|
|
295
295
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
296
|
-
//
|
|
296
|
+
// BRIDGE CODEC CLASS
|
|
297
297
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
298
298
|
|
|
299
299
|
/**
|
|
300
|
-
*
|
|
300
|
+
* BridgeCodec provides unified validation and serialization for JS<->Python
|
|
301
301
|
* boundary crossing with configurable guardrails.
|
|
302
302
|
*
|
|
303
303
|
* @example
|
|
304
304
|
* ```typescript
|
|
305
|
-
* const codec = new
|
|
305
|
+
* const codec = new BridgeCodec({ rejectSpecialFloats: true });
|
|
306
306
|
*
|
|
307
307
|
* // Encoding a request
|
|
308
308
|
* const payload = codec.encodeRequest({ data: [1, 2, 3] });
|
|
@@ -314,7 +314,7 @@ function findSpecialFloatPath(
|
|
|
314
314
|
* const dataframe = await codec.decodeResponseAsync<ArrowTable>(arrowPayload);
|
|
315
315
|
* ```
|
|
316
316
|
*/
|
|
317
|
-
export class
|
|
317
|
+
export class BridgeCodec {
|
|
318
318
|
private readonly rejectSpecialFloats: boolean;
|
|
319
319
|
private readonly rejectNonStringKeys: boolean;
|
|
320
320
|
private readonly maxPayloadBytes: number;
|
|
@@ -332,7 +332,7 @@ export class SafeCodec {
|
|
|
332
332
|
}
|
|
333
333
|
|
|
334
334
|
private assertValidBase64(b64: string): void {
|
|
335
|
-
if (!
|
|
335
|
+
if (!BridgeCodec.base64Pattern.test(b64)) {
|
|
336
336
|
throw new BridgeCodecError('Invalid base64 in bytes envelope', {
|
|
337
337
|
codecPhase: 'decode',
|
|
338
338
|
valueType: 'bytes',
|
|
@@ -368,8 +368,8 @@ export class SafeCodec {
|
|
|
368
368
|
* JSON.parse reviver that decodes bytes envelopes.
|
|
369
369
|
*
|
|
370
370
|
* Supported shapes:
|
|
371
|
-
* - { "__tywrap_bytes__": true, "b64": "..." } (JS
|
|
372
|
-
* - { "__type__": "bytes", "encoding": "base64", "data": "..." } (Python
|
|
371
|
+
* - { "__tywrap_bytes__": true, "b64": "..." } (JS BridgeCodec.encodeRequest; also allowed in responses)
|
|
372
|
+
* - { "__type__": "bytes", "encoding": "base64", "data": "..." } (Python BridgeCodec default encoder)
|
|
373
373
|
*/
|
|
374
374
|
private reviveValue(_key: string, value: unknown): unknown {
|
|
375
375
|
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
@@ -410,20 +410,20 @@ export class SafeCodec {
|
|
|
410
410
|
return bridgeError;
|
|
411
411
|
}
|
|
412
412
|
|
|
413
|
-
private
|
|
414
|
-
if (
|
|
415
|
-
const
|
|
416
|
-
const hasResult = hasOwnKey(
|
|
417
|
-
const hasError = hasOwnKey(
|
|
413
|
+
private extractResultFromRpcResponse(parsed: unknown): unknown {
|
|
414
|
+
if (isRpcResponse(parsed)) {
|
|
415
|
+
const response = parsed;
|
|
416
|
+
const hasResult = hasOwnKey(response, 'result');
|
|
417
|
+
const hasError = hasOwnKey(response, 'error');
|
|
418
418
|
|
|
419
419
|
if (hasResult && hasError) {
|
|
420
420
|
throw new BridgeProtocolError('Protocol response cannot include both "result" and "error"');
|
|
421
421
|
}
|
|
422
422
|
|
|
423
423
|
if (hasError) {
|
|
424
|
-
const normalizedError = normalizeErrorPayload(
|
|
424
|
+
const normalizedError = normalizeErrorPayload(response.error);
|
|
425
425
|
if (!normalizedError) {
|
|
426
|
-
const details = describeInvalidErrorPayload(
|
|
426
|
+
const details = describeInvalidErrorPayload(response.error);
|
|
427
427
|
throw new BridgeProtocolError(`Invalid response "error" payload: ${details}`);
|
|
428
428
|
}
|
|
429
429
|
throw this.toBridgeExecutionError(normalizedError);
|
|
@@ -433,7 +433,7 @@ export class SafeCodec {
|
|
|
433
433
|
throw new BridgeProtocolError('Protocol response missing "result" or "error" field');
|
|
434
434
|
}
|
|
435
435
|
|
|
436
|
-
return
|
|
436
|
+
return response.result;
|
|
437
437
|
}
|
|
438
438
|
|
|
439
439
|
if (isPythonErrorResponse(parsed)) {
|
|
@@ -522,7 +522,7 @@ export class SafeCodec {
|
|
|
522
522
|
* @param payload - The JSON string received from Python
|
|
523
523
|
* @returns Decoded and validated result
|
|
524
524
|
* @throws BridgeCodecError if payload is invalid or decoding fails
|
|
525
|
-
* @throws BridgeProtocolError if
|
|
525
|
+
* @throws BridgeProtocolError if the RPC response is invalid
|
|
526
526
|
* @throws BridgeExecutionError if response contains a Python error
|
|
527
527
|
*/
|
|
528
528
|
decodeResponse<T>(payload: string): T {
|
|
@@ -553,7 +553,7 @@ export class SafeCodec {
|
|
|
553
553
|
// Validate protocol version (if present)
|
|
554
554
|
validateProtocolVersion(parsed);
|
|
555
555
|
|
|
556
|
-
const result = this.
|
|
556
|
+
const result = this.extractResultFromRpcResponse(parsed);
|
|
557
557
|
|
|
558
558
|
// Post-decode validation for special floats if enabled
|
|
559
559
|
if (this.rejectSpecialFloats && containsSpecialFloat(result)) {
|
|
@@ -574,7 +574,7 @@ export class SafeCodec {
|
|
|
574
574
|
* @param payload - The JSON string received from Python
|
|
575
575
|
* @returns Decoded and validated result with Arrow decoding applied
|
|
576
576
|
* @throws BridgeCodecError if payload is invalid or decoding fails
|
|
577
|
-
* @throws BridgeProtocolError if
|
|
577
|
+
* @throws BridgeProtocolError if the RPC response is invalid
|
|
578
578
|
* @throws BridgeExecutionError if response contains a Python error
|
|
579
579
|
*/
|
|
580
580
|
async decodeResponseAsync<T>(payload: string): Promise<T> {
|
|
@@ -605,7 +605,7 @@ export class SafeCodec {
|
|
|
605
605
|
// Validate protocol version (if present)
|
|
606
606
|
validateProtocolVersion(parsed);
|
|
607
607
|
|
|
608
|
-
const result = this.
|
|
608
|
+
const result = this.extractResultFromRpcResponse(parsed);
|
|
609
609
|
|
|
610
610
|
// Apply Arrow decoding to the result
|
|
611
611
|
let decoded: unknown;
|
|
@@ -20,9 +20,9 @@ import type { Transport } from './transport.js';
|
|
|
20
20
|
// =============================================================================
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* Configuration options for
|
|
23
|
+
* Configuration options for the HttpTransport.
|
|
24
24
|
*/
|
|
25
|
-
export interface
|
|
25
|
+
export interface HttpTransportOptions {
|
|
26
26
|
/** Base URL for the Python server (e.g., 'http://localhost:8000') */
|
|
27
27
|
baseURL: string;
|
|
28
28
|
|
|
@@ -52,7 +52,7 @@ export interface HttpIOOptions {
|
|
|
52
52
|
*
|
|
53
53
|
* @example
|
|
54
54
|
* ```typescript
|
|
55
|
-
* const transport = new
|
|
55
|
+
* const transport = new HttpTransport({ baseURL: 'http://localhost:8000' });
|
|
56
56
|
* await transport.init(); // No-op but follows Transport contract
|
|
57
57
|
*
|
|
58
58
|
* const response = await transport.send(
|
|
@@ -63,13 +63,13 @@ export interface HttpIOOptions {
|
|
|
63
63
|
* await transport.dispose(); // Marks as disposed
|
|
64
64
|
* ```
|
|
65
65
|
*/
|
|
66
|
-
export class
|
|
66
|
+
export class HttpTransport implements Transport {
|
|
67
67
|
private readonly baseURL: string;
|
|
68
68
|
private readonly headers: Record<string, string>;
|
|
69
69
|
private readonly defaultTimeoutMs: number;
|
|
70
70
|
private _isDisposed = false;
|
|
71
71
|
|
|
72
|
-
constructor(options:
|
|
72
|
+
constructor(options: HttpTransportOptions) {
|
|
73
73
|
// Normalize URL - remove trailing slash
|
|
74
74
|
this.baseURL = options.baseURL.replace(/\/$/, '');
|
|
75
75
|
this.headers = {
|
package/src/runtime/http.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* HTTP runtime bridge.
|
|
3
3
|
*
|
|
4
4
|
* HttpBridge is a thin facade: it extends DisposableBase (lifecycle/resources)
|
|
5
|
-
* and implements PythonRuntime by HOLDING an RpcClient over an
|
|
5
|
+
* and implements PythonRuntime by HOLDING an RpcClient over an HttpTransport
|
|
6
6
|
* for stateless HTTP POST-based communication with a Python server.
|
|
7
7
|
*
|
|
8
8
|
* @see https://github.com/bbopen/tywrap/issues/149
|
|
@@ -13,8 +13,8 @@ import { autoRegisterArrowDecoder } from '../utils/codec.js';
|
|
|
13
13
|
|
|
14
14
|
import { DisposableBase } from './bounded-context.js';
|
|
15
15
|
import { RpcClient, type GetBridgeInfoOptions } from './rpc-client.js';
|
|
16
|
-
import {
|
|
17
|
-
import type { CodecOptions } from './
|
|
16
|
+
import { HttpTransport } from './http-transport.js';
|
|
17
|
+
import type { CodecOptions } from './bridge-codec.js';
|
|
18
18
|
|
|
19
19
|
// =============================================================================
|
|
20
20
|
// OPTIONS
|
|
@@ -51,7 +51,7 @@ export interface HttpBridgeOptions {
|
|
|
51
51
|
* Features:
|
|
52
52
|
* - Stateless HTTP POST communication
|
|
53
53
|
* - Timeout handling via AbortController
|
|
54
|
-
* - Full
|
|
54
|
+
* - Full BridgeCodec validation (NaN/Infinity rejection, key validation)
|
|
55
55
|
* - Automatic Arrow decoding for DataFrames/ndarrays
|
|
56
56
|
*
|
|
57
57
|
* @example
|
|
@@ -76,7 +76,7 @@ export class HttpBridge extends DisposableBase implements PythonRuntime {
|
|
|
76
76
|
constructor(options: HttpBridgeOptions) {
|
|
77
77
|
super();
|
|
78
78
|
|
|
79
|
-
const transport = new
|
|
79
|
+
const transport = new HttpTransport({
|
|
80
80
|
baseURL: options.baseURL,
|
|
81
81
|
headers: options.headers,
|
|
82
82
|
defaultTimeoutMs: options.timeoutMs,
|
package/src/runtime/index.ts
CHANGED
|
@@ -1,9 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* tywrap/runtime entrypoint.
|
|
3
|
+
*
|
|
4
|
+
* Exposes the runtime bridge registry used by generated wrappers, plus the
|
|
5
|
+
* lower-level boundary primitives (codec + transport contract) for consumers
|
|
6
|
+
* building custom bridges. These were previously re-exported from the package
|
|
7
|
+
* root; they now live here so the root surface stays small.
|
|
3
8
|
*/
|
|
4
9
|
|
|
5
10
|
import type { RuntimeExecution } from '../types/index.js';
|
|
6
11
|
|
|
12
|
+
// BridgeCodec — validation and serialization for the JS<->Python boundary
|
|
13
|
+
export { BridgeCodec, type CodecOptions } from './bridge-codec.js';
|
|
14
|
+
|
|
15
|
+
// Transport contract — abstract I/O channel interface and guards
|
|
16
|
+
export type {
|
|
17
|
+
Transport,
|
|
18
|
+
TransportOptions,
|
|
19
|
+
ProtocolMessage,
|
|
20
|
+
ProtocolResponse,
|
|
21
|
+
} from './transport.js';
|
|
22
|
+
export {
|
|
23
|
+
PROTOCOL_ID,
|
|
24
|
+
isTransport,
|
|
25
|
+
isProtocolMessage,
|
|
26
|
+
isProtocolResponse,
|
|
27
|
+
} from './transport.js';
|
|
28
|
+
|
|
7
29
|
let runtimeBridge: RuntimeExecution | null = null;
|
|
8
30
|
|
|
9
31
|
export function setRuntimeBridge(bridge: RuntimeExecution): void {
|
package/src/runtime/node.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Node.js runtime bridge.
|
|
3
3
|
*
|
|
4
4
|
* NodeBridge is a thin facade: it extends DisposableBase (lifecycle/resources)
|
|
5
|
-
* and implements PythonRuntime by HOLDING an RpcClient. It uses
|
|
5
|
+
* and implements PythonRuntime by HOLDING an RpcClient. It uses SubprocessTransport
|
|
6
6
|
* transports with optional pooling for concurrent Python execution.
|
|
7
7
|
*
|
|
8
8
|
* @see https://github.com/bbopen/tywrap/issues/149
|
|
@@ -22,10 +22,10 @@ import { globalCache } from '../utils/cache.js';
|
|
|
22
22
|
import { DisposableBase } from './bounded-context.js';
|
|
23
23
|
import { RpcClient, type GetBridgeInfoOptions } from './rpc-client.js';
|
|
24
24
|
import { BridgeCodecError, BridgeExecutionError, BridgeProtocolError } from './errors.js';
|
|
25
|
-
import {
|
|
25
|
+
import { SubprocessTransport } from './subprocess-transport.js';
|
|
26
26
|
import { PooledTransport } from './pooled-transport.js';
|
|
27
|
-
import type { CodecOptions } from './
|
|
28
|
-
import type {
|
|
27
|
+
import type { CodecOptions } from './bridge-codec.js';
|
|
28
|
+
import type { TransportLease } from './transport-pool.js';
|
|
29
29
|
|
|
30
30
|
// =============================================================================
|
|
31
31
|
// OPTIONS
|
|
@@ -84,12 +84,12 @@ export interface NodeBridgeOptions {
|
|
|
84
84
|
// ===========================================================================
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
|
-
* @deprecated No longer used. Pool idle time is managed by
|
|
87
|
+
* @deprecated No longer used. Pool idle time is managed by TransportPool.
|
|
88
88
|
*/
|
|
89
89
|
maxIdleTime?: number;
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
|
-
* @deprecated No longer used. Process restart is managed by
|
|
92
|
+
* @deprecated No longer used. Process restart is managed by SubprocessTransport.
|
|
93
93
|
*/
|
|
94
94
|
maxRequestsPerProcess?: number;
|
|
95
95
|
|
|
@@ -99,7 +99,7 @@ export interface NodeBridgeOptions {
|
|
|
99
99
|
enableJsonFallback?: boolean;
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
|
-
* @deprecated Use
|
|
102
|
+
* @deprecated Use SubprocessTransport options instead.
|
|
103
103
|
*/
|
|
104
104
|
maxLineLength?: number;
|
|
105
105
|
}
|
|
@@ -277,7 +277,7 @@ function normalizeWarmupCommands(commands: NodeBridgeOptions['warmupCommands']):
|
|
|
277
277
|
* Features:
|
|
278
278
|
* - Single or multi-process execution via process pooling
|
|
279
279
|
* - Virtual environment support
|
|
280
|
-
* - Full
|
|
280
|
+
* - Full BridgeCodec validation (NaN/Infinity rejection, key validation)
|
|
281
281
|
* - Automatic Arrow decoding for DataFrames/ndarrays
|
|
282
282
|
* - Optional result caching for pure functions
|
|
283
283
|
* - Process warmup commands
|
|
@@ -343,7 +343,7 @@ export class NodeBridge extends DisposableBase implements PythonRuntime {
|
|
|
343
343
|
codec: options.codec,
|
|
344
344
|
warmupCommands,
|
|
345
345
|
};
|
|
346
|
-
// Build environment for
|
|
346
|
+
// Build environment for SubprocessTransport
|
|
347
347
|
const processEnv = buildProcessEnv(resolvedOptions);
|
|
348
348
|
|
|
349
349
|
// Why a late-bound holder: PooledTransport copies onWorkerReady eagerly at
|
|
@@ -360,10 +360,10 @@ export class NodeBridge extends DisposableBase implements PythonRuntime {
|
|
|
360
360
|
resolvedOptions.timeoutMs
|
|
361
361
|
);
|
|
362
362
|
|
|
363
|
-
// Create pooled transport with
|
|
363
|
+
// Create pooled transport with SubprocessTransport workers
|
|
364
364
|
const transport = new PooledTransport({
|
|
365
|
-
createTransport: ():
|
|
366
|
-
new
|
|
365
|
+
createTransport: (): SubprocessTransport =>
|
|
366
|
+
new SubprocessTransport({
|
|
367
367
|
pythonPath: resolvedOptions.pythonPath,
|
|
368
368
|
bridgeScript: resolvedOptions.scriptPath,
|
|
369
369
|
env: processEnv,
|
|
@@ -676,8 +676,8 @@ function createWorkerReadyCallback(
|
|
|
676
676
|
rpcHolder: { rpc?: RpcClient },
|
|
677
677
|
warmupCommands: WarmupCommand[],
|
|
678
678
|
timeoutMs: number
|
|
679
|
-
): (worker:
|
|
680
|
-
return async (worker:
|
|
679
|
+
): (worker: TransportLease) => Promise<void> {
|
|
680
|
+
return async (worker: TransportLease) => {
|
|
681
681
|
const rpc = rpcHolder.rpc;
|
|
682
682
|
if (!rpc) {
|
|
683
683
|
throw new BridgeExecutionError('Worker warmup attempted before RpcClient was wired');
|
|
@@ -721,7 +721,7 @@ function createWorkerReadyCallback(
|
|
|
721
721
|
// =============================================================================
|
|
722
722
|
|
|
723
723
|
/**
|
|
724
|
-
* Build environment variables for
|
|
724
|
+
* Build environment variables for SubprocessTransport.
|
|
725
725
|
*/
|
|
726
726
|
function buildProcessEnv(options: ResolvedOptions): Record<string, string> {
|
|
727
727
|
const allowedPrefixes = ['TYWRAP_'];
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PooledTransport - Transport adapter that wraps
|
|
2
|
+
* PooledTransport - Transport adapter that wraps TransportPool for multi-process support.
|
|
3
3
|
*
|
|
4
|
-
* This transport implements the Transport interface by delegating to a
|
|
5
|
-
* of
|
|
4
|
+
* This transport implements the Transport interface by delegating to a TransportPool
|
|
5
|
+
* of SubprocessTransport transports. Each send() acquires a worker, sends the message,
|
|
6
6
|
* and releases the worker back to the pool.
|
|
7
7
|
*
|
|
8
8
|
* @see https://github.com/bbopen/tywrap/issues/149
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { DisposableBase } from './bounded-context.js';
|
|
12
12
|
import { BridgeDisposedError, BridgeExecutionError } from './errors.js';
|
|
13
13
|
import type { Transport } from './transport.js';
|
|
14
|
-
import {
|
|
14
|
+
import { TransportPool, type TransportLease } from './transport-pool.js';
|
|
15
15
|
|
|
16
16
|
// =============================================================================
|
|
17
17
|
// TYPES
|
|
@@ -40,12 +40,12 @@ export interface PooledTransportOptions {
|
|
|
40
40
|
* Callback invoked after each worker is created and initialized.
|
|
41
41
|
* Use this for per-worker warmup (e.g., importing modules, running setup).
|
|
42
42
|
*/
|
|
43
|
-
onWorkerReady?: (worker:
|
|
43
|
+
onWorkerReady?: (worker: TransportLease) => Promise<void>;
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* Optional callback used only for background replacement workers.
|
|
47
47
|
*/
|
|
48
|
-
onReplacementWorkerReady?: (worker:
|
|
48
|
+
onReplacementWorkerReady?: (worker: TransportLease) => Promise<void>;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
// =============================================================================
|
|
@@ -53,10 +53,10 @@ export interface PooledTransportOptions {
|
|
|
53
53
|
// =============================================================================
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
* Transport adapter that wraps
|
|
56
|
+
* Transport adapter that wraps TransportPool for multi-process message handling.
|
|
57
57
|
*
|
|
58
58
|
* PooledTransport presents a single Transport interface while internally
|
|
59
|
-
* distributing requests across multiple worker transports (typically
|
|
59
|
+
* distributing requests across multiple worker transports (typically SubprocessTransport).
|
|
60
60
|
*
|
|
61
61
|
* Features:
|
|
62
62
|
* - Lazy worker creation (transports created on demand)
|
|
@@ -67,7 +67,7 @@ export interface PooledTransportOptions {
|
|
|
67
67
|
* @example
|
|
68
68
|
* ```typescript
|
|
69
69
|
* const transport = new PooledTransport({
|
|
70
|
-
* createTransport: () => new
|
|
70
|
+
* createTransport: () => new SubprocessTransport({
|
|
71
71
|
* bridgeScript: '/path/to/bridge.py',
|
|
72
72
|
* }),
|
|
73
73
|
* maxWorkers: 4,
|
|
@@ -87,10 +87,10 @@ export class PooledTransport extends DisposableBase implements Transport {
|
|
|
87
87
|
Required<PooledTransportOptions>,
|
|
88
88
|
'onWorkerReady' | 'onReplacementWorkerReady'
|
|
89
89
|
> & {
|
|
90
|
-
onWorkerReady?: (worker:
|
|
91
|
-
onReplacementWorkerReady?: (worker:
|
|
90
|
+
onWorkerReady?: (worker: TransportLease) => Promise<void>;
|
|
91
|
+
onReplacementWorkerReady?: (worker: TransportLease) => Promise<void>;
|
|
92
92
|
};
|
|
93
|
-
private pool?:
|
|
93
|
+
private pool?: TransportPool;
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
96
|
* Create a new PooledTransport.
|
|
@@ -122,11 +122,11 @@ export class PooledTransport extends DisposableBase implements Transport {
|
|
|
122
122
|
/**
|
|
123
123
|
* Initialize the pooled transport.
|
|
124
124
|
*
|
|
125
|
-
* Creates and initializes the internal
|
|
125
|
+
* Creates and initializes the internal TransportPool.
|
|
126
126
|
* If minWorkers > 0, workers are pre-spawned during init.
|
|
127
127
|
*/
|
|
128
128
|
protected async doInit(): Promise<void> {
|
|
129
|
-
this.pool = new
|
|
129
|
+
this.pool = new TransportPool({
|
|
130
130
|
createTransport: this.poolOptions.createTransport,
|
|
131
131
|
maxWorkers: this.poolOptions.maxWorkers,
|
|
132
132
|
minWorkers: this.poolOptions.minWorkers,
|
|
@@ -142,7 +142,7 @@ export class PooledTransport extends DisposableBase implements Transport {
|
|
|
142
142
|
/**
|
|
143
143
|
* Dispose the pooled transport.
|
|
144
144
|
*
|
|
145
|
-
* Disposes the internal
|
|
145
|
+
* Disposes the internal TransportPool, which disposes all workers.
|
|
146
146
|
*/
|
|
147
147
|
protected async doDispose(): Promise<void> {
|
|
148
148
|
if (this.pool) {
|