I've built SaaS for clients for 13 years. Every project started the same way: 3–4 weeks of auth, billing, admin, email — before a single line of the actual product.
So I extracted all of it into ShipKit: Bun + Elysia + Next.js 16, fourteen production subsystems pre-wired.
Building it in public from here. Day-0 numbers below
@saltyAom Macros are where most of our own plumbing lives too. Rough edge from our 2.0 codemod run: after a void-returning branch, the macro's typing isn't in the route context. Repro's in issue 1959.
@jarredsumner Different layer, but a data point: Elysia's AOT bundler plugin took our boot to healthy from 1.00s to 0.48s, three runs each, production start, no --watch. Idle RSS 200 MB to 145 MB. Throughput didn't move: health -0.3%, DB -7.1%, inside the 10% noise floor we'd set.
restaurantId was two things in one codebase we audited. /restaurants returns an externalVenueId; menu.service.ts:57 passes that same value as an internal terminalId, so /menu/:id/composition comes back empty and never errors. The order route resolves it correctly.
"Do not treat us as the reference." That was the heading another session put above its reply to us, ahead of every technical answer. Under it: protocol-complete, 88 tests. Zero live orders ever processed. The real reference was a ~1,700-line doc from a production deployment.
A probe file we wrote under the media root 404'd on both public hosts, one behind an nginx block untouched for a month. Not the config. namei -l: the file world-readable, every directory world-executable but the app's home, which sits at drwxr-x---. nginx runs as www-data.
@saltyAom Does the adapter trust the Drizzle schema for a table's shape, or introspect the database? In production we declared a geometry column { srid: 4326 }, drizzle-orm 0.44.7 emitted geometry(point), Postgres stored SRID 0. The schema and the table can disagree.
@shadcn Yesterday we posted that BullMQ needs maxRetriesPerRequest: null. False, deleted. It type-checked, it linted, our AI-tell detector scored it clean. Every machine we own agreed. What caught it was a person who opened the library, an hour later, at 3 views.
BullMQ nulls maxRetriesPerRequest only for blocking connections, redis-connection.js:52. A Queue is built with blocking: false, so the null on our producer was ours. With ioredis's offline queue on, an awaited add() hangs instead of throwing while Redis is down. Check yours.
@jarredsumner On our dev bench, isolating the runtime step from 1.3.14 to a 1.4.0 canary came out +19.6% on health and +84.4% on the DB endpoint, with zero code changes. What our bench can't answer is oven-sh/bun#17723: does 1.4 size the heap against the cgroup limit rather than host memory?
Selling self-hosted software means proving an install is licensed without touching the customer's machine. Astervis built it on ShipKit: keys issued on purchase, bound to a hardware fingerprint, validated by each running install. Revoking a seat is remote too.
@rauchg We run Bun and Elysia in production, so on any base image we check what it reports as available memory. Per oven-sh/bun#17723, Bun sizes its heap against host memory, not the cgroup: a process around 500MB under Node can climb toward ~1.2GB in a constrained container.
@rauchg@aisdk In our code the swap really is one argument: the chat helper defaults to an Anthropic model, and any call can pass its own. Spend bounds we wrote ourselves. A client controls the message history, so we clamp it at 50 messages and 100,000 chars of input before the call.
@marclou We draft with AI, then a second model fact-checks it. One batch came back 56 of 56 claims verified. Two were wrong, and we don't know which two, so the only way back is re-reading all 56.
What the 9-package baseline supplied and what the fork added on top — packages/sms, the Payme and Click merchant integrations, apps/courier: https://t.co/9atGH1I2KN
https://t.co/6XjeKOv1kp sells grooming appliances in Uzbekistan, where checkout means Payme, Click, or cash on delivery and sign-in is a phone number plus an SMS code. Our team's boilerplate assumed email and password. The fork swapped in SMS OTP and both local merchant APIs.
ShipKit ships an ai package, an analytics package, a job queue and a worker app today. RunMate predates all four — our team had to write them, and they were folded into the kit later. The boilerplate is downstream of the apps built on it.
On ①: in our own breach the attacker never had to beat the kernel. A compromised deploy user was in the docker group, so they ran a privileged container with / bind-mounted, chrooted to the host, and wrote themselves a NOPASSWD sudoers file. A stronger sandbox boundary doesn't help when the escape path is the daemon API itself.