yuzuthread 1.0.1 → 1.0.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
@@ -20,6 +20,22 @@ npm i yuzuthread typed-struct
20
20
 
21
21
  `typed-struct` is required because `yuzuthread` declares it as a peer dependency.
22
22
 
23
+ ### TypeScript Configuration
24
+
25
+ Enable decorators in your `tsconfig.json`:
26
+
27
+ ```json
28
+ {
29
+ "compilerOptions": {
30
+ "experimentalDecorators": true,
31
+ "emitDecoratorMetadata": true
32
+ }
33
+ }
34
+ ```
35
+
36
+ - `experimentalDecorators` is required for all decorators
37
+ - `emitDecoratorMetadata` enables automatic type inference for `@TransportType()`
38
+
23
39
  ## Define a Worker Class
24
40
 
25
41
  Put each worker class in its own file and add `@DefineWorker()`.
@@ -138,22 +154,417 @@ console.log(instance.value); // -> 0x7f
138
154
  await instance.finalize();
139
155
  ```
140
156
 
157
+ ## Custom Class Transport
158
+
159
+ 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.
160
+
161
+ ### Basic Class Transport
162
+
163
+ Use `@TransportType()` to mark parameters and return values that need class restoration:
164
+
165
+ ```ts
166
+ import { DefineWorker, WorkerMethod, TransportType, initWorker } from 'yuzuthread';
167
+
168
+ class UserData {
169
+ constructor(
170
+ public id: number,
171
+ public name: string,
172
+ ) {}
173
+
174
+ greet() {
175
+ return `Hello, ${this.name}!`;
176
+ }
177
+ }
178
+
179
+ @DefineWorker()
180
+ class DataWorker {
181
+ @WorkerMethod()
182
+ @TransportType(() => UserData) // Return type
183
+ async processUser(
184
+ @TransportType(() => UserData) user: UserData, // Parameter
185
+ ): Promise<UserData> {
186
+ return new UserData(user.id, user.name.toUpperCase());
187
+ }
188
+ }
189
+
190
+ const worker = await initWorker(DataWorker);
191
+ const input = new UserData(1, 'alice');
192
+ const result = await worker.processUser(input);
193
+
194
+ console.log(result.greet()); // -> "Hello, ALICE!"
195
+ await worker.finalize();
196
+ ```
197
+
198
+ ### Array Transport
199
+
200
+ Use `[Class]` syntax for arrays:
201
+
202
+ ```ts
203
+ @DefineWorker()
204
+ class BatchWorker {
205
+ @WorkerMethod()
206
+ @TransportType(() => [UserData])
207
+ async processBatch(
208
+ @TransportType(() => [UserData]) users: UserData[],
209
+ ): Promise<UserData[]> {
210
+ return users.map((u) => new UserData(u.id + 100, u.name.toLowerCase()));
211
+ }
212
+ }
213
+ ```
214
+
215
+ ### Nested Objects
216
+
217
+ For classes with custom-class properties, add `@TransportType()` to the property:
218
+
219
+ ```ts
220
+ class Address {
221
+ constructor(
222
+ public city: string,
223
+ public country: string,
224
+ ) {}
225
+ }
226
+
227
+ class Person {
228
+ @TransportType(() => Address)
229
+ address!: Address;
230
+
231
+ constructor(
232
+ public name: string,
233
+ address: Address,
234
+ ) {
235
+ this.address = address;
236
+ }
237
+ }
238
+
239
+ @DefineWorker()
240
+ class PersonWorker {
241
+ @WorkerMethod()
242
+ @TransportType(() => Person)
243
+ async processPerson(
244
+ @TransportType(() => Person) person: Person,
245
+ ): Promise<Person> {
246
+ return new Person(
247
+ person.name,
248
+ new Address(person.address.city.toUpperCase(), person.address.country),
249
+ );
250
+ }
251
+ }
252
+ ```
253
+
254
+ ### Custom Encoder
255
+
256
+ For complex serialization logic, use `@TransportEncoder()`:
257
+
258
+ ```ts
259
+ import { DefineWorker, WorkerMethod, TransportEncoder, initWorker } from 'yuzuthread';
260
+
261
+ @DefineWorker()
262
+ class DateWorker {
263
+ @WorkerMethod()
264
+ @TransportEncoder(
265
+ (date: Date) => date.toISOString(),
266
+ (str: string) => new Date(str),
267
+ )
268
+ async addDay(
269
+ @TransportEncoder(
270
+ (date: Date) => date.toISOString(),
271
+ (str: string) => new Date(str),
272
+ )
273
+ date: Date,
274
+ ): Promise<Date> {
275
+ const next = new Date(date);
276
+ next.setDate(next.getDate() + 1);
277
+ return next;
278
+ }
279
+ }
280
+
281
+ const worker = await initWorker(DateWorker);
282
+ const today = new Date('2024-01-01');
283
+ const tomorrow = await worker.addDay(today);
284
+ console.log(tomorrow); // -> Date('2024-01-02')
285
+ await worker.finalize();
286
+ ```
287
+
288
+ Encoders support async operations:
289
+
290
+ ```ts
291
+ @TransportEncoder(
292
+ async (obj: MyClass) => {
293
+ // async encoding logic
294
+ return await serialize(obj);
295
+ },
296
+ async (data: string) => {
297
+ // async decoding logic
298
+ return await deserialize(data);
299
+ },
300
+ )
301
+ ```
302
+
303
+ ### Built-in Type Handling
304
+
305
+ - **Primitives** (`string`, `number`, `boolean`, etc.) - passed as-is
306
+ - **Built-in objects** (`Date`, `RegExp`, `Map`, `Set`, etc.) - handled by structured clone
307
+ - **Buffer** - automatically encoded/decoded with `Uint8Array`
308
+ - **TypedArrays** (`Uint8Array`, `Int32Array`, etc.) - passed directly
309
+ - **Plain objects** - passed as-is
310
+ - **Custom classes** - require `@TransportType()` or `@TransportEncoder()`
311
+
312
+ ### Typed Struct Classes
313
+
314
+ 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:
315
+
316
+ ```ts
317
+ import { Struct } from 'typed-struct';
318
+ import { DefineWorker, WorkerMethod, TransportType, initWorker } from 'yuzuthread';
319
+
320
+ const Base = new Struct('DataBase')
321
+ .UInt8('id')
322
+ .UInt32LE('counter')
323
+ .compile();
324
+
325
+ class MyData extends Base {
326
+ declare id: number;
327
+ declare counter: number;
328
+ extraField: string = '';
329
+ nestedData?: { name: string };
330
+ }
331
+
332
+ @DefineWorker()
333
+ class StructWorker {
334
+ @WorkerMethod()
335
+ @TransportType(() => MyData)
336
+ async processData(
337
+ @TransportType(() => MyData) data?: MyData,
338
+ ): Promise<MyData> {
339
+ const result = new MyData();
340
+ if (data) {
341
+ result.id = data.id;
342
+ result.counter = data.counter + 1;
343
+ result.extraField = data.extraField + ' processed';
344
+ result.nestedData = data.nestedData;
345
+ } else {
346
+ result.id = 1;
347
+ result.counter = 0;
348
+ result.extraField = 'new';
349
+ }
350
+ return result;
351
+ }
352
+ }
353
+
354
+ const worker = await initWorker(StructWorker);
355
+ const data = await worker.processData();
356
+ console.log(data.id); // -> 1
357
+ console.log(data.counter); // -> 0
358
+ console.log(data.extraField); // -> 'new'
359
+ ```
360
+
361
+ **How it works:**
362
+ - When encoding, the library dumps the struct buffer and encodes non-struct fields separately
363
+ - When decoding, it creates a new instance with the buffer, then restores non-struct fields
364
+ - All struct fields (defined by `typed-struct`) are preserved in the buffer
365
+ - Additional class fields are transported using the standard transport logic
366
+
367
+ **Expected usage pattern:**
368
+ ```ts
369
+ class SomeDataClass extends new Struct()...compile() {
370
+ // Struct fields (declare them for TypeScript)
371
+ declare structField1: number;
372
+ declare structField2: number;
373
+
374
+ // Other fields (will be transported separately)
375
+ otherField: string = '';
376
+ }
377
+ ```
378
+
379
+ **Mixed scenarios with transport decorators:**
380
+
381
+ Typed-struct classes can use `@TransportType()` and `@TransportEncoder()` on their non-struct fields:
382
+
383
+ ```ts
384
+ class ComplexData extends Base {
385
+ declare value: number; // struct field
386
+ declare count: number; // struct field
387
+
388
+ @TransportType(() => MyData)
389
+ nested?: MyData; // non-struct field with custom class
390
+
391
+ @TransportEncoder(
392
+ (date: Date) => date.toISOString(),
393
+ (str: string) => new Date(str),
394
+ )
395
+ timestamp?: Date; // non-struct field with custom encoder
396
+ }
397
+ ```
398
+
399
+ Regular classes can have typed-struct fields:
400
+
401
+ ```ts
402
+ class WrapperClass {
403
+ @TransportType(() => MyStructData)
404
+ data!: MyStructData; // typed-struct class field
405
+
406
+ label: string = ''; // regular field
407
+ }
408
+ ```
409
+
410
+ All combinations work seamlessly - the transport system automatically handles:
411
+ - Typed-struct classes with decorated non-struct fields
412
+ - Regular classes with typed-struct fields
413
+ - Arrays of any of the above
414
+ - Nested structures of any depth
415
+
416
+ ### Notes on Transport
417
+
418
+ - `@TransportType()` can be used without arguments to enable `emitDecoratorMetadata` without registering metadata
419
+ - Transport decorators work with both `@WorkerMethod()` and `@WorkerCallback()`
420
+ - Multiple `@TransportType()` can be stacked (e.g., for method + parameters)
421
+ - Encoding/decoding happens automatically during method calls
422
+ - Transport uses structured clone algorithm with custom class restoration
423
+
424
+ ## Worker Status
425
+
426
+ You can check the worker status at any time using `workerStatus()`:
427
+
428
+ ```ts
429
+ const worker = await initWorker(CounterWorker);
430
+ console.log(worker.workerStatus()); // -> 'Ready'
431
+
432
+ await worker.increment(5);
433
+ console.log(worker.workerStatus()); // -> 'Ready'
434
+
435
+ await worker.finalize();
436
+ console.log(worker.workerStatus()); // -> 'Finalized'
437
+ ```
438
+
439
+ Possible status values (`WorkerStatus` enum):
440
+ - `Initializing` - Worker is being created
441
+ - `Ready` - Worker is ready to accept calls
442
+ - `InitError` - Worker failed to initialize
443
+ - `WorkerError` - Worker encountered a runtime error
444
+ - `Exited` - Worker exited unexpectedly
445
+ - `Finalized` - Worker was finalized via `finalize()`
446
+
447
+ ## Worker Event Handlers
448
+
449
+ You can handle worker lifecycle events in the main thread using event decorators:
450
+
451
+ ```ts
452
+ import { DefineWorker, WorkerMethod, OnWorkerEvent, OnWorkerExit, OnWorkerError, initWorker } from 'yuzuthread';
453
+
454
+ @DefineWorker()
455
+ class MonitoredWorker {
456
+ @WorkerMethod()
457
+ async doWork() {
458
+ // some work
459
+ }
460
+
461
+ @OnWorkerError()
462
+ handleError(error: Error) {
463
+ console.log('Worker error:', error.message);
464
+ }
465
+
466
+ @OnWorkerExit()
467
+ handleExit(code: number) {
468
+ console.log('Worker exited with code:', code);
469
+ }
470
+
471
+ @OnWorkerEvent('online')
472
+ handleOnline() {
473
+ console.log('Worker is online');
474
+ }
475
+
476
+ // One method can handle multiple events
477
+ @OnWorkerEvent('online')
478
+ @OnWorkerEvent('exit')
479
+ handleMultipleEvents(arg?: unknown) {
480
+ console.log('Worker online or exited');
481
+ }
482
+ }
483
+
484
+ const worker = await initWorker(MonitoredWorker);
485
+ // Event handlers will be called automatically when events occur
486
+ ```
487
+
488
+ Available decorators:
489
+ - `@OnWorkerEvent(event: WorkerEventName)` - Handle any worker event (e.g., 'online', 'message', 'messageerror')
490
+ - `WorkerEventName` is typed to match `Worker.on()` events for type safety
491
+ - Can be stacked on the same method to handle multiple events
492
+ - `@OnWorkerError()` - Shorthand for `@OnWorkerEvent('error')`
493
+ - `@OnWorkerExit()` - Shorthand for `@OnWorkerEvent('exit')`
494
+
495
+ Event handlers run on the main thread and can access the main-thread instance state. Multiple handlers can be registered for the same event, and one method can handle multiple events. If a handler throws an error, it will be logged but won't affect other handlers or worker operation.
496
+
141
497
  ## API
142
498
 
499
+ ### Decorators
500
+
501
+ #### Worker Definition
502
+
143
503
  - `DefineWorker(options?)`
144
504
  - `options.filePath?`: worker file path override (optional, auto-inferred by default)
145
505
  - `options.id?`: custom class registration ID (optional)
506
+
507
+ #### Method Execution
508
+
146
509
  - `WorkerMethod()`
147
510
  - marks a method to execute on worker thread
148
511
  - `WorkerCallback()`
149
512
  - marks a method to execute on main thread when called from worker
513
+
514
+ #### Event Handlers
515
+
516
+ - `OnWorkerEvent(event: WorkerEventName)`
517
+ - marks a method to handle worker events on main thread
518
+ - `WorkerEventName` is typed to match `Worker.on()` for type safety
519
+ - supports: 'error', 'exit', 'online', 'message', 'messageerror'
520
+ - can be stacked on the same method to handle multiple events
521
+ - `OnWorkerError()`
522
+ - shorthand for `@OnWorkerEvent('error')`
523
+ - `OnWorkerExit()`
524
+ - shorthand for `@OnWorkerEvent('exit')`
525
+
526
+ #### Data Transport
527
+
528
+ - `TransportType(factory?: () => Class | [Class])`
529
+ - marks parameter, return value, or property for custom class transport
530
+ - use `() => Class` for single instance
531
+ - use `() => [Class]` for arrays
532
+ - can be used without arguments to enable `emitDecoratorMetadata` only
533
+ - works as `PropertyDecorator`, `MethodDecorator`, and `ParameterDecorator`
534
+ - `TransportEncoder<T, U>(encode, decode)`
535
+ - custom encoder/decoder for transport
536
+ - `encode: (obj: T) => Awaitable<U>` - serialize function
537
+ - `decode: (encoded: U) => Awaitable<T>` - deserialize function
538
+ - supports async operations
539
+ - works as `PropertyDecorator`, `MethodDecorator`, and `ParameterDecorator`
540
+
541
+ ### Functions
542
+
150
543
  - `initWorker(cls, ...args)`
151
- - creates a persistent worker and returns instance with `finalize(): Promise<void>`
544
+ - creates a persistent worker and returns instance with `finalize(): Promise<void>` and `workerStatus(): WorkerStatus`
152
545
  - `runInWorker(cls, cb, ...args)`
153
546
  - one-time worker execution with automatic finalize
154
547
 
548
+ ### Types
549
+
550
+ - `WorkerStatus`
551
+ - enum for worker status states
552
+ - `WorkerInstance<T>`
553
+ - type for worker instance with `finalize()` and `workerStatus()` methods
554
+ - `WorkerEventName`
555
+ - type for worker event names, matches `Worker.on()` event parameter
556
+ - `Awaitable<T>`
557
+ - type for value that can be sync or async: `T | Promise<T>`
558
+ - `TransportTypeFactory`
559
+ - type for transport type factory: `() => Class | [Class]`
560
+ - `TransportEncoderType<T, U>`
561
+ - type for custom encoder/decoder object
562
+
155
563
  ## Notes
156
564
 
157
565
  - Worker classes are still normal classes when instantiated directly via `new`.
158
566
  - Only decorated methods go through the RPC channel.
159
567
  - TypeScript decorators require `experimentalDecorators`.
568
+ - For `@TransportType()` to automatically infer types from TypeScript metadata, enable `emitDecoratorMetadata` in `tsconfig.json`.
569
+ - Transport decorators work with both `@WorkerMethod()` and `@WorkerCallback()`.
570
+ - Custom class transport preserves the prototype chain and method definitions.