first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
# Worker-Side Jobs Proxy Client
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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.