/** * Retention and Purge Policies for postgres.do * * Implements comprehensive data retention management with support for: * - Time-based retention (e.g., keep last 30 days) * - Size-based retention (e.g., keep under 1GB) * - Count-based retention (e.g., keep last 1000 records) * - Custom policies with complex predicates * * Compliance features: * - GDPR right-to-erasure support * - Legal hold capability * - Audit trail for all deletions * - Cascade policies across related data * * @module retention */ /** * Types of retention policies */ type RetentionType = 'time' | 'size' | 'count' | 'custom'; /** * Time units for time-based retention */ type TimeUnit = 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'months' | 'years'; /** * Size units for size-based retention */ type SizeUnit = 'bytes' | 'kb' | 'mb' | 'gb' | 'tb'; /** * Purge action types */ type PurgeAction = 'soft_delete' | 'hard_delete' | 'archive' | 'cryptographic_erasure'; /** * Status of a retention policy */ type PolicyStatus = 'active' | 'paused' | 'disabled'; /** * Compliance framework identifiers */ type ComplianceFramework = 'gdpr' | 'hipaa' | 'sox' | 'pci_dss' | 'ccpa' | 'custom'; /** * Base retention policy interface */ interface RetentionPolicy { /** Unique identifier for the policy */ id: string; /** Human-readable name */ name: string; /** Description of the policy */ description?: string | undefined; /** Type of retention policy */ type: RetentionType; /** Numeric value for the retention threshold */ value: number; /** Unit for the value (time, size, or count) */ unit?: TimeUnit | SizeUnit | undefined; /** Target table(s) or data type(s) */ targets: RetentionTarget[]; /** Current status of the policy */ status: PolicyStatus; /** When the policy was created */ createdAt: Date; /** When the policy was last updated */ updatedAt: Date; /** Priority (lower = higher priority) */ priority: number; /** Compliance frameworks this policy supports */ compliance?: ComplianceFramework[]; /** Schedule for policy evaluation (cron expression or interval) */ schedule?: RetentionSchedule; /** Action to take when policy triggers */ action: PurgeAction; /** Whether to cascade to related data */ cascade?: CascadePolicy; } /** * Target specification for retention policies */ interface RetentionTarget { /** Table name or pattern */ table: string; /** Schema name (default: public) */ schema?: string; /** Column to evaluate for time-based retention */ timestampColumn?: string; /** Column to evaluate for size calculation */ sizeColumn?: string; /** Additional WHERE clause predicate */ predicate?: string; } /** * Schedule configuration for automatic policy execution */ interface RetentionSchedule { /** Cron expression (e.g., "0 0 * * *" for daily at midnight) */ cron?: string; /** Interval in milliseconds */ intervalMs?: number; /** Timezone for cron expressions */ timezone?: string; /** Whether to run immediately on policy activation */ runOnActivation?: boolean; } /** * Cascade policy for related data deletion */ interface CascadePolicy { /** Enable cascade deletion */ enabled: boolean; /** Tables to cascade to (in order) */ targets?: string[]; /** Foreign key columns to follow */ foreignKeys?: string[]; /** Maximum depth for cascade (default: 10) */ maxDepth?: number; } /** * Time-based retention policy */ interface TimeRetentionPolicy extends RetentionPolicy { type: 'time'; unit: TimeUnit; /** Optional grace period before deletion */ gracePeriod?: { value: number; unit: TimeUnit; }; } /** * Size-based retention policy */ interface SizeRetentionPolicy extends RetentionPolicy { type: 'size'; unit: SizeUnit; /** Strategy when size exceeded: oldest_first, largest_first, custom */ strategy?: 'oldest_first' | 'largest_first' | 'custom'; } /** * Count-based retention policy */ interface CountRetentionPolicy extends RetentionPolicy { type: 'count'; /** Order by clause for determining which records to keep */ orderBy?: { column: string; direction: 'asc' | 'desc'; }; } /** * Custom retention policy with predicate */ interface CustomRetentionPolicy extends RetentionPolicy { type: 'custom'; /** SQL predicate that returns true for records to delete */ deletePredicate: string; /** Optional SQL to run before deletion */ preDeleteHook?: string; /** Optional SQL to run after deletion */ postDeleteHook?: string; } /** * Legal hold configuration */ interface LegalHold { /** Unique identifier */ id: string; /** Name of the legal hold */ name: string; /** Description and legal reference */ description: string; /** Tables/data affected */ scope: LegalHoldScope[]; /** When the hold was created */ createdAt: Date; /** Who created the hold */ createdBy: string; /** Expected release date (informational) */ expectedReleaseDate?: Date | undefined; /** Whether the hold is active */ active: boolean; /** Legal case reference */ caseReference?: string | undefined; /** Additional metadata */ metadata?: Record | undefined; } /** * Scope definition for legal holds */ interface LegalHoldScope { /** Table name */ table: string; /** Schema name */ schema?: string; /** Optional predicate to limit scope */ predicate?: string; /** Specific record IDs (if known) */ recordIds?: string[]; } /** * GDPR erasure request */ interface GdprErasureRequest { /** Unique request identifier */ id: string; /** Subject identifier (user ID, email, etc.) */ subjectId: string; /** Type of identifier */ subjectIdType: 'user_id' | 'email' | 'phone' | 'custom'; /** When the request was received */ requestedAt: Date; /** Deadline for completion (72 hours from request per GDPR) */ deadline: Date; /** Current status */ status: 'pending' | 'in_progress' | 'completed' | 'failed' | 'blocked'; /** Tables to scan for subject data */ tables: GdprTableMapping[]; /** Results of the erasure */ result?: GdprErasureResult | undefined; /** Reason if blocked (e.g., legal hold) */ blockedReason?: string | undefined; /** Audit trail */ auditTrail: AuditEntry[]; } /** * Table mapping for GDPR erasure */ interface GdprTableMapping { /** Table name */ table: string; /** Schema name */ schema?: string; /** Column containing the subject identifier */ subjectColumn: string; /** Action: delete, anonymize, or pseudonymize */ action: 'delete' | 'anonymize' | 'pseudonymize'; /** Columns to anonymize (if action is anonymize) */ anonymizeColumns?: string[]; /** Pseudonymization mapping (if action is pseudonymize) */ pseudonymizeMap?: Record; } /** * Result of a GDPR erasure operation */ interface GdprErasureResult { /** When erasure completed */ completedAt: Date; /** Total records affected */ totalRecords: number; /** Records per table */ recordsByTable: Record; /** Any errors encountered */ errors: GdprErasureError[]; /** Verification hash (proof of erasure) */ verificationHash?: string; } /** * Error during GDPR erasure */ interface GdprErasureError { /** Table where error occurred */ table: string; /** Error message */ message: string; /** Whether the error was recoverable */ recoverable: boolean; /** Record IDs that failed (if known) */ failedRecordIds?: string[]; } /** * Audit entry for tracking deletions */ interface AuditEntry { /** Timestamp of the action */ timestamp: Date; /** Action taken */ action: string; /** Actor (user or system) */ actor: string; /** Details of the action */ details: Record; /** Affected table */ table?: string; /** Number of records affected */ recordCount?: number; /** Hash of deleted data (for compliance verification) */ dataHash?: string; } /** * Purge operation request */ interface PurgeRequest { /** Policy ID triggering the purge */ policyId?: string; /** Target tables */ targets: PurgeTarget[]; /** Action type */ action: PurgeAction; /** Whether to run in dry-run mode */ dryRun?: boolean; /** Maximum records to purge in this operation */ batchSize?: number; /** Timeout for the operation in milliseconds */ timeoutMs?: number; /** Reason for the purge */ reason?: string; /** Actor initiating the purge */ actor?: string; } /** * Target for a purge operation */ interface PurgeTarget { /** Table name */ table: string; /** Schema name */ schema?: string; /** WHERE clause predicate */ predicate: string; /** Order by clause (for batch processing) */ orderBy?: string; } /** * Result of a purge operation */ interface PurgeResult { /** Whether the operation succeeded */ success: boolean; /** Request that was executed */ request: PurgeRequest; /** When the purge started */ startedAt: Date; /** When the purge completed */ completedAt: Date; /** Total records purged */ totalPurged: number; /** Records purged per table */ purgedByTable: Record; /** Any errors encountered */ errors: PurgeError[]; /** Audit entries created */ auditEntries: AuditEntry[]; /** Whether this was a dry run */ wasDryRun: boolean; } /** * Error during purge operation */ interface PurgeError { /** Table where error occurred */ table: string; /** Error message */ message: string; /** Error code (PostgreSQL error code if applicable) */ code?: string | undefined; /** Whether the operation was rolled back */ rolledBack: boolean; } /** * Configuration for the retention manager */ interface RetentionManagerConfig { /** SQL client for executing queries */ sql: RetentionSqlClient; /** Table to store policies (default: _retention_policies) */ policiesTable?: string; /** Table to store audit entries (default: _retention_audit) */ auditTable?: string; /** Table to store legal holds (default: _legal_holds) */ legalHoldsTable?: string; /** Table to store GDPR requests (default: _gdpr_requests) */ gdprRequestsTable?: string; /** Schema for retention tables (default: postgres_do) */ schema?: string; /** Default batch size for purge operations */ defaultBatchSize?: number; /** Enable automatic scheduling */ enableScheduler?: boolean; /** Callback for audit events */ onAuditEvent?: (entry: AuditEntry) => void | Promise; } /** * Minimal SQL client interface for retention operations */ interface RetentionSqlClient { unsafe = Record>(query: string, params?: unknown[]): Promise; } /** * Convert time value to milliseconds */ declare function timeToMs(value: number, unit: TimeUnit): number; /** * Convert size value to bytes */ declare function sizeToBytes(value: number, unit: SizeUnit): number; /** * Convert PostgreSQL interval string from time value and unit */ declare function toPgInterval(value: number, unit: TimeUnit): string; /** * Retention Manager * * Main class for managing retention policies, legal holds, and GDPR requests. * * @example * ```typescript * import { RetentionManager } from 'postgres.do/retention' * * const retention = new RetentionManager({ sql }) * * // Create a time-based retention policy * const policy = await retention.createPolicy({ * name: 'Delete old logs', * type: 'time', * value: 30, * unit: 'days', * targets: [{ table: 'logs', timestampColumn: 'created_at' }], * action: 'hard_delete', * }) * * // Execute the policy * const result = await retention.executePolicy(policy.id) * console.log(`Deleted ${result.totalPurged} records`) * ``` */ declare class RetentionManager { private config; private scheduler; constructor(config: RetentionManagerConfig); /** * Initialize the retention system (creates required tables) */ initialize(): Promise; /** * Create a new retention policy */ createPolicy(options: Omit & { status?: PolicyStatus; }): Promise; /** * Get a policy by ID */ getPolicy(id: string): Promise; /** * List all policies */ listPolicies(options?: { status?: PolicyStatus; type?: RetentionType; compliance?: ComplianceFramework; }): Promise; /** * Update a policy */ updatePolicy(id: string, updates: Partial>): Promise; /** * Delete a policy */ deletePolicy(id: string): Promise; /** * Execute a retention policy */ executePolicy(policyId: string, options?: { dryRun?: boolean; actor?: string; }): Promise; /** * Build purge targets based on policy type */ private buildPurgeTargets; /** * Execute a purge operation */ purge(request: PurgeRequest): Promise; /** * Create a legal hold */ createLegalHold(options: Omit & { active?: boolean; }): Promise; /** * Release a legal hold */ releaseLegalHold(id: string, actor: string, reason?: string): Promise; /** * Check if a table is under legal hold */ isTableBlocked(table: string): Promise; /** * Get all tables currently under legal hold */ private getBlockedTables; /** * List all active legal holds */ listLegalHolds(options?: { active?: boolean; }): Promise; /** * Create a GDPR erasure request */ createGdprErasureRequest(options: Omit): Promise; /** * Execute a GDPR erasure request */ executeGdprErasureRequest(requestId: string, options?: { actor?: string; }): Promise; /** * Get pending GDPR requests approaching deadline */ getPendingGdprRequests(options?: { withinHours?: number; }): Promise; /** * Create an audit entry */ private audit; /** * Get audit trail for a policy or table */ getAuditTrail(options?: { policyId?: string; table?: string; since?: Date; limit?: number; }): Promise; /** * Start the scheduler for automatic policy execution */ startScheduler(): void; /** * Stop the scheduler */ stopScheduler(): void; /** * Safely parse JSON that may already be an object (from JSONB columns) */ private parseJsonField; /** * Convert database row to RetentionPolicy */ private rowToPolicy; /** * Convert database row to LegalHold */ private rowToLegalHold; /** * Convert database row to GdprErasureRequest */ private rowToGdprRequest; } /** * Compliance policy templates */ declare const ComplianceTemplates: { /** * GDPR-compliant retention policy template */ readonly gdpr: { readonly type: "time"; readonly value: 365; readonly unit: "days"; readonly compliance: readonly ["gdpr"]; readonly action: "hard_delete"; readonly description: "GDPR-compliant data retention (1 year)"; }; /** * HIPAA-compliant retention policy template (6 years) */ readonly hipaa: { readonly type: "time"; readonly value: 6; readonly unit: "years"; readonly compliance: readonly ["hipaa"]; readonly action: "archive"; readonly description: "HIPAA-compliant medical record retention (6 years)"; }; /** * SOX-compliant retention policy template (7 years) */ readonly sox: { readonly type: "time"; readonly value: 7; readonly unit: "years"; readonly compliance: readonly ["sox"]; readonly action: "archive"; readonly description: "SOX-compliant financial record retention (7 years)"; }; /** * PCI-DSS compliant policy (1 year for logs) */ readonly pciDss: { readonly type: "time"; readonly value: 1; readonly unit: "years"; readonly compliance: readonly ["pci_dss"]; readonly action: "hard_delete"; readonly description: "PCI-DSS compliant audit log retention (1 year)"; }; /** * CCPA-compliant policy (varies by data type) */ readonly ccpa: { readonly type: "time"; readonly value: 365; readonly unit: "days"; readonly compliance: readonly ["ccpa"]; readonly action: "hard_delete"; readonly description: "CCPA-compliant personal data retention"; }; }; /** * Create a pre-configured retention manager with compliance templates */ declare function createRetentionManager(config: RetentionManagerConfig): RetentionManager; export { type AuditEntry, type CascadePolicy, type ComplianceFramework, ComplianceTemplates, type CountRetentionPolicy, type CustomRetentionPolicy, type GdprErasureError, type GdprErasureRequest, type GdprErasureResult, type GdprTableMapping, type LegalHold, type LegalHoldScope, type PolicyStatus, type PurgeAction, type PurgeError, type PurgeRequest, type PurgeResult, type PurgeTarget, RetentionManager, type RetentionManagerConfig, type RetentionPolicy, type RetentionSchedule, type RetentionSqlClient, type RetentionTarget, type RetentionType, type SizeRetentionPolicy, type SizeUnit, type TimeRetentionPolicy, type TimeUnit, createRetentionManager, sizeToBytes, timeToMs, toPgInterval };