first commit
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user