@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).