GraphQL API for the hololive Official Card Game (hOCG). Scrapes card data from the official English website and serves it via a public GraphQL endpoint. Hosted on Cloudflare Workers with D1 (SQLite).
Single Cloudflare Worker application with two deployment environments managed by Cloudflare's Git integration:
| Environment | URL | D1 Database | Trigger |
|---|---|---|---|
| Production | https://api.oshi.cards |
oshicard-db-prod |
Push to main |
| Preview | Auto-generated preview URL | oshicard-db (override in CF dashboard) |
Push to any other branch / PR |
Preview D1 binding is configured in the Cloudflare dashboard under Worker Settings → Bindings → Preview environment.
- Runtime: Cloudflare Workers (TypeScript)
- Database: Cloudflare D1 (SQLite)
- GraphQL: graphql-yoga
- HTML Parsing: cheerio
- Build/Deploy: Cloudflare Git integration (auto-builds on push)
- Automated Scraping: GitHub Actions (daily cron)
wrangler.toml # Workers config, D1 binding, custom domain
schema.sql # D1 database schema (run with wrangler d1 execute)
scrape-all.sh # Local scrape script (--dev flag for dev)
.github/workflows/
scrape-prod.yml # Daily cron (3 AM UTC) — scrapes prod DB
scrape-dev.yml # Manual trigger only — scrapes preview DB
src/
index.ts # Worker entry: GraphQL + scrape endpoints
types.ts # Shared TypeScript types (DB rows, parsed cards, env)
schema/
typeDefs.ts # GraphQL type definitions (Card, filters, enums)
resolvers.ts # GraphQL resolvers (queries D1)
db/
queries.ts # SQL query builders (upsert, search, filters)
scraper/
index.ts # Scrape helpers (getPageIds, scrapePage)
parseList.ts # Extracts card IDs from search result HTML
parseDetail.ts # Parses card detail pages into structured data
client.ts # HTTP fetch with 200ms delay + retry
npm run dev # Start local dev server (wrangler dev)
npm run deploy # Manual deploy to production
npm run db:init # Apply schema.sql to local D1
npm run db:init:remote # Apply schema.sql to remote prod D1
./scrape-all.sh # Scrape production environment
./scrape-all.sh --dev # Scrape dev/preview environmentProduction deploys happen automatically via Cloudflare Git integration on push to main.
Card types: holomem, oshi, support, cheer. Buzz holomem are stored as holomem with is_buzz = 1. Support cards have a support_type (Item, Staff, Mascot, Fan, Event, Tool) and optional is_limited flag.
Colors are stored as uppercase enums: RED, GREEN, BLUE, WHITE, PURPLE, YELLOW, NEUTRAL.
Art costs and baton pass are stored as JSON arrays of color strings (e.g., ["RED", "COLORLESS", "COLORLESS"]). COLORLESS represents the colorless/neutral cost icon.
Unique constraint is on id (INTEGER PRIMARY KEY) — the official site's card ID.
release_date holds the oldest release date across every product the card appears in, stored as ISO YYYY-MM-DD so the RELEASE_DATE sort is chronological (SQLite compares TEXT lexicographically). GraphQL serves the display string as releaseDate and the stored value as releaseDateISO; conversion both ways lives in src/utils/releaseDate.ts.
Related tables:
card_arts— holomem moves/attackscard_oshi_skills— oshi and SP oshi skillscard_tags— hashtags like #EN, #Gen 1card_sets— set names (cards can belong to multiple sets)card_qna— Q&A pairs from card detail pages
All text fields are sanitized to replace Unicode non-breaking spaces (U+00A0) with regular spaces.
- Source:
https://en.hololive-official-cardgame.com/cardlist/ - Phase 1:
/scrape-page-ids?page=Npaginates search results to collect card IDs - Phase 2:
/scrape-one?id=Nfetches and parses each card detail page with cheerio - GitHub Actions scrape-prod runs daily at 3 AM UTC
- GitHub Actions scrape-dev is manual trigger only
- 200ms delay between card fetches, 1s delay between pages
- Card detail HTML uses
.cardlist-Detailcontainer,h1.namefor card name,dl > dt/ddfor fields,.oshi.skill/.sp.skillfor oshi skills,div[class*='arts']for arts,div.extrafor extra text,.cardlist-Detail_QA .qa-List_Itemfor Q&A
GET /— health check with card countGET /scrape-page-ids?page=N— returns array of card IDs on a search pageGET /scrape-one?id=N— scrape and save a single card (returns parsed JSON)GET /scrape-status— returns current card countGET /graphql— GraphiQL playgroundPOST /graphql— GraphQL queries
Production deploys automatically on push to main via Cloudflare Git integration. Preview deployments are created for PRs/branches.
- Create database:
npx wrangler d1 create oshicard-db-prod - Update
database_idinwrangler.toml - Apply schema:
npm run db:init:remote - Connect repo to Cloudflare Workers Git integration in dashboard
- Optionally configure a preview D1 binding (
oshicard-db) in the dashboard for preview deployments - Trigger scrape to populate the database