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 @@
# Main-Side Jobs Proxy Server
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`createJobsProxyServer(port, mainHandle, opts)` runs in the host main
process. It receives `registerJob` RPC frames from a worker-side
`createJobsProxyClient` over the supplied `MessagePort`, validates the
incoming job name against the plugin's manifest-declared jobs, and
forwards the registration to the host's real
`JobRegistrationHandle` (which in practice wraps
`@suaveplan/job-scheduler`). Each successful registration is bound to a
closure that, when ticked by the scheduler, dispatches a `runJob` RPC
back to the worker. This is The piece that lets a plugin's
worker-isolated code participate in the main-process cron/queue
lifecycle without sharing memory.
## Features
- Manifest-bound: `declaredJobs` is the authoritative whitelist. Names
not in the list reject with `JOBS_PROXY.JOB_NOT_DECLARED`.
- Kind enforcement: registering a name as `cron` when the manifest
declares it `queued` (or vice versa) is also rejected as
`JOB_NOT_DECLARED`.
- Optional per-dispatch RPC timeout (`runRpcTimeoutMs`) so a hung
worker handler does not stall scheduler ticks indefinitely.
- `onDispatchError` observer for telemetry on failed worker
invocations.
- Idempotent `close()` tears down the inbound listener AND the outbound
RPC client.
- `registeredCount` accessor for tests + diagnostics.
## Usage: Basic
```ts
import { MessageChannel } from "node:worker_threads";
import { createJobsProxyServer } from "@suaveplan/api-jobs-worker-proxy/main-side";
const server = createJobsProxyServer(channel.port2, hostScheduler, {
declaredJobs: [{ name: "rotate-keys", kind: "cron" }],
});
```
## Usage: Advanced
```ts
import { createJobsProxyServer } from "@suaveplan/api-jobs-worker-proxy/main-side";
const server = createJobsProxyServer(channel.port2, hostScheduler, {
declaredJobs: manifest.jobs,
runRpcTimeoutMs: 60_000,
onDispatchError: (err, ctx) => metrics.increment("jobs.dispatch_failed", { name: ctx.name }),
});
// Later, on teardown:
server.close("plugin-unload");
```
## API Reference
- `createJobsProxyServer(port, mainHandle, opts) -> JobsProxyServer`.
- `JobsProxyServer`:
- `close(reason?: string): void` — idempotent disposer.
- `registeredCount: number` — count of accepted registrations.
- `JobsProxyServerOptions`:
- `declaredJobs: ReadonlyArray<{name, kind}>` — manifest whitelist.
- `runRpcTimeoutMs?: number` — optional per-tick RPC timeout.
- `onDispatchError?: (err, ctx) => void` — telemetry hook.
## Implementation Notes
The server mounts a `serveWorkerRpc` handlers object exposing a single
`registerJob` method that parses incoming payloads with
`registerJobRequestSchema`. The manifest whitelist is normalised into a
`Map<name, kind>` at construction time for O(1) lookups. When a
registration is accepted, the closure stored with the host scheduler
captures the supplied `name`/`kind` and, on tick, invokes
`rpc.call("runJob", [{name, kind, payload}])` via a paired
`createRawWorkerRpcClient`. The dispatcher checks `closed` first so a
late tick after `close()` raises `JOBS_PROXY.PROXY_DISPOSED` instead of
posting on a dead port. The server intentionally has no opinion about
backpressure: the underlying RPC layer already supplies a bounded
outbound queue.