procxy 0.1.0-alpha.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 (55) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/README.md +936 -0
  3. package/dist/child/agent.d.ts +3 -0
  4. package/dist/child/agent.d.ts.map +1 -0
  5. package/dist/child/agent.js +126 -0
  6. package/dist/child/agent.js.map +1 -0
  7. package/dist/child/child-proxy.d.ts +23 -0
  8. package/dist/child/child-proxy.d.ts.map +1 -0
  9. package/dist/child/child-proxy.js +193 -0
  10. package/dist/child/child-proxy.js.map +1 -0
  11. package/dist/child/event-bridge.d.ts +15 -0
  12. package/dist/child/event-bridge.d.ts.map +1 -0
  13. package/dist/child/event-bridge.js +51 -0
  14. package/dist/child/event-bridge.js.map +1 -0
  15. package/dist/index.d.ts +11 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +8 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/parent/ipc-client.d.ts +31 -0
  20. package/dist/parent/ipc-client.d.ts.map +1 -0
  21. package/dist/parent/ipc-client.js +316 -0
  22. package/dist/parent/ipc-client.js.map +1 -0
  23. package/dist/parent/parent-proxy.d.ts +4 -0
  24. package/dist/parent/parent-proxy.d.ts.map +1 -0
  25. package/dist/parent/parent-proxy.js +76 -0
  26. package/dist/parent/parent-proxy.js.map +1 -0
  27. package/dist/parent/procxy.d.ts +6 -0
  28. package/dist/parent/procxy.d.ts.map +1 -0
  29. package/dist/parent/procxy.js +120 -0
  30. package/dist/parent/procxy.js.map +1 -0
  31. package/dist/shared/errors.d.ts +31 -0
  32. package/dist/shared/errors.d.ts.map +1 -0
  33. package/dist/shared/errors.js +85 -0
  34. package/dist/shared/errors.js.map +1 -0
  35. package/dist/shared/module-resolver.d.ts +6 -0
  36. package/dist/shared/module-resolver.d.ts.map +1 -0
  37. package/dist/shared/module-resolver.js +99 -0
  38. package/dist/shared/module-resolver.js.map +1 -0
  39. package/dist/shared/protocol.d.ts +86 -0
  40. package/dist/shared/protocol.d.ts.map +1 -0
  41. package/dist/shared/protocol.js +2 -0
  42. package/dist/shared/protocol.js.map +1 -0
  43. package/dist/shared/serialization.d.ts +20 -0
  44. package/dist/shared/serialization.d.ts.map +1 -0
  45. package/dist/shared/serialization.js +96 -0
  46. package/dist/shared/serialization.js.map +1 -0
  47. package/dist/types/options.d.ts +9 -0
  48. package/dist/types/options.d.ts.map +1 -0
  49. package/dist/types/options.js +2 -0
  50. package/dist/types/options.js.map +1 -0
  51. package/dist/types/procxy.d.ts +50 -0
  52. package/dist/types/procxy.d.ts.map +1 -0
  53. package/dist/types/procxy.js +2 -0
  54. package/dist/types/procxy.js.map +1 -0
  55. package/package.json +87 -0
package/README.md ADDED
@@ -0,0 +1,936 @@
1
+ # Procxy ๐Ÿš€
2
+
3
+ > **procxy** */หˆprษ’k.si/* โ€” Transparent and type-safe process-based proxy for class instances
4
+
5
+ [![npm version](https://img.shields.io/npm/v/procxy.svg)](https://www.npmjs.com/package/procxy)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+ [![Tests](https://img.shields.io/badge/tests-243%20passing-brightgreen.svg)](./tests)
9
+ [![Coverage](https://img.shields.io/badge/coverage->90%25-brightgreen.svg)](./coverage)
10
+ [![Performance](https://img.shields.io/badge/overhead-<10ms-blueviolet.svg)](./README.md#-performance)
11
+ [![GitHub Sponsors](https://img.shields.io/badge/Sponsor-%E2%9D%A4-pink.svg)](https://github.com/sponsors/pradeepmouli)
12
+
13
+ > โš ๏ธ **Alpha Release** - API may change. Please report issues and provide feedback!
14
+
15
+ Run class instances in isolated child processes while interacting with them as if they were local objects. All method calls become async and are transparently forwarded over IPC with full TypeScript support.
16
+
17
+ ## โœจ Features
18
+
19
+ - **๐ŸŽฏ Type-Safe** - Full TypeScript support with IntelliSense autocomplete
20
+ - **๐Ÿช„ Automagic Module Resolution** - Zero-config import path detection from your source code
21
+ - **โšก Fast** - <10ms overhead per method call
22
+ - **๐Ÿ”„ Events & Callbacks** - Transparent EventEmitter forwarding and bidirectional callback support
23
+ - **๐Ÿ  Properties** - Read-only properties on parent, full read/write on child
24
+ - **๐Ÿ›ก๏ธ Error Handling** - Complete error propagation with stack traces
25
+ - **๐Ÿงน Lifecycle** - Automatic cleanup with disposable protocol (`using`/`await using`)
26
+ - **โš™๏ธ Configurable** - Timeouts, retries, custom environment, working directory
27
+ - **๐Ÿ“ฆ Zero Dependencies** - Minimal bundle size (<50KB)
28
+ - **๐Ÿงช Well Tested** - See above
29
+
30
+ ## ๐Ÿ“ฆ Installation
31
+
32
+ ```bash
33
+ npm install procxy
34
+ ```
35
+
36
+ ```bash
37
+ pnpm add procxy
38
+ ```
39
+
40
+ ```bash
41
+ yarn add procxy
42
+ ```
43
+
44
+ ## ๐Ÿš€ Quick Start
45
+
46
+ ### Basic Usage
47
+
48
+ ```typescript
49
+ import { procxy } from 'procxy';
50
+ import { Calculator } from './calculator.js';
51
+
52
+ // Automatic module path detection (recommended)
53
+ const calc = await procxy(Calculator);
54
+
55
+ // Call methods (now async!)
56
+ const sum = await calc.add(5, 3); // 8
57
+ const product = await calc.multiply(4, 7); // 28
58
+
59
+ // Clean up
60
+ await calc.$terminate();
61
+
62
+ // Or with explicit module path (needed for dynamic imports)
63
+ const calc2 = await procxy(Calculator, './calculator.js');
64
+ ```
65
+
66
+ ### Using Disposables (Recommended)
67
+
68
+ ```typescript
69
+ import { Calculator } from './calculator.js';
70
+
71
+ // Automatic cleanup with await using
72
+ await using calc = await procxy(Calculator);
73
+ const result = await calc.add(5, 3);
74
+ // Automatically terminated when scope exits
75
+ ```
76
+
77
+ ### Constructor Arguments
78
+
79
+ ```typescript
80
+ import { Worker } from './worker.js';
81
+
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
+ }
90
+
91
+ // Pass constructor arguments after options
92
+ const worker = await procxy(
93
+ Worker,
94
+ './worker.js', // Can be omitted if Worker is imported
95
+ undefined, // options (or omit)
96
+ 'MyWorker', // name argument
97
+ 4 // threads argument
98
+ );
99
+
100
+ const result = await worker.process(['hello', 'world']);
101
+ // ['HELLO', 'WORLD']
102
+
103
+ await worker.$terminate();
104
+ ```
105
+
106
+ ## ๐Ÿช„ Automagic Module Resolution
107
+
108
+ 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 figures it out by parsing your import statements at runtime.
109
+
110
+ ### How It Works
111
+
112
+ When you call `procxy(MyClass)`, the library:
113
+
114
+ 1. **Inspects the call stack** to find where `procxy()` was called
115
+ 2. **Reads your source file** and parses import/require statements
116
+ 3. **Matches the class name** to find the corresponding import path
117
+ 4. **Resolves the absolute path** for the child process
118
+
119
+ ```typescript
120
+ // Your code
121
+ import { Calculator } from './math/calculator.js';
122
+ import { DataProcessor } from '@myorg/data-processor';
123
+
124
+ // Just works! No modulePath needed
125
+ const calc = await procxy(Calculator);
126
+ const processor = await procxy(DataProcessor);
127
+ ```
128
+
129
+ ### Supported Import Styles
130
+
131
+ Procxy can detect module paths from:
132
+
133
+ ```typescript
134
+ // โœ… ESM named imports
135
+ import { MyClass } from './path/to/module.js';
136
+
137
+ // โœ… ESM default imports
138
+ import MyClass from './path/to/module.js';
139
+
140
+ // โœ… CommonJS requires
141
+ const { MyClass } = require('./path/to/module');
142
+
143
+ // โœ… Classes defined in the same file
144
+ class MyClass { ... }
145
+ const instance = await procxy(MyClass);
146
+ ```
147
+
148
+ ### When to Use Explicit Paths
149
+
150
+ There are cases where auto-detection won't work:
151
+
152
+ ```typescript
153
+ // โŒ Dynamic imports - must provide explicit path
154
+ const { Worker } = await import('./worker.js');
155
+ const worker = await procxy(Worker, './worker.js');
156
+
157
+ // โŒ Re-exported classes with different names
158
+ import { OriginalClass as AliasedClass } from './module.js';
159
+ const instance = await procxy(AliasedClass, './module.js');
160
+
161
+ // โŒ Classes from complex barrel exports
162
+ import { DeepNestedClass } from './barrel/index.js';
163
+ const instance = await procxy(DeepNestedClass, './barrel/deep/nested.js');
164
+ ```
165
+
166
+ ### Best Practices
167
+
168
+ ```typescript
169
+ // ๐Ÿ‘ Recommended: Let procxy detect the path
170
+ import { Calculator } from './calculator.js';
171
+ const calc = await procxy(Calculator);
172
+
173
+ // ๐Ÿ‘ Also good: Explicit path when needed
174
+ const calc = await procxy(Calculator, './calculator.js');
175
+
176
+ // ๐Ÿ‘Ž Avoid: Using anonymous classes (name required for resolution)
177
+ const calc = await procxy(class { add(a, b) { return a + b; } });
178
+ // Throws: Constructor must be a named class
179
+ ```
180
+
181
+ ## ๐Ÿ“š API Reference
182
+
183
+ ### `procxy<T>(Class, modulePath?, options?, ...constructorArgs)`
184
+
185
+ Creates a process-based proxy for a class instance.
186
+
187
+ **Parameters:**
188
+
189
+ - `Class: Constructor<T>` - The class constructor to instantiate
190
+ - `modulePath?: string` - **Optional** path to the module containing the class (auto-detected if omitted)
191
+ - `options?: ProcxyOptions` - Optional configuration
192
+ - `...constructorArgs` - Arguments for the class constructor
193
+
194
+ **Returns:** `Promise<Procxy<T>>` - Proxy object with all methods transformed to async
195
+
196
+ **Module Path Auto-Detection:**
197
+
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!
202
+
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
+ ```
228
+
229
+ ### `Procxy<T>` Type
230
+
231
+ The proxy type that wraps your class instance:
232
+
233
+ - **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
237
+ - **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
241
+
242
+ ### `ProcxyOptions`
243
+
244
+ Configuration options:
245
+
246
+ ```typescript
247
+ 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
253
+ }
254
+ ```
255
+
256
+ ## ๐ŸŽฏ Use Cases
257
+
258
+ ### CPU-Intensive Tasks
259
+
260
+ Offload heavy computations without blocking the event loop:
261
+
262
+ ```typescript
263
+ class ImageProcessor {
264
+ async resize(image: Buffer, width: number): Promise<Buffer> {
265
+ // Heavy image processing
266
+ }
267
+ }
268
+
269
+ await using processor = await procxy(ImageProcessor, './image-processor.js');
270
+ const resized = await processor.resize(imageData, 800);
271
+ ```
272
+
273
+ ### Isolated Execution
274
+
275
+ Run untrusted code in isolated processes:
276
+
277
+ ```typescript
278
+ class SandboxedRunner {
279
+ async execute(code: string): Promise<any> {
280
+ // Execute in isolated environment
281
+ }
282
+ }
283
+
284
+ const sandbox = await procxy(SandboxedRunner, './sandbox.js', {
285
+ timeout: 5000, // Kill after 5s
286
+ env: { NODE_ENV: 'sandbox' }
287
+ });
288
+ ```
289
+
290
+ ### EventEmitter Support
291
+
292
+ Classes extending EventEmitter work transparently:
293
+
294
+ ```typescript
295
+ import { EventEmitter } from 'events';
296
+
297
+ class DataStream extends EventEmitter {
298
+ async start(): Promise<void> {
299
+ // Stream data and emit events
300
+ this.emit('data', { chunk: 'example' });
301
+ }
302
+ }
303
+
304
+ const stream = await procxy(DataStream, './stream.js');
305
+
306
+ stream.on('data', (chunk) => {
307
+ console.log('Received:', chunk);
308
+ });
309
+
310
+ await stream.start();
311
+ ```
312
+
313
+ ### Callback Support
314
+
315
+ Pass callbacks as function parameters and they'll be transparently proxied across the process boundary:
316
+
317
+ ```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');
333
+
334
+ const result = await worker.processWithCallback(
335
+ ['hello', 'world'],
336
+ (current, total) => {
337
+ console.log(`Progress: ${current}/${total}`);
338
+ }
339
+ );
340
+
341
+ // Output:
342
+ // Progress: 1/2
343
+ // Progress: 2/2
344
+ ```
345
+
346
+ **How Callbacks Work:**
347
+ - Callback functions are serialized as callback IDs
348
+ - A registry maintains references in both processes
349
+ - IPC messages proxy callback invocations
350
+ - Automatic cleanup when the process terminates
351
+ - Bidirectional: parent can pass callbacks to child, child can invoke them
352
+
353
+ ### Properties Support
354
+
355
+ Public properties are accessible as read-only on the parent and can be read/modified on the child:
356
+
357
+ ```typescript
358
+ class Counter {
359
+ public count: number = 0;
360
+ public name: string = '';
361
+
362
+ increment(): void {
363
+ this.count++;
364
+ }
365
+
366
+ setName(newName: string): void {
367
+ this.name = newName;
368
+ }
369
+
370
+ getCount(): number {
371
+ return this.count;
372
+ }
373
+ }
374
+
375
+ const counter = await procxy(Counter, './counter.js');
376
+
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
380
+
381
+ // Modify via child methods
382
+ await counter.increment();
383
+ await counter.setName('MyCounter');
384
+
385
+ // Parent reads updated values
386
+ console.log(counter.count); // 1 - automatically synced from child
387
+ console.log(counter.name); // 'MyCounter' - automatically synced
388
+ ```
389
+
390
+ **Property Synchronization:**
391
+ - 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)
396
+ - No race conditions: only the child can modify properties
397
+
398
+ ## ๐Ÿ›ก๏ธ Error Handling
399
+
400
+ All errors from the child process are propagated with full stack traces:
401
+
402
+ ```typescript
403
+ import { TimeoutError, ChildCrashedError } from 'procxy';
404
+
405
+ try {
406
+ await proxy.slowMethod();
407
+ } catch (err) {
408
+ if (err instanceof TimeoutError) {
409
+ console.log('Timeout after', err.timeoutMs, 'ms');
410
+ } else if (err instanceof ChildCrashedError) {
411
+ console.log('Child crashed with code:', err.exitCode);
412
+ } else {
413
+ console.log('Remote error:', err.message);
414
+ console.log('Stack trace:', err.stack);
415
+ }
416
+ }
417
+ ```
418
+
419
+ ### Error Types
420
+
421
+ - `ProcxyError` - Base error class
422
+ - `TimeoutError` - Method call exceeded timeout
423
+ - `ChildCrashedError` - Child process exited unexpectedly
424
+ - `ModuleResolutionError` - Cannot find or load the module
425
+ - `SerializationError` - Arguments/return values not JSON-serializable
426
+ - `OptionsValidationError` - Invalid configuration options
427
+
428
+ ## โš™๏ธ Configuration
429
+
430
+ ### Timeouts and Retries
431
+
432
+ ```typescript
433
+ const worker = await procxy(Worker, './worker.js', {
434
+ timeout: 60000, // 60 seconds per call
435
+ retries: 5 // Retry 5 times before failing
436
+ });
437
+ ```
438
+
439
+ ### Custom Environment
440
+
441
+ ```typescript
442
+ const worker = await procxy(Worker, './worker.js', {
443
+ env: {
444
+ NODE_ENV: 'production',
445
+ API_KEY: process.env.API_KEY,
446
+ LOG_LEVEL: 'debug'
447
+ }
448
+ });
449
+ ```
450
+
451
+ ### Working Directory
452
+
453
+ ```typescript
454
+ const worker = await procxy(Worker, './worker.js', {
455
+ cwd: '/tmp/workspace'
456
+ });
457
+ ```
458
+
459
+ ## ๐Ÿ” Limitations
460
+
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)
465
+
466
+ ## ๐Ÿงช Testing
467
+
468
+ ```bash
469
+ # Run all tests
470
+ pnpm test
471
+
472
+ # Run tests with coverage
473
+ pnpm test:coverage
474
+
475
+ # Run specific test file
476
+ pnpm test tests/integration/basic-invocation.test.ts
477
+ ```
478
+
479
+ ## ๐Ÿ“Š Performance
480
+
481
+ - **Method call overhead**: <10ms average
482
+ - **Memory overhead**: <1MB per instance
483
+ - **Bundle size**: <50KB minified
484
+ - **Test coverage**: >90%
485
+
486
+ ## ๐Ÿ”ง Troubleshooting
487
+
488
+ ### Module Resolution Errors
489
+
490
+ If you get `ModuleResolutionError`, ensure the `modulePath` argument points to the correct file:
491
+
492
+ ```typescript
493
+ // โœ… Correct - explicit path
494
+ await procxy(Worker, './worker.js');
495
+ await procxy(Worker, '/absolute/path/to/worker.js');
496
+
497
+ // โŒ Wrong - missing module path
498
+ await procxy(Worker); // Error!
499
+ ```
500
+
501
+ ### Serialization Errors
502
+
503
+ Ensure all arguments and return values are JSON-serializable:
504
+
505
+ ```typescript
506
+ // โœ… OK
507
+ await proxy.process({ name: 'test', count: 42 });
508
+
509
+ // โŒ Not OK - contains function
510
+ await proxy.process({ name: 'test', fn: () => {} });
511
+ ```
512
+
513
+ ### Timeout Issues
514
+
515
+ Increase timeout for long-running methods:
516
+
517
+ ```typescript
518
+ const worker = await procxy(Worker, './worker.js', {
519
+ timeout: 300000 // 5 minutes
520
+ });
521
+ ```
522
+
523
+ ## ๐Ÿค Contributing
524
+
525
+ Contributions welcome! Please read [CONTRIBUTING.md](./CONTRIBUTING.md) first.
526
+
527
+ ## ๐Ÿš€ Future Enhancements
528
+
529
+ We're planning several exciting features for future releases:
530
+
531
+ ### Computed Properties
532
+
533
+ Support for getters/setters with custom logic:
534
+
535
+ ```typescript
536
+ // This will be possible in a future release
537
+ class Temperature {
538
+ private _celsius: number = 0;
539
+
540
+ get fahrenheit(): number {
541
+ return (this._celsius * 9/5) + 32; // Computed property
542
+ }
543
+
544
+ set fahrenheit(value: number) {
545
+ this._celsius = (value - 32) * 5/9;
546
+ }
547
+ }
548
+
549
+ const temp = await procxy(Temperature, './temperature.js');
550
+ console.log(temp.fahrenheit); // Computed value from child
551
+ ```
552
+
553
+ ### Async Property Access
554
+
555
+ Lazy-load or compute properties asynchronously:
556
+
557
+ ```typescript
558
+ class DataLoader {
559
+ async $loadConfig(): Promise<Config> {
560
+ // Load from disk/network
561
+ }
562
+ }
563
+
564
+ const loader = await procxy(DataLoader, './loader.js');
565
+ const config = await loader.$loadConfig();
566
+ ```
567
+
568
+ ### TypedEmitter Integration
569
+
570
+ Full support for strongly-typed EventEmitter patterns:
571
+
572
+ ```typescript
573
+ type Events = {
574
+ progress: [number, number]; // [current, total]
575
+ complete: [result: string];
576
+ };
577
+
578
+ class Task extends EventEmitter<Events> {
579
+ async run(): Promise<string> {
580
+ this.emit('progress', 1, 10);
581
+ // ...
582
+ return 'done';
583
+ }
584
+ }
585
+ ```
586
+
587
+ ## ๐Ÿ“š More Examples
588
+
589
+ ### Data Processing Pipeline
590
+
591
+ ```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
+ }
601
+
602
+ await using processor = await procxy(ImageProcessor, './image-processor.js');
603
+
604
+ let image = await processor.resize(imageData, 800);
605
+ image = await processor.compress(image, 85);
606
+
607
+ console.log('Processed:', image.length, 'bytes');
608
+ ```
609
+
610
+ ### Worker Pool Pattern
611
+
612
+ ```typescript
613
+ class WorkerPool {
614
+ private workers: any[] = [];
615
+
616
+ async initialize(poolSize: number): Promise<void> {
617
+ for (let i = 0; i < poolSize; i++) {
618
+ this.workers.push(await procxy(Worker, './worker.js'));
619
+ }
620
+ }
621
+
622
+ async execute(task: string): Promise<string> {
623
+ const worker = this.workers[Math.floor(Math.random() * this.workers.length)];
624
+ return await worker.processTask(task);
625
+ }
626
+
627
+ async shutdown(): Promise<void> {
628
+ await Promise.all(this.workers.map(w => w.$terminate()));
629
+ }
630
+ }
631
+ ```
632
+
633
+ ### Real-Time Data Streaming
634
+
635
+ ```typescript
636
+ class DataStream extends EventEmitter {
637
+ async startStream(filter: (x: any) => boolean): Promise<void> {
638
+ // Start streaming data
639
+ }
640
+ }
641
+
642
+ const stream = await procxy(DataStream, './stream.js');
643
+
644
+ let dataCount = 0;
645
+ stream.on('data', (chunk) => {
646
+ console.log('Chunk:', chunk);
647
+ dataCount++;
648
+ });
649
+
650
+ stream.on('complete', () => {
651
+ console.log('Total chunks:', dataCount);
652
+ });
653
+
654
+ await stream.startStream(x => x.value > 100);
655
+ ```
656
+
657
+ ### Batch Processing with Progress
658
+
659
+ ```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
+ }
679
+
680
+ const processor = await procxy(BatchProcessor, './batch-processor.js');
681
+
682
+ const results = await processor.processBatch(
683
+ ['item1', 'item2', 'item3'],
684
+ (processed, total, current) => {
685
+ console.log(`Processed ${processed}/${total} (${current})`);
686
+ }
687
+ );
688
+
689
+ console.log('Results:', results);
690
+ ```
691
+
692
+ ### Async Factory Pattern
693
+
694
+ Use static factory methods or wrapper functions for cleaner initialization:
695
+
696
+ ```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
+ }
713
+
714
+ // Factory function for cleaner API
715
+ async function createDatabase(url: string) {
716
+ const db = await procxy(Database, './database.js');
717
+ await db.connect(url);
718
+ return db;
719
+ }
720
+
721
+ // Usage
722
+ const db = await createDatabase('postgres://localhost/mydb');
723
+ const results = await db.query('SELECT * FROM users');
724
+ await db.$terminate();
725
+ ```
726
+
727
+ ### Builder Pattern with Async Configuration
728
+
729
+ Build complex instances with fluent API and async setup:
730
+
731
+ ```typescript
732
+ class WorkerBuilder {
733
+ private name: string = 'Worker';
734
+ private threads: number = 1;
735
+ private timeout: number = 30000;
736
+
737
+ setName(name: string): void {
738
+ this.name = name;
739
+ }
740
+
741
+ setThreads(threads: number): void {
742
+ this.threads = threads;
743
+ }
744
+
745
+ setTimeout(ms: number): void {
746
+ this.timeout = ms;
747
+ }
748
+
749
+ async build(): Promise<Procxy<Worker>> {
750
+ const worker = await procxy(
751
+ Worker,
752
+ './worker.js',
753
+ { timeout: this.timeout },
754
+ this.name,
755
+ this.threads
756
+ );
757
+
758
+ // Async initialization
759
+ await worker.initialize();
760
+ return worker;
761
+ }
762
+ }
763
+
764
+ // Usage with fluent API
765
+ const worker = await new WorkerBuilder()
766
+ .setName('ImageProcessor')
767
+ .setThreads(4)
768
+ .setTimeout(60000)
769
+ .build();
770
+
771
+ await worker.processImage(imageData);
772
+ ```
773
+
774
+ ### Resource Pool with Async Initialization
775
+
776
+ Manage a pool of async-initialized resources:
777
+
778
+ ```typescript
779
+ class AsyncResourcePool<T> {
780
+ private available: Procxy<T>[] = [];
781
+ private inUse = new Set<Procxy<T>>();
782
+
783
+ async initialize(
784
+ factory: () => Promise<Procxy<T>>,
785
+ poolSize: number
786
+ ): Promise<void> {
787
+ for (let i = 0; i < poolSize; i++) {
788
+ const resource = await factory();
789
+ this.available.push(resource);
790
+ }
791
+ }
792
+
793
+ async acquire(): Promise<Procxy<T>> {
794
+ while (this.available.length === 0) {
795
+ // Wait for resource to be released
796
+ await new Promise(r => setTimeout(r, 10));
797
+ }
798
+ const resource = this.available.pop()!;
799
+ this.inUse.add(resource);
800
+ return resource;
801
+ }
802
+
803
+ release(resource: Procxy<T>): void {
804
+ this.inUse.delete(resource);
805
+ this.available.push(resource);
806
+ }
807
+
808
+ async shutdown(): Promise<void> {
809
+ const allResources = [...this.available, ...this.inUse];
810
+ await Promise.all(allResources.map(r => r.$terminate()));
811
+ }
812
+ }
813
+
814
+ // Usage
815
+ async function createWorker() {
816
+ return await procxy(Worker, './worker.js');
817
+ }
818
+
819
+ const pool = new AsyncResourcePool<Worker>();
820
+ await pool.initialize(createWorker, 5);
821
+
822
+ // Acquire and use
823
+ const worker = await pool.acquire();
824
+ try {
825
+ const result = await worker.process(data);
826
+ } finally {
827
+ pool.release(worker);
828
+ }
829
+
830
+ // Cleanup
831
+ await pool.shutdown();
832
+ ```
833
+
834
+ ### Lazy Initialization with Singleton Pattern
835
+
836
+ Create and cache instances on first use:
837
+
838
+ ```typescript
839
+ class LazyWorkerSingleton {
840
+ private static instance: Procxy<Worker> | null = null;
841
+
842
+ static async getInstance(): Promise<Procxy<Worker>> {
843
+ if (!this.instance) {
844
+ console.log('Initializing worker...');
845
+ this.instance = await procxy(Worker, './worker.js');
846
+
847
+ // Set up cleanup on process exit
848
+ process.on('exit', async () => {
849
+ if (this.instance) {
850
+ await this.instance.$terminate();
851
+ }
852
+ });
853
+ }
854
+ return this.instance;
855
+ }
856
+
857
+ static async reset(): Promise<void> {
858
+ if (this.instance) {
859
+ await this.instance.$terminate();
860
+ this.instance = null;
861
+ }
862
+ }
863
+ }
864
+
865
+ // Usage - only creates once
866
+ const worker1 = await LazyWorkerSingleton.getInstance();
867
+ const worker2 = await LazyWorkerSingleton.getInstance();
868
+ console.log(worker1 === worker2); // true - same instance
869
+
870
+ // Reset if needed
871
+ await LazyWorkerSingleton.reset();
872
+ ```
873
+
874
+ ### Dependency Injection with Async Resolution
875
+
876
+ Resolve dependencies asynchronously before using proxies:
877
+
878
+ ```typescript
879
+ class ServiceContainer {
880
+ private services = new Map<string, any>();
881
+
882
+ async register<T>(
883
+ name: string,
884
+ factory: () => Promise<Procxy<T>>
885
+ ): Promise<void> {
886
+ this.services.set(name, await factory());
887
+ }
888
+
889
+ get<T>(name: string): Procxy<T> {
890
+ const service = this.services.get(name);
891
+ if (!service) throw new Error(`Service '${name}' not found`);
892
+ return service;
893
+ }
894
+
895
+ async shutdown(): Promise<void> {
896
+ const promises = Array.from(this.services.values())
897
+ .map(service => service.$terminate?.());
898
+ await Promise.all(promises);
899
+ }
900
+ }
901
+
902
+ // Setup
903
+ const container = new ServiceContainer();
904
+
905
+ await container.register('database', () =>
906
+ procxy(Database, './database.js')
907
+ );
908
+
909
+ await container.register('cache', () =>
910
+ procxy(Cache, './cache.js')
911
+ );
912
+
913
+ // Usage with injected dependencies
914
+ const db = container.get<Database>('database');
915
+ const cache = container.get<Cache>('cache');
916
+
917
+ await db.query('SELECT ...');
918
+
919
+ // Cleanup all
920
+ await container.shutdown();
921
+ ```
922
+
923
+ ## ๐Ÿ“„ License
924
+
925
+ MIT
926
+
927
+ ## ๐Ÿ”— Links
928
+
929
+ - [Documentation](https://github.com/pradeepmouli/procxy)
930
+ - [npm Package](https://www.npmjs.com/package/procxy)
931
+ - [Issue Tracker](https://github.com/pradeepmouli/procxy/issues)
932
+ - [Changelog](./CHANGELOG.md)
933
+
934
+ ---
935
+
936
+ **Made with โค๏ธ by the Procxy team**