Offload functions to worker threads with shared memory primitives for Node.js.
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs: update README with installation, task-args, streaming, and workers default

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+62 -18
+62 -18
README.md
··· 2 2 3 3 > Offload functions to worker threads with shared memory primitives for Node.js. 4 4 5 + ## Installation 6 + 7 + ```sh 8 + npm install moroutine 9 + ``` 10 + 11 + Requires Node.js v24+. 12 + 5 13 ## Quick Start 6 14 7 15 ```ts 16 + // main.ts 17 + import { isPrime } from './is-prime.ts'; 18 + 19 + const result = await isPrime(999_999_937); 20 + console.log(result); // true 21 + ``` 22 + 23 + ```ts 8 24 // is-prime.ts 9 25 import { mo } from 'moroutine'; 10 26 ··· 17 33 }); 18 34 ``` 19 35 20 - ```ts 21 - // main.ts 22 - import { workers } from 'moroutine'; 23 - import { isPrime } from './is-prime.ts'; 24 - 25 - using run = workers(4); 26 - const results = await run([isPrime(999_999_937), isPrime(1_000_000_007)]); 27 - console.log(results); // [true, true] 28 - ``` 29 - 30 36 Define a function with `mo()` in its own module, then import and run it on a worker pool. Moroutine modules must be side-effect free — workers import them to find the registered functions. 31 37 32 38 ## Core API ··· 46 52 47 53 ### `workers(size)` 48 54 49 - Creates a pool of worker threads. Returns a `Runner` that dispatches tasks with round-robin scheduling. Disposable via `using` or `[Symbol.dispose]()`. 55 + Creates a pool of worker threads. Returns a `Runner` that dispatches tasks with round-robin scheduling. Disposable via `using` or `[Symbol.dispose]()`. Defaults to `os.availableParallelism()` workers when `size` is omitted. 50 56 51 57 ```ts 52 58 import { workers } from 'moroutine'; ··· 68 74 const result = await add(3, 4); // runs on a dedicated worker for `add` 69 75 ``` 70 76 77 + ### Task-Args 78 + 79 + Pass a task as an argument to another task. The result is resolved on the worker and cached, so it never crosses back to the main thread. This is useful for non-transferable context like a database connection. 80 + 81 + ```ts 82 + // db.ts 83 + import { DatabaseSync } from 'node:sqlite'; 84 + import { mo } from 'moroutine'; 85 + 86 + export const openDb = mo(import.meta, (filename: string): DatabaseSync => { 87 + return new DatabaseSync(filename); 88 + }); 89 + 90 + export const exec = mo(import.meta, (db: DatabaseSync, sql: string): void => { 91 + db.exec(sql); 92 + }); 93 + 94 + export const query = mo(import.meta, (db: DatabaseSync, sql: string): unknown[] => { 95 + return db.prepare(sql).all(); 96 + }); 97 + ``` 98 + 99 + ```ts 100 + import { workers } from 'moroutine'; 101 + import { openDb, exec, query } from './db.ts'; 102 + 103 + const db = openDb(':memory:'); 104 + 105 + { 106 + using run = workers(1); 107 + await run(exec(db, `CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)`)); 108 + await run(exec(db, `INSERT INTO users (name) VALUES ('Alice')`)); 109 + const rows = await run(query(db, 'SELECT * FROM users')); // [{ id: 1, name: 'Alice' }] 110 + } 111 + ``` 112 + 113 + `openDb()` returns a `Task<DatabaseSync>`, and `exec()`/`query()` accept it in place of `DatabaseSync`. The database is opened once on the worker and reused for every subsequent call — the main thread never touches it. 114 + 71 115 ## Shared Memory 72 116 73 117 ### Descriptors and `shared()` ··· 136 180 const buf = bytes(32); // fixed 32-byte buffer 137 181 buf.store(new Uint8Array(32)); // exact length required 138 182 buf.load(); // Readonly<Uint8Array> view 139 - buf.view[0] = 0xff; // direct mutable access 183 + buf.view[0] = 0xff; // direct mutable access 140 184 141 185 const name = string(64); // UTF-8, max 64 bytes 142 186 name.store('hello'); ··· 151 195 shared(0) // Int32 initialized to 0 152 196 shared(true) // Bool initialized to true 153 197 shared(0n) // Int64 initialized to 0n 154 - shared({ x: 10, y: 20 }) // struct with Int32 fields 198 + shared({ x: 10, y: 20 }) // struct with Int32 fields 155 199 ``` 156 200 157 201 ### Locks ··· 275 319 ``` 276 320 277 321 ```ts 278 - const data = channel(generate(100)); 322 + const ch = channel(generate(100)); 279 323 280 324 { 281 325 using run = workers(4); 282 326 const [a, b, c, d] = await run([ 283 - process(data), 284 - process(data), 285 - process(data), 286 - process(data), 327 + process(ch), 328 + process(ch), 329 + process(ch), 330 + process(ch), 287 331 ]); 288 332 // Items distributed across workers — no duplicates, no gaps 289 333 }