Subscription-gated HLS video streaming: Cloudflare Worker API + Nuxt 4 web app, with optional Deno backup API and a media VM for transcoding.
- Architecture
- Monorepo packages
- Documentation
- Prerequisites
- Local development
- Deploy
- Secrets and configuration
- Media encoding
| Layer | Technology |
|---|---|
| API | Cloudflare Worker (@vmp/api) — REST, auth, Stripe, push, RSS |
| Web | Nuxt 4 / Vue 3 (@vmp/web) — Cloudflare Workers SSR (vmp-web-worker-dev / vmp-web-worker-prod) |
| Database | Cloudflare D1 (SQLite); Postgres shim on Deno backup |
| Object storage | Cloudflare R2 (pluggable via @vmp/storage) |
| Payments | Stripe (+ optional legacy Qerko via @vmp/payments) |
| Email / push | Brevo transactional, Web Push (VAPID) |
| Transcoding | @vmp/media-pipeline on a media VM (SVT Encore + Shaka → R2) |
| Backup API | @vmp/api-node on Deno Deploy (same handlers, Postgres + S3) |
Cloudflare Pages (vmp-fe) is deprecated. Do not attach production hostnames to Pages.
Browser ──► @vmp/web (Worker SSR)
│
▼
@vmp/api (Worker) ──► D1 / R2 / KV
│
├── Stripe webhooks, Brevo, Web Push
└── pipeline-status ◄── @vmp/media-pipeline (VM)
| Package | Path | Role |
|---|---|---|
@vmp/api |
packages/api |
Primary Cloudflare Worker API |
@vmp/web |
packages/web |
Nuxt 4 frontend (Workers SSR) |
@vmp/shared |
packages/shared |
Shared TypeScript types |
@vmp/storage |
packages/storage |
Pluggable object storage (R2 / S3-compatible) — README |
@vmp/payments |
packages/payments |
Payment provider registry (Stripe, legacy Qerko) — README |
@vmp/api-node |
packages/api-node |
Deno Deploy backup API — README |
@vmp/media-pipeline |
packages/media-pipeline |
Media VM: Encore + Shaka HLS + R2 — README |
@vmp/offloading |
packages/offloading |
R2 ↔ Garage hot/cold tiering — README |
@vmp/moq-probe |
packages/moq-probe |
MoQ live broadcast diagnostic probe — README |
Core API/web packages do not ship separate READMEs; see AGENTS.md for auth, D1 schema, roles, and agent workflow.
| Document | Audience |
|---|---|
| AGENTS.md | Canonical architecture, git workflow, secrets, local Cloud setup, roadmap |
| DEPLOYMENT.md | CI/CD, env vars, smoke checks, bootstrap / rollback |
| docs/README.md | Index of API notes and historical design docs |
| packages/web/docs/workers-deploy-env.md | Web Worker build-time env vars |
Package READMEs under packages/* |
Package-specific ops (pipeline, api-node, storage, …) |
- Node.js 20+ and npm 10+ (
packageManagerin rootpackage.json). - Cloudflare account with Workers, D1, R2, and KV (per environment).
- CI deploy secrets:
CLOUDFLARE_API_TOKEN_{STAGING,PROD}andCLOUDFLARE_ACCOUNT_ID_{STAGING,PROD}.
npm ci
npm run typecheck
# API (Wrangler, port 8787) — needs packages/api/.dev.vars
npm run dev --workspace=@vmp/api
# Web (Nuxt, port 3000)
API_URL=http://localhost:8787 npm run dev --workspace=@vmp/webApply D1 migrations locally before serving data:
cd packages/api
for f in $(ls -1 migrations/*.sql | sort); do
npx wrangler d1 execute video-subscription-db --local --file="$f"
doneSeed videos start as drafts. To publish them for the homepage:
npx wrangler d1 execute video-subscription-db --local \
--command="UPDATE videos SET publish_status = 'published', published_at = CURRENT_TIMESTAMP WHERE publish_status = 'draft';"Full Cloud-agent local notes (secrets, gotchas): AGENTS.md → Cursor Cloud-specific instructions.
| Trigger | What deploys |
|---|---|
Push to main |
Staging: API Worker + web Worker (vmp-web-worker-dev) via .github/workflows/deploy.yml |
Tag v*.*.* |
Production: API Worker + web Worker (vmp-web-worker-prod) |
| Push / PR (Deno git integration) | @vmp/api-node preview/production build on Deno Deploy — not from deploy.yml. PR check status is pending until deploy/tjm/vmp clears; maintainer log review on console.deno.com is required when that check stays red. |
Manual commands:
npm run deploy:api
npm run deploy --workspace=@vmp/web # staging Worker
npm run deploy:prod --workspace=@vmp/web # production WorkerDeploy gates typecheck @vmp/shared, @vmp/storage, @vmp/api, and @vmp/web before build. Details: DEPLOYMENT.md.
Never push feature work directly to main — use a branch + pull request (autodeploy + CodeRabbit). See AGENTS.md → Git workflow.
- Template: root
.env.example. Copy to.env.staging/.env.productionas needed. - Local API secrets:
packages/api/.dev.vars(never commit). - Required Worker secrets (JWT, Stripe, Brevo, VAPID, RSS, TOTP, …): listed in AGENTS.md and DEPLOYMENT.md.
- Prices, plan names, and limits live in D1
admin_settings— not hardcoded.
Transcoding runs on a media VM via @vmp/media-pipeline:
- Watchfolder intake → SVT Encore encode
- encore-packager (Shaka) → fMP4 HLS ladder uploaded to R2
- HMAC callback to
POST /api/admin/videos/:id/pipeline-status
There is no AWS Elemental MediaConvert admin upload/transcode UI in this repo anymore. The historical media_convert_jobs D1 table remains: playback and offline-download code may still read completed Bunny Stream rows (provider = 'bunnystream', bunny_playback_url) as an alternate HLS entrypoint. Do not drop that table without a migration that replaces those reads.