21. #ucm/server reference

Everything server code can import from #ucm/server. Importing any of it makes a function run on the server (see web apps). For how these fit together, see the guides: server functions, data, background jobs.

import { request, session, setSession, table, enqueue, HttpError } from "#ucm/server";

21.1 Requests

function request(): ServerRequest;

interface ServerRequest {
  method: string;
  url: string;
  headers: Headers;
  cookies: Record<string, string>;
  ip: string | undefined;   // the client's address, as the socket sees it
  signal: AbortSignal;      // aborted when the client disconnects
}

The request the current server function is answering. It's available anywhere in the call, through any number of awaits. Outside a request (a job, a test without withRequest) it throws.

21.2 Cookies

function cookie(name: string): string | undefined;
function setCookie(name: string, value: string, opts?: CookieOptions): void;
function deleteCookie(name: string, opts?: { path?: string }): void;

interface CookieOptions {
  maxAge?: number;          // seconds
  path?: string;
  httpOnly?: boolean;       // default true: not readable by browser scripts
  secure?: boolean;         // default true when served over TLS
  sameSite?: "Strict" | "Lax" | "None";   // default "Lax"
}

21.3 Sessions

function session(): Record<string, unknown>;
function setSession(data: Record<string, unknown>, maxAge?: number): void;
function clearSession(): void;
function revokeSessions(match: (data: Record<string, unknown>) => boolean): number;

21.4 Errors and limits

class HttpError extends Error {
  constructor(status: number, message: string, opts?: { retryAfter?: number });
  readonly status: number;
  readonly retryAfter?: number;   // seconds, sent as Retry-After
}

function rateLimit(key: string, opts: { max: number; per: number }): void;

21.5 Tables

function table<T>(name: string, opts?: TableOptions<T>): Table<T>;

interface Table<T> {
  readonly name: string;
  insert(value: T): Promise<string>;                 // returns the new row's id
  put(id: string, value: T): Promise<void>;          // store under id, replacing what was there
  get(id: string): Promise<T | undefined>;
  delete(id: string): Promise<boolean>;
  update(id: string, fn: (current: T) => T): Promise<T | undefined>;   // atomic read-modify-write
  find(where?: Partial<T>, opts?: FindOptions<T>): Promise<{ id: string; value: T }[]>;
  findOne(where?: Partial<T>, opts?: Omit<FindOptions<T>, "limit">): Promise<{ id: string; value: T } | undefined>;
  count(where?: Partial<T>): Promise<number>;
}

interface TableOptions<T> {
  migrate?: ((old: any) => T | Promise<T>) | readonly ((old: any) => unknown)[];
  unique?: readonly (keyof T & string)[];
  index?: readonly (keyof T & string)[];
  renamedFrom?: string;
}

interface FindOptions<T> {
  orderBy?: keyof T & string;
  desc?: boolean;
  limit?: number;
  offset?: number;
}

Declare a table at the top level: export const users = table<User>("users"). Rows are checked against T on every write, and rows stored as an older version of T are checked, migrated or refused when read. find returns rows whose fields equal where's, oldest first unless ordered. See data for migrations, indexes and the rest.

21.6 Transactions

function transaction<R>(fn: () => R | Promise<R>): Promise<R>;

All of fn's table writes happen, or, if it throws, none do. Other requests' table calls wait while it runs. Jobs enqueued inside are queued when it commits and dropped if it rolls back. Nested calls are savepoints. Keep it short, and don't await anything inside that itself needs a table from outside it.

21.7 Background jobs

function enqueue<A extends unknown[]>(fn: (...args: A) => unknown, ...args: A): string;
function enqueueAfter<A extends unknown[]>(seconds: number, fn: (...args: A) => unknown, ...args: A): string;
function every(spec: string, fn: () => unknown): RecurringJob;

21.8 For tests

function withRequest<T>(
  init: { method?: string; url?: string; headers?: Record<string, string>; cookies?: Record<string, string>; ip?: string },
  fn: () => T | Promise<T>,
): Promise<{ value: T; setCookies: string[] }>;

function runJobs(opts?: { all?: boolean }): Promise<{ done: number; failed: number }>;

Under ucm test, tables and jobs live in memory. See testing.

built from 5890e54f