Data model
Vumbnail has very little “database” in the traditional sense. Its real data is cached images in R2, a small live allow-list in KV, and a billing record in D1. The Postgres schema is mostly auth boilerplate.
R2 — the thumbnail store (vumbnail-thumbnails)
Section titled “R2 — the thumbnail store (vumbnail-thumbnails)”All objects live under the thumbnails/ prefix:
thumbnails/<videoId>/clean.jpg clean variant (paying / under-threshold)thumbnails/<videoId>/wm.jpg watermarked variantthumbnails/_shared/error.jpg one shared graphic for all failuresthumbnails/_shared/locked.jpg one shared graphic for password-lockedthumbnails/_negative/<cacheId>.json negative-cache record (see below)thumbnails/_resolver-success/<id:pw>.json memoized Vimeo resolutionClean images on the public cache.vumbnail.com domain are blocked by a WAF rule;
only wm.jpg and the shared objects are publicly fetchable. See
Caching & delivery for the record shapes.
KV — THUMBNAIL_CONFIG
Section titled “KV — THUMBNAIL_CONFIG”The hot-path config and live allow-list, shared between the thumbnail Worker (read) and the accounts Worker (write):
| Key | Purpose |
|---|---|
billing-entitlements:projection | the live allow-list (domains/IPs/keys/UAs) read on the hot path |
billing-entitlements:mode | static / shadow / runtime (currently runtime; hardcoded out of the hot path) |
youtube:playlist-resolver-url(s) | optional external playlist-resolver overrides |
A separate KV namespace, PROCESSED_EVENTS_STORE, de-duplicates Stripe webhook
events (~90-day TTL).
D1 — vumbnail-billing-entitlements
Section titled “D1 — vumbnail-billing-entitlements”The billing record of truth, written by the accounts Worker:
-- latest entitlement per subscriptionbilling_entitlements_current ( stripe_subscription_id TEXT PRIMARY KEY, stripe_customer_id TEXT, stripe_price_id TEXT, stripe_product_id TEXT, customer_email TEXT, domains_json TEXT NOT NULL, -- referer hosts → clean ips_json TEXT NOT NULL, -- client IPs → clean added_at TEXT NOT NULL, updated_at TEXT NOT NULL);
-- append-only audit of every processed eventbilling_entitlement_events ( stripe_event_id TEXT PRIMARY KEY, stripe_event_type TEXT NOT NULL, stripe_subscription_id TEXT NOT NULL, domains_json TEXT NOT NULL, ips_json TEXT NOT NULL, observed_at TEXT NOT NULL);D1 is regenerated into the KV projection, which is what the hot path actually
reads. See Paid access.
Committed allow-list — apps/thumbnail/lib/customers.json
Section titled “Committed allow-list — apps/thumbnail/lib/customers.json”A third copy of entitlements, committed to git and bundled at deploy time as the
fallback when KV is unavailable. Each entry mirrors the
CustomerAllowlistRecord. It is kept in sync
automatically by the accounts Worker via the GitHub API (visible as
chore(thumbnail): sync Stripe allowlist for sub_… commits).
Postgres (Neon) — auth only
Section titled “Postgres (Neon) — auth only”The Drizzle schema is Better Auth + organization-plugin boilerplate: user,
session, identity, verification, organization, member, team,
team_member, invitation, passkey. It backs operator-dashboard sign-in.
No Vumbnail product entity lives here — thumbnails are R2, entitlements are
D1/KV.
Shared policy constants — @repo/core
Section titled “Shared policy constants — @repo/core”Single source of truth for numbers that several apps depend on:
VUMBNAIL_FREE_TIER_MONTHLY_REQUESTS = 50_000VUMBNAIL_OUTREACH_GRACE_PERCENT = 20VUMBNAIL_OUTREACH_MONTHLY_THRESHOLD = 60_000
// billing-entitlements.tsBILLING_ENTITLEMENTS_PROJECTION_KEY = "billing-entitlements:projection"BILLING_ENTITLEMENTS_MODE_KEY = "billing-entitlements:mode"// projection "kind": "billing-entitlements/v1"