feat(broadcast): controller + destination-store + types updates
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
31cbe4d9e8
commit
d77a63d7ff
3 changed files with 43 additions and 6 deletions
|
|
@ -13,15 +13,15 @@
|
|||
* consistent state after every mutation.
|
||||
*/
|
||||
|
||||
import type { Destination, RelayStatus, ObsScene } from "../shared/types";
|
||||
import type { Destination, RelayStatus, ObsScene, IDestinationStore } from "../shared/types";
|
||||
import { ObsClient, ObsError } from "./obs-client";
|
||||
import { FanoutManager } from "./fanout-manager";
|
||||
import { DestinationStore } from "./destination-store";
|
||||
|
||||
export interface BroadcastControllerOptions {
|
||||
obs: ObsClient;
|
||||
fanout: FanoutManager;
|
||||
store: DestinationStore;
|
||||
/** Any IDestinationStore impl (file-backed in controller, Postgres in backend-api). */
|
||||
store: IDestinationStore;
|
||||
}
|
||||
|
||||
export class BroadcastController {
|
||||
|
|
@ -35,7 +35,7 @@ export class BroadcastController {
|
|||
return this.opts.fanout;
|
||||
}
|
||||
|
||||
get store(): DestinationStore {
|
||||
get store(): IDestinationStore {
|
||||
return this.opts.store;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@
|
|||
* - All mutations await save.
|
||||
*/
|
||||
|
||||
import type { Destination } from "../shared/types";
|
||||
import type { Destination, IDestinationStore } from "../shared/types";
|
||||
|
||||
const DEFAULT_FILE = "/data/destinations.json";
|
||||
|
||||
export class DestinationStore {
|
||||
export class DestinationStore implements IDestinationStore {
|
||||
private destinations: Destination[] = [];
|
||||
private filePath: string;
|
||||
private readonly intendedFilePath: string;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,43 @@ export interface Destination {
|
|||
url: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default tenant id for the Quinn-first phase. Every persisted row carries a
|
||||
* tenant_id so flipping to multi-performer later is a data activation, not a
|
||||
* schema rewrite. Until SSO/`my` performer identity is wired as the tenant key,
|
||||
* all broadcast rows belong to this seeded tenant.
|
||||
*/
|
||||
export const DEFAULT_TENANT_ID = 1;
|
||||
|
||||
/**
|
||||
* Persistence contract for RTMP destinations.
|
||||
*
|
||||
* Two implementations satisfy this (DIP — BroadcastController depends only on
|
||||
* the interface, never the concrete class):
|
||||
* - file-backed `DestinationStore` (controller/, zero-dep, bare-droplet path)
|
||||
* - `PgDestinationStore` (backend-api/, Postgres, always-on control plane)
|
||||
*
|
||||
* `list()`/`find()` are synchronous: both impls keep an authoritative in-memory
|
||||
* snapshot, hydrated by `load()` and kept in sync on every mutation.
|
||||
*/
|
||||
export interface IDestinationStore {
|
||||
/** Hydrate in-memory state. `initialFromEnv` seeds when the backing store is empty. */
|
||||
load(initialFromEnv?: Destination[]): Promise<void>;
|
||||
/** Snapshot copy (safe to mutate by caller). */
|
||||
list(): Destination[];
|
||||
find(name: string): Destination | undefined;
|
||||
/** Idempotent upsert by name. Returns the new full list. */
|
||||
add(dest: Destination): Promise<Destination[]>;
|
||||
/** Remove by name (no-op if absent). Returns the new full list. */
|
||||
remove(name: string): Promise<Destination[]>;
|
||||
/** Replace the whole set. Returns the new full list. */
|
||||
replaceAll(newList: Destination[]): Promise<Destination[]>;
|
||||
/** Diagnostic: where state persists (file path, "postgres", or "" for memory-only). */
|
||||
readonly path: string;
|
||||
/** True when persistence degraded to a fallback/memory-only location. */
|
||||
readonly isUsingFallback: boolean;
|
||||
}
|
||||
|
||||
export interface FanoutStatus {
|
||||
name: string;
|
||||
url: string; // may be redacted in some contexts
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue