Public: anyone can read this; the crew can change it.
A ration a machine can read: src/ration.ts, a bucket per key and a retry that is true #31
Check · Review · Land · Live done — Landed and live in production.
`src/ration.ts` is a new, self-contained token bucket: `take(key, now?, limit?)` returns `{ ok: true, remaining }` or `{ ok: false, retryAfterMs }`, with the clock as a parameter, no timers, no `Request`, no database — keys are strings and the answer is milliseconds, so rounding up for a `retry-after` header stays the door's job.
shipped production is serving it deploy 460b31c
Sign in to join this conversation.
src/ration.tsis a new, self-contained token bucket:take(key, now?, limit?)returns{ ok: true, remaining }or{ ok: false, retryAfterMs }, with the clock as a parameter, no timers, noRequest, no database — keys are strings and the answer is milliseconds, so rounding up for aretry-afterheader stays the door's job.The bucket is stored as one number, the time the schedule has run up to, rather than a tokens-and-timestamp pair. It is the same arithmetic (the header comment gives the formula and the equivalence), with the property the issue asks for: a refusal stores nothing, so the retry is measured against the very number it will later be compared to.
src/ration.test.tstakes a refusal, advances by exactlyretryAfterMsand succeeds, advances one millisecond less and fails — exactly, not within a tolerance. The other tests are hand calculations: twenty tokens counted down to zero then refused at 500 ms, 1.5 s of quiet worth three tokens, half-second refills accumulating (250, 125, 1) instead of truncating, a day of idling still holding only twenty, and one key never spending another's.Two limits live here and nowhere else:
PERSON_WRITES(20 burst, 2/s — whatallow()applies today) andAPP_EVENTS(100 burst, 5/s), each with a sentence on why those numbers. A test assertsPERSON_WRITESaccepts exactly whatallow()accepts, so #8's swap is a no-op in behaviour. Memory is capped at 10,000 keys by insertion-order LRU; the last test floods past the cap twice, assertskeyCount()stops at 10,000, and asserts an evicted key returns with a full bucket.What I chose not to do: nothing calls it.
src/http.tsis untouched andallow()still rations every door, so this pull adds a file and a test and changes nothing that runs. No README change either — there is no behaviour yet to document. No dependencies. The header comment says the ration is in memory, resets on restart and counts per process, and why that beats a SQLite write per request here. Worth a look: theperToken/aheadderivation intake, and the1e-9inremaining, which is there only so the last bit of a float divide cannot hide a whole token a caller holds.🤖 Generated with Claude Code
Closes #24