@xhub-short/adapters 0.1.0-beta.10 → 0.1.0-beta.12

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 +146 -127
  2. package/dist/index.js +374 -174
  3. package/package.json +4 -4
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, 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
  */
@@ -446,11 +459,11 @@ declare class MockInteractionAdapter implements IInteraction {
446
459
  /**
447
460
  * Report a video
448
461
  *
449
- * @param _videoId - ID of the video to report
450
- * @param _reason - Report reason code
451
- * @param _description - Optional additional description
462
+ * @param videoId - ID of the video to report
463
+ * @param reason - Report reason code
464
+ * @param description - Optional additional description
452
465
  */
453
- report(_videoId: string, _reason: string, _description?: string): Promise<void>;
466
+ report(videoId: string, reason: string, description?: string): Promise<void>;
454
467
  /**
455
468
  * Check if video is liked (for testing)
456
469
  *
@@ -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
  */
@@ -990,6 +1019,12 @@ interface RESTEndpointMap {
990
1019
  deleteComment: string;
991
1020
  /** POST /videos/:id/share (optional) */
992
1021
  share?: string;
1022
+ /** POST /content/:id/report (optional) */
1023
+ report?: string;
1024
+ /** GET /report-reasons (optional) */
1025
+ reportReasons?: string;
1026
+ /** POST /content/:id/not-interested (optional) */
1027
+ notInterested?: string;
993
1028
  };
994
1029
  /**
995
1030
  * Analytics endpoints (optional) - BATCH mode
@@ -1100,6 +1135,14 @@ interface RetryConfig {
1100
1135
  */
1101
1136
  exponentialBackoff?: boolean;
1102
1137
  }
1138
+ /**
1139
+ * Report reason from API
1140
+ */
1141
+ interface ReportReasonItem {
1142
+ id: string;
1143
+ label: string;
1144
+ description?: string;
1145
+ }
1103
1146
  /**
1104
1147
  * Response transform configuration
1105
1148
  */
@@ -1118,6 +1161,51 @@ interface TransformConfig {
1118
1161
  nextCursor: string | null;
1119
1162
  hasMore: boolean;
1120
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;
1174
+ /**
1175
+ * Transform report reasons response from API
1176
+ * If not provided, uses default transform
1177
+ *
1178
+ * @example
1179
+ * ```ts
1180
+ * reportReasons: (response) => {
1181
+ * const data = response.data?.reasons || [];
1182
+ * return data.map(item => ({
1183
+ * id: item.id,
1184
+ * label: item.title,
1185
+ * description: item.description,
1186
+ * }));
1187
+ * }
1188
+ * ```
1189
+ */
1190
+ reportReasons?: (apiResponse: unknown) => ReportReasonItem[];
1191
+ /**
1192
+ * Transform report request body before sending to API
1193
+ * If not provided, uses default format: { reason, description }
1194
+ *
1195
+ * @example
1196
+ * ```ts
1197
+ * reportBody: ({ contentId, reasonId, description }) => ({
1198
+ * video_id: contentId,
1199
+ * reason_id: reasonId,
1200
+ * description: description || '',
1201
+ * })
1202
+ * ```
1203
+ */
1204
+ reportBody?: (input: {
1205
+ contentId: string;
1206
+ reasonId: string;
1207
+ description?: string;
1208
+ }) => Record<string, unknown>;
1121
1209
  /**
1122
1210
  * Field mapping for default transforms
1123
1211
  * Used when API field names differ from defaults
@@ -1430,6 +1518,8 @@ interface PresetAdapters {
1430
1518
  dataSource: IDataSource;
1431
1519
  interaction: IInteraction;
1432
1520
  analytics: IAnalytics;
1521
+ /** Playlist adapter (optional) */
1522
+ playlist?: IPlaylistDataSource;
1433
1523
  /** Comment adapter (only if comment endpoints are configured) */
1434
1524
  comment?: ICommentAdapter;
1435
1525
  }
@@ -1897,6 +1987,8 @@ interface FullPresetAdapters {
1897
1987
  videoLoader: IVideoLoader;
1898
1988
  /** Poster preloader (Image) */
1899
1989
  posterLoader: IPosterLoader;
1990
+ /** Playlist adapter (REST API, optional) */
1991
+ playlist?: IPlaylistDataSource;
1900
1992
  /** Comment adapter (REST API, only if comment endpoints configured) */
1901
1993
  comment?: ICommentAdapter;
1902
1994
  /** Logger (passed through) */
@@ -2060,127 +2152,6 @@ declare class HttpClient {
2060
2152
  private sleep;
2061
2153
  }
2062
2154
 
2063
- /**
2064
- * CircuitBreaker - Prevents cascade failures
2065
- *
2066
- * State transitions:
2067
- * CLOSED → (failures >= threshold) → OPEN
2068
- * OPEN → (cooldown passes) → HALF_OPEN
2069
- * HALF_OPEN → (success) → CLOSED
2070
- * HALF_OPEN → (failure) → OPEN
2071
- *
2072
- * @packageDocumentation
2073
- */
2074
- /**
2075
- * Circuit breaker states
2076
- */
2077
- type CircuitState = 'closed' | 'open' | 'half-open';
2078
- /**
2079
- * Circuit breaker configuration
2080
- */
2081
- interface CircuitBreakerConfig {
2082
- /** Number of failures before opening (default: 5) */
2083
- failureThreshold?: number;
2084
- /** Time window for failure counting in ms (default: 30000) */
2085
- failureWindow?: number;
2086
- /** Cool-down period before half-open in ms (default: 30000) */
2087
- cooldownPeriod?: number;
2088
- /** Number of successes in half-open to close (default: 1) */
2089
- successThreshold?: number;
2090
- /** Name for logging */
2091
- name?: string;
2092
- }
2093
- /**
2094
- * Circuit breaker events
2095
- */
2096
- interface CircuitBreakerEvents {
2097
- stateChange: (state: CircuitState, previousState: CircuitState) => void;
2098
- failure: (error: Error, failureCount: number) => void;
2099
- success: () => void;
2100
- rejected: () => void;
2101
- }
2102
- /**
2103
- * Error thrown when circuit is open
2104
- */
2105
- declare class CircuitOpenError extends Error {
2106
- constructor(message?: string);
2107
- }
2108
- /**
2109
- * CircuitBreaker - Prevents cascade failures
2110
- *
2111
- * @example
2112
- * ```typescript
2113
- * const breaker = new CircuitBreaker({ failureThreshold: 5 });
2114
- *
2115
- * async function fetchData() {
2116
- * return breaker.execute(async () => {
2117
- * const response = await fetch('/api/data');
2118
- * return response.json();
2119
- * });
2120
- * }
2121
- * ```
2122
- */
2123
- declare class CircuitBreaker {
2124
- private state;
2125
- private failures;
2126
- private lastFailureTime;
2127
- private halfOpenSuccesses;
2128
- private config;
2129
- private listeners;
2130
- constructor(config?: CircuitBreakerConfig);
2131
- /**
2132
- * Get current circuit state
2133
- */
2134
- getState(): CircuitState;
2135
- /**
2136
- * Check if circuit allows requests
2137
- */
2138
- isAllowed(): boolean;
2139
- /**
2140
- * Execute a function through the circuit breaker
2141
- */
2142
- execute<T>(fn: () => Promise<T>): Promise<T>;
2143
- /**
2144
- * Record a successful call
2145
- */
2146
- recordSuccess(): void;
2147
- /**
2148
- * Record a failed call
2149
- */
2150
- recordFailure(error: Error): void;
2151
- /**
2152
- * Manually reset the circuit breaker
2153
- */
2154
- reset(): void;
2155
- /**
2156
- * Subscribe to events
2157
- */
2158
- on<K extends keyof CircuitBreakerEvents>(event: K, listener: CircuitBreakerEvents[K]): () => void;
2159
- /**
2160
- * Get failure count
2161
- */
2162
- getFailureCount(): number;
2163
- /**
2164
- * Get config
2165
- */
2166
- getConfig(): Required<CircuitBreakerConfig>;
2167
- private updateState;
2168
- private transitionTo;
2169
- private emit;
2170
- }
2171
- /**
2172
- * Get or create a circuit breaker by name
2173
- */
2174
- declare function getCircuitBreaker(name?: string, config?: CircuitBreakerConfig): CircuitBreaker;
2175
- /**
2176
- * Reset all circuit breakers
2177
- */
2178
- declare function resetAllCircuitBreakers(): void;
2179
- /**
2180
- * Get global circuit breaker (default)
2181
- */
2182
- declare function getGlobalCircuitBreaker(): CircuitBreaker;
2183
-
2184
2155
  /**
2185
2156
  * Default Transforms - Auto-transform API responses to SDK format
2186
2157
  *
@@ -2215,6 +2186,8 @@ interface ResolvedTransforms {
2215
2186
  nextCursor: string | null;
2216
2187
  hasMore: boolean;
2217
2188
  };
2189
+ playlist: (data: unknown) => PlaylistData;
2190
+ playlistCollection: (data: unknown) => PlaylistCollectionResponse;
2218
2191
  }
2219
2192
  declare function createTransforms(config?: TransformConfig, logger?: ILogger): ResolvedTransforms;
2220
2193
 
@@ -2280,6 +2253,14 @@ declare class RESTDataAdapter implements IDataSource {
2280
2253
  * - share (optional)
2281
2254
  */
2282
2255
 
2256
+ /**
2257
+ * Report body input from SDK
2258
+ */
2259
+ interface ReportBodyInput {
2260
+ contentId: string;
2261
+ reasonId: string;
2262
+ description?: string;
2263
+ }
2283
2264
  /**
2284
2265
  * REST Interaction Adapter configuration
2285
2266
  */
@@ -2287,6 +2268,10 @@ interface RESTInteractionAdapterConfig {
2287
2268
  httpClient: HttpClient;
2288
2269
  endpoints: RESTEndpointMap['interaction'];
2289
2270
  logger?: ILogger;
2271
+ /** Custom transform for report reasons response */
2272
+ transformReportReasons?: (apiResponse: unknown) => ReportReasonItem[];
2273
+ /** Custom transform for report request body */
2274
+ transformReportBody?: (input: ReportBodyInput) => Record<string, unknown>;
2290
2275
  }
2291
2276
  /**
2292
2277
  * REST Interaction Adapter
@@ -2295,6 +2280,8 @@ declare class RESTInteractionAdapter implements IInteraction {
2295
2280
  private readonly httpClient;
2296
2281
  private readonly endpoints;
2297
2282
  private readonly logger?;
2283
+ private readonly customTransformReportReasons?;
2284
+ private readonly customTransformReportBody?;
2298
2285
  constructor(config: RESTInteractionAdapterConfig);
2299
2286
  /**
2300
2287
  * Like a video
@@ -2332,6 +2319,38 @@ declare class RESTInteractionAdapter implements IInteraction {
2332
2319
  * Share a video (optional tracking)
2333
2320
  */
2334
2321
  share(videoId: string, platform?: string): Promise<void>;
2322
+ /**
2323
+ * Report content (video or image post)
2324
+ *
2325
+ * @param contentId - ID of the content to report
2326
+ * @param reason - Report reason code/ID
2327
+ * @param description - Optional additional description
2328
+ */
2329
+ report(contentId: string, reason: string, description?: string): Promise<void>;
2330
+ /**
2331
+ * Get available report reasons
2332
+ *
2333
+ * @returns Array of report reasons, or empty array if not configured
2334
+ */
2335
+ getReportReasons(): Promise<ReportReason[]>;
2336
+ /**
2337
+ * Mark content as "not interested"
2338
+ *
2339
+ * Used for recommendation algorithm feedback.
2340
+ * Content should be hidden from feed after this action.
2341
+ *
2342
+ * @param contentId - ID of the content (video or image post)
2343
+ */
2344
+ notInterested(contentId: string): Promise<void>;
2345
+ /**
2346
+ * Default transform for API report reasons response to ReportReason[]
2347
+ *
2348
+ * Expected format: [{ id, label, description }]
2349
+ * Or wrapped: { data: [{ id, label, description }] }
2350
+ *
2351
+ * For custom API formats, use `transforms.reportReasons` in preset config.
2352
+ */
2353
+ private transformReportReasons;
2335
2354
  /**
2336
2355
  * Transform API comment response to Comment type
2337
2356
  */
@@ -2701,4 +2720,4 @@ declare class RESTCommentAdapter implements ICommentAdapter {
2701
2720
  private unwrapResponse;
2702
2721
  }
2703
2722
 
2704
- 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, CircuitBreaker, type CircuitBreakerConfig, type CircuitBreakerEvents, CircuitOpenError, type CircuitState, 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, getCircuitBreaker, getGlobalCircuitBreaker, resetAllCircuitBreakers };
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,
@@ -1107,13 +1207,14 @@ var MockInteractionAdapter = class {
1107
1207
  /**
1108
1208
  * Report a video
1109
1209
  *
1110
- * @param _videoId - ID of the video to report
1111
- * @param _reason - Report reason code
1112
- * @param _description - Optional additional description
1210
+ * @param videoId - ID of the video to report
1211
+ * @param reason - Report reason code
1212
+ * @param description - Optional additional description
1113
1213
  */
1114
- async report(_videoId, _reason, _description) {
1214
+ async report(videoId, reason, description) {
1115
1215
  await this.simulateDelay();
1116
1216
  this.maybeThrowError();
1217
+ console.log("[MockInteractionAdapter] Report called (mock)", { videoId, reason, description });
1117
1218
  }
1118
1219
  // ═══════════════════════════════════════════════════════════════
1119
1220
  // TESTING HELPERS (not part of IInteraction interface)
@@ -2609,6 +2710,7 @@ var RESTDataAdapter = class {
2609
2710
  createFallbackVideoItem(data) {
2610
2711
  const obj = data ?? {};
2611
2712
  return {
2713
+ type: "video",
2612
2714
  id: String(obj.id ?? obj.video_id ?? `fallback-${Date.now()}`),
2613
2715
  source: {
2614
2716
  url: String(obj.video_url ?? obj.url ?? ""),
@@ -2638,6 +2740,8 @@ var RESTInteractionAdapter = class {
2638
2740
  this.httpClient = config.httpClient;
2639
2741
  this.endpoints = config.endpoints;
2640
2742
  this.logger = config.logger;
2743
+ this.customTransformReportReasons = config.transformReportReasons;
2744
+ this.customTransformReportBody = config.transformReportBody;
2641
2745
  }
2642
2746
  /**
2643
2747
  * Like a video
@@ -2762,6 +2866,107 @@ var RESTInteractionAdapter = class {
2762
2866
  this.logger?.warn("[RESTInteractionAdapter] share tracking failed", { error });
2763
2867
  }
2764
2868
  }
2869
+ /**
2870
+ * Report content (video or image post)
2871
+ *
2872
+ * @param contentId - ID of the content to report
2873
+ * @param reason - Report reason code/ID
2874
+ * @param description - Optional additional description
2875
+ */
2876
+ async report(contentId, reason, description) {
2877
+ if (!this.endpoints.report) {
2878
+ this.logger?.warn("[RESTInteractionAdapter] report endpoint not configured");
2879
+ return;
2880
+ }
2881
+ try {
2882
+ const body = this.customTransformReportBody ? this.customTransformReportBody({ contentId, reasonId: reason, description }) : { reason, description };
2883
+ this.logger?.debug("[RESTInteractionAdapter] Sending report", {
2884
+ path: this.endpoints.report,
2885
+ contentId,
2886
+ body
2887
+ });
2888
+ await this.httpClient.request({
2889
+ method: "POST",
2890
+ path: this.endpoints.report,
2891
+ pathParams: { id: contentId },
2892
+ body
2893
+ });
2894
+ this.logger?.debug("[RESTInteractionAdapter] Report sent successfully");
2895
+ } catch (error) {
2896
+ this.logger?.error("[RESTInteractionAdapter] report failed", error);
2897
+ throw error;
2898
+ }
2899
+ }
2900
+ /**
2901
+ * Get available report reasons
2902
+ *
2903
+ * @returns Array of report reasons, or empty array if not configured
2904
+ */
2905
+ async getReportReasons() {
2906
+ if (!this.endpoints.reportReasons) {
2907
+ this.logger?.debug("[RESTInteractionAdapter] reportReasons endpoint not configured");
2908
+ return [];
2909
+ }
2910
+ try {
2911
+ const response = await this.httpClient.request({
2912
+ method: "GET",
2913
+ path: this.endpoints.reportReasons
2914
+ });
2915
+ if (this.customTransformReportReasons) {
2916
+ return this.customTransformReportReasons(response);
2917
+ }
2918
+ return this.transformReportReasons(response);
2919
+ } catch (error) {
2920
+ this.logger?.error("[RESTInteractionAdapter] getReportReasons failed", error);
2921
+ return [];
2922
+ }
2923
+ }
2924
+ /**
2925
+ * Mark content as "not interested"
2926
+ *
2927
+ * Used for recommendation algorithm feedback.
2928
+ * Content should be hidden from feed after this action.
2929
+ *
2930
+ * @param contentId - ID of the content (video or image post)
2931
+ */
2932
+ async notInterested(contentId) {
2933
+ if (!this.endpoints.notInterested) {
2934
+ this.logger?.warn("[RESTInteractionAdapter] notInterested endpoint not configured");
2935
+ return;
2936
+ }
2937
+ try {
2938
+ await this.httpClient.request({
2939
+ method: "POST",
2940
+ path: this.endpoints.notInterested,
2941
+ pathParams: { id: contentId }
2942
+ });
2943
+ } catch (error) {
2944
+ this.logger?.error("[RESTInteractionAdapter] notInterested failed", error);
2945
+ throw error;
2946
+ }
2947
+ }
2948
+ /**
2949
+ * Default transform for API report reasons response to ReportReason[]
2950
+ *
2951
+ * Expected format: [{ id, label, description }]
2952
+ * Or wrapped: { data: [{ id, label, description }] }
2953
+ *
2954
+ * For custom API formats, use `transforms.reportReasons` in preset config.
2955
+ */
2956
+ transformReportReasons(response) {
2957
+ const data = this.unwrapResponse(response);
2958
+ if (!Array.isArray(data)) {
2959
+ return [];
2960
+ }
2961
+ return data.map((item) => {
2962
+ const obj = item;
2963
+ return {
2964
+ id: String(obj.id ?? obj.reason_id ?? ""),
2965
+ label: String(obj.label ?? obj.name ?? obj.title ?? ""),
2966
+ description: obj.description
2967
+ };
2968
+ });
2969
+ }
2765
2970
  /**
2766
2971
  * Transform API comment response to Comment type
2767
2972
  */
@@ -2796,6 +3001,69 @@ var RESTInteractionAdapter = class {
2796
3001
  }
2797
3002
  };
2798
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
+
2799
3067
  // src/preset/adapters/RESTViewTrackingAdapter.ts
2800
3068
  var DEFAULT_VIEW_EVENT_VALUE = "seek";
2801
3069
  var DEFAULT_HEARTBEAT_INTERVAL = 1e4;
@@ -3266,6 +3534,83 @@ var HttpClient = class {
3266
3534
  }
3267
3535
  };
3268
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
+
3269
3614
  // src/preset/transforms/defaults.ts
3270
3615
  function getNestedValue(obj, path) {
3271
3616
  if (!obj || typeof obj !== "object") return void 0;
@@ -3373,6 +3718,7 @@ function defaultVideoItemTransform(apiResponse, fieldMap, logger) {
3373
3718
  const author = defaultAuthorTransform(obj);
3374
3719
  const stats = defaultStatsTransform(obj);
3375
3720
  const videoItem = {
3721
+ type: "video",
3376
3722
  id,
3377
3723
  source,
3378
3724
  poster: toSafeString(
@@ -3454,7 +3800,13 @@ function defaultFeedResponseTransform(apiResponse, fieldMap, logger) {
3454
3800
  function createTransforms(config, logger) {
3455
3801
  return {
3456
3802
  videoItem: config?.videoItem ? config.videoItem : (data) => defaultVideoItemTransform(data, config?.fieldMap?.video, logger),
3457
- 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)
3458
3810
  };
3459
3811
  }
3460
3812
 
@@ -3506,7 +3858,9 @@ function createRESTAdapters(config) {
3506
3858
  const interaction = new RESTInteractionAdapter({
3507
3859
  httpClient,
3508
3860
  endpoints: endpoints.interaction,
3509
- logger
3861
+ logger,
3862
+ transformReportReasons: transforms?.reportReasons,
3863
+ transformReportBody: transforms?.reportBody
3510
3864
  });
3511
3865
  let analytics;
3512
3866
  if (endpoints.viewTracking) {
@@ -3531,11 +3885,22 @@ function createRESTAdapters(config) {
3531
3885
  endpoints: endpoints.comment,
3532
3886
  logger
3533
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;
3534
3898
  return {
3535
3899
  dataSource,
3536
3900
  interaction,
3537
3901
  analytics,
3538
- comment
3902
+ comment,
3903
+ playlist
3539
3904
  };
3540
3905
  }
3541
3906
 
@@ -4170,174 +4535,9 @@ function createBrowserAdapters(config) {
4170
4535
  videoLoader,
4171
4536
  posterLoader,
4172
4537
  comment,
4538
+ playlist: restAdapters.playlist,
4173
4539
  logger: config.logger
4174
4540
  };
4175
4541
  }
4176
4542
 
4177
- // src/preset/http/CircuitBreaker.ts
4178
- var DEFAULT_CONFIG3 = {
4179
- failureThreshold: 5,
4180
- failureWindow: 3e4,
4181
- cooldownPeriod: 3e4,
4182
- successThreshold: 1,
4183
- name: "default"
4184
- };
4185
- var CircuitOpenError = class extends Error {
4186
- constructor(message = "Circuit breaker is open") {
4187
- super(message);
4188
- this.name = "CircuitOpenError";
4189
- }
4190
- };
4191
- var CircuitBreaker = class {
4192
- constructor(config = {}) {
4193
- this.state = "closed";
4194
- this.failures = [];
4195
- this.lastFailureTime = 0;
4196
- this.halfOpenSuccesses = 0;
4197
- this.listeners = /* @__PURE__ */ new Map();
4198
- this.config = { ...DEFAULT_CONFIG3, ...config };
4199
- }
4200
- /**
4201
- * Get current circuit state
4202
- */
4203
- getState() {
4204
- this.updateState();
4205
- return this.state;
4206
- }
4207
- /**
4208
- * Check if circuit allows requests
4209
- */
4210
- isAllowed() {
4211
- this.updateState();
4212
- return this.state !== "open";
4213
- }
4214
- /**
4215
- * Execute a function through the circuit breaker
4216
- */
4217
- async execute(fn) {
4218
- this.updateState();
4219
- if (this.state === "open") {
4220
- this.emit("rejected");
4221
- throw new CircuitOpenError(`Circuit breaker [${this.config.name}] is open`);
4222
- }
4223
- try {
4224
- const result = await fn();
4225
- this.recordSuccess();
4226
- return result;
4227
- } catch (error) {
4228
- this.recordFailure(error);
4229
- throw error;
4230
- }
4231
- }
4232
- /**
4233
- * Record a successful call
4234
- */
4235
- recordSuccess() {
4236
- if (this.state === "half-open") {
4237
- this.halfOpenSuccesses++;
4238
- if (this.halfOpenSuccesses >= this.config.successThreshold) {
4239
- this.transitionTo("closed");
4240
- this.failures = [];
4241
- this.halfOpenSuccesses = 0;
4242
- }
4243
- }
4244
- this.emit("success");
4245
- }
4246
- /**
4247
- * Record a failed call
4248
- */
4249
- recordFailure(error) {
4250
- const now = Date.now();
4251
- this.failures.push(now);
4252
- this.lastFailureTime = now;
4253
- const cutoff = now - this.config.failureWindow;
4254
- this.failures = this.failures.filter((t) => t > cutoff);
4255
- this.emit("failure", error, this.failures.length);
4256
- if (this.state === "half-open") {
4257
- this.transitionTo("open");
4258
- this.halfOpenSuccesses = 0;
4259
- } else if (this.state === "closed" && this.failures.length >= this.config.failureThreshold) {
4260
- this.transitionTo("open");
4261
- }
4262
- }
4263
- /**
4264
- * Manually reset the circuit breaker
4265
- */
4266
- reset() {
4267
- this.failures = [];
4268
- this.halfOpenSuccesses = 0;
4269
- this.transitionTo("closed");
4270
- }
4271
- /**
4272
- * Subscribe to events
4273
- */
4274
- on(event, listener) {
4275
- if (!this.listeners.has(event)) {
4276
- this.listeners.set(event, /* @__PURE__ */ new Set());
4277
- }
4278
- const listeners = this.listeners.get(event);
4279
- if (listeners) {
4280
- listeners.add(listener);
4281
- }
4282
- return () => {
4283
- const listeners2 = this.listeners.get(event);
4284
- if (listeners2) {
4285
- listeners2.delete(listener);
4286
- }
4287
- };
4288
- }
4289
- /**
4290
- * Get failure count
4291
- */
4292
- getFailureCount() {
4293
- return this.failures.length;
4294
- }
4295
- /**
4296
- * Get config
4297
- */
4298
- getConfig() {
4299
- return { ...this.config };
4300
- }
4301
- updateState() {
4302
- if (this.state === "open") {
4303
- const timeSinceLastFailure = Date.now() - this.lastFailureTime;
4304
- if (timeSinceLastFailure >= this.config.cooldownPeriod) {
4305
- this.transitionTo("half-open");
4306
- this.halfOpenSuccesses = 0;
4307
- }
4308
- }
4309
- }
4310
- transitionTo(newState) {
4311
- if (this.state === newState) return;
4312
- const previousState = this.state;
4313
- this.state = newState;
4314
- this.emit("stateChange", newState, previousState);
4315
- }
4316
- emit(event, ...args) {
4317
- const listeners = this.listeners.get(event);
4318
- if (listeners) {
4319
- for (const listener of listeners) {
4320
- listener(...args);
4321
- }
4322
- }
4323
- }
4324
- };
4325
- var circuitBreakers = /* @__PURE__ */ new Map();
4326
- function getCircuitBreaker(name = "default", config) {
4327
- let breaker = circuitBreakers.get(name);
4328
- if (!breaker) {
4329
- breaker = new CircuitBreaker({ ...config, name });
4330
- circuitBreakers.set(name, breaker);
4331
- }
4332
- return breaker;
4333
- }
4334
- function resetAllCircuitBreakers() {
4335
- for (const breaker of circuitBreakers.values()) {
4336
- breaker.reset();
4337
- }
4338
- }
4339
- function getGlobalCircuitBreaker() {
4340
- return getCircuitBreaker("global");
4341
- }
4342
-
4343
- export { BrowserPosterLoader, BrowserVideoLoader, CircuitBreaker, CircuitOpenError, 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, getCircuitBreaker, getGlobalCircuitBreaker, resetAllCircuitBreakers };
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.10",
4
+ "version": "0.1.0-beta.12",
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.10"
23
+ "@xhub-short/contracts": "0.1.0-beta.12"
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.0.1-beta.9"
32
+ "@xhub-short/vitest-config": "0.1.0-beta.11",
33
+ "@xhub-short/tsconfig": "0.0.1-beta.0"
34
34
  },
35
35
  "scripts": {
36
36
  "build": "tsup",