Files
WRNexusJS/packages/rpc
ClintchizandClaude Opus 5 69020b2555
Quality / quality (ubuntu-latest) (push) Failing after 10m7s
Quality / quality (windows-latest) (push) Canceled after 0s
docs: make the component sections executable in one pass
Expands 3.1 and 3.2 so the work can be done without re-deriving anything.

3.1 now records what 0.8.6 already fixed, separated into the ten components
that were miswired and the five that gained outputs they had been firing
undeclared, with the caveat that Map's three were converted but never confirmed
in a browser. For the 22 that remain it adds the finding that changes the
decision: all nine are pure scaffolds with no state, functions or handlers, and
five of them duplicate a component that already works -- FileUpload against
FileInput and FileUploadProgress, Toast and ToastNotifications against Toaster,
AdvancedDatePicker against DatePicker, AdvancedRangeSlider against RangeSlider.
Superseding those is a migration entry rather than new code, and leaves Chart,
TreeView, Confetti and CopyMarkup as the only ones needing to be built.

3.2 corrects the scaffold count from 23 to 28; the earlier figure used a looser
rule. Nine of the 28 are the 3.1 components, so the two items must be planned
together, and several of the rest are primitives that need only their styles
moved out of ui.css rather than any behaviour.

Also corrects the dead-output component count from 11 to 9 in both documents.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 10:34:22 +05:30
..
2026-08-09 01:54:44 +05:30

@wrnexus/rpc

Define a service contract in a shared workspace package, then import that same contract from the caller and callee.

import {
  defineService,
  implement,
  inProcessTransport,
  procedure,
  serviceClient,
} from "@wrnexus/rpc";
import { v } from "@wrnexus/validation";

const greeter = defineService({
  name: "greeter",
  procedures: {
    greet: procedure
      .input(v.object({ name: v.string() }))
      .output<{ message: string }>()
      .build(),
  },
});

const service = implement(
  greeter,
  { greet: async ({ name }) => ({ message: `Hello, ${name}` }) },
  { selfApp: "greeter" },
);

const client = serviceClient(greeter, {
  app: "greeter",
  transport: inProcessTransport({
    "greeter/greet": (input, identity) => service.invoke("greet", input, identity),
  }),
});
await client.greet({ name: "Ada" });

Service files default-export implement(...) from app/services. The development server mounts them under the private /__wrnexus/rpc prefix.

Pass { as: ctx } to serviceClient to propagate the subject. The signed token contains only subject and tenant identifiers; permissions are always checked by the callee. Set WRNEXUS_RPC_SECRET in every app, use at least 32 characters, and never reuse the session secret.

Calls time out by default. Retrying is intentionally deferred; when introduced, only procedures marked .idempotent() may be retried.

Deployment requirement: apps must be unreachable except through the gateway

/__wrnexus/rpc/* is authenticated by TWO signals together: a marker header (x-wrnexus-internal: 1) AND the absence of any X-Forwarded-* header. The WrNexus gateway satisfies this by construction — it strips any inbound marker header from the public request, and it always adds X-Forwarded-* when proxying to an app. A direct loopback call from a sibling app process carries the marker and no forwarded headers, so it passes; anything that came through the gateway carries forwarded headers, so it's rejected even if it also carries the marker.

This check only works if the app process is unreachable except through the gateway. If an app's port is exposed directly, or if a reverse proxy sits in front of it WITHOUT setting X-Forwarded-* (a bare proxy_pass with no proxy_set_header X-Forwarded-For/X-Forwarded-Host/X-Forwarded-Proto), then an external caller can set the marker header itself, arrive with no forwarded headers, and reach /__wrnexus/rpc/* as if it were an internal call — bypassing the gateway's edge block entirely.

Requirements for any deployment:

  • App processes must bind to a private/loopback interface and be reachable ONLY through the gateway (or an equivalent trusted front door) — never exposed directly to the internet or an untrusted network.
  • Any reverse proxy placed in front of an app (nginx, a load balancer, etc.) MUST set X-Forwarded-For, X-Forwarded-Host, and X-Forwarded-Proto on every request it forwards. Omitting these silently reopens the private RPC namespace to anyone who can reach the proxy.