Contributing › Architecture
Architecture
spellcache is four containers and one external data source. The rules behind these choices, and the anti-patterns they rule out, are in Development.
Components
| Component | Role |
|---|---|
web (apps/web) |
Next.js app: pages, Server Actions, sign-in, public share pages, thumbnail proxy. Applies the database migrations at start-up. |
worker (apps/worker) |
Downloads Scryfall’s bulk files every night, upserts the catalogue and prices, then revalues every container. Consumes jobs queued by web (Settings › Administration › Jobs). |
postgres |
One database: the rebuildable catalogue (cards, sets, card_prices, import_runs) and the irreplaceable user data. |
redis (Valkey) |
Job queue between web and worker, the nightly lock, search cache and sign-in rate limits. |
packages/core |
Scryfall client and schemas, image URLs, job queue contract. No Next.js dependency. |
packages/db |
Drizzle schema, migrations, PostgreSQL client, shared test database. |
Card data and images
- The catalogue is a local mirror: no Scryfall API call is made during a user request, except two rate-limited server-side cases (a set released today, a card missing from the catalogue).
- Small images and art crops go through the
webthumbnail proxy, which stores them on thethumbnailsvolume on first request. Large images are loaded from Scryfall’s image CDN by the browser. - Image URLs come from the bulk data, never built by hand.
Data model
collections ─┬─ collection_members (user, role: owner | editor | viewer) └─ containers (kind: collection | binder | deck | list) ├─ holdings (card, finish, condition, language, quantity, deck zone) └─ container_stats (precomputed value)- A collection is a shared space, not an account’s property. Access always
goes through
collection_members; containers never point to a user. - Containers hold everything: the collection root, binders, decks and
lists. A deck is a container whose holdings carry a zone (
main,side,commander) and whose state moves throughplan,assemble,builtanddismantled. - container_stats stores each container’s value, recomputed by the worker after the nightly import, so a value banner reads one row.
- Trades live in
tradesandtrade_lines, and only touch holdings when they are applied.
Nightly run
- The worker’s scheduler (or the host’s cron entry) takes the
spellcache:worker:lock:nightlylock in Valkey, so a night never runs twice. - The bulk import downloads the latest files and upserts cards, sets and
prices, recording the run in
import_runs. - The revaluation recomputes
container_statsfor every container from the new prices.
See Scheduled tasks for the host side.