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 +412 -1
- package/dist/index.cjs +1030 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.mjs +990 -0
- package/dist/index.mjs.map +7 -0
- package/dist/src/init-worker.d.ts +7 -0
- package/dist/src/run-in-worker.d.ts +2 -0
- package/dist/src/utility/find-typed-struct-cls.d.ts +3 -0
- package/dist/src/utility/metadata.d.ts +15 -0
- package/dist/src/utility/mutate-typed-struct-proto.d.ts +2 -0
- package/dist/src/utility/transport-metadata.d.ts +55 -0
- package/dist/src/utility/transport.d.ts +29 -0
- package/dist/src/utility/types.d.ts +10 -0
- package/dist/src/worker-method.d.ts +9 -0
- package/dist/src/worker.d.ts +81 -0
- package/index.ts +4 -0
- package/package.json +1 -1
- package/dist/src/.nfs.200510fa.6938 +0 -4
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.
|