@xhub-short/core 0.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +1466 -0
  2. package/dist/index.js +2495 -0
  3. package/package.json +41 -0
@@ -0,0 +1,1466 @@
1
+ import { VideoItem, IDataSource, IAnalytics, ILogger, ISessionStorage, SessionSnapshot, INetworkAdapter, IVideoLoader, IPosterLoader, VideoSource, IInteraction } from '@xhub-short/contracts';
2
+ export { SessionSnapshot } from '@xhub-short/contracts';
3
+ import { StoreApi } from 'zustand/vanilla';
4
+
5
+ /**
6
+ * Feed state for zustand store
7
+ */
8
+ interface FeedState {
9
+ /** Normalized video data - Map for O(1) lookup */
10
+ itemsById: Map<string, VideoItem>;
11
+ /** Ordered list of video IDs for rendering */
12
+ displayOrder: string[];
13
+ /** Initial loading state */
14
+ loading: boolean;
15
+ /** Loading more (pagination) state */
16
+ loadingMore: boolean;
17
+ /** Error state */
18
+ error: FeedError | null;
19
+ /** Cursor for pagination */
20
+ cursor: string | null;
21
+ /** Whether more items can be loaded */
22
+ hasMore: boolean;
23
+ /** Whether data is stale and needs revalidation */
24
+ isStale: boolean;
25
+ /** Last successful fetch timestamp */
26
+ lastFetchTime: number | null;
27
+ }
28
+ /**
29
+ * Feed error with additional context
30
+ */
31
+ interface FeedError {
32
+ /** Error message */
33
+ message: string;
34
+ /** Error code for programmatic handling */
35
+ code: 'NETWORK_ERROR' | 'TIMEOUT' | 'SERVER_ERROR' | 'UNKNOWN';
36
+ /** Number of retry attempts made */
37
+ retryCount: number;
38
+ /** Whether error is recoverable */
39
+ recoverable: boolean;
40
+ }
41
+ /**
42
+ * FeedManager configuration
43
+ */
44
+ interface FeedConfig {
45
+ /** Page size for pagination (default: 10) */
46
+ pageSize?: number;
47
+ /** Maximum retry attempts (default: 3) */
48
+ maxRetries?: number;
49
+ /** Base delay for exponential backoff in ms (default: 1000) */
50
+ retryBaseDelay?: number;
51
+ /** Stale time in ms - data older than this will be revalidated (default: 5 minutes) */
52
+ staleTime?: number;
53
+ /** Whether to enable SWR pattern (default: true) */
54
+ enableSWR?: boolean;
55
+ /**
56
+ * Maximum number of videos to keep in memory cache
57
+ * When exceeded, older videos will be evicted (LRU policy)
58
+ * @default 100
59
+ */
60
+ maxCacheSize?: number;
61
+ /**
62
+ * Whether to enable automatic garbage collection
63
+ * @default true
64
+ */
65
+ enableGC?: boolean;
66
+ }
67
+ /**
68
+ * Default feed configuration
69
+ */
70
+ declare const DEFAULT_FEED_CONFIG: Required<FeedConfig>;
71
+
72
+ /**
73
+ * FeedManager - Manages video feed data with zustand/vanilla store
74
+ *
75
+ * Features:
76
+ * - Data normalization (Map for O(1) lookup + ordered IDs)
77
+ * - Deduplication
78
+ * - Race condition protection
79
+ * - Request deduplication (prevents duplicate API calls)
80
+ * - Exponential backoff retry
81
+ * - SWR pattern support
82
+ * - Garbage Collection (LRU eviction when cache exceeds maxCacheSize)
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * const feedManager = new FeedManager(dataSource, { pageSize: 10 });
87
+ *
88
+ * // Subscribe to state changes
89
+ * feedManager.store.subscribe((state) => console.log(state));
90
+ *
91
+ * // Load initial data
92
+ * await feedManager.loadInitial();
93
+ *
94
+ * // Load more
95
+ * await feedManager.loadMore();
96
+ * ```
97
+ */
98
+ declare class FeedManager {
99
+ private readonly dataSource;
100
+ /** Zustand vanilla store - Single Source of Truth */
101
+ readonly store: StoreApi<FeedState>;
102
+ /** Resolved configuration */
103
+ private readonly config;
104
+ /** Abort controller for cancelling in-flight requests */
105
+ private abortController;
106
+ /**
107
+ * Request deduplication: Map of cursor → in-flight Promise
108
+ * Prevents duplicate API calls for the same cursor
109
+ */
110
+ private inFlightRequests;
111
+ /**
112
+ * LRU tracking: Map of videoId → lastAccessTime
113
+ * Used for garbage collection
114
+ */
115
+ private accessOrder;
116
+ constructor(dataSource: IDataSource, config?: FeedConfig);
117
+ /**
118
+ * Load initial feed data
119
+ *
120
+ * Implements SWR pattern:
121
+ * 1. If cached data exists, show it immediately
122
+ * 2. If data is stale (>staleTime), revalidate in background
123
+ * 3. If no cached data, fetch from network
124
+ *
125
+ * Request Deduplication:
126
+ * - If a request for the same cursor is already in-flight, returns the existing Promise
127
+ * - Prevents duplicate API calls from rapid UI interactions
128
+ */
129
+ loadInitial(): Promise<void>;
130
+ /**
131
+ * Internal: Execute load initial logic
132
+ */
133
+ private executeLoadInitial;
134
+ /**
135
+ * Load more videos (pagination)
136
+ *
137
+ * Features:
138
+ * - Race condition protection (prevents concurrent calls)
139
+ * - Request deduplication (prevents duplicate API calls for same cursor)
140
+ * - Exponential backoff retry on failure
141
+ */
142
+ loadMore(): Promise<void>;
143
+ /**
144
+ * Internal: Execute load more logic
145
+ */
146
+ private executeLoadMore;
147
+ /**
148
+ * Revalidate feed data in background (SWR pattern)
149
+ *
150
+ * Used when cached data is stale but still shown to user
151
+ */
152
+ revalidate(): Promise<void>;
153
+ /**
154
+ * Get a video by ID
155
+ * Also updates LRU access time for garbage collection
156
+ */
157
+ getVideo(id: string): VideoItem | undefined;
158
+ /**
159
+ * Get ordered list of videos
160
+ */
161
+ getVideos(): VideoItem[];
162
+ /**
163
+ * Update a video in the feed (for optimistic updates)
164
+ */
165
+ updateVideo(id: string, updates: Partial<VideoItem>): void;
166
+ /**
167
+ * Check if data is stale and needs revalidation
168
+ */
169
+ isStale(): boolean;
170
+ /**
171
+ * Reset feed state
172
+ */
173
+ reset(): void;
174
+ /**
175
+ * Cancel any pending requests
176
+ */
177
+ cancelPendingRequests(): void;
178
+ /**
179
+ * Destroy the manager and cleanup
180
+ */
181
+ destroy(): void;
182
+ /**
183
+ * Fetch with exponential backoff retry
184
+ */
185
+ private fetchWithRetry;
186
+ /**
187
+ * Add videos with deduplication
188
+ * Triggers garbage collection if cache exceeds maxCacheSize
189
+ */
190
+ private addVideos;
191
+ /**
192
+ * Merge videos (for SWR revalidation)
193
+ * Updates existing videos, adds new ones at the beginning
194
+ */
195
+ private mergeVideos;
196
+ /**
197
+ * Handle and categorize errors
198
+ */
199
+ private handleError;
200
+ /**
201
+ * Categorize error for better error handling
202
+ */
203
+ private categorizeError;
204
+ /**
205
+ * Sleep utility for retry delays
206
+ */
207
+ private sleep;
208
+ /**
209
+ * Run garbage collection using LRU (Least Recently Used) policy
210
+ *
211
+ * When cache size exceeds maxCacheSize:
212
+ * 1. Sort videos by last access time (oldest first)
213
+ * 2. Evict oldest videos until cache is within limit
214
+ * 3. Keep videos that are currently in viewport (most recent in displayOrder)
215
+ *
216
+ * @returns Number of evicted items
217
+ */
218
+ private runGarbageCollection;
219
+ /**
220
+ * Manually trigger garbage collection
221
+ * Useful for debugging or when memory pressure is detected
222
+ *
223
+ * @returns Number of evicted items
224
+ */
225
+ forceGarbageCollection(): number;
226
+ /**
227
+ * Get current cache statistics
228
+ * Useful for debugging and monitoring
229
+ */
230
+ getCacheStats(): {
231
+ size: number;
232
+ maxSize: number;
233
+ utilizationPercent: number;
234
+ oldestAccessTime: number | null;
235
+ newestAccessTime: number | null;
236
+ };
237
+ }
238
+
239
+ /**
240
+ * Player status enum - represents the current state of the player
241
+ */
242
+ declare enum PlayerStatus {
243
+ /** Initial state - no video loaded */
244
+ IDLE = "idle",
245
+ /** Loading video resources */
246
+ LOADING = "loading",
247
+ /** Video is playing */
248
+ PLAYING = "playing",
249
+ /** Video is paused */
250
+ PAUSED = "paused",
251
+ /** Buffering due to network/resource constraints */
252
+ BUFFERING = "buffering",
253
+ /** Error occurred */
254
+ ERROR = "error"
255
+ }
256
+ /**
257
+ * Player error with context
258
+ */
259
+ interface PlayerError {
260
+ /** Error message */
261
+ message: string;
262
+ /** Error code for programmatic handling */
263
+ code: 'MEDIA_ERROR' | 'NETWORK_ERROR' | 'DECODE_ERROR' | 'NOT_SUPPORTED' | 'UNKNOWN';
264
+ /** Whether the error is recoverable */
265
+ recoverable: boolean;
266
+ /** Original error if available */
267
+ originalError?: Error;
268
+ }
269
+ /**
270
+ * Player state for zustand store
271
+ */
272
+ interface PlayerState {
273
+ /** Current player status */
274
+ status: PlayerStatus;
275
+ /** Currently loaded video */
276
+ currentVideo: VideoItem | null;
277
+ /** Current playback time in seconds */
278
+ currentTime: number;
279
+ /** Video duration in seconds */
280
+ duration: number;
281
+ /** Buffered percentage (0-100) */
282
+ buffered: number;
283
+ /** Volume level (0-1) */
284
+ volume: number;
285
+ /** Whether video is muted */
286
+ muted: boolean;
287
+ /** Playback rate (1 = normal) */
288
+ playbackRate: number;
289
+ /** Loop count - how many times video has looped */
290
+ loopCount: number;
291
+ /** Total watch time in seconds for current video */
292
+ watchTime: number;
293
+ /** Error state */
294
+ error: PlayerError | null;
295
+ /** Whether video has ended */
296
+ ended: boolean;
297
+ }
298
+ /**
299
+ * PlayerEngine configuration
300
+ */
301
+ interface PlayerConfig {
302
+ /** Auto-play when video is loaded (default: true) */
303
+ autoplay?: boolean;
304
+ /** Default volume (default: 1) */
305
+ defaultVolume?: number;
306
+ /** Default muted state (default: true for mobile) */
307
+ defaultMuted?: boolean;
308
+ /** Enable looping (default: true for short videos) */
309
+ loop?: boolean;
310
+ /** Analytics adapter for tracking */
311
+ analytics?: IAnalytics;
312
+ /** Logger adapter */
313
+ logger?: ILogger;
314
+ /** Watch time threshold for analytics (seconds) (default: 3) */
315
+ watchTimeThreshold?: number;
316
+ /** Completion threshold percentage (default: 90) */
317
+ completionThreshold?: number;
318
+ /** Circuit Breaker configuration */
319
+ circuitBreaker?: CircuitBreakerConfig;
320
+ }
321
+ /**
322
+ * Default player configuration
323
+ */
324
+ declare const DEFAULT_PLAYER_CONFIG: Required<Omit<PlayerConfig, 'analytics' | 'logger' | 'circuitBreaker'>>;
325
+ /**
326
+ * Player events for external listening
327
+ */
328
+ type PlayerEvent = {
329
+ type: 'statusChange';
330
+ status: PlayerStatus;
331
+ previousStatus: PlayerStatus;
332
+ } | {
333
+ type: 'timeUpdate';
334
+ currentTime: number;
335
+ duration: number;
336
+ } | {
337
+ type: 'seek';
338
+ time: number;
339
+ } | {
340
+ type: 'ended';
341
+ loopCount: number;
342
+ watchTime: number;
343
+ } | {
344
+ type: 'error';
345
+ error: PlayerError;
346
+ } | {
347
+ type: 'videoChange';
348
+ video: VideoItem | null;
349
+ } | {
350
+ type: 'circuitOpened';
351
+ consecutiveErrors: number;
352
+ lastError: PlayerError;
353
+ } | {
354
+ type: 'circuitHalfOpen';
355
+ } | {
356
+ type: 'circuitClosed';
357
+ reason: 'success' | 'manual';
358
+ } | {
359
+ type: 'loadRejected';
360
+ reason: 'circuit_open';
361
+ };
362
+ /**
363
+ * Player event listener
364
+ */
365
+ type PlayerEventListener = (event: PlayerEvent) => void;
366
+ /**
367
+ * Circuit Breaker state
368
+ *
369
+ * - CLOSED: Normal operation, allow load()
370
+ * - OPEN: Reject load(), return cached error (circuit tripped)
371
+ * - HALF_OPEN: Allow 1 try, success→CLOSED, fail→OPEN
372
+ */
373
+ declare enum CircuitState {
374
+ /** Normal operation - allow video loading */
375
+ CLOSED = "closed",
376
+ /** Circuit tripped - reject video loading */
377
+ OPEN = "open",
378
+ /** Testing state - allow one request to test recovery */
379
+ HALF_OPEN = "half_open"
380
+ }
381
+ /**
382
+ * Circuit Breaker configuration
383
+ */
384
+ interface CircuitBreakerConfig {
385
+ /**
386
+ * Number of consecutive recoverable errors to trip the circuit
387
+ * @default 3
388
+ */
389
+ consecutiveErrorThreshold?: number;
390
+ /**
391
+ * Time in ms before transitioning from OPEN to HALF_OPEN
392
+ * @default 30000 (30 seconds)
393
+ */
394
+ resetTimeoutMs?: number;
395
+ /**
396
+ * Number of successful loads needed to close the circuit from HALF_OPEN
397
+ * @default 1
398
+ */
399
+ successThreshold?: number;
400
+ /**
401
+ * Enable circuit breaker (default: true)
402
+ * Set to false to disable circuit breaker behavior
403
+ */
404
+ enabled?: boolean;
405
+ }
406
+ /**
407
+ * Circuit Breaker internal state
408
+ */
409
+ interface CircuitBreakerState {
410
+ /** Current circuit state */
411
+ state: CircuitState;
412
+ /** Number of consecutive recoverable errors */
413
+ consecutiveErrors: number;
414
+ /** Number of successful loads in HALF_OPEN state */
415
+ halfOpenSuccesses: number;
416
+ /** Timestamp when circuit was opened (for reset timeout) */
417
+ openedAt: number | null;
418
+ /** Last error that caused the circuit to open */
419
+ lastError: PlayerError | null;
420
+ }
421
+
422
+ /**
423
+ * PlayerEngine - Video player state machine with zustand/vanilla store
424
+ *
425
+ * Features:
426
+ * - Guarded state transitions (only valid transitions allowed)
427
+ * - Watch time tracking
428
+ * - Loop count tracking
429
+ * - Analytics integration
430
+ * - Event system for external listeners
431
+ *
432
+ * @example
433
+ * ```typescript
434
+ * const player = new PlayerEngine({ analytics, logger });
435
+ *
436
+ * // Subscribe to state changes
437
+ * player.store.subscribe((state) => console.log(state.status));
438
+ *
439
+ * // Load and play a video
440
+ * player.load(videoItem);
441
+ *
442
+ * // Control playback
443
+ * player.play();
444
+ * player.pause();
445
+ * player.seek(10);
446
+ * ```
447
+ */
448
+ declare class PlayerEngine {
449
+ /** Zustand vanilla store - Single Source of Truth */
450
+ readonly store: StoreApi<PlayerState>;
451
+ /** Resolved configuration */
452
+ private readonly config;
453
+ /** Circuit Breaker configuration */
454
+ private readonly circuitBreakerConfig;
455
+ /** Analytics adapter */
456
+ private readonly analytics?;
457
+ /** Logger adapter */
458
+ private readonly logger?;
459
+ /** Event listeners */
460
+ private readonly eventListeners;
461
+ /** Watch time tracking interval */
462
+ private watchTimeInterval;
463
+ /** Last status for tracking transitions */
464
+ private lastStatus;
465
+ /** Circuit Breaker state */
466
+ private circuitBreaker;
467
+ /** Circuit Breaker reset timer */
468
+ private circuitResetTimer;
469
+ constructor(config?: PlayerConfig);
470
+ /**
471
+ * Load a video and prepare for playback
472
+ *
473
+ * @returns true if load was initiated, false if rejected (circuit open or invalid state)
474
+ */
475
+ load(video: VideoItem): boolean;
476
+ /**
477
+ * Called when video is ready to play (after loading)
478
+ */
479
+ onReady(): boolean;
480
+ /**
481
+ * Start or resume playback
482
+ */
483
+ play(): boolean;
484
+ /**
485
+ * Pause playback
486
+ */
487
+ pause(): boolean;
488
+ /**
489
+ * Seek to a specific time
490
+ *
491
+ * Emits a 'seek' event that VideoPlayer listens to
492
+ * to apply the seek to the actual video element.
493
+ */
494
+ seek(time: number): boolean;
495
+ /**
496
+ * Set volume
497
+ */
498
+ setVolume(volume: number): void;
499
+ /**
500
+ * Toggle mute
501
+ */
502
+ setMuted(muted: boolean): void;
503
+ /**
504
+ * Set playback rate
505
+ */
506
+ setPlaybackRate(rate: number): void;
507
+ /**
508
+ * Handle time update from video element
509
+ */
510
+ onTimeUpdate(currentTime: number): void;
511
+ /**
512
+ * Handle duration change from video element
513
+ *
514
+ * Called when video metadata is loaded and actual duration is available.
515
+ * This may differ from the API-provided duration in VideoItem.
516
+ *
517
+ * @param duration - Actual video duration in seconds from video element
518
+ */
519
+ onDurationChange(duration: number): void;
520
+ /**
521
+ * Handle buffering start
522
+ */
523
+ onBuffering(): boolean;
524
+ /**
525
+ * Handle buffering end
526
+ */
527
+ onBufferingEnd(): boolean;
528
+ /**
529
+ * Handle buffer progress update
530
+ */
531
+ onBufferProgress(bufferedPercent: number): void;
532
+ /**
533
+ * Handle video ended
534
+ */
535
+ onEnded(): void;
536
+ /**
537
+ * Handle video error
538
+ */
539
+ onError(error: Error, mediaError?: MediaError): void;
540
+ /**
541
+ * Reset player to initial state
542
+ */
543
+ reset(): void;
544
+ /**
545
+ * Destroy player and cleanup
546
+ */
547
+ destroy(): void;
548
+ /**
549
+ * Add event listener
550
+ */
551
+ addEventListener(listener: PlayerEventListener): () => void;
552
+ /**
553
+ * Remove event listener
554
+ */
555
+ removeEventListener(listener: PlayerEventListener): void;
556
+ /**
557
+ * Get current status
558
+ */
559
+ getStatus(): PlayerStatus;
560
+ /**
561
+ * Get current video
562
+ */
563
+ getCurrentVideo(): VideoItem | null;
564
+ /**
565
+ * Check if playing
566
+ */
567
+ isPlaying(): boolean;
568
+ /**
569
+ * Check if paused
570
+ */
571
+ isPaused(): boolean;
572
+ /**
573
+ * Attempt to transition to a new state
574
+ */
575
+ private transitionTo;
576
+ /**
577
+ * Emit event to all listeners
578
+ */
579
+ private emitEvent;
580
+ /**
581
+ * Start watch time tracking
582
+ */
583
+ private startWatchTimeTracking;
584
+ /**
585
+ * Stop watch time tracking
586
+ */
587
+ private stopWatchTimeTracking;
588
+ /**
589
+ * Track completion analytics
590
+ */
591
+ private trackCompletion;
592
+ /**
593
+ * Categorize media error
594
+ */
595
+ private categorizeError;
596
+ /**
597
+ * Get current circuit breaker state
598
+ */
599
+ getCircuitBreakerState(): CircuitBreakerState;
600
+ /**
601
+ * Check if circuit breaker is open (blocking loads)
602
+ */
603
+ isCircuitOpen(): boolean;
604
+ /**
605
+ * Manually reset circuit breaker to CLOSED state
606
+ * Useful for user-triggered retry after showing error UI
607
+ */
608
+ resetCircuitBreaker(): void;
609
+ /**
610
+ * Check if circuit breaker allows load operation
611
+ * Also handles OPEN → HALF_OPEN transition based on timeout
612
+ */
613
+ private checkCircuitBreaker;
614
+ /**
615
+ * Record a recoverable error for circuit breaker tracking
616
+ */
617
+ private recordCircuitBreakerError;
618
+ /**
619
+ * Record a successful load for circuit breaker tracking
620
+ */
621
+ private recordCircuitBreakerSuccess;
622
+ /**
623
+ * Trip the circuit breaker (CLOSED/HALF_OPEN → OPEN)
624
+ */
625
+ private tripCircuit;
626
+ /**
627
+ * Transition from OPEN to HALF_OPEN
628
+ */
629
+ private transitionToHalfOpen;
630
+ /**
631
+ * Close the circuit breaker (HALF_OPEN → CLOSED)
632
+ */
633
+ private closeCircuit;
634
+ /**
635
+ * Schedule automatic transition from OPEN to HALF_OPEN
636
+ */
637
+ private scheduleHalfOpenTransition;
638
+ /**
639
+ * Clear circuit reset timer
640
+ */
641
+ private clearCircuitResetTimer;
642
+ }
643
+
644
+ /**
645
+ * Check if a state transition is valid
646
+ */
647
+ declare function isValidTransition(from: PlayerStatus, to: PlayerStatus): boolean;
648
+ /**
649
+ * Check if player is in a "active" state (can receive playback commands)
650
+ */
651
+ declare function isActiveState(status: PlayerStatus): boolean;
652
+ /**
653
+ * Check if player can start playback
654
+ */
655
+ declare function canPlay(status: PlayerStatus): boolean;
656
+ /**
657
+ * Check if player can pause
658
+ */
659
+ declare function canPause(status: PlayerStatus): boolean;
660
+ /**
661
+ * Check if player can seek
662
+ */
663
+ declare function canSeek(status: PlayerStatus): boolean;
664
+
665
+ /**
666
+ * Lifecycle state for zustand store
667
+ */
668
+ interface LifecycleState {
669
+ /** Whether manager is initialized */
670
+ isInitialized: boolean;
671
+ /** Whether there's a pending save operation */
672
+ isSaving: boolean;
673
+ /** Whether there's a pending restore operation */
674
+ isRestoring: boolean;
675
+ /** Last saved timestamp */
676
+ lastSavedAt: number | null;
677
+ /** Last restored timestamp */
678
+ lastRestoredAt: number | null;
679
+ /** Whether the restored snapshot needs revalidation */
680
+ needsRevalidation: boolean;
681
+ /** Current visibility state */
682
+ visibilityState: DocumentVisibilityState;
683
+ }
684
+ /**
685
+ * Restore result
686
+ */
687
+ interface RestoreResult {
688
+ /** Whether restore was successful */
689
+ success: boolean;
690
+ /** Restored snapshot data (null if no valid snapshot) */
691
+ snapshot: SessionSnapshot | null;
692
+ /** Whether the restored data is stale and needs revalidation */
693
+ needsRevalidation: boolean;
694
+ /** Reason for failure (if any) */
695
+ reason?: 'no_snapshot' | 'expired' | 'invalid' | 'error';
696
+ /** Playback time in seconds (only present if restorePlaybackPosition config is enabled) */
697
+ playbackTime?: number;
698
+ /** Video ID that was playing (only present if restorePlaybackPosition config is enabled) */
699
+ currentVideoId?: string;
700
+ }
701
+ /**
702
+ * LifecycleManager configuration
703
+ */
704
+ interface LifecycleConfig {
705
+ /** Storage adapter for persistence */
706
+ storage?: ISessionStorage;
707
+ /** Logger adapter */
708
+ logger?: ILogger;
709
+ /** Snapshot expiry time in ms (default: 24 hours) */
710
+ snapshotExpiryMs?: number;
711
+ /** Revalidation threshold in ms (default: 5 minutes) */
712
+ revalidationThresholdMs?: number;
713
+ /** Auto-save on visibility change (default: true) */
714
+ autoSaveOnHidden?: boolean;
715
+ /**
716
+ * Enable restoring video playback position (default: false)
717
+ * When enabled, saves and restores the exact playback time (seconds)
718
+ * so video can seek() to the exact position user was watching
719
+ */
720
+ restorePlaybackPosition?: boolean;
721
+ /** SDK version for compatibility */
722
+ version?: string;
723
+ }
724
+ /**
725
+ * Default lifecycle configuration
726
+ */
727
+ declare const DEFAULT_LIFECYCLE_CONFIG: Required<Omit<LifecycleConfig, 'storage' | 'logger'>>;
728
+ /**
729
+ * Lifecycle events for external listening
730
+ */
731
+ type LifecycleEvent = {
732
+ type: 'saveStart';
733
+ } | {
734
+ type: 'saveComplete';
735
+ timestamp: number;
736
+ } | {
737
+ type: 'saveFailed';
738
+ error: Error;
739
+ } | {
740
+ type: 'restoreStart';
741
+ } | {
742
+ type: 'restoreComplete';
743
+ result: RestoreResult;
744
+ } | {
745
+ type: 'visibilityChange';
746
+ state: DocumentVisibilityState;
747
+ };
748
+ /**
749
+ * Lifecycle event listener
750
+ */
751
+ type LifecycleEventListener = (event: LifecycleEvent) => void;
752
+
753
+ /**
754
+ * LifecycleManager - Handles session persistence and restoration
755
+ *
756
+ * Strategy: "State Persistence, DOM Destruction"
757
+ * - Save snapshot to storage when user leaves
758
+ * - Restore state from storage when user returns
759
+ * - Video DOM nodes are destroyed to free RAM
760
+ *
761
+ * Features:
762
+ * - Auto-save on visibility change (optional)
763
+ * - Snapshot expiry (24 hours default)
764
+ * - Stale data detection for revalidation
765
+ * - Event system for UI coordination
766
+ *
767
+ * @example
768
+ * ```typescript
769
+ * const lifecycle = new LifecycleManager({ storage, logger });
770
+ *
771
+ * // Initialize and attempt restore
772
+ * const result = await lifecycle.initialize();
773
+ * if (result.success) {
774
+ * feedManager.restoreFromSnapshot(result.snapshot);
775
+ * if (result.needsRevalidation) {
776
+ * feedManager.revalidate();
777
+ * }
778
+ * }
779
+ *
780
+ * // Save snapshot before leaving
781
+ * lifecycle.saveSnapshot({
782
+ * items: feedManager.getVideos(),
783
+ * cursor: feedManager.store.getState().cursor,
784
+ * focusedIndex: resourceGovernor.store.getState().focusedIndex,
785
+ * });
786
+ * ```
787
+ */
788
+ declare class LifecycleManager {
789
+ /** Zustand vanilla store - Single Source of Truth */
790
+ readonly store: StoreApi<LifecycleState>;
791
+ /** Resolved configuration */
792
+ private readonly config;
793
+ /** Storage adapter */
794
+ private readonly storage?;
795
+ /** Logger adapter */
796
+ private readonly logger?;
797
+ /** Event listeners */
798
+ private readonly eventListeners;
799
+ /** Visibility change handler reference (for cleanup) */
800
+ private visibilityHandler?;
801
+ /** Pending save data (for debouncing) */
802
+ private pendingSaveData?;
803
+ constructor(config?: LifecycleConfig);
804
+ /**
805
+ * Initialize lifecycle manager and attempt to restore session
806
+ */
807
+ initialize(): Promise<RestoreResult>;
808
+ /**
809
+ * Destroy lifecycle manager and cleanup
810
+ */
811
+ destroy(): void;
812
+ /**
813
+ * Restore session from storage
814
+ */
815
+ restoreSession(): Promise<RestoreResult>;
816
+ /**
817
+ * Save session snapshot to storage
818
+ *
819
+ * @param data - Snapshot data to save
820
+ * @param data.playbackTime - Current video playback position (only saved if restorePlaybackPosition config is enabled)
821
+ * @param data.currentVideoId - Current video ID (only saved if restorePlaybackPosition config is enabled)
822
+ */
823
+ saveSnapshot(data: {
824
+ items: VideoItem[];
825
+ cursor: string | null;
826
+ focusedIndex: number;
827
+ scrollPosition?: number;
828
+ /** Current video playback time in seconds (only used when restorePlaybackPosition is enabled) */
829
+ playbackTime?: number;
830
+ /** Current video ID (only used when restorePlaybackPosition is enabled) */
831
+ currentVideoId?: string;
832
+ }): Promise<boolean>;
833
+ /**
834
+ * Clear saved snapshot
835
+ */
836
+ clearSnapshot(): Promise<void>;
837
+ /**
838
+ * Mark that pending data should be saved (for use with debouncing)
839
+ *
840
+ * @param data - Data to save when flush is called
841
+ */
842
+ setPendingSave(data: Omit<SessionSnapshot, 'savedAt' | 'version'>): void;
843
+ /**
844
+ * Check if restorePlaybackPosition config is enabled
845
+ * SDK can use this to decide whether to collect playbackTime
846
+ */
847
+ isPlaybackPositionRestoreEnabled(): boolean;
848
+ /**
849
+ * Flush pending save (called on visibility hidden)
850
+ */
851
+ flushPendingSave(): Promise<void>;
852
+ /**
853
+ * Handle visibility state change
854
+ */
855
+ onVisibilityChange(state: DocumentVisibilityState): void;
856
+ /**
857
+ * Get current visibility state
858
+ */
859
+ getVisibilityState(): DocumentVisibilityState;
860
+ /**
861
+ * Add event listener
862
+ */
863
+ addEventListener(listener: LifecycleEventListener): () => void;
864
+ /**
865
+ * Remove event listener
866
+ */
867
+ removeEventListener(listener: LifecycleEventListener): void;
868
+ /**
869
+ * Setup visibility change listener
870
+ */
871
+ private setupVisibilityListener;
872
+ /**
873
+ * Validate snapshot data
874
+ */
875
+ private validateSnapshot;
876
+ /**
877
+ * Check if snapshot is stale (needs revalidation)
878
+ */
879
+ private isSnapshotStale;
880
+ /**
881
+ * Emit event to all listeners
882
+ */
883
+ private emitEvent;
884
+ }
885
+
886
+ /**
887
+ * Network type for prefetch strategy
888
+ * Simplified from contracts NetworkType for prefetch decisions
889
+ */
890
+ type NetworkType = 'wifi' | 'cellular' | 'offline';
891
+ /**
892
+ * Map contracts NetworkType to simplified NetworkType
893
+ */
894
+ declare function mapNetworkType(type: string): NetworkType;
895
+ /**
896
+ * Resource state for zustand store
897
+ */
898
+ interface ResourceState {
899
+ /** Currently allocated video slot indices (max 3) */
900
+ activeAllocations: Set<number>;
901
+ /** Queue of indices to preload */
902
+ preloadQueue: number[];
903
+ /** Currently focused/playing video index */
904
+ focusedIndex: number;
905
+ /** Current network type */
906
+ networkType: NetworkType;
907
+ /** Total number of items in feed (for bounds checking) */
908
+ totalItems: number;
909
+ /** Whether resource governor is active */
910
+ isActive: boolean;
911
+ /** Whether preloading is throttled due to scroll thrashing */
912
+ isThrottled: boolean;
913
+ /** Indices currently being preloaded */
914
+ preloadingIndices: Set<number>;
915
+ }
916
+ /**
917
+ * Allocation result returned by requestAllocation
918
+ */
919
+ interface AllocationResult {
920
+ /** Indices that should mount video elements */
921
+ toMount: number[];
922
+ /** Indices that should unmount video elements */
923
+ toUnmount: number[];
924
+ /** Currently active allocations after this operation */
925
+ activeAllocations: number[];
926
+ /** Whether focus change was successful */
927
+ success: boolean;
928
+ }
929
+ /**
930
+ * Prefetch configuration by network type
931
+ */
932
+ interface PrefetchConfig {
933
+ /** Number of posters to prefetch ahead (default: wifi=5, cellular=3, offline=1) */
934
+ posterCount: number;
935
+ /** Number of video segments to prefetch (default: wifi=2, cellular=1, offline=0) */
936
+ videoSegmentCount: number;
937
+ /** Whether to prefetch video at all (default: true for wifi/cellular) */
938
+ prefetchVideo: boolean;
939
+ }
940
+ /**
941
+ * Scroll thrashing configuration
942
+ * Prevents excessive preloading when user scrolls too fast
943
+ */
944
+ interface ScrollThrashingConfig {
945
+ /** Time window to measure scroll rate (ms) - default: 1000 */
946
+ windowMs: number;
947
+ /** Max focus changes allowed in window before throttling - default: 3 */
948
+ maxChangesInWindow: number;
949
+ /** Cooldown time after throttle before resuming preload (ms) - default: 500 */
950
+ cooldownMs: number;
951
+ }
952
+ /**
953
+ * ResourceGovernor configuration
954
+ */
955
+ interface ResourceConfig {
956
+ /** Maximum video DOM nodes (default: 3) */
957
+ maxAllocations?: number;
958
+ /** Focus debounce time in ms (default: 150) */
959
+ focusDebounceMs?: number;
960
+ /** Prefetch configuration overrides by network type */
961
+ prefetch?: Partial<Record<NetworkType, Partial<PrefetchConfig>>>;
962
+ /** Scroll thrashing configuration */
963
+ scrollThrashing?: Partial<ScrollThrashingConfig>;
964
+ /** Network adapter for detecting connection type */
965
+ networkAdapter?: INetworkAdapter;
966
+ /** Video loader adapter for preloading video data */
967
+ videoLoader?: IVideoLoader;
968
+ /** Poster loader adapter for preloading thumbnails */
969
+ posterLoader?: IPosterLoader;
970
+ /** Logger adapter */
971
+ logger?: ILogger;
972
+ }
973
+ /**
974
+ * Default prefetch configuration by network type
975
+ */
976
+ declare const DEFAULT_PREFETCH_CONFIG: Record<NetworkType, PrefetchConfig>;
977
+ /**
978
+ * Default resource configuration
979
+ */
980
+ declare const DEFAULT_RESOURCE_CONFIG: Required<Omit<ResourceConfig, 'networkAdapter' | 'videoLoader' | 'posterLoader' | 'logger' | 'prefetch' | 'scrollThrashing'>> & {
981
+ prefetch: Record<NetworkType, PrefetchConfig>;
982
+ scrollThrashing: ScrollThrashingConfig;
983
+ };
984
+ /**
985
+ * Resource events for external listening
986
+ */
987
+ type ResourceEvent = {
988
+ type: 'allocationChange';
989
+ toMount: number[];
990
+ toUnmount: number[];
991
+ } | {
992
+ type: 'focusChange';
993
+ index: number;
994
+ previousIndex: number;
995
+ } | {
996
+ type: 'networkChange';
997
+ networkType: NetworkType;
998
+ } | {
999
+ type: 'prefetchRequest';
1000
+ indices: number[];
1001
+ };
1002
+ /**
1003
+ * Resource event listener
1004
+ */
1005
+ type ResourceEventListener = (event: ResourceEvent) => void;
1006
+
1007
+ /**
1008
+ * Video source getter function type
1009
+ * ResourceGovernor needs this to get video sources for preloading
1010
+ */
1011
+ type VideoSourceGetter = (index: number) => {
1012
+ id: string;
1013
+ source: VideoSource;
1014
+ poster?: string;
1015
+ } | null;
1016
+ /**
1017
+ * ResourceGovernor - Manages video DOM allocation and prefetch strategy
1018
+ *
1019
+ * Features:
1020
+ * - Sliding window allocation (max 3 video DOM nodes)
1021
+ * - Focus debouncing for smooth swipe
1022
+ * - Network-aware prefetch strategy
1023
+ * - Event system for UI synchronization
1024
+ *
1025
+ * Memory Strategy:
1026
+ * - Only 3 video elements exist in DOM at any time
1027
+ * - Sliding window: [previous, current, next]
1028
+ * - When user scrolls, we unmount far elements and mount new ones
1029
+ *
1030
+ * @example
1031
+ * ```typescript
1032
+ * const governor = new ResourceGovernor({ maxAllocations: 3 });
1033
+ *
1034
+ * // Initialize with feed size
1035
+ * governor.setTotalItems(20);
1036
+ * governor.activate();
1037
+ *
1038
+ * // Handle scroll/swipe
1039
+ * governor.setFocusedIndex(5); // Debounced
1040
+ *
1041
+ * // Listen to allocation changes
1042
+ * governor.addEventListener((event) => {
1043
+ * if (event.type === 'allocationChange') {
1044
+ * event.toMount.forEach(i => mountVideoAt(i));
1045
+ * event.toUnmount.forEach(i => unmountVideoAt(i));
1046
+ * }
1047
+ * });
1048
+ * ```
1049
+ */
1050
+ declare class ResourceGovernor {
1051
+ /** Zustand vanilla store - Single Source of Truth */
1052
+ readonly store: StoreApi<ResourceState>;
1053
+ /** Resolved configuration */
1054
+ private readonly config;
1055
+ /** Network adapter */
1056
+ private readonly networkAdapter?;
1057
+ /** Video loader adapter for preloading video data */
1058
+ private readonly videoLoader?;
1059
+ /** Poster loader adapter for preloading thumbnails */
1060
+ private readonly posterLoader?;
1061
+ /** Logger adapter */
1062
+ private readonly logger?;
1063
+ /** Event listeners */
1064
+ private readonly eventListeners;
1065
+ /** Focus debounce timer */
1066
+ private focusDebounceTimer;
1067
+ /** Pending focused index (before debounce completes) */
1068
+ private pendingFocusedIndex;
1069
+ /** Network change unsubscribe function */
1070
+ private networkUnsubscribe?;
1071
+ /** Video source getter (injected via setVideoSourceGetter) */
1072
+ private videoSourceGetter?;
1073
+ /** Scroll thrashing detection - timestamps of recent focus changes */
1074
+ private focusChangeTimestamps;
1075
+ /** Scroll thrashing cooldown timer */
1076
+ private thrashingCooldownTimer;
1077
+ constructor(config?: ResourceConfig);
1078
+ /**
1079
+ * Activate the resource governor
1080
+ * Starts network monitoring and performs initial allocation
1081
+ */
1082
+ activate(): Promise<void>;
1083
+ /**
1084
+ * Deactivate the resource governor
1085
+ * Stops network monitoring and clears allocations
1086
+ */
1087
+ deactivate(): void;
1088
+ /**
1089
+ * Destroy the resource governor
1090
+ */
1091
+ destroy(): void;
1092
+ /**
1093
+ * Set total number of items in feed
1094
+ */
1095
+ setTotalItems(count: number): void;
1096
+ /**
1097
+ * Set focused index with debouncing
1098
+ * This is called during scroll/swipe
1099
+ */
1100
+ setFocusedIndex(index: number): void;
1101
+ /**
1102
+ * Set focused index immediately (skip debounce)
1103
+ * Use this for programmatic navigation, not scroll
1104
+ */
1105
+ setFocusedIndexImmediate(index: number): void;
1106
+ /**
1107
+ * Request allocation for given indices
1108
+ * Returns what needs to be mounted/unmounted
1109
+ */
1110
+ requestAllocation(indices: number[]): AllocationResult;
1111
+ /**
1112
+ * Get current active allocations
1113
+ */
1114
+ getActiveAllocations(): number[];
1115
+ /**
1116
+ * Check if an index is currently allocated
1117
+ */
1118
+ isAllocated(index: number): boolean;
1119
+ /**
1120
+ * Get current network type
1121
+ */
1122
+ getNetworkType(): NetworkType;
1123
+ /**
1124
+ * Get prefetch configuration for current network
1125
+ */
1126
+ getPrefetchConfig(): PrefetchConfig;
1127
+ /**
1128
+ * Set video source getter function
1129
+ * This is called by SDK to provide video data for preloading
1130
+ *
1131
+ * @param getter - Function that returns video info for a given index
1132
+ */
1133
+ setVideoSourceGetter(getter: VideoSourceGetter): void;
1134
+ /**
1135
+ * Check if preloading is currently throttled due to scroll thrashing
1136
+ */
1137
+ isPreloadThrottled(): boolean;
1138
+ /**
1139
+ * Get indices currently being preloaded
1140
+ */
1141
+ getPreloadingIndices(): number[];
1142
+ /**
1143
+ * Manually trigger preload for specific indices
1144
+ * Respects throttling and network conditions
1145
+ */
1146
+ triggerPreload(indices: number[]): Promise<void>;
1147
+ /**
1148
+ * Add event listener
1149
+ */
1150
+ addEventListener(listener: ResourceEventListener): () => void;
1151
+ /**
1152
+ * Remove event listener
1153
+ */
1154
+ removeEventListener(listener: ResourceEventListener): void;
1155
+ /**
1156
+ * Initialize network detection
1157
+ */
1158
+ private initializeNetwork;
1159
+ /**
1160
+ * Perform allocation for a given focused index
1161
+ */
1162
+ private performAllocation;
1163
+ /**
1164
+ * Update prefetch queue based on current state
1165
+ */
1166
+ private updatePrefetchQueue;
1167
+ /**
1168
+ * Track focus change timestamp for scroll thrashing detection
1169
+ */
1170
+ private trackFocusChange;
1171
+ /**
1172
+ * Cancel all in-progress preloads
1173
+ */
1174
+ private cancelAllPreloads;
1175
+ /**
1176
+ * Execute video preloading for given indices
1177
+ */
1178
+ private executePreload;
1179
+ /**
1180
+ * Execute poster preloading for given indices
1181
+ */
1182
+ private executePreloadPosters;
1183
+ /**
1184
+ * Emit event to all listeners
1185
+ */
1186
+ private emitEvent;
1187
+ }
1188
+
1189
+ /**
1190
+ * Calculate sliding window allocation around focused index
1191
+ *
1192
+ * The sliding window strategy:
1193
+ * - Always allocate focused index (current video)
1194
+ * - Allocate previous index (for scroll up)
1195
+ * - Allocate next index (for scroll down)
1196
+ * - Total max 3 allocations
1197
+ *
1198
+ * @param focusedIndex Current focused video index
1199
+ * @param totalItems Total number of items in feed
1200
+ * @param maxAllocations Maximum allocations allowed (default: 3)
1201
+ * @returns Array of indices that should be allocated
1202
+ */
1203
+ declare function calculateWindowIndices(focusedIndex: number, totalItems: number, maxAllocations?: number): number[];
1204
+ /**
1205
+ * Calculate prefetch indices beyond the window
1206
+ *
1207
+ * @param focusedIndex Current focused video index
1208
+ * @param totalItems Total number of items in feed
1209
+ * @param prefetchCount Number of items to prefetch ahead
1210
+ * @param windowIndices Currently allocated window indices
1211
+ * @returns Array of indices to prefetch (posters)
1212
+ */
1213
+ declare function calculatePrefetchIndices(focusedIndex: number, totalItems: number, prefetchCount: number, windowIndices: number[]): number[];
1214
+ /**
1215
+ * Compute allocation changes between current and desired state
1216
+ *
1217
+ * @param currentAllocations Current active allocations
1218
+ * @param desiredAllocations Desired allocations based on new focus
1219
+ * @returns AllocationResult with toMount and toUnmount arrays
1220
+ */
1221
+ declare function computeAllocationChanges(currentAllocations: Set<number>, desiredAllocations: number[]): Omit<AllocationResult, 'success'>;
1222
+
1223
+ /**
1224
+ * Types of optimistic actions
1225
+ */
1226
+ type ActionType = 'like' | 'unlike' | 'follow' | 'unfollow';
1227
+ /**
1228
+ * Pending optimistic action
1229
+ */
1230
+ interface PendingAction {
1231
+ /** Unique action ID */
1232
+ id: string;
1233
+ /** Action type */
1234
+ type: ActionType;
1235
+ /** Target video ID */
1236
+ videoId: string;
1237
+ /** Data to rollback to on failure */
1238
+ rollbackData: Partial<VideoItem>;
1239
+ /** Timestamp when action was initiated */
1240
+ timestamp: number;
1241
+ /** Current status */
1242
+ status: 'pending' | 'success' | 'failed';
1243
+ /** Number of retry attempts */
1244
+ retryCount: number;
1245
+ /** Error message if failed */
1246
+ error?: string;
1247
+ }
1248
+ /**
1249
+ * Optimistic state for zustand store
1250
+ */
1251
+ interface OptimisticState {
1252
+ /** Map of pending actions by ID */
1253
+ pendingActions: Map<string, PendingAction>;
1254
+ /** Queue of failed actions for retry */
1255
+ failedQueue: string[];
1256
+ /** Whether there are any pending actions */
1257
+ hasPending: boolean;
1258
+ /** Whether retry is in progress */
1259
+ isRetrying: boolean;
1260
+ }
1261
+ /**
1262
+ * OptimisticManager configuration
1263
+ */
1264
+ interface OptimisticConfig {
1265
+ /** Interaction adapter for API calls */
1266
+ interaction?: IInteraction;
1267
+ /** FeedManager for updating video state */
1268
+ feedManager?: FeedManager;
1269
+ /** Logger adapter */
1270
+ logger?: ILogger;
1271
+ /** Maximum retry attempts (default: 3) */
1272
+ maxRetries?: number;
1273
+ /** Retry delay in ms (default: 1000) */
1274
+ retryDelayMs?: number;
1275
+ /** Whether to auto-retry failed actions (default: true) */
1276
+ autoRetry?: boolean;
1277
+ }
1278
+ /**
1279
+ * Default optimistic configuration
1280
+ */
1281
+ declare const DEFAULT_OPTIMISTIC_CONFIG: Required<Omit<OptimisticConfig, 'interaction' | 'feedManager' | 'logger'>>;
1282
+ /**
1283
+ * Optimistic events for external listening
1284
+ */
1285
+ type OptimisticEvent = {
1286
+ type: 'actionStart';
1287
+ action: PendingAction;
1288
+ } | {
1289
+ type: 'actionSuccess';
1290
+ action: PendingAction;
1291
+ } | {
1292
+ type: 'actionFailed';
1293
+ action: PendingAction;
1294
+ error: Error;
1295
+ } | {
1296
+ type: 'actionRollback';
1297
+ action: PendingAction;
1298
+ } | {
1299
+ type: 'retryStart';
1300
+ actionId: string;
1301
+ } | {
1302
+ type: 'retryExhausted';
1303
+ action: PendingAction;
1304
+ };
1305
+ /**
1306
+ * Optimistic event listener
1307
+ */
1308
+ type OptimisticEventListener = (event: OptimisticEvent) => void;
1309
+
1310
+ /**
1311
+ * OptimisticManager - Handles optimistic UI updates with rollback
1312
+ *
1313
+ * Pattern:
1314
+ * 1. User action (like) -> Update UI immediately
1315
+ * 2. Call API in background
1316
+ * 3. On success -> Keep UI state, remove pending action
1317
+ * 4. On error -> Rollback to previous state
1318
+ *
1319
+ * Features:
1320
+ * - Immediate UI feedback
1321
+ * - Automatic rollback on failure
1322
+ * - Retry queue for failed actions
1323
+ * - Prevents duplicate pending actions
1324
+ *
1325
+ * @example
1326
+ * ```typescript
1327
+ * const optimistic = new OptimisticManager({
1328
+ * interaction,
1329
+ * feedManager,
1330
+ * logger,
1331
+ * });
1332
+ *
1333
+ * // User taps like button
1334
+ * await optimistic.like(videoId);
1335
+ *
1336
+ * // User taps follow button
1337
+ * await optimistic.follow(videoId);
1338
+ * ```
1339
+ */
1340
+ declare class OptimisticManager {
1341
+ /** Zustand vanilla store - Single Source of Truth */
1342
+ readonly store: StoreApi<OptimisticState>;
1343
+ /** Resolved configuration */
1344
+ private readonly config;
1345
+ /** Interaction adapter */
1346
+ private readonly interaction?;
1347
+ /** Feed manager for updating video state */
1348
+ private readonly feedManager?;
1349
+ /** Logger adapter */
1350
+ private readonly logger?;
1351
+ /** Event listeners */
1352
+ private readonly eventListeners;
1353
+ /** Retry timer */
1354
+ private retryTimer;
1355
+ constructor(config?: OptimisticConfig);
1356
+ /**
1357
+ * Like a video with optimistic update
1358
+ */
1359
+ like(videoId: string): Promise<boolean>;
1360
+ /**
1361
+ * Unlike a video with optimistic update
1362
+ */
1363
+ unlike(videoId: string): Promise<boolean>;
1364
+ /**
1365
+ * Toggle like state (like if not liked, unlike if liked)
1366
+ */
1367
+ toggleLike(videoId: string): Promise<boolean>;
1368
+ /**
1369
+ * Follow a video author with optimistic update
1370
+ */
1371
+ follow(videoId: string): Promise<boolean>;
1372
+ /**
1373
+ * Unfollow a video author with optimistic update
1374
+ */
1375
+ unfollow(videoId: string): Promise<boolean>;
1376
+ /**
1377
+ * Toggle follow state
1378
+ */
1379
+ toggleFollow(videoId: string): Promise<boolean>;
1380
+ /**
1381
+ * Get all pending actions
1382
+ */
1383
+ getPendingActions(): PendingAction[];
1384
+ /**
1385
+ * Check if there's a pending action for a video
1386
+ */
1387
+ hasPendingAction(videoId: string, type?: ActionType): boolean;
1388
+ /**
1389
+ * Get failed actions queue
1390
+ */
1391
+ getFailedQueue(): PendingAction[];
1392
+ /**
1393
+ * Manually retry failed actions
1394
+ */
1395
+ retryFailed(): Promise<void>;
1396
+ /**
1397
+ * Clear all failed actions
1398
+ */
1399
+ clearFailed(): void;
1400
+ /**
1401
+ * Reset manager state
1402
+ */
1403
+ reset(): void;
1404
+ /**
1405
+ * Destroy manager and cleanup
1406
+ */
1407
+ destroy(): void;
1408
+ /**
1409
+ * Add event listener
1410
+ */
1411
+ addEventListener(listener: OptimisticEventListener): () => void;
1412
+ /**
1413
+ * Remove event listener
1414
+ */
1415
+ removeEventListener(listener: OptimisticEventListener): void;
1416
+ /**
1417
+ * Perform an optimistic action
1418
+ */
1419
+ private performAction;
1420
+ /**
1421
+ * Create rollback data based on action type
1422
+ */
1423
+ private createRollbackData;
1424
+ /**
1425
+ * Apply optimistic update to feed
1426
+ */
1427
+ private applyOptimisticUpdate;
1428
+ /**
1429
+ * Apply rollback
1430
+ */
1431
+ private applyRollback;
1432
+ /**
1433
+ * Add pending action to store
1434
+ */
1435
+ private addPendingAction;
1436
+ /**
1437
+ * Mark action as success
1438
+ */
1439
+ private markActionSuccess;
1440
+ /**
1441
+ * Mark action as failed
1442
+ */
1443
+ private markActionFailed;
1444
+ /**
1445
+ * Retry a failed action
1446
+ */
1447
+ private retryAction;
1448
+ /**
1449
+ * Execute API call based on action type
1450
+ */
1451
+ private executeApiCall;
1452
+ /**
1453
+ * Schedule retry of failed actions
1454
+ */
1455
+ private scheduleRetry;
1456
+ /**
1457
+ * Cancel retry timer
1458
+ */
1459
+ private cancelRetryTimer;
1460
+ /**
1461
+ * Emit event to all listeners
1462
+ */
1463
+ private emitEvent;
1464
+ }
1465
+
1466
+ export { type ActionType, type AllocationResult, DEFAULT_FEED_CONFIG, DEFAULT_LIFECYCLE_CONFIG, DEFAULT_OPTIMISTIC_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_PREFETCH_CONFIG, DEFAULT_RESOURCE_CONFIG, type FeedConfig, type FeedError, FeedManager, type FeedState, type LifecycleConfig, type LifecycleEvent, type LifecycleEventListener, LifecycleManager, type LifecycleState, type NetworkType, type OptimisticConfig, type OptimisticEvent, type OptimisticEventListener, OptimisticManager, type OptimisticState, type PendingAction, type PlayerConfig, PlayerEngine, type PlayerError, type PlayerEvent, type PlayerEventListener, type PlayerState, PlayerStatus, type PrefetchConfig, type ResourceConfig, type ResourceEvent, type ResourceEventListener, ResourceGovernor, type ResourceState, type RestoreResult, calculatePrefetchIndices, calculateWindowIndices, canPause, canPlay, canSeek, computeAllocationChanges, isActiveState, isValidTransition, mapNetworkType };