Contributing to Strum VOD
Thanks for your interest in contributing to Strum VOD! This document provides guidelines and instructions for contributing.
Getting Started
- Fork the repository and clone your fork locally
- Install dependencies:
pnpm install - Copy environment config:
cp .env.example .env - Build the shared package first:
pnpm run build -w @strum-vod/db - Start infrastructure:
docker compose up -d postgres redis minio minio-init - 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.examplefor the full recipe (avoids a real Upstash account and needs no tunnel) - 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
- Open an issue first to discuss what you'd like to change
- Create a branch from
mainwith a descriptive name (e.g.,fix/upload-timeout,feat/webhook-support) - Make your changes following the code style guidelines below
- Test your changes locally with the full stack running
- Run type checks:
pnpm run typecheck - Submit a pull request referencing the issue
Code Style
- TypeScript throughout — avoid
anytypes, use proper interfaces - ESM modules with
.jsextensions 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/dbfor 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):
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 paramsunwrapauto-unwraps the{ data: T }envelope and throwsApiErroron non-2xx- The
api()raw helper is retained only forThumbnailModal.tsx's binaryPUT(rawimage/*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-edgewith@hono/zod-validatorso it's exported inAppType, then useapiClientin 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 typecheckpasses 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.