| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Actor-style remote objects on Node.js worker threads — write normal classes, call them like local instances.
import { Runtime, actor } from "@js-ak/remote-objects";
export class Counter {
constructor(public value = 0) {}
inc() {
this.value += 1;
return this.value;
}
}
actor(Counter, import.meta);
const runtime = new Runtime({ workers: 2 });
const counter = await runtime.spawn(Counter, 10);
console.log(await counter.inc()); // 11
await runtime.dispose();npm install @js-ak/remote-objectsRequires Node.js 20.19+. Works with both ESM and CommonJS:
import { Runtime, actor } from "@js-ak/remote-objects"; // ESMconst { Runtime, actor } = require("@js-ak/remote-objects"); // CJSEach actor class must be bound in its own module so workers know which file to load.
ESM:
import { actor } from "@js-ak/remote-objects";
export class Database { /* ... */ }
actor(Database, import.meta);CJS:
const { actor } = require("@js-ak/remote-objects");
class Database { /* ... */ }
actor(Database, __filename);
// or: actor(Database, { filename: __filename })
module.exports = { Database };spawn and getOrSpawn auto-register the class on first use (or call runtime.register(Database) explicitly).
new Runtime({
workers: 4,
debug: true, // or (event) => { ... } or { onEvent: (event) => { ... } }
callTimeoutMs: 5_000,
});Debug events include register, spawn, destroy, call:start, call:end, call:timeout, worker:error, bridge:call, bridge:result, dispose. Spawn/call events carry actorId as "workerId:objectId". getOrSpawn emits the same spawn event when it creates a new actor (cache hits do not spawn again).
await runtime.destroy(counter); // close (dispose/close) then drop one actor
await runtime.destroy(counter, { close: false }); // drop without close
await runtime.dispose(); // close actors, drain, terminate workers
await runtime.dispose({ closeActors: false });After dispose, further spawn, getOrSpawn, or method calls fail with a clear error.
Proxies can be passed into methods (including across workers), including nested inside plain objects/arrays:
await linker.link(counter);
await linker.readOther();
const wrapped = await nested.wrap(counter); // { counter, label }
await nested.readWrapped(wrapped);Functions may be passed as arguments or returned from methods. Remote invocations are always async.
await actor.withProgress(10, async (n) => {
console.log("progress", n);
});
const add = await actor.makeAdder(10);
await add(5); // 15Node.js Readable / Writable / Duplex can be args or results (objectMode preserved; backpressure via pause/resume).
const stream = await actor.query(100);
for await (const row of stream) {
// ...
}Use sticky actors when a worker should own long-lived state (DB pools, SDK clients, caches):
Same-actor calls are serialized (mailbox). Different actors may run in parallel on the pool.
You do not need to predict every app up front — pick a pattern from what you are doing:
| You want… | Use |
|---|---|
| Long-lived client state (DB pool, SDK session, in-memory cache) | One actor per resource; or getOrSpawn(key, ...) for one actor per tenant/shard |
| More throughput on CPU or I/O | workers: 2+ and separate actor instances (each spawn → least-loaded worker) |
| Strict ordering for one object | One actor — overlapping calls on the same proxy are queued (mailbox) |
| Parallel work on the same class | Multiple spawns, or getOrSpawn + extra spawns (see examples/db.ts) |
| Progress / one-off handlers during a call | Callback in args (released when the call finishes) |
| Long-lived handler returned from a method | Callback in return value (released on destroy of the owning actor) |
| Many rows or chunked I/O | Streams as args or results (backpressure built in) |
| Compose actors (even on different workers) | Pass actor proxies as method arguments |
| Shut down one resource | destroy(proxy) — runs close/dispose on the actor by default |
| Shut down the whole runtime | dispose() — drain in-flight work, then terminate workers |
Worker count
One actor vs many
One actor per tenant / key
Use getOrSpawn(key, Class, ...args) — the runtime keeps one proxy per key until destroy. The worker is chosen by hash(key) % workers and stays stable for that key. Reusing a key with a different class or constructor args throws.
const db = await runtime.getOrSpawn(`tenant:${tenantId}`, Database, creds);
// later, same process → same proxy / same pool
const same = await runtime.getOrSpawn(`tenant:${tenantId}`, Database, creds);
expect(same).toBe(db);
await runtime.destroy(db); // drops the key; next getOrSpawn creates a fresh actorFor many independent instances of the same class (parallel pools), use spawn instead. spawn does not register a key — spawn(Database, creds) and getOrSpawn("db", Database, creds) are always separate actors.
Not a fit
| remote-objects | Comlink | Piscina | |
|---|---|---|---|
| Model | Sticky class actors + proxies | RPC proxies | Task pool |
| Best for | Stateful isolation (DB/SDK) | General worker RPC | Stateless jobs |
| Callbacks / streams | Yes (Node streams) | Callbacks / proxies | Per-task message |
| Migration between workers | No | N/A | N/A |
import { getActorHandle } from "@js-ak/remote-objects";
const handle = getActorHandle(counter); // { workerId, objectId } | undefinedMIT
| Back | FazBrowse Home | New Git URL |