yuzuthread 1.0.2 → 1.0.4
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/AGENTS.md +1 -0
- package/README.md +443 -1
- package/dist/index.cjs +894 -89
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +5 -2
- package/dist/index.mjs +891 -88
- package/dist/index.mjs.map +4 -4
- package/dist/src/to-shared.d.ts +25 -0
- package/dist/src/utility/shared-decorator.d.ts +47 -0
- package/dist/src/utility/transport-metadata.d.ts +1 -0
- package/dist/src/utility/transport.d.ts +10 -0
- package/dist/src/utility/type-helpers.d.ts +41 -0
- package/dist/src/utility/typed-struct-registry.d.ts +78 -0
- package/dist/src/worker.d.ts +4 -0
- package/index.ts +12 -2
- package/package.json +1 -1
package/AGENTS.md
CHANGED
package/README.md
CHANGED
|
@@ -123,7 +123,9 @@ console.log(value); // -> 5
|
|
|
123
123
|
|
|
124
124
|
Use this when worker-side logic needs to call back into main-thread state or services.
|
|
125
125
|
|
|
126
|
-
## `typed-struct`
|
|
126
|
+
## Shared Memory with `typed-struct`
|
|
127
|
+
|
|
128
|
+
### Worker Class Shared Memory
|
|
127
129
|
|
|
128
130
|
If a worker class inherits from a compiled `typed-struct` class, `yuzuthread` automatically:
|
|
129
131
|
|
|
@@ -154,6 +156,197 @@ console.log(instance.value); // -> 0x7f
|
|
|
154
156
|
await instance.finalize();
|
|
155
157
|
```
|
|
156
158
|
|
|
159
|
+
### Shared Constructor Parameters with `@Shared`
|
|
160
|
+
|
|
161
|
+
Use `@Shared()` to mark constructor parameters that should use shared memory:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
import { Struct } from 'typed-struct';
|
|
165
|
+
import { DefineWorker, WorkerMethod, Shared, initWorker } from 'yuzuthread';
|
|
166
|
+
|
|
167
|
+
const SharedDataBase = new Struct('SharedData')
|
|
168
|
+
.UInt32LE('counter')
|
|
169
|
+
.compile();
|
|
170
|
+
|
|
171
|
+
class SharedData extends SharedDataBase {
|
|
172
|
+
declare counter: number;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@DefineWorker()
|
|
176
|
+
class SharedParamWorker {
|
|
177
|
+
constructor(
|
|
178
|
+
@Shared(() => SharedData) public data: SharedData,
|
|
179
|
+
) {}
|
|
180
|
+
|
|
181
|
+
@WorkerMethod()
|
|
182
|
+
increment() {
|
|
183
|
+
this.data.counter++;
|
|
184
|
+
return this.data.counter;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const data = new SharedData();
|
|
189
|
+
data.counter = 100;
|
|
190
|
+
|
|
191
|
+
const worker = await initWorker(SharedParamWorker, [data]);
|
|
192
|
+
|
|
193
|
+
// Main thread and worker share the same memory
|
|
194
|
+
await worker.increment();
|
|
195
|
+
console.log(worker.data.counter); // -> 101 (updated by worker)
|
|
196
|
+
|
|
197
|
+
data.counter = 200;
|
|
198
|
+
console.log(await worker.increment()); // -> 201 (sees main thread's change)
|
|
199
|
+
|
|
200
|
+
await worker.finalize();
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**How it works:**
|
|
204
|
+
- Parameters marked with `@Shared()` are converted to use `SharedArrayBuffer` during worker initialization
|
|
205
|
+
- The factory function `() => Type` is optional - if omitted, type is inferred from `design:paramtypes`
|
|
206
|
+
- The library calculates total memory needed (including worker class itself if typed-struct)
|
|
207
|
+
- Both main thread and worker thread share the same underlying memory
|
|
208
|
+
- Works with `Buffer`, `SharedArrayBuffer`, `typed-struct` classes, and user classes containing these
|
|
209
|
+
|
|
210
|
+
**Requirements:**
|
|
211
|
+
- The parameter type must contain shared memory segments (`typed-struct`, `Buffer`, or `SharedArrayBuffer`)
|
|
212
|
+
- If the type has no shared memory segments, an error is thrown
|
|
213
|
+
- Can be combined with worker class shared memory (worker class itself is typed-struct)
|
|
214
|
+
|
|
215
|
+
**Multiple shared parameters:**
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
@DefineWorker()
|
|
219
|
+
class MultiSharedWorker {
|
|
220
|
+
constructor(
|
|
221
|
+
@Shared(() => SharedData) public data1: SharedData,
|
|
222
|
+
@Shared(() => SharedData) public data2: SharedData,
|
|
223
|
+
@Shared(() => Buffer) public buffer: Buffer,
|
|
224
|
+
) {}
|
|
225
|
+
|
|
226
|
+
@WorkerMethod()
|
|
227
|
+
updateAll() {
|
|
228
|
+
this.data1.counter++;
|
|
229
|
+
this.data2.counter--;
|
|
230
|
+
this.buffer[0] = 0xff;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const data1 = new SharedData();
|
|
235
|
+
const data2 = new SharedData();
|
|
236
|
+
const buffer = Buffer.alloc(10);
|
|
237
|
+
|
|
238
|
+
const worker = await initWorker(MultiSharedWorker, [data1, data2, buffer]);
|
|
239
|
+
await worker.updateAll();
|
|
240
|
+
|
|
241
|
+
// All parameters are shared
|
|
242
|
+
console.log(data1.counter); // updated by worker
|
|
243
|
+
console.log(data2.counter); // updated by worker
|
|
244
|
+
console.log(buffer[0]); // -> 0xff
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Manual Shared Memory Conversion with `toShared()`
|
|
248
|
+
|
|
249
|
+
`toShared()` converts objects to use shared memory. **Important:** The object must contain shared memory segments to be converted:
|
|
250
|
+
- The class itself is a `typed-struct` class, OR
|
|
251
|
+
- The object has `Buffer` or `SharedArrayBuffer` fields, OR
|
|
252
|
+
- The object has fields (marked with `@TransportType()`) that are `typed-struct` classes
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
import { Struct } from 'typed-struct';
|
|
256
|
+
import { toShared, TransportType } from 'yuzuthread';
|
|
257
|
+
|
|
258
|
+
// Example 1: typed-struct class
|
|
259
|
+
const SharedDataBase = new Struct('SharedData')
|
|
260
|
+
.UInt32LE('counter')
|
|
261
|
+
.compile();
|
|
262
|
+
|
|
263
|
+
class SharedData extends SharedDataBase {
|
|
264
|
+
declare counter: number;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const data = new SharedData();
|
|
268
|
+
data.counter = 42;
|
|
269
|
+
|
|
270
|
+
const sharedData = toShared(data);
|
|
271
|
+
// sharedData is a NEW instance using SharedArrayBuffer
|
|
272
|
+
console.log(sharedData.counter); // -> 42
|
|
273
|
+
|
|
274
|
+
// Example 2: Buffer
|
|
275
|
+
const buffer = Buffer.from('hello');
|
|
276
|
+
const sharedBuffer = toShared(buffer);
|
|
277
|
+
// sharedBuffer is a NEW Buffer backed by SharedArrayBuffer
|
|
278
|
+
console.log(sharedBuffer.toString()); // -> 'hello'
|
|
279
|
+
|
|
280
|
+
// Example 3: User class with typed-struct field
|
|
281
|
+
class Container {
|
|
282
|
+
@TransportType(() => SharedData)
|
|
283
|
+
data!: SharedData;
|
|
284
|
+
|
|
285
|
+
label: string = '';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const container = new Container();
|
|
289
|
+
container.data = new SharedData();
|
|
290
|
+
container.data.counter = 100;
|
|
291
|
+
container.label = 'test';
|
|
292
|
+
|
|
293
|
+
toShared(container); // Converts container.data in-place
|
|
294
|
+
// container.data is now a different instance using SharedArrayBuffer
|
|
295
|
+
console.log(container.data.counter); // -> 100
|
|
296
|
+
console.log(container.label); // -> 'test' (unchanged)
|
|
297
|
+
|
|
298
|
+
// Example 4: User class with Buffer field
|
|
299
|
+
class BufferContainer {
|
|
300
|
+
@TransportType(() => Buffer)
|
|
301
|
+
buffer!: Buffer;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const bufferContainer = new BufferContainer();
|
|
305
|
+
bufferContainer.buffer = Buffer.from('data');
|
|
306
|
+
|
|
307
|
+
toShared(bufferContainer); // Converts bufferContainer.buffer in-place
|
|
308
|
+
// bufferContainer.buffer is now backed by SharedArrayBuffer
|
|
309
|
+
|
|
310
|
+
// Example 5: Complex nested structure
|
|
311
|
+
class NestedContainer {
|
|
312
|
+
@TransportType(() => Container)
|
|
313
|
+
container!: Container;
|
|
314
|
+
|
|
315
|
+
@TransportType(() => Buffer)
|
|
316
|
+
buffer!: Buffer;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const nested = new NestedContainer();
|
|
320
|
+
nested.container = new Container();
|
|
321
|
+
nested.container.data = new SharedData();
|
|
322
|
+
nested.container.data.counter = 200;
|
|
323
|
+
nested.buffer = Buffer.from('nested');
|
|
324
|
+
|
|
325
|
+
toShared(nested);
|
|
326
|
+
// Both nested.container.data and nested.buffer are now shared
|
|
327
|
+
console.log(nested.container.data.counter); // -> 200
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**How `toShared()` works:**
|
|
331
|
+
- **Buffer** → Creates a `SharedArrayBuffer` copy, returns new `Buffer` instance
|
|
332
|
+
- **SharedArrayBuffer** → Returns as-is (already shared)
|
|
333
|
+
- **typed-struct classes** → Creates new instance with `SharedArrayBuffer`
|
|
334
|
+
- **User classes** → Recursively converts fields marked with `@TransportType()` **in-place**
|
|
335
|
+
- **Arrays** → Converts each element in-place
|
|
336
|
+
- Built-in types (Date, RegExp, etc.) → Not supported, returns as-is
|
|
337
|
+
|
|
338
|
+
**Field conversion rules:**
|
|
339
|
+
- Only converts fields with `@TransportType()` decorator
|
|
340
|
+
- Or fields with `design:type` metadata (when `emitDecoratorMetadata` is enabled)
|
|
341
|
+
- Skips fields with manual encoders (`@TransportEncoder()`)
|
|
342
|
+
- Skips built-in types
|
|
343
|
+
|
|
344
|
+
**Important notes:**
|
|
345
|
+
- For `typed-struct` classes and `Buffer`, `toShared()` returns a **new instance** (cannot modify in-place)
|
|
346
|
+
- For user classes, `toShared()` modifies fields **in-place** (the object itself is the same, but its fields may be replaced)
|
|
347
|
+
- The object must contain at least one shared memory segment, otherwise it's returned unchanged
|
|
348
|
+
- Use `@TransportType()` to mark fields that should be recursively converted
|
|
349
|
+
|
|
157
350
|
## Custom Class Transport
|
|
158
351
|
|
|
159
352
|
By default, `worker_threads` can only pass serializable data (primitives, plain objects, `Buffer`, etc.). For custom classes, `yuzuthread` provides transport decorators to automatically serialize and deserialize instances.
|
|
@@ -309,6 +502,110 @@ Encoders support async operations:
|
|
|
309
502
|
- **Plain objects** - passed as-is
|
|
310
503
|
- **Custom classes** - require `@TransportType()` or `@TransportEncoder()`
|
|
311
504
|
|
|
505
|
+
### Typed Struct Classes
|
|
506
|
+
|
|
507
|
+
Classes that extend `typed-struct` are automatically detected and handled with special transport logic. The library separates struct fields (stored in the buffer) from regular fields:
|
|
508
|
+
|
|
509
|
+
```ts
|
|
510
|
+
import { Struct } from 'typed-struct';
|
|
511
|
+
import { DefineWorker, WorkerMethod, TransportType, initWorker } from 'yuzuthread';
|
|
512
|
+
|
|
513
|
+
const Base = new Struct('DataBase')
|
|
514
|
+
.UInt8('id')
|
|
515
|
+
.UInt32LE('counter')
|
|
516
|
+
.compile();
|
|
517
|
+
|
|
518
|
+
class MyData extends Base {
|
|
519
|
+
declare id: number;
|
|
520
|
+
declare counter: number;
|
|
521
|
+
extraField: string = '';
|
|
522
|
+
nestedData?: { name: string };
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
@DefineWorker()
|
|
526
|
+
class StructWorker {
|
|
527
|
+
@WorkerMethod()
|
|
528
|
+
@TransportType(() => MyData)
|
|
529
|
+
async processData(
|
|
530
|
+
@TransportType(() => MyData) data?: MyData,
|
|
531
|
+
): Promise<MyData> {
|
|
532
|
+
const result = new MyData();
|
|
533
|
+
if (data) {
|
|
534
|
+
result.id = data.id;
|
|
535
|
+
result.counter = data.counter + 1;
|
|
536
|
+
result.extraField = data.extraField + ' processed';
|
|
537
|
+
result.nestedData = data.nestedData;
|
|
538
|
+
} else {
|
|
539
|
+
result.id = 1;
|
|
540
|
+
result.counter = 0;
|
|
541
|
+
result.extraField = 'new';
|
|
542
|
+
}
|
|
543
|
+
return result;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const worker = await initWorker(StructWorker);
|
|
548
|
+
const data = await worker.processData();
|
|
549
|
+
console.log(data.id); // -> 1
|
|
550
|
+
console.log(data.counter); // -> 0
|
|
551
|
+
console.log(data.extraField); // -> 'new'
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
**How it works:**
|
|
555
|
+
- When encoding, the library dumps the struct buffer and encodes non-struct fields separately
|
|
556
|
+
- When decoding, it creates a new instance with the buffer, then restores non-struct fields
|
|
557
|
+
- All struct fields (defined by `typed-struct`) are preserved in the buffer
|
|
558
|
+
- Additional class fields are transported using the standard transport logic
|
|
559
|
+
|
|
560
|
+
**Expected usage pattern:**
|
|
561
|
+
```ts
|
|
562
|
+
class SomeDataClass extends new Struct()...compile() {
|
|
563
|
+
// Struct fields (declare them for TypeScript)
|
|
564
|
+
declare structField1: number;
|
|
565
|
+
declare structField2: number;
|
|
566
|
+
|
|
567
|
+
// Other fields (will be transported separately)
|
|
568
|
+
otherField: string = '';
|
|
569
|
+
}
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
**Mixed scenarios with transport decorators:**
|
|
573
|
+
|
|
574
|
+
Typed-struct classes can use `@TransportType()` and `@TransportEncoder()` on their non-struct fields:
|
|
575
|
+
|
|
576
|
+
```ts
|
|
577
|
+
class ComplexData extends Base {
|
|
578
|
+
declare value: number; // struct field
|
|
579
|
+
declare count: number; // struct field
|
|
580
|
+
|
|
581
|
+
@TransportType(() => MyData)
|
|
582
|
+
nested?: MyData; // non-struct field with custom class
|
|
583
|
+
|
|
584
|
+
@TransportEncoder(
|
|
585
|
+
(date: Date) => date.toISOString(),
|
|
586
|
+
(str: string) => new Date(str),
|
|
587
|
+
)
|
|
588
|
+
timestamp?: Date; // non-struct field with custom encoder
|
|
589
|
+
}
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
Regular classes can have typed-struct fields:
|
|
593
|
+
|
|
594
|
+
```ts
|
|
595
|
+
class WrapperClass {
|
|
596
|
+
@TransportType(() => MyStructData)
|
|
597
|
+
data!: MyStructData; // typed-struct class field
|
|
598
|
+
|
|
599
|
+
label: string = ''; // regular field
|
|
600
|
+
}
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
All combinations work seamlessly - the transport system automatically handles:
|
|
604
|
+
- Typed-struct classes with decorated non-struct fields
|
|
605
|
+
- Regular classes with typed-struct fields
|
|
606
|
+
- Arrays of any of the above
|
|
607
|
+
- Nested structures of any depth
|
|
608
|
+
|
|
312
609
|
### Notes on Transport
|
|
313
610
|
|
|
314
611
|
- `@TransportType()` can be used without arguments to enable `emitDecoratorMetadata` without registering metadata
|
|
@@ -434,27 +731,53 @@ Event handlers run on the main thread and can access the main-thread instance st
|
|
|
434
731
|
- supports async operations
|
|
435
732
|
- works as `PropertyDecorator`, `MethodDecorator`, and `ParameterDecorator`
|
|
436
733
|
|
|
734
|
+
#### Shared Memory
|
|
735
|
+
|
|
736
|
+
- `Shared(factory?: () => Type)`
|
|
737
|
+
- marks constructor parameter to use shared memory
|
|
738
|
+
- factory function `() => Type` is optional (inferred from `design:paramtypes` if omitted)
|
|
739
|
+
- parameter type must contain shared memory segments (`typed-struct`, `Buffer`, `SharedArrayBuffer`)
|
|
740
|
+
- throws error if type has no shared memory segments
|
|
741
|
+
- works as `ParameterDecorator` (constructor parameters only)
|
|
742
|
+
- automatically converts parameter to use `SharedArrayBuffer` during worker initialization
|
|
743
|
+
- both main thread and worker thread share the same memory
|
|
744
|
+
|
|
437
745
|
### Functions
|
|
438
746
|
|
|
439
747
|
- `initWorker(cls, ...args)`
|
|
440
748
|
- creates a persistent worker and returns instance with `finalize(): Promise<void>` and `workerStatus(): WorkerStatus`
|
|
749
|
+
- automatically handles `@Shared` constructor parameters
|
|
750
|
+
- preserves prototype chain for custom class constructor parameters
|
|
441
751
|
- `runInWorker(cls, cb, ...args)`
|
|
442
752
|
- one-time worker execution with automatic finalize
|
|
753
|
+
- same constructor parameter handling as `initWorker`
|
|
754
|
+
- `toShared(obj)`
|
|
755
|
+
- converts object to use shared memory
|
|
756
|
+
- returns new instance for `typed-struct` classes
|
|
757
|
+
- modifies in-place for user classes (updates fields)
|
|
758
|
+
- creates `SharedArrayBuffer` copy for `Buffer`
|
|
759
|
+
- returns as-is for `SharedArrayBuffer`
|
|
760
|
+
- recursively processes fields with `@TransportType()` or `design:type` metadata
|
|
443
761
|
|
|
444
762
|
### Types
|
|
445
763
|
|
|
446
764
|
- `WorkerStatus`
|
|
447
765
|
- enum for worker status states
|
|
766
|
+
- values: `Initializing`, `Ready`, `InitError`, `WorkerError`, `Exited`, `Finalized`
|
|
448
767
|
- `WorkerInstance<T>`
|
|
449
768
|
- type for worker instance with `finalize()` and `workerStatus()` methods
|
|
450
769
|
- `WorkerEventName`
|
|
451
770
|
- type for worker event names, matches `Worker.on()` event parameter
|
|
771
|
+
- includes: `'error'`, `'exit'`, `'online'`, `'message'`, `'messageerror'`
|
|
452
772
|
- `Awaitable<T>`
|
|
453
773
|
- type for value that can be sync or async: `T | Promise<T>`
|
|
454
774
|
- `TransportTypeFactory`
|
|
455
775
|
- type for transport type factory: `() => Class | [Class]`
|
|
456
776
|
- `TransportEncoderType<T, U>`
|
|
457
777
|
- type for custom encoder/decoder object
|
|
778
|
+
- `SharedTypeFactory`
|
|
779
|
+
- type for shared type factory: `() => Class`
|
|
780
|
+
- used with `@Shared()` decorator
|
|
458
781
|
|
|
459
782
|
## Notes
|
|
460
783
|
|
|
@@ -464,3 +787,122 @@ Event handlers run on the main thread and can access the main-thread instance st
|
|
|
464
787
|
- For `@TransportType()` to automatically infer types from TypeScript metadata, enable `emitDecoratorMetadata` in `tsconfig.json`.
|
|
465
788
|
- Transport decorators work with both `@WorkerMethod()` and `@WorkerCallback()`.
|
|
466
789
|
- Custom class transport preserves the prototype chain and method definitions.
|
|
790
|
+
|
|
791
|
+
### Constructor Parameter Transport
|
|
792
|
+
|
|
793
|
+
Constructor parameters passed to `initWorker()` are automatically transported with prototype preservation:
|
|
794
|
+
|
|
795
|
+
```ts
|
|
796
|
+
class Config {
|
|
797
|
+
@TransportType(() => Date)
|
|
798
|
+
createdAt: Date;
|
|
799
|
+
|
|
800
|
+
constructor(public name: string) {
|
|
801
|
+
this.createdAt = new Date();
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
@DefineWorker()
|
|
806
|
+
class ConfigWorker {
|
|
807
|
+
constructor(
|
|
808
|
+
public config: Config, // No decorator needed if emitDecoratorMetadata is enabled
|
|
809
|
+
) {}
|
|
810
|
+
|
|
811
|
+
@WorkerMethod()
|
|
812
|
+
getConfigName() {
|
|
813
|
+
return this.config.name.toUpperCase();
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
const config = new Config('app');
|
|
818
|
+
const worker = await initWorker(ConfigWorker, [config]);
|
|
819
|
+
|
|
820
|
+
// config methods are preserved in worker
|
|
821
|
+
const name = await worker.getConfigName();
|
|
822
|
+
console.log(name); // -> "APP"
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
- Constructor parameters with custom classes are automatically transported
|
|
826
|
+
- Prototype chain is preserved using `@TransportType()` or `design:paramtypes` metadata
|
|
827
|
+
- Works the same way as method parameters and return values
|
|
828
|
+
- `@Shared` parameters are converted first, then transported
|
|
829
|
+
|
|
830
|
+
### Circular Reference Detection
|
|
831
|
+
|
|
832
|
+
The library detects and prevents circular references in both transport and shared memory:
|
|
833
|
+
|
|
834
|
+
**Transport:**
|
|
835
|
+
```ts
|
|
836
|
+
class Node {
|
|
837
|
+
@TransportType(() => Node)
|
|
838
|
+
next?: Node;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
const node1 = new Node();
|
|
842
|
+
const node2 = new Node();
|
|
843
|
+
node1.next = node2;
|
|
844
|
+
node2.next = node1; // Circular reference
|
|
845
|
+
|
|
846
|
+
await worker.processNode(node1); // Throws: "Circular reference detected"
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
**Shared memory:**
|
|
850
|
+
```ts
|
|
851
|
+
class CircularContainer {
|
|
852
|
+
@TransportType(() => SharedData)
|
|
853
|
+
data!: SharedData;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
const container: any = new Container();
|
|
857
|
+
container.data = new SharedData();
|
|
858
|
+
container.self = container; // Circular reference
|
|
859
|
+
|
|
860
|
+
toShared(container); // Throws: "Circular reference detected"
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
Circular references in type hierarchies are also detected:
|
|
864
|
+
```ts
|
|
865
|
+
class CircularA {
|
|
866
|
+
@TransportType(() => CircularB)
|
|
867
|
+
b?: CircularB;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
class CircularB {
|
|
871
|
+
@TransportType(() => CircularA)
|
|
872
|
+
a?: CircularA;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// Throws when scanning metadata or attempting to transport
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
### SharedArrayBuffer Support
|
|
879
|
+
|
|
880
|
+
`SharedArrayBuffer` is automatically detected and handled in transport:
|
|
881
|
+
|
|
882
|
+
```ts
|
|
883
|
+
@DefineWorker()
|
|
884
|
+
class SharedBufferWorker {
|
|
885
|
+
@WorkerMethod()
|
|
886
|
+
incrementBuffer(
|
|
887
|
+
@TransportType(() => Buffer) buffer: Buffer,
|
|
888
|
+
) {
|
|
889
|
+
buffer[0]++;
|
|
890
|
+
return buffer[0];
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
const sab = new SharedArrayBuffer(10);
|
|
895
|
+
const buffer = Buffer.from(sab);
|
|
896
|
+
buffer[0] = 100;
|
|
897
|
+
|
|
898
|
+
const worker = await initWorker(SharedBufferWorker);
|
|
899
|
+
|
|
900
|
+
// Buffer backed by SharedArrayBuffer is shared
|
|
901
|
+
await worker.incrementBuffer(buffer);
|
|
902
|
+
console.log(buffer[0]); // -> 101 (updated by worker)
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
- `Buffer` backed by `SharedArrayBuffer` is automatically detected
|
|
906
|
+
- No copy is made - the same memory is shared
|
|
907
|
+
- Works with both `@TransportType()` and `@Shared()`
|
|
908
|
+
- `SharedArrayBuffer` can be passed directly as a parameter type
|