@xhub-short/core 1.0.0-beta.24 → 1.0.0-beta.26

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.
package/dist/index.d.ts CHANGED
@@ -2023,6 +2023,7 @@ interface PlaylistCollectionState {
2023
2023
  declare class PlaylistManager {
2024
2024
  private readonly dataSource?;
2025
2025
  private readonly governor?;
2026
+ private readonly storage?;
2026
2027
  /** Zustand vanilla store */
2027
2028
  readonly store: StoreApi<PlaylistState>;
2028
2029
  /** Resolved configuration */
@@ -2034,10 +2035,17 @@ declare class PlaylistManager {
2034
2035
  private fullMetadataItems;
2035
2036
  /** Unsubscribe from governor events */
2036
2037
  private governorUnsubscribe?;
2037
- constructor(dataSource?: IPlaylistDataSource | undefined, config?: PlaylistConfig, governor?: ResourceGovernor | undefined);
2038
+ constructor(dataSource?: IPlaylistDataSource | undefined, config?: PlaylistConfig, governor?: ResourceGovernor | undefined, storage?: ISessionStorage | undefined);
2038
2039
  /**
2039
2040
  * Load a playlist by ID
2040
2041
  */
2042
+ /**
2043
+ * Generates a cache key for a specific playlist
2044
+ */
2045
+ private getCacheKey;
2046
+ /**
2047
+ * Load playlist data, using cache for an immediate render before network finishes.
2048
+ */
2041
2049
  loadPlaylist(id: string): Promise<void>;
2042
2050
  /**
2043
2051
  * Set playlist data directly
@@ -2059,6 +2067,14 @@ declare class PlaylistManager {
2059
2067
  * Reset state
2060
2068
  */
2061
2069
  reset(): void;
2070
+ /**
2071
+ * Get the full (un-minified) list of items in the current playlist.
2072
+ *
2073
+ * Unlike `store.items` (which applies the sliding window and may contain
2074
+ * minified items), this always returns the complete metadata for every item.
2075
+ * Used by PlaylistFeedAdapter to ensure FeedManager receives playable data.
2076
+ */
2077
+ getFullItems(): ContentItem[];
2062
2078
  /**
2063
2079
  * Destroy the manager
2064
2080
  */
@@ -2085,9 +2101,20 @@ declare class PlaylistManager {
2085
2101
  */
2086
2102
  declare class PlaylistCollectionManager {
2087
2103
  private readonly dataSource?;
2104
+ private readonly storage?;
2088
2105
  /** Zustand vanilla store */
2089
2106
  readonly store: StoreApi<PlaylistCollectionState>;
2090
- constructor(dataSource?: IPlaylistDataSource | undefined);
2107
+ /** Cache key for playlist collection */
2108
+ private static readonly CACHE_KEY;
2109
+ constructor(dataSource?: IPlaylistDataSource | undefined, storage?: ISessionStorage | undefined);
2110
+ /**
2111
+ * Hydrate collection from cache
2112
+ */
2113
+ hydrateFromCache(): Promise<void>;
2114
+ /**
2115
+ * Update cache with current state
2116
+ */
2117
+ private updateCache;
2091
2118
  /**
2092
2119
  * Load more playlists (pagination)
2093
2120
  */
package/dist/index.js CHANGED
@@ -2042,11 +2042,21 @@ function calculateWindowIndices(focusedIndex, totalItems, maxAllocations = 3) {
2042
2042
  const indices = [];
2043
2043
  const clampedFocus = Math.max(0, Math.min(focusedIndex, totalItems - 1));
2044
2044
  indices.push(clampedFocus);
2045
- if (clampedFocus > 0 && indices.length < maxAllocations) {
2046
- indices.push(clampedFocus - 1);
2047
- }
2048
- if (clampedFocus < totalItems - 1 && indices.length < maxAllocations) {
2049
- indices.push(clampedFocus + 1);
2045
+ let offset = 1;
2046
+ while (indices.length < maxAllocations) {
2047
+ let added = false;
2048
+ if (clampedFocus - offset >= 0 && indices.length < maxAllocations) {
2049
+ indices.push(clampedFocus - offset);
2050
+ added = true;
2051
+ }
2052
+ if (clampedFocus + offset < totalItems && indices.length < maxAllocations) {
2053
+ indices.push(clampedFocus + offset);
2054
+ added = true;
2055
+ }
2056
+ if (!added) {
2057
+ break;
2058
+ }
2059
+ offset++;
2050
2060
  }
2051
2061
  return indices.sort((a, b) => a - b);
2052
2062
  }
@@ -2101,18 +2111,18 @@ function mapNetworkType(type) {
2101
2111
  }
2102
2112
  var DEFAULT_SCROLL_THRASHING_CONFIG = {
2103
2113
  windowMs: 1e3,
2104
- maxChangesInWindow: 6,
2105
- cooldownMs: 300
2114
+ maxChangesInWindow: 3,
2115
+ cooldownMs: 500
2106
2116
  };
2107
2117
  var DEFAULT_PREFETCH_CONFIG = {
2108
2118
  wifi: {
2109
- posterCount: 15,
2110
- videoSegmentCount: 10,
2119
+ posterCount: 5,
2120
+ videoSegmentCount: 2,
2111
2121
  prefetchVideo: true
2112
2122
  },
2113
2123
  cellular: {
2114
- posterCount: 10,
2115
- videoSegmentCount: 5,
2124
+ posterCount: 3,
2125
+ videoSegmentCount: 1,
2116
2126
  prefetchVideo: true
2117
2127
  },
2118
2128
  offline: {
@@ -2567,7 +2577,7 @@ var ResourceGovernor = class {
2567
2577
  newPreloadingIndices.add(index);
2568
2578
  }
2569
2579
  this.store.setState({ preloadingIndices: newPreloadingIndices });
2570
- const maxParallel = 3;
2580
+ const maxParallel = 2;
2571
2581
  const executeInBatches = async () => {
2572
2582
  for (let i = 0; i < indicesToPreload.length; i += maxParallel) {
2573
2583
  const batch = indicesToPreload.slice(i, i + maxParallel);
@@ -3678,9 +3688,10 @@ var createInitialState6 = () => ({
3678
3688
  error: null
3679
3689
  });
3680
3690
  var PlaylistManager = class {
3681
- constructor(dataSource, config = {}, governor) {
3691
+ constructor(dataSource, config = {}, governor, storage) {
3682
3692
  this.dataSource = dataSource;
3683
3693
  this.governor = governor;
3694
+ this.storage = storage;
3684
3695
  /**
3685
3696
  * Internal cache of full metadata items.
3686
3697
  * Items in store.items may be minified to save memory.
@@ -3704,20 +3715,49 @@ var PlaylistManager = class {
3704
3715
  /**
3705
3716
  * Load a playlist by ID
3706
3717
  */
3718
+ /**
3719
+ * Generates a cache key for a specific playlist
3720
+ */
3721
+ getCacheKey(id) {
3722
+ return `sv-playlist-data-${id}`;
3723
+ }
3724
+ /**
3725
+ * Load playlist data, using cache for an immediate render before network finishes.
3726
+ */
3707
3727
  async loadPlaylist(id) {
3708
3728
  if (!this.dataSource) {
3709
3729
  this.store.setState({ error: new Error("No playlist data source provided") });
3710
3730
  return;
3711
3731
  }
3732
+ const cacheKey = this.getCacheKey(id);
3733
+ try {
3734
+ if (this.storage) {
3735
+ const cachedPlaylist = await this.storage.get(cacheKey);
3736
+ if (cachedPlaylist) {
3737
+ this.setPlaylist(cachedPlaylist);
3738
+ }
3739
+ }
3740
+ } catch {
3741
+ }
3712
3742
  this.store.setState({ loading: true, error: null });
3713
3743
  try {
3714
3744
  const playlist = await this.dataSource.fetchPlaylist(id);
3715
3745
  this.setPlaylist(playlist);
3746
+ if (this.storage) {
3747
+ const itemsToCache = playlist.items.slice(0, Math.max(this.config.metadataWindowSize, 5));
3748
+ const cachePayload = { ...playlist, items: itemsToCache };
3749
+ this.storage.set(cacheKey, cachePayload).catch(() => {
3750
+ });
3751
+ }
3716
3752
  } catch (error) {
3717
- this.store.setState({
3718
- loading: false,
3719
- error: error instanceof Error ? error : new Error("Failed to load playlist")
3720
- });
3753
+ if (!this.store.getState().playlist) {
3754
+ this.store.setState({
3755
+ loading: false,
3756
+ error: error instanceof Error ? error : new Error("Failed to load playlist")
3757
+ });
3758
+ } else {
3759
+ this.store.setState({ loading: false });
3760
+ }
3721
3761
  }
3722
3762
  }
3723
3763
  /**
@@ -3725,10 +3765,15 @@ var PlaylistManager = class {
3725
3765
  */
3726
3766
  setPlaylist(playlist) {
3727
3767
  this.fullMetadataItems = [...playlist.items];
3728
- this.updateMetadataWindow(0);
3768
+ const { metadataWindowSize } = this.config;
3769
+ const end = Math.min(this.fullMetadataItems.length, metadataWindowSize);
3770
+ const windowedItems = this.fullMetadataItems.map(
3771
+ (item, index) => index < end ? item : this.minifyItem(item)
3772
+ );
3729
3773
  this.store.setState({
3730
3774
  playlist,
3731
3775
  currentIndex: 0,
3776
+ items: windowedItems,
3732
3777
  loading: false,
3733
3778
  error: null
3734
3779
  });
@@ -3767,6 +3812,16 @@ var PlaylistManager = class {
3767
3812
  this.fullMetadataItems = [];
3768
3813
  this.store.setState(createInitialState6());
3769
3814
  }
3815
+ /**
3816
+ * Get the full (un-minified) list of items in the current playlist.
3817
+ *
3818
+ * Unlike `store.items` (which applies the sliding window and may contain
3819
+ * minified items), this always returns the complete metadata for every item.
3820
+ * Used by PlaylistFeedAdapter to ensure FeedManager receives playable data.
3821
+ */
3822
+ getFullItems() {
3823
+ return [...this.fullMetadataItems];
3824
+ }
3770
3825
  /**
3771
3826
  * Destroy the manager
3772
3827
  */
@@ -3829,11 +3884,38 @@ var createInitialState7 = () => ({
3829
3884
  hasMore: true,
3830
3885
  error: null
3831
3886
  });
3832
- var PlaylistCollectionManager = class {
3833
- constructor(dataSource) {
3887
+ var _PlaylistCollectionManager = class _PlaylistCollectionManager {
3888
+ constructor(dataSource, storage) {
3834
3889
  this.dataSource = dataSource;
3890
+ this.storage = storage;
3835
3891
  this.store = createStore(createInitialState7);
3836
3892
  }
3893
+ /**
3894
+ * Hydrate collection from cache
3895
+ */
3896
+ async hydrateFromCache() {
3897
+ if (!this.storage) return;
3898
+ try {
3899
+ const cached = await this.storage.get(_PlaylistCollectionManager.CACHE_KEY);
3900
+ if (Array.isArray(cached?.playlists)) {
3901
+ this.store.setState({
3902
+ playlists: cached.playlists,
3903
+ cursor: cached.cursor,
3904
+ hasMore: cached.hasMore
3905
+ });
3906
+ }
3907
+ } catch {
3908
+ }
3909
+ }
3910
+ /**
3911
+ * Update cache with current state
3912
+ */
3913
+ updateCache() {
3914
+ if (!this.storage) return;
3915
+ const { playlists, cursor, hasMore } = this.store.getState();
3916
+ this.storage.set(_PlaylistCollectionManager.CACHE_KEY, { playlists, cursor, hasMore }).catch(() => {
3917
+ });
3918
+ }
3837
3919
  /**
3838
3920
  * Load more playlists (pagination)
3839
3921
  */
@@ -3849,6 +3931,7 @@ var PlaylistCollectionManager = class {
3849
3931
  hasMore: response.hasMore,
3850
3932
  loading: false
3851
3933
  }));
3934
+ this.updateCache();
3852
3935
  } catch (error) {
3853
3936
  this.store.setState({
3854
3937
  loading: false,
@@ -3871,6 +3954,7 @@ var PlaylistCollectionManager = class {
3871
3954
  loading: false,
3872
3955
  error: null
3873
3956
  });
3957
+ this.updateCache();
3874
3958
  } catch (error) {
3875
3959
  this.store.setState({
3876
3960
  loading: false,
@@ -3885,5 +3969,8 @@ var PlaylistCollectionManager = class {
3885
3969
  this.store.setState(createInitialState7());
3886
3970
  }
3887
3971
  };
3972
+ /** Cache key for playlist collection */
3973
+ _PlaylistCollectionManager.CACHE_KEY = "sv-playlist-collection";
3974
+ var PlaylistCollectionManager = _PlaylistCollectionManager;
3888
3975
 
3889
3976
  export { CommentManager, DEFAULT_COMMENT_MANAGER_CONFIG, DEFAULT_FEED_CONFIG, DEFAULT_LIFECYCLE_CONFIG, DEFAULT_OPTIMISTIC_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_PREFETCH_CACHE_CONFIG, DEFAULT_PREFETCH_CONFIG, DEFAULT_RESOURCE_CONFIG, FeedManager, LifecycleManager, OptimisticManager, PlayerEngine, PlayerStatus, PlaylistCollectionManager, PlaylistManager, ResourceGovernor, calculatePrefetchIndices, calculateWindowIndices, canPause, canPlay, canSeek, computeAllocationChanges, createInitialCommentState, createInitialVideoCommentState, isActiveState, isValidTransition, mapNetworkType };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xhub-short/core",
3
3
  "sideEffects": false,
4
- "version": "1.0.0-beta.24",
4
+ "version": "1.0.0-beta.26",
5
5
  "type": "module",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -21,14 +21,14 @@
21
21
  ],
22
22
  "dependencies": {
23
23
  "zustand": "^5.0.0",
24
- "@xhub-short/contracts": "1.0.0-beta.24"
24
+ "@xhub-short/contracts": "1.0.0-beta.26"
25
25
  },
26
26
  "devDependencies": {
27
27
  "tsup": "^8.3.0",
28
28
  "typescript": "^5.7.0",
29
29
  "vitest": "^2.1.0",
30
- "@xhub-short/tsconfig": "0.1.0-beta.3",
31
- "@xhub-short/vitest-config": "0.1.0-beta.14"
30
+ "@xhub-short/tsconfig": "0.0.1-beta.2",
31
+ "@xhub-short/vitest-config": "0.1.0-beta.13"
32
32
  },
33
33
  "scripts": {
34
34
  "build": "tsup",