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.
Files changed (93) hide show
  1. package/README.md +11 -4
  2. package/dist/index.d.ts +20 -4
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +22 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/runtime/base.d.ts +17 -7
  7. package/dist/runtime/base.d.ts.map +1 -1
  8. package/dist/runtime/base.js +18 -1
  9. package/dist/runtime/base.js.map +1 -1
  10. package/dist/runtime/bounded-context.d.ts +252 -0
  11. package/dist/runtime/bounded-context.d.ts.map +1 -0
  12. package/dist/runtime/bounded-context.js +454 -0
  13. package/dist/runtime/bounded-context.js.map +1 -0
  14. package/dist/runtime/bridge-protocol.d.ts +167 -0
  15. package/dist/runtime/bridge-protocol.d.ts.map +1 -0
  16. package/dist/runtime/bridge-protocol.js +247 -0
  17. package/dist/runtime/bridge-protocol.js.map +1 -0
  18. package/dist/runtime/disposable.d.ts +40 -0
  19. package/dist/runtime/disposable.d.ts.map +1 -0
  20. package/dist/runtime/disposable.js +49 -0
  21. package/dist/runtime/disposable.js.map +1 -0
  22. package/dist/runtime/http-io.d.ts +91 -0
  23. package/dist/runtime/http-io.d.ts.map +1 -0
  24. package/dist/runtime/http-io.js +195 -0
  25. package/dist/runtime/http-io.js.map +1 -0
  26. package/dist/runtime/http.d.ts +47 -13
  27. package/dist/runtime/http.d.ts.map +1 -1
  28. package/dist/runtime/http.js +55 -74
  29. package/dist/runtime/http.js.map +1 -1
  30. package/dist/runtime/node.d.ts +142 -28
  31. package/dist/runtime/node.d.ts.map +1 -1
  32. package/dist/runtime/node.js +321 -168
  33. package/dist/runtime/node.js.map +1 -1
  34. package/dist/runtime/optimized-node.d.ts +19 -125
  35. package/dist/runtime/optimized-node.d.ts.map +1 -1
  36. package/dist/runtime/optimized-node.js +19 -550
  37. package/dist/runtime/optimized-node.js.map +1 -1
  38. package/dist/runtime/pooled-transport.d.ts +131 -0
  39. package/dist/runtime/pooled-transport.d.ts.map +1 -0
  40. package/dist/runtime/pooled-transport.js +175 -0
  41. package/dist/runtime/pooled-transport.js.map +1 -0
  42. package/dist/runtime/process-io.d.ts +204 -0
  43. package/dist/runtime/process-io.d.ts.map +1 -0
  44. package/dist/runtime/process-io.js +695 -0
  45. package/dist/runtime/process-io.js.map +1 -0
  46. package/dist/runtime/pyodide-io.d.ts +155 -0
  47. package/dist/runtime/pyodide-io.d.ts.map +1 -0
  48. package/dist/runtime/pyodide-io.js +397 -0
  49. package/dist/runtime/pyodide-io.js.map +1 -0
  50. package/dist/runtime/pyodide.d.ts +51 -19
  51. package/dist/runtime/pyodide.d.ts.map +1 -1
  52. package/dist/runtime/pyodide.js +57 -186
  53. package/dist/runtime/pyodide.js.map +1 -1
  54. package/dist/runtime/safe-codec.d.ts +81 -0
  55. package/dist/runtime/safe-codec.d.ts.map +1 -0
  56. package/dist/runtime/safe-codec.js +345 -0
  57. package/dist/runtime/safe-codec.js.map +1 -0
  58. package/dist/runtime/transport.d.ts +186 -0
  59. package/dist/runtime/transport.d.ts.map +1 -0
  60. package/dist/runtime/transport.js +86 -0
  61. package/dist/runtime/transport.js.map +1 -0
  62. package/dist/runtime/validators.d.ts +131 -0
  63. package/dist/runtime/validators.d.ts.map +1 -0
  64. package/dist/runtime/validators.js +219 -0
  65. package/dist/runtime/validators.js.map +1 -0
  66. package/dist/runtime/worker-pool.d.ts +196 -0
  67. package/dist/runtime/worker-pool.d.ts.map +1 -0
  68. package/dist/runtime/worker-pool.js +371 -0
  69. package/dist/runtime/worker-pool.js.map +1 -0
  70. package/dist/utils/codec.d.ts.map +1 -1
  71. package/dist/utils/codec.js +120 -1
  72. package/dist/utils/codec.js.map +1 -1
  73. package/package.json +2 -2
  74. package/runtime/python_bridge.py +30 -3
  75. package/runtime/safe_codec.py +344 -0
  76. package/src/index.ts +48 -5
  77. package/src/runtime/base.ts +18 -26
  78. package/src/runtime/bounded-context.ts +608 -0
  79. package/src/runtime/bridge-protocol.ts +319 -0
  80. package/src/runtime/disposable.ts +65 -0
  81. package/src/runtime/http-io.ts +244 -0
  82. package/src/runtime/http.ts +71 -117
  83. package/src/runtime/node.ts +460 -217
  84. package/src/runtime/optimized-node.ts +19 -761
  85. package/src/runtime/pooled-transport.ts +252 -0
  86. package/src/runtime/process-io.ts +902 -0
  87. package/src/runtime/pyodide-io.ts +485 -0
  88. package/src/runtime/pyodide.ts +75 -215
  89. package/src/runtime/safe-codec.ts +443 -0
  90. package/src/runtime/transport.ts +273 -0
  91. package/src/runtime/validators.ts +241 -0
  92. package/src/runtime/worker-pool.ts +498 -0
  93. package/src/utils/codec.ts +126 -1
@@ -11,6 +11,8 @@ import decimal
11
11
  import uuid
12
12
  from pathlib import Path, PurePath
13
13
 
14
+ from safe_codec import SafeCodec, CodecError
15
+
14
16
  # Ensure the working directory is importable so local modules can be resolved when
15
17
  # the bridge is launched as a script from a different directory.
16
18
  try:
@@ -95,6 +97,16 @@ def get_codec_max_bytes():
95
97
  # Why: parse once at startup to avoid per-response env lookups.
96
98
  CODEC_MAX_BYTES = get_codec_max_bytes()
97
99
 
100
+ # Why: use SafeCodec for final JSON encoding to reject NaN/Infinity and handle
101
+ # edge cases like numpy scalars. We use sys.maxsize for SafeCodec's internal limit
102
+ # to preserve the original "no limit unless TYWRAP_CODEC_MAX_BYTES is set" behavior.
103
+ # The explicit size check in encode_response() provides the specific error message
104
+ # mentioning the env var name, which is important for debugging.
105
+ _response_codec = SafeCodec(
106
+ allow_nan=False,
107
+ max_payload_bytes=sys.maxsize,
108
+ )
109
+
98
110
 
99
111
  def get_request_max_bytes():
100
112
  """
@@ -267,6 +279,11 @@ def serialize_ndarray(obj):
267
279
  Why: Arrow IPC gives a compact, lossless binary payload that the JS side can decode as a
268
280
  Table. If JSON fallback is explicitly requested, honor it even when pyarrow is installed so
269
281
  callers don't unexpectedly need an Arrow decoder on the TypeScript side.
282
+
283
+ Note: PyArrow's pa.array() only handles 1D arrays. For multi-dimensional arrays, we flatten
284
+ before encoding and include shape metadata for reconstruction on the JS side. This maintains
285
+ Arrow's binary efficiency while working with the current arrow-js implementation (which
286
+ doesn't yet support FixedShapeTensorArray). See: https://github.com/apache/arrow-js/issues/115
270
287
  """
271
288
  if FALLBACK_JSON:
272
289
  return serialize_ndarray_json(obj)
@@ -277,7 +294,11 @@ def serialize_ndarray(obj):
277
294
  'Arrow encoding unavailable for ndarray; install pyarrow or set TYWRAP_CODEC_FALLBACK=json to enable JSON fallback'
278
295
  ) from exc
279
296
  try:
280
- arr = pa.array(obj)
297
+ # Flatten multi-dimensional arrays for Arrow compatibility
298
+ # pa.array() only handles 1D arrays; we preserve shape for JS-side reconstruction
299
+ original_shape = list(obj.shape) if hasattr(obj, 'shape') else None
300
+ flat = obj.flatten() if hasattr(obj, 'ndim') and obj.ndim > 1 else obj
301
+ arr = pa.array(flat)
281
302
  table = pa.Table.from_arrays([arr], names=['value'])
282
303
  sink = pa.BufferOutputStream()
283
304
  with pa.ipc.new_stream(sink, table.schema) as writer:
@@ -289,7 +310,8 @@ def serialize_ndarray(obj):
289
310
  'codecVersion': CODEC_VERSION,
290
311
  'encoding': 'arrow',
291
312
  'b64': b64,
292
- 'shape': getattr(obj, 'shape', None),
313
+ 'shape': original_shape,
314
+ 'dtype': str(obj.dtype) if hasattr(obj, 'dtype') else None,
293
315
  }
294
316
  except Exception as exc:
295
317
  if FALLBACK_JSON:
@@ -779,8 +801,13 @@ def encode_response(out):
779
801
  Serialize the response and enforce size limits.
780
802
 
781
803
  Why: keep payload size checks outside the main loop for clarity and lint compliance.
804
+ Uses SafeCodec to reject NaN/Infinity and handle edge cases like numpy scalars.
782
805
  """
783
- payload = json.dumps(out)
806
+ try:
807
+ payload = _response_codec.encode(out)
808
+ except CodecError as exc:
809
+ # Convert CodecError to ValueError for consistent error handling
810
+ raise ValueError(str(exc)) from exc
784
811
  payload_bytes = len(payload.encode('utf-8'))
785
812
  if CODEC_MAX_BYTES is not None and payload_bytes > CODEC_MAX_BYTES:
786
813
  raise PayloadTooLargeError(payload_bytes, CODEC_MAX_BYTES)
@@ -0,0 +1,344 @@
1
+ """
2
+ Safe JSON codec with explicit edge case handling.
3
+
4
+ Provides bidirectional validation and serialization for the JS<->Python bridge.
5
+ This module handles special Python types and enforces payload size limits to
6
+ ensure predictable behavior at the language boundary.
7
+ """
8
+
9
+ import base64
10
+ import json
11
+ import math
12
+ from datetime import date, datetime, time, timedelta
13
+ from decimal import Decimal
14
+ from pathlib import Path, PurePath
15
+ from typing import Any, Optional, Union
16
+ from uuid import UUID
17
+
18
+
19
+ class CodecError(Exception):
20
+ """Raised when encoding/decoding fails."""
21
+
22
+ pass
23
+
24
+
25
+ def _is_nan_or_inf(value: Any) -> bool:
26
+ """
27
+ Check if a numeric value is NaN or Infinity.
28
+
29
+ Args:
30
+ value: The value to check.
31
+
32
+ Returns:
33
+ True if the value is NaN, positive infinity, or negative infinity.
34
+ """
35
+ if not isinstance(value, (int, float)):
36
+ return False
37
+ try:
38
+ return math.isnan(value) or math.isinf(value)
39
+ except (TypeError, ValueError):
40
+ return False
41
+
42
+
43
+ def _is_numpy_scalar(obj: Any) -> bool:
44
+ """
45
+ Detect numpy scalar types when NumPy is installed.
46
+
47
+ Why: numpy scalars have an `.item()` method to extract Python primitives,
48
+ which is needed for JSON serialization.
49
+ """
50
+ try:
51
+ import numpy as np
52
+ except ImportError:
53
+ return False
54
+ return isinstance(obj, (np.generic, np.ndarray)) and obj.ndim == 0
55
+
56
+
57
+ def _is_pandas_scalar(obj: Any) -> bool:
58
+ """
59
+ Detect pandas scalar types (Timestamp, Timedelta, etc.).
60
+
61
+ Why: pandas wraps numpy scalars with additional metadata; extracting the
62
+ underlying value ensures clean JSON serialization.
63
+ """
64
+ try:
65
+ import pandas as pd
66
+ except ImportError:
67
+ return False
68
+ # Check for pandas Timestamp, Timedelta, NaT, etc.
69
+ return isinstance(
70
+ obj,
71
+ (
72
+ pd.Timestamp,
73
+ pd.Timedelta,
74
+ type(pd.NaT),
75
+ ),
76
+ )
77
+
78
+
79
+ def _has_pydantic_model_dump(obj: Any) -> bool:
80
+ """
81
+ Check if object is a Pydantic v2 model with model_dump method.
82
+
83
+ Why: Pydantic models should be serialized via their built-in mechanism
84
+ to respect field aliases and serialization modes.
85
+ """
86
+ model_dump = getattr(obj, 'model_dump', None)
87
+ return callable(model_dump)
88
+
89
+
90
+ class SafeCodec:
91
+ """
92
+ Safe JSON codec with explicit edge case handling.
93
+
94
+ This codec provides:
95
+ - Rejection of NaN/Infinity by default (configurable)
96
+ - Payload size limits to prevent memory exhaustion
97
+ - Automatic handling of common Python types (datetime, Decimal, UUID, etc.)
98
+ - Support for numpy/pandas scalars
99
+ - Pydantic model serialization
100
+
101
+ Args:
102
+ allow_nan: If False (default), reject NaN/Infinity values.
103
+ max_payload_bytes: Maximum payload size in bytes (default 10MB).
104
+
105
+ Example:
106
+ >>> codec = SafeCodec()
107
+ >>> codec.encode({"key": "value"})
108
+ '{"key": "value"}'
109
+ >>> codec.decode('{"key": "value"}')
110
+ {'key': 'value'}
111
+ """
112
+
113
+ def __init__(
114
+ self,
115
+ allow_nan: bool = False,
116
+ max_payload_bytes: int = 10 * 1024 * 1024,
117
+ ) -> None:
118
+ """
119
+ Initialize the codec with configuration.
120
+
121
+ Args:
122
+ allow_nan: If False (default), reject NaN/Infinity values.
123
+ max_payload_bytes: Maximum payload size in bytes (default 10MB).
124
+ """
125
+ self.allow_nan = allow_nan
126
+ self.max_payload_bytes = max_payload_bytes
127
+
128
+ def encode(self, value: Any) -> str:
129
+ """
130
+ Encode a Python value to a JSON string.
131
+
132
+ Args:
133
+ value: The Python value to encode.
134
+
135
+ Returns:
136
+ A JSON string representation of the value.
137
+
138
+ Raises:
139
+ CodecError: If encoding fails due to:
140
+ - NaN/Infinity values when allow_nan is False
141
+ - Payload exceeds max_payload_bytes
142
+ - Value contains non-serializable types
143
+ """
144
+ try:
145
+ result = json.dumps(
146
+ value,
147
+ default=self._default_encoder,
148
+ allow_nan=self.allow_nan,
149
+ )
150
+ except ValueError as exc:
151
+ # json.dumps raises ValueError for NaN/Infinity when allow_nan=False
152
+ error_msg = str(exc).lower()
153
+ if 'nan' in error_msg or 'infinity' in error_msg or 'inf' in error_msg:
154
+ raise CodecError(
155
+ 'Cannot serialize NaN - NaN/Infinity not allowed in JSON'
156
+ ) from exc
157
+ raise CodecError(f'JSON encoding failed: {exc}') from exc
158
+ except TypeError as exc:
159
+ raise CodecError(f'JSON encoding failed: {exc}') from exc
160
+
161
+ # Check payload size
162
+ payload_bytes = len(result.encode('utf-8'))
163
+ if payload_bytes > self.max_payload_bytes:
164
+ raise CodecError(f'Payload exceeds {self.max_payload_bytes} bytes')
165
+
166
+ return result
167
+
168
+ def decode(self, payload: str) -> Any:
169
+ """
170
+ Decode a JSON string to a Python value.
171
+
172
+ Args:
173
+ payload: The JSON string to decode.
174
+
175
+ Returns:
176
+ The decoded Python value.
177
+
178
+ Raises:
179
+ CodecError: If decoding fails due to:
180
+ - Payload exceeds max_payload_bytes
181
+ - Invalid JSON syntax
182
+ """
183
+ # Check payload size first
184
+ payload_bytes = len(payload.encode('utf-8'))
185
+ if payload_bytes > self.max_payload_bytes:
186
+ raise CodecError(f'Payload exceeds {self.max_payload_bytes} bytes')
187
+
188
+ try:
189
+ return json.loads(payload)
190
+ except json.JSONDecodeError as exc:
191
+ raise CodecError(f'JSON decoding failed: {exc}') from exc
192
+
193
+ def _default_encoder(self, obj: Any) -> Any:
194
+ """
195
+ Handle special Python types during JSON encoding.
196
+
197
+ This method is called by json.dumps for objects that are not natively
198
+ JSON serializable.
199
+
200
+ Args:
201
+ obj: The object to encode.
202
+
203
+ Returns:
204
+ A JSON-serializable representation of the object.
205
+
206
+ Raises:
207
+ TypeError: If the object cannot be serialized.
208
+ CodecError: If the object contains NaN/Infinity when not allowed.
209
+ """
210
+ # Handle numpy/pandas scalars first (they need .item() extraction)
211
+ if _is_numpy_scalar(obj):
212
+ extracted = obj.item()
213
+ # Check for NaN/Infinity in extracted value
214
+ if not self.allow_nan and _is_nan_or_inf(extracted):
215
+ raise CodecError(
216
+ 'Cannot serialize NaN - NaN/Infinity not allowed in JSON'
217
+ )
218
+ return extracted
219
+
220
+ if _is_pandas_scalar(obj):
221
+ try:
222
+ import pandas as pd
223
+ except ImportError:
224
+ pass
225
+ else:
226
+ # Handle NaT (Not a Time)
227
+ if obj is pd.NaT or (hasattr(pd, 'isna') and pd.isna(obj)):
228
+ return None
229
+ # Pandas Timestamp -> ISO string
230
+ if isinstance(obj, pd.Timestamp):
231
+ return obj.isoformat()
232
+ # Pandas Timedelta -> total seconds
233
+ if isinstance(obj, pd.Timedelta):
234
+ return obj.total_seconds()
235
+
236
+ # datetime types
237
+ if isinstance(obj, datetime):
238
+ return obj.isoformat()
239
+
240
+ if isinstance(obj, date):
241
+ return obj.isoformat()
242
+
243
+ if isinstance(obj, time):
244
+ return obj.isoformat()
245
+
246
+ # timedelta -> total seconds (consistent with python_bridge.py)
247
+ if isinstance(obj, timedelta):
248
+ return obj.total_seconds()
249
+
250
+ # Decimal -> string (preserves precision)
251
+ if isinstance(obj, Decimal):
252
+ return str(obj)
253
+
254
+ # UUID -> string
255
+ if isinstance(obj, UUID):
256
+ return str(obj)
257
+
258
+ # Path -> string
259
+ if isinstance(obj, (Path, PurePath)):
260
+ return str(obj)
261
+
262
+ # bytes/bytearray -> base64 with type marker
263
+ if isinstance(obj, (bytes, bytearray)):
264
+ return {
265
+ '__type__': 'bytes',
266
+ 'encoding': 'base64',
267
+ 'data': base64.b64encode(obj).decode('ascii'),
268
+ }
269
+
270
+ # Pydantic models
271
+ if _has_pydantic_model_dump(obj):
272
+ try:
273
+ return obj.model_dump(by_alias=True, mode='json')
274
+ except TypeError:
275
+ # Older Pydantic versions may not support mode='json'
276
+ return obj.model_dump(by_alias=True)
277
+
278
+ # Sets -> lists (common conversion)
279
+ if isinstance(obj, (set, frozenset)):
280
+ return list(obj)
281
+
282
+ # Complex numbers (rejected by default as they contain floats)
283
+ if isinstance(obj, complex):
284
+ raise TypeError(
285
+ f'Object of type {type(obj).__name__} is not JSON serializable'
286
+ )
287
+
288
+ # Fallback: raise TypeError with clear message
289
+ raise TypeError(
290
+ f'Object of type {type(obj).__name__} is not JSON serializable'
291
+ )
292
+
293
+
294
+ # Module-level convenience instance with default settings
295
+ _default_codec: Optional[SafeCodec] = None
296
+
297
+
298
+ def get_default_codec() -> SafeCodec:
299
+ """
300
+ Get or create the default SafeCodec instance.
301
+
302
+ Returns:
303
+ The default SafeCodec instance with standard settings.
304
+ """
305
+ global _default_codec
306
+ if _default_codec is None:
307
+ _default_codec = SafeCodec()
308
+ return _default_codec
309
+
310
+
311
+ def encode(value: Any, *, allow_nan: bool = False) -> str:
312
+ """
313
+ Convenience function to encode a value using default settings.
314
+
315
+ Args:
316
+ value: The Python value to encode.
317
+ allow_nan: If True, allow NaN/Infinity values.
318
+
319
+ Returns:
320
+ A JSON string representation of the value.
321
+
322
+ Raises:
323
+ CodecError: If encoding fails.
324
+ """
325
+ if allow_nan:
326
+ codec = SafeCodec(allow_nan=True)
327
+ return codec.encode(value)
328
+ return get_default_codec().encode(value)
329
+
330
+
331
+ def decode(payload: str) -> Any:
332
+ """
333
+ Convenience function to decode a JSON string using default settings.
334
+
335
+ Args:
336
+ payload: The JSON string to decode.
337
+
338
+ Returns:
339
+ The decoded Python value.
340
+
341
+ Raises:
342
+ CodecError: If decoding fails.
343
+ """
344
+ return get_default_codec().decode(payload)
package/src/index.ts CHANGED
@@ -9,7 +9,50 @@ import { tywrap } from './tywrap.js';
9
9
 
10
10
  export type { TywrapConfig } from './config/index.js';
11
11
  export { defineConfig, resolveConfig } from './config/index.js';
12
+ // BoundedContext - unified abstraction for cross-boundary concerns
13
+ export { BoundedContext, type ContextState, type ExecuteOptions } from './runtime/bounded-context.js';
14
+ // BridgeProtocol - unified BoundedContext + SafeCodec + Transport
15
+ export { BridgeProtocol, type BridgeProtocolOptions } from './runtime/bridge-protocol.js';
16
+ // SafeCodec - validation and serialization for JS<->Python boundary
17
+ export { SafeCodec, type CodecOptions } from './runtime/safe-codec.js';
18
+ // Transport - abstract I/O channel interface
19
+ export type { Transport, TransportOptions, ProtocolMessage, ProtocolResponse } from './runtime/transport.js';
20
+ export { PROTOCOL_ID, isTransport, isProtocolMessage, isProtocolResponse } from './runtime/transport.js';
21
+ // Transport implementations
22
+ export { ProcessIO, type ProcessIOOptions } from './runtime/process-io.js';
23
+ export { HttpIO, type HttpIOOptions } from './runtime/http-io.js';
24
+ export { PyodideIO, type PyodideIOOptions } from './runtime/pyodide-io.js';
25
+ // WorkerPool - concurrent transport management
26
+ export { WorkerPool, type WorkerPoolOptions, type PooledWorker } from './runtime/worker-pool.js';
27
+ // PooledTransport - Transport adapter that wraps WorkerPool
28
+ export { PooledTransport, type PooledTransportOptions } from './runtime/pooled-transport.js';
29
+ export type { Disposable } from './runtime/disposable.js';
30
+ export { isDisposable, safeDispose, disposeAll } from './runtime/disposable.js';
31
+ export {
32
+ ValidationError,
33
+ isFiniteNumber,
34
+ isPositiveNumber,
35
+ isNonNegativeNumber,
36
+ isNonEmptyString,
37
+ isPlainObject,
38
+ assertFiniteNumber,
39
+ assertPositive,
40
+ assertNonNegative,
41
+ assertString,
42
+ assertNonEmptyString,
43
+ assertArray,
44
+ assertObject,
45
+ containsSpecialFloat,
46
+ assertNoSpecialFloats,
47
+ sanitizeForFilename,
48
+ containsPathTraversal,
49
+ } from './runtime/validators.js';
50
+
51
+ /**
52
+ * @deprecated Use BoundedContext instead. RuntimeBridge will be removed in the next major version.
53
+ */
12
54
  export { RuntimeBridge } from './runtime/base.js';
55
+
13
56
  export {
14
57
  BridgeError,
15
58
  BridgeProtocolError,
@@ -19,10 +62,10 @@ export {
19
62
  } from './runtime/errors.js';
20
63
  export { getRuntimeBridge, setRuntimeBridge, clearRuntimeBridge } from './runtime/index.js';
21
64
 
22
- // Runtime-specific exports
23
- export { NodeBridge } from './runtime/node.js';
24
- export { PyodideBridge } from './runtime/pyodide.js';
25
- export { HttpBridge } from './runtime/http.js';
65
+ // Runtime-specific exports (Bridges using new BridgeProtocol architecture)
66
+ export { NodeBridge, type NodeBridgeOptions } from './runtime/node.js';
67
+ export { PyodideBridge, type PyodideBridgeOptions } from './runtime/pyodide.js';
68
+ export { HttpBridge, type HttpBridgeOptions } from './runtime/http.js';
26
69
 
27
70
  // Core types
28
71
  export type {
@@ -75,7 +118,7 @@ export {
75
118
  } from './utils/codec.js';
76
119
 
77
120
  // Version info
78
- export const VERSION = '0.1.2';
121
+ export const VERSION = '0.2.1';
79
122
 
80
123
  /**
81
124
  * Quick setup function for getting started
@@ -1,32 +1,24 @@
1
1
  /**
2
2
  * Base runtime bridge
3
+ *
4
+ * @deprecated Use BoundedContext instead. RuntimeBridge will be removed in the next major version.
3
5
  */
4
6
 
5
- import type { RuntimeExecution } from '../types/index.js';
7
+ import { BoundedContext } from './bounded-context.js';
6
8
 
7
- export abstract class RuntimeBridge implements RuntimeExecution {
8
- abstract call<T = unknown>(
9
- module: string,
10
- functionName: string,
11
- args: unknown[],
12
- kwargs?: Record<string, unknown>
13
- ): Promise<T>;
14
-
15
- abstract instantiate<T = unknown>(
16
- module: string,
17
- className: string,
18
- args: unknown[],
19
- kwargs?: Record<string, unknown>
20
- ): Promise<T>;
21
-
22
- abstract callMethod<T = unknown>(
23
- handle: string,
24
- methodName: string,
25
- args: unknown[],
26
- kwargs?: Record<string, unknown>
27
- ): Promise<T>;
28
-
29
- abstract disposeInstance(handle: string): Promise<void>;
9
+ /**
10
+ * @deprecated Use BoundedContext instead. RuntimeBridge will be removed in the next major version.
11
+ *
12
+ * All bridges now extend BoundedContext which provides:
13
+ * - Lifecycle management (init/dispose state machine)
14
+ * - Validation helpers
15
+ * - Error classification
16
+ * - Bounded execution (timeout, retry)
17
+ * - Resource ownership tracking
18
+ *
19
+ * @see BoundedContext
20
+ */
21
+ export abstract class RuntimeBridge extends BoundedContext {}
30
22
 
31
- abstract dispose(): Promise<void>;
32
- }
23
+ // Re-export BoundedContext for backwards compatibility
24
+ export { BoundedContext };