tywrap 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/README.md +65 -31
  2. package/dist/cli.js +49 -2
  3. package/dist/cli.js.map +1 -1
  4. package/dist/config/index.d.ts +11 -7
  5. package/dist/config/index.d.ts.map +1 -1
  6. package/dist/config/index.js +43 -7
  7. package/dist/config/index.js.map +1 -1
  8. package/dist/core/analyzer.d.ts +1 -0
  9. package/dist/core/analyzer.d.ts.map +1 -1
  10. package/dist/core/analyzer.js +47 -12
  11. package/dist/core/analyzer.js.map +1 -1
  12. package/dist/core/annotation-parser.d.ts +8 -0
  13. package/dist/core/annotation-parser.d.ts.map +1 -0
  14. package/dist/core/annotation-parser.js +409 -0
  15. package/dist/core/annotation-parser.js.map +1 -0
  16. package/dist/core/discovery.d.ts +1 -0
  17. package/dist/core/discovery.d.ts.map +1 -1
  18. package/dist/core/discovery.js +9 -9
  19. package/dist/core/discovery.js.map +1 -1
  20. package/dist/core/generator.d.ts +9 -1
  21. package/dist/core/generator.d.ts.map +1 -1
  22. package/dist/core/generator.js +738 -104
  23. package/dist/core/generator.js.map +1 -1
  24. package/dist/core/mapper.d.ts +9 -7
  25. package/dist/core/mapper.d.ts.map +1 -1
  26. package/dist/core/mapper.js +129 -44
  27. package/dist/core/mapper.js.map +1 -1
  28. package/dist/index.d.ts +23 -7
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +23 -3
  31. package/dist/index.js.map +1 -1
  32. package/dist/runtime/base.d.ts +17 -7
  33. package/dist/runtime/base.d.ts.map +1 -1
  34. package/dist/runtime/base.js +18 -1
  35. package/dist/runtime/base.js.map +1 -1
  36. package/dist/runtime/bounded-context.d.ts +252 -0
  37. package/dist/runtime/bounded-context.d.ts.map +1 -0
  38. package/dist/runtime/bounded-context.js +454 -0
  39. package/dist/runtime/bounded-context.js.map +1 -0
  40. package/dist/runtime/bridge-core.d.ts +1 -0
  41. package/dist/runtime/bridge-core.d.ts.map +1 -1
  42. package/dist/runtime/bridge-core.js +52 -17
  43. package/dist/runtime/bridge-core.js.map +1 -1
  44. package/dist/runtime/bridge-protocol.d.ts +184 -0
  45. package/dist/runtime/bridge-protocol.d.ts.map +1 -0
  46. package/dist/runtime/bridge-protocol.js +344 -0
  47. package/dist/runtime/bridge-protocol.js.map +1 -0
  48. package/dist/runtime/disposable.d.ts +40 -0
  49. package/dist/runtime/disposable.d.ts.map +1 -0
  50. package/dist/runtime/disposable.js +49 -0
  51. package/dist/runtime/disposable.js.map +1 -0
  52. package/dist/runtime/errors.d.ts +10 -0
  53. package/dist/runtime/errors.d.ts.map +1 -1
  54. package/dist/runtime/errors.js +9 -0
  55. package/dist/runtime/errors.js.map +1 -1
  56. package/dist/runtime/http-io.d.ts +91 -0
  57. package/dist/runtime/http-io.d.ts.map +1 -0
  58. package/dist/runtime/http-io.js +195 -0
  59. package/dist/runtime/http-io.js.map +1 -0
  60. package/dist/runtime/http.d.ts +48 -13
  61. package/dist/runtime/http.d.ts.map +1 -1
  62. package/dist/runtime/http.js +60 -73
  63. package/dist/runtime/http.js.map +1 -1
  64. package/dist/runtime/node.d.ts +97 -130
  65. package/dist/runtime/node.d.ts.map +1 -1
  66. package/dist/runtime/node.js +392 -521
  67. package/dist/runtime/node.js.map +1 -1
  68. package/dist/runtime/optimized-node.d.ts +1 -1
  69. package/dist/runtime/optimized-node.d.ts.map +1 -1
  70. package/dist/runtime/optimized-node.js +1 -1
  71. package/dist/runtime/optimized-node.js.map +1 -1
  72. package/dist/runtime/pooled-transport.d.ts +135 -0
  73. package/dist/runtime/pooled-transport.d.ts.map +1 -0
  74. package/dist/runtime/pooled-transport.js +177 -0
  75. package/dist/runtime/pooled-transport.js.map +1 -0
  76. package/dist/runtime/process-io.d.ts +209 -0
  77. package/dist/runtime/process-io.d.ts.map +1 -0
  78. package/dist/runtime/process-io.js +729 -0
  79. package/dist/runtime/process-io.js.map +1 -0
  80. package/dist/runtime/pyodide-io.d.ts +154 -0
  81. package/dist/runtime/pyodide-io.d.ts.map +1 -0
  82. package/dist/runtime/pyodide-io.js +459 -0
  83. package/dist/runtime/pyodide-io.js.map +1 -0
  84. package/dist/runtime/pyodide.d.ts +51 -19
  85. package/dist/runtime/pyodide.d.ts.map +1 -1
  86. package/dist/runtime/pyodide.js +57 -186
  87. package/dist/runtime/pyodide.js.map +1 -1
  88. package/dist/runtime/safe-codec.d.ts +103 -0
  89. package/dist/runtime/safe-codec.d.ts.map +1 -0
  90. package/dist/runtime/safe-codec.js +519 -0
  91. package/dist/runtime/safe-codec.js.map +1 -0
  92. package/dist/runtime/transport.d.ts +191 -0
  93. package/dist/runtime/transport.d.ts.map +1 -0
  94. package/dist/runtime/transport.js +86 -0
  95. package/dist/runtime/transport.js.map +1 -0
  96. package/dist/runtime/validators.d.ts +120 -0
  97. package/dist/runtime/validators.d.ts.map +1 -0
  98. package/dist/runtime/validators.js +229 -0
  99. package/dist/runtime/validators.js.map +1 -0
  100. package/dist/runtime/worker-pool.d.ts +208 -0
  101. package/dist/runtime/worker-pool.d.ts.map +1 -0
  102. package/dist/runtime/worker-pool.js +419 -0
  103. package/dist/runtime/worker-pool.js.map +1 -0
  104. package/dist/types/index.d.ts +51 -1
  105. package/dist/types/index.d.ts.map +1 -1
  106. package/dist/tywrap.d.ts.map +1 -1
  107. package/dist/tywrap.js +227 -328
  108. package/dist/tywrap.js.map +1 -1
  109. package/dist/utils/cache.d.ts.map +1 -1
  110. package/dist/utils/cache.js +53 -5
  111. package/dist/utils/cache.js.map +1 -1
  112. package/dist/utils/codec.d.ts.map +1 -1
  113. package/dist/utils/codec.js +114 -1
  114. package/dist/utils/codec.js.map +1 -1
  115. package/dist/utils/ir-cache.d.ts +28 -0
  116. package/dist/utils/ir-cache.d.ts.map +1 -0
  117. package/dist/utils/ir-cache.js +29 -0
  118. package/dist/utils/ir-cache.js.map +1 -0
  119. package/dist/utils/parallel-processor.d.ts +1 -1
  120. package/dist/utils/parallel-processor.d.ts.map +1 -1
  121. package/dist/utils/parallel-processor.js +111 -40
  122. package/dist/utils/parallel-processor.js.map +1 -1
  123. package/dist/utils/runtime.d.ts +5 -1
  124. package/dist/utils/runtime.d.ts.map +1 -1
  125. package/dist/utils/runtime.js +112 -18
  126. package/dist/utils/runtime.js.map +1 -1
  127. package/package.json +40 -18
  128. package/runtime/python_bridge.py +108 -15
  129. package/runtime/safe_codec.py +344 -0
  130. package/src/cli.ts +61 -2
  131. package/src/config/index.ts +63 -17
  132. package/src/core/analyzer.ts +52 -15
  133. package/src/core/annotation-parser.ts +500 -0
  134. package/src/core/discovery.ts +26 -12
  135. package/src/core/generator.ts +956 -116
  136. package/src/core/mapper.ts +158 -51
  137. package/src/index.ts +78 -6
  138. package/src/runtime/base.ts +18 -26
  139. package/src/runtime/bounded-context.ts +608 -0
  140. package/src/runtime/bridge-core.ts +58 -18
  141. package/src/runtime/bridge-protocol.ts +483 -0
  142. package/src/runtime/disposable.ts +65 -0
  143. package/src/runtime/errors.ts +14 -0
  144. package/src/runtime/http-io.ts +245 -0
  145. package/src/runtime/http.ts +76 -115
  146. package/src/runtime/node.ts +563 -676
  147. package/src/runtime/optimized-node.ts +4 -1
  148. package/src/runtime/pooled-transport.ts +263 -0
  149. package/src/runtime/process-io.ts +930 -0
  150. package/src/runtime/pyodide-io.ts +551 -0
  151. package/src/runtime/pyodide.ts +75 -215
  152. package/src/runtime/safe-codec.ts +651 -0
  153. package/src/runtime/transport.ts +278 -0
  154. package/src/runtime/validators.ts +252 -0
  155. package/src/runtime/worker-pool.ts +559 -0
  156. package/src/types/global.d.ts +11 -1
  157. package/src/types/index.ts +63 -0
  158. package/src/tywrap.ts +279 -358
  159. package/src/utils/cache.ts +59 -5
  160. package/src/utils/codec.ts +122 -1
  161. package/src/utils/ir-cache.ts +51 -0
  162. package/src/utils/parallel-processor.ts +119 -43
  163. package/src/utils/runtime.ts +129 -18
@@ -0,0 +1,483 @@
1
+ /**
2
+ * BridgeProtocol - Unified abstraction for JS<->Python communication.
3
+ *
4
+ * Combines BoundedContext + SafeCodec + Transport into a single base class
5
+ * that handles all cross-boundary concerns:
6
+ * - Lifecycle management (init/dispose state machine)
7
+ * - Request/response encoding with validation
8
+ * - Transport-agnostic message passing
9
+ * - Bounded execution (timeout, retry, abort)
10
+ *
11
+ * Subclasses (NodeBridge, HttpBridge, PyodideBridge) only need to:
12
+ * 1. Create their transport in their constructor
13
+ * 2. Pass it to super() via BridgeProtocolOptions
14
+ * 3. Optionally override doInit() and doDispose() for additional setup/teardown
15
+ *
16
+ * @see https://github.com/bbopen/tywrap/issues/149
17
+ */
18
+
19
+ import type { BridgeInfo } from '../types/index.js';
20
+
21
+ import { BoundedContext, type ExecuteOptions } from './bounded-context.js';
22
+ import { BridgeProtocolError } from './errors.js';
23
+ import { SafeCodec, type CodecOptions } from './safe-codec.js';
24
+ import { TYWRAP_PROTOCOL_VERSION } from './protocol.js';
25
+ import { PROTOCOL_ID, type Transport, type ProtocolMessage } from './transport.js';
26
+
27
+ // =============================================================================
28
+ // TYPES
29
+ // =============================================================================
30
+
31
+ export interface GetBridgeInfoOptions {
32
+ /**
33
+ * If true, bypasses the cached info and queries the bridge again.
34
+ * This is useful when you want up-to-date instance counts or diagnostics.
35
+ */
36
+ refresh?: boolean;
37
+ }
38
+
39
+ /**
40
+ * Configuration options for BridgeProtocol.
41
+ */
42
+ export interface BridgeProtocolOptions {
43
+ /** The transport to use for communication */
44
+ transport: Transport;
45
+
46
+ /** Codec options for validation/serialization */
47
+ codec?: CodecOptions;
48
+
49
+ /** Default timeout for operations in ms. Default: 30000 (30s) */
50
+ defaultTimeoutMs?: number;
51
+ }
52
+
53
+ function validateBridgeInfoPayload(value: unknown): BridgeInfo {
54
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
55
+ const kind = value === null ? 'null' : Array.isArray(value) ? 'array' : typeof value;
56
+ throw new BridgeProtocolError(`Invalid bridge info payload: expected object, got ${kind}`);
57
+ }
58
+
59
+ interface BridgeInfoWire {
60
+ protocol?: unknown;
61
+ protocolVersion?: unknown;
62
+ bridge?: unknown;
63
+ pythonVersion?: unknown;
64
+ pid?: unknown;
65
+ codecFallback?: unknown;
66
+ arrowAvailable?: unknown;
67
+ scipyAvailable?: unknown;
68
+ torchAvailable?: unknown;
69
+ sklearnAvailable?: unknown;
70
+ instances?: unknown;
71
+ }
72
+
73
+ const formatValue = (val: unknown): string => {
74
+ try {
75
+ const serialized = JSON.stringify(val);
76
+ return serialized ?? String(val);
77
+ } catch {
78
+ return String(val);
79
+ }
80
+ };
81
+
82
+ const obj = value as BridgeInfoWire;
83
+
84
+ const protocol = obj.protocol;
85
+ if (protocol !== PROTOCOL_ID) {
86
+ throw new BridgeProtocolError(
87
+ `Invalid bridge info payload: protocol expected "${PROTOCOL_ID}", got ${formatValue(protocol)}`
88
+ );
89
+ }
90
+
91
+ const protocolVersion = obj.protocolVersion;
92
+ if (protocolVersion !== TYWRAP_PROTOCOL_VERSION) {
93
+ throw new BridgeProtocolError(
94
+ `Invalid bridge info payload: protocolVersion expected ${TYWRAP_PROTOCOL_VERSION}, got ${formatValue(protocolVersion)}`
95
+ );
96
+ }
97
+
98
+ const bridge = obj.bridge;
99
+ if (bridge !== 'python-subprocess') {
100
+ throw new BridgeProtocolError(
101
+ `Invalid bridge info payload: bridge expected "python-subprocess", got ${formatValue(bridge)}`
102
+ );
103
+ }
104
+
105
+ const pythonVersion = obj.pythonVersion;
106
+ if (typeof pythonVersion !== 'string' || pythonVersion.length === 0) {
107
+ throw new BridgeProtocolError(
108
+ `Invalid bridge info payload: pythonVersion expected non-empty string, got ${formatValue(pythonVersion)}`
109
+ );
110
+ }
111
+
112
+ const pid = obj.pid;
113
+ if (typeof pid !== 'number' || !Number.isInteger(pid) || pid <= 0) {
114
+ throw new BridgeProtocolError(
115
+ `Invalid bridge info payload: pid expected positive integer, got ${formatValue(pid)}`
116
+ );
117
+ }
118
+
119
+ const codecFallback = obj.codecFallback;
120
+ if (codecFallback !== 'json' && codecFallback !== 'none') {
121
+ throw new BridgeProtocolError(
122
+ `Invalid bridge info payload: codecFallback expected "json" or "none", got ${formatValue(codecFallback)}`
123
+ );
124
+ }
125
+
126
+ const arrowAvailable = obj.arrowAvailable;
127
+ if (typeof arrowAvailable !== 'boolean') {
128
+ throw new BridgeProtocolError(
129
+ `Invalid bridge info payload: arrowAvailable expected boolean, got ${formatValue(arrowAvailable)}`
130
+ );
131
+ }
132
+
133
+ const scipyAvailable = obj.scipyAvailable;
134
+ if (typeof scipyAvailable !== 'boolean') {
135
+ throw new BridgeProtocolError(
136
+ `Invalid bridge info payload: scipyAvailable expected boolean, got ${formatValue(scipyAvailable)}`
137
+ );
138
+ }
139
+
140
+ const torchAvailable = obj.torchAvailable;
141
+ if (typeof torchAvailable !== 'boolean') {
142
+ throw new BridgeProtocolError(
143
+ `Invalid bridge info payload: torchAvailable expected boolean, got ${formatValue(torchAvailable)}`
144
+ );
145
+ }
146
+
147
+ const sklearnAvailable = obj.sklearnAvailable;
148
+ if (typeof sklearnAvailable !== 'boolean') {
149
+ throw new BridgeProtocolError(
150
+ `Invalid bridge info payload: sklearnAvailable expected boolean, got ${formatValue(sklearnAvailable)}`
151
+ );
152
+ }
153
+
154
+ const instances = obj.instances;
155
+ if (typeof instances !== 'number' || !Number.isInteger(instances) || instances < 0) {
156
+ throw new BridgeProtocolError(
157
+ `Invalid bridge info payload: instances expected non-negative integer, got ${formatValue(instances)}`
158
+ );
159
+ }
160
+
161
+ return {
162
+ protocol: PROTOCOL_ID,
163
+ protocolVersion: TYWRAP_PROTOCOL_VERSION,
164
+ bridge: 'python-subprocess',
165
+ pythonVersion,
166
+ pid,
167
+ codecFallback,
168
+ arrowAvailable,
169
+ scipyAvailable,
170
+ torchAvailable,
171
+ sklearnAvailable,
172
+ instances,
173
+ };
174
+ }
175
+
176
+ // =============================================================================
177
+ // BRIDGE PROTOCOL BASE CLASS
178
+ // =============================================================================
179
+
180
+ /**
181
+ * BridgeProtocol combines BoundedContext + SafeCodec + Transport
182
+ * into a unified abstraction for all JS<->Python communication.
183
+ *
184
+ * This class provides:
185
+ * - Automatic transport lifecycle management
186
+ * - Request encoding with guardrails (special float rejection, key validation)
187
+ * - Response decoding with Arrow support
188
+ * - Full RuntimeExecution interface implementation
189
+ *
190
+ * Subclasses should:
191
+ * 1. Create their transport in their constructor
192
+ * 2. Pass it to super() via BridgeProtocolOptions
193
+ * 3. Optionally override doInit() and doDispose() for additional setup/teardown
194
+ *
195
+ * @example
196
+ * ```typescript
197
+ * class NodeBridge extends BridgeProtocol {
198
+ * constructor(options: NodeBridgeOptions) {
199
+ * const transport = new ProcessIO(options);
200
+ * super({ transport, defaultTimeoutMs: options.timeout });
201
+ * }
202
+ * }
203
+ * ```
204
+ */
205
+ export class BridgeProtocol extends BoundedContext {
206
+ /** Codec instance for validation and serialization */
207
+ protected readonly codec: SafeCodec;
208
+
209
+ /** Transport instance for message passing */
210
+ protected readonly transport: Transport;
211
+
212
+ /** Default timeout for operations in milliseconds */
213
+ protected readonly defaultTimeoutMs: number;
214
+
215
+ /** Counter for generating unique request IDs */
216
+ private requestId = 0;
217
+
218
+ /** Cached bridge diagnostics info (populated by getBridgeInfo). */
219
+ private bridgeInfoCache?: BridgeInfo;
220
+
221
+ /**
222
+ * Create a new BridgeProtocol instance.
223
+ *
224
+ * @param options - Configuration options including transport and codec settings
225
+ */
226
+ constructor(options: BridgeProtocolOptions) {
227
+ super();
228
+ this.codec = new SafeCodec(options.codec);
229
+ this.transport = options.transport;
230
+ this.defaultTimeoutMs = options.defaultTimeoutMs ?? 30000;
231
+
232
+ // Track the transport for automatic cleanup during dispose
233
+ this.trackResource(this.transport);
234
+ }
235
+
236
+ // ===========================================================================
237
+ // LIFECYCLE
238
+ // ===========================================================================
239
+
240
+ /**
241
+ * Initialize the protocol.
242
+ *
243
+ * Initializes the underlying transport. Subclasses can override this
244
+ * to add additional initialization logic, but must call super.doInit().
245
+ */
246
+ protected async doInit(): Promise<void> {
247
+ await this.transport.init();
248
+ }
249
+
250
+ /**
251
+ * Dispose the protocol.
252
+ *
253
+ * The transport is tracked as a resource and will be disposed automatically
254
+ * by BoundedContext. Subclasses can override this to add additional cleanup,
255
+ * but should not need to dispose the transport manually.
256
+ */
257
+ protected async doDispose(): Promise<void> {
258
+ this.bridgeInfoCache = undefined;
259
+ // Transport is tracked and will be disposed by BoundedContext
260
+ // Subclasses can override to add additional cleanup
261
+ }
262
+
263
+ // ===========================================================================
264
+ // MESSAGE SENDING
265
+ // ===========================================================================
266
+
267
+ /**
268
+ * Send a protocol message and receive a typed response.
269
+ *
270
+ * This method:
271
+ * 1. Generates a unique request ID
272
+ * 2. Encodes the request with validation (special float rejection, etc.)
273
+ * 3. Sends via transport with timeout/abort support
274
+ * 4. Decodes and validates the response
275
+ *
276
+ * @param message - The protocol message (without id field)
277
+ * @param options - Execution options (timeout, retries, validation)
278
+ * @returns The typed response from Python
279
+ *
280
+ * @throws BridgeProtocolError if encoding/decoding fails
281
+ * @throws BridgeExecutionError if Python returns an error
282
+ * @throws BridgeTimeoutError if the operation times out
283
+ */
284
+ protected async sendMessage<T>(
285
+ message: Omit<ProtocolMessage, 'id' | 'protocol'>,
286
+ options?: ExecuteOptions<T>
287
+ ): Promise<T> {
288
+ const fullMessage: ProtocolMessage = {
289
+ ...message,
290
+ id: this.generateId(),
291
+ protocol: PROTOCOL_ID,
292
+ };
293
+
294
+ return this.execute(async () => {
295
+ // 1. Encode request (validates args)
296
+ const encoded = this.codec.encodeRequest(fullMessage);
297
+
298
+ // 2. Send via transport
299
+ const responseStr = await this.transport.send(
300
+ encoded,
301
+ options?.timeoutMs ?? this.defaultTimeoutMs,
302
+ options?.signal
303
+ );
304
+
305
+ // 3. Decode response (validates result)
306
+ return this.codec.decodeResponse<T>(responseStr);
307
+ }, options);
308
+ }
309
+
310
+ /**
311
+ * Async version that uses decodeResponseAsync for Arrow support.
312
+ *
313
+ * Use this method when the response may contain encoded DataFrames,
314
+ * ndarrays, or other Arrow-encoded data structures.
315
+ *
316
+ * @param message - The protocol message (without id field)
317
+ * @param options - Execution options (timeout, retries, validation)
318
+ * @returns The typed response from Python with Arrow decoding applied
319
+ *
320
+ * @throws BridgeProtocolError if encoding/decoding fails
321
+ * @throws BridgeExecutionError if Python returns an error
322
+ * @throws BridgeTimeoutError if the operation times out
323
+ */
324
+ protected async sendMessageAsync<T>(
325
+ message: Omit<ProtocolMessage, 'id' | 'protocol'>,
326
+ options?: ExecuteOptions<T>
327
+ ): Promise<T> {
328
+ const fullMessage: ProtocolMessage = {
329
+ ...message,
330
+ id: this.generateId(),
331
+ protocol: PROTOCOL_ID,
332
+ };
333
+
334
+ return this.execute(async () => {
335
+ // 1. Encode request (validates args)
336
+ const encoded = this.codec.encodeRequest(fullMessage);
337
+
338
+ // 2. Send via transport
339
+ const responseStr = await this.transport.send(
340
+ encoded,
341
+ options?.timeoutMs ?? this.defaultTimeoutMs,
342
+ options?.signal
343
+ );
344
+
345
+ // 3. Decode response with Arrow support
346
+ return this.codec.decodeResponseAsync<T>(responseStr);
347
+ }, options);
348
+ }
349
+
350
+ /**
351
+ * Generate a unique request ID.
352
+ *
353
+ * Returns a monotonically increasing integer that ensures uniqueness
354
+ * within a process lifetime.
355
+ */
356
+ private generateId(): number {
357
+ return ++this.requestId;
358
+ }
359
+
360
+ // ===========================================================================
361
+ // RUNTIME EXECUTION INTERFACE
362
+ // ===========================================================================
363
+
364
+ /**
365
+ * Call a Python function.
366
+ *
367
+ * @param module - Python module path (e.g., 'numpy', 'mypackage.submodule')
368
+ * @param functionName - Function name to call
369
+ * @param args - Positional arguments
370
+ * @param kwargs - Keyword arguments
371
+ * @returns The function result
372
+ */
373
+ async call<T = unknown>(
374
+ module: string,
375
+ functionName: string,
376
+ args: unknown[],
377
+ kwargs?: Record<string, unknown>
378
+ ): Promise<T> {
379
+ return this.sendMessageAsync<T>({
380
+ method: 'call',
381
+ params: {
382
+ module,
383
+ functionName,
384
+ args,
385
+ kwargs,
386
+ },
387
+ });
388
+ }
389
+
390
+ /**
391
+ * Instantiate a Python class.
392
+ *
393
+ * @param module - Python module path containing the class
394
+ * @param className - Class name to instantiate
395
+ * @param args - Positional constructor arguments
396
+ * @param kwargs - Keyword constructor arguments
397
+ * @returns A handle to the created instance
398
+ */
399
+ async instantiate<T = unknown>(
400
+ module: string,
401
+ className: string,
402
+ args: unknown[],
403
+ kwargs?: Record<string, unknown>
404
+ ): Promise<T> {
405
+ return this.sendMessageAsync<T>({
406
+ method: 'instantiate',
407
+ params: {
408
+ module,
409
+ className,
410
+ args,
411
+ kwargs,
412
+ },
413
+ });
414
+ }
415
+
416
+ /**
417
+ * Call a method on a Python instance.
418
+ *
419
+ * @param handle - Instance handle returned from instantiate()
420
+ * @param methodName - Method name to call
421
+ * @param args - Positional arguments
422
+ * @param kwargs - Keyword arguments
423
+ * @returns The method result
424
+ */
425
+ async callMethod<T = unknown>(
426
+ handle: string,
427
+ methodName: string,
428
+ args: unknown[],
429
+ kwargs?: Record<string, unknown>
430
+ ): Promise<T> {
431
+ return this.sendMessageAsync<T>({
432
+ method: 'call_method',
433
+ params: {
434
+ handle,
435
+ methodName,
436
+ args,
437
+ kwargs,
438
+ },
439
+ });
440
+ }
441
+
442
+ /**
443
+ * Dispose a Python instance.
444
+ *
445
+ * Releases the instance handle on the Python side, allowing
446
+ * the object to be garbage collected.
447
+ *
448
+ * @param handle - Instance handle to dispose
449
+ */
450
+ async disposeInstance(handle: string): Promise<void> {
451
+ await this.sendMessageAsync<void>({
452
+ method: 'dispose_instance',
453
+ params: {
454
+ handle,
455
+ },
456
+ });
457
+ }
458
+
459
+ /**
460
+ * Fetch bridge diagnostics and feature availability.
461
+ *
462
+ * The Python bridge supports a `meta` method that returns protocol and environment info
463
+ * (including optional codec availability and current instance count).
464
+ */
465
+ async getBridgeInfo(options: GetBridgeInfoOptions = {}): Promise<BridgeInfo> {
466
+ if (!options.refresh && this.bridgeInfoCache) {
467
+ return this.bridgeInfoCache;
468
+ }
469
+
470
+ const info = await this.sendMessage<BridgeInfo>(
471
+ {
472
+ method: 'meta',
473
+ params: {},
474
+ },
475
+ {
476
+ validate: validateBridgeInfoPayload,
477
+ }
478
+ );
479
+
480
+ this.bridgeInfoCache = info;
481
+ return info;
482
+ }
483
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Disposable interface for resources that need cleanup.
3
+ *
4
+ * This interface enables the BoundedContext resource tracking system
5
+ * to automatically dispose of owned resources when the context is disposed.
6
+ */
7
+
8
+ /**
9
+ * Interface for resources that require explicit cleanup.
10
+ */
11
+ export interface Disposable {
12
+ /**
13
+ * Release resources held by this object.
14
+ * This method should be idempotent (safe to call multiple times).
15
+ */
16
+ dispose(): Promise<void>;
17
+ }
18
+
19
+ /**
20
+ * Type guard to check if a value implements the Disposable interface.
21
+ *
22
+ * @param value - The value to check
23
+ * @returns True if the value has a dispose method
24
+ */
25
+ export function isDisposable(value: unknown): value is Disposable {
26
+ return (
27
+ value !== null &&
28
+ typeof value === 'object' &&
29
+ typeof (value as Disposable).dispose === 'function'
30
+ );
31
+ }
32
+
33
+ /**
34
+ * Safely dispose a value if it implements Disposable.
35
+ * Does nothing if the value is not disposable.
36
+ *
37
+ * @param value - The value to dispose
38
+ * @returns Promise that resolves when disposal is complete
39
+ */
40
+ export async function safeDispose(value: unknown): Promise<void> {
41
+ if (isDisposable(value)) {
42
+ await value.dispose();
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Dispose multiple resources, collecting any errors.
48
+ * All resources will be attempted even if some fail.
49
+ *
50
+ * @param resources - Iterable of Disposable resources
51
+ * @returns Array of errors that occurred during disposal (empty if all succeeded)
52
+ */
53
+ export async function disposeAll(resources: Iterable<Disposable>): Promise<Error[]> {
54
+ const errors: Error[] = [];
55
+
56
+ for (const resource of resources) {
57
+ try {
58
+ await resource.dispose();
59
+ } catch (e) {
60
+ errors.push(e instanceof Error ? e : new Error(String(e)));
61
+ }
62
+ }
63
+
64
+ return errors;
65
+ }
@@ -14,6 +14,20 @@ export class BridgeProtocolError extends BridgeError {}
14
14
  export class BridgeTimeoutError extends BridgeError {}
15
15
  export class BridgeDisposedError extends BridgeError {}
16
16
 
17
+ export class BridgeCodecError extends BridgeError {
18
+ codecPhase?: string;
19
+ valueType?: string;
20
+
21
+ constructor(
22
+ message: string,
23
+ options?: { code?: string; cause?: unknown; codecPhase?: string; valueType?: string }
24
+ ) {
25
+ super(message, { code: options?.code, cause: options?.cause });
26
+ this.codecPhase = options?.codecPhase;
27
+ this.valueType = options?.valueType;
28
+ }
29
+ }
30
+
17
31
  export class BridgeExecutionError extends BridgeError {
18
32
  traceback?: string;
19
33
  }