/** * Teams Backup Service * * Provides automatic backup and restore functionality for teams data. * * Two layers of durability: * 1. **Live single-file backup** (`teams-backup.json`) — preserved for * backward compat with the existing status / restore endpoints. * 2. **Rotating ring buffer** (N=30 snapshots in `teams-backup-history/`) — * introduced by Persistence P0 (A2). Even if a corrupting save propagates * to the live backup, the previous N-1 slots remain intact. * * @module services/core/teams-backup.service */ import type { Team } from '../../types/index.js'; /** Number of rotating snapshot slots to retain. */ export declare const MAX_HISTORY_SLOTS = 30; /** * Shape of the backup file */ export interface TeamsBackup { /** ISO timestamp of when the backup was written */ timestamp: string; /** Full team objects at time of backup */ teams: Team[]; } /** * Status returned by getBackupStatus */ export interface TeamsBackupStatus { /** Whether current teams are empty but backup has data */ hasMismatch: boolean; /** Number of teams in the backup file */ backupTeamCount: number; /** Number of teams currently in storage */ currentTeamCount: number; /** ISO timestamp of the most recent backup */ backupTimestamp: string | null; } /** * Single entry in the rotating-snapshot history. */ export interface TeamsBackupHistoryEntry { /** Slot index (0..MAX_HISTORY_SLOTS-1) */ slot: number; /** ISO timestamp the snapshot was written */ timestamp: string; /** Number of teams captured in the snapshot */ teamCount: number; /** Whether this slot is the most recently written one */ isHead: boolean; } /** * On-disk index that tracks the ring buffer. */ export interface TeamsBackupHistoryIndex { /** Slot index of the most recently written snapshot. -1 = empty. */ head: number; /** Total snapshots written ever (monotonic; not the current count). */ written: number; /** Per-slot metadata. Slots without data are omitted. */ slots: Record; } /** * TeamsBackupService manages the live single-file backup AND a rotating * ring buffer of historical snapshots. * * @example * ```typescript * const backupService = TeamsBackupService.getInstance(); * await backupService.updateBackup(teams); * const status = await backupService.getBackupStatus(currentTeams); * const history = await backupService.getBackupHistory(); * const snapshot = await backupService.restoreFromSlot(2); * ``` */ export declare class TeamsBackupService { private static instance; private backupPath; private historyDir; private historyIndexPath; private logger; /** * Create a new TeamsBackupService. * * @param crewlyHome - Path to the .crewly home directory */ constructor(crewlyHome?: string); /** * Get singleton instance. * * @param crewlyHome - Optional override for the home directory * @returns TeamsBackupService instance */ static getInstance(crewlyHome?: string): TeamsBackupService; /** * Clear singleton instance (for testing). */ static clearInstance(): void; /** * Write or update the backup file with the current teams data. * Called after every successful saveTeam() or deleteTeam(). * * Writes both the live single-file backup AND a new rotating-snapshot * slot. If history rotation fails, the live backup still proceeds — * we never want a history-write bug to block the primary durability path. * * @param teams - Current full list of teams */ updateBackup(teams: Team[]): Promise; /** * Read the backup file from disk. * * @returns The parsed backup or null if no backup exists */ readBackup(): Promise; /** * Check whether current teams data is empty but backup has data. * * @param currentTeams - The current teams from storage * @returns Backup status with mismatch flag */ getBackupStatus(currentTeams: Team[]): Promise; /** * Get the backup file path (for testing). * * @returns Absolute path to the backup file */ getBackupPath(): string; /** * Get the history directory path (for testing). * * @returns Absolute path to the rotating-snapshot directory */ getHistoryDir(): string; /** * List all populated rotating-snapshot slots, newest first. * * @returns Array of slot metadata. Empty array if history is empty. */ getBackupHistory(): Promise; /** * Read a single rotating-snapshot slot. * * @param slot - Slot index 0..MAX_HISTORY_SLOTS-1 * @returns Parsed snapshot or null if slot is empty / corrupted */ readSlot(slot: number): Promise; /** * Restore from a specific rotating-snapshot slot. The caller is * responsible for re-saving each team via StorageService — this method * just returns the snapshot contents. * * @param slot - Slot index 0..MAX_HISTORY_SLOTS-1 * @returns Snapshot to restore, or null if slot is empty */ restoreFromSlot(slot: number): Promise; /** * Read the rotating-snapshot index from disk. * * @returns Parsed index or null if it does not exist / is corrupted */ private readHistoryIndex; /** * Write a new rotating snapshot, advancing the head pointer. * * Order matters: snapshot file written FIRST, then index. If the process * dies between the two writes, we end up with an unreferenced slot file * (harmless) rather than an index pointing at a half-written slot. * * @param teams - Teams to snapshot into the new slot */ private writeRotatingSnapshot; /** * @returns A fresh empty history index (head=-1, no slots). */ private emptyIndex; } //# sourceMappingURL=teams-backup.service.d.ts.map