Files
skills/docs/api-stack/api/api-jobs-worker-proxy/src/server/server.md
2026-05-29 08:20:56 +01:00

3.3 KiB

Main-Side Jobs Proxy Server

(no submodules)

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

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

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.