# Server-side usage

Build per-request clients from cookies, verify sessions, and hydrate browser clients from the server.

`PolyesterServerClient` is the core client plus session awareness. It parses the cookies your browser client sets, uses the bearer token for authenticated calls, and exposes what it knows about the user for rendering.

## One client per request

Create the client from the incoming request (or your framework's cookie API) in the handler:

```ts
import {
	createPolyesterServerClientFromRequest,
	POLYESTER_DEVNET_ENVIRONMENT,
} from "@polyester/sdk";

export async function handle(request: Request) {
	const client = createPolyesterServerClientFromRequest({
		environment: POLYESTER_DEVNET_ENVIRONMENT,
		request,
	});

	if (client.hasUsableBearerToken) {
		const balances = await client.balances.list();
		// ...
	}
}
```

Clients are cheap (services, realtime, and catalog are lazy), so per-request creation is the intended pattern.

With a framework cookie API instead of a `Request`, await an asynchronous store before passing it to the SDK. For example, current Next.js versions:

```ts
import { cookies } from "next/headers";
import {
	createPolyesterServerClientFromCookies,
	POLYESTER_DEVNET_ENVIRONMENT,
} from "@polyester/sdk";

const client = createPolyesterServerClientFromCookies({
	environment: POLYESTER_DEVNET_ENVIRONMENT,
	cookies: await cookies(),
	cookieLocation: { hostname: "localhost", port: "3000", protocol: "http:" },
});
```

The factory accepts a `Request`, a plain `{ [name]: value }` record, or a synchronous `.get(name)` store that returns a string or `{ value: string }`. A `Request` supplies its own URL. For a framework cookie store on loopback, `.localhost`, or an RFC1918 private IPv4 host, provide `cookieLocation` for the page request. The SDK scopes local bearer and display-session cookie names by port, including default HTTP (`80`) and HTTPS (`443`) ports, so it needs that location to select the right cookies. Public hosts keep stable names.

## Display session vs authenticated session

Two cookies. The SDK keeps them apart on purpose:

| Cookie            | Contents                                                      | Trust level                       |
| ----------------- | ------------------------------------------------------------- | --------------------------------- |
| Session cookie    | Who the user appears to be: username, active account, wallets | Unsigned. Display only.           |
| Auth token cookie | The JWT bearer token                                          | Proof, but verify before trusting |

```ts
client.hasDisplaySession; // can I render a username immediately?
client.session; // the parsed ServerSessionSnapshot
client.hasBearerToken; // is a token present?
client.hasUsableBearerToken; // ...and not expired?

const me = await client.verifySession(); // backend-verified identity, or null
```

Use the display session for instant, non-sensitive rendering (avatar, username). Call `verifySession()` before anything that must be correct. It returns `null` only when credentials are missing or rejected as unauthenticated; transport and backend failures throw, so an outage is not mistaken for a logout. Data requests are still authorized by the backend on every call.

A missing or malformed display cookie does not discard a valid bearer cookie. An explicitly different-environment display cookie is rejected as a whole, so its snapshot and bearer are not used for the requested environment.

> **Never authorize from the display session**
>
> The session cookie is client-writable. The SDK types it as display-only (`ServerSessionSnapshot`) on purpose. Authorization belongs to the backend, which validates the bearer token on each request.

### Subaccount defaulting

By default, server clients scope calls to the main account. If your account switcher should carry over to SSR, opt in:

```ts
import { createPolyesterServerClientFromRequest } from "@polyester/sdk";

const client = createPolyesterServerClientFromRequest({
	environment,
	request,
	useDisplaySessionActiveAccountAsDefault: true,
});
```

That treats the display session's active account as caller intent for calls that omit `account`. The backend still enforces access.

## Hydrating the browser

Two handoffs keep client startup quiet.

Auth state: render without a logged-out flash, then restore:

```ts
import { PolyesterBrowserClient } from "@polyester/sdk";

const client = new PolyesterBrowserClient({ environment });
client.auth.hydrateAuthState({
	mainAccountId,
	username,
	activeAccountId,
});
await client.auth.restoreSession();
```

Catalog snapshot: skip the reference-data fetch by baking the server's catalog into the page:

```ts
import { PolyesterBrowserClient } from "@polyester/sdk";

// server
await serverClient.catalog.ensureReady();
const snapshot = serverClient.catalog.snapshot();

// browser
const client = new PolyesterBrowserClient({
	environment,
	catalogSnapshot: snapshot,
});
```

## Lower-level helpers

`@polyester/sdk/server-session` exposes the primitives without a client: `parseSessionCookie(cookies, environment, options?)`, `emptyServerSessionSnapshot()`, and `isJwtValid(token)`. Read a bearer token through the parsed snapshot:

```ts
import { parseSessionCookie } from "@polyester/sdk/server-session";

const { bearerToken } = parseSessionCookie(cookies, environment, {
	cookieLocation: new URL(request.url),
});
```

Use the incoming request URL for `cookieLocation` when reading a framework store or cookie record. A `Request` source supplies its own location. Pass `tokenCookieName` in the options when browser storage uses a custom bearer-cookie base name.

The root export has the cookie-name constants `POLYESTER_AUTH_TOKEN_COOKIE_NAME` and `POLYESTER_SESSION_COOKIE_NAME`.

`isJwtValid` accepts `unknown` and returns `false` for missing, malformed, or expired values.

## Edge runtimes

The SDK runs on edge/serverless (Cloudflare Workers, Vercel Edge): fetch-based, ESM-only, heavy deps deferred. Realtime subscriptions work from any long-lived server process, but not from an SSR render pass. When rendering a page, fetch a snapshot server-side and subscribe after hydration. See [Streaming](https://testnet.polyester.com/docs/sdk/typescript/guides/streaming).
