@polynomialhq/server
Token signing, room management, storage inspection, and server events for trusted Node.js backends.
pnpm add @polynomialhq/serverThis package uses node:crypto and belongs in a trusted Node.js runtime. Never import it into a
browser bundle or an edge runtime without Node APIs.
PolynomialServer
import { PolynomialServer } from "@polynomialhq/server";
const polynomial = new PolynomialServer({
secretKey: process.env.POLYNOMIAL_SECRET_KEY!,
});PolynomialServerOptions
Prop
Type
The constructor throws RoomTokenError if secretKey does not begin with sk_.
Authorize a room
const token = polynomial.authorizeRoom({
expiresInSeconds: 3600,
metadata: { organizationId: "org_acme" },
projectId: "project_123",
role: "write",
roomId: "document:42",
userId: "user_123",
userInfo: {
avatar: "https://example.com/avatar.png",
name: "Ada",
},
});AuthorizeRoomInput requires projectId, roomId, and userId. It also accepts
expiresInSeconds (default 1800), metadata, role (default "write"), and userInfo.
authorizeRoom() and identifyUser() sign locally with HMAC-SHA256 and return a string
synchronously. They make no network request, so there is nothing to await and nothing to retry.
RoomRole is "read" | "write" | "admin".
Identify a user
const token = polynomial.identifyUser({
expiresInSeconds: 3600,
groupIds: ["team_design"],
organizationId: "org_acme",
projectId: "project_123",
userId: "user_123",
userInfo: { name: "Ada" },
});IdentifyUserInput supports user, group, organization, project, expiry, and public user
information. Only userId is required.
Manage rooms
All room-management methods are asynchronous, hit the management API, and are authenticated with the server secret as a bearer token.
createRoom
const room = await polynomial.createRoom({
defaultRole: "write",
metadata: { documentId: "doc_42" },
projectId: "project_123",
roomId: "document:42",
});Accepts CreateRoomInput.
getRoom
const room = await polynomial.getRoom({
projectId: "project_123",
roomId: "document:42",
});Accepts GetRoomInput. Throws PolynomialApiError with status: 404 when the room does not
exist.
listRooms
const rooms = await polynomial.listRooms({
projectId: "project_123",
});Accepts ListRoomsInput and returns every room in the project.
updateRoom
const room = await polynomial.updateRoom({
defaultRole: "read",
metadata: { archived: true },
projectId: "project_123",
roomId: "document:42",
});Accepts UpdateRoomInput. Fields left undefined are omitted from the request body rather than
sent as nulls, so a partial update leaves the other fields untouched.
upsertRoom
const room = await polynomial.upsertRoom({
create: {
defaultRole: "write",
metadata: { documentId: "doc_42" },
},
projectId: "project_123",
roomId: "document:42",
update: {
metadata: { lastOpenedAt: new Date().toISOString() },
},
});Accepts UpsertRoomInput. create applies when the room is new and update when it already
exists.
deleteRoom
const deletedRoom = await polynomial.deleteRoom({
projectId: "project_123",
roomId: "document:42",
});Accepts DeleteRoomInput. Deletion is a soft delete: the returned room has state: "deleted" and
a populated deletedAt.
RoomManagementRoom
Room-management methods return:
| Field | Type |
|---|---|
id | Internal room record ID |
projectId | Owning project |
roomId | Application room identifier |
defaultRole | RoomRole |
metadata | Record<string, unknown> |
state | RoomState ("active" | "deleted") |
createdAt | ISO timestamp |
updatedAt | ISO timestamp |
deletedAt | ISO timestamp or null |
roomId is the identifier your application and room tokens use. id is the internal record ID and
should not be used for authorization.
Inspect persisted storage
const storage = await polynomial.getRoomStorage({
projectId: "project_123",
roomId: "document:42",
});GetRoomStorageInput identifies the room. RoomStorageDocument contains:
snapshot: the latestYjsSnapshotDocument, ornull;updates: tailYjsUpdateDocumententries after the snapshot.
Both formats use base64 encoding and include byte length and sequence metadata.
This returns the persisted Yjs representation, not the decoded application JSON. To read the
document server-side, apply the snapshot and then the tail updates to a Y.Doc with the yjs
package.
Broadcast from the server
await polynomial.broadcastEvent({
event: {
type: "export.completed",
exportId: "export_123",
},
projectId: "project_123",
roomId: "document:42",
});BroadcastRoomEventInput contains a BroadcastRoomEvent, projectId, and roomId. The event
object must have a type string; browser listeners receive it under that name.
Errors
PolynomialApiError
Management API failures throw PolynomialApiError, which includes the numeric HTTP status:
try {
await polynomial.getRoom({ projectId, roomId });
} catch (error) {
if (error instanceof PolynomialApiError && error.status === 404) {
// Room does not exist.
}
}The message is taken from the API response's error field when present.
RoomTokenError
Invalid secret keys, malformed tokens, signature failures, and expired tokens throw
RoomTokenError. This is a signing and verification error, never a transport error.
Token helpers
Use these functions when a class instance does not fit your integration:
createRoomToken
const token = createRoomToken({
now: new Date(),
projectId,
role: "write",
roomId,
secretKey,
userId,
});Accepts AuthorizeRoomInput plus secretKey and optional now. now is the base for iat and
exp, which makes tokens deterministic in tests.
verifyRoomToken
const claims = verifyRoomToken(token, {
secretKey,
});Returns RoomTokenClaims after checking the key ID, HMAC signature, schema, and expiry. The
signature comparison is constant-time.
createIdentityToken and verifyIdentityToken
These mirror the room helpers for IdentifyUserInput and IdentityTokenClaims:
const identity = createIdentityToken({
secretKey,
userId: "user_123",
});
const claims = verifyIdentityToken(identity, { secretKey });Room tokens and identity tokens are not interchangeable: they carry different aud claims and each
verifier rejects the other kind.
Token schemas
The package exports Zod schemas for validation and introspection:
roomRoleSchemaroomStateSchemaroomTokenClaimsSchemaidentityTokenClaimsSchema