withSession
withSession<TRole, T>( pool: Pool, auth: { token: string; roleName?: TRole }, fn: (client: PoolClient, ctx: SessionContext<TRole>) => Promise<T>, options?: { scope?: ScopeHook; logger?: Logger },): Promise<T>The default API. It opens a @smplcty/db transaction, resolves the session from
its token, validates it, picks the active role, sets the
identity GUCs, runs your optional
scope hook, runs your callback, and commits —
or rolls back on throw.
const widgets = await withSession(pool, { token, roleName: 'user' }, async (client, ctx) => { // ctx = { userId, activeRole, roles, privileges } const { rows } = await client.query('SELECT * FROM widgets'); return rows;});Active-role selection (in TypeScript, not the resolver)
Section titled “Active-role selection (in TypeScript, not the resolver)”resolve_session is a pure resolver that validates nothing. withSession
picks the active role:
- the requested
roleNameif given — must be one the user holds, elseRoleNotHeldError; - otherwise the user’s sole role, if they hold exactly one — counted by distinct name, so the same role across several tenants still counts as one. An admin who only holds
securitygets it activated without asking, so admins never need the defaultuserrole just to have an active role; - otherwise the user’s default role (
roles.is_default) — the tie-breaker when they hold two or more; - otherwise none — a privilege-only request, which is not an error.
Errors
Section titled “Errors”withSession throws before your callback runs if anything’s wrong:
| Error | When |
|---|---|
SessionNotFoundError | token matches no session |
SessionExpiredError | session has expired (or was revoked) |
RoleNotHeldError | a requested roleName the user does not hold |
InvalidInputError | token is empty / wrong type |
All extend AuthError and carry a code:
try { await withSession(pool, { token, roleName: 'user' }, fn);} catch (err) { if (err instanceof AuthError && err.code === 'SESSION_EXPIRED') { // redirect to login } throw err;}Typed roles
Section titled “Typed roles”roleName defaults to string. Narrow it in a thin wrapper for autocomplete:
export function withSession<T>( pool: Pool, auth: { token: string; roleName?: 'user' | 'settings' | 'security' }, fn: (client: PoolClient, ctx: SessionContext<'user' | 'settings' | 'security'>) => Promise<T>, options?: Parameters<typeof baseWithSession>[3],) { return baseWithSession(pool, auth, fn, options);}