@supersuit/bring-up
v0.1.2
Published
To Bring Up: each person's private list of things to raise the next time they talk. Rules, a Firestore store, Next.js route handlers and a React list, for any app that says who is signed in and where items are kept.
Maintainers
Readme
@supersuit/bring-up
To Bring Up: each person's own private list of things to raise the next time they talk to someone, so the day is not a stream of texts. Nobody else can see a list, not even that it exists: an item that is not yours answers exactly as one that does not exist. Items are checked off as they are talked about, and a person's own agent can add to their list from the desk.
The package knows how the feature works. The app says who is signed in, which agent key acts as which person, where items are kept, and how they look.
Install
npm install @supersuit/bring-upPeers: react and react-dom 19, and firebase-admin 13 for the server entry. With Tailwind 4,
add one line to the stylesheet that imports Tailwind, so the list's classes are generated:
@source "../node_modules/@supersuit/bring-up/dist";Three entry points
@supersuit/bring-up: the types and every rule, pure, safe anywhere (sortAgenda,agendaTilefor a home tile,applyAgendaPatch, ...).@supersuit/bring-up/server:createBringUpStore(host)andcreateBringUpHandlers(host). Server only.@supersuit/bring-up/client:AgendaList,BringUpProvider,setTheme.
The host
import type { BringUpHost } from '@supersuit/bring-up/server'
export const host: BringUpHost<'a' | 'b'> = {
member: async () => /* who is signed in, or null */ null,
agentMember: async (req) => /* the person this request's agent key acts as, or null */ null,
db: () => getFirestore(), // called on use, never at import
collection: 'agenda', // the list items are kept in
// isRefusal: (err) => err instanceof MyAppRefusal, // optional
}What the host is responsible for
agentMember is where the package hands off a security decision, and the package cannot check it
for you. The host must:
- compare the presented agent key in constant time (for example
crypto.timingSafeEqualover equal-length digests), never with===; - map each key to exactly one member, and never let two members share a key, because the key alone decides whose list an agent reads and writes;
- return
nullfor a missing, malformed or unknown key.
member must likewise return only the person the request is authenticated as.
isRefusal
This package's own refusals (RuleError) always reach the client with their message and status:
403 private for a stranger, 401 unauthorized for an agent without a valid key, 404 not found
for an item that is not yours, 400 for a body it cannot take. Every other error is logged on the
server and answers an opaque 500.
An app with refusals of its own passes isRefusal to say which of ITS errors may be shown too. The
hook ADDS to the package's refusals and never replaces them. An error is never shown merely because
it carries a 4xx status. A refusal answers with its status when that is 400-499, and 403 when it
has none; a refusal carrying any other status is a bug and is answered as an internal error.
Mounting the handlers
// app/api/agenda/route.ts
export const runtime = 'nodejs'
export const { GET, POST, DELETE } = createBringUpHandlers(host).list
// app/api/agenda/[id]/route.ts: export const { PATCH, DELETE } = createBringUpHandlers(host).item
// app/api/agenda/agent/route.ts: export const { GET, POST } = createBringUpHandlers(host).agentGET lists the signed-in person's items, POST { text } adds one (201), and DELETE on the list
clears what is checked off ({ cleared }). On an item, PATCH { kind: 'done', value } or
{ kind: 'text', text } changes it, and DELETE removes it (204). The agent routes read and add
to the one list the key's person owns. A refusal answers { error } with its status; a stranger
gets 403, an agent without a valid key 401.
The screen
<BringUpProvider theme={{ serif, ink, muted, hairline, onInk }}>
<AgendaList initial={items} otherName="the other person" apiBase="/api/agenda" />
</BringUpProvider>initial comes from createBringUpStore(host).listAgenda(member) in a server component.
Releasing
A release is a version tag. Bump version (npm version <x.y.z> --no-git-tag-version), add the
CHANGELOG entry, commit package.json and package-lock.json together (npm ci refuses a release whose lockfile does not match the version), push, then
git tag v<x.y.z> && git push origin v<x.y.z>. GitHub Actions tests, builds and publishes through
npm trusted publishing. Nothing is ever published from a laptop.
