Skip to main content

@amphoze/cns-server

The backend SDK: emit as an app/publisher (service) principal, and mint + inject render-tokens as the witness for protected content you serve. Re-exports everything from @amphoze/cns-core as well.

Construction

import { CNSServer } from "@amphoze/cns-server";

const server = new CNSServer({
baseUrl: "https://cns.example.com",
getServiceToken: () => mintOrCacheServiceToken(), // NOT an end-user token — see below
fetchImpl: undefined,
});

emit(eventType, input?)

emit(eventType: string, input?: Omit<EmitEventInput, "event_type">): Promise<CNSEvent>

POSTs to /v1/events authenticated as the app/publisher credential. The ingestor derives actor and tenant_id server-side from the service-JWT — this method never sets them directly. Unlike CNSClient.emit(), it does not auto-populate session_id/seq/event_time/monotonic_offset_ms for you — pass your own if ordering matters for this event stream. You may set actor_subject_id to assert which tenant subject performed the action (see Emitting events).

:::caution getServiceToken — minting is not implemented in this SDK This function must already return a valid, signed EdDSA service-JWT: {app, tenant, key_ver, iss: "cns-service-auth", iat, exp}. No package in the TS SDK mints one. The only implementation shipped today is the Go core's internal/core/trust.ServiceMinter — verified by searching the entire sdk/ts tree for any service-token helper and finding none. A non-Go backend needs to hand-roll this JWT (EdDSA, using the private key your secrets adapter materialized for this app) until a TS/Python minting helper ships. This is a genuine SDK gap, not a configuration step you're missing. :::

serveWithProof(opts, carrier)

serveWithProof(
opts: MintRenderTokenOptions, // app, tenant, subject, docId, keyVersion, privateKey, ttlSeconds?
carrier: HeaderSetter | { html: string } | { json: Record<string, unknown> },
): Promise<{ token: string; html?: string; json?: Record<string, unknown>; headerInjected?: boolean }>

Mints a render-token locally — no network call to CNS — using the per-app ed25519 private key your own secrets adapter already materialized into opts.privateKey, and injects it into whichever carrier shape you pass:

// Node/Express-style response object → X-Notify-Proof header
const { token, headerInjected } = await serveWithProof(
{ app: "docs-svc", tenant: "acme", subject: subjectId, docId, keyVersion, privateKey },
res,
);

// SSR HTML body → <meta name="notify-proof"> injected into <head>
const { html } = await serveWithProof(opts, { html: renderedPage });

// JSON API body → _proof field added
const { json } = await serveWithProof(opts, { json: responseBody });

This backend is the witness (spec's trust model) — never fetch signing material inside this call; it only imports what your secrets adapter already hydrated.

What's not here

No method reads GET /v1/feed or GET /v1/stream — those are subscribe/read-plane operations for an authenticated end user, not something an app/publisher credential is authorized to do (see Authz planes).