tywrap 0.5.1 → 0.6.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 (178) hide show
  1. package/dist/cli.js +8 -6
  2. package/dist/cli.js.map +1 -1
  3. package/dist/config/index.d.ts.map +1 -1
  4. package/dist/config/index.js +236 -147
  5. package/dist/config/index.js.map +1 -1
  6. package/dist/core/annotation-parser.d.ts.map +1 -1
  7. package/dist/core/annotation-parser.js +112 -97
  8. package/dist/core/annotation-parser.js.map +1 -1
  9. package/dist/core/discovery.d.ts +17 -0
  10. package/dist/core/discovery.d.ts.map +1 -1
  11. package/dist/core/discovery.js +85 -49
  12. package/dist/core/discovery.js.map +1 -1
  13. package/dist/core/emit-call.d.ts +58 -0
  14. package/dist/core/emit-call.d.ts.map +1 -0
  15. package/dist/core/emit-call.js +103 -0
  16. package/dist/core/emit-call.js.map +1 -0
  17. package/dist/core/generator.d.ts +2 -0
  18. package/dist/core/generator.d.ts.map +1 -1
  19. package/dist/core/generator.js +54 -211
  20. package/dist/core/generator.js.map +1 -1
  21. package/dist/core/mapper.d.ts.map +1 -1
  22. package/dist/core/mapper.js +118 -104
  23. package/dist/core/mapper.js.map +1 -1
  24. package/dist/core/validation.d.ts +23 -0
  25. package/dist/core/validation.d.ts.map +1 -1
  26. package/dist/core/validation.js +52 -48
  27. package/dist/core/validation.js.map +1 -1
  28. package/dist/dev.d.ts.map +1 -1
  29. package/dist/dev.js +175 -133
  30. package/dist/dev.js.map +1 -1
  31. package/dist/index.d.ts +6 -25
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +9 -31
  34. package/dist/index.js.map +1 -1
  35. package/dist/runtime/bounded-context.d.ts +2 -2
  36. package/dist/runtime/bounded-context.js +2 -2
  37. package/dist/runtime/{safe-codec.d.ts → bridge-codec.d.ts} +26 -11
  38. package/dist/runtime/bridge-codec.d.ts.map +1 -0
  39. package/dist/runtime/{safe-codec.js → bridge-codec.js} +69 -72
  40. package/dist/runtime/bridge-codec.js.map +1 -0
  41. package/dist/runtime/{http-io.d.ts → http-transport.d.ts} +6 -6
  42. package/dist/runtime/http-transport.d.ts.map +1 -0
  43. package/dist/runtime/{http-io.js → http-transport.js} +3 -3
  44. package/dist/runtime/http-transport.js.map +1 -0
  45. package/dist/runtime/http.d.ts +3 -3
  46. package/dist/runtime/http.d.ts.map +1 -1
  47. package/dist/runtime/http.js +4 -4
  48. package/dist/runtime/http.js.map +1 -1
  49. package/dist/runtime/index.d.ts +9 -1
  50. package/dist/runtime/index.d.ts.map +1 -1
  51. package/dist/runtime/index.js +9 -1
  52. package/dist/runtime/index.js.map +1 -1
  53. package/dist/runtime/node.d.ts +6 -6
  54. package/dist/runtime/node.d.ts.map +1 -1
  55. package/dist/runtime/node.js +7 -7
  56. package/dist/runtime/node.js.map +1 -1
  57. package/dist/runtime/pooled-transport.d.ts +11 -11
  58. package/dist/runtime/pooled-transport.d.ts.map +1 -1
  59. package/dist/runtime/pooled-transport.js +10 -10
  60. package/dist/runtime/pooled-transport.js.map +1 -1
  61. package/dist/runtime/pyodide-bootstrap-core.generated.d.ts.map +1 -1
  62. package/dist/runtime/pyodide-bootstrap-core.generated.js +1 -1
  63. package/dist/runtime/pyodide-bootstrap-core.generated.js.map +1 -1
  64. package/dist/runtime/{pyodide-io.d.ts → pyodide-transport.d.ts} +8 -8
  65. package/dist/runtime/pyodide-transport.d.ts.map +1 -0
  66. package/dist/runtime/{pyodide-io.js → pyodide-transport.js} +5 -5
  67. package/dist/runtime/pyodide-transport.js.map +1 -0
  68. package/dist/runtime/pyodide.d.ts +3 -3
  69. package/dist/runtime/pyodide.d.ts.map +1 -1
  70. package/dist/runtime/pyodide.js +5 -5
  71. package/dist/runtime/pyodide.js.map +1 -1
  72. package/dist/runtime/rpc-client.d.ts +23 -5
  73. package/dist/runtime/rpc-client.d.ts.map +1 -1
  74. package/dist/runtime/rpc-client.js +36 -31
  75. package/dist/runtime/rpc-client.js.map +1 -1
  76. package/dist/runtime/{process-io.d.ts → subprocess-transport.d.ts} +25 -9
  77. package/dist/runtime/subprocess-transport.d.ts.map +1 -0
  78. package/dist/runtime/{process-io.js → subprocess-transport.js} +60 -41
  79. package/dist/runtime/subprocess-transport.js.map +1 -0
  80. package/dist/runtime/{worker-pool.d.ts → transport-pool.d.ts} +15 -15
  81. package/dist/runtime/transport-pool.d.ts.map +1 -0
  82. package/dist/runtime/{worker-pool.js → transport-pool.js} +6 -6
  83. package/dist/runtime/transport-pool.js.map +1 -0
  84. package/dist/runtime/transport.d.ts +14 -8
  85. package/dist/runtime/transport.d.ts.map +1 -1
  86. package/dist/runtime/transport.js +10 -4
  87. package/dist/runtime/transport.js.map +1 -1
  88. package/dist/types/index.d.ts +10 -1
  89. package/dist/types/index.d.ts.map +1 -1
  90. package/dist/tywrap.d.ts +0 -5
  91. package/dist/tywrap.d.ts.map +1 -1
  92. package/dist/tywrap.js +0 -20
  93. package/dist/tywrap.js.map +1 -1
  94. package/dist/utils/cache.d.ts +13 -2
  95. package/dist/utils/cache.d.ts.map +1 -1
  96. package/dist/utils/cache.js +52 -60
  97. package/dist/utils/cache.js.map +1 -1
  98. package/dist/utils/codec.d.ts +1 -1
  99. package/dist/utils/codec.d.ts.map +1 -1
  100. package/dist/utils/codec.js +181 -182
  101. package/dist/utils/codec.js.map +1 -1
  102. package/dist/utils/python.d.ts.map +1 -1
  103. package/dist/utils/python.js +28 -17
  104. package/dist/utils/python.js.map +1 -1
  105. package/dist/utils/runtime.d.ts.map +1 -1
  106. package/dist/utils/runtime.js +22 -16
  107. package/dist/utils/runtime.js.map +1 -1
  108. package/dist/version.d.ts +12 -0
  109. package/dist/version.d.ts.map +1 -0
  110. package/dist/version.js +12 -0
  111. package/dist/version.js.map +1 -0
  112. package/package.json +3 -2
  113. package/runtime/__pycache__/safe_codec.cpython-311.pyc +0 -0
  114. package/runtime/__pycache__/tywrap_bridge_core.cpython-311.pyc +0 -0
  115. package/runtime/python_bridge.py +54 -6
  116. package/runtime/safe_codec.py +8 -8
  117. package/runtime/tywrap_bridge_core.py +159 -23
  118. package/src/cli.ts +8 -12
  119. package/src/config/index.ts +271 -165
  120. package/src/core/annotation-parser.ts +135 -123
  121. package/src/core/discovery.ts +102 -54
  122. package/src/core/emit-call.ts +171 -0
  123. package/src/core/generator.ts +67 -263
  124. package/src/core/mapper.ts +125 -110
  125. package/src/core/validation.ts +84 -48
  126. package/src/dev.ts +237 -153
  127. package/src/index.ts +11 -68
  128. package/src/runtime/bounded-context.ts +2 -2
  129. package/src/runtime/{safe-codec.ts → bridge-codec.ts} +83 -95
  130. package/src/runtime/{http-io.ts → http-transport.ts} +5 -5
  131. package/src/runtime/http.ts +5 -5
  132. package/src/runtime/index.ts +23 -1
  133. package/src/runtime/node.ts +15 -15
  134. package/src/runtime/pooled-transport.ts +15 -15
  135. package/src/runtime/pyodide-bootstrap-core.generated.ts +1 -1
  136. package/src/runtime/{pyodide-io.ts → pyodide-transport.ts} +7 -7
  137. package/src/runtime/pyodide.ts +6 -6
  138. package/src/runtime/rpc-client.ts +51 -40
  139. package/src/runtime/{process-io.ts → subprocess-transport.ts} +77 -47
  140. package/src/runtime/{worker-pool.ts → transport-pool.ts} +26 -26
  141. package/src/runtime/transport.ts +15 -8
  142. package/src/types/index.ts +10 -1
  143. package/src/tywrap.ts +0 -24
  144. package/src/utils/cache.ts +61 -63
  145. package/src/utils/codec.ts +217 -196
  146. package/src/utils/python.ts +33 -20
  147. package/src/utils/runtime.ts +24 -13
  148. package/src/version.ts +12 -0
  149. package/dist/runtime/http-io.d.ts.map +0 -1
  150. package/dist/runtime/http-io.js.map +0 -1
  151. package/dist/runtime/optimized-node.d.ts +0 -23
  152. package/dist/runtime/optimized-node.d.ts.map +0 -1
  153. package/dist/runtime/optimized-node.js +0 -23
  154. package/dist/runtime/optimized-node.js.map +0 -1
  155. package/dist/runtime/process-io.d.ts.map +0 -1
  156. package/dist/runtime/process-io.js.map +0 -1
  157. package/dist/runtime/protocol.d.ts +0 -3
  158. package/dist/runtime/protocol.d.ts.map +0 -1
  159. package/dist/runtime/protocol.js +0 -3
  160. package/dist/runtime/protocol.js.map +0 -1
  161. package/dist/runtime/pyodide-io.d.ts.map +0 -1
  162. package/dist/runtime/pyodide-io.js.map +0 -1
  163. package/dist/runtime/safe-codec.d.ts.map +0 -1
  164. package/dist/runtime/safe-codec.js.map +0 -1
  165. package/dist/runtime/worker-pool.d.ts.map +0 -1
  166. package/dist/runtime/worker-pool.js.map +0 -1
  167. package/dist/utils/bundle-optimizer.d.ts +0 -182
  168. package/dist/utils/bundle-optimizer.d.ts.map +0 -1
  169. package/dist/utils/bundle-optimizer.js +0 -680
  170. package/dist/utils/bundle-optimizer.js.map +0 -1
  171. package/dist/utils/memory-profiler.d.ts +0 -123
  172. package/dist/utils/memory-profiler.d.ts.map +0 -1
  173. package/dist/utils/memory-profiler.js +0 -426
  174. package/dist/utils/memory-profiler.js.map +0 -1
  175. package/src/runtime/optimized-node.ts +0 -25
  176. package/src/runtime/protocol.ts +0 -2
  177. package/src/utils/bundle-optimizer.ts +0 -876
  178. package/src/utils/memory-profiler.ts +0 -582
@@ -1,5 +1,5 @@
1
1
  /**
2
- * SafeCodec - Unified validation and serialization for JS<->Python boundary crossing.
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 SafeCodec behavior.
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 ProtocolEnvelope {
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 protocol response envelope.
171
- * Envelopes must include a numeric id.
170
+ * Type guard for the RPC response wrapper ({ id, result | error }).
171
+ * Responses must include a numeric id.
172
172
  */
173
- function isProtocolEnvelope(value: unknown): value is ProtocolEnvelope {
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 a protocol envelope (has 'id' field).
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 protocol envelopes (responses with 'id' field)
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
- // SAFE CODEC CLASS
296
+ // BRIDGE CODEC CLASS
297
297
  // ═══════════════════════════════════════════════════════════════════════════
298
298
 
299
299
  /**
300
- * SafeCodec provides unified validation and serialization for JS<->Python
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 SafeCodec({ rejectSpecialFloats: true });
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 SafeCodec {
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 (!SafeCodec.base64Pattern.test(b64)) {
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 SafeCodec.encodeRequest; also allowed in responses)
372
- * - { "__type__": "bytes", "encoding": "base64", "data": "..." } (Python SafeCodec default encoder)
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,74 @@ export class SafeCodec {
410
410
  return bridgeError;
411
411
  }
412
412
 
413
- private extractResultFromResponseEnvelope(parsed: unknown): unknown {
414
- if (isProtocolEnvelope(parsed)) {
415
- const envelope = parsed;
416
- const hasResult = hasOwnKey(envelope, 'result');
417
- const hasError = hasOwnKey(envelope, 'error');
413
+ /**
414
+ * Shared prelude for both decode paths: size guard, JSON parse (with bytes
415
+ * revival), protocol-version validation, and RPC-envelope unwrapping.
416
+ *
417
+ * Behavior-preserving extraction of the common head of decodeResponse and
418
+ * decodeResponseAsync; the only divergence between the two is what they do
419
+ * with the returned result (sync returns it as-is, async applies Arrow
420
+ * decoding) and so that divergence stays in the callers.
421
+ */
422
+ private parseResponseResult(payload: string): unknown {
423
+ // Check payload size first
424
+ const payloadBytes = new TextEncoder().encode(payload).length;
425
+ if (payloadBytes > this.maxPayloadBytes) {
426
+ throw new BridgeCodecError(
427
+ `Response payload size ${payloadBytes} bytes exceeds maximum ${this.maxPayloadBytes} bytes`,
428
+ { codecPhase: 'decode', valueType: 'payload' }
429
+ );
430
+ }
431
+
432
+ // Parse JSON
433
+ let parsed: unknown;
434
+ try {
435
+ parsed = JSON.parse(payload, this.reviveValueBound);
436
+ } catch (err) {
437
+ if (err instanceof BridgeCodecError || err instanceof BridgeProtocolError) {
438
+ throw err;
439
+ }
440
+ const errorMessage = err instanceof Error ? err.message : String(err);
441
+ throw new BridgeCodecError(
442
+ `JSON parse failed: ${errorMessage}. Payload snippet: ${summarizePayloadForError(payload)}`,
443
+ { codecPhase: 'decode', valueType: 'json' }
444
+ );
445
+ }
446
+
447
+ // Validate protocol version (if present)
448
+ validateProtocolVersion(parsed);
449
+
450
+ return this.extractResultFromRpcResponse(parsed);
451
+ }
452
+
453
+ /**
454
+ * Post-decode guard: reject non-finite numbers (NaN/Infinity) when enabled.
455
+ * Shared by both decode paths.
456
+ */
457
+ private assertNoSpecialFloats(value: unknown): void {
458
+ if (this.rejectSpecialFloats && containsSpecialFloat(value)) {
459
+ const floatPath = findSpecialFloatPath(value);
460
+ throw new BridgeCodecError(
461
+ `Response contains non-finite number (NaN or Infinity) at ${floatPath}`,
462
+ { codecPhase: 'decode', valueType: 'number' }
463
+ );
464
+ }
465
+ }
466
+
467
+ private extractResultFromRpcResponse(parsed: unknown): unknown {
468
+ if (isRpcResponse(parsed)) {
469
+ const response = parsed;
470
+ const hasResult = hasOwnKey(response, 'result');
471
+ const hasError = hasOwnKey(response, 'error');
418
472
 
419
473
  if (hasResult && hasError) {
420
474
  throw new BridgeProtocolError('Protocol response cannot include both "result" and "error"');
421
475
  }
422
476
 
423
477
  if (hasError) {
424
- const normalizedError = normalizeErrorPayload(envelope.error);
478
+ const normalizedError = normalizeErrorPayload(response.error);
425
479
  if (!normalizedError) {
426
- const details = describeInvalidErrorPayload(envelope.error);
480
+ const details = describeInvalidErrorPayload(response.error);
427
481
  throw new BridgeProtocolError(`Invalid response "error" payload: ${details}`);
428
482
  }
429
483
  throw this.toBridgeExecutionError(normalizedError);
@@ -433,7 +487,7 @@ export class SafeCodec {
433
487
  throw new BridgeProtocolError('Protocol response missing "result" or "error" field');
434
488
  }
435
489
 
436
- return envelope.result;
490
+ return response.result;
437
491
  }
438
492
 
439
493
  if (isPythonErrorResponse(parsed)) {
@@ -522,47 +576,14 @@ export class SafeCodec {
522
576
  * @param payload - The JSON string received from Python
523
577
  * @returns Decoded and validated result
524
578
  * @throws BridgeCodecError if payload is invalid or decoding fails
525
- * @throws BridgeProtocolError if protocol envelope is invalid
579
+ * @throws BridgeProtocolError if the RPC response is invalid
526
580
  * @throws BridgeExecutionError if response contains a Python error
527
581
  */
528
582
  decodeResponse<T>(payload: string): T {
529
- // Check payload size first
530
- const payloadBytes = new TextEncoder().encode(payload).length;
531
- if (payloadBytes > this.maxPayloadBytes) {
532
- throw new BridgeCodecError(
533
- `Response payload size ${payloadBytes} bytes exceeds maximum ${this.maxPayloadBytes} bytes`,
534
- { codecPhase: 'decode', valueType: 'payload' }
535
- );
536
- }
537
-
538
- // Parse JSON
539
- let parsed: unknown;
540
- try {
541
- parsed = JSON.parse(payload, this.reviveValueBound);
542
- } catch (err) {
543
- if (err instanceof BridgeCodecError || err instanceof BridgeProtocolError) {
544
- throw err;
545
- }
546
- const errorMessage = err instanceof Error ? err.message : String(err);
547
- throw new BridgeCodecError(
548
- `JSON parse failed: ${errorMessage}. Payload snippet: ${summarizePayloadForError(payload)}`,
549
- { codecPhase: 'decode', valueType: 'json' }
550
- );
551
- }
552
-
553
- // Validate protocol version (if present)
554
- validateProtocolVersion(parsed);
555
-
556
- const result = this.extractResultFromResponseEnvelope(parsed);
583
+ const result = this.parseResponseResult(payload);
557
584
 
558
585
  // Post-decode validation for special floats if enabled
559
- if (this.rejectSpecialFloats && containsSpecialFloat(result)) {
560
- const floatPath = findSpecialFloatPath(result);
561
- throw new BridgeCodecError(
562
- `Response contains non-finite number (NaN or Infinity) at ${floatPath}`,
563
- { codecPhase: 'decode', valueType: 'number' }
564
- );
565
- }
586
+ this.assertNoSpecialFloats(result);
566
587
 
567
588
  return result as T;
568
589
  }
@@ -574,38 +595,11 @@ export class SafeCodec {
574
595
  * @param payload - The JSON string received from Python
575
596
  * @returns Decoded and validated result with Arrow decoding applied
576
597
  * @throws BridgeCodecError if payload is invalid or decoding fails
577
- * @throws BridgeProtocolError if protocol envelope is invalid
598
+ * @throws BridgeProtocolError if the RPC response is invalid
578
599
  * @throws BridgeExecutionError if response contains a Python error
579
600
  */
580
601
  async decodeResponseAsync<T>(payload: string): Promise<T> {
581
- // Check payload size first
582
- const payloadBytes = new TextEncoder().encode(payload).length;
583
- if (payloadBytes > this.maxPayloadBytes) {
584
- throw new BridgeCodecError(
585
- `Response payload size ${payloadBytes} bytes exceeds maximum ${this.maxPayloadBytes} bytes`,
586
- { codecPhase: 'decode', valueType: 'payload' }
587
- );
588
- }
589
-
590
- // Parse JSON
591
- let parsed: unknown;
592
- try {
593
- parsed = JSON.parse(payload, this.reviveValueBound);
594
- } catch (err) {
595
- if (err instanceof BridgeCodecError || err instanceof BridgeProtocolError) {
596
- throw err;
597
- }
598
- const errorMessage = err instanceof Error ? err.message : String(err);
599
- throw new BridgeCodecError(
600
- `JSON parse failed: ${errorMessage}. Payload snippet: ${summarizePayloadForError(payload)}`,
601
- { codecPhase: 'decode', valueType: 'json' }
602
- );
603
- }
604
-
605
- // Validate protocol version (if present)
606
- validateProtocolVersion(parsed);
607
-
608
- const result = this.extractResultFromResponseEnvelope(parsed);
602
+ const result = this.parseResponseResult(payload);
609
603
 
610
604
  // Apply Arrow decoding to the result
611
605
  let decoded: unknown;
@@ -621,13 +615,7 @@ export class SafeCodec {
621
615
 
622
616
  // Post-decode validation for special floats if enabled
623
617
  // Note: Arrow decoders can introduce NaN/Infinity from binary representations.
624
- if (this.rejectSpecialFloats && containsSpecialFloat(decoded)) {
625
- const floatPath = findSpecialFloatPath(decoded);
626
- throw new BridgeCodecError(
627
- `Response contains non-finite number (NaN or Infinity) at ${floatPath}`,
628
- { codecPhase: 'decode', valueType: 'number' }
629
- );
630
- }
618
+ this.assertNoSpecialFloats(decoded);
631
619
 
632
620
  return decoded as T;
633
621
  }
@@ -20,9 +20,9 @@ import type { Transport } from './transport.js';
20
20
  // =============================================================================
21
21
 
22
22
  /**
23
- * Configuration options for HttpIO transport.
23
+ * Configuration options for the HttpTransport.
24
24
  */
25
- export interface HttpIOOptions {
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 HttpIO({ baseURL: 'http://localhost:8000' });
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 HttpIO implements Transport {
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: HttpIOOptions) {
72
+ constructor(options: HttpTransportOptions) {
73
73
  // Normalize URL - remove trailing slash
74
74
  this.baseURL = options.baseURL.replace(/\/$/, '');
75
75
  this.headers = {
@@ -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 HttpIO transport
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 { HttpIO } from './http-io.js';
17
- import type { CodecOptions } from './safe-codec.js';
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 SafeCodec validation (NaN/Infinity rejection, key validation)
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 HttpIO({
79
+ const transport = new HttpTransport({
80
80
  baseURL: options.baseURL,
81
81
  headers: options.headers,
82
82
  defaultTimeoutMs: options.timeoutMs,
@@ -1,9 +1,31 @@
1
1
  /**
2
- * Runtime bridge registry for generated wrappers.
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 {
@@ -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 ProcessIO
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 { ProcessIO } from './process-io.js';
25
+ import { SubprocessTransport } from './subprocess-transport.js';
26
26
  import { PooledTransport } from './pooled-transport.js';
27
- import type { CodecOptions } from './safe-codec.js';
28
- import type { PooledWorker } from './worker-pool.js';
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 WorkerPool.
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 ProcessIO.
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 ProcessIO options instead.
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 SafeCodec validation (NaN/Infinity rejection, key validation)
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 ProcessIO
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 ProcessIO workers
363
+ // Create pooled transport with SubprocessTransport workers
364
364
  const transport = new PooledTransport({
365
- createTransport: (): ProcessIO =>
366
- new ProcessIO({
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: PooledWorker) => Promise<void> {
680
- return async (worker: PooledWorker) => {
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 ProcessIO.
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 WorkerPool for multi-process support.
2
+ * PooledTransport - Transport adapter that wraps TransportPool for multi-process support.
3
3
  *
4
- * This transport implements the Transport interface by delegating to a WorkerPool
5
- * of ProcessIO transports. Each send() acquires a worker, sends the message,
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 { WorkerPool, type PooledWorker } from './worker-pool.js';
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: PooledWorker) => Promise<void>;
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: PooledWorker) => Promise<void>;
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 WorkerPool for multi-process message handling.
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 ProcessIO).
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 ProcessIO({
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: PooledWorker) => Promise<void>;
91
- onReplacementWorkerReady?: (worker: PooledWorker) => Promise<void>;
90
+ onWorkerReady?: (worker: TransportLease) => Promise<void>;
91
+ onReplacementWorkerReady?: (worker: TransportLease) => Promise<void>;
92
92
  };
93
- private pool?: WorkerPool;
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 WorkerPool.
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 WorkerPool({
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 WorkerPool, which disposes all workers.
145
+ * Disposes the internal TransportPool, which disposes all workers.
146
146
  */
147
147
  protected async doDispose(): Promise<void> {
148
148
  if (this.pool) {