API Reference
Complete export list for @withnen/client and @withnen/server. Every symbol, its signature, and when to use it.
@withnen/client
NenClient
The main client class. Manages the session lifecycle and encrypts/decrypts requests.
import { NenClient } from '@withnen/client';
const client = new NenClient(baseUrl: string, options?: NenClientOptions);
| Option | Type | Default | Description |
|---|---|---|---|
identityMode | 'tls' | 'pqc' | 'tls' | 'pqc' adds a one-time ML-DSA-65 identity signature over your ephemeral ML-KEM key at handshake. Prevents active MITM on first contact. |
Methods
// Establish (or re-establish) a session. Must succeed before nenFetch / nenStream.
await client.handshake(): Promise<void>
// Replace the current session with a fresh one. Old session is destroyed.
await client.rotate(): Promise<void>
// Destroy the current session (logout / forward secrecy).
await client.terminate(): Promise<void>
// Encrypted fetch — mirrors the fetch() API. JSON responses are auto-decrypted.
await client.nenFetch(path: string, init?: RequestInit): Promise<unknown>
// Encrypted SSE stream — yields decrypted string chunks.
client.nenStream(path: string, init?: RequestInit): AsyncIterable<string>
createNenFetch
Factory that creates a nenFetch function with lazy handshake. Handshakes automatically on the first call — use this for simple cases where you don't need explicit lifecycle control.
import { createNenFetch } from '@withnen/client';
const nenFetch = createNenFetch(baseUrl: string, options?: NenClientOptions);
const data = await nenFetch('/api/secure-data', {
method: 'POST',
body: JSON.stringify({ amount: 1840 }),
});
createNenStream
Factory for an encrypted SSE stream with lazy handshake.
import { createNenStream } from '@withnen/client';
const nenStream = createNenStream(baseUrl: string, options?: NenClientOptions);
for await (const chunk of nenStream('/api/chat', { method: 'POST', body: JSON.stringify({ prompt }) })) {
process.stdout.write(chunk);
}
NenError (client)
Thrown by all client operations on failure. Carries a stable ISO-xxxx code.
import { NenError } from '@withnen/client';
try {
await nenFetch('/api/secure-data', { method: 'POST', body: '...' });
} catch (err) {
if (err instanceof NenError) {
console.log(err.code); // e.g. 'ISO-2002'
console.log(err.message); // human-readable, safe to display
}
}
describeNenCode (client)
Reverse-lookup a code from the client catalog. Returns undefined for server-only codes.
import { describeNenCode } from '@withnen/client';
describeNenCode('ISO-2001');
// → { code: 'ISO-2001', status: 409, message: '…', hint: '…' }
@withnen/server
withNen
Middleware wrapper for a Next.js App Router route handler. Verifies the per-request HMAC, enforces the 30s replay window, decrypts the body, and encrypts the return value.
import { withNen } from '@withnen/server';
export const POST = withNen(
async (req: Request, body: unknown) => {
// `body` is already decrypted and authenticated.
return { ok: true, received: body };
},
{ strict: true } // default — set false ONLY for legacy clients that cannot sign
);
| Option | Type | Default | Description |
|---|---|---|---|
strict | boolean | true | When true, a request without a valid HMAC signature is rejected with ISO-3001. The auth-downgrade guard. Never disable in production. |
withNenStream
Stream wrapper. The handler returns an AsyncIterable<string>; each yielded chunk is encrypted independently and sent as an SSE frame.
import { withNenStream } from '@withnen/server';
export const POST = withNenStream(async (req: Request, body: unknown) => {
async function* tokens() {
for (const word of ['secure', 'streaming', 'works']) yield word + ' ';
}
return tokens();
});
The response carries X-Nen-Stream-Nonce and Content-Type: text/event-stream. The client reads it with client.nenStream() or createNenStream().
Session route handlers
Mount all four under a single dynamic route at src/app/api/nen/[action]/route.ts:
import { handleHandshake, handleRotate, handleTerminate, handleStatus } from '@withnen/server';
export async function POST(req: Request, { params }: { params: Promise<{ action: string }> }) {
const { action } = await params;
switch (action) {
case 'handshake': return handleHandshake(req);
case 'rotate': return handleRotate(req);
case 'terminate': return handleTerminate(req);
default: return new Response('Not Found', { status: 404 });
}
}
export async function GET(req: Request, { params }: { params: Promise<{ action: string }> }) {
const { action } = await params;
if (action === 'status') return handleStatus(req);
return new Response('Not Found', { status: 404 });
}
Session stores
import {
setSessionStore,
InMemorySessionStore,
RedisSessionStore,
UpstashSessionStore,
} from '@withnen/server';
| Store | Runtime | When to use |
|---|---|---|
InMemorySessionStore | Any | Development and single-instance deployments only. Sessions live in process memory — a second instance will not find them (ISO-2002). |
RedisSessionStore(client) | Node.js / serverless with TCP | Any multi-instance or serverless deployment where TCP is available. Pass your ioredis or node-redis client. |
UpstashSessionStore(url, token) | Edge runtimes | Vercel Edge, Cloudflare Workers, or any runtime without TCP. Uses the Upstash REST API over fetch — no extra dependency. |
// Edge — set these env vars in your dashboard
setSessionStore(new UpstashSessionStore(
process.env.UPSTASH_REDIS_REST_URL!,
process.env.UPSTASH_REDIS_REST_TOKEN!,
));
NenError (server)
Thrown inside middleware on every authentication or crypto failure. .toResponse() logs the hint server-side and returns a safe { error: { code, message } } JSON Response — the internal hint is never sent to the client.
import { NenError } from '@withnen/server';
// Inside a custom handler or middleware:
throw new NenError('AUTH_SIGNATURE_MISSING');
// Or wrap an unknown error:
const err = NenError.from(unknownThrown);
return err.toResponse();
describeNenCode (server)
Reverse-lookup any ISO-xxxx code from the full server catalog.
import { describeNenCode } from '@withnen/server';
describeNenCode('ISO-3001');
// → { code: 'ISO-3001', status: 401, message: 'Request signature missing', hint: '…' }
See the Error codes reference for the full catalog.
@withnen/ai
Thin wrappers over nenStream that mirror the official OpenAI and Anthropic SDK shapes. Prompts and streamed tokens are encrypted from the browser to your backend — not from your backend to the model provider.
createSecureOpenAI
import { createSecureOpenAI } from '@withnen/ai';
const ai = createSecureOpenAI({
baseUrl: 'https://app.yourdomain.com', // YOUR backend, not OpenAI
endpoint: '/api/ai/chat', // optional, default '/api/ai/chat'
});
// Streamed — yields decrypted string deltas
for await (const delta of ai.chat.completions.stream({ messages })) {
process.stdout.write(delta);
}
// Non-streamed — resolves decrypted JSON
const result = await ai.chat.completions.create({ messages });
createSecureAnthropic
import { createSecureAnthropic } from '@withnen/ai';
const ai = createSecureAnthropic({
baseUrl: 'https://app.yourdomain.com',
endpoint: '/api/ai/messages', // optional, default '/api/ai/messages'
});
for await (const delta of ai.messages.stream({ messages })) {
process.stdout.write(delta);
}
withSecureAI (server)
import { withSecureAI } from '@withnen/ai/server';
// Wraps withNenStream — decrypt the prompt, call the model, stream back encrypted tokens
export const POST = withSecureAI('openai');
See the Secure AI page for the full trust boundary and what this protects.