diff --git a/README.md b/README.md index 4524e3e..8e4fd49 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,29 @@ const random: Balancer = { Each `WorkerHandle` exposes `activeCount` (in-flight tasks) and `thread` (the underlying `worker_threads.Worker`) for building custom strategies. +`isTask(moroutine, task)` narrows a task to the descriptor type produced by a specific moroutine — useful inside a balancer to route by task kind or by a key in the args. For example, a worker-affinity balancer can hash a shard key out of the args so that every call for the same key hits the worker that already has its state loaded: + +```ts +import { isTask, roundRobin } from 'moroutine'; +import type { Balancer } from 'moroutine'; +import { increment, read } from './counter.ts'; + +export function keyAffinity(): Balancer { + const fallback = roundRobin(); + return { + select(workers, task) { + let key: string | undefined; + if (isTask(increment, task)) key = task.args[0]; + else if (isTask(read, task)) key = task.args[0]; + if (key === undefined) return fallback.select(workers, task); + return workers[hash(key) % workers.length]; + }, + }; +} +``` + +Inside the `isTask` branch, `task.args` is typed as the moroutine's argument tuple (e.g. `[key: string, n: number]` for `increment`). See [`examples/worker-affinity`](examples/worker-affinity) for the full demo, including per-worker state that only stays consistent under affinity routing. + ### Dedicated Workers Awaiting a task directly (without a pool) runs it on a dedicated worker thread, one per moroutine function.