Repository navigation
Conversation
…k, abuse guardrails, e2e tests - db: credits_account / credits_transaction (append-only ledger, (type,refId) idempotency anchor) / generation (processing→succeeded|failed state machine) - lib/wallet: atomic conditional debit (no overdraft under concurrency), replay-safe credit/refund, lazy signup grant - routers/generate: debit-before-model money path; model/R2 failures refund through the ledger; debit failures never refund (transaction aborted = no money moved); client idempotency keys replay original outcome - guardrails: Turnstile (fail-closed when secret set), KV fixed-window rate limit (degrades to pass without binding), prompt blocklist, zod validation - routers/credits + history: balance/ledger/my-generations reads - wrangler: AI / R2 / KV bindings (top-level and dev env) - tests: 20 new (wallet idempotency, concurrency, overdraft, refund-once, generate success/rollback/idempotency, guardrails, history isolation)
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Opening this PR to gauge interest — happy to reshape the scope (e.g. wallet-core only), split it up, or adjust the design to fit the project's direction.
Motivation
Usage-based / prepaid-credits pricing is a common need when building on this starter (AI tools in particular). The
@better-auth/stripeplugin covers subscriptions well, but there is no building block today for:What this adds
DB (
db/schema/credits.ts,db/schema/generation.ts, migration0001):credits_account— one row per user, live balancecredits_transaction— append-only ledger;(type, refId)unique index is the idempotency anchor (signup:{userId}, invoice id, checkout session id, generation id,refund:{generationId})generation—processing → succeeded | failedstate machine with terminal-only transitions and error-kind columnsWallet (
apps/api/lib/wallet.ts):balance >= amount), so concurrent spends cannot drive the balance negative; replayed refs abort the transaction and roll back the speculative updateGenerate pipeline (
apps/api/routers/generate.ts):TURNSTILE_SECRET_KEYis set), KV fixed-window rate limit (degrades to pass without the binding), prompt blocklist, zod input caps, server-generated R2 object keysReads (
routers/credits.ts,routers/history.ts): balance / ledger / my-generations. Read/write live in separate files with all queries in a pure-param layer (lib/generations.ts) — that split also makes a future public gallery trivial to reuse.Tests
20 new Vitest + PGlite cases (idempotency, concurrency, overdraft refusal, refund-once, failure rollback, guardrails, history isolation). Full suite: 113 passing;
tsc --build,oxlintclean.For maintainers
If a credits layer feels out of scope for the starter as a whole, feedback on splitting the wallet core (generic, provider-agnostic) from the generation example (opinionated) is very welcome. Bindings (
ai,r2_buckets,kv_namespaces) are optional inenv.tsand degrade explicitly when absent.