@xhub-short/adapters 0.1.0-beta.11 → 0.1.0-beta.13

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
@@ -1,4 +1,4 @@
1
- import { IDataSource, VideoItem, FeedResponse, ILogger, LogLevel, LogEntry, IStorage, ISessionStorage, SessionSnapshot, IInteraction, Comment, ICommentAdapter, MockCommentAdapterConfig, CommentListResponse, ReplyListResponse, PostCommentPayload, CommentItem, PostReplyPayload, ReplyItem, EditCommentPayload, DeleteCommentPayload, ReportCommentPayload, IAnalytics, AnalyticsEvent, INetworkAdapter, NetworkType, NetworkQuality, IVideoLoader, VideoSource, PreloadConfig, PreloadResult, PreloadStatus, IPosterLoader, ReportReason, CommentTransformers } from '@xhub-short/contracts';
1
+ import { IDataSource, VideoItem, FeedResponse, IPlaylistDataSource, PlaylistData, PlaylistCollectionResponse, ILogger, LogLevel, LogEntry, IStorage, ISessionStorage, SessionSnapshot, IInteraction, Comment, ICommentAdapter, MockCommentAdapterConfig, CommentListResponse, ReplyListResponse, PostCommentPayload, CommentItem, PostReplyPayload, ReplyItem, EditCommentPayload, DeleteCommentPayload, ReportCommentPayload, IAnalytics, AnalyticsEvent, INetworkAdapter, NetworkType, NetworkQuality, IVideoLoader, VideoSource, PreloadConfig, PreloadResult, PreloadStatus, IPosterLoader, ReportReason, CommentTransformers } from '@xhub-short/contracts';
2
2
 
3
3
  /**
4
4
  * MockDataAdapter - Development/Testing Data Source
@@ -46,6 +46,19 @@ interface MockDataAdapterOptions {
46
46
  delay?: number;
47
47
  }
48
48
 
49
+ /**
50
+ * MockPlaylistAdapter - Development/Testing Playlist Data Source
51
+ */
52
+ declare class MockPlaylistAdapter implements IPlaylistDataSource {
53
+ private readonly delay;
54
+ constructor(options?: {
55
+ delay?: number;
56
+ });
57
+ fetchPlaylist(id: string): Promise<PlaylistData>;
58
+ fetchPlaylistCollection(cursor?: string): Promise<PlaylistCollectionResponse>;
59
+ private simulateDelay;
60
+ }
61
+
49
62
  /**
50
63
  * Configuration options for MockLoggerAdapter
51
64
  */
@@ -972,6 +985,22 @@ interface RESTEndpointMap {
972
985
  */
973
986
  detail: string;
974
987
  };
988
+ /**
989
+ * Playlist-related endpoints
990
+ */
991
+ playlist?: {
992
+ /**
993
+ * GET endpoint for playlist collection (listing)
994
+ * Example: '/playlists'
995
+ */
996
+ list: string;
997
+ /**
998
+ * GET endpoint for playlist detail
999
+ * :id will be replaced with playlist ID
1000
+ * Example: '/playlists/:id'
1001
+ */
1002
+ detail: string;
1003
+ };
975
1004
  /**
976
1005
  * Interaction endpoints
977
1006
  */
@@ -1132,6 +1161,16 @@ interface TransformConfig {
1132
1161
  nextCursor: string | null;
1133
1162
  hasMore: boolean;
1134
1163
  };
1164
+ /**
1165
+ * Transform playlist response from API
1166
+ * If not provided, uses default transform
1167
+ */
1168
+ playlist?: (apiResponse: unknown) => PlaylistData;
1169
+ /**
1170
+ * Transform playlist collection response from API
1171
+ * If not provided, uses default transform
1172
+ */
1173
+ playlistCollection?: (apiResponse: unknown) => PlaylistCollectionResponse;
1135
1174
  /**
1136
1175
  * Transform report reasons response from API
1137
1176
  * If not provided, uses default transform
@@ -1479,6 +1518,8 @@ interface PresetAdapters {
1479
1518
  dataSource: IDataSource;
1480
1519
  interaction: IInteraction;
1481
1520
  analytics: IAnalytics;
1521
+ /** Playlist adapter (optional) */
1522
+ playlist?: IPlaylistDataSource;
1482
1523
  /** Comment adapter (only if comment endpoints are configured) */
1483
1524
  comment?: ICommentAdapter;
1484
1525
  }
@@ -1946,6 +1987,8 @@ interface FullPresetAdapters {
1946
1987
  videoLoader: IVideoLoader;
1947
1988
  /** Poster preloader (Image) */
1948
1989
  posterLoader: IPosterLoader;
1990
+ /** Playlist adapter (REST API, optional) */
1991
+ playlist?: IPlaylistDataSource;
1949
1992
  /** Comment adapter (REST API, only if comment endpoints configured) */
1950
1993
  comment?: ICommentAdapter;
1951
1994
  /** Logger (passed through) */
@@ -2143,6 +2186,8 @@ interface ResolvedTransforms {
2143
2186
  nextCursor: string | null;
2144
2187
  hasMore: boolean;
2145
2188
  };
2189
+ playlist: (data: unknown) => PlaylistData;
2190
+ playlistCollection: (data: unknown) => PlaylistCollectionResponse;
2146
2191
  }
2147
2192
  declare function createTransforms(config?: TransformConfig, logger?: ILogger): ResolvedTransforms;
2148
2193
 
@@ -2675,4 +2720,4 @@ declare class RESTCommentAdapter implements ICommentAdapter {
2675
2720
  private unwrapResponse;
2676
2721
  }
2677
2722
 
2678
- export { type AuthConfig, type AuthError, type BatchAnalyticsConfig, type BatchAnalyticsContext, type BatchAnalyticsDeviceType, type BatchAnalyticsEventData, type BatchAnalyticsEventTransformer, type BatchAnalyticsEventType, type BatchAnalyticsNetworkType, type BatchAnalyticsRequestBody, type BatchAnalyticsRequestEvent, type BrowserAdaptersConfig, BrowserPosterLoader, BrowserVideoLoader, type BrowserVideoLoaderConfig, DEFAULT_REQUEST_CONFIG, DEFAULT_RETRY_CONFIG, type FieldMapConfig, type FullPresetAdapters, HttpClient, HttpError, LocalSessionStorageAdapter, LocalStorageAdapter, type LocalStorageConfig, MockAnalyticsAdapter, type MockAnalyticsAdapterOptions, MockCommentAdapter, MockDataAdapter, type MockDataAdapterOptions, MockInteractionAdapter, type MockInteractionAdapterOptions, MockLoggerAdapter, type MockLoggerAdapterOptions, MockNetworkAdapter, type MockNetworkAdapterOptions, MockPosterLoader, MockSessionStorageAdapter, type MockSessionStorageAdapterOptions, MockStorageAdapter, type MockStorageAdapterOptions, MockVideoLoader, type MockVideoLoaderOptions, type PresetAdapters, RESTAnalyticsAdapter, type RESTAnalyticsAdapterConfig, RESTCommentAdapter, type RESTCommentAdapterConfig, RESTDataAdapter, type RESTEndpointMap, RESTInteractionAdapter, type RESTPresetConfig, type RESTRequestConfig, RESTViewTrackingAdapter, type RESTViewTrackingAdapterConfig, type ResolvedTransforms, type RetryConfig, type TransformConfig, type ViewEventData, type ViewEventRequestBody, type ViewEventTransformer, type ViewTrackingConfig, WebNetworkAdapter, type WebNetworkConfig, createBrowserAdapters, createBrowserPosterLoader, createBrowserVideoLoader, createLocalStorageAdapter, createNoOpAnalyticsAdapter, createRESTAdapters, createSessionStorageAdapter, createTransforms, createWebNetworkAdapter, defaultFeedResponseTransform, defaultVideoItemTransform };
2723
+ export { type AuthConfig, type AuthError, type BatchAnalyticsConfig, type BatchAnalyticsContext, type BatchAnalyticsDeviceType, type BatchAnalyticsEventData, type BatchAnalyticsEventTransformer, type BatchAnalyticsEventType, type BatchAnalyticsNetworkType, type BatchAnalyticsRequestBody, type BatchAnalyticsRequestEvent, type BrowserAdaptersConfig, BrowserPosterLoader, BrowserVideoLoader, type BrowserVideoLoaderConfig, DEFAULT_REQUEST_CONFIG, DEFAULT_RETRY_CONFIG, type FieldMapConfig, type FullPresetAdapters, HttpClient, HttpError, LocalSessionStorageAdapter, LocalStorageAdapter, type LocalStorageConfig, MockAnalyticsAdapter, type MockAnalyticsAdapterOptions, MockCommentAdapter, MockDataAdapter, type MockDataAdapterOptions, MockInteractionAdapter, type MockInteractionAdapterOptions, MockLoggerAdapter, type MockLoggerAdapterOptions, MockNetworkAdapter, type MockNetworkAdapterOptions, MockPlaylistAdapter, MockPosterLoader, MockSessionStorageAdapter, type MockSessionStorageAdapterOptions, MockStorageAdapter, type MockStorageAdapterOptions, MockVideoLoader, type MockVideoLoaderOptions, type PresetAdapters, RESTAnalyticsAdapter, type RESTAnalyticsAdapterConfig, RESTCommentAdapter, type RESTCommentAdapterConfig, RESTDataAdapter, type RESTEndpointMap, RESTInteractionAdapter, type RESTPresetConfig, type RESTRequestConfig, RESTViewTrackingAdapter, type RESTViewTrackingAdapterConfig, type ResolvedTransforms, type RetryConfig, type TransformConfig, type ViewEventData, type ViewEventRequestBody, type ViewEventTransformer, type ViewTrackingConfig, WebNetworkAdapter, type WebNetworkConfig, createBrowserAdapters, createBrowserPosterLoader, createBrowserVideoLoader, createLocalStorageAdapter, createNoOpAnalyticsAdapter, createRESTAdapters, createSessionStorageAdapter, createTransforms, createWebNetworkAdapter, defaultFeedResponseTransform, defaultVideoItemTransform };
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ var MOCK_VIDEOS = [
4
4
  // HLS Videos (for testing hls.js integration)
5
5
  // ═══════════════════════════════════════════════════════════════════════════
6
6
  {
7
+ type: "video",
7
8
  id: "video-1",
8
9
  source: {
9
10
  url: "https://peertube.teknix.services/static/streaming-playlists/hls/dd8de71d-0b75-4677-a1a2-6f60e673bee4/465faffa-6d08-4f34-ae40-691cc904ce7b-master.m3u8",
@@ -30,6 +31,7 @@ var MOCK_VIDEOS = [
30
31
  hashtags: ["hls", "streaming", "test"]
31
32
  },
32
33
  {
34
+ type: "video",
33
35
  id: "video-2",
34
36
  source: {
35
37
  url: "https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8",
@@ -59,6 +61,7 @@ var MOCK_VIDEOS = [
59
61
  // MP4 Videos
60
62
  // ═══════════════════════════════════════════════════════════════════════════
61
63
  {
64
+ type: "video",
62
65
  id: "video-3",
63
66
  source: {
64
67
  url: "https://peertube.teknix.services/static/streaming-playlists/hls/ea58b245-b3bf-4958-b2f0-b31f8113d142/83f1bb91-e76a-4dcd-8034-2ec37cb70ead-master.m3u8",
@@ -85,6 +88,7 @@ var MOCK_VIDEOS = [
85
88
  hashtags: ["chrome", "blazes"]
86
89
  },
87
90
  {
91
+ type: "video",
88
92
  id: "video-4",
89
93
  source: {
90
94
  url: "https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4",
@@ -111,6 +115,7 @@ var MOCK_VIDEOS = [
111
115
  hashtags: ["chrome", "escapes"]
112
116
  },
113
117
  {
118
+ type: "video",
114
119
  id: "video-5",
115
120
  source: {
116
121
  url: "https://peertube.teknix.services/static/streaming-playlists/hls/003a41a3-25c1-419b-9548-7a1597adc85f/9cc93564-c9c9-4107-ae12-cd22d0db8046-master.m3u8",
@@ -137,6 +142,7 @@ var MOCK_VIDEOS = [
137
142
  hashtags: ["chrome", "fun"]
138
143
  },
139
144
  {
145
+ type: "video",
140
146
  id: "video-6",
141
147
  source: {
142
148
  url: "https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4",
@@ -163,6 +169,7 @@ var MOCK_VIDEOS = [
163
169
  hashtags: ["adventure", "joyride", "travel"]
164
170
  },
165
171
  {
172
+ type: "video",
166
173
  id: "video-7",
167
174
  source: {
168
175
  url: "https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerMeltdowns.mp4",
@@ -189,6 +196,7 @@ var MOCK_VIDEOS = [
189
196
  hashtags: ["satisfying", "icecream", "asmr"]
190
197
  },
191
198
  {
199
+ type: "video",
192
200
  id: "video-8",
193
201
  source: {
194
202
  url: "https://storage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4",
@@ -215,6 +223,7 @@ var MOCK_VIDEOS = [
215
223
  hashtags: ["fantasy", "animation", "sintel", "blender"]
216
224
  },
217
225
  {
226
+ type: "video",
218
227
  id: "video-9",
219
228
  source: {
220
229
  url: "https://storage.googleapis.com/gtv-videos-bucket/sample/SubaruOutbackOnStreetAndDirt.mp4",
@@ -241,6 +250,7 @@ var MOCK_VIDEOS = [
241
250
  hashtags: ["cars", "subaru", "offroad", "review"]
242
251
  },
243
252
  {
253
+ type: "video",
244
254
  id: "video-10",
245
255
  source: {
246
256
  url: "https://storage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4",
@@ -267,6 +277,7 @@ var MOCK_VIDEOS = [
267
277
  hashtags: ["scifi", "drama", "blender", "vfx"]
268
278
  },
269
279
  {
280
+ type: "video",
270
281
  id: "video-11",
271
282
  source: {
272
283
  url: "https://storage.googleapis.com/gtv-videos-bucket/sample/VolkswagenGTIReview.mp4",
@@ -293,6 +304,7 @@ var MOCK_VIDEOS = [
293
304
  hashtags: ["cars", "vw", "gti", "hothatch"]
294
305
  },
295
306
  {
307
+ type: "video",
296
308
  id: "video-12",
297
309
  source: {
298
310
  url: "https://storage.googleapis.com/gtv-videos-bucket/sample/WeAreGoingOnBullrun.mp4",
@@ -319,6 +331,7 @@ var MOCK_VIDEOS = [
319
331
  hashtags: ["rally", "racing", "bullrun", "supercars"]
320
332
  },
321
333
  {
334
+ type: "video",
322
335
  id: "video-13",
323
336
  source: {
324
337
  url: "https://storage.googleapis.com/gtv-videos-bucket/sample/WhatCarCanYouGetForAGrand.mp4",
@@ -345,6 +358,7 @@ var MOCK_VIDEOS = [
345
358
  hashtags: ["budget", "usedcars", "tips", "bargain"]
346
359
  },
347
360
  {
361
+ type: "video",
348
362
  id: "video-14",
349
363
  source: {
350
364
  url: "https://storage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
@@ -371,6 +385,7 @@ var MOCK_VIDEOS = [
371
385
  hashtags: ["bts", "animation", "3d", "making"]
372
386
  },
373
387
  {
388
+ type: "video",
374
389
  id: "video-15",
375
390
  source: {
376
391
  url: "https://storage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4",
@@ -397,6 +412,7 @@ var MOCK_VIDEOS = [
397
412
  hashtags: ["tutorial", "animation", "blender", "makingof"]
398
413
  },
399
414
  {
415
+ type: "video",
400
416
  id: "video-16",
401
417
  source: {
402
418
  url: "https://storage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4",
@@ -423,6 +439,7 @@ var MOCK_VIDEOS = [
423
439
  hashtags: ["characterdesign", "sintel", "tutorial", "art"]
424
440
  },
425
441
  {
442
+ type: "video",
426
443
  id: "video-17",
427
444
  source: {
428
445
  url: "https://storage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4",
@@ -449,6 +466,7 @@ var MOCK_VIDEOS = [
449
466
  hashtags: ["vfx", "breakdown", "compositing", "cgi"]
450
467
  },
451
468
  {
469
+ type: "video",
452
470
  id: "video-18",
453
471
  source: {
454
472
  url: "https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4",
@@ -475,6 +493,7 @@ var MOCK_VIDEOS = [
475
493
  hashtags: ["pov", "roadtrip", "travel", "wanderlust"]
476
494
  },
477
495
  {
496
+ type: "video",
478
497
  id: "video-19",
479
498
  source: {
480
499
  url: "https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4",
@@ -501,6 +520,7 @@ var MOCK_VIDEOS = [
501
520
  hashtags: ["satisfying", "fire", "asmr", "relaxing"]
502
521
  },
503
522
  {
523
+ type: "video",
504
524
  id: "video-20",
505
525
  source: {
506
526
  url: "https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerMeltdowns.mp4",
@@ -576,6 +596,86 @@ var MockDataAdapter = class {
576
596
  }
577
597
  };
578
598
 
599
+ // src/playlist/MockPlaylistAdapter.ts
600
+ var MOCK_PLAYLISTS = [
601
+ {
602
+ id: "p1",
603
+ title: "Workout Jams",
604
+ description: "High energy tracks for your workout",
605
+ cover: "https://images.unsplash.com/photo-1534438327276-14e5300c3a48?w=400&h=600&fit=crop",
606
+ totalItems: 4,
607
+ items: []
608
+ // Populated on fetchPlaylist
609
+ },
610
+ {
611
+ id: "p2",
612
+ title: "Chill Vibes",
613
+ description: "Relax and unwind with these lo-fi beats",
614
+ cover: "https://images.unsplash.com/photo-1516280440614-37939bbacd81?w=400&h=600&fit=crop",
615
+ totalItems: 4,
616
+ items: []
617
+ },
618
+ {
619
+ id: "p3",
620
+ title: "Travel Diaries",
621
+ description: "Explore the world through music and video",
622
+ cover: "https://images.unsplash.com/photo-1476514525535-07fb3b4ae5f1?w=400&h=600&fit=crop",
623
+ totalItems: 4,
624
+ items: []
625
+ },
626
+ {
627
+ id: "p4",
628
+ title: "Cooking with Chef loct",
629
+ description: "Delicious recipes and kitchen tips",
630
+ cover: "https://images.unsplash.com/photo-1556910103-1c02745aae4d?w=400&h=600&fit=crop",
631
+ totalItems: 4,
632
+ items: []
633
+ }
634
+ ];
635
+ var MockPlaylistAdapter = class {
636
+ constructor(options = {}) {
637
+ this.delay = options.delay ?? 300;
638
+ }
639
+ async fetchPlaylist(id) {
640
+ await this.simulateDelay();
641
+ const playlist = MOCK_PLAYLISTS.find((p) => p.id === id);
642
+ if (!playlist) throw new Error(`Playlist ${id} not found`);
643
+ let items = [];
644
+ if (id === "p1") items = MOCK_VIDEOS.slice(0, 4);
645
+ else if (id === "p2") items = MOCK_VIDEOS.slice(4, 8);
646
+ else if (id === "p3") items = MOCK_VIDEOS.slice(8, 12);
647
+ else if (id === "p4") items = MOCK_VIDEOS.slice(12, 16);
648
+ return { ...playlist, items };
649
+ }
650
+ async fetchPlaylistCollection(cursor) {
651
+ await this.simulateDelay();
652
+ const offset = cursor ? Number.parseInt(cursor, 10) : 0;
653
+ const limit = 4;
654
+ const items = MOCK_PLAYLISTS.slice(offset, offset + limit);
655
+ const nextOffset = offset + limit;
656
+ const hasMore = nextOffset < MOCK_PLAYLISTS.length;
657
+ const playlists = items.map((p) => ({
658
+ id: p.id,
659
+ title: p.title,
660
+ description: p.description,
661
+ cover: p.cover,
662
+ totalItems: p.totalItems,
663
+ author: { id: "a1", name: "System" },
664
+ updatedAt: "2 days ago"
665
+ }));
666
+ return {
667
+ playlists,
668
+ nextCursor: hasMore ? String(nextOffset) : null,
669
+ hasMore
670
+ };
671
+ }
672
+ async simulateDelay() {
673
+ if (this.delay > 0) {
674
+ await new Promise((resolve) => setTimeout(resolve, this.delay));
675
+ }
676
+ }
677
+ };
678
+
579
679
  // src/logger/mock.ts
580
680
  var LOG_LEVEL_PRIORITY = {
581
681
  debug: 0,
@@ -2610,6 +2710,7 @@ var RESTDataAdapter = class {
2610
2710
  createFallbackVideoItem(data) {
2611
2711
  const obj = data ?? {};
2612
2712
  return {
2713
+ type: "video",
2613
2714
  id: String(obj.id ?? obj.video_id ?? `fallback-${Date.now()}`),
2614
2715
  source: {
2615
2716
  url: String(obj.video_url ?? obj.url ?? ""),
@@ -2900,6 +3001,69 @@ var RESTInteractionAdapter = class {
2900
3001
  }
2901
3002
  };
2902
3003
 
3004
+ // src/preset/adapters/RESTPlaylistAdapter.ts
3005
+ var RESTPlaylistAdapter = class {
3006
+ constructor(config) {
3007
+ this.httpClient = config.httpClient;
3008
+ this.endpoint = config.endpoint;
3009
+ this.collectionEndpoint = config.collectionEndpoint;
3010
+ this.transforms = config.transforms;
3011
+ this.logger = config.logger;
3012
+ }
3013
+ /**
3014
+ * Fetch a complete playlist by ID
3015
+ *
3016
+ * @param id - The playlist ID
3017
+ * @returns Promise resolving to PlaylistData
3018
+ */
3019
+ async fetchPlaylist(id) {
3020
+ try {
3021
+ this.logger?.info(`[RESTPlaylistAdapter] Fetching playlist: ${id}`);
3022
+ const path = this.endpoint.replace(":id", id);
3023
+ const response = await this.httpClient.request({
3024
+ method: "GET",
3025
+ path
3026
+ });
3027
+ const playlist = this.transforms.playlist(response);
3028
+ if (playlist.items.length === 0) {
3029
+ this.logger?.warn(`[RESTPlaylistAdapter] Playlist ${id} is empty`);
3030
+ }
3031
+ return playlist;
3032
+ } catch (error) {
3033
+ this.logger?.error(`[RESTPlaylistAdapter] Failed to fetch playlist: ${id}`, error);
3034
+ throw error;
3035
+ }
3036
+ }
3037
+ /**
3038
+ * Fetch a collection of playlists
3039
+ *
3040
+ * @param cursor - Pagination cursor
3041
+ * @returns Promise resolving to PlaylistCollectionResponse
3042
+ */
3043
+ async fetchPlaylistCollection(cursor) {
3044
+ try {
3045
+ if (!this.collectionEndpoint || !this.transforms.collection) {
3046
+ throw new Error(
3047
+ "[RESTPlaylistAdapter] collectionEndpoint or collection transform not configured"
3048
+ );
3049
+ }
3050
+ this.logger?.info(`[RESTPlaylistAdapter] Fetching playlist collection (cursor: ${cursor})`);
3051
+ const path = cursor ? `${this.collectionEndpoint}?cursor=${cursor}` : this.collectionEndpoint;
3052
+ const response = await this.httpClient.request({
3053
+ method: "GET",
3054
+ path
3055
+ });
3056
+ return this.transforms.collection(response);
3057
+ } catch (error) {
3058
+ this.logger?.error(
3059
+ "[RESTPlaylistAdapter] Failed to fetch playlist collection",
3060
+ error
3061
+ );
3062
+ throw error;
3063
+ }
3064
+ }
3065
+ };
3066
+
2903
3067
  // src/preset/adapters/RESTViewTrackingAdapter.ts
2904
3068
  var DEFAULT_VIEW_EVENT_VALUE = "seek";
2905
3069
  var DEFAULT_HEARTBEAT_INTERVAL = 1e4;
@@ -3370,6 +3534,83 @@ var HttpClient = class {
3370
3534
  }
3371
3535
  };
3372
3536
 
3537
+ // src/preset/transforms/playlist.ts
3538
+ function defaultPlaylistTransform(apiResponse, videoItemTransform, logger) {
3539
+ if (!apiResponse || typeof apiResponse !== "object") {
3540
+ logger?.error("[PlaylistTransform] Invalid API response", void 0, {
3541
+ apiResponse: String(apiResponse)
3542
+ });
3543
+ return createEmptyPlaylist();
3544
+ }
3545
+ const obj = apiResponse;
3546
+ const data = obj.data ?? obj.result ?? obj.playlist ?? obj;
3547
+ const rawItems = data.items ?? data.reels ?? data.videos ?? data.list ?? [];
3548
+ if (!Array.isArray(rawItems)) {
3549
+ logger?.warn("[PlaylistTransform] Items is not an array", { data });
3550
+ return createEmptyPlaylist(data);
3551
+ }
3552
+ const items = rawItems.map((item) => {
3553
+ try {
3554
+ return videoItemTransform(item);
3555
+ } catch (error) {
3556
+ logger?.error("[PlaylistTransform] Failed to transform item", error);
3557
+ return null;
3558
+ }
3559
+ }).filter((item) => item !== null);
3560
+ return {
3561
+ id: String(data.id ?? data.playlist_id ?? ""),
3562
+ title: String(data.title ?? data.name ?? "Untitled Playlist"),
3563
+ description: String(data.description ?? ""),
3564
+ cover: String(data.cover ?? data.cover_url ?? data.thumbnail ?? ""),
3565
+ items,
3566
+ totalItems: Number(data.total_items ?? data.total ?? items.length)
3567
+ };
3568
+ }
3569
+ function defaultPlaylistSummaryTransform(data) {
3570
+ const obj = data;
3571
+ const authorObj = obj.author ?? obj.user ?? obj.creator ?? {};
3572
+ return {
3573
+ id: String(obj.id ?? obj.playlist_id ?? ""),
3574
+ title: String(obj.title ?? obj.name ?? "Untitled Playlist"),
3575
+ description: String(obj.description ?? ""),
3576
+ cover: String(obj.cover ?? obj.cover_url ?? obj.thumbnail ?? ""),
3577
+ totalItems: Number(obj.total_items ?? obj.items_count ?? 0),
3578
+ author: {
3579
+ id: String(authorObj.id ?? authorObj.user_id ?? ""),
3580
+ name: String(authorObj.name ?? authorObj.display_name ?? authorObj.username ?? "Unknown")
3581
+ },
3582
+ updatedAt: String(obj.updated_at ?? obj.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString())
3583
+ };
3584
+ }
3585
+ function defaultPlaylistCollectionTransform(apiResponse, logger) {
3586
+ if (!apiResponse || typeof apiResponse !== "object") {
3587
+ return { playlists: [], nextCursor: null, hasMore: false };
3588
+ }
3589
+ const obj = apiResponse;
3590
+ const data = obj.data ?? obj.result ?? obj;
3591
+ const rawPlaylists = data.playlists ?? data.items ?? data.list ?? [];
3592
+ if (!Array.isArray(rawPlaylists)) {
3593
+ logger?.warn("[PlaylistTransform] Playlists is not an array", { data });
3594
+ return { playlists: [], nextCursor: null, hasMore: false };
3595
+ }
3596
+ const playlists = rawPlaylists.map(defaultPlaylistSummaryTransform);
3597
+ const nextCursor = String(data.next_cursor ?? data.cursor ?? data.nextCursor ?? null);
3598
+ const hasMore = Boolean(data.has_more ?? data.hasMore ?? (nextCursor && nextCursor !== "null"));
3599
+ return {
3600
+ playlists,
3601
+ nextCursor: nextCursor === "null" ? null : nextCursor,
3602
+ hasMore
3603
+ };
3604
+ }
3605
+ function createEmptyPlaylist(data) {
3606
+ return {
3607
+ id: String(data?.id ?? ""),
3608
+ title: String(data?.title ?? "Empty Playlist"),
3609
+ items: [],
3610
+ totalItems: 0
3611
+ };
3612
+ }
3613
+
3373
3614
  // src/preset/transforms/defaults.ts
3374
3615
  function getNestedValue(obj, path) {
3375
3616
  if (!obj || typeof obj !== "object") return void 0;
@@ -3477,6 +3718,7 @@ function defaultVideoItemTransform(apiResponse, fieldMap, logger) {
3477
3718
  const author = defaultAuthorTransform(obj);
3478
3719
  const stats = defaultStatsTransform(obj);
3479
3720
  const videoItem = {
3721
+ type: "video",
3480
3722
  id,
3481
3723
  source,
3482
3724
  poster: toSafeString(
@@ -3558,7 +3800,13 @@ function defaultFeedResponseTransform(apiResponse, fieldMap, logger) {
3558
3800
  function createTransforms(config, logger) {
3559
3801
  return {
3560
3802
  videoItem: config?.videoItem ? config.videoItem : (data) => defaultVideoItemTransform(data, config?.fieldMap?.video, logger),
3561
- feedResponse: config?.feedResponse ? config.feedResponse : (data) => defaultFeedResponseTransform(data, config?.fieldMap?.feed, logger)
3803
+ feedResponse: config?.feedResponse ? config.feedResponse : (data) => defaultFeedResponseTransform(data, config?.fieldMap?.feed, logger),
3804
+ playlist: config?.playlist ? config.playlist : (data) => defaultPlaylistTransform(
3805
+ data,
3806
+ (item) => config?.videoItem ? config.videoItem(item) : defaultVideoItemTransform(item, config?.fieldMap?.video, logger),
3807
+ logger
3808
+ ),
3809
+ playlistCollection: config?.playlistCollection ? config.playlistCollection : (data) => defaultPlaylistCollectionTransform(data, logger)
3562
3810
  };
3563
3811
  }
3564
3812
 
@@ -3637,11 +3885,22 @@ function createRESTAdapters(config) {
3637
3885
  endpoints: endpoints.comment,
3638
3886
  logger
3639
3887
  }) : void 0;
3888
+ const playlist = endpoints.playlist ? new RESTPlaylistAdapter({
3889
+ httpClient,
3890
+ endpoint: endpoints.playlist.detail,
3891
+ collectionEndpoint: endpoints.playlist.list,
3892
+ transforms: {
3893
+ playlist: resolvedTransforms.playlist,
3894
+ collection: (data) => resolvedTransforms.playlistCollection(data)
3895
+ },
3896
+ logger
3897
+ }) : void 0;
3640
3898
  return {
3641
3899
  dataSource,
3642
3900
  interaction,
3643
3901
  analytics,
3644
- comment
3902
+ comment,
3903
+ playlist
3645
3904
  };
3646
3905
  }
3647
3906
 
@@ -4276,8 +4535,9 @@ function createBrowserAdapters(config) {
4276
4535
  videoLoader,
4277
4536
  posterLoader,
4278
4537
  comment,
4538
+ playlist: restAdapters.playlist,
4279
4539
  logger: config.logger
4280
4540
  };
4281
4541
  }
4282
4542
 
4283
- export { BrowserPosterLoader, BrowserVideoLoader, DEFAULT_REQUEST_CONFIG, DEFAULT_RETRY_CONFIG, HttpClient, HttpError, LocalSessionStorageAdapter, LocalStorageAdapter, MockAnalyticsAdapter, MockCommentAdapter, MockDataAdapter, MockInteractionAdapter, MockLoggerAdapter, MockNetworkAdapter, MockPosterLoader, MockSessionStorageAdapter, MockStorageAdapter, MockVideoLoader, RESTAnalyticsAdapter, RESTCommentAdapter, RESTDataAdapter, RESTInteractionAdapter, RESTViewTrackingAdapter, WebNetworkAdapter, createBrowserAdapters, createBrowserPosterLoader, createBrowserVideoLoader, createLocalStorageAdapter, createNoOpAnalyticsAdapter, createRESTAdapters, createSessionStorageAdapter, createTransforms, createWebNetworkAdapter, defaultFeedResponseTransform, defaultVideoItemTransform };
4543
+ export { BrowserPosterLoader, BrowserVideoLoader, DEFAULT_REQUEST_CONFIG, DEFAULT_RETRY_CONFIG, HttpClient, HttpError, LocalSessionStorageAdapter, LocalStorageAdapter, MockAnalyticsAdapter, MockCommentAdapter, MockDataAdapter, MockInteractionAdapter, MockLoggerAdapter, MockNetworkAdapter, MockPlaylistAdapter, MockPosterLoader, MockSessionStorageAdapter, MockStorageAdapter, MockVideoLoader, RESTAnalyticsAdapter, RESTCommentAdapter, RESTDataAdapter, RESTInteractionAdapter, RESTViewTrackingAdapter, WebNetworkAdapter, createBrowserAdapters, createBrowserPosterLoader, createBrowserVideoLoader, createLocalStorageAdapter, createNoOpAnalyticsAdapter, createRESTAdapters, createSessionStorageAdapter, createTransforms, createWebNetworkAdapter, defaultFeedResponseTransform, defaultVideoItemTransform };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xhub-short/adapters",
3
3
  "sideEffects": false,
4
- "version": "0.1.0-beta.11",
4
+ "version": "0.1.0-beta.13",
5
5
  "type": "module",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -20,7 +20,7 @@
20
20
  "dist"
21
21
  ],
22
22
  "dependencies": {
23
- "@xhub-short/contracts": "0.1.0-beta.11"
23
+ "@xhub-short/contracts": "0.1.0-beta.13"
24
24
  },
25
25
  "optionalDependencies": {
26
26
  "hls.js": "^1.5.0"
@@ -29,8 +29,8 @@
29
29
  "tsup": "^8.3.0",
30
30
  "typescript": "^5.7.0",
31
31
  "vitest": "^2.1.0",
32
- "@xhub-short/tsconfig": "0.0.0",
33
- "@xhub-short/vitest-config": "0.1.0-beta.10"
32
+ "@xhub-short/tsconfig": "0.0.1-beta.1",
33
+ "@xhub-short/vitest-config": "0.1.0-beta.12"
34
34
  },
35
35
  "scripts": {
36
36
  "build": "tsup",