v0.1.8
Menu
Download

TypeScript

Frontend domain types and IPC boundaries are TypeScript so refactors fail at compile time, not in the webview.

src/types.ts defines MediaFile, VideoInfo, DownloadJob, chapter shapes, and progress payloads returned from Rust. Gallery entries discriminate kind: "media" vs playlist stacks. Sidecar fields like sourceId and playlistIndex flow from yt-dlp JSON into the player and SponsorBlock hooks.

src/store/types.ts holds RuforgeSettings (download format, SponsorBlock modes, auto-advance audio, auto scrub previews, max concurrent jobs, cookie paths). Settings UI and store persist the same object shape.

npm run build runs tsc -b before Vite bundles. Nullable playingFile, queue rows removed mid-download, and gallery scans in flight are the usual places strict null checks catch real bugs.

The website under website/src/lib/*.ts is also TypeScript: builtWithPages.ts, sitePages.ts, nav helpers. App and site share naming (RuForge, rf-* tokens) but not a shared npm package. Copy types manually when they must align.

In the repo

export interface MediaFile {
  name: string;
  path: string;
  size: number;
  created: number;
  duration: number;
  thumbnailPath: string | null;
  ruforgePosterPath: string | null;
  subtitlePath: string | null;
  chapters: Chapter[] | null;
  downloadMetadataHint: string | null;
  sourceUrl: string | null;
  /** yt-dlp video id from sidecar `.info.json` `id` (when `sourceUrl` is missing). */
  sourceId: string | null;
  /** yt-dlp `playlist_index` from sidecar when downloaded as part of a playlist. */
  playlistIndex?: number | null;
}
export interface VideoInfo {
  title: string;
  thumbnail: string;
  duration: number;
  formats: any[];
  fileSizeBytes?: number | null;
  fileSizeBytesAudio?: number | null;
  fileSizeBytesVideo?: number | null;
  isPlaylist: boolean;
  playlistItems?: PlaylistItem[];
  uploader?: string | null;
  channel?: string | null;
}
export type UpdateCheckResult =
  | { kind: "available"; update: Update; version: string; teaserNotes: string }
  | { kind: "up-to-date"; currentVersion: string }
  | { kind: "error"; message: string };

export async function runUpdateCheck(): Promise<UpdateCheckResult> {
  try {
    const currentVersion = await getVersion();
    const next = await check();
    if (!next) {
      return { kind: "up-to-date", currentVersion };
    }
    return {
      kind: "available",
      update: next,
      version: next.version,
      teaserNotes: teaserNotesFromUpdaterBody(next.body ?? ""),
    };
  } catch (e) {
    const message = e instanceof Error ? e.message : String(e);
    return { kind: "error", message };
  }
}

Where it shows up

  • src/types.ts, src/store/types.ts
  • Typed invoke at download, gallery, SponsorBlock, and media commands
  • website/src/lib/*.ts content registries and page defs