cursedbelt-server
v4.22.0
Published
The app-facing Bun/Hono server tier of the cursedbelt split — storage, sharing, activity, guard, sync. React-free; cursedbelt-core below it.
Readme
cursedbelt-server
The server tier of the cursedbelt split: Hono on Bun, the D1 seam a Worker crosses, the
binary-server client, the guard, and the fleet standards (retention, activity, notifications,
engagement). cursedbelt-core is below it, the React design system cursedbelt above it.
bun add cursedbelt-server🔴 The root export is a BARREL — import the leaf
import … from "cursedbelt-server" re-exports everything, so it reaches every optional peer and
bun:sqlite at once (the table below). An app that wants one function pays for all of them, and
on a Worker wrangler deploy fails to bundle it. Import the subpath that owns the symbol —
cursedbelt-server/login-throttle, cursedbelt-server/binary-store, cursedbelt-server/d1 —
never the root. package.json exports is the list of subpaths.
Why this stays prose: which symbol an app WANTS is decided in the app, and a repo's gate proves that repo — so the barrel-importer grep belongs to the generation's tools, not to this package.
What you install
Most subpaths need nothing beyond hono (and zod where they validate). These are the only
ones that statically reach an OPTIONAL peer, or a bun builtin a Worker does not have. The table
is checked: src/readmeInstallTable.spec.ts fails when it disagrees with src/subpathReach.ts,
and src/barrelsReachNoOptionalPeer.spec.ts fails when that disagrees with what a bundler
actually keeps.
| subpath | optional peers it imports | bun builtins it needs |
|---|---|---|
| . | kysely, kysely-bun-sqlite, otplib, plainjob | bun:sqlite |
| ./jobs | plainjob | — |
| ./d1/backup-local | — | bun:sqlite |
| ./guard | — | bun:sqlite |
| ./guard/revocations | — | bun:sqlite |
| ./sqlite | — | bun:sqlite |
| ./engagement | — | bun:sqlite |
| ./request-log | — | bun:sqlite |
sharp and @node-rs/argon2 are loaded lazily (await import()), so a missing one breaks only
the feature that asks for it, not the build.
🔴 Bun.serve's websocket selects an OVERLOAD
It is not an optional field. An options object whose websocket may be undefined — a
conditional value or a conditional spread — matches no overload, and TypeScript reports an error
about the whole options object rather than the one field. Use serveBun(app, { websocket }),
which takes it as a genuinely optional field and makes the two concrete calls itself.
src/server/serveBunOverload.spec.ts runs tsc on the trap and goes red when bun's types change.
Standards
docs/retention.md, docs/activity.md, docs/notifications.md, docs/engagement.md — the
contracts every app that uses those modules is agreeing to. Cite them from an app with the
package prefix (cursedbelt-server/docs/retention.md).
Verifying
cd "$FORGE/libs/cursedbelt-server" && bun run verify