procxy 0.1.0-alpha.1 → 0.1.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,7 +17,7 @@ Run class instances in isolated child processes while interacting with them as i
17
17
  ## ✨ Features
18
18
 
19
19
  - **🎯 Type-Safe** - Full TypeScript support with IntelliSense autocomplete
20
- - **🪄 Automagic Module Resolution** - Zero-config import path detection from your source code
20
+ - **🪄 Automatic Module Resolution** - Zero-config import path detection from your source code
21
21
  - **⚡ Fast** - <10ms overhead per method call
22
22
  - **🔄 Events & Callbacks** - Transparent EventEmitter forwarding and bidirectional callback support
23
23
  - **🏠 Properties** - Read-only properties on parent, full read/write on child
@@ -66,6 +66,7 @@ const calc2 = await procxy(Calculator, './calculator.js');
66
66
  ### Using Disposables (Recommended)
67
67
 
68
68
  ```typescript
69
+ import { procxy } from 'procxy';
69
70
  import { Calculator } from './calculator.js';
70
71
 
71
72
  // Automatic cleanup with await using
@@ -77,22 +78,22 @@ const result = await calc.add(5, 3);
77
78
  ### Constructor Arguments
78
79
 
79
80
  ```typescript
81
+ import { procxy } from 'procxy';
80
82
  import { Worker } from './worker.js';
81
83
 
82
- class Worker {
83
- constructor(public name: string, public threads: number) {}
84
-
85
- async process(data: string[]): Promise<string[]> {
86
- // Heavy processing in isolated process
87
- return data.map(s => s.toUpperCase());
88
- }
89
- }
84
+ // Worker class (in worker.ts):
85
+ // class Worker {
86
+ // constructor(public name: string, public threads: number) {}
87
+ //
88
+ // async process(data: string[]): Promise<string[]> {
89
+ // return data.map(s => s.toUpperCase());
90
+ // }
91
+ // }
90
92
 
91
93
  // Pass constructor arguments after options
92
94
  const worker = await procxy(
93
95
  Worker,
94
- './worker.js', // Can be omitted if Worker is imported
95
- undefined, // options (or omit)
96
+ undefined, // options (or omit entirely)
96
97
  'MyWorker', // name argument
97
98
  4 // threads argument
98
99
  );
@@ -103,9 +104,9 @@ const result = await worker.process(['hello', 'world']);
103
104
  await worker.$terminate();
104
105
  ```
105
106
 
106
- ## 🪄 Automagic Module Resolution
107
+ ## 🪄 Automatic Module Resolution
107
108
 
108
- One of procxy's most convenient features is **automatic module path detection**. You don't need to manually specify where your class is locatedprocxy figures it out by parsing your import statements at runtime.
109
+ One of procxy's most convenient features is **automatic module path detection**. You don't need to manually specify where your class is located - procxy will figure it out by parsing your import statements at runtime.
109
110
 
110
111
  ### How It Works
111
112
 
@@ -186,8 +187,10 @@ Creates a process-based proxy for a class instance.
186
187
 
187
188
  **Parameters:**
188
189
 
189
- - `Class: Constructor<T>` - The class constructor to instantiate
190
- - `modulePath?: string` - **Optional** path to the module containing the class (auto-detected if omitted)
190
+ - `Class: Constructor<T>` - The class constructor to instantiate (must be a named class)
191
+ - `modulePath?: string` - **Optional** - Path to the module containing the class
192
+ - **Omit this parameter** when using static imports (automatic resolution)
193
+ - **Provide explicitly** for dynamic imports or complex re-exports
191
194
  - `options?: ProcxyOptions` - Optional configuration
192
195
  - `...constructorArgs` - Arguments for the class constructor
193
196
 
@@ -195,64 +198,68 @@ Creates a process-based proxy for a class instance.
195
198
 
196
199
  **Module Path Auto-Detection:**
197
200
 
198
- procxy can automatically detect the module path by parsing your source file's import statements:
199
-
200
- ```typescript
201
- import { Worker } from './worker.js'; // procxy detects this!
201
+ When `modulePath` is omitted, procxy automatically detects the module path by:
202
+ 1. Inspecting the call stack to find where procxy was called
203
+ 2. Reading and parsing the source file for import/require statements
204
+ 3. Matching the class name to find the corresponding import path
202
205
 
203
- // No modulePath needed
204
- const worker = await procxy(Worker);
205
- ```
206
-
207
- The module path is auto-detected from:
208
- - ESM named imports: `import { Class } from './path'`
209
- - ESM default imports: `import Class from './path'`
210
- - CommonJS imports: `const { Class } = require('./path')`
211
- - Class definitions in the same file
212
-
213
- **When to provide explicit modulePath:**
214
- - Dynamic imports: `const { Worker } = await import('./worker')`
215
- - Ambiguous import scenarios
216
- - When importing from multiple locations
217
-
218
- **Example:**
219
-
220
- ```typescript
221
- const worker = await procxy(Worker, './worker.js', {
222
- timeout: 60000, // 60s per method call
223
- retries: 3, // Retry 3 times on failure
224
- env: { NODE_ENV: 'production' },
225
- cwd: '/tmp'
226
- });
227
- ```
206
+ This works with ESM imports, CommonJS requires, and classes defined in the same file. See [Automatic Module Resolution](#-automatic-module-resolution) for details.
228
207
 
229
208
  ### `Procxy<T>` Type
230
209
 
231
210
  The proxy type that wraps your class instance:
232
211
 
233
212
  - **Methods**: All methods become `async` and return `Promise<ReturnType>`
234
- - **Properties**: Public properties available as read-only on the parent proxy (updated from child)
235
- - **Callbacks**: Function parameters are automatically proxied across process boundaries
236
- - **Filtering**: Only JSON-serializable methods and properties are included
213
+ - **Properties**: Public properties are read-only on the parent (synchronized from child after method calls)
214
+ - **Callbacks**: Function parameters are automatically proxied bidirectionally across processes
215
+ - **Events**: EventEmitter events flow from child to parent transparently
237
216
  - **Lifecycle Methods**:
238
- - `$terminate(): Promise<void>` - Terminate the child process
239
- - `$process: ChildProcess` - Access the underlying process
240
- - **Disposable**: Supports `using` and `await using` for automatic cleanup
217
+ - `$terminate(): Promise<void>` - Gracefully terminate the child process
218
+ - `$process: ChildProcess` - Access the underlying Node.js child process
219
+ - **Disposable Protocol**: Supports `using` and `await using` for automatic cleanup
220
+ - **Type Safety**: Full TypeScript IntelliSense and autocomplete support
241
221
 
242
222
  ### `ProcxyOptions`
243
223
 
244
- Configuration options:
224
+ Configuration options for customizing child process behavior:
245
225
 
246
226
  ```typescript
247
227
  interface ProcxyOptions {
248
- timeout?: number; // Timeout per method call (default: 30000ms)
249
- retries?: number; // Retry attempts (default: 3)
250
- env?: Record<string, string>; // Custom environment variables
251
- cwd?: string; // Working directory for child process
252
- args?: Jsonifiable[]; // Command line arguments
228
+ modulePath?: string; // Path to module (optional if using static imports)
229
+ timeout?: number; // Timeout per method call in ms (default: 30000)
230
+ retries?: number; // Number of retry attempts on failure (default: 3)
231
+ env?: Record<string, string>; // Custom environment variables for child process
232
+ cwd?: string; // Working directory for child process
233
+ args?: Jsonifiable[]; // Additional command line arguments
234
+ serialization?: 'json' | 'advanced'; // Serialization mode (default: 'json')
235
+ supportHandles?: boolean; // Enable handle passing for sockets (advanced mode only)
253
236
  }
254
237
  ```
255
238
 
239
+ **Examples:**
240
+
241
+ ```typescript
242
+ import { procxy } from 'procxy';
243
+ import { HeavyWorker } from './heavy-worker.js';
244
+ import { APIClient } from './api-client.js';
245
+ import { FileProcessor } from './file-processor.js';
246
+ import { BinaryProcessor } from './binary-processor.js';
247
+
248
+ // Long-running operations
249
+ await procxy(HeavyWorker, { timeout: 300000 }); // 5 minutes
250
+
251
+ // Custom environment
252
+ await procxy(APIClient, {
253
+ env: { API_KEY: process.env.API_KEY }
254
+ });
255
+
256
+ // Isolated working directory
257
+ await procxy(FileProcessor, { cwd: '/tmp/workspace' });
258
+
259
+ // Advanced serialization for binary data
260
+ await procxy(BinaryProcessor, { serialization: 'advanced' });
261
+ ```
262
+
256
263
  ## 🎯 Use Cases
257
264
 
258
265
  ### CPU-Intensive Tasks
@@ -260,13 +267,10 @@ interface ProcxyOptions {
260
267
  Offload heavy computations without blocking the event loop:
261
268
 
262
269
  ```typescript
263
- class ImageProcessor {
264
- async resize(image: Buffer, width: number): Promise<Buffer> {
265
- // Heavy image processing
266
- }
267
- }
270
+ import { procxy } from 'procxy';
271
+ import { ImageProcessor } from './image-processor.js';
268
272
 
269
- await using processor = await procxy(ImageProcessor, './image-processor.js');
273
+ await using processor = await procxy(ImageProcessor);
270
274
  const resized = await processor.resize(imageData, 800);
271
275
  ```
272
276
 
@@ -275,13 +279,10 @@ const resized = await processor.resize(imageData, 800);
275
279
  Run untrusted code in isolated processes:
276
280
 
277
281
  ```typescript
278
- class SandboxedRunner {
279
- async execute(code: string): Promise<any> {
280
- // Execute in isolated environment
281
- }
282
- }
282
+ import { procxy } from 'procxy';
283
+ import { SandboxedRunner } from './sandbox.js';
283
284
 
284
- const sandbox = await procxy(SandboxedRunner, './sandbox.js', {
285
+ const sandbox = await procxy(SandboxedRunner, {
285
286
  timeout: 5000, // Kill after 5s
286
287
  env: { NODE_ENV: 'sandbox' }
287
288
  });
@@ -292,16 +293,18 @@ const sandbox = await procxy(SandboxedRunner, './sandbox.js', {
292
293
  Classes extending EventEmitter work transparently:
293
294
 
294
295
  ```typescript
296
+ import { procxy } from 'procxy';
295
297
  import { EventEmitter } from 'events';
298
+ import { DataStream } from './stream.js';
296
299
 
297
- class DataStream extends EventEmitter {
298
- async start(): Promise<void> {
299
- // Stream data and emit events
300
- this.emit('data', { chunk: 'example' });
301
- }
302
- }
300
+ // DataStream class (in stream.ts):
301
+ // class DataStream extends EventEmitter {
302
+ // async start(): Promise<void> {
303
+ // this.emit('data', { chunk: 'example' });
304
+ // }
305
+ // }
303
306
 
304
- const stream = await procxy(DataStream, './stream.js');
307
+ const stream = await procxy(DataStream);
305
308
 
306
309
  stream.on('data', (chunk) => {
307
310
  console.log('Received:', chunk);
@@ -315,21 +318,25 @@ await stream.start();
315
318
  Pass callbacks as function parameters and they'll be transparently proxied across the process boundary:
316
319
 
317
320
  ```typescript
318
- class AsyncWorker {
319
- async processWithCallback(
320
- data: string[],
321
- onProgress: (current: number, total: number) => void
322
- ): Promise<string[]> {
323
- const result = [];
324
- for (let i = 0; i < data.length; i++) {
325
- result.push(data[i].toUpperCase());
326
- onProgress(i + 1, data.length); // Invokes callback in parent
327
- }
328
- return result;
329
- }
330
- }
331
-
332
- const worker = await procxy(AsyncWorker, './async-worker.js');
321
+ import { procxy } from 'procxy';
322
+ import { AsyncWorker } from './async-worker.js';
323
+
324
+ // AsyncWorker class (in async-worker.ts):
325
+ // class AsyncWorker {
326
+ // async processWithCallback(
327
+ // data: string[],
328
+ // onProgress: (current: number, total: number) => void
329
+ // ): Promise<string[]> {
330
+ // const result = [];
331
+ // for (let i = 0; i < data.length; i++) {
332
+ // result.push(data[i].toUpperCase());
333
+ // onProgress(i + 1, data.length); // Invokes callback in parent
334
+ // }
335
+ // return result;
336
+ // }
337
+ // }
338
+
339
+ const worker = await procxy(AsyncWorker);
333
340
 
334
341
  const result = await worker.processWithCallback(
335
342
  ['hello', 'world'],
@@ -352,47 +359,72 @@ const result = await worker.processWithCallback(
352
359
 
353
360
  ### Properties Support
354
361
 
355
- Public properties are accessible as read-only on the parent and can be read/modified on the child:
362
+ Public properties are accessible with different capabilities on parent vs. child:
363
+
364
+ **Parent Process (Read-Only):**
365
+ - **Get**: Synchronous property reads from local property store
366
+ - Properties are automatically synced after each method call
367
+
368
+ **Child Process (Read/Write):**
369
+ - **Get**: Direct property access (no IPC overhead)
370
+ - **Set**: Modifies property and syncs to parent
356
371
 
357
372
  ```typescript
373
+ // counter.ts
374
+ import { procxy } from 'procxy';
375
+
358
376
  class Counter {
359
377
  public count: number = 0;
360
378
  public name: string = '';
361
379
 
362
380
  increment(): void {
381
+ // Child can SET properties directly
363
382
  this.count++;
364
383
  }
365
384
 
366
385
  setName(newName: string): void {
386
+ // Child can SET properties
367
387
  this.name = newName;
368
388
  }
369
389
 
370
390
  getCount(): number {
391
+ // Child can GET properties directly (no IPC)
371
392
  return this.count;
372
393
  }
394
+
395
+ getName(): string {
396
+ // Child can GET properties directly
397
+ return this.name;
398
+ }
373
399
  }
374
400
 
375
- const counter = await procxy(Counter, './counter.js');
401
+ // main.ts
402
+ import { procxy } from 'procxy';
403
+ import { Counter } from './counter.js';
404
+
405
+ const counter = await procxy(Counter);
376
406
 
377
- // Properties are read-only on parent
378
- console.log(counter.count); // 0 - synchronous read from property store
379
- console.log(counter.name); // '' - synchronous read
407
+ // Parent can GET properties (synchronous read from property store)
408
+ console.log(counter.count); // 0 - no IPC, reads from local store
409
+ console.log(counter.name); // '' - synchronous
380
410
 
381
- // Modify via child methods
411
+ // Modify via child methods (parent CANNOT set directly)
382
412
  await counter.increment();
383
413
  await counter.setName('MyCounter');
384
414
 
385
- // Parent reads updated values
386
- console.log(counter.count); // 1 - automatically synced from child
387
- console.log(counter.name); // 'MyCounter' - automatically synced
415
+ // Parent GETs updated values (automatically synced from child)
416
+ console.log(counter.count); // 1 - synced after increment()
417
+ console.log(counter.name); // 'MyCounter' - synced after setName()
418
+
419
+ // Parent cannot SET properties (read-only)
420
+ // counter.count = 5; // ❌ Throws error: properties are read-only on parent
388
421
  ```
389
422
 
390
423
  **Property Synchronization:**
391
424
  - Parent maintains a property store synchronized from the child
392
- - Child can read properties directly (no IPC needed)
393
- - Child can set properties (sends update to parent)
394
- - Property updates are automatically synced after method calls
395
- - Parent can only read properties (attempting to set throws an error)
425
+ - Child can **get** and **set** properties directly (no IPC needed for reads)
426
+ - Property updates are automatically synced to parent after method calls
427
+ - Parent can only **get** properties (attempting to set throws an error)
396
428
  - No race conditions: only the child can modify properties
397
429
 
398
430
  ## 🛡️ Error Handling
@@ -430,7 +462,10 @@ try {
430
462
  ### Timeouts and Retries
431
463
 
432
464
  ```typescript
433
- const worker = await procxy(Worker, './worker.js', {
465
+ import { procxy } from 'procxy';
466
+ import { Worker } from './worker.js';
467
+
468
+ const worker = await procxy(Worker, {
434
469
  timeout: 60000, // 60 seconds per call
435
470
  retries: 5 // Retry 5 times before failing
436
471
  });
@@ -439,7 +474,10 @@ const worker = await procxy(Worker, './worker.js', {
439
474
  ### Custom Environment
440
475
 
441
476
  ```typescript
442
- const worker = await procxy(Worker, './worker.js', {
477
+ import { procxy } from 'procxy';
478
+ import { Worker } from './worker.js';
479
+
480
+ const worker = await procxy(Worker, {
443
481
  env: {
444
482
  NODE_ENV: 'production',
445
483
  API_KEY: process.env.API_KEY,
@@ -451,17 +489,253 @@ const worker = await procxy(Worker, './worker.js', {
451
489
  ### Working Directory
452
490
 
453
491
  ```typescript
454
- const worker = await procxy(Worker, './worker.js', {
492
+ import { procxy } from 'procxy';
493
+ import { Worker } from './worker.js';
494
+
495
+ const worker = await procxy(Worker, {
455
496
  cwd: '/tmp/workspace'
456
497
  });
457
498
  ```
458
499
 
500
+ ## 🎨 Advanced Serialization (V8 Structured Clone)
501
+
502
+ By default, procxy uses JSON serialization for IPC messages. However, you can enable **advanced serialization mode** to support additional data types using V8's structured clone algorithm.
503
+
504
+ ### Supported Types in Advanced Mode
505
+
506
+ When using `serialization: 'advanced'`, you can pass these additional types:
507
+
508
+ - **Binary Data**: `Buffer`, `ArrayBuffer`, `TypedArray` (Uint8Array, Int32Array, Float32Array, etc.)
509
+ - **Collections**: `Map`, `Set` with full fidelity (not converted to arrays)
510
+ - **Large Numbers**: `BigInt` values
511
+ - **Built-in Objects**: `Date`, `RegExp`, `Error` instances with all properties preserved
512
+
513
+ ### Usage
514
+
515
+ ```typescript
516
+ import { procxy } from 'procxy';
517
+ import { ImageProcessor } from './image-processor.js';
518
+
519
+ // Enable advanced serialization
520
+ await using processor = await procxy(ImageProcessor, {
521
+ serialization: 'advanced' // 👈 Enable V8 structured clone
522
+ });
523
+
524
+ // Now you can pass Buffer, TypedArray, Map, Set, BigInt, etc.
525
+ const imageBuffer = Buffer.from(imageData);
526
+ const processed = await processor.processImage(imageBuffer);
527
+
528
+ // Use Map for caching
529
+ const cache = new Map([
530
+ ['key1', Buffer.from('data1')],
531
+ ['key2', Buffer.from('data2')]
532
+ ]);
533
+ await processor.bulkCache(cache);
534
+
535
+ // Use BigInt for large numbers
536
+ const timestamp = BigInt(Date.now()) * BigInt(1000000);
537
+ await processor.recordTimestamp(timestamp);
538
+
539
+ // TypedArray for binary protocols
540
+ const binaryData = new Uint8Array([0x00, 0x01, 0x02, 0x03]);
541
+ await processor.sendBinary(binaryData);
542
+
543
+ // Set for unique collections
544
+ const uniqueIds = new Set([1, 2, 3, 4, 5]);
545
+ await processor.processIds(uniqueIds);
546
+ ```
547
+
548
+ ### Example: Binary Data Processing
549
+
550
+ ```typescript
551
+ // binary-processor.ts
552
+ export class BinaryProcessor {
553
+ // Process Buffer data
554
+ processBuffer(data: Buffer): Buffer {
555
+ const result = Buffer.alloc(data.length);
556
+ for (let i = 0; i < data.length; i++) {
557
+ result[i] = data[i] ^ 0xFF; // XOR transformation
558
+ }
559
+ return result;
560
+ }
561
+
562
+ // Work with TypedArray
563
+ sumFloat32Array(arr: Float32Array): number {
564
+ return arr.reduce((sum, val) => sum + val, 0);
565
+ }
566
+
567
+ // Use Map for caching
568
+ private cache = new Map<string, Buffer>();
569
+
570
+ cacheData(key: string, data: Buffer): void {
571
+ this.cache.set(key, data);
572
+ }
573
+
574
+ getAllCache(): Map<string, Buffer> {
575
+ return new Map(this.cache); // Returns actual Map, not array
576
+ }
577
+
578
+ // Process BigInt
579
+ multiplyBigInt(a: bigint, b: bigint): bigint {
580
+ return a * b;
581
+ }
582
+ }
583
+
584
+ // main.ts
585
+ import { procxy } from 'procxy';
586
+ import { BinaryProcessor } from './binary-processor.js';
587
+
588
+ await using processor = await procxy(BinaryProcessor, {
589
+ serialization: 'advanced'
590
+ });
591
+
592
+ // Process binary data
593
+ const input = Buffer.from([0x00, 0x11, 0x22, 0x33]);
594
+ const output = await processor.processBuffer(input);
595
+ // Buffer: [0xFF, 0xEE, 0xDD, 0xCC]
596
+
597
+ // Work with TypedArray
598
+ const floats = new Float32Array([1.1, 2.2, 3.3]);
599
+ const sum = await processor.sumFloat32Array(floats); // 6.6
600
+
601
+ // Use Map
602
+ await processor.cacheData('key1', Buffer.from('data1'));
603
+ const cache = await processor.getAllCache(); // Returns Map, not array
604
+ console.log(cache instanceof Map); // true
605
+ console.log(cache.get('key1')?.toString()); // 'data1'
606
+
607
+ // BigInt support
608
+ const result = await processor.multiplyBigInt(BigInt(123), BigInt(456));
609
+ console.log(result); // 56088n
610
+ ```
611
+
612
+ ### JSON vs Advanced Mode Comparison
613
+
614
+ | Feature | JSON Mode (default) | Advanced Mode |
615
+ |---------|-------------------|---------------|
616
+ | Primitives (string, number, boolean) | ✅ | ✅ |
617
+ | Objects & Arrays | ✅ | ✅ |
618
+ | null & undefined | ✅ | ✅ |
619
+ | Buffer | ❌ | ✅ |
620
+ | TypedArray | ❌ | ✅ |
621
+ | Map | ❌ | ✅ |
622
+ | Set | ❌ | ✅ |
623
+ | BigInt | ❌ | ✅ |
624
+ | Date | ⚠️ (as string) | ✅ (as Date) |
625
+ | RegExp | ⚠️ (as object) | ✅ (as RegExp) |
626
+ | Error | ⚠️ (partial) | ✅ (full props) |
627
+ | Handle Passing (sockets) | ❌ | ✅ (with `supportHandles`) |
628
+ | Performance | Faster for simple objects | Slightly slower |
629
+
630
+ ### Handle Passing
631
+
632
+ Advanced serialization mode also enables **handle passing** - the ability to transfer network sockets and server handles between processes.
633
+
634
+ ```typescript
635
+ import { procxy } from 'procxy';
636
+ import * as net from 'net';
637
+
638
+ class SocketHandler {
639
+ registerConnection(id: string, socket: net.Socket): void {
640
+ socket.on('data', (data) => {
641
+ console.log(`Received on ${id}:`, data.toString());
642
+ });
643
+ }
644
+ }
645
+
646
+ // Enable handle passing
647
+ const handler = await procxy(SocketHandler, {
648
+ serialization: 'advanced',
649
+ supportHandles: true // Required for handle passing
650
+ } as const); // Use 'as const' for type inference
651
+
652
+ // Create a server and accept connections
653
+ const server = net.createServer((socket) => {
654
+ // Transfer the socket to the child process
655
+ handler.$sendHandle(socket, 'connection-1'); // TypeScript knows $sendHandle is available!
656
+
657
+ // Register it in the child
658
+ handler.registerConnection('connection-1', socket);
659
+ });
660
+
661
+ server.listen(8080);
662
+ ```
663
+
664
+ **Platform Support:**
665
+ - ✅ **Unix/Linux/macOS**: Full support for socket transfer
666
+ - ⚠️ **Windows**: Limited support - some handle types may not work correctly
667
+
668
+ See [examples/advanced-serialization/socket-transfer.ts](./examples/advanced-serialization/socket-transfer.ts) for a complete example.
669
+
670
+ ### When to Use Advanced Mode
671
+
672
+ **Use JSON mode (default) when:**
673
+ - Working with simple data structures (objects, arrays, primitives)
674
+ - Maximum performance is critical for simple types
675
+ - No need for binary data or collections
676
+
677
+ **Use Advanced mode when:**
678
+ - Processing binary data (images, files, network protocols)
679
+ - Working with Map/Set collections
680
+ - Handling large numbers with BigInt
681
+ - Need to preserve Date/RegExp/Error objects with full fidelity
682
+ - Transferring TypedArray data between processes
683
+ - **Passing network sockets between processes** (requires `supportHandles: true`)
684
+
685
+ ### More Examples
686
+
687
+ See [examples/advanced-serialization/](./examples/advanced-serialization/) for comprehensive examples:
688
+ - [Buffer Processing](./examples/advanced-serialization/buffer-processing.ts) - Image data processing with Buffers
689
+ - [BigInt Calculations](./examples/advanced-serialization/bigint-calculations.ts) - Large number operations
690
+ - [Collection Processing](./examples/advanced-serialization/collection-processing.ts) - Map and Set usage
691
+ - [Socket Transfer](./examples/advanced-serialization/socket-transfer.ts) - Handle passing with network sockets
692
+ - [Error Preservation](./examples/advanced-serialization/error-preservation.ts) - Full Error object preservation
693
+ - [Migration Guide](./examples/advanced-serialization/migration-guide.md) - Step-by-step migration from JSON mode
694
+
695
+ ### Performance Considerations
696
+
697
+ Advanced serialization has a small overhead compared to JSON for simple objects, but enables much broader type support. See [Performance Benchmarks](#-performance) for detailed comparisons.
698
+
699
+ ```typescript
700
+ // Benchmark results (see benchmark/serialization-comparison.ts)
701
+ // Simple object (100 calls):
702
+ // JSON mode: 0.12ms average
703
+ // Advanced mode: 0.15ms average (~25% slower)
704
+ //
705
+ // Buffer data (100 calls):
706
+ // JSON mode: Not supported
707
+ // Advanced mode: 0.18ms average
708
+ //
709
+ // Map with 100 entries (100 calls):
710
+ // JSON mode: Not supported
711
+ // Advanced mode: 0.25ms average
712
+ ```
713
+
459
714
  ## 🔍 Limitations
460
715
 
461
- 1. **JSON Serialization** - Method arguments and return values must be JSON-serializable (functions are proxied as callbacks)
462
- 2. **Properties Read-Only on Parent** - Parent can only read properties, modifications must be done via child methods
463
- 3. **One-Way Events** - EventEmitter events only flow from child to parent (emit only works in child)
464
- 4. **Callback Context** - Callbacks are invoked with serialized arguments (e.g., no `this` binding)
716
+ Understanding these constraints will help you use procxy effectively:
717
+
718
+ 1. **Serialization Requirements**
719
+ - **JSON mode (default)**: Method arguments and return values must be JSON-serializable
720
+ - **Advanced mode**: Supports V8 structured clone types (Buffer, Map, Set, BigInt, etc.)
721
+ - Functions are automatically proxied as callbacks (no manual serialization needed)
722
+ - **JSON mode**: Circular references and symbols are not supported
723
+ - **Advanced mode**: Circular references are supported via V8 structured clone; symbols are not supported
724
+
725
+ 2. **Parent Properties Are Read-Only**
726
+ - Parent process can only **read** properties (via local synchronized store)
727
+ - Child process can **read and write** properties
728
+ - Modifications must be done via child methods, not direct assignment on parent
729
+
730
+ 3. **One-Way Event Flow**
731
+ - EventEmitter events flow from child → parent only
732
+ - Parent can listen to events, but cannot emit events to the child
733
+ - The child process owns the EventEmitter instance
734
+
735
+ 4. **Callback Context Limitations**
736
+ - Callbacks are invoked with serialized arguments
737
+ - No `this` binding preservation across process boundaries
738
+ - Callback functions cannot access closure variables from the other process
465
739
 
466
740
  ## 🧪 Testing
467
741
 
@@ -487,27 +761,96 @@ pnpm test tests/integration/basic-invocation.test.ts
487
761
 
488
762
  ### Module Resolution Errors
489
763
 
490
- If you get `ModuleResolutionError`, ensure the `modulePath` argument points to the correct file:
764
+ If you get `ModuleResolutionError`, ensure you have a static import or provide an explicit `modulePath`:
491
765
 
492
766
  ```typescript
493
- // ✅ Correct - explicit path
767
+ // ✅ Best - automatic resolution with static import
768
+ import { Worker } from './worker.js';
769
+ await procxy(Worker);
770
+
771
+ // ✅ Also works - explicit path
494
772
  await procxy(Worker, './worker.js');
495
- await procxy(Worker, '/absolute/path/to/worker.js');
496
773
 
497
- // ❌ Wrong - missing module path
498
- await procxy(Worker); // Error!
774
+ // ❌ Won't work - dynamic import without explicit path
775
+ const { Worker } = await import('./worker.js');
776
+ await procxy(Worker); // Error: Cannot resolve module path!
777
+
778
+ // ✅ Fix - provide explicit path with dynamic import
779
+ const { Worker } = await import('./worker.js');
780
+ await procxy(Worker, './worker.js');
499
781
  ```
500
782
 
501
783
  ### Serialization Errors
502
784
 
503
- Ensure all arguments and return values are JSON-serializable:
785
+ Ensure all arguments and return values are serializable for your chosen mode:
504
786
 
787
+ **JSON Mode:**
505
788
  ```typescript
506
789
  // ✅ OK
507
790
  await proxy.process({ name: 'test', count: 42 });
508
791
 
509
792
  // ❌ Not OK - contains function
510
793
  await proxy.process({ name: 'test', fn: () => {} });
794
+
795
+ // ❌ Not OK - Buffer requires advanced mode
796
+ await proxy.processImage(Buffer.from('data'));
797
+ ```
798
+
799
+ **Advanced Mode:**
800
+ ```typescript
801
+ // Enable advanced mode
802
+ const proxy = await procxy<Worker, 'advanced'>(
803
+ Worker,
804
+ { serialization: 'advanced' }
805
+ );
806
+
807
+ // ✅ Now OK - Buffer is supported
808
+ await proxy.processImage(Buffer.from('data'));
809
+
810
+ // ✅ OK - Map and Set supported
811
+ await proxy.processMap(new Map([['key', 'value']]));
812
+ await proxy.processSet(new Set([1, 2, 3]));
813
+
814
+ // ✅ OK - BigInt supported
815
+ await proxy.calculate(123456789n);
816
+ ```
817
+
818
+ ### Type Inference Issues
819
+
820
+ When using advanced serialization, ensure the type parameter matches the option:
821
+
822
+ ```typescript
823
+ // ✅ Correct - type parameter matches serialization option
824
+ const worker = await procxy<Worker, 'advanced'>(
825
+ Worker,
826
+ { serialization: 'advanced' }
827
+ );
828
+
829
+ // ❌ Wrong - type mismatch will cause TypeScript errors
830
+ const worker = await procxy<Worker, 'json'>(
831
+ Worker,
832
+ { serialization: 'advanced' } // TypeScript error!
833
+ );
834
+ ```
835
+
836
+ ### Handle Passing Issues
837
+
838
+ If handle passing doesn't work:
839
+
840
+ ```typescript
841
+ // ✅ Ensure both advanced mode AND supportHandles are enabled with 'as const'
842
+ const handler = await procxy(Handler, {
843
+ serialization: 'advanced',
844
+ supportHandles: true // Required!
845
+ } as const); // 'as const' ensures TypeScript infers supportHandles: true
846
+
847
+ // ✅ Now $sendHandle is available in TypeScript autocomplete
848
+ await handler.$sendHandle(socket);
849
+
850
+ // ✅ Check platform - Windows has limited support
851
+ if (process.platform === 'win32') {
852
+ console.warn('Handle passing may not work on Windows');
853
+ }
511
854
  ```
512
855
 
513
856
  ### Timeout Issues
@@ -515,7 +858,10 @@ await proxy.process({ name: 'test', fn: () => {} });
515
858
  Increase timeout for long-running methods:
516
859
 
517
860
  ```typescript
518
- const worker = await procxy(Worker, './worker.js', {
861
+ import { procxy } from 'procxy';
862
+ import { Worker } from './worker.js';
863
+
864
+ const worker = await procxy(Worker, {
519
865
  timeout: 300000 // 5 minutes
520
866
  });
521
867
  ```
@@ -589,17 +935,10 @@ class Task extends EventEmitter<Events> {
589
935
  ### Data Processing Pipeline
590
936
 
591
937
  ```typescript
592
- class ImageProcessor {
593
- async resize(data: Buffer, width: number): Promise<Buffer> {
594
- // Heavy computation in isolated process
595
- }
596
-
597
- async compress(data: Buffer, quality: number): Promise<Buffer> {
598
- // Another heavy operation
599
- }
600
- }
938
+ import { procxy } from 'procxy';
939
+ import { ImageProcessor } from './image-processor.js';
601
940
 
602
- await using processor = await procxy(ImageProcessor, './image-processor.js');
941
+ await using processor = await procxy(ImageProcessor);
603
942
 
604
943
  let image = await processor.resize(imageData, 800);
605
944
  image = await processor.compress(image, 85);
@@ -610,12 +949,15 @@ console.log('Processed:', image.length, 'bytes');
610
949
  ### Worker Pool Pattern
611
950
 
612
951
  ```typescript
952
+ import { procxy } from 'procxy';
953
+ import { Worker } from './worker.js';
954
+
613
955
  class WorkerPool {
614
956
  private workers: any[] = [];
615
957
 
616
958
  async initialize(poolSize: number): Promise<void> {
617
959
  for (let i = 0; i < poolSize; i++) {
618
- this.workers.push(await procxy(Worker, './worker.js'));
960
+ this.workers.push(await procxy(Worker));
619
961
  }
620
962
  }
621
963
 
@@ -633,13 +975,10 @@ class WorkerPool {
633
975
  ### Real-Time Data Streaming
634
976
 
635
977
  ```typescript
636
- class DataStream extends EventEmitter {
637
- async startStream(filter: (x: any) => boolean): Promise<void> {
638
- // Start streaming data
639
- }
640
- }
978
+ import { procxy } from 'procxy';
979
+ import { DataStream } from './stream.js';
641
980
 
642
- const stream = await procxy(DataStream, './stream.js');
981
+ const stream = await procxy(DataStream);
643
982
 
644
983
  let dataCount = 0;
645
984
  stream.on('data', (chunk) => {
@@ -657,27 +996,10 @@ await stream.startStream(x => x.value > 100);
657
996
  ### Batch Processing with Progress
658
997
 
659
998
  ```typescript
660
- class BatchProcessor {
661
- async processBatch(
662
- items: string[],
663
- onProgress: (processed: number, total: number, item: string) => void
664
- ): Promise<string[]> {
665
- const results = [];
666
- for (let i = 0; i < items.length; i++) {
667
- const result = await this.heavyProcess(items[i]);
668
- results.push(result);
669
- onProgress(i + 1, items.length, items[i]);
670
- }
671
- return results;
672
- }
673
-
674
- private async heavyProcess(item: string): Promise<string> {
675
- // CPU-intensive work
676
- return item.toUpperCase();
677
- }
678
- }
999
+ import { procxy } from 'procxy';
1000
+ import { BatchProcessor } from './batch-processor.js';
679
1001
 
680
- const processor = await procxy(BatchProcessor, './batch-processor.js');
1002
+ const processor = await procxy(BatchProcessor);
681
1003
 
682
1004
  const results = await processor.processBatch(
683
1005
  ['item1', 'item2', 'item3'],
@@ -694,26 +1016,12 @@ console.log('Results:', results);
694
1016
  Use static factory methods or wrapper functions for cleaner initialization:
695
1017
 
696
1018
  ```typescript
697
- class Database {
698
- private connectionString: string = '';
699
- private connected: boolean = false;
700
-
701
- async connect(url: string): Promise<void> {
702
- this.connectionString = url;
703
- this.connected = true;
704
- // Simulate connection setup
705
- await new Promise(r => setTimeout(r, 100));
706
- }
707
-
708
- async query(sql: string): Promise<any[]> {
709
- if (!this.connected) throw new Error('Not connected');
710
- return [{ result: 'example' }];
711
- }
712
- }
1019
+ import { procxy } from 'procxy';
1020
+ import { Database } from './database.js';
713
1021
 
714
1022
  // Factory function for cleaner API
715
1023
  async function createDatabase(url: string) {
716
- const db = await procxy(Database, './database.js');
1024
+ const db = await procxy(Database);
717
1025
  await db.connect(url);
718
1026
  return db;
719
1027
  }
@@ -729,27 +1037,32 @@ await db.$terminate();
729
1037
  Build complex instances with fluent API and async setup:
730
1038
 
731
1039
  ```typescript
1040
+ import { procxy } from 'procxy';
1041
+ import { Worker } from './worker.js';
1042
+
732
1043
  class WorkerBuilder {
733
1044
  private name: string = 'Worker';
734
1045
  private threads: number = 1;
735
1046
  private timeout: number = 30000;
736
1047
 
737
- setName(name: string): void {
1048
+ setName(name: string): this {
738
1049
  this.name = name;
1050
+ return this;
739
1051
  }
740
1052
 
741
- setThreads(threads: number): void {
1053
+ setThreads(threads: number): this {
742
1054
  this.threads = threads;
1055
+ return this;
743
1056
  }
744
1057
 
745
- setTimeout(ms: number): void {
1058
+ setTimeout(ms: number): this {
746
1059
  this.timeout = ms;
1060
+ return this;
747
1061
  }
748
1062
 
749
1063
  async build(): Promise<Procxy<Worker>> {
750
1064
  const worker = await procxy(
751
1065
  Worker,
752
- './worker.js',
753
1066
  { timeout: this.timeout },
754
1067
  this.name,
755
1068
  this.threads
@@ -776,6 +1089,9 @@ await worker.processImage(imageData);
776
1089
  Manage a pool of async-initialized resources:
777
1090
 
778
1091
  ```typescript
1092
+ import { procxy } from 'procxy';
1093
+ import { Worker } from './worker.js';
1094
+
779
1095
  class AsyncResourcePool<T> {
780
1096
  private available: Procxy<T>[] = [];
781
1097
  private inUse = new Set<Procxy<T>>();
@@ -813,7 +1129,7 @@ class AsyncResourcePool<T> {
813
1129
 
814
1130
  // Usage
815
1131
  async function createWorker() {
816
- return await procxy(Worker, './worker.js');
1132
+ return await procxy(Worker);
817
1133
  }
818
1134
 
819
1135
  const pool = new AsyncResourcePool<Worker>();
@@ -836,13 +1152,16 @@ await pool.shutdown();
836
1152
  Create and cache instances on first use:
837
1153
 
838
1154
  ```typescript
1155
+ import { procxy } from 'procxy';
1156
+ import { Worker } from './worker.js';
1157
+
839
1158
  class LazyWorkerSingleton {
840
1159
  private static instance: Procxy<Worker> | null = null;
841
1160
 
842
1161
  static async getInstance(): Promise<Procxy<Worker>> {
843
1162
  if (!this.instance) {
844
1163
  console.log('Initializing worker...');
845
- this.instance = await procxy(Worker, './worker.js');
1164
+ this.instance = await procxy(Worker);
846
1165
 
847
1166
  // Set up cleanup on process exit
848
1167
  process.on('exit', async () => {
@@ -876,6 +1195,10 @@ await LazyWorkerSingleton.reset();
876
1195
  Resolve dependencies asynchronously before using proxies:
877
1196
 
878
1197
  ```typescript
1198
+ import { procxy } from 'procxy';
1199
+ import { Database } from './database.js';
1200
+ import { Cache } from './cache.js';
1201
+
879
1202
  class ServiceContainer {
880
1203
  private services = new Map<string, any>();
881
1204
 
@@ -902,13 +1225,8 @@ class ServiceContainer {
902
1225
  // Setup
903
1226
  const container = new ServiceContainer();
904
1227
 
905
- await container.register('database', () =>
906
- procxy(Database, './database.js')
907
- );
908
-
909
- await container.register('cache', () =>
910
- procxy(Cache, './cache.js')
911
- );
1228
+ await container.register('database', () => procxy(Database));
1229
+ await container.register('cache', () => procxy(Cache));
912
1230
 
913
1231
  // Usage with injected dependencies
914
1232
  const db = container.get<Database>('database');