# Partitions One slice of a table per page: the comments for a post, the messages in a room, the tasks for a project. ```ts let comments = new LiveTable(); export default function route(req) { return new html({ comments: comments.view({ postId: req.params.id }) }); } ``` The object passed to `view()` is a set of column equalities. One object does four jobs: - It names the channel. Each partition gets its own pub/sub channel, so a mutation in one post's comments only reaches browsers watching that post. - It narrows the select. Auto-select adds `where post_id = $1`; a custom select receives the object and writes the same clause. - It fills the columns on insert. `comments.insert({ text })` gets `postId` from the view. Passing a different value throws. - It is checked on every mutation. A row naming another partition is refused with `PartitionMismatchError`, before any handler runs and independent of anything the handler checks. Several columns compose the same way, and the order you write them does not matter: ```ts tasks.view({ projectId, status: "open" }) ``` Only a column holding a string, number, or boolean can partition. The value has to become part of a channel name, a SQL parameter, and a string comparison on the browser. ## Authorization lives at the call Nothing about a partition is a permission. `comments.view({ postId })` opens that post's comments for whoever asked, so the route or RPC that calls it is where the check goes, because that is the only place that knows who is asking: ```ts export default function route(req) { session.isLoggedInOrThrow(); return new html({ transfers: transfers.view({ userId: session.getOrThrow("userId") }) }); } ``` `view()` is server-only, and so is the LiveTable it is called on. The browser receives the view already partitioned. There is no way for browser code to widen it, because it never holds the declaration that could. ## A row that changes partition When an update moves a row from one partition to another, `status` going from `"open"` to `"closed"` say, the server broadcasts a delete to the partition it left and an insert to the one it joined. Any column may be a partition key, and a view stays correct when rows move. ## Whole table and partition together A page on `view()` and a page on `view({ roomId })` can both be open. A write through either view broadcasts on the whole-table channel and on the channel of every partition column set the table has been opened with, named by the row's own values, so both pages hear it. That holds for a write through `view()` on the server too, as long as this process has opened a partitioned view of the table before. A freshly started process that has only ever served `view()` reaches the whole-table channel alone, so when a server-side write belongs to a room, make it through `view({ roomId })`. ## Filtering beyond the partition Everything narrower than a column equality is `filter()` in the template: ```html
  • !c.archived)}> ``` A custom `select` may also filter in SQL to keep the first render small, but the stream does not know about that clause: a row broadcast on the partition's channel reaches the browser whether or not the select would have returned it. Keep the template's filter as the rule and treat the select's as an optimization of the snapshot. For a table where the unwanted rows outnumber the wanted ones, use a window (see `windows`) or a narrower partition.