# Worker-Side Jobs Proxy Client _(no submodules)_ ## Purpose `createJobsProxyClient(port, opts)` is the worker-tier entry point for the `@suaveplan/api-jobs-worker-proxy` package. It returns an object that implements the `JobRegistrationHandle` capability slot from `@suaveplan/api-types`, letting a worker-isolated API plugin register cron and queued jobs even though the actual scheduler runs in the host main process. Every `registerCron` and `registerQueued` call stores the handler in a local `HandlerRegistry` and asynchronously marshals a `registerJob` RPC to the main side via the supplied `MessagePort`. When the main scheduler ticks a job, the same port carries a `runJob` dispatch back to the worker; the local serve-side listener invokes the stored handler under an optional timeout. ## Features - Implements the exact `JobRegistrationHandle` surface (`registerCron`, `registerQueued`) — no proxy-specific extensions on the public type. - Bidirectional over a single port: outbound registration RPCs + inbound `runJob` dispatch via paired raw RPC client + serve handler. - Fire-and-forget registration semantics with an `onHandlerError` observer for asynchronous rejections (job-not-declared, port closed). - Per-handler timeout via `handlerTimeoutMs`; long-running closures reject with `JOBS_PROXY.JOB_TIMEOUT`. - Idempotent `close(reason?)` tears down both the RPC client and the serve listener. ## Usage: Basic ```ts import { MessageChannel } from "node:worker_threads"; import { createJobsProxyClient } from "@suaveplan/api-jobs-worker-proxy/worker-side"; const channel = new MessageChannel(); const client = createJobsProxyClient(channel.port1); client.registerCron("rotate-keys", async () => { await rotate(); }); ``` ## Usage: Advanced ```ts import { createJobsProxyClient } from "@suaveplan/api-jobs-worker-proxy/worker-side"; const client = createJobsProxyClient(workerPort, { handlerTimeoutMs: 30_000, onHandlerError: (err, ctx) => { logger.warn({ code: err.code, name: ctx.name, kind: ctx.kind }); }, }); client.registerQueued("send-email", async (payload) => { await mailer.send(payload as MailPayload); }); ``` ## API Reference - `createJobsProxyClient(port, opts?) -> JobsProxyClient` — factory. - `JobsProxyClient extends JobRegistrationHandle` — adds `close()` and a `sizes` accessor reporting registration counts. - `JobsProxyClientOptions`: - `handlerTimeoutMs?: number` — per-dispatch timeout, `0` disables. - `onHandlerError?: (err, ctx) => void` — observer hook. ## Implementation Notes The client multiplexes a `createRawWorkerRpcClient` (for outbound `registerJob` calls) and a `serveWorkerRpc` handlers object (for inbound `runJob`) over the same `PortLike`. Protocol frames partition cleanly: `response` frames target the RPC client's pending registry, and `request` frames target the serve dispatcher; each listener silently drops the other's traffic. The local `HandlerRegistry` is the single source of truth for which handlers can be dispatched — the main side relies on the worker holding the closure and we never serialize the function across the wire. The fire-and-forget `registerJob` call uses `.catch` to funnel rejections (including `JOB_NOT_DECLARED` from main) through `onHandlerError`, preserving the original `JobRegistrationHandle` synchronous-`void` return contract.