@on-the-ground/proxxy
v0.1.1
Published
Async proxies that route method calls through partitioned daemon queues, with per-key ordering and cross-key concurrency.
Maintainers
Readme
@on-the-ground/proxxy
Async proxies that shard an object across N independently-constructed instances and dispatch
method calls onto per-shard @on-the-ground/daemonizer
Daemon queues — a JS/TS counterpart to proxxy (JVM).
📁 Directory Structure
src/
├── index.ts # Entry point
└── proxxy.ts # startProxy✅ Features
- Wraps a
constructor— not a live instance — and builds one independent instance per shard (vianew constructor()), so shards never share mutable state - A caller-supplied
keyExtractorroutes each call to a shard; calls routed to the same shard are strictly ordered, calls on different shards run concurrently - Every call returns
Promise<Awaited<R>>, propagating the method's result or thrown error straight back to the caller — proxxy never swallows or redirects errors, that's the caller's call - Cancellation via
AbortSignal;close()returns aPromise<void>that resolves once every shard has fully drained - Only methods are proxied — plain property access (get and set) throws, since a single field has no coherent value once state is sharded
- Proxied methods must use regular method syntax (
method() {}), not arrow-function class fields (method = () => {}) — see Method syntax below
Usage
import { startProxy } from "@on-the-ground/proxxy";
class Account {
private balance = 0;
// first arg is only used for routing below, the method itself doesn't need it
async deposit(_accountId: string, amount: number) {
return (this.balance += amount);
}
}
const controller = new AbortController();
const { proxy, close } = startProxy(Account, controller.signal, {
partitionCount: 4,
// calls for the same accountId must land on the same shard to observe each other
keyExtractor: (_method, args) => hashOf(args[0] as string),
});
await proxy.deposit("acct-1", 100); // 100
await proxy.deposit("acct-1", 50); // 150 — same shard, ordered after the first call
await proxy.deposit("acct-2", 10); // runs concurrently with acct-1's calls, different shard
await close();Wrapping a single object with no sharding at all is just the default: omit partitionCount
(defaults to 1) and keyExtractor (defaults to always routing to shard 0).
Method syntax
Define proxied methods with regular method syntax:
class Account {
async deposit(id: string, amount: number) { /* ... */ } // ✅ visible to startProxy
}Not as arrow-function class fields:
class Account {
deposit = async (id: string, amount: number) => { /* ... */ }; // ❌ not recognized
}startProxy tells methods apart from plain properties by checking constructor.prototype,
without constructing a throwaway instance just to inspect its shape. Regular methods live on
the prototype; arrow-function class fields are assigned per-instance in the constructor, so
they're invisible until an instance already exists. This costs nothing in practice — proxxy
always invokes methods via fn.apply(instance, args), so the usual reason to reach for an
arrow field (auto-bound this) doesn't apply here.
🔧 Build
yarn build🧪 Testing
yarn test # vitest
yarn typecheck # tsc --noEmit, covers src/ and test/