Skip to content

Contributing to Strum VOD ​

Thanks for your interest in contributing to Strum VOD! This document provides guidelines and instructions for contributing.

Getting Started ​

  1. Fork the repository and clone your fork locally
  2. Install dependencies: pnpm install
  3. Copy environment config: cp .env.example .env
  4. Build the shared package first: pnpm run build -w @strum-vod/db
  5. Start infrastructure: docker compose up -d postgres redis minio minio-init
  6. Start the local QStash emulator (api/worker/transcoder all require QStash credentials at boot — no dormant mode): pnpm dev:qstash, then paste one of its printed credential sets into .env — see the "QStash local dev" block in .env.example for the full recipe (avoids a real Upstash account and needs no tunnel)
  7. Start development servers:
    bash
    pnpm run dev -w @strum-vod/api
    pnpm run dev -w @strum-vod/worker
    pnpm run dev:transcoder     # Go transcoder — needs Go 1.23+ and ffmpeg on PATH; .env isn't auto-loaded here, export it first: set -a; source .env; set +a
    pnpm run dev -w @strum-vod/dashboard

This project uses pnpm with workspaces. Do not use npm, and do not commit an npm package-lock.json.

Development Workflow ​

  1. Open an issue first to discuss what you'd like to change
  2. Create a branch from main with a descriptive name (e.g., fix/upload-timeout, feat/webhook-support)
  3. Make your changes following the code style guidelines below
  4. Test your changes locally with the full stack running
  5. Run type checks: pnpm run typecheck
  6. Submit a pull request referencing the issue

Code Style ​

  • TypeScript throughout — avoid any types, use proper interfaces
  • ESM modules with .js extensions in imports
  • camelCase for variables and functions, PascalCase for types/interfaces/components
  • snake_case for database column names (Drizzle schema maps to camelCase)
  • Wrap API responses in { data: {...} } for success or { error: "..." } for errors
  • Use the shared constants from @strum-vod/db for status values, S3 paths, and ID lengths

Dashboard → API calls (Hono RPC) ​

All HTTP calls from the dashboard must use the typed Hono RPC client — never raw fetch or the legacy api() helper (except for binary uploads, see below):

ts
import { apiClient, unwrap } from '../lib/api.js';

// Reading data
const asset = await unwrap(apiClient.v1.assets[':id'].$get({ param: { id } }));

// Writing data
await unwrap(apiClient.v1.assets[':id'].$patch({
  param: { id },
  json: { title: 'New title' },
}));

Key rules:

  • Hyphenated segments must be bracket-quoted: ['upload-url'], ['stem-mix'], etc.
  • param: carries URL path params, json: carries the request body, query: carries query string params
  • unwrap auto-unwraps the { data: T } envelope and throws ApiError on non-2xx
  • The api() raw helper is retained only for ThumbnailModal.tsx's binary PUT (raw image/* body, which the RPC client can't represent)
  • To add a new dashboard feature that calls a new API route: add the route to apps/api-edge with @hono/zod-validator so it's exported in AppType, then use apiClient in the dashboard — no string URLs

Project Structure ​

apps/api-edge/   → Hono REST API on Cloudflare Workers (exports AppType for RPC)
apps/tus-edge/   → TUS resumable upload gateway (Cloudflare Workers)
apps/transcoder/ → Go: FFmpeg HLS ladder (360p–4320p)
apps/dashboard/  → React SPA (Vite + Tailwind, PWA) — uses Hono RPC via packages/api-client
apps/player/     → React SPA — public embeddable player (PWA)
packages/api-client/ → Typed Hono RPC client factory + unwrap helper
packages/db/     → Shared Drizzle ORM schemas and constants (Postgres)
packages/email/  → Transactional email templates (OTP)
packages/subtitles/ → VTT/SRT generation
packages/player-ui/ → Shared player components
packages/design-tokens/ → Brand design tokens / CSS variables
packages/ai-providers/ → AI provider registry (mode/healthProbe/required) shared by api + worker
packages/costs/  → Monthly cost-estimator engine (see docs/costs.md)

Build order: @strum-vod/db must be built before @strum-vod/api-edge and other packages that depend on it (email, subtitles, ai-providers, costs). @strum-vod/api-client depends on @strum-vod/api-edge (imports AppType) and must be built before the dashboard.

Go transcoder: apps/transcoder is a standalone Go module (not a pnpm workspace). The enum/constant strings in apps/transcoder/internal/constants are hand-mirrored from packages/db/src/constants.ts — a change to one requires the other. Run pnpm test:transcoder to enforce the parity.

Pull Request Guidelines ​

  • Keep PRs focused — one feature or fix per PR
  • Include a clear description of what changed and why
  • Update documentation if your change affects the API or configuration
  • Ensure pnpm run typecheck passes with no errors
  • Update the hand-mirrored Go constants if you touch packages/db/src/constants.ts

Reporting Bugs ​

Open an issue with:

  • Steps to reproduce
  • Expected behavior
  • Actual behavior
  • Environment details (OS, Node version, Docker version)

License ​

Strum VOD is not open source. It is distributed under the STRUM Proprietary License — © 2026 Strum, all rights reserved. By contributing, you agree that your contributions become the property of Strum and are licensed under the same terms. See LICENSE.

STRUM Proprietary License — © 2026 Strum. All rights reserved.