@dracoonghost/trndup-sdk 1.3.13 → 1.3.15

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/README.md CHANGED
@@ -8,13 +8,13 @@ Official TypeScript SDK for the TrndUp API with Firebase authentication support.
8
8
  # In your mobile app
9
9
  npm install file:../trndup-service/src/sdk
10
10
  # or when published
11
- npm install @trndup/sdk
11
+ npm install @dracoonghost/trndup-sdk
12
12
  ```
13
13
 
14
14
  ## Quick Start
15
15
 
16
16
  ```typescript
17
- import { TrndUpSDK } from '@trndup/sdk';
17
+ import { TrndUpSDK } from '@dracoonghost/trndup-sdk';
18
18
  import auth from '@react-native-firebase/auth';
19
19
 
20
20
  // Initialize SDK
@@ -132,7 +132,7 @@ const trending = await sdk.insights.getTrending();
132
132
  ## Error Handling
133
133
 
134
134
  ```typescript
135
- import { TrndUpApiError, TrndUpNetworkError } from '@trndup/sdk';
135
+ import { TrndUpApiError, TrndUpNetworkError } from '@dracoonghost/trndup-sdk';
136
136
 
137
137
  try {
138
138
  await sdk.youtube.getVideos();
@@ -191,7 +191,7 @@ interface TrndUpClientConfig {
191
191
  The SDK is fully typed with TypeScript. All request/response types are available:
192
192
 
193
193
  ```typescript
194
- import type { Auth, YouTube, Instagram, Social, Insights } from '@trndup/sdk';
194
+ import type { Auth, YouTube, Instagram, Social, Insights } from '@dracoonghost/trndup-sdk';
195
195
 
196
196
  // Use types in your code
197
197
  const user: Auth.User = await sdk.auth.getCurrentUser();
@@ -216,7 +216,7 @@ npm run type-check
216
216
  npm link
217
217
 
218
218
  # In your mobile app
219
- npm link @trndup/sdk
219
+ npm link @dracoonghost/trndup-sdk
220
220
  ```
221
221
 
222
222
  ## License
package/dist/index.d.mts CHANGED
@@ -10,7 +10,7 @@
10
10
  * @example
11
11
  * ```typescript
12
12
  * import { GoogleSignin } from '@react-native-google-signin/google-signin';
13
- * import { YOUTUBE_SCOPES } from '@trndup/sdk';
13
+ * import { YOUTUBE_SCOPES } from '@dracoonghost/trndup-sdk';
14
14
  *
15
15
  * GoogleSignin.configure({
16
16
  * webClientId: 'YOUR_WEB_CLIENT_ID',
@@ -297,11 +297,6 @@ declare namespace YouTube {
297
297
  * const videos = await client.youtube.getVideos();
298
298
  * localStorage.set('videosLastSync', status.data.syncs.videos.lastSyncAt);
299
299
  * }
300
- *
301
- * // Show user that refresh is happening
302
- * if (status.data.summary.anyInProgress) {
303
- * showRefreshIndicator();
304
- * }
305
300
  * ```
306
301
  */
307
302
  interface UnifiedSyncStatusResponse {
@@ -324,17 +319,6 @@ declare namespace YouTube {
324
319
  /** Video analytics (per-video metrics) */
325
320
  videoAnalytics: SyncTypeStatus;
326
321
  };
327
- /** Summary flags for quick checks */
328
- summary: {
329
- /** All sync types completed at least once with data */
330
- allSynced: boolean;
331
- /** At least one sync type is older than its refresh interval */
332
- anyStale: boolean;
333
- /** At least one sync type failed */
334
- anyFailed: boolean;
335
- /** At least one sync is currently in progress */
336
- anyInProgress: boolean;
337
- };
338
322
  }
339
323
  /**
340
324
  * Response from GET /v1/platforms/youtube/analytics/channel/sync/status
@@ -503,6 +487,109 @@ declare namespace Insights {
503
487
  youtube?: YouTube.Video[];
504
488
  instagram?: Instagram.Post[];
505
489
  }
490
+ /** Momentum status labels */
491
+ type MomentumStatusLabel = 'surging' | 'rising' | 'steady' | 'cooling' | 'dropping';
492
+ /** Momentum trend direction */
493
+ type MomentumTrend = 'accelerating' | 'holding' | 'decelerating';
494
+ /**
495
+ * Metric breakdown showing current vs previous period
496
+ */
497
+ interface MetricComparison {
498
+ /** Current period value */
499
+ current: number;
500
+ /** Previous period value */
501
+ previous: number;
502
+ /** Percentage change (-100 to +100+) */
503
+ change: number;
504
+ }
505
+ /**
506
+ * Momentum Status Response
507
+ *
508
+ * Provides a 0-100 score indicating channel momentum:
509
+ * - 50 = Steady (no change week over week)
510
+ * - 0 = Severe decline (-100% or worse)
511
+ * - 100 = Explosive growth (+100% or better)
512
+ *
513
+ * Status labels:
514
+ * - 80-100: Surging 🚀
515
+ * - 60-79: Rising 📈
516
+ * - 40-59: Steady ➡️
517
+ * - 20-39: Cooling 📉
518
+ * - 0-19: Dropping 🔻
519
+ */
520
+ interface MomentumStatusResponse {
521
+ /** Normalized score 0-100 (50 = no change) */
522
+ score: number;
523
+ /** Raw weighted average change before normalization */
524
+ rawChange: number;
525
+ /** Human-friendly status label */
526
+ status: MomentumStatusLabel;
527
+ /** Trend direction compared to previous period */
528
+ trend: MomentumTrend;
529
+ /** Previous period's score (null if first calculation) */
530
+ previousScore: number | null;
531
+ /** Time periods being compared */
532
+ period: {
533
+ current: {
534
+ start: string;
535
+ end: string;
536
+ };
537
+ previous: {
538
+ start: string;
539
+ end: string;
540
+ };
541
+ };
542
+ /** Breakdown of individual metrics */
543
+ breakdown: {
544
+ views: MetricComparison;
545
+ engagement: MetricComparison;
546
+ watchTime: MetricComparison;
547
+ subscribers: MetricComparison;
548
+ };
549
+ /** Weights used in calculation */
550
+ weights: {
551
+ views: number;
552
+ engagement: number;
553
+ watchTime: number;
554
+ subscribers: number;
555
+ };
556
+ /** Human-readable insight summary */
557
+ insight: string;
558
+ /** Cache metadata */
559
+ _meta?: {
560
+ fromCache: boolean;
561
+ refreshed?: boolean;
562
+ cacheValidFor: string;
563
+ algorithmVersion: string;
564
+ };
565
+ }
566
+ /**
567
+ * Single history point for momentum graphing
568
+ */
569
+ interface MomentumHistoryPoint {
570
+ /** Date (YYYY-MM-DD) */
571
+ date: string;
572
+ /** Momentum score at that time */
573
+ score: number;
574
+ /** Status label at that time */
575
+ status: MomentumStatusLabel;
576
+ }
577
+ /**
578
+ * Momentum history response for graphing
579
+ */
580
+ interface MomentumHistoryResponse {
581
+ /** Historical data points (oldest first) */
582
+ history: MomentumHistoryPoint[];
583
+ /** Number of points returned */
584
+ count: number;
585
+ }
586
+ /**
587
+ * Parameters for momentum history request
588
+ */
589
+ interface GetMomentumHistoryParams {
590
+ /** Max number of data points (default: 30, max: 90) */
591
+ limit?: number;
592
+ }
506
593
  }
507
594
 
508
595
  /**
@@ -851,6 +938,57 @@ declare class InsightsModule {
851
938
  * GET /v1/insights/trending
852
939
  */
853
940
  getTrending(): Promise<Insights.TrendingContent>;
941
+ /**
942
+ * Get YouTube channel momentum status
943
+ *
944
+ * Returns a 0-100 score indicating channel momentum:
945
+ * - 50 = Steady (no change week over week)
946
+ * - 0 = Severe decline
947
+ * - 100 = Explosive growth
948
+ *
949
+ * Results are cached for 12 hours. Use `refreshYouTubeMomentum()` to force recalculation.
950
+ *
951
+ * @example
952
+ * ```typescript
953
+ * const momentum = await client.insights.getYouTubeMomentum();
954
+ *
955
+ * console.log(`Score: ${momentum.score}/100`);
956
+ * console.log(`Status: ${momentum.status}`); // 'surging' | 'rising' | 'steady' | 'cooling' | 'dropping'
957
+ * console.log(`Trend: ${momentum.trend}`); // 'accelerating' | 'holding' | 'decelerating'
958
+ * console.log(momentum.insight); // Human-readable summary
959
+ *
960
+ * // Show breakdown
961
+ * console.log(`Views: ${momentum.breakdown.views.change}%`);
962
+ * console.log(`Engagement: ${momentum.breakdown.engagement.change}%`);
963
+ * ```
964
+ *
965
+ * GET /v1/insights/youtube/momentum
966
+ */
967
+ getYouTubeMomentum(): Promise<Insights.MomentumStatusResponse>;
968
+ /**
969
+ * Get YouTube momentum history for graphing
970
+ *
971
+ * Returns historical momentum scores, oldest first.
972
+ * Use this to plot momentum trends over time.
973
+ *
974
+ * @param params - Query parameters
975
+ * @param params.limit - Max data points (default: 30, max: 90)
976
+ *
977
+ * @example
978
+ * ```typescript
979
+ * const { history } = await client.insights.getYouTubeMomentumHistory({ limit: 30 });
980
+ *
981
+ * // Plot on a chart
982
+ * const chartData = history.map(point => ({
983
+ * x: new Date(point.date),
984
+ * y: point.score,
985
+ * label: point.status
986
+ * }));
987
+ * ```
988
+ *
989
+ * GET /v1/insights/youtube/momentum/history
990
+ */
991
+ getYouTubeMomentumHistory(params?: Insights.GetMomentumHistoryParams): Promise<Insights.MomentumHistoryResponse>;
854
992
  }
855
993
 
856
994
  /**
@@ -861,7 +999,7 @@ declare class InsightsModule {
861
999
  *
862
1000
  * @example
863
1001
  * ```typescript
864
- * import { TrndUpSDK, YOUTUBE_SCOPES } from '@trndup/sdk';
1002
+ * import { TrndUpSDK, YOUTUBE_SCOPES } from '@dracoonghost/trndup-sdk';
865
1003
  * import auth from '@react-native-firebase/auth';
866
1004
  * import { GoogleSignin } from '@react-native-google-signin/google-signin';
867
1005
  *
package/dist/index.d.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * @example
11
11
  * ```typescript
12
12
  * import { GoogleSignin } from '@react-native-google-signin/google-signin';
13
- * import { YOUTUBE_SCOPES } from '@trndup/sdk';
13
+ * import { YOUTUBE_SCOPES } from '@dracoonghost/trndup-sdk';
14
14
  *
15
15
  * GoogleSignin.configure({
16
16
  * webClientId: 'YOUR_WEB_CLIENT_ID',
@@ -297,11 +297,6 @@ declare namespace YouTube {
297
297
  * const videos = await client.youtube.getVideos();
298
298
  * localStorage.set('videosLastSync', status.data.syncs.videos.lastSyncAt);
299
299
  * }
300
- *
301
- * // Show user that refresh is happening
302
- * if (status.data.summary.anyInProgress) {
303
- * showRefreshIndicator();
304
- * }
305
300
  * ```
306
301
  */
307
302
  interface UnifiedSyncStatusResponse {
@@ -324,17 +319,6 @@ declare namespace YouTube {
324
319
  /** Video analytics (per-video metrics) */
325
320
  videoAnalytics: SyncTypeStatus;
326
321
  };
327
- /** Summary flags for quick checks */
328
- summary: {
329
- /** All sync types completed at least once with data */
330
- allSynced: boolean;
331
- /** At least one sync type is older than its refresh interval */
332
- anyStale: boolean;
333
- /** At least one sync type failed */
334
- anyFailed: boolean;
335
- /** At least one sync is currently in progress */
336
- anyInProgress: boolean;
337
- };
338
322
  }
339
323
  /**
340
324
  * Response from GET /v1/platforms/youtube/analytics/channel/sync/status
@@ -503,6 +487,109 @@ declare namespace Insights {
503
487
  youtube?: YouTube.Video[];
504
488
  instagram?: Instagram.Post[];
505
489
  }
490
+ /** Momentum status labels */
491
+ type MomentumStatusLabel = 'surging' | 'rising' | 'steady' | 'cooling' | 'dropping';
492
+ /** Momentum trend direction */
493
+ type MomentumTrend = 'accelerating' | 'holding' | 'decelerating';
494
+ /**
495
+ * Metric breakdown showing current vs previous period
496
+ */
497
+ interface MetricComparison {
498
+ /** Current period value */
499
+ current: number;
500
+ /** Previous period value */
501
+ previous: number;
502
+ /** Percentage change (-100 to +100+) */
503
+ change: number;
504
+ }
505
+ /**
506
+ * Momentum Status Response
507
+ *
508
+ * Provides a 0-100 score indicating channel momentum:
509
+ * - 50 = Steady (no change week over week)
510
+ * - 0 = Severe decline (-100% or worse)
511
+ * - 100 = Explosive growth (+100% or better)
512
+ *
513
+ * Status labels:
514
+ * - 80-100: Surging 🚀
515
+ * - 60-79: Rising 📈
516
+ * - 40-59: Steady ➡️
517
+ * - 20-39: Cooling 📉
518
+ * - 0-19: Dropping 🔻
519
+ */
520
+ interface MomentumStatusResponse {
521
+ /** Normalized score 0-100 (50 = no change) */
522
+ score: number;
523
+ /** Raw weighted average change before normalization */
524
+ rawChange: number;
525
+ /** Human-friendly status label */
526
+ status: MomentumStatusLabel;
527
+ /** Trend direction compared to previous period */
528
+ trend: MomentumTrend;
529
+ /** Previous period's score (null if first calculation) */
530
+ previousScore: number | null;
531
+ /** Time periods being compared */
532
+ period: {
533
+ current: {
534
+ start: string;
535
+ end: string;
536
+ };
537
+ previous: {
538
+ start: string;
539
+ end: string;
540
+ };
541
+ };
542
+ /** Breakdown of individual metrics */
543
+ breakdown: {
544
+ views: MetricComparison;
545
+ engagement: MetricComparison;
546
+ watchTime: MetricComparison;
547
+ subscribers: MetricComparison;
548
+ };
549
+ /** Weights used in calculation */
550
+ weights: {
551
+ views: number;
552
+ engagement: number;
553
+ watchTime: number;
554
+ subscribers: number;
555
+ };
556
+ /** Human-readable insight summary */
557
+ insight: string;
558
+ /** Cache metadata */
559
+ _meta?: {
560
+ fromCache: boolean;
561
+ refreshed?: boolean;
562
+ cacheValidFor: string;
563
+ algorithmVersion: string;
564
+ };
565
+ }
566
+ /**
567
+ * Single history point for momentum graphing
568
+ */
569
+ interface MomentumHistoryPoint {
570
+ /** Date (YYYY-MM-DD) */
571
+ date: string;
572
+ /** Momentum score at that time */
573
+ score: number;
574
+ /** Status label at that time */
575
+ status: MomentumStatusLabel;
576
+ }
577
+ /**
578
+ * Momentum history response for graphing
579
+ */
580
+ interface MomentumHistoryResponse {
581
+ /** Historical data points (oldest first) */
582
+ history: MomentumHistoryPoint[];
583
+ /** Number of points returned */
584
+ count: number;
585
+ }
586
+ /**
587
+ * Parameters for momentum history request
588
+ */
589
+ interface GetMomentumHistoryParams {
590
+ /** Max number of data points (default: 30, max: 90) */
591
+ limit?: number;
592
+ }
506
593
  }
507
594
 
508
595
  /**
@@ -851,6 +938,57 @@ declare class InsightsModule {
851
938
  * GET /v1/insights/trending
852
939
  */
853
940
  getTrending(): Promise<Insights.TrendingContent>;
941
+ /**
942
+ * Get YouTube channel momentum status
943
+ *
944
+ * Returns a 0-100 score indicating channel momentum:
945
+ * - 50 = Steady (no change week over week)
946
+ * - 0 = Severe decline
947
+ * - 100 = Explosive growth
948
+ *
949
+ * Results are cached for 12 hours. Use `refreshYouTubeMomentum()` to force recalculation.
950
+ *
951
+ * @example
952
+ * ```typescript
953
+ * const momentum = await client.insights.getYouTubeMomentum();
954
+ *
955
+ * console.log(`Score: ${momentum.score}/100`);
956
+ * console.log(`Status: ${momentum.status}`); // 'surging' | 'rising' | 'steady' | 'cooling' | 'dropping'
957
+ * console.log(`Trend: ${momentum.trend}`); // 'accelerating' | 'holding' | 'decelerating'
958
+ * console.log(momentum.insight); // Human-readable summary
959
+ *
960
+ * // Show breakdown
961
+ * console.log(`Views: ${momentum.breakdown.views.change}%`);
962
+ * console.log(`Engagement: ${momentum.breakdown.engagement.change}%`);
963
+ * ```
964
+ *
965
+ * GET /v1/insights/youtube/momentum
966
+ */
967
+ getYouTubeMomentum(): Promise<Insights.MomentumStatusResponse>;
968
+ /**
969
+ * Get YouTube momentum history for graphing
970
+ *
971
+ * Returns historical momentum scores, oldest first.
972
+ * Use this to plot momentum trends over time.
973
+ *
974
+ * @param params - Query parameters
975
+ * @param params.limit - Max data points (default: 30, max: 90)
976
+ *
977
+ * @example
978
+ * ```typescript
979
+ * const { history } = await client.insights.getYouTubeMomentumHistory({ limit: 30 });
980
+ *
981
+ * // Plot on a chart
982
+ * const chartData = history.map(point => ({
983
+ * x: new Date(point.date),
984
+ * y: point.score,
985
+ * label: point.status
986
+ * }));
987
+ * ```
988
+ *
989
+ * GET /v1/insights/youtube/momentum/history
990
+ */
991
+ getYouTubeMomentumHistory(params?: Insights.GetMomentumHistoryParams): Promise<Insights.MomentumHistoryResponse>;
854
992
  }
855
993
 
856
994
  /**
@@ -861,7 +999,7 @@ declare class InsightsModule {
861
999
  *
862
1000
  * @example
863
1001
  * ```typescript
864
- * import { TrndUpSDK, YOUTUBE_SCOPES } from '@trndup/sdk';
1002
+ * import { TrndUpSDK, YOUTUBE_SCOPES } from '@dracoonghost/trndup-sdk';
865
1003
  * import auth from '@react-native-firebase/auth';
866
1004
  * import { GoogleSignin } from '@react-native-google-signin/google-signin';
867
1005
  *
package/dist/index.js CHANGED
@@ -534,6 +534,67 @@ var InsightsModule = class {
534
534
  async getTrending() {
535
535
  return this.client.get("/v1/insights/trending");
536
536
  }
537
+ // =========================================================================
538
+ // YOUTUBE MOMENTUM
539
+ // =========================================================================
540
+ /**
541
+ * Get YouTube channel momentum status
542
+ *
543
+ * Returns a 0-100 score indicating channel momentum:
544
+ * - 50 = Steady (no change week over week)
545
+ * - 0 = Severe decline
546
+ * - 100 = Explosive growth
547
+ *
548
+ * Results are cached for 12 hours. Use `refreshYouTubeMomentum()` to force recalculation.
549
+ *
550
+ * @example
551
+ * ```typescript
552
+ * const momentum = await client.insights.getYouTubeMomentum();
553
+ *
554
+ * console.log(`Score: ${momentum.score}/100`);
555
+ * console.log(`Status: ${momentum.status}`); // 'surging' | 'rising' | 'steady' | 'cooling' | 'dropping'
556
+ * console.log(`Trend: ${momentum.trend}`); // 'accelerating' | 'holding' | 'decelerating'
557
+ * console.log(momentum.insight); // Human-readable summary
558
+ *
559
+ * // Show breakdown
560
+ * console.log(`Views: ${momentum.breakdown.views.change}%`);
561
+ * console.log(`Engagement: ${momentum.breakdown.engagement.change}%`);
562
+ * ```
563
+ *
564
+ * GET /v1/insights/youtube/momentum
565
+ */
566
+ async getYouTubeMomentum() {
567
+ return this.client.get("/v1/insights/youtube/momentum");
568
+ }
569
+ /**
570
+ * Get YouTube momentum history for graphing
571
+ *
572
+ * Returns historical momentum scores, oldest first.
573
+ * Use this to plot momentum trends over time.
574
+ *
575
+ * @param params - Query parameters
576
+ * @param params.limit - Max data points (default: 30, max: 90)
577
+ *
578
+ * @example
579
+ * ```typescript
580
+ * const { history } = await client.insights.getYouTubeMomentumHistory({ limit: 30 });
581
+ *
582
+ * // Plot on a chart
583
+ * const chartData = history.map(point => ({
584
+ * x: new Date(point.date),
585
+ * y: point.score,
586
+ * label: point.status
587
+ * }));
588
+ * ```
589
+ *
590
+ * GET /v1/insights/youtube/momentum/history
591
+ */
592
+ async getYouTubeMomentumHistory(params) {
593
+ const query = params?.limit ? `?limit=${params.limit}` : "";
594
+ return this.client.get(
595
+ `/v1/insights/youtube/momentum/history${query}`
596
+ );
597
+ }
537
598
  };
538
599
 
539
600
  // types.ts
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../client.ts","../modules/auth.ts","../modules/youtube.ts","../modules/instagram.ts","../modules/social.ts","../modules/insights.ts","../types.ts","../index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * TrndUp SDK - Base Client\n * \n * Core HTTP client with Firebase authentication support,\n * error handling, and automatic retries.\n */\n\nimport type { ApiResponse, ApiErrorResponse, ApiSuccessResponse } from './types';\n\n// =============================================================================\n// CONFIGURATION\n// =============================================================================\n\nexport interface TrndUpClientConfig {\n /** Base URL for the API (e.g., 'https://api.trndup.app' or 'http://localhost:3000') */\n baseUrl: string;\n \n /** Function to get the current Firebase ID token */\n getToken: () => string | null | Promise<string | null>;\n \n /** Called when auth completely fails (user needs to re-login) */\n onAuthFailure?: () => void | Promise<void>;\n \n /** Called on any API error */\n onError?: (error: ApiErrorResponse, endpoint: string) => void;\n \n /** Custom headers to include in all requests */\n defaultHeaders?: Record<string, string>;\n \n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n \n /** Enable debug logging (default: false) */\n debug?: boolean;\n}\n\nexport interface RequestOptions {\n /** Skip authentication for this request */\n skipAuth?: boolean;\n \n /** Additional headers for this request */\n headers?: Record<string, string>;\n \n /** AbortController signal for cancellation */\n signal?: AbortSignal;\n \n /** Custom timeout for this request */\n timeout?: number;\n}\n\ninterface RequestConfig extends RequestOptions {\n method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n path: string;\n params?: Record<string, string | number | boolean | undefined>;\n body?: unknown;\n}\n\n// =============================================================================\n// ERROR CLASSES\n// =============================================================================\n\nexport class TrndUpApiError extends Error {\n constructor(\n public readonly response: ApiErrorResponse,\n public readonly status: number,\n public readonly endpoint: string\n ) {\n super(response.error);\n this.name = 'TrndUpApiError';\n }\n \n get code(): string | undefined {\n return this.response.code;\n }\n\n get metadata(): Record<string, unknown> | undefined {\n return this.response.metadata;\n }\n\n /** Check if error is due to authentication failure */\n isAuthError(): boolean {\n return this.status === 401 || this.code === 'AUTH_EXPIRED' || this.code === 'AUTH_INVALID';\n }\n\n /** Check if error is due to rate limiting */\n isRateLimitError(): boolean {\n return this.status === 429 || this.code === 'RATE_LIMITED';\n }\n}\n\nexport class TrndUpNetworkError extends Error {\n constructor(\n public readonly originalError: Error,\n public readonly endpoint: string\n ) {\n super(`Network error calling ${endpoint}: ${originalError.message}`);\n this.name = 'TrndUpNetworkError';\n }\n}\n\n// =============================================================================\n// BASE CLIENT\n// =============================================================================\n\nexport class TrndUpClient {\n private config: Required<Omit<TrndUpClientConfig, 'onAuthFailure' | 'onError' | 'defaultHeaders' | 'debug'>> & {\n onAuthFailure?: TrndUpClientConfig['onAuthFailure'];\n onError?: TrndUpClientConfig['onError'];\n defaultHeaders?: Record<string, string>;\n debug?: boolean;\n };\n\n // Module instances (imported by subclasses or external modules)\n public auth!: any;\n public youtube!: any;\n public instagram!: any;\n public social!: any;\n public insights!: any;\n\n constructor(config: TrndUpClientConfig) {\n this.config = {\n baseUrl: config.baseUrl.replace(/\\/$/, ''), // Remove trailing slash\n getToken: config.getToken,\n timeout: config.timeout ?? 30000,\n onAuthFailure: config.onAuthFailure,\n onError: config.onError,\n defaultHeaders: config.defaultHeaders,\n debug: config.debug ?? false,\n };\n }\n\n // =============================================================================\n // PUBLIC REQUEST METHODS\n // =============================================================================\n\n async get<T = unknown>(\n path: string,\n params?: Record<string, string | number | boolean | undefined>,\n options?: RequestOptions\n ): Promise<T> {\n return this.request<T>({ method: 'GET', path, params, ...options });\n }\n\n async post<T = unknown>(\n path: string,\n body?: unknown,\n options?: RequestOptions\n ): Promise<T> {\n return this.request<T>({ method: 'POST', path, body, ...options });\n }\n\n async put<T = unknown>(\n path: string,\n body?: unknown,\n options?: RequestOptions\n ): Promise<T> {\n return this.request<T>({ method: 'PUT', path, body, ...options });\n }\n\n async patch<T = unknown>(\n path: string,\n body?: unknown,\n options?: RequestOptions\n ): Promise<T> {\n return this.request<T>({ method: 'PATCH', path, body, ...options });\n }\n\n async delete<T = unknown>(\n path: string,\n options?: RequestOptions\n ): Promise<T> {\n return this.request<T>({ method: 'DELETE', path, ...options });\n }\n\n // =============================================================================\n // CORE REQUEST LOGIC\n // =============================================================================\n\n private async request<T>(config: RequestConfig): Promise<T> {\n const { method, path, params, body, skipAuth, headers, signal, timeout } = config;\n\n // Build URL\n const url = this.buildUrl(path, params);\n\n // Build headers\n const requestHeaders = await this.buildHeaders(skipAuth, headers);\n\n // Build request init\n const init: RequestInit = {\n method,\n headers: requestHeaders,\n signal,\n };\n\n if (body && method !== 'GET') {\n init.body = JSON.stringify(body);\n }\n\n // Add timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(\n () => controller.abort(),\n timeout ?? this.config.timeout\n );\n\n try {\n if (this.config.debug) {\n console.log(`[TrndUp SDK] ${method} ${url}`, { body, headers: requestHeaders });\n }\n\n const response = await fetch(url, {\n ...init,\n signal: signal ?? controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n return await this.handleResponse<T>(response, path);\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof TrndUpApiError) {\n throw error;\n }\n\n // Network error\n const networkError = new TrndUpNetworkError(\n error as Error,\n path\n );\n\n if (this.config.debug) {\n console.error('[TrndUp SDK] Network error:', networkError);\n }\n\n throw networkError;\n }\n }\n\n private buildUrl(path: string, params?: Record<string, string | number | boolean | undefined>): string {\n const url = new URL(`${this.config.baseUrl}${path}`);\n\n if (params) {\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n url.searchParams.append(key, String(value));\n }\n });\n }\n\n return url.toString();\n }\n\n private async buildHeaders(\n skipAuth?: boolean,\n additionalHeaders?: Record<string, string>\n ): Promise<Record<string, string>> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...this.config.defaultHeaders,\n ...additionalHeaders,\n };\n\n // Add Firebase ID token for authentication\n if (!skipAuth) {\n const token = await this.config.getToken();\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n }\n\n return headers;\n }\n\n private async handleResponse<T>(response: Response, endpoint: string): Promise<T> {\n const contentType = response.headers.get('content-type');\n const isJson = contentType?.includes('application/json');\n\n if (!response.ok) {\n // Try to parse error response\n let errorResponse: ApiErrorResponse;\n\n if (isJson) {\n try {\n const parsed = await response.json();\n errorResponse = parsed as ApiErrorResponse;\n } catch {\n errorResponse = {\n success: false,\n error: response.statusText || 'Unknown error',\n code: `HTTP_${response.status}`,\n };\n }\n } else {\n const text = await response.text();\n errorResponse = {\n success: false,\n error: text || response.statusText || 'Unknown error',\n code: `HTTP_${response.status}`,\n };\n }\n\n const apiError = new TrndUpApiError(errorResponse, response.status, endpoint);\n\n // Call error handler\n if (this.config.onError) {\n this.config.onError(errorResponse, endpoint);\n }\n\n // Call auth failure handler for 401 errors\n if (apiError.isAuthError() && this.config.onAuthFailure) {\n await this.config.onAuthFailure();\n }\n\n if (this.config.debug) {\n console.error('[TrndUp SDK] API error:', apiError);\n }\n\n throw apiError;\n }\n\n // Parse success response\n if (isJson) {\n const parsed = await response.json();\n const jsonResponse = parsed as ApiResponse<T>;\n\n if (jsonResponse.success === false) {\n // This shouldn't happen with 2xx status, but handle it\n throw new TrndUpApiError(jsonResponse, response.status, endpoint);\n }\n\n return (jsonResponse as ApiSuccessResponse<T>).data;\n }\n\n // Non-JSON response\n return (await response.text()) as unknown as T;\n }\n}\n","/**\n * TrndUp SDK - Auth Module\n * \n * Firebase authentication methods with Twilio phone OTP\n */\n\nimport type { TrndUpClient } from '../client';\nimport type { Auth } from '../types';\n\nexport class AuthModule {\n constructor(private client: TrndUpClient) {}\n\n // =========================================================================\n // PHONE OTP AUTHENTICATION (via Twilio - no reCAPTCHA needed)\n // =========================================================================\n\n /**\n * Send OTP verification code to phone number\n * POST /auth/phone/send-otp\n * \n * @param phoneNumber - Phone number (E.164 format recommended, e.g., +14155551234)\n */\n async sendOTP(phoneNumber: string): Promise<Auth.SendOTPResponse> {\n return this.client.post<Auth.SendOTPResponse>(\n '/auth/phone/send-otp', \n { phoneNumber }, \n { skipAuth: true }\n );\n }\n\n /**\n * Verify OTP and get Firebase custom token\n * POST /auth/phone/verify-otp\n * \n * After calling this, use Firebase signInWithCustomToken(customToken)\n * to complete authentication on the client.\n * \n * @param phoneNumber - Phone number that received the OTP\n * @param code - 6-digit verification code\n */\n async verifyOTP(phoneNumber: string, code: string): Promise<Auth.VerifyOTPResponse> {\n return this.client.post<Auth.VerifyOTPResponse>(\n '/auth/phone/verify-otp',\n { phoneNumber, code },\n { skipAuth: true }\n );\n }\n\n // =========================================================================\n // FIREBASE ID TOKEN AUTHENTICATION\n // =========================================================================\n\n /**\n * Login with Firebase ID token (for Google, Apple, Email/Password)\n * POST /auth/login\n */\n async login(idToken: string): Promise<Auth.LoginResponse> {\n return this.client.post<Auth.LoginResponse>('/auth/login', { idToken }, { skipAuth: true });\n }\n\n /**\n * Get current authenticated user's profile\n * GET /auth/me\n */\n async getCurrentUser(): Promise<Auth.CurrentUserResponse> {\n return this.client.get<Auth.CurrentUserResponse>('/auth/me');\n }\n\n /**\n * Update user profile\n * PATCH /auth/me\n */\n async updateProfile(data: Auth.UpdateProfileRequest): Promise<Auth.UpdateProfileResponse> {\n return this.client.patch<Auth.UpdateProfileResponse>('/auth/me', data);\n }\n\n /**\n * Logout (acknowledge logout on backend)\n * POST /auth/logout\n */\n async logout(): Promise<{ message: string }> {\n return this.client.post<{ message: string }>('/auth/logout');\n }\n\n /**\n * Check auth service status\n * GET /auth/status\n */\n async getStatus(): Promise<{ success: boolean; message: string }> {\n return this.client.get('/auth/status', undefined, { skipAuth: true });\n }\n\n /**\n * Get user's platform connection status\n * GET /user/platforms/status\n */\n async getPlatformStatus(): Promise<Auth.PlatformStatus> {\n return this.client.get<Auth.PlatformStatus>('/user/platforms/status');\n }\n}\n","/**\n * TrndUp SDK - YouTube Module\n * \n * YouTube analytics and data methods\n */\n\nimport type { TrndUpClient } from '../client';\nimport type { YouTube } from '../types';\n\nexport class YouTubeModule {\n constructor(private client: TrndUpClient) {}\n\n /**\n * Get YouTube initialization status (channel sync)\n * Check if user needs to init or has already synced channel\n * \n * GET /v1/platforms/youtube/init/status\n */\n async getInitStatus(): Promise<YouTube.InitStatusResponse> {\n return this.client.get<YouTube.InitStatusResponse>('/v1/platforms/youtube/init/status');\n }\n\n /**\n * Initialize YouTube channel sync\n * Fetches channel info and stores in database\n * \n * POST /v1/platforms/youtube/init\n */\n async initialize(): Promise<YouTube.InitResponse> {\n return this.client.post<YouTube.InitResponse>('/v1/platforms/youtube/init');\n }\n\n /**\n * Get videos sync status\n * Check if videos need to be synced\n * \n * GET /v1/platforms/youtube/videos/sync/status\n */\n async getVideosSyncStatus(): Promise<YouTube.VideosSyncStatusResponse> {\n return this.client.get<YouTube.VideosSyncStatusResponse>('/v1/platforms/youtube/videos/sync/status');\n }\n\n /**\n * Sync all videos from YouTube\n * Fetches all videos and stores in database\n * Returns video counts and oldest video date (for analytics start date)\n * \n * POST /v1/platforms/youtube/videos/sync\n */\n async syncVideos(): Promise<YouTube.VideosSyncResponse> {\n return this.client.post<YouTube.VideosSyncResponse>('/v1/platforms/youtube/videos/sync');\n }\n\n /**\n * Get YouTube videos from database\n * GET /v1/platforms/youtube/videos\n */\n async getVideos(params?: YouTube.GetVideosParams): Promise<YouTube.GetVideosResponse> {\n return this.client.get<YouTube.GetVideosResponse>(\n '/v1/platforms/youtube/videos', \n params as Record<string, string | number | boolean | undefined>\n );\n }\n\n /**\n * Get specific video by ID\n * GET /v1/platforms/youtube/videos/:videoId\n */\n async getVideo(videoId: string): Promise<YouTube.Video> {\n return this.client.get<YouTube.Video>(`/v1/platforms/youtube/videos/${videoId}`);\n }\n\n /**\n * Get channel metrics\n * GET /v1/platforms/youtube/channel/metrics\n */\n async getChannelMetrics(): Promise<YouTube.ChannelMetrics> {\n return this.client.get<YouTube.ChannelMetrics>('/v1/platforms/youtube/channel/metrics');\n }\n\n /**\n * Get channel health score\n * GET /v1/platforms/youtube/health\n */\n async getHealthScore(params?: YouTube.GetHealthScoreParams): Promise<YouTube.HealthScore> {\n return this.client.get<YouTube.HealthScore>(\n '/v1/platforms/youtube/health', \n params as Record<string, string | number | boolean | undefined>\n );\n }\n\n /**\n * Refresh YouTube data\n * POST /v1/platforms/youtube/refresh\n */\n async refresh(): Promise<{ message: string; jobId?: string }> {\n return this.client.post<{ message: string; jobId?: string }>('/v1/platforms/youtube/refresh');\n }\n\n // =========================================================================\n // UNIFIED SYNC STATUS\n // =========================================================================\n\n /**\n * Get unified sync status for all YouTube data types.\n * \n * Use this for:\n * - Checking if UI cache needs refresh (compare lastSyncAt)\n * - Showing sync status in settings/debug screens\n * - Detecting if background sync is running\n * \n * GET /v1/platforms/youtube/sync/status\n * \n * @example\n * ```typescript\n * const status = await client.youtube.getUnifiedSyncStatus();\n * \n * // Check if backend synced since our last fetch\n * const storedLastSync = await AsyncStorage.getItem('videosLastSync');\n * if (status.syncs.videos.lastSyncAt > storedLastSync) {\n * const videos = await client.youtube.getVideos();\n * await AsyncStorage.setItem('videosLastSync', status.syncs.videos.lastSyncAt);\n * }\n * ```\n */\n async getUnifiedSyncStatus(): Promise<YouTube.UnifiedSyncStatusResponse> {\n return this.client.get<YouTube.UnifiedSyncStatusResponse>(\n '/v1/platforms/youtube/sync/status'\n );\n }\n\n // =========================================================================\n // CHANNEL ANALYTICS SYNC\n // =========================================================================\n\n /**\n * Get channel analytics sync status\n * GET /v1/platforms/youtube/analytics/channel/sync/status\n */\n async getChannelAnalyticsSyncStatus(): Promise<YouTube.ChannelAnalyticsSyncStatusResponse> {\n return this.client.get<YouTube.ChannelAnalyticsSyncStatusResponse>(\n '/v1/platforms/youtube/analytics/channel/sync/status'\n );\n }\n\n /**\n * Sync channel analytics (daily + weekly aggregates)\n * Fetches from YouTube Analytics API and stores in database\n * Uses oldest video date as start date\n * \n * POST /v1/platforms/youtube/analytics/channel/sync\n */\n async syncChannelAnalytics(): Promise<YouTube.ChannelAnalyticsSyncResponse> {\n return this.client.post<YouTube.ChannelAnalyticsSyncResponse>(\n '/v1/platforms/youtube/analytics/channel/sync'\n );\n }\n\n // =========================================================================\n // VIDEO ANALYTICS SYNC\n // =========================================================================\n\n /**\n * Get video analytics sync status\n * GET /v1/platforms/youtube/analytics/videos/sync/status\n */\n async getVideoAnalyticsSyncStatus(): Promise<YouTube.VideoAnalyticsSyncStatusResponse> {\n return this.client.get<YouTube.VideoAnalyticsSyncStatusResponse>(\n '/v1/platforms/youtube/analytics/videos/sync/status'\n );\n }\n\n /**\n * Sync analytics for all videos\n * Fetches from YouTube Analytics API and updates video records\n * \n * POST /v1/platforms/youtube/analytics/videos/sync\n */\n async syncVideoAnalytics(): Promise<YouTube.VideoAnalyticsSyncResponse> {\n return this.client.post<YouTube.VideoAnalyticsSyncResponse>(\n '/v1/platforms/youtube/analytics/videos/sync'\n );\n }\n}\n","/**\n * TrndUp SDK - Instagram Module\n * \n * Instagram analytics and data methods\n */\n\nimport type { TrndUpClient } from '../client';\nimport type { Instagram } from '../types';\n\nexport class InstagramModule {\n constructor(private client: TrndUpClient) {}\n\n /**\n * Get Instagram initialization status\n * GET /v1/platforms/instagram/init/status\n */\n async getInitStatus(): Promise<Instagram.InitStatusResponse> {\n return this.client.get<Instagram.InitStatusResponse>('/v1/platforms/instagram/init/status');\n }\n\n /**\n * Initialize Instagram data sync\n * POST /v1/platforms/instagram/init\n */\n async initialize(): Promise<{ message: string }> {\n return this.client.post<{ message: string }>('/v1/platforms/instagram/init');\n }\n\n /**\n * Get Instagram posts\n * GET /v1/platforms/instagram/posts\n */\n async getPosts(params?: Instagram.GetPostsParams): Promise<Instagram.GetPostsResponse> {\n return this.client.get<Instagram.GetPostsResponse>(\n '/v1/platforms/instagram/posts', \n params as Record<string, string | number | boolean | undefined>\n );\n }\n\n /**\n * Get specific post by ID\n * GET /v1/platforms/instagram/posts/:postId\n */\n async getPost(postId: string): Promise<Instagram.Post> {\n return this.client.get<Instagram.Post>(`/v1/platforms/instagram/posts/${postId}`);\n }\n\n /**\n * Get account metrics\n * GET /v1/platforms/instagram/account/metrics\n */\n async getAccountMetrics(): Promise<Instagram.AccountMetrics> {\n return this.client.get<Instagram.AccountMetrics>('/v1/platforms/instagram/account/metrics');\n }\n\n /**\n * Refresh Instagram data\n * POST /v1/platforms/instagram/refresh\n */\n async refresh(): Promise<{ message: string }> {\n return this.client.post<{ message: string }>('/v1/platforms/instagram/refresh');\n }\n}\n","/**\n * TrndUp SDK - Social Module\n * \n * Social account linking methods\n */\n\nimport type { TrndUpClient } from '../client';\nimport type { Social } from '../types';\n\nexport class SocialModule {\n constructor(private client: TrndUpClient) {}\n\n /**\n * Link YouTube account\n * POST /social/link/youtube\n */\n async linkYouTube(data: Social.LinkYouTubeRequest): Promise<Social.LinkAccountResponse> {\n return this.client.post<Social.LinkAccountResponse>('/social/link/youtube', data);\n }\n\n /**\n * Link Instagram account\n * POST /social/link/instagram\n */\n async linkInstagram(data: Social.LinkInstagramRequest): Promise<Social.LinkAccountResponse> {\n return this.client.post<Social.LinkAccountResponse>('/social/link/instagram', data);\n }\n\n /**\n * Get all connected social accounts\n * GET /social/accounts\n */\n async getConnectedAccounts(): Promise<Social.ConnectedAccount[]> {\n return this.client.get<Social.ConnectedAccount[]>('/social/accounts');\n }\n\n /**\n * Unlink a social account\n * DELETE /social/unlink/:provider\n */\n async unlinkAccount(provider: string): Promise<{ message: string }> {\n return this.client.delete<{ message: string }>(`/social/unlink/${provider}`);\n }\n}\n","/**\n * TrndUp SDK - Insights Module\n * \n * Cross-platform insights and analytics\n */\n\nimport type { TrndUpClient } from '../client';\nimport type { Insights } from '../types';\n\nexport class InsightsModule {\n constructor(private client: TrndUpClient) {}\n\n /**\n * Get cross-platform metrics\n * GET /v1/insights/overview\n */\n async getOverview(): Promise<Insights.CrossPlatformMetrics> {\n return this.client.get<Insights.CrossPlatformMetrics>('/v1/insights/overview');\n }\n\n /**\n * Get trending content across all platforms\n * GET /v1/insights/trending\n */\n async getTrending(): Promise<Insights.TrendingContent> {\n return this.client.get<Insights.TrendingContent>('/v1/insights/trending');\n }\n}\n","/**\n * TrndUp SDK Types\n * \n * Type definitions for the TrndUp API SDK\n */\n\n// =============================================================================\n// OAUTH SCOPES\n// =============================================================================\n\n/**\n * Required OAuth scopes for YouTube account linking\n * Use these when configuring Google Sign-In in your mobile app\n * \n * @example\n * ```typescript\n * import { GoogleSignin } from '@react-native-google-signin/google-signin';\n * import { YOUTUBE_SCOPES } from '@trndup/sdk';\n * \n * GoogleSignin.configure({\n * webClientId: 'YOUR_WEB_CLIENT_ID',\n * offlineAccess: true,\n * scopes: YOUTUBE_SCOPES\n * });\n * ```\n */\nexport const YOUTUBE_SCOPES = [\n 'openid',\n 'https://www.googleapis.com/auth/userinfo.profile',\n 'https://www.googleapis.com/auth/userinfo.email',\n 'https://www.googleapis.com/auth/youtube.readonly',\n 'https://www.googleapis.com/auth/yt-analytics.readonly',\n 'https://www.googleapis.com/auth/yt-analytics-monetary.readonly',\n 'https://www.googleapis.com/auth/youtube.force-ssl',\n 'https://www.googleapis.com/auth/youtubepartner'\n];\n\n/**\n * Required OAuth scopes for Instagram account linking\n */\nexport const INSTAGRAM_SCOPES = [\n 'instagram_business_basic',\n 'instagram_business_manage_insights',\n 'instagram_business_manage_comments'\n];\n\n// =============================================================================\n// API RESPONSE WRAPPER\n// =============================================================================\n\nexport interface ApiSuccessResponse<T = unknown> {\n success: true;\n data: T;\n meta?: {\n page?: number;\n limit?: number;\n total?: number;\n };\n}\n\nexport interface ApiErrorResponse {\n success: false;\n error: string;\n code?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport type ApiResponse<T = unknown> = ApiSuccessResponse<T> | ApiErrorResponse;\n\n// =============================================================================\n// AUTH TYPES\n// =============================================================================\n\nexport namespace Auth {\n export interface User {\n id: string;\n firebaseUid?: string;\n email?: string;\n phoneNumber?: string;\n username: string;\n picture?: string;\n emailVerified?: boolean;\n signInProvider?: string; // 'phone', 'google.com', 'password', 'apple.com'\n createdAt: string;\n updatedAt: string;\n }\n\n export interface SocialAccount {\n provider: string;\n accountName?: string;\n username?: string;\n profilePictureUrl?: string;\n connectedAt: string;\n }\n\n // Phone OTP Authentication (Twilio)\n export interface SendOTPRequest {\n phoneNumber: string;\n }\n\n export interface SendOTPResponse {\n message: string;\n expiresIn: number;\n }\n\n export interface VerifyOTPRequest {\n phoneNumber: string;\n code: string;\n }\n\n export interface VerifyOTPResponse {\n customToken: string;\n isNewUser: boolean;\n user: User;\n platformStatus: PlatformStatus;\n }\n\n // Firebase ID Token Login\n export interface LoginRequest {\n idToken: string;\n }\n\n export interface LoginResponse {\n user: User;\n platformStatus: PlatformStatus;\n idToken: string;\n }\n\n export interface CurrentUserResponse {\n user: User;\n socialAccounts: SocialAccount[];\n }\n\n export interface UpdateProfileRequest {\n username?: string;\n picture?: string;\n }\n\n export interface UpdateProfileResponse {\n user: User;\n }\n\n /**\n * Status for a single sync step\n */\n export interface SyncStepStatus {\n status: 'never_synced' | 'pending' | 'in_progress' | 'completed' | 'failed';\n lastSyncAt?: string | null;\n }\n\n /**\n * Sync status for a single platform\n */\n export interface PlatformSyncInfo {\n connected: boolean;\n /** Overall sync status (completed only if ALL steps done) */\n syncStatus?: 'never_synced' | 'pending' | 'in_progress' | 'completed' | 'failed';\n lastSyncAt?: string | null;\n /** True if any sync step incomplete - show loading/sync screen */\n needsInit?: boolean;\n /** Next step UI should perform */\n nextStep?: 'channel' | 'videos' | 'channel_analytics' | 'video_analytics' | null;\n /** Per-step status for UI progress display */\n steps?: {\n channel: SyncStepStatus;\n videos: SyncStepStatus;\n channelAnalytics: SyncStepStatus;\n videoAnalytics: SyncStepStatus;\n };\n }\n\n /**\n * Platform connection and sync status returned in login responses\n * Used by UI to decide navigation: onboarding vs loading screen vs dashboard\n */\n export interface PlatformStatus {\n hasConnectedPlatforms: boolean;\n connectedPlatforms: Array<'youtube' | 'instagram'>;\n youtube: PlatformSyncInfo;\n instagram: PlatformSyncInfo;\n }\n}\n\n// =============================================================================\n// SOCIAL TYPES\n// =============================================================================\n\nexport namespace Social {\n /**\n * Link YouTube account request\n * \n * Two options:\n * 1. serverAuthCode (recommended): Backend exchanges for tokens with proper expiry\n * 2. Direct tokens: Frontend provides accessToken (expiresAt defaults to 1 hour)\n */\n export interface LinkYouTubeRequest {\n idToken: string; // Google ID token for verification (always required)\n serverAuthCode?: string; // Google server auth code - backend exchanges for tokens (recommended)\n accessToken?: string; // Google access token (alternative to serverAuthCode)\n refreshToken?: string; // Google refresh token (only with accessToken flow)\n expiresAt?: number; // Token expiry timestamp in ms (only with accessToken flow)\n grantedScopes?: string; // Actual scopes user granted (space-separated)\n platform?: string; // 'ios' | 'android' | 'web'\n environment?: string; // 'development' | 'production'\n }\n\n export interface LinkInstagramRequest {\n accessToken: string;\n }\n\n export interface LinkAccountResponse {\n message: string;\n provider: string;\n accountName?: string;\n }\n\n export interface ConnectedAccount {\n provider: string;\n accountName?: string;\n username?: string;\n profilePictureUrl?: string;\n connectedAt: string;\n lastSyncAt?: string;\n }\n}\n\n// =============================================================================\n// YOUTUBE TYPES\n// =============================================================================\n\nexport namespace YouTube {\n /**\n * Response from GET /v1/platforms/youtube/init/status\n */\n export interface InitStatusResponse {\n needsInit: boolean;\n status: 'NEVER_SYNCED' | 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED' | 'never_synced';\n lastSyncAt: string | null;\n }\n\n /**\n * Response from POST /v1/platforms/youtube/init\n */\n export interface InitResponse {\n channel: {\n channelId: string;\n channelTitle: string;\n subscriberCount: number;\n videoCount: number;\n viewCount: number;\n thumbnailUrl: string | null;\n };\n syncStatus: {\n isFirstSync: boolean;\n lastSyncAt: string | null;\n };\n }\n\n /**\n * Response from GET /v1/platforms/youtube/videos/sync/status\n */\n export interface VideosSyncStatusResponse {\n needsSync: boolean;\n status: 'NEVER_SYNCED' | 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED' | 'never_synced';\n lastSyncAt: string | null;\n videoCount: number;\n oldestVideoDate: string | null;\n analyticsStartDate: string | null;\n }\n\n /**\n * Response from POST /v1/platforms/youtube/videos/sync\n */\n export interface VideosSyncResponse {\n videos: {\n total: number;\n synced: number;\n oldestVideoDate: string | null;\n };\n syncStatus: {\n isFirstSync: boolean;\n lastSyncAt: string;\n };\n }\n\n export interface Video {\n id: string;\n videoId: string;\n title: string;\n description?: string;\n thumbnailUrl?: string;\n publishedAt: string;\n duration?: number;\n viewCount?: number;\n likeCount?: number;\n commentCount?: number;\n engagementRate?: number;\n }\n\n export interface GetVideosParams {\n limit?: number;\n offset?: number;\n sortBy?: 'recent' | 'views' | 'engagement';\n }\n\n export interface GetVideosResponse {\n videos: Video[];\n total: number;\n hasMore: boolean;\n }\n\n export interface ChannelMetrics {\n subscriberCount: number;\n totalViews: number;\n totalVideos: number;\n averageViews: number;\n engagementRate: number;\n }\n\n export interface HealthScore {\n score: number;\n grade: 'A' | 'B' | 'C' | 'D' | 'F';\n metrics: {\n consistency: number;\n engagement: number;\n growth: number;\n quality: number;\n };\n }\n\n export interface GetHealthScoreParams {\n range?: string; // e.g., '7d', '30d', '90d'\n }\n\n // =========================================================================\n // UNIFIED SYNC STATUS\n // =========================================================================\n\n /**\n * Status of a single sync type\n */\n export interface SyncTypeStatus {\n /** Current sync status */\n status: 'never_synced' | 'pending' | 'in_progress' | 'completed' | 'failed';\n /** \n * When the backend last fetched data from YouTube API.\n * UI should store this value and compare on next call.\n * If lastSyncAt is newer than stored → refetch data from API\n */\n lastSyncAt: string | null;\n /** Last error message if sync failed */\n lastError: string | null;\n /** Number of consecutive failed attempts */\n failedAttempts: number;\n /** Whether data is older than the refresh interval (backend will auto-sync) */\n isStale: boolean;\n /** When the next automatic sync will run */\n nextSyncAt: string | null;\n /** Human-readable time until next sync (e.g., \"6 hours\") */\n timeUntilNextSync: string;\n /** Sync interval for this data type (e.g., \"12 hours\") */\n interval: string;\n }\n\n /**\n * Response from GET /v1/platforms/youtube/sync/status\n * \n * Use this endpoint to:\n * 1. Check if any sync is stale (anyStale) - backend will auto-sync\n * 2. Check if new data available (compare lastSyncAt with stored value)\n * 3. Show sync status in UI settings/debug screen\n * \n * @example\n * ```typescript\n * const status = await client.youtube.getUnifiedSyncStatus();\n * \n * // Check if backend has newer data than our cache\n * const storedLastSync = localStorage.get('videosLastSync');\n * if (status.data.syncs.videos.lastSyncAt > storedLastSync) {\n * // Backend synced since our last fetch, refetch videos\n * const videos = await client.youtube.getVideos();\n * localStorage.set('videosLastSync', status.data.syncs.videos.lastSyncAt);\n * }\n * \n * // Show user that refresh is happening\n * if (status.data.summary.anyInProgress) {\n * showRefreshIndicator();\n * }\n * ```\n */\n export interface UnifiedSyncStatusResponse {\n /** Whether YouTube account is connected */\n isConnected: boolean;\n /** YouTube channel ID */\n channelId: string | null;\n /** YouTube channel name */\n channelName: string | null;\n /** Total videos stored in database */\n totalVideos: number;\n \n /** Sync status for each data type */\n syncs: {\n /** Channel info (subscribers, video count) */\n channelInfo: SyncTypeStatus;\n /** Videos list */\n videos: SyncTypeStatus;\n /** Channel analytics (daily metrics, demographics) */\n channelAnalytics: SyncTypeStatus;\n /** Video analytics (per-video metrics) */\n videoAnalytics: SyncTypeStatus;\n };\n \n /** Summary flags for quick checks */\n summary: {\n /** All sync types completed at least once with data */\n allSynced: boolean;\n /** At least one sync type is older than its refresh interval */\n anyStale: boolean;\n /** At least one sync type failed */\n anyFailed: boolean;\n /** At least one sync is currently in progress */\n anyInProgress: boolean;\n };\n }\n\n // =========================================================================\n // ANALYTICS SYNC TYPES\n // =========================================================================\n\n /**\n * Response from GET /v1/platforms/youtube/analytics/channel/sync/status\n */\n export interface ChannelAnalyticsSyncStatusResponse {\n needsSync: boolean;\n status: 'NEVER_SYNCED' | 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED' | 'never_synced';\n dailyRecords: number;\n dateRange: {\n start: string;\n end: string;\n } | null;\n lastSyncAt: string | null;\n message: string;\n }\n\n /**\n * Response from POST /v1/platforms/youtube/analytics/channel/sync\n */\n export interface ChannelAnalyticsSyncResponse {\n syncedDays: number;\n hasRevenue: boolean;\n dateRange: {\n start: string;\n end: string;\n };\n demographics: number;\n trafficSources: number;\n countries: number;\n devices: number;\n syncStatus: 'COMPLETED' | 'FAILED';\n lastSyncAt: string;\n }\n\n /**\n * Response from GET /v1/platforms/youtube/analytics/videos/sync/status\n */\n export interface VideoAnalyticsSyncStatusResponse {\n needsSync: boolean;\n status: 'NEVER_SYNCED' | 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED';\n /** \n * Progress tracking - USE THESE FOR UI DISPLAY\n * These values reflect actual database state, not queue state\n */\n progress: {\n /** Number of videos with analytics saved to database */\n completed: number;\n /** Total number of videos to sync */\n total: number;\n /** Videos still needing sync */\n remaining: number;\n /** Progress percentage (0-100) */\n percent: number;\n };\n /** @deprecated Use progress.total instead */\n totalVideos: number;\n /** @deprecated Use progress.completed instead */\n videosWithAnalytics: number;\n /** @deprecated Use progress.remaining instead */\n videosNeedingSync: number;\n lastSyncAt: string | null;\n lastError: string | null;\n /** \n * Queue info (for debugging only, NOT for progress display)\n * Note: Queue counts reset and don't reflect true progress\n */\n queue: {\n /** Whether workers are actively processing */\n isProcessing: boolean;\n /** Jobs currently being processed */\n jobsActive: number;\n /** Jobs waiting in queue */\n jobsWaiting: number;\n /** Jobs that failed after retries */\n jobsFailed: number;\n };\n message: string;\n }\n\n /**\n * Response from POST /v1/platforms/youtube/analytics/videos/sync\n * \n * Returns immediately - sync runs in background via BullMQ queue.\n * Poll VideoAnalyticsSyncStatusResponse for progress.\n */\n export interface VideoAnalyticsSyncResponse {\n /** Status message */\n message: string;\n /** Current sync status - will be 'IN_PROGRESS' when sync starts */\n status: 'IN_PROGRESS' | 'COMPLETED' | 'FAILED';\n /** Total number of videos */\n totalVideos: number;\n /** Number of videos queued for sync (excludes already-synced) */\n queued: number;\n /** Number of videos skipped (already have analytics) */\n skipped: number;\n }\n\n /**\n * Daily analytics data for a single video\n * Stored in YouTubeVideoDaily collection\n */\n export interface VideoDailyAnalytics {\n videoId: string;\n date: string;\n views: number;\n likes: number;\n dislikes: number;\n comments: number;\n estimatedMinutesWatched: number;\n averageViewDuration: number;\n subscribersGained: number;\n subscribersLost: number;\n }\n}\n\n// =============================================================================\n// INSTAGRAM TYPES\n// =============================================================================\n\nexport namespace Instagram {\n export interface InitStatusResponse {\n needsInit: boolean;\n message: string;\n postCount?: number;\n accountId?: string;\n username?: string;\n }\n\n export interface Post {\n id: string;\n caption?: string;\n mediaType: 'IMAGE' | 'VIDEO' | 'CAROUSEL_ALBUM';\n mediaUrl: string;\n permalink: string;\n timestamp: string;\n likeCount?: number;\n commentsCount?: number;\n engagementRate?: number;\n }\n\n export interface GetPostsParams {\n limit?: number;\n offset?: number;\n }\n\n export interface GetPostsResponse {\n posts: Post[];\n total: number;\n hasMore: boolean;\n }\n\n export interface AccountMetrics {\n followersCount: number;\n followsCount: number;\n mediaCount: number;\n averageLikes: number;\n engagementRate: number;\n }\n}\n\n// =============================================================================\n// INSIGHTS TYPES\n// =============================================================================\n\nexport namespace Insights {\n export interface CrossPlatformMetrics {\n totalFollowers: number;\n totalEngagement: number;\n platforms: {\n youtube?: {\n subscribers: number;\n views: number;\n videos: number;\n };\n instagram?: {\n followers: number;\n posts: number;\n avgEngagement: number;\n };\n };\n }\n\n export interface TrendingContent {\n youtube?: YouTube.Video[];\n instagram?: Instagram.Post[];\n }\n}\n","/**\n * TrndUp API SDK\n * \n * Official TypeScript SDK for the TrndUp API.\n * Provides type-safe methods for all API endpoints with Firebase authentication.\n * \n * @example\n * ```typescript\n * import { TrndUpSDK, YOUTUBE_SCOPES } from '@trndup/sdk';\n * import auth from '@react-native-firebase/auth';\n * import { GoogleSignin } from '@react-native-google-signin/google-signin';\n * \n * // Configure Google Sign-In with YouTube scopes\n * GoogleSignin.configure({\n * webClientId: 'YOUR_WEB_CLIENT_ID',\n * offlineAccess: true,\n * scopes: YOUTUBE_SCOPES\n * });\n * \n * const sdk = new TrndUpSDK({\n * baseUrl: 'https://api.trndup.app',\n * getToken: async () => {\n * const user = auth().currentUser;\n * return user ? await user.getIdToken() : null;\n * },\n * onAuthFailure: () => {\n * // Handle auth failure - navigate to login\n * navigation.navigate('Login');\n * },\n * });\n * \n * // Type-safe API calls\n * const user = await sdk.auth.getCurrentUser();\n * const videos = await sdk.youtube.getVideos({ limit: 10 });\n * const healthScore = await sdk.youtube.getHealthScore({ range: '30d' });\n * ```\n */\n\nimport { TrndUpClient, TrndUpClientConfig } from './client';\nimport { AuthModule } from './modules/auth';\nimport { YouTubeModule } from './modules/youtube';\nimport { InstagramModule } from './modules/instagram';\nimport { SocialModule } from './modules/social';\nimport { InsightsModule } from './modules/insights';\n\nexport class TrndUpSDK extends TrndUpClient {\n public auth: AuthModule;\n public youtube: YouTubeModule;\n public instagram: InstagramModule;\n public social: SocialModule;\n public insights: InsightsModule;\n\n constructor(config: TrndUpClientConfig) {\n super(config);\n\n // Initialize all modules\n this.auth = new AuthModule(this);\n this.youtube = new YouTubeModule(this);\n this.instagram = new InstagramModule(this);\n this.social = new SocialModule(this);\n this.insights = new InsightsModule(this);\n }\n}\n\n// Re-export types and classes\nexport type { TrndUpClientConfig, RequestOptions } from './client';\nexport { TrndUpApiError, TrndUpNetworkError } from './client';\n\n// Re-export all type namespaces\nexport type { Auth, Social, YouTube, Instagram, Insights } from './types';\n\n// Re-export OAuth scopes for configuring Google Sign-In\nexport { YOUTUBE_SCOPES, INSTAGRAM_SCOPES } from './types';\n\n// Version\nexport const SDK_VERSION = '1.0.0';\n"]}
1
+ {"version":3,"sources":["../client.ts","../modules/auth.ts","../modules/youtube.ts","../modules/instagram.ts","../modules/social.ts","../modules/insights.ts","../types.ts","../index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * TrndUp SDK - Base Client\n * \n * Core HTTP client with Firebase authentication support,\n * error handling, and automatic retries.\n */\n\nimport type { ApiResponse, ApiErrorResponse, ApiSuccessResponse } from './types';\n\n// =============================================================================\n// CONFIGURATION\n// =============================================================================\n\nexport interface TrndUpClientConfig {\n /** Base URL for the API (e.g., 'https://api.trndup.app' or 'http://localhost:3000') */\n baseUrl: string;\n \n /** Function to get the current Firebase ID token */\n getToken: () => string | null | Promise<string | null>;\n \n /** Called when auth completely fails (user needs to re-login) */\n onAuthFailure?: () => void | Promise<void>;\n \n /** Called on any API error */\n onError?: (error: ApiErrorResponse, endpoint: string) => void;\n \n /** Custom headers to include in all requests */\n defaultHeaders?: Record<string, string>;\n \n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n \n /** Enable debug logging (default: false) */\n debug?: boolean;\n}\n\nexport interface RequestOptions {\n /** Skip authentication for this request */\n skipAuth?: boolean;\n \n /** Additional headers for this request */\n headers?: Record<string, string>;\n \n /** AbortController signal for cancellation */\n signal?: AbortSignal;\n \n /** Custom timeout for this request */\n timeout?: number;\n}\n\ninterface RequestConfig extends RequestOptions {\n method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n path: string;\n params?: Record<string, string | number | boolean | undefined>;\n body?: unknown;\n}\n\n// =============================================================================\n// ERROR CLASSES\n// =============================================================================\n\nexport class TrndUpApiError extends Error {\n constructor(\n public readonly response: ApiErrorResponse,\n public readonly status: number,\n public readonly endpoint: string\n ) {\n super(response.error);\n this.name = 'TrndUpApiError';\n }\n \n get code(): string | undefined {\n return this.response.code;\n }\n\n get metadata(): Record<string, unknown> | undefined {\n return this.response.metadata;\n }\n\n /** Check if error is due to authentication failure */\n isAuthError(): boolean {\n return this.status === 401 || this.code === 'AUTH_EXPIRED' || this.code === 'AUTH_INVALID';\n }\n\n /** Check if error is due to rate limiting */\n isRateLimitError(): boolean {\n return this.status === 429 || this.code === 'RATE_LIMITED';\n }\n}\n\nexport class TrndUpNetworkError extends Error {\n constructor(\n public readonly originalError: Error,\n public readonly endpoint: string\n ) {\n super(`Network error calling ${endpoint}: ${originalError.message}`);\n this.name = 'TrndUpNetworkError';\n }\n}\n\n// =============================================================================\n// BASE CLIENT\n// =============================================================================\n\nexport class TrndUpClient {\n private config: Required<Omit<TrndUpClientConfig, 'onAuthFailure' | 'onError' | 'defaultHeaders' | 'debug'>> & {\n onAuthFailure?: TrndUpClientConfig['onAuthFailure'];\n onError?: TrndUpClientConfig['onError'];\n defaultHeaders?: Record<string, string>;\n debug?: boolean;\n };\n\n // Module instances (imported by subclasses or external modules)\n public auth!: any;\n public youtube!: any;\n public instagram!: any;\n public social!: any;\n public insights!: any;\n\n constructor(config: TrndUpClientConfig) {\n this.config = {\n baseUrl: config.baseUrl.replace(/\\/$/, ''), // Remove trailing slash\n getToken: config.getToken,\n timeout: config.timeout ?? 30000,\n onAuthFailure: config.onAuthFailure,\n onError: config.onError,\n defaultHeaders: config.defaultHeaders,\n debug: config.debug ?? false,\n };\n }\n\n // =============================================================================\n // PUBLIC REQUEST METHODS\n // =============================================================================\n\n async get<T = unknown>(\n path: string,\n params?: Record<string, string | number | boolean | undefined>,\n options?: RequestOptions\n ): Promise<T> {\n return this.request<T>({ method: 'GET', path, params, ...options });\n }\n\n async post<T = unknown>(\n path: string,\n body?: unknown,\n options?: RequestOptions\n ): Promise<T> {\n return this.request<T>({ method: 'POST', path, body, ...options });\n }\n\n async put<T = unknown>(\n path: string,\n body?: unknown,\n options?: RequestOptions\n ): Promise<T> {\n return this.request<T>({ method: 'PUT', path, body, ...options });\n }\n\n async patch<T = unknown>(\n path: string,\n body?: unknown,\n options?: RequestOptions\n ): Promise<T> {\n return this.request<T>({ method: 'PATCH', path, body, ...options });\n }\n\n async delete<T = unknown>(\n path: string,\n options?: RequestOptions\n ): Promise<T> {\n return this.request<T>({ method: 'DELETE', path, ...options });\n }\n\n // =============================================================================\n // CORE REQUEST LOGIC\n // =============================================================================\n\n private async request<T>(config: RequestConfig): Promise<T> {\n const { method, path, params, body, skipAuth, headers, signal, timeout } = config;\n\n // Build URL\n const url = this.buildUrl(path, params);\n\n // Build headers\n const requestHeaders = await this.buildHeaders(skipAuth, headers);\n\n // Build request init\n const init: RequestInit = {\n method,\n headers: requestHeaders,\n signal,\n };\n\n if (body && method !== 'GET') {\n init.body = JSON.stringify(body);\n }\n\n // Add timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(\n () => controller.abort(),\n timeout ?? this.config.timeout\n );\n\n try {\n if (this.config.debug) {\n console.log(`[TrndUp SDK] ${method} ${url}`, { body, headers: requestHeaders });\n }\n\n const response = await fetch(url, {\n ...init,\n signal: signal ?? controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n return await this.handleResponse<T>(response, path);\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof TrndUpApiError) {\n throw error;\n }\n\n // Network error\n const networkError = new TrndUpNetworkError(\n error as Error,\n path\n );\n\n if (this.config.debug) {\n console.error('[TrndUp SDK] Network error:', networkError);\n }\n\n throw networkError;\n }\n }\n\n private buildUrl(path: string, params?: Record<string, string | number | boolean | undefined>): string {\n const url = new URL(`${this.config.baseUrl}${path}`);\n\n if (params) {\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n url.searchParams.append(key, String(value));\n }\n });\n }\n\n return url.toString();\n }\n\n private async buildHeaders(\n skipAuth?: boolean,\n additionalHeaders?: Record<string, string>\n ): Promise<Record<string, string>> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...this.config.defaultHeaders,\n ...additionalHeaders,\n };\n\n // Add Firebase ID token for authentication\n if (!skipAuth) {\n const token = await this.config.getToken();\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n }\n\n return headers;\n }\n\n private async handleResponse<T>(response: Response, endpoint: string): Promise<T> {\n const contentType = response.headers.get('content-type');\n const isJson = contentType?.includes('application/json');\n\n if (!response.ok) {\n // Try to parse error response\n let errorResponse: ApiErrorResponse;\n\n if (isJson) {\n try {\n const parsed = await response.json();\n errorResponse = parsed as ApiErrorResponse;\n } catch {\n errorResponse = {\n success: false,\n error: response.statusText || 'Unknown error',\n code: `HTTP_${response.status}`,\n };\n }\n } else {\n const text = await response.text();\n errorResponse = {\n success: false,\n error: text || response.statusText || 'Unknown error',\n code: `HTTP_${response.status}`,\n };\n }\n\n const apiError = new TrndUpApiError(errorResponse, response.status, endpoint);\n\n // Call error handler\n if (this.config.onError) {\n this.config.onError(errorResponse, endpoint);\n }\n\n // Call auth failure handler for 401 errors\n if (apiError.isAuthError() && this.config.onAuthFailure) {\n await this.config.onAuthFailure();\n }\n\n if (this.config.debug) {\n console.error('[TrndUp SDK] API error:', apiError);\n }\n\n throw apiError;\n }\n\n // Parse success response\n if (isJson) {\n const parsed = await response.json();\n const jsonResponse = parsed as ApiResponse<T>;\n\n if (jsonResponse.success === false) {\n // This shouldn't happen with 2xx status, but handle it\n throw new TrndUpApiError(jsonResponse, response.status, endpoint);\n }\n\n return (jsonResponse as ApiSuccessResponse<T>).data;\n }\n\n // Non-JSON response\n return (await response.text()) as unknown as T;\n }\n}\n","/**\n * TrndUp SDK - Auth Module\n * \n * Firebase authentication methods with Twilio phone OTP\n */\n\nimport type { TrndUpClient } from '../client';\nimport type { Auth } from '../types';\n\nexport class AuthModule {\n constructor(private client: TrndUpClient) {}\n\n // =========================================================================\n // PHONE OTP AUTHENTICATION (via Twilio - no reCAPTCHA needed)\n // =========================================================================\n\n /**\n * Send OTP verification code to phone number\n * POST /auth/phone/send-otp\n * \n * @param phoneNumber - Phone number (E.164 format recommended, e.g., +14155551234)\n */\n async sendOTP(phoneNumber: string): Promise<Auth.SendOTPResponse> {\n return this.client.post<Auth.SendOTPResponse>(\n '/auth/phone/send-otp', \n { phoneNumber }, \n { skipAuth: true }\n );\n }\n\n /**\n * Verify OTP and get Firebase custom token\n * POST /auth/phone/verify-otp\n * \n * After calling this, use Firebase signInWithCustomToken(customToken)\n * to complete authentication on the client.\n * \n * @param phoneNumber - Phone number that received the OTP\n * @param code - 6-digit verification code\n */\n async verifyOTP(phoneNumber: string, code: string): Promise<Auth.VerifyOTPResponse> {\n return this.client.post<Auth.VerifyOTPResponse>(\n '/auth/phone/verify-otp',\n { phoneNumber, code },\n { skipAuth: true }\n );\n }\n\n // =========================================================================\n // FIREBASE ID TOKEN AUTHENTICATION\n // =========================================================================\n\n /**\n * Login with Firebase ID token (for Google, Apple, Email/Password)\n * POST /auth/login\n */\n async login(idToken: string): Promise<Auth.LoginResponse> {\n return this.client.post<Auth.LoginResponse>('/auth/login', { idToken }, { skipAuth: true });\n }\n\n /**\n * Get current authenticated user's profile\n * GET /auth/me\n */\n async getCurrentUser(): Promise<Auth.CurrentUserResponse> {\n return this.client.get<Auth.CurrentUserResponse>('/auth/me');\n }\n\n /**\n * Update user profile\n * PATCH /auth/me\n */\n async updateProfile(data: Auth.UpdateProfileRequest): Promise<Auth.UpdateProfileResponse> {\n return this.client.patch<Auth.UpdateProfileResponse>('/auth/me', data);\n }\n\n /**\n * Logout (acknowledge logout on backend)\n * POST /auth/logout\n */\n async logout(): Promise<{ message: string }> {\n return this.client.post<{ message: string }>('/auth/logout');\n }\n\n /**\n * Check auth service status\n * GET /auth/status\n */\n async getStatus(): Promise<{ success: boolean; message: string }> {\n return this.client.get('/auth/status', undefined, { skipAuth: true });\n }\n\n /**\n * Get user's platform connection status\n * GET /user/platforms/status\n */\n async getPlatformStatus(): Promise<Auth.PlatformStatus> {\n return this.client.get<Auth.PlatformStatus>('/user/platforms/status');\n }\n}\n","/**\n * TrndUp SDK - YouTube Module\n * \n * YouTube analytics and data methods\n */\n\nimport type { TrndUpClient } from '../client';\nimport type { YouTube } from '../types';\n\nexport class YouTubeModule {\n constructor(private client: TrndUpClient) {}\n\n /**\n * Get YouTube initialization status (channel sync)\n * Check if user needs to init or has already synced channel\n * \n * GET /v1/platforms/youtube/init/status\n */\n async getInitStatus(): Promise<YouTube.InitStatusResponse> {\n return this.client.get<YouTube.InitStatusResponse>('/v1/platforms/youtube/init/status');\n }\n\n /**\n * Initialize YouTube channel sync\n * Fetches channel info and stores in database\n * \n * POST /v1/platforms/youtube/init\n */\n async initialize(): Promise<YouTube.InitResponse> {\n return this.client.post<YouTube.InitResponse>('/v1/platforms/youtube/init');\n }\n\n /**\n * Get videos sync status\n * Check if videos need to be synced\n * \n * GET /v1/platforms/youtube/videos/sync/status\n */\n async getVideosSyncStatus(): Promise<YouTube.VideosSyncStatusResponse> {\n return this.client.get<YouTube.VideosSyncStatusResponse>('/v1/platforms/youtube/videos/sync/status');\n }\n\n /**\n * Sync all videos from YouTube\n * Fetches all videos and stores in database\n * Returns video counts and oldest video date (for analytics start date)\n * \n * POST /v1/platforms/youtube/videos/sync\n */\n async syncVideos(): Promise<YouTube.VideosSyncResponse> {\n return this.client.post<YouTube.VideosSyncResponse>('/v1/platforms/youtube/videos/sync');\n }\n\n /**\n * Get YouTube videos from database\n * GET /v1/platforms/youtube/videos\n */\n async getVideos(params?: YouTube.GetVideosParams): Promise<YouTube.GetVideosResponse> {\n return this.client.get<YouTube.GetVideosResponse>(\n '/v1/platforms/youtube/videos', \n params as Record<string, string | number | boolean | undefined>\n );\n }\n\n /**\n * Get specific video by ID\n * GET /v1/platforms/youtube/videos/:videoId\n */\n async getVideo(videoId: string): Promise<YouTube.Video> {\n return this.client.get<YouTube.Video>(`/v1/platforms/youtube/videos/${videoId}`);\n }\n\n /**\n * Get channel metrics\n * GET /v1/platforms/youtube/channel/metrics\n */\n async getChannelMetrics(): Promise<YouTube.ChannelMetrics> {\n return this.client.get<YouTube.ChannelMetrics>('/v1/platforms/youtube/channel/metrics');\n }\n\n /**\n * Get channel health score\n * GET /v1/platforms/youtube/health\n */\n async getHealthScore(params?: YouTube.GetHealthScoreParams): Promise<YouTube.HealthScore> {\n return this.client.get<YouTube.HealthScore>(\n '/v1/platforms/youtube/health', \n params as Record<string, string | number | boolean | undefined>\n );\n }\n\n /**\n * Refresh YouTube data\n * POST /v1/platforms/youtube/refresh\n */\n async refresh(): Promise<{ message: string; jobId?: string }> {\n return this.client.post<{ message: string; jobId?: string }>('/v1/platforms/youtube/refresh');\n }\n\n // =========================================================================\n // UNIFIED SYNC STATUS\n // =========================================================================\n\n /**\n * Get unified sync status for all YouTube data types.\n * \n * Use this for:\n * - Checking if UI cache needs refresh (compare lastSyncAt)\n * - Showing sync status in settings/debug screens\n * - Detecting if background sync is running\n * \n * GET /v1/platforms/youtube/sync/status\n * \n * @example\n * ```typescript\n * const status = await client.youtube.getUnifiedSyncStatus();\n * \n * // Check if backend synced since our last fetch\n * const storedLastSync = await AsyncStorage.getItem('videosLastSync');\n * if (status.syncs.videos.lastSyncAt > storedLastSync) {\n * const videos = await client.youtube.getVideos();\n * await AsyncStorage.setItem('videosLastSync', status.syncs.videos.lastSyncAt);\n * }\n * ```\n */\n async getUnifiedSyncStatus(): Promise<YouTube.UnifiedSyncStatusResponse> {\n return this.client.get<YouTube.UnifiedSyncStatusResponse>(\n '/v1/platforms/youtube/sync/status'\n );\n }\n\n // =========================================================================\n // CHANNEL ANALYTICS SYNC\n // =========================================================================\n\n /**\n * Get channel analytics sync status\n * GET /v1/platforms/youtube/analytics/channel/sync/status\n */\n async getChannelAnalyticsSyncStatus(): Promise<YouTube.ChannelAnalyticsSyncStatusResponse> {\n return this.client.get<YouTube.ChannelAnalyticsSyncStatusResponse>(\n '/v1/platforms/youtube/analytics/channel/sync/status'\n );\n }\n\n /**\n * Sync channel analytics (daily + weekly aggregates)\n * Fetches from YouTube Analytics API and stores in database\n * Uses oldest video date as start date\n * \n * POST /v1/platforms/youtube/analytics/channel/sync\n */\n async syncChannelAnalytics(): Promise<YouTube.ChannelAnalyticsSyncResponse> {\n return this.client.post<YouTube.ChannelAnalyticsSyncResponse>(\n '/v1/platforms/youtube/analytics/channel/sync'\n );\n }\n\n // =========================================================================\n // VIDEO ANALYTICS SYNC\n // =========================================================================\n\n /**\n * Get video analytics sync status\n * GET /v1/platforms/youtube/analytics/videos/sync/status\n */\n async getVideoAnalyticsSyncStatus(): Promise<YouTube.VideoAnalyticsSyncStatusResponse> {\n return this.client.get<YouTube.VideoAnalyticsSyncStatusResponse>(\n '/v1/platforms/youtube/analytics/videos/sync/status'\n );\n }\n\n /**\n * Sync analytics for all videos\n * Fetches from YouTube Analytics API and updates video records\n * \n * POST /v1/platforms/youtube/analytics/videos/sync\n */\n async syncVideoAnalytics(): Promise<YouTube.VideoAnalyticsSyncResponse> {\n return this.client.post<YouTube.VideoAnalyticsSyncResponse>(\n '/v1/platforms/youtube/analytics/videos/sync'\n );\n }\n}\n","/**\n * TrndUp SDK - Instagram Module\n * \n * Instagram analytics and data methods\n */\n\nimport type { TrndUpClient } from '../client';\nimport type { Instagram } from '../types';\n\nexport class InstagramModule {\n constructor(private client: TrndUpClient) {}\n\n /**\n * Get Instagram initialization status\n * GET /v1/platforms/instagram/init/status\n */\n async getInitStatus(): Promise<Instagram.InitStatusResponse> {\n return this.client.get<Instagram.InitStatusResponse>('/v1/platforms/instagram/init/status');\n }\n\n /**\n * Initialize Instagram data sync\n * POST /v1/platforms/instagram/init\n */\n async initialize(): Promise<{ message: string }> {\n return this.client.post<{ message: string }>('/v1/platforms/instagram/init');\n }\n\n /**\n * Get Instagram posts\n * GET /v1/platforms/instagram/posts\n */\n async getPosts(params?: Instagram.GetPostsParams): Promise<Instagram.GetPostsResponse> {\n return this.client.get<Instagram.GetPostsResponse>(\n '/v1/platforms/instagram/posts', \n params as Record<string, string | number | boolean | undefined>\n );\n }\n\n /**\n * Get specific post by ID\n * GET /v1/platforms/instagram/posts/:postId\n */\n async getPost(postId: string): Promise<Instagram.Post> {\n return this.client.get<Instagram.Post>(`/v1/platforms/instagram/posts/${postId}`);\n }\n\n /**\n * Get account metrics\n * GET /v1/platforms/instagram/account/metrics\n */\n async getAccountMetrics(): Promise<Instagram.AccountMetrics> {\n return this.client.get<Instagram.AccountMetrics>('/v1/platforms/instagram/account/metrics');\n }\n\n /**\n * Refresh Instagram data\n * POST /v1/platforms/instagram/refresh\n */\n async refresh(): Promise<{ message: string }> {\n return this.client.post<{ message: string }>('/v1/platforms/instagram/refresh');\n }\n}\n","/**\n * TrndUp SDK - Social Module\n * \n * Social account linking methods\n */\n\nimport type { TrndUpClient } from '../client';\nimport type { Social } from '../types';\n\nexport class SocialModule {\n constructor(private client: TrndUpClient) {}\n\n /**\n * Link YouTube account\n * POST /social/link/youtube\n */\n async linkYouTube(data: Social.LinkYouTubeRequest): Promise<Social.LinkAccountResponse> {\n return this.client.post<Social.LinkAccountResponse>('/social/link/youtube', data);\n }\n\n /**\n * Link Instagram account\n * POST /social/link/instagram\n */\n async linkInstagram(data: Social.LinkInstagramRequest): Promise<Social.LinkAccountResponse> {\n return this.client.post<Social.LinkAccountResponse>('/social/link/instagram', data);\n }\n\n /**\n * Get all connected social accounts\n * GET /social/accounts\n */\n async getConnectedAccounts(): Promise<Social.ConnectedAccount[]> {\n return this.client.get<Social.ConnectedAccount[]>('/social/accounts');\n }\n\n /**\n * Unlink a social account\n * DELETE /social/unlink/:provider\n */\n async unlinkAccount(provider: string): Promise<{ message: string }> {\n return this.client.delete<{ message: string }>(`/social/unlink/${provider}`);\n }\n}\n","/**\n * TrndUp SDK - Insights Module\n * \n * Cross-platform insights and analytics\n */\n\nimport type { TrndUpClient } from '../client';\nimport type { Insights } from '../types';\n\nexport class InsightsModule {\n constructor(private client: TrndUpClient) {}\n\n /**\n * Get cross-platform metrics\n * GET /v1/insights/overview\n */\n async getOverview(): Promise<Insights.CrossPlatformMetrics> {\n return this.client.get<Insights.CrossPlatformMetrics>('/v1/insights/overview');\n }\n\n /**\n * Get trending content across all platforms\n * GET /v1/insights/trending\n */\n async getTrending(): Promise<Insights.TrendingContent> {\n return this.client.get<Insights.TrendingContent>('/v1/insights/trending');\n }\n\n // =========================================================================\n // YOUTUBE MOMENTUM\n // =========================================================================\n\n /**\n * Get YouTube channel momentum status\n * \n * Returns a 0-100 score indicating channel momentum:\n * - 50 = Steady (no change week over week)\n * - 0 = Severe decline\n * - 100 = Explosive growth\n * \n * Results are cached for 12 hours. Use `refreshYouTubeMomentum()` to force recalculation.\n * \n * @example\n * ```typescript\n * const momentum = await client.insights.getYouTubeMomentum();\n * \n * console.log(`Score: ${momentum.score}/100`);\n * console.log(`Status: ${momentum.status}`); // 'surging' | 'rising' | 'steady' | 'cooling' | 'dropping'\n * console.log(`Trend: ${momentum.trend}`); // 'accelerating' | 'holding' | 'decelerating'\n * console.log(momentum.insight); // Human-readable summary\n * \n * // Show breakdown\n * console.log(`Views: ${momentum.breakdown.views.change}%`);\n * console.log(`Engagement: ${momentum.breakdown.engagement.change}%`);\n * ```\n * \n * GET /v1/insights/youtube/momentum\n */\n async getYouTubeMomentum(): Promise<Insights.MomentumStatusResponse> {\n return this.client.get<Insights.MomentumStatusResponse>('/v1/insights/youtube/momentum');\n }\n\n /**\n * Get YouTube momentum history for graphing\n * \n * Returns historical momentum scores, oldest first.\n * Use this to plot momentum trends over time.\n * \n * @param params - Query parameters\n * @param params.limit - Max data points (default: 30, max: 90)\n * \n * @example\n * ```typescript\n * const { history } = await client.insights.getYouTubeMomentumHistory({ limit: 30 });\n * \n * // Plot on a chart\n * const chartData = history.map(point => ({\n * x: new Date(point.date),\n * y: point.score,\n * label: point.status\n * }));\n * ```\n * \n * GET /v1/insights/youtube/momentum/history\n */\n async getYouTubeMomentumHistory(\n params?: Insights.GetMomentumHistoryParams\n ): Promise<Insights.MomentumHistoryResponse> {\n const query = params?.limit ? `?limit=${params.limit}` : '';\n return this.client.get<Insights.MomentumHistoryResponse>(\n `/v1/insights/youtube/momentum/history${query}`\n );\n }\n\n}\n","/**\n * TrndUp SDK Types\n * \n * Type definitions for the TrndUp API SDK\n */\n\n// =============================================================================\n// OAUTH SCOPES\n// =============================================================================\n\n/**\n * Required OAuth scopes for YouTube account linking\n * Use these when configuring Google Sign-In in your mobile app\n * \n * @example\n * ```typescript\n * import { GoogleSignin } from '@react-native-google-signin/google-signin';\n * import { YOUTUBE_SCOPES } from '@dracoonghost/trndup-sdk';\n * \n * GoogleSignin.configure({\n * webClientId: 'YOUR_WEB_CLIENT_ID',\n * offlineAccess: true,\n * scopes: YOUTUBE_SCOPES\n * });\n * ```\n */\nexport const YOUTUBE_SCOPES = [\n 'openid',\n 'https://www.googleapis.com/auth/userinfo.profile',\n 'https://www.googleapis.com/auth/userinfo.email',\n 'https://www.googleapis.com/auth/youtube.readonly',\n 'https://www.googleapis.com/auth/yt-analytics.readonly',\n 'https://www.googleapis.com/auth/yt-analytics-monetary.readonly',\n 'https://www.googleapis.com/auth/youtube.force-ssl',\n 'https://www.googleapis.com/auth/youtubepartner'\n];\n\n/**\n * Required OAuth scopes for Instagram account linking\n */\nexport const INSTAGRAM_SCOPES = [\n 'instagram_business_basic',\n 'instagram_business_manage_insights',\n 'instagram_business_manage_comments'\n];\n\n// =============================================================================\n// API RESPONSE WRAPPER\n// =============================================================================\n\nexport interface ApiSuccessResponse<T = unknown> {\n success: true;\n data: T;\n meta?: {\n page?: number;\n limit?: number;\n total?: number;\n };\n}\n\nexport interface ApiErrorResponse {\n success: false;\n error: string;\n code?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport type ApiResponse<T = unknown> = ApiSuccessResponse<T> | ApiErrorResponse;\n\n// =============================================================================\n// AUTH TYPES\n// =============================================================================\n\nexport namespace Auth {\n export interface User {\n id: string;\n firebaseUid?: string;\n email?: string;\n phoneNumber?: string;\n username: string;\n picture?: string;\n emailVerified?: boolean;\n signInProvider?: string; // 'phone', 'google.com', 'password', 'apple.com'\n createdAt: string;\n updatedAt: string;\n }\n\n export interface SocialAccount {\n provider: string;\n accountName?: string;\n username?: string;\n profilePictureUrl?: string;\n connectedAt: string;\n }\n\n // Phone OTP Authentication (Twilio)\n export interface SendOTPRequest {\n phoneNumber: string;\n }\n\n export interface SendOTPResponse {\n message: string;\n expiresIn: number;\n }\n\n export interface VerifyOTPRequest {\n phoneNumber: string;\n code: string;\n }\n\n export interface VerifyOTPResponse {\n customToken: string;\n isNewUser: boolean;\n user: User;\n platformStatus: PlatformStatus;\n }\n\n // Firebase ID Token Login\n export interface LoginRequest {\n idToken: string;\n }\n\n export interface LoginResponse {\n user: User;\n platformStatus: PlatformStatus;\n idToken: string;\n }\n\n export interface CurrentUserResponse {\n user: User;\n socialAccounts: SocialAccount[];\n }\n\n export interface UpdateProfileRequest {\n username?: string;\n picture?: string;\n }\n\n export interface UpdateProfileResponse {\n user: User;\n }\n\n /**\n * Status for a single sync step\n */\n export interface SyncStepStatus {\n status: 'never_synced' | 'pending' | 'in_progress' | 'completed' | 'failed';\n lastSyncAt?: string | null;\n }\n\n /**\n * Sync status for a single platform\n */\n export interface PlatformSyncInfo {\n connected: boolean;\n /** Overall sync status (completed only if ALL steps done) */\n syncStatus?: 'never_synced' | 'pending' | 'in_progress' | 'completed' | 'failed';\n lastSyncAt?: string | null;\n /** True if any sync step incomplete - show loading/sync screen */\n needsInit?: boolean;\n /** Next step UI should perform */\n nextStep?: 'channel' | 'videos' | 'channel_analytics' | 'video_analytics' | null;\n /** Per-step status for UI progress display */\n steps?: {\n channel: SyncStepStatus;\n videos: SyncStepStatus;\n channelAnalytics: SyncStepStatus;\n videoAnalytics: SyncStepStatus;\n };\n }\n\n /**\n * Platform connection and sync status returned in login responses\n * Used by UI to decide navigation: onboarding vs loading screen vs dashboard\n */\n export interface PlatformStatus {\n hasConnectedPlatforms: boolean;\n connectedPlatforms: Array<'youtube' | 'instagram'>;\n youtube: PlatformSyncInfo;\n instagram: PlatformSyncInfo;\n }\n}\n\n// =============================================================================\n// SOCIAL TYPES\n// =============================================================================\n\nexport namespace Social {\n /**\n * Link YouTube account request\n * \n * Two options:\n * 1. serverAuthCode (recommended): Backend exchanges for tokens with proper expiry\n * 2. Direct tokens: Frontend provides accessToken (expiresAt defaults to 1 hour)\n */\n export interface LinkYouTubeRequest {\n idToken: string; // Google ID token for verification (always required)\n serverAuthCode?: string; // Google server auth code - backend exchanges for tokens (recommended)\n accessToken?: string; // Google access token (alternative to serverAuthCode)\n refreshToken?: string; // Google refresh token (only with accessToken flow)\n expiresAt?: number; // Token expiry timestamp in ms (only with accessToken flow)\n grantedScopes?: string; // Actual scopes user granted (space-separated)\n platform?: string; // 'ios' | 'android' | 'web'\n environment?: string; // 'development' | 'production'\n }\n\n export interface LinkInstagramRequest {\n accessToken: string;\n }\n\n export interface LinkAccountResponse {\n message: string;\n provider: string;\n accountName?: string;\n }\n\n export interface ConnectedAccount {\n provider: string;\n accountName?: string;\n username?: string;\n profilePictureUrl?: string;\n connectedAt: string;\n lastSyncAt?: string;\n }\n}\n\n// =============================================================================\n// YOUTUBE TYPES\n// =============================================================================\n\nexport namespace YouTube {\n /**\n * Response from GET /v1/platforms/youtube/init/status\n */\n export interface InitStatusResponse {\n needsInit: boolean;\n status: 'NEVER_SYNCED' | 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED' | 'never_synced';\n lastSyncAt: string | null;\n }\n\n /**\n * Response from POST /v1/platforms/youtube/init\n */\n export interface InitResponse {\n channel: {\n channelId: string;\n channelTitle: string;\n subscriberCount: number;\n videoCount: number;\n viewCount: number;\n thumbnailUrl: string | null;\n };\n syncStatus: {\n isFirstSync: boolean;\n lastSyncAt: string | null;\n };\n }\n\n /**\n * Response from GET /v1/platforms/youtube/videos/sync/status\n */\n export interface VideosSyncStatusResponse {\n needsSync: boolean;\n status: 'NEVER_SYNCED' | 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED' | 'never_synced';\n lastSyncAt: string | null;\n videoCount: number;\n oldestVideoDate: string | null;\n analyticsStartDate: string | null;\n }\n\n /**\n * Response from POST /v1/platforms/youtube/videos/sync\n */\n export interface VideosSyncResponse {\n videos: {\n total: number;\n synced: number;\n oldestVideoDate: string | null;\n };\n syncStatus: {\n isFirstSync: boolean;\n lastSyncAt: string;\n };\n }\n\n export interface Video {\n id: string;\n videoId: string;\n title: string;\n description?: string;\n thumbnailUrl?: string;\n publishedAt: string;\n duration?: number;\n viewCount?: number;\n likeCount?: number;\n commentCount?: number;\n engagementRate?: number;\n }\n\n export interface GetVideosParams {\n limit?: number;\n offset?: number;\n sortBy?: 'recent' | 'views' | 'engagement';\n }\n\n export interface GetVideosResponse {\n videos: Video[];\n total: number;\n hasMore: boolean;\n }\n\n export interface ChannelMetrics {\n subscriberCount: number;\n totalViews: number;\n totalVideos: number;\n averageViews: number;\n engagementRate: number;\n }\n\n export interface HealthScore {\n score: number;\n grade: 'A' | 'B' | 'C' | 'D' | 'F';\n metrics: {\n consistency: number;\n engagement: number;\n growth: number;\n quality: number;\n };\n }\n\n export interface GetHealthScoreParams {\n range?: string; // e.g., '7d', '30d', '90d'\n }\n\n // =========================================================================\n // UNIFIED SYNC STATUS\n // =========================================================================\n\n /**\n * Status of a single sync type\n */\n export interface SyncTypeStatus {\n /** Current sync status */\n status: 'never_synced' | 'pending' | 'in_progress' | 'completed' | 'failed';\n /** \n * When the backend last fetched data from YouTube API.\n * UI should store this value and compare on next call.\n * If lastSyncAt is newer than stored → refetch data from API\n */\n lastSyncAt: string | null;\n /** Last error message if sync failed */\n lastError: string | null;\n /** Number of consecutive failed attempts */\n failedAttempts: number;\n /** Whether data is older than the refresh interval (backend will auto-sync) */\n isStale: boolean;\n /** When the next automatic sync will run */\n nextSyncAt: string | null;\n /** Human-readable time until next sync (e.g., \"6 hours\") */\n timeUntilNextSync: string;\n /** Sync interval for this data type (e.g., \"12 hours\") */\n interval: string;\n }\n\n /**\n * Response from GET /v1/platforms/youtube/sync/status\n * \n * Use this endpoint to:\n * 1. Check if any sync is stale (anyStale) - backend will auto-sync\n * 2. Check if new data available (compare lastSyncAt with stored value)\n * 3. Show sync status in UI settings/debug screen\n * \n * @example\n * ```typescript\n * const status = await client.youtube.getUnifiedSyncStatus();\n * \n * // Check if backend has newer data than our cache\n * const storedLastSync = localStorage.get('videosLastSync');\n * if (status.data.syncs.videos.lastSyncAt > storedLastSync) {\n * // Backend synced since our last fetch, refetch videos\n * const videos = await client.youtube.getVideos();\n * localStorage.set('videosLastSync', status.data.syncs.videos.lastSyncAt);\n * }\n * ```\n */\n export interface UnifiedSyncStatusResponse {\n /** Whether YouTube account is connected */\n isConnected: boolean;\n /** YouTube channel ID */\n channelId: string | null;\n /** YouTube channel name */\n channelName: string | null;\n /** Total videos stored in database */\n totalVideos: number;\n \n /** Sync status for each data type */\n syncs: {\n /** Channel info (subscribers, video count) */\n channelInfo: SyncTypeStatus;\n /** Videos list */\n videos: SyncTypeStatus;\n /** Channel analytics (daily metrics, demographics) */\n channelAnalytics: SyncTypeStatus;\n /** Video analytics (per-video metrics) */\n videoAnalytics: SyncTypeStatus;\n };\n }\n\n // =========================================================================\n // ANALYTICS SYNC TYPES\n // =========================================================================\n\n /**\n * Response from GET /v1/platforms/youtube/analytics/channel/sync/status\n */\n export interface ChannelAnalyticsSyncStatusResponse {\n needsSync: boolean;\n status: 'NEVER_SYNCED' | 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED' | 'never_synced';\n dailyRecords: number;\n dateRange: {\n start: string;\n end: string;\n } | null;\n lastSyncAt: string | null;\n message: string;\n }\n\n /**\n * Response from POST /v1/platforms/youtube/analytics/channel/sync\n */\n export interface ChannelAnalyticsSyncResponse {\n syncedDays: number;\n hasRevenue: boolean;\n dateRange: {\n start: string;\n end: string;\n };\n demographics: number;\n trafficSources: number;\n countries: number;\n devices: number;\n syncStatus: 'COMPLETED' | 'FAILED';\n lastSyncAt: string;\n }\n\n /**\n * Response from GET /v1/platforms/youtube/analytics/videos/sync/status\n */\n export interface VideoAnalyticsSyncStatusResponse {\n needsSync: boolean;\n status: 'NEVER_SYNCED' | 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED';\n /** \n * Progress tracking - USE THESE FOR UI DISPLAY\n * These values reflect actual database state, not queue state\n */\n progress: {\n /** Number of videos with analytics saved to database */\n completed: number;\n /** Total number of videos to sync */\n total: number;\n /** Videos still needing sync */\n remaining: number;\n /** Progress percentage (0-100) */\n percent: number;\n };\n /** @deprecated Use progress.total instead */\n totalVideos: number;\n /** @deprecated Use progress.completed instead */\n videosWithAnalytics: number;\n /** @deprecated Use progress.remaining instead */\n videosNeedingSync: number;\n lastSyncAt: string | null;\n lastError: string | null;\n /** \n * Queue info (for debugging only, NOT for progress display)\n * Note: Queue counts reset and don't reflect true progress\n */\n queue: {\n /** Whether workers are actively processing */\n isProcessing: boolean;\n /** Jobs currently being processed */\n jobsActive: number;\n /** Jobs waiting in queue */\n jobsWaiting: number;\n /** Jobs that failed after retries */\n jobsFailed: number;\n };\n message: string;\n }\n\n /**\n * Response from POST /v1/platforms/youtube/analytics/videos/sync\n * \n * Returns immediately - sync runs in background via BullMQ queue.\n * Poll VideoAnalyticsSyncStatusResponse for progress.\n */\n export interface VideoAnalyticsSyncResponse {\n /** Status message */\n message: string;\n /** Current sync status - will be 'IN_PROGRESS' when sync starts */\n status: 'IN_PROGRESS' | 'COMPLETED' | 'FAILED';\n /** Total number of videos */\n totalVideos: number;\n /** Number of videos queued for sync (excludes already-synced) */\n queued: number;\n /** Number of videos skipped (already have analytics) */\n skipped: number;\n }\n\n /**\n * Daily analytics data for a single video\n * Stored in YouTubeVideoDaily collection\n */\n export interface VideoDailyAnalytics {\n videoId: string;\n date: string;\n views: number;\n likes: number;\n dislikes: number;\n comments: number;\n estimatedMinutesWatched: number;\n averageViewDuration: number;\n subscribersGained: number;\n subscribersLost: number;\n }\n}\n\n// =============================================================================\n// INSTAGRAM TYPES\n// =============================================================================\n\nexport namespace Instagram {\n export interface InitStatusResponse {\n needsInit: boolean;\n message: string;\n postCount?: number;\n accountId?: string;\n username?: string;\n }\n\n export interface Post {\n id: string;\n caption?: string;\n mediaType: 'IMAGE' | 'VIDEO' | 'CAROUSEL_ALBUM';\n mediaUrl: string;\n permalink: string;\n timestamp: string;\n likeCount?: number;\n commentsCount?: number;\n engagementRate?: number;\n }\n\n export interface GetPostsParams {\n limit?: number;\n offset?: number;\n }\n\n export interface GetPostsResponse {\n posts: Post[];\n total: number;\n hasMore: boolean;\n }\n\n export interface AccountMetrics {\n followersCount: number;\n followsCount: number;\n mediaCount: number;\n averageLikes: number;\n engagementRate: number;\n }\n}\n\n// =============================================================================\n// INSIGHTS TYPES\n// =============================================================================\n\nexport namespace Insights {\n export interface CrossPlatformMetrics {\n totalFollowers: number;\n totalEngagement: number;\n platforms: {\n youtube?: {\n subscribers: number;\n views: number;\n videos: number;\n };\n instagram?: {\n followers: number;\n posts: number;\n avgEngagement: number;\n };\n };\n }\n\n export interface TrendingContent {\n youtube?: YouTube.Video[];\n instagram?: Instagram.Post[];\n }\n\n // =========================================================================\n // MOMENTUM STATUS\n // =========================================================================\n\n /** Momentum status labels */\n export type MomentumStatusLabel = 'surging' | 'rising' | 'steady' | 'cooling' | 'dropping';\n \n /** Momentum trend direction */\n export type MomentumTrend = 'accelerating' | 'holding' | 'decelerating';\n\n /**\n * Metric breakdown showing current vs previous period\n */\n export interface MetricComparison {\n /** Current period value */\n current: number;\n /** Previous period value */\n previous: number;\n /** Percentage change (-100 to +100+) */\n change: number;\n }\n\n /**\n * Momentum Status Response\n * \n * Provides a 0-100 score indicating channel momentum:\n * - 50 = Steady (no change week over week)\n * - 0 = Severe decline (-100% or worse)\n * - 100 = Explosive growth (+100% or better)\n * \n * Status labels:\n * - 80-100: Surging 🚀\n * - 60-79: Rising 📈\n * - 40-59: Steady ➡️\n * - 20-39: Cooling 📉\n * - 0-19: Dropping 🔻\n */\n export interface MomentumStatusResponse {\n /** Normalized score 0-100 (50 = no change) */\n score: number;\n /** Raw weighted average change before normalization */\n rawChange: number;\n /** Human-friendly status label */\n status: MomentumStatusLabel;\n /** Trend direction compared to previous period */\n trend: MomentumTrend;\n /** Previous period's score (null if first calculation) */\n previousScore: number | null;\n /** Time periods being compared */\n period: {\n current: { start: string; end: string };\n previous: { start: string; end: string };\n };\n /** Breakdown of individual metrics */\n breakdown: {\n views: MetricComparison;\n engagement: MetricComparison;\n watchTime: MetricComparison;\n subscribers: MetricComparison;\n };\n /** Weights used in calculation */\n weights: {\n views: number;\n engagement: number;\n watchTime: number;\n subscribers: number;\n };\n /** Human-readable insight summary */\n insight: string;\n /** Cache metadata */\n _meta?: {\n fromCache: boolean;\n refreshed?: boolean;\n cacheValidFor: string;\n algorithmVersion: string;\n };\n }\n\n /**\n * Single history point for momentum graphing\n */\n export interface MomentumHistoryPoint {\n /** Date (YYYY-MM-DD) */\n date: string;\n /** Momentum score at that time */\n score: number;\n /** Status label at that time */\n status: MomentumStatusLabel;\n }\n\n /**\n * Momentum history response for graphing\n */\n export interface MomentumHistoryResponse {\n /** Historical data points (oldest first) */\n history: MomentumHistoryPoint[];\n /** Number of points returned */\n count: number;\n }\n\n /**\n * Parameters for momentum history request\n */\n export interface GetMomentumHistoryParams {\n /** Max number of data points (default: 30, max: 90) */\n limit?: number;\n }\n}\n","/**\n * TrndUp API SDK\n * \n * Official TypeScript SDK for the TrndUp API.\n * Provides type-safe methods for all API endpoints with Firebase authentication.\n * \n * @example\n * ```typescript\n * import { TrndUpSDK, YOUTUBE_SCOPES } from '@dracoonghost/trndup-sdk';\n * import auth from '@react-native-firebase/auth';\n * import { GoogleSignin } from '@react-native-google-signin/google-signin';\n * \n * // Configure Google Sign-In with YouTube scopes\n * GoogleSignin.configure({\n * webClientId: 'YOUR_WEB_CLIENT_ID',\n * offlineAccess: true,\n * scopes: YOUTUBE_SCOPES\n * });\n * \n * const sdk = new TrndUpSDK({\n * baseUrl: 'https://api.trndup.app',\n * getToken: async () => {\n * const user = auth().currentUser;\n * return user ? await user.getIdToken() : null;\n * },\n * onAuthFailure: () => {\n * // Handle auth failure - navigate to login\n * navigation.navigate('Login');\n * },\n * });\n * \n * // Type-safe API calls\n * const user = await sdk.auth.getCurrentUser();\n * const videos = await sdk.youtube.getVideos({ limit: 10 });\n * const healthScore = await sdk.youtube.getHealthScore({ range: '30d' });\n * ```\n */\n\nimport { TrndUpClient, TrndUpClientConfig } from './client';\nimport { AuthModule } from './modules/auth';\nimport { YouTubeModule } from './modules/youtube';\nimport { InstagramModule } from './modules/instagram';\nimport { SocialModule } from './modules/social';\nimport { InsightsModule } from './modules/insights';\n\nexport class TrndUpSDK extends TrndUpClient {\n public auth: AuthModule;\n public youtube: YouTubeModule;\n public instagram: InstagramModule;\n public social: SocialModule;\n public insights: InsightsModule;\n\n constructor(config: TrndUpClientConfig) {\n super(config);\n\n // Initialize all modules\n this.auth = new AuthModule(this);\n this.youtube = new YouTubeModule(this);\n this.instagram = new InstagramModule(this);\n this.social = new SocialModule(this);\n this.insights = new InsightsModule(this);\n }\n}\n\n// Re-export types and classes\nexport type { TrndUpClientConfig, RequestOptions } from './client';\nexport { TrndUpApiError, TrndUpNetworkError } from './client';\n\n// Re-export all type namespaces\nexport type { Auth, Social, YouTube, Instagram, Insights } from './types';\n\n// Re-export OAuth scopes for configuring Google Sign-In\nexport { YOUTUBE_SCOPES, INSTAGRAM_SCOPES } from './types';\n\n// Version\nexport const SDK_VERSION = '1.0.0';\n"]}
package/dist/index.mjs CHANGED
@@ -532,6 +532,67 @@ var InsightsModule = class {
532
532
  async getTrending() {
533
533
  return this.client.get("/v1/insights/trending");
534
534
  }
535
+ // =========================================================================
536
+ // YOUTUBE MOMENTUM
537
+ // =========================================================================
538
+ /**
539
+ * Get YouTube channel momentum status
540
+ *
541
+ * Returns a 0-100 score indicating channel momentum:
542
+ * - 50 = Steady (no change week over week)
543
+ * - 0 = Severe decline
544
+ * - 100 = Explosive growth
545
+ *
546
+ * Results are cached for 12 hours. Use `refreshYouTubeMomentum()` to force recalculation.
547
+ *
548
+ * @example
549
+ * ```typescript
550
+ * const momentum = await client.insights.getYouTubeMomentum();
551
+ *
552
+ * console.log(`Score: ${momentum.score}/100`);
553
+ * console.log(`Status: ${momentum.status}`); // 'surging' | 'rising' | 'steady' | 'cooling' | 'dropping'
554
+ * console.log(`Trend: ${momentum.trend}`); // 'accelerating' | 'holding' | 'decelerating'
555
+ * console.log(momentum.insight); // Human-readable summary
556
+ *
557
+ * // Show breakdown
558
+ * console.log(`Views: ${momentum.breakdown.views.change}%`);
559
+ * console.log(`Engagement: ${momentum.breakdown.engagement.change}%`);
560
+ * ```
561
+ *
562
+ * GET /v1/insights/youtube/momentum
563
+ */
564
+ async getYouTubeMomentum() {
565
+ return this.client.get("/v1/insights/youtube/momentum");
566
+ }
567
+ /**
568
+ * Get YouTube momentum history for graphing
569
+ *
570
+ * Returns historical momentum scores, oldest first.
571
+ * Use this to plot momentum trends over time.
572
+ *
573
+ * @param params - Query parameters
574
+ * @param params.limit - Max data points (default: 30, max: 90)
575
+ *
576
+ * @example
577
+ * ```typescript
578
+ * const { history } = await client.insights.getYouTubeMomentumHistory({ limit: 30 });
579
+ *
580
+ * // Plot on a chart
581
+ * const chartData = history.map(point => ({
582
+ * x: new Date(point.date),
583
+ * y: point.score,
584
+ * label: point.status
585
+ * }));
586
+ * ```
587
+ *
588
+ * GET /v1/insights/youtube/momentum/history
589
+ */
590
+ async getYouTubeMomentumHistory(params) {
591
+ const query = params?.limit ? `?limit=${params.limit}` : "";
592
+ return this.client.get(
593
+ `/v1/insights/youtube/momentum/history${query}`
594
+ );
595
+ }
535
596
  };
536
597
 
537
598
  // types.ts
@@ -1 +1 @@
1
- {"version":3,"sources":["../client.ts","../modules/auth.ts","../modules/youtube.ts","../modules/instagram.ts","../modules/social.ts","../modules/insights.ts","../types.ts","../index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * TrndUp SDK - Base Client\n * \n * Core HTTP client with Firebase authentication support,\n * error handling, and automatic retries.\n */\n\nimport type { ApiResponse, ApiErrorResponse, ApiSuccessResponse } from './types';\n\n// =============================================================================\n// CONFIGURATION\n// =============================================================================\n\nexport interface TrndUpClientConfig {\n /** Base URL for the API (e.g., 'https://api.trndup.app' or 'http://localhost:3000') */\n baseUrl: string;\n \n /** Function to get the current Firebase ID token */\n getToken: () => string | null | Promise<string | null>;\n \n /** Called when auth completely fails (user needs to re-login) */\n onAuthFailure?: () => void | Promise<void>;\n \n /** Called on any API error */\n onError?: (error: ApiErrorResponse, endpoint: string) => void;\n \n /** Custom headers to include in all requests */\n defaultHeaders?: Record<string, string>;\n \n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n \n /** Enable debug logging (default: false) */\n debug?: boolean;\n}\n\nexport interface RequestOptions {\n /** Skip authentication for this request */\n skipAuth?: boolean;\n \n /** Additional headers for this request */\n headers?: Record<string, string>;\n \n /** AbortController signal for cancellation */\n signal?: AbortSignal;\n \n /** Custom timeout for this request */\n timeout?: number;\n}\n\ninterface RequestConfig extends RequestOptions {\n method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n path: string;\n params?: Record<string, string | number | boolean | undefined>;\n body?: unknown;\n}\n\n// =============================================================================\n// ERROR CLASSES\n// =============================================================================\n\nexport class TrndUpApiError extends Error {\n constructor(\n public readonly response: ApiErrorResponse,\n public readonly status: number,\n public readonly endpoint: string\n ) {\n super(response.error);\n this.name = 'TrndUpApiError';\n }\n \n get code(): string | undefined {\n return this.response.code;\n }\n\n get metadata(): Record<string, unknown> | undefined {\n return this.response.metadata;\n }\n\n /** Check if error is due to authentication failure */\n isAuthError(): boolean {\n return this.status === 401 || this.code === 'AUTH_EXPIRED' || this.code === 'AUTH_INVALID';\n }\n\n /** Check if error is due to rate limiting */\n isRateLimitError(): boolean {\n return this.status === 429 || this.code === 'RATE_LIMITED';\n }\n}\n\nexport class TrndUpNetworkError extends Error {\n constructor(\n public readonly originalError: Error,\n public readonly endpoint: string\n ) {\n super(`Network error calling ${endpoint}: ${originalError.message}`);\n this.name = 'TrndUpNetworkError';\n }\n}\n\n// =============================================================================\n// BASE CLIENT\n// =============================================================================\n\nexport class TrndUpClient {\n private config: Required<Omit<TrndUpClientConfig, 'onAuthFailure' | 'onError' | 'defaultHeaders' | 'debug'>> & {\n onAuthFailure?: TrndUpClientConfig['onAuthFailure'];\n onError?: TrndUpClientConfig['onError'];\n defaultHeaders?: Record<string, string>;\n debug?: boolean;\n };\n\n // Module instances (imported by subclasses or external modules)\n public auth!: any;\n public youtube!: any;\n public instagram!: any;\n public social!: any;\n public insights!: any;\n\n constructor(config: TrndUpClientConfig) {\n this.config = {\n baseUrl: config.baseUrl.replace(/\\/$/, ''), // Remove trailing slash\n getToken: config.getToken,\n timeout: config.timeout ?? 30000,\n onAuthFailure: config.onAuthFailure,\n onError: config.onError,\n defaultHeaders: config.defaultHeaders,\n debug: config.debug ?? false,\n };\n }\n\n // =============================================================================\n // PUBLIC REQUEST METHODS\n // =============================================================================\n\n async get<T = unknown>(\n path: string,\n params?: Record<string, string | number | boolean | undefined>,\n options?: RequestOptions\n ): Promise<T> {\n return this.request<T>({ method: 'GET', path, params, ...options });\n }\n\n async post<T = unknown>(\n path: string,\n body?: unknown,\n options?: RequestOptions\n ): Promise<T> {\n return this.request<T>({ method: 'POST', path, body, ...options });\n }\n\n async put<T = unknown>(\n path: string,\n body?: unknown,\n options?: RequestOptions\n ): Promise<T> {\n return this.request<T>({ method: 'PUT', path, body, ...options });\n }\n\n async patch<T = unknown>(\n path: string,\n body?: unknown,\n options?: RequestOptions\n ): Promise<T> {\n return this.request<T>({ method: 'PATCH', path, body, ...options });\n }\n\n async delete<T = unknown>(\n path: string,\n options?: RequestOptions\n ): Promise<T> {\n return this.request<T>({ method: 'DELETE', path, ...options });\n }\n\n // =============================================================================\n // CORE REQUEST LOGIC\n // =============================================================================\n\n private async request<T>(config: RequestConfig): Promise<T> {\n const { method, path, params, body, skipAuth, headers, signal, timeout } = config;\n\n // Build URL\n const url = this.buildUrl(path, params);\n\n // Build headers\n const requestHeaders = await this.buildHeaders(skipAuth, headers);\n\n // Build request init\n const init: RequestInit = {\n method,\n headers: requestHeaders,\n signal,\n };\n\n if (body && method !== 'GET') {\n init.body = JSON.stringify(body);\n }\n\n // Add timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(\n () => controller.abort(),\n timeout ?? this.config.timeout\n );\n\n try {\n if (this.config.debug) {\n console.log(`[TrndUp SDK] ${method} ${url}`, { body, headers: requestHeaders });\n }\n\n const response = await fetch(url, {\n ...init,\n signal: signal ?? controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n return await this.handleResponse<T>(response, path);\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof TrndUpApiError) {\n throw error;\n }\n\n // Network error\n const networkError = new TrndUpNetworkError(\n error as Error,\n path\n );\n\n if (this.config.debug) {\n console.error('[TrndUp SDK] Network error:', networkError);\n }\n\n throw networkError;\n }\n }\n\n private buildUrl(path: string, params?: Record<string, string | number | boolean | undefined>): string {\n const url = new URL(`${this.config.baseUrl}${path}`);\n\n if (params) {\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n url.searchParams.append(key, String(value));\n }\n });\n }\n\n return url.toString();\n }\n\n private async buildHeaders(\n skipAuth?: boolean,\n additionalHeaders?: Record<string, string>\n ): Promise<Record<string, string>> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...this.config.defaultHeaders,\n ...additionalHeaders,\n };\n\n // Add Firebase ID token for authentication\n if (!skipAuth) {\n const token = await this.config.getToken();\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n }\n\n return headers;\n }\n\n private async handleResponse<T>(response: Response, endpoint: string): Promise<T> {\n const contentType = response.headers.get('content-type');\n const isJson = contentType?.includes('application/json');\n\n if (!response.ok) {\n // Try to parse error response\n let errorResponse: ApiErrorResponse;\n\n if (isJson) {\n try {\n const parsed = await response.json();\n errorResponse = parsed as ApiErrorResponse;\n } catch {\n errorResponse = {\n success: false,\n error: response.statusText || 'Unknown error',\n code: `HTTP_${response.status}`,\n };\n }\n } else {\n const text = await response.text();\n errorResponse = {\n success: false,\n error: text || response.statusText || 'Unknown error',\n code: `HTTP_${response.status}`,\n };\n }\n\n const apiError = new TrndUpApiError(errorResponse, response.status, endpoint);\n\n // Call error handler\n if (this.config.onError) {\n this.config.onError(errorResponse, endpoint);\n }\n\n // Call auth failure handler for 401 errors\n if (apiError.isAuthError() && this.config.onAuthFailure) {\n await this.config.onAuthFailure();\n }\n\n if (this.config.debug) {\n console.error('[TrndUp SDK] API error:', apiError);\n }\n\n throw apiError;\n }\n\n // Parse success response\n if (isJson) {\n const parsed = await response.json();\n const jsonResponse = parsed as ApiResponse<T>;\n\n if (jsonResponse.success === false) {\n // This shouldn't happen with 2xx status, but handle it\n throw new TrndUpApiError(jsonResponse, response.status, endpoint);\n }\n\n return (jsonResponse as ApiSuccessResponse<T>).data;\n }\n\n // Non-JSON response\n return (await response.text()) as unknown as T;\n }\n}\n","/**\n * TrndUp SDK - Auth Module\n * \n * Firebase authentication methods with Twilio phone OTP\n */\n\nimport type { TrndUpClient } from '../client';\nimport type { Auth } from '../types';\n\nexport class AuthModule {\n constructor(private client: TrndUpClient) {}\n\n // =========================================================================\n // PHONE OTP AUTHENTICATION (via Twilio - no reCAPTCHA needed)\n // =========================================================================\n\n /**\n * Send OTP verification code to phone number\n * POST /auth/phone/send-otp\n * \n * @param phoneNumber - Phone number (E.164 format recommended, e.g., +14155551234)\n */\n async sendOTP(phoneNumber: string): Promise<Auth.SendOTPResponse> {\n return this.client.post<Auth.SendOTPResponse>(\n '/auth/phone/send-otp', \n { phoneNumber }, \n { skipAuth: true }\n );\n }\n\n /**\n * Verify OTP and get Firebase custom token\n * POST /auth/phone/verify-otp\n * \n * After calling this, use Firebase signInWithCustomToken(customToken)\n * to complete authentication on the client.\n * \n * @param phoneNumber - Phone number that received the OTP\n * @param code - 6-digit verification code\n */\n async verifyOTP(phoneNumber: string, code: string): Promise<Auth.VerifyOTPResponse> {\n return this.client.post<Auth.VerifyOTPResponse>(\n '/auth/phone/verify-otp',\n { phoneNumber, code },\n { skipAuth: true }\n );\n }\n\n // =========================================================================\n // FIREBASE ID TOKEN AUTHENTICATION\n // =========================================================================\n\n /**\n * Login with Firebase ID token (for Google, Apple, Email/Password)\n * POST /auth/login\n */\n async login(idToken: string): Promise<Auth.LoginResponse> {\n return this.client.post<Auth.LoginResponse>('/auth/login', { idToken }, { skipAuth: true });\n }\n\n /**\n * Get current authenticated user's profile\n * GET /auth/me\n */\n async getCurrentUser(): Promise<Auth.CurrentUserResponse> {\n return this.client.get<Auth.CurrentUserResponse>('/auth/me');\n }\n\n /**\n * Update user profile\n * PATCH /auth/me\n */\n async updateProfile(data: Auth.UpdateProfileRequest): Promise<Auth.UpdateProfileResponse> {\n return this.client.patch<Auth.UpdateProfileResponse>('/auth/me', data);\n }\n\n /**\n * Logout (acknowledge logout on backend)\n * POST /auth/logout\n */\n async logout(): Promise<{ message: string }> {\n return this.client.post<{ message: string }>('/auth/logout');\n }\n\n /**\n * Check auth service status\n * GET /auth/status\n */\n async getStatus(): Promise<{ success: boolean; message: string }> {\n return this.client.get('/auth/status', undefined, { skipAuth: true });\n }\n\n /**\n * Get user's platform connection status\n * GET /user/platforms/status\n */\n async getPlatformStatus(): Promise<Auth.PlatformStatus> {\n return this.client.get<Auth.PlatformStatus>('/user/platforms/status');\n }\n}\n","/**\n * TrndUp SDK - YouTube Module\n * \n * YouTube analytics and data methods\n */\n\nimport type { TrndUpClient } from '../client';\nimport type { YouTube } from '../types';\n\nexport class YouTubeModule {\n constructor(private client: TrndUpClient) {}\n\n /**\n * Get YouTube initialization status (channel sync)\n * Check if user needs to init or has already synced channel\n * \n * GET /v1/platforms/youtube/init/status\n */\n async getInitStatus(): Promise<YouTube.InitStatusResponse> {\n return this.client.get<YouTube.InitStatusResponse>('/v1/platforms/youtube/init/status');\n }\n\n /**\n * Initialize YouTube channel sync\n * Fetches channel info and stores in database\n * \n * POST /v1/platforms/youtube/init\n */\n async initialize(): Promise<YouTube.InitResponse> {\n return this.client.post<YouTube.InitResponse>('/v1/platforms/youtube/init');\n }\n\n /**\n * Get videos sync status\n * Check if videos need to be synced\n * \n * GET /v1/platforms/youtube/videos/sync/status\n */\n async getVideosSyncStatus(): Promise<YouTube.VideosSyncStatusResponse> {\n return this.client.get<YouTube.VideosSyncStatusResponse>('/v1/platforms/youtube/videos/sync/status');\n }\n\n /**\n * Sync all videos from YouTube\n * Fetches all videos and stores in database\n * Returns video counts and oldest video date (for analytics start date)\n * \n * POST /v1/platforms/youtube/videos/sync\n */\n async syncVideos(): Promise<YouTube.VideosSyncResponse> {\n return this.client.post<YouTube.VideosSyncResponse>('/v1/platforms/youtube/videos/sync');\n }\n\n /**\n * Get YouTube videos from database\n * GET /v1/platforms/youtube/videos\n */\n async getVideos(params?: YouTube.GetVideosParams): Promise<YouTube.GetVideosResponse> {\n return this.client.get<YouTube.GetVideosResponse>(\n '/v1/platforms/youtube/videos', \n params as Record<string, string | number | boolean | undefined>\n );\n }\n\n /**\n * Get specific video by ID\n * GET /v1/platforms/youtube/videos/:videoId\n */\n async getVideo(videoId: string): Promise<YouTube.Video> {\n return this.client.get<YouTube.Video>(`/v1/platforms/youtube/videos/${videoId}`);\n }\n\n /**\n * Get channel metrics\n * GET /v1/platforms/youtube/channel/metrics\n */\n async getChannelMetrics(): Promise<YouTube.ChannelMetrics> {\n return this.client.get<YouTube.ChannelMetrics>('/v1/platforms/youtube/channel/metrics');\n }\n\n /**\n * Get channel health score\n * GET /v1/platforms/youtube/health\n */\n async getHealthScore(params?: YouTube.GetHealthScoreParams): Promise<YouTube.HealthScore> {\n return this.client.get<YouTube.HealthScore>(\n '/v1/platforms/youtube/health', \n params as Record<string, string | number | boolean | undefined>\n );\n }\n\n /**\n * Refresh YouTube data\n * POST /v1/platforms/youtube/refresh\n */\n async refresh(): Promise<{ message: string; jobId?: string }> {\n return this.client.post<{ message: string; jobId?: string }>('/v1/platforms/youtube/refresh');\n }\n\n // =========================================================================\n // UNIFIED SYNC STATUS\n // =========================================================================\n\n /**\n * Get unified sync status for all YouTube data types.\n * \n * Use this for:\n * - Checking if UI cache needs refresh (compare lastSyncAt)\n * - Showing sync status in settings/debug screens\n * - Detecting if background sync is running\n * \n * GET /v1/platforms/youtube/sync/status\n * \n * @example\n * ```typescript\n * const status = await client.youtube.getUnifiedSyncStatus();\n * \n * // Check if backend synced since our last fetch\n * const storedLastSync = await AsyncStorage.getItem('videosLastSync');\n * if (status.syncs.videos.lastSyncAt > storedLastSync) {\n * const videos = await client.youtube.getVideos();\n * await AsyncStorage.setItem('videosLastSync', status.syncs.videos.lastSyncAt);\n * }\n * ```\n */\n async getUnifiedSyncStatus(): Promise<YouTube.UnifiedSyncStatusResponse> {\n return this.client.get<YouTube.UnifiedSyncStatusResponse>(\n '/v1/platforms/youtube/sync/status'\n );\n }\n\n // =========================================================================\n // CHANNEL ANALYTICS SYNC\n // =========================================================================\n\n /**\n * Get channel analytics sync status\n * GET /v1/platforms/youtube/analytics/channel/sync/status\n */\n async getChannelAnalyticsSyncStatus(): Promise<YouTube.ChannelAnalyticsSyncStatusResponse> {\n return this.client.get<YouTube.ChannelAnalyticsSyncStatusResponse>(\n '/v1/platforms/youtube/analytics/channel/sync/status'\n );\n }\n\n /**\n * Sync channel analytics (daily + weekly aggregates)\n * Fetches from YouTube Analytics API and stores in database\n * Uses oldest video date as start date\n * \n * POST /v1/platforms/youtube/analytics/channel/sync\n */\n async syncChannelAnalytics(): Promise<YouTube.ChannelAnalyticsSyncResponse> {\n return this.client.post<YouTube.ChannelAnalyticsSyncResponse>(\n '/v1/platforms/youtube/analytics/channel/sync'\n );\n }\n\n // =========================================================================\n // VIDEO ANALYTICS SYNC\n // =========================================================================\n\n /**\n * Get video analytics sync status\n * GET /v1/platforms/youtube/analytics/videos/sync/status\n */\n async getVideoAnalyticsSyncStatus(): Promise<YouTube.VideoAnalyticsSyncStatusResponse> {\n return this.client.get<YouTube.VideoAnalyticsSyncStatusResponse>(\n '/v1/platforms/youtube/analytics/videos/sync/status'\n );\n }\n\n /**\n * Sync analytics for all videos\n * Fetches from YouTube Analytics API and updates video records\n * \n * POST /v1/platforms/youtube/analytics/videos/sync\n */\n async syncVideoAnalytics(): Promise<YouTube.VideoAnalyticsSyncResponse> {\n return this.client.post<YouTube.VideoAnalyticsSyncResponse>(\n '/v1/platforms/youtube/analytics/videos/sync'\n );\n }\n}\n","/**\n * TrndUp SDK - Instagram Module\n * \n * Instagram analytics and data methods\n */\n\nimport type { TrndUpClient } from '../client';\nimport type { Instagram } from '../types';\n\nexport class InstagramModule {\n constructor(private client: TrndUpClient) {}\n\n /**\n * Get Instagram initialization status\n * GET /v1/platforms/instagram/init/status\n */\n async getInitStatus(): Promise<Instagram.InitStatusResponse> {\n return this.client.get<Instagram.InitStatusResponse>('/v1/platforms/instagram/init/status');\n }\n\n /**\n * Initialize Instagram data sync\n * POST /v1/platforms/instagram/init\n */\n async initialize(): Promise<{ message: string }> {\n return this.client.post<{ message: string }>('/v1/platforms/instagram/init');\n }\n\n /**\n * Get Instagram posts\n * GET /v1/platforms/instagram/posts\n */\n async getPosts(params?: Instagram.GetPostsParams): Promise<Instagram.GetPostsResponse> {\n return this.client.get<Instagram.GetPostsResponse>(\n '/v1/platforms/instagram/posts', \n params as Record<string, string | number | boolean | undefined>\n );\n }\n\n /**\n * Get specific post by ID\n * GET /v1/platforms/instagram/posts/:postId\n */\n async getPost(postId: string): Promise<Instagram.Post> {\n return this.client.get<Instagram.Post>(`/v1/platforms/instagram/posts/${postId}`);\n }\n\n /**\n * Get account metrics\n * GET /v1/platforms/instagram/account/metrics\n */\n async getAccountMetrics(): Promise<Instagram.AccountMetrics> {\n return this.client.get<Instagram.AccountMetrics>('/v1/platforms/instagram/account/metrics');\n }\n\n /**\n * Refresh Instagram data\n * POST /v1/platforms/instagram/refresh\n */\n async refresh(): Promise<{ message: string }> {\n return this.client.post<{ message: string }>('/v1/platforms/instagram/refresh');\n }\n}\n","/**\n * TrndUp SDK - Social Module\n * \n * Social account linking methods\n */\n\nimport type { TrndUpClient } from '../client';\nimport type { Social } from '../types';\n\nexport class SocialModule {\n constructor(private client: TrndUpClient) {}\n\n /**\n * Link YouTube account\n * POST /social/link/youtube\n */\n async linkYouTube(data: Social.LinkYouTubeRequest): Promise<Social.LinkAccountResponse> {\n return this.client.post<Social.LinkAccountResponse>('/social/link/youtube', data);\n }\n\n /**\n * Link Instagram account\n * POST /social/link/instagram\n */\n async linkInstagram(data: Social.LinkInstagramRequest): Promise<Social.LinkAccountResponse> {\n return this.client.post<Social.LinkAccountResponse>('/social/link/instagram', data);\n }\n\n /**\n * Get all connected social accounts\n * GET /social/accounts\n */\n async getConnectedAccounts(): Promise<Social.ConnectedAccount[]> {\n return this.client.get<Social.ConnectedAccount[]>('/social/accounts');\n }\n\n /**\n * Unlink a social account\n * DELETE /social/unlink/:provider\n */\n async unlinkAccount(provider: string): Promise<{ message: string }> {\n return this.client.delete<{ message: string }>(`/social/unlink/${provider}`);\n }\n}\n","/**\n * TrndUp SDK - Insights Module\n * \n * Cross-platform insights and analytics\n */\n\nimport type { TrndUpClient } from '../client';\nimport type { Insights } from '../types';\n\nexport class InsightsModule {\n constructor(private client: TrndUpClient) {}\n\n /**\n * Get cross-platform metrics\n * GET /v1/insights/overview\n */\n async getOverview(): Promise<Insights.CrossPlatformMetrics> {\n return this.client.get<Insights.CrossPlatformMetrics>('/v1/insights/overview');\n }\n\n /**\n * Get trending content across all platforms\n * GET /v1/insights/trending\n */\n async getTrending(): Promise<Insights.TrendingContent> {\n return this.client.get<Insights.TrendingContent>('/v1/insights/trending');\n }\n}\n","/**\n * TrndUp SDK Types\n * \n * Type definitions for the TrndUp API SDK\n */\n\n// =============================================================================\n// OAUTH SCOPES\n// =============================================================================\n\n/**\n * Required OAuth scopes for YouTube account linking\n * Use these when configuring Google Sign-In in your mobile app\n * \n * @example\n * ```typescript\n * import { GoogleSignin } from '@react-native-google-signin/google-signin';\n * import { YOUTUBE_SCOPES } from '@trndup/sdk';\n * \n * GoogleSignin.configure({\n * webClientId: 'YOUR_WEB_CLIENT_ID',\n * offlineAccess: true,\n * scopes: YOUTUBE_SCOPES\n * });\n * ```\n */\nexport const YOUTUBE_SCOPES = [\n 'openid',\n 'https://www.googleapis.com/auth/userinfo.profile',\n 'https://www.googleapis.com/auth/userinfo.email',\n 'https://www.googleapis.com/auth/youtube.readonly',\n 'https://www.googleapis.com/auth/yt-analytics.readonly',\n 'https://www.googleapis.com/auth/yt-analytics-monetary.readonly',\n 'https://www.googleapis.com/auth/youtube.force-ssl',\n 'https://www.googleapis.com/auth/youtubepartner'\n];\n\n/**\n * Required OAuth scopes for Instagram account linking\n */\nexport const INSTAGRAM_SCOPES = [\n 'instagram_business_basic',\n 'instagram_business_manage_insights',\n 'instagram_business_manage_comments'\n];\n\n// =============================================================================\n// API RESPONSE WRAPPER\n// =============================================================================\n\nexport interface ApiSuccessResponse<T = unknown> {\n success: true;\n data: T;\n meta?: {\n page?: number;\n limit?: number;\n total?: number;\n };\n}\n\nexport interface ApiErrorResponse {\n success: false;\n error: string;\n code?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport type ApiResponse<T = unknown> = ApiSuccessResponse<T> | ApiErrorResponse;\n\n// =============================================================================\n// AUTH TYPES\n// =============================================================================\n\nexport namespace Auth {\n export interface User {\n id: string;\n firebaseUid?: string;\n email?: string;\n phoneNumber?: string;\n username: string;\n picture?: string;\n emailVerified?: boolean;\n signInProvider?: string; // 'phone', 'google.com', 'password', 'apple.com'\n createdAt: string;\n updatedAt: string;\n }\n\n export interface SocialAccount {\n provider: string;\n accountName?: string;\n username?: string;\n profilePictureUrl?: string;\n connectedAt: string;\n }\n\n // Phone OTP Authentication (Twilio)\n export interface SendOTPRequest {\n phoneNumber: string;\n }\n\n export interface SendOTPResponse {\n message: string;\n expiresIn: number;\n }\n\n export interface VerifyOTPRequest {\n phoneNumber: string;\n code: string;\n }\n\n export interface VerifyOTPResponse {\n customToken: string;\n isNewUser: boolean;\n user: User;\n platformStatus: PlatformStatus;\n }\n\n // Firebase ID Token Login\n export interface LoginRequest {\n idToken: string;\n }\n\n export interface LoginResponse {\n user: User;\n platformStatus: PlatformStatus;\n idToken: string;\n }\n\n export interface CurrentUserResponse {\n user: User;\n socialAccounts: SocialAccount[];\n }\n\n export interface UpdateProfileRequest {\n username?: string;\n picture?: string;\n }\n\n export interface UpdateProfileResponse {\n user: User;\n }\n\n /**\n * Status for a single sync step\n */\n export interface SyncStepStatus {\n status: 'never_synced' | 'pending' | 'in_progress' | 'completed' | 'failed';\n lastSyncAt?: string | null;\n }\n\n /**\n * Sync status for a single platform\n */\n export interface PlatformSyncInfo {\n connected: boolean;\n /** Overall sync status (completed only if ALL steps done) */\n syncStatus?: 'never_synced' | 'pending' | 'in_progress' | 'completed' | 'failed';\n lastSyncAt?: string | null;\n /** True if any sync step incomplete - show loading/sync screen */\n needsInit?: boolean;\n /** Next step UI should perform */\n nextStep?: 'channel' | 'videos' | 'channel_analytics' | 'video_analytics' | null;\n /** Per-step status for UI progress display */\n steps?: {\n channel: SyncStepStatus;\n videos: SyncStepStatus;\n channelAnalytics: SyncStepStatus;\n videoAnalytics: SyncStepStatus;\n };\n }\n\n /**\n * Platform connection and sync status returned in login responses\n * Used by UI to decide navigation: onboarding vs loading screen vs dashboard\n */\n export interface PlatformStatus {\n hasConnectedPlatforms: boolean;\n connectedPlatforms: Array<'youtube' | 'instagram'>;\n youtube: PlatformSyncInfo;\n instagram: PlatformSyncInfo;\n }\n}\n\n// =============================================================================\n// SOCIAL TYPES\n// =============================================================================\n\nexport namespace Social {\n /**\n * Link YouTube account request\n * \n * Two options:\n * 1. serverAuthCode (recommended): Backend exchanges for tokens with proper expiry\n * 2. Direct tokens: Frontend provides accessToken (expiresAt defaults to 1 hour)\n */\n export interface LinkYouTubeRequest {\n idToken: string; // Google ID token for verification (always required)\n serverAuthCode?: string; // Google server auth code - backend exchanges for tokens (recommended)\n accessToken?: string; // Google access token (alternative to serverAuthCode)\n refreshToken?: string; // Google refresh token (only with accessToken flow)\n expiresAt?: number; // Token expiry timestamp in ms (only with accessToken flow)\n grantedScopes?: string; // Actual scopes user granted (space-separated)\n platform?: string; // 'ios' | 'android' | 'web'\n environment?: string; // 'development' | 'production'\n }\n\n export interface LinkInstagramRequest {\n accessToken: string;\n }\n\n export interface LinkAccountResponse {\n message: string;\n provider: string;\n accountName?: string;\n }\n\n export interface ConnectedAccount {\n provider: string;\n accountName?: string;\n username?: string;\n profilePictureUrl?: string;\n connectedAt: string;\n lastSyncAt?: string;\n }\n}\n\n// =============================================================================\n// YOUTUBE TYPES\n// =============================================================================\n\nexport namespace YouTube {\n /**\n * Response from GET /v1/platforms/youtube/init/status\n */\n export interface InitStatusResponse {\n needsInit: boolean;\n status: 'NEVER_SYNCED' | 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED' | 'never_synced';\n lastSyncAt: string | null;\n }\n\n /**\n * Response from POST /v1/platforms/youtube/init\n */\n export interface InitResponse {\n channel: {\n channelId: string;\n channelTitle: string;\n subscriberCount: number;\n videoCount: number;\n viewCount: number;\n thumbnailUrl: string | null;\n };\n syncStatus: {\n isFirstSync: boolean;\n lastSyncAt: string | null;\n };\n }\n\n /**\n * Response from GET /v1/platforms/youtube/videos/sync/status\n */\n export interface VideosSyncStatusResponse {\n needsSync: boolean;\n status: 'NEVER_SYNCED' | 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED' | 'never_synced';\n lastSyncAt: string | null;\n videoCount: number;\n oldestVideoDate: string | null;\n analyticsStartDate: string | null;\n }\n\n /**\n * Response from POST /v1/platforms/youtube/videos/sync\n */\n export interface VideosSyncResponse {\n videos: {\n total: number;\n synced: number;\n oldestVideoDate: string | null;\n };\n syncStatus: {\n isFirstSync: boolean;\n lastSyncAt: string;\n };\n }\n\n export interface Video {\n id: string;\n videoId: string;\n title: string;\n description?: string;\n thumbnailUrl?: string;\n publishedAt: string;\n duration?: number;\n viewCount?: number;\n likeCount?: number;\n commentCount?: number;\n engagementRate?: number;\n }\n\n export interface GetVideosParams {\n limit?: number;\n offset?: number;\n sortBy?: 'recent' | 'views' | 'engagement';\n }\n\n export interface GetVideosResponse {\n videos: Video[];\n total: number;\n hasMore: boolean;\n }\n\n export interface ChannelMetrics {\n subscriberCount: number;\n totalViews: number;\n totalVideos: number;\n averageViews: number;\n engagementRate: number;\n }\n\n export interface HealthScore {\n score: number;\n grade: 'A' | 'B' | 'C' | 'D' | 'F';\n metrics: {\n consistency: number;\n engagement: number;\n growth: number;\n quality: number;\n };\n }\n\n export interface GetHealthScoreParams {\n range?: string; // e.g., '7d', '30d', '90d'\n }\n\n // =========================================================================\n // UNIFIED SYNC STATUS\n // =========================================================================\n\n /**\n * Status of a single sync type\n */\n export interface SyncTypeStatus {\n /** Current sync status */\n status: 'never_synced' | 'pending' | 'in_progress' | 'completed' | 'failed';\n /** \n * When the backend last fetched data from YouTube API.\n * UI should store this value and compare on next call.\n * If lastSyncAt is newer than stored → refetch data from API\n */\n lastSyncAt: string | null;\n /** Last error message if sync failed */\n lastError: string | null;\n /** Number of consecutive failed attempts */\n failedAttempts: number;\n /** Whether data is older than the refresh interval (backend will auto-sync) */\n isStale: boolean;\n /** When the next automatic sync will run */\n nextSyncAt: string | null;\n /** Human-readable time until next sync (e.g., \"6 hours\") */\n timeUntilNextSync: string;\n /** Sync interval for this data type (e.g., \"12 hours\") */\n interval: string;\n }\n\n /**\n * Response from GET /v1/platforms/youtube/sync/status\n * \n * Use this endpoint to:\n * 1. Check if any sync is stale (anyStale) - backend will auto-sync\n * 2. Check if new data available (compare lastSyncAt with stored value)\n * 3. Show sync status in UI settings/debug screen\n * \n * @example\n * ```typescript\n * const status = await client.youtube.getUnifiedSyncStatus();\n * \n * // Check if backend has newer data than our cache\n * const storedLastSync = localStorage.get('videosLastSync');\n * if (status.data.syncs.videos.lastSyncAt > storedLastSync) {\n * // Backend synced since our last fetch, refetch videos\n * const videos = await client.youtube.getVideos();\n * localStorage.set('videosLastSync', status.data.syncs.videos.lastSyncAt);\n * }\n * \n * // Show user that refresh is happening\n * if (status.data.summary.anyInProgress) {\n * showRefreshIndicator();\n * }\n * ```\n */\n export interface UnifiedSyncStatusResponse {\n /** Whether YouTube account is connected */\n isConnected: boolean;\n /** YouTube channel ID */\n channelId: string | null;\n /** YouTube channel name */\n channelName: string | null;\n /** Total videos stored in database */\n totalVideos: number;\n \n /** Sync status for each data type */\n syncs: {\n /** Channel info (subscribers, video count) */\n channelInfo: SyncTypeStatus;\n /** Videos list */\n videos: SyncTypeStatus;\n /** Channel analytics (daily metrics, demographics) */\n channelAnalytics: SyncTypeStatus;\n /** Video analytics (per-video metrics) */\n videoAnalytics: SyncTypeStatus;\n };\n \n /** Summary flags for quick checks */\n summary: {\n /** All sync types completed at least once with data */\n allSynced: boolean;\n /** At least one sync type is older than its refresh interval */\n anyStale: boolean;\n /** At least one sync type failed */\n anyFailed: boolean;\n /** At least one sync is currently in progress */\n anyInProgress: boolean;\n };\n }\n\n // =========================================================================\n // ANALYTICS SYNC TYPES\n // =========================================================================\n\n /**\n * Response from GET /v1/platforms/youtube/analytics/channel/sync/status\n */\n export interface ChannelAnalyticsSyncStatusResponse {\n needsSync: boolean;\n status: 'NEVER_SYNCED' | 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED' | 'never_synced';\n dailyRecords: number;\n dateRange: {\n start: string;\n end: string;\n } | null;\n lastSyncAt: string | null;\n message: string;\n }\n\n /**\n * Response from POST /v1/platforms/youtube/analytics/channel/sync\n */\n export interface ChannelAnalyticsSyncResponse {\n syncedDays: number;\n hasRevenue: boolean;\n dateRange: {\n start: string;\n end: string;\n };\n demographics: number;\n trafficSources: number;\n countries: number;\n devices: number;\n syncStatus: 'COMPLETED' | 'FAILED';\n lastSyncAt: string;\n }\n\n /**\n * Response from GET /v1/platforms/youtube/analytics/videos/sync/status\n */\n export interface VideoAnalyticsSyncStatusResponse {\n needsSync: boolean;\n status: 'NEVER_SYNCED' | 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED';\n /** \n * Progress tracking - USE THESE FOR UI DISPLAY\n * These values reflect actual database state, not queue state\n */\n progress: {\n /** Number of videos with analytics saved to database */\n completed: number;\n /** Total number of videos to sync */\n total: number;\n /** Videos still needing sync */\n remaining: number;\n /** Progress percentage (0-100) */\n percent: number;\n };\n /** @deprecated Use progress.total instead */\n totalVideos: number;\n /** @deprecated Use progress.completed instead */\n videosWithAnalytics: number;\n /** @deprecated Use progress.remaining instead */\n videosNeedingSync: number;\n lastSyncAt: string | null;\n lastError: string | null;\n /** \n * Queue info (for debugging only, NOT for progress display)\n * Note: Queue counts reset and don't reflect true progress\n */\n queue: {\n /** Whether workers are actively processing */\n isProcessing: boolean;\n /** Jobs currently being processed */\n jobsActive: number;\n /** Jobs waiting in queue */\n jobsWaiting: number;\n /** Jobs that failed after retries */\n jobsFailed: number;\n };\n message: string;\n }\n\n /**\n * Response from POST /v1/platforms/youtube/analytics/videos/sync\n * \n * Returns immediately - sync runs in background via BullMQ queue.\n * Poll VideoAnalyticsSyncStatusResponse for progress.\n */\n export interface VideoAnalyticsSyncResponse {\n /** Status message */\n message: string;\n /** Current sync status - will be 'IN_PROGRESS' when sync starts */\n status: 'IN_PROGRESS' | 'COMPLETED' | 'FAILED';\n /** Total number of videos */\n totalVideos: number;\n /** Number of videos queued for sync (excludes already-synced) */\n queued: number;\n /** Number of videos skipped (already have analytics) */\n skipped: number;\n }\n\n /**\n * Daily analytics data for a single video\n * Stored in YouTubeVideoDaily collection\n */\n export interface VideoDailyAnalytics {\n videoId: string;\n date: string;\n views: number;\n likes: number;\n dislikes: number;\n comments: number;\n estimatedMinutesWatched: number;\n averageViewDuration: number;\n subscribersGained: number;\n subscribersLost: number;\n }\n}\n\n// =============================================================================\n// INSTAGRAM TYPES\n// =============================================================================\n\nexport namespace Instagram {\n export interface InitStatusResponse {\n needsInit: boolean;\n message: string;\n postCount?: number;\n accountId?: string;\n username?: string;\n }\n\n export interface Post {\n id: string;\n caption?: string;\n mediaType: 'IMAGE' | 'VIDEO' | 'CAROUSEL_ALBUM';\n mediaUrl: string;\n permalink: string;\n timestamp: string;\n likeCount?: number;\n commentsCount?: number;\n engagementRate?: number;\n }\n\n export interface GetPostsParams {\n limit?: number;\n offset?: number;\n }\n\n export interface GetPostsResponse {\n posts: Post[];\n total: number;\n hasMore: boolean;\n }\n\n export interface AccountMetrics {\n followersCount: number;\n followsCount: number;\n mediaCount: number;\n averageLikes: number;\n engagementRate: number;\n }\n}\n\n// =============================================================================\n// INSIGHTS TYPES\n// =============================================================================\n\nexport namespace Insights {\n export interface CrossPlatformMetrics {\n totalFollowers: number;\n totalEngagement: number;\n platforms: {\n youtube?: {\n subscribers: number;\n views: number;\n videos: number;\n };\n instagram?: {\n followers: number;\n posts: number;\n avgEngagement: number;\n };\n };\n }\n\n export interface TrendingContent {\n youtube?: YouTube.Video[];\n instagram?: Instagram.Post[];\n }\n}\n","/**\n * TrndUp API SDK\n * \n * Official TypeScript SDK for the TrndUp API.\n * Provides type-safe methods for all API endpoints with Firebase authentication.\n * \n * @example\n * ```typescript\n * import { TrndUpSDK, YOUTUBE_SCOPES } from '@trndup/sdk';\n * import auth from '@react-native-firebase/auth';\n * import { GoogleSignin } from '@react-native-google-signin/google-signin';\n * \n * // Configure Google Sign-In with YouTube scopes\n * GoogleSignin.configure({\n * webClientId: 'YOUR_WEB_CLIENT_ID',\n * offlineAccess: true,\n * scopes: YOUTUBE_SCOPES\n * });\n * \n * const sdk = new TrndUpSDK({\n * baseUrl: 'https://api.trndup.app',\n * getToken: async () => {\n * const user = auth().currentUser;\n * return user ? await user.getIdToken() : null;\n * },\n * onAuthFailure: () => {\n * // Handle auth failure - navigate to login\n * navigation.navigate('Login');\n * },\n * });\n * \n * // Type-safe API calls\n * const user = await sdk.auth.getCurrentUser();\n * const videos = await sdk.youtube.getVideos({ limit: 10 });\n * const healthScore = await sdk.youtube.getHealthScore({ range: '30d' });\n * ```\n */\n\nimport { TrndUpClient, TrndUpClientConfig } from './client';\nimport { AuthModule } from './modules/auth';\nimport { YouTubeModule } from './modules/youtube';\nimport { InstagramModule } from './modules/instagram';\nimport { SocialModule } from './modules/social';\nimport { InsightsModule } from './modules/insights';\n\nexport class TrndUpSDK extends TrndUpClient {\n public auth: AuthModule;\n public youtube: YouTubeModule;\n public instagram: InstagramModule;\n public social: SocialModule;\n public insights: InsightsModule;\n\n constructor(config: TrndUpClientConfig) {\n super(config);\n\n // Initialize all modules\n this.auth = new AuthModule(this);\n this.youtube = new YouTubeModule(this);\n this.instagram = new InstagramModule(this);\n this.social = new SocialModule(this);\n this.insights = new InsightsModule(this);\n }\n}\n\n// Re-export types and classes\nexport type { TrndUpClientConfig, RequestOptions } from './client';\nexport { TrndUpApiError, TrndUpNetworkError } from './client';\n\n// Re-export all type namespaces\nexport type { Auth, Social, YouTube, Instagram, Insights } from './types';\n\n// Re-export OAuth scopes for configuring Google Sign-In\nexport { YOUTUBE_SCOPES, INSTAGRAM_SCOPES } from './types';\n\n// Version\nexport const SDK_VERSION = '1.0.0';\n"]}
1
+ {"version":3,"sources":["../client.ts","../modules/auth.ts","../modules/youtube.ts","../modules/instagram.ts","../modules/social.ts","../modules/insights.ts","../types.ts","../index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * TrndUp SDK - Base Client\n * \n * Core HTTP client with Firebase authentication support,\n * error handling, and automatic retries.\n */\n\nimport type { ApiResponse, ApiErrorResponse, ApiSuccessResponse } from './types';\n\n// =============================================================================\n// CONFIGURATION\n// =============================================================================\n\nexport interface TrndUpClientConfig {\n /** Base URL for the API (e.g., 'https://api.trndup.app' or 'http://localhost:3000') */\n baseUrl: string;\n \n /** Function to get the current Firebase ID token */\n getToken: () => string | null | Promise<string | null>;\n \n /** Called when auth completely fails (user needs to re-login) */\n onAuthFailure?: () => void | Promise<void>;\n \n /** Called on any API error */\n onError?: (error: ApiErrorResponse, endpoint: string) => void;\n \n /** Custom headers to include in all requests */\n defaultHeaders?: Record<string, string>;\n \n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n \n /** Enable debug logging (default: false) */\n debug?: boolean;\n}\n\nexport interface RequestOptions {\n /** Skip authentication for this request */\n skipAuth?: boolean;\n \n /** Additional headers for this request */\n headers?: Record<string, string>;\n \n /** AbortController signal for cancellation */\n signal?: AbortSignal;\n \n /** Custom timeout for this request */\n timeout?: number;\n}\n\ninterface RequestConfig extends RequestOptions {\n method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n path: string;\n params?: Record<string, string | number | boolean | undefined>;\n body?: unknown;\n}\n\n// =============================================================================\n// ERROR CLASSES\n// =============================================================================\n\nexport class TrndUpApiError extends Error {\n constructor(\n public readonly response: ApiErrorResponse,\n public readonly status: number,\n public readonly endpoint: string\n ) {\n super(response.error);\n this.name = 'TrndUpApiError';\n }\n \n get code(): string | undefined {\n return this.response.code;\n }\n\n get metadata(): Record<string, unknown> | undefined {\n return this.response.metadata;\n }\n\n /** Check if error is due to authentication failure */\n isAuthError(): boolean {\n return this.status === 401 || this.code === 'AUTH_EXPIRED' || this.code === 'AUTH_INVALID';\n }\n\n /** Check if error is due to rate limiting */\n isRateLimitError(): boolean {\n return this.status === 429 || this.code === 'RATE_LIMITED';\n }\n}\n\nexport class TrndUpNetworkError extends Error {\n constructor(\n public readonly originalError: Error,\n public readonly endpoint: string\n ) {\n super(`Network error calling ${endpoint}: ${originalError.message}`);\n this.name = 'TrndUpNetworkError';\n }\n}\n\n// =============================================================================\n// BASE CLIENT\n// =============================================================================\n\nexport class TrndUpClient {\n private config: Required<Omit<TrndUpClientConfig, 'onAuthFailure' | 'onError' | 'defaultHeaders' | 'debug'>> & {\n onAuthFailure?: TrndUpClientConfig['onAuthFailure'];\n onError?: TrndUpClientConfig['onError'];\n defaultHeaders?: Record<string, string>;\n debug?: boolean;\n };\n\n // Module instances (imported by subclasses or external modules)\n public auth!: any;\n public youtube!: any;\n public instagram!: any;\n public social!: any;\n public insights!: any;\n\n constructor(config: TrndUpClientConfig) {\n this.config = {\n baseUrl: config.baseUrl.replace(/\\/$/, ''), // Remove trailing slash\n getToken: config.getToken,\n timeout: config.timeout ?? 30000,\n onAuthFailure: config.onAuthFailure,\n onError: config.onError,\n defaultHeaders: config.defaultHeaders,\n debug: config.debug ?? false,\n };\n }\n\n // =============================================================================\n // PUBLIC REQUEST METHODS\n // =============================================================================\n\n async get<T = unknown>(\n path: string,\n params?: Record<string, string | number | boolean | undefined>,\n options?: RequestOptions\n ): Promise<T> {\n return this.request<T>({ method: 'GET', path, params, ...options });\n }\n\n async post<T = unknown>(\n path: string,\n body?: unknown,\n options?: RequestOptions\n ): Promise<T> {\n return this.request<T>({ method: 'POST', path, body, ...options });\n }\n\n async put<T = unknown>(\n path: string,\n body?: unknown,\n options?: RequestOptions\n ): Promise<T> {\n return this.request<T>({ method: 'PUT', path, body, ...options });\n }\n\n async patch<T = unknown>(\n path: string,\n body?: unknown,\n options?: RequestOptions\n ): Promise<T> {\n return this.request<T>({ method: 'PATCH', path, body, ...options });\n }\n\n async delete<T = unknown>(\n path: string,\n options?: RequestOptions\n ): Promise<T> {\n return this.request<T>({ method: 'DELETE', path, ...options });\n }\n\n // =============================================================================\n // CORE REQUEST LOGIC\n // =============================================================================\n\n private async request<T>(config: RequestConfig): Promise<T> {\n const { method, path, params, body, skipAuth, headers, signal, timeout } = config;\n\n // Build URL\n const url = this.buildUrl(path, params);\n\n // Build headers\n const requestHeaders = await this.buildHeaders(skipAuth, headers);\n\n // Build request init\n const init: RequestInit = {\n method,\n headers: requestHeaders,\n signal,\n };\n\n if (body && method !== 'GET') {\n init.body = JSON.stringify(body);\n }\n\n // Add timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(\n () => controller.abort(),\n timeout ?? this.config.timeout\n );\n\n try {\n if (this.config.debug) {\n console.log(`[TrndUp SDK] ${method} ${url}`, { body, headers: requestHeaders });\n }\n\n const response = await fetch(url, {\n ...init,\n signal: signal ?? controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n return await this.handleResponse<T>(response, path);\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof TrndUpApiError) {\n throw error;\n }\n\n // Network error\n const networkError = new TrndUpNetworkError(\n error as Error,\n path\n );\n\n if (this.config.debug) {\n console.error('[TrndUp SDK] Network error:', networkError);\n }\n\n throw networkError;\n }\n }\n\n private buildUrl(path: string, params?: Record<string, string | number | boolean | undefined>): string {\n const url = new URL(`${this.config.baseUrl}${path}`);\n\n if (params) {\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n url.searchParams.append(key, String(value));\n }\n });\n }\n\n return url.toString();\n }\n\n private async buildHeaders(\n skipAuth?: boolean,\n additionalHeaders?: Record<string, string>\n ): Promise<Record<string, string>> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...this.config.defaultHeaders,\n ...additionalHeaders,\n };\n\n // Add Firebase ID token for authentication\n if (!skipAuth) {\n const token = await this.config.getToken();\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n }\n\n return headers;\n }\n\n private async handleResponse<T>(response: Response, endpoint: string): Promise<T> {\n const contentType = response.headers.get('content-type');\n const isJson = contentType?.includes('application/json');\n\n if (!response.ok) {\n // Try to parse error response\n let errorResponse: ApiErrorResponse;\n\n if (isJson) {\n try {\n const parsed = await response.json();\n errorResponse = parsed as ApiErrorResponse;\n } catch {\n errorResponse = {\n success: false,\n error: response.statusText || 'Unknown error',\n code: `HTTP_${response.status}`,\n };\n }\n } else {\n const text = await response.text();\n errorResponse = {\n success: false,\n error: text || response.statusText || 'Unknown error',\n code: `HTTP_${response.status}`,\n };\n }\n\n const apiError = new TrndUpApiError(errorResponse, response.status, endpoint);\n\n // Call error handler\n if (this.config.onError) {\n this.config.onError(errorResponse, endpoint);\n }\n\n // Call auth failure handler for 401 errors\n if (apiError.isAuthError() && this.config.onAuthFailure) {\n await this.config.onAuthFailure();\n }\n\n if (this.config.debug) {\n console.error('[TrndUp SDK] API error:', apiError);\n }\n\n throw apiError;\n }\n\n // Parse success response\n if (isJson) {\n const parsed = await response.json();\n const jsonResponse = parsed as ApiResponse<T>;\n\n if (jsonResponse.success === false) {\n // This shouldn't happen with 2xx status, but handle it\n throw new TrndUpApiError(jsonResponse, response.status, endpoint);\n }\n\n return (jsonResponse as ApiSuccessResponse<T>).data;\n }\n\n // Non-JSON response\n return (await response.text()) as unknown as T;\n }\n}\n","/**\n * TrndUp SDK - Auth Module\n * \n * Firebase authentication methods with Twilio phone OTP\n */\n\nimport type { TrndUpClient } from '../client';\nimport type { Auth } from '../types';\n\nexport class AuthModule {\n constructor(private client: TrndUpClient) {}\n\n // =========================================================================\n // PHONE OTP AUTHENTICATION (via Twilio - no reCAPTCHA needed)\n // =========================================================================\n\n /**\n * Send OTP verification code to phone number\n * POST /auth/phone/send-otp\n * \n * @param phoneNumber - Phone number (E.164 format recommended, e.g., +14155551234)\n */\n async sendOTP(phoneNumber: string): Promise<Auth.SendOTPResponse> {\n return this.client.post<Auth.SendOTPResponse>(\n '/auth/phone/send-otp', \n { phoneNumber }, \n { skipAuth: true }\n );\n }\n\n /**\n * Verify OTP and get Firebase custom token\n * POST /auth/phone/verify-otp\n * \n * After calling this, use Firebase signInWithCustomToken(customToken)\n * to complete authentication on the client.\n * \n * @param phoneNumber - Phone number that received the OTP\n * @param code - 6-digit verification code\n */\n async verifyOTP(phoneNumber: string, code: string): Promise<Auth.VerifyOTPResponse> {\n return this.client.post<Auth.VerifyOTPResponse>(\n '/auth/phone/verify-otp',\n { phoneNumber, code },\n { skipAuth: true }\n );\n }\n\n // =========================================================================\n // FIREBASE ID TOKEN AUTHENTICATION\n // =========================================================================\n\n /**\n * Login with Firebase ID token (for Google, Apple, Email/Password)\n * POST /auth/login\n */\n async login(idToken: string): Promise<Auth.LoginResponse> {\n return this.client.post<Auth.LoginResponse>('/auth/login', { idToken }, { skipAuth: true });\n }\n\n /**\n * Get current authenticated user's profile\n * GET /auth/me\n */\n async getCurrentUser(): Promise<Auth.CurrentUserResponse> {\n return this.client.get<Auth.CurrentUserResponse>('/auth/me');\n }\n\n /**\n * Update user profile\n * PATCH /auth/me\n */\n async updateProfile(data: Auth.UpdateProfileRequest): Promise<Auth.UpdateProfileResponse> {\n return this.client.patch<Auth.UpdateProfileResponse>('/auth/me', data);\n }\n\n /**\n * Logout (acknowledge logout on backend)\n * POST /auth/logout\n */\n async logout(): Promise<{ message: string }> {\n return this.client.post<{ message: string }>('/auth/logout');\n }\n\n /**\n * Check auth service status\n * GET /auth/status\n */\n async getStatus(): Promise<{ success: boolean; message: string }> {\n return this.client.get('/auth/status', undefined, { skipAuth: true });\n }\n\n /**\n * Get user's platform connection status\n * GET /user/platforms/status\n */\n async getPlatformStatus(): Promise<Auth.PlatformStatus> {\n return this.client.get<Auth.PlatformStatus>('/user/platforms/status');\n }\n}\n","/**\n * TrndUp SDK - YouTube Module\n * \n * YouTube analytics and data methods\n */\n\nimport type { TrndUpClient } from '../client';\nimport type { YouTube } from '../types';\n\nexport class YouTubeModule {\n constructor(private client: TrndUpClient) {}\n\n /**\n * Get YouTube initialization status (channel sync)\n * Check if user needs to init or has already synced channel\n * \n * GET /v1/platforms/youtube/init/status\n */\n async getInitStatus(): Promise<YouTube.InitStatusResponse> {\n return this.client.get<YouTube.InitStatusResponse>('/v1/platforms/youtube/init/status');\n }\n\n /**\n * Initialize YouTube channel sync\n * Fetches channel info and stores in database\n * \n * POST /v1/platforms/youtube/init\n */\n async initialize(): Promise<YouTube.InitResponse> {\n return this.client.post<YouTube.InitResponse>('/v1/platforms/youtube/init');\n }\n\n /**\n * Get videos sync status\n * Check if videos need to be synced\n * \n * GET /v1/platforms/youtube/videos/sync/status\n */\n async getVideosSyncStatus(): Promise<YouTube.VideosSyncStatusResponse> {\n return this.client.get<YouTube.VideosSyncStatusResponse>('/v1/platforms/youtube/videos/sync/status');\n }\n\n /**\n * Sync all videos from YouTube\n * Fetches all videos and stores in database\n * Returns video counts and oldest video date (for analytics start date)\n * \n * POST /v1/platforms/youtube/videos/sync\n */\n async syncVideos(): Promise<YouTube.VideosSyncResponse> {\n return this.client.post<YouTube.VideosSyncResponse>('/v1/platforms/youtube/videos/sync');\n }\n\n /**\n * Get YouTube videos from database\n * GET /v1/platforms/youtube/videos\n */\n async getVideos(params?: YouTube.GetVideosParams): Promise<YouTube.GetVideosResponse> {\n return this.client.get<YouTube.GetVideosResponse>(\n '/v1/platforms/youtube/videos', \n params as Record<string, string | number | boolean | undefined>\n );\n }\n\n /**\n * Get specific video by ID\n * GET /v1/platforms/youtube/videos/:videoId\n */\n async getVideo(videoId: string): Promise<YouTube.Video> {\n return this.client.get<YouTube.Video>(`/v1/platforms/youtube/videos/${videoId}`);\n }\n\n /**\n * Get channel metrics\n * GET /v1/platforms/youtube/channel/metrics\n */\n async getChannelMetrics(): Promise<YouTube.ChannelMetrics> {\n return this.client.get<YouTube.ChannelMetrics>('/v1/platforms/youtube/channel/metrics');\n }\n\n /**\n * Get channel health score\n * GET /v1/platforms/youtube/health\n */\n async getHealthScore(params?: YouTube.GetHealthScoreParams): Promise<YouTube.HealthScore> {\n return this.client.get<YouTube.HealthScore>(\n '/v1/platforms/youtube/health', \n params as Record<string, string | number | boolean | undefined>\n );\n }\n\n /**\n * Refresh YouTube data\n * POST /v1/platforms/youtube/refresh\n */\n async refresh(): Promise<{ message: string; jobId?: string }> {\n return this.client.post<{ message: string; jobId?: string }>('/v1/platforms/youtube/refresh');\n }\n\n // =========================================================================\n // UNIFIED SYNC STATUS\n // =========================================================================\n\n /**\n * Get unified sync status for all YouTube data types.\n * \n * Use this for:\n * - Checking if UI cache needs refresh (compare lastSyncAt)\n * - Showing sync status in settings/debug screens\n * - Detecting if background sync is running\n * \n * GET /v1/platforms/youtube/sync/status\n * \n * @example\n * ```typescript\n * const status = await client.youtube.getUnifiedSyncStatus();\n * \n * // Check if backend synced since our last fetch\n * const storedLastSync = await AsyncStorage.getItem('videosLastSync');\n * if (status.syncs.videos.lastSyncAt > storedLastSync) {\n * const videos = await client.youtube.getVideos();\n * await AsyncStorage.setItem('videosLastSync', status.syncs.videos.lastSyncAt);\n * }\n * ```\n */\n async getUnifiedSyncStatus(): Promise<YouTube.UnifiedSyncStatusResponse> {\n return this.client.get<YouTube.UnifiedSyncStatusResponse>(\n '/v1/platforms/youtube/sync/status'\n );\n }\n\n // =========================================================================\n // CHANNEL ANALYTICS SYNC\n // =========================================================================\n\n /**\n * Get channel analytics sync status\n * GET /v1/platforms/youtube/analytics/channel/sync/status\n */\n async getChannelAnalyticsSyncStatus(): Promise<YouTube.ChannelAnalyticsSyncStatusResponse> {\n return this.client.get<YouTube.ChannelAnalyticsSyncStatusResponse>(\n '/v1/platforms/youtube/analytics/channel/sync/status'\n );\n }\n\n /**\n * Sync channel analytics (daily + weekly aggregates)\n * Fetches from YouTube Analytics API and stores in database\n * Uses oldest video date as start date\n * \n * POST /v1/platforms/youtube/analytics/channel/sync\n */\n async syncChannelAnalytics(): Promise<YouTube.ChannelAnalyticsSyncResponse> {\n return this.client.post<YouTube.ChannelAnalyticsSyncResponse>(\n '/v1/platforms/youtube/analytics/channel/sync'\n );\n }\n\n // =========================================================================\n // VIDEO ANALYTICS SYNC\n // =========================================================================\n\n /**\n * Get video analytics sync status\n * GET /v1/platforms/youtube/analytics/videos/sync/status\n */\n async getVideoAnalyticsSyncStatus(): Promise<YouTube.VideoAnalyticsSyncStatusResponse> {\n return this.client.get<YouTube.VideoAnalyticsSyncStatusResponse>(\n '/v1/platforms/youtube/analytics/videos/sync/status'\n );\n }\n\n /**\n * Sync analytics for all videos\n * Fetches from YouTube Analytics API and updates video records\n * \n * POST /v1/platforms/youtube/analytics/videos/sync\n */\n async syncVideoAnalytics(): Promise<YouTube.VideoAnalyticsSyncResponse> {\n return this.client.post<YouTube.VideoAnalyticsSyncResponse>(\n '/v1/platforms/youtube/analytics/videos/sync'\n );\n }\n}\n","/**\n * TrndUp SDK - Instagram Module\n * \n * Instagram analytics and data methods\n */\n\nimport type { TrndUpClient } from '../client';\nimport type { Instagram } from '../types';\n\nexport class InstagramModule {\n constructor(private client: TrndUpClient) {}\n\n /**\n * Get Instagram initialization status\n * GET /v1/platforms/instagram/init/status\n */\n async getInitStatus(): Promise<Instagram.InitStatusResponse> {\n return this.client.get<Instagram.InitStatusResponse>('/v1/platforms/instagram/init/status');\n }\n\n /**\n * Initialize Instagram data sync\n * POST /v1/platforms/instagram/init\n */\n async initialize(): Promise<{ message: string }> {\n return this.client.post<{ message: string }>('/v1/platforms/instagram/init');\n }\n\n /**\n * Get Instagram posts\n * GET /v1/platforms/instagram/posts\n */\n async getPosts(params?: Instagram.GetPostsParams): Promise<Instagram.GetPostsResponse> {\n return this.client.get<Instagram.GetPostsResponse>(\n '/v1/platforms/instagram/posts', \n params as Record<string, string | number | boolean | undefined>\n );\n }\n\n /**\n * Get specific post by ID\n * GET /v1/platforms/instagram/posts/:postId\n */\n async getPost(postId: string): Promise<Instagram.Post> {\n return this.client.get<Instagram.Post>(`/v1/platforms/instagram/posts/${postId}`);\n }\n\n /**\n * Get account metrics\n * GET /v1/platforms/instagram/account/metrics\n */\n async getAccountMetrics(): Promise<Instagram.AccountMetrics> {\n return this.client.get<Instagram.AccountMetrics>('/v1/platforms/instagram/account/metrics');\n }\n\n /**\n * Refresh Instagram data\n * POST /v1/platforms/instagram/refresh\n */\n async refresh(): Promise<{ message: string }> {\n return this.client.post<{ message: string }>('/v1/platforms/instagram/refresh');\n }\n}\n","/**\n * TrndUp SDK - Social Module\n * \n * Social account linking methods\n */\n\nimport type { TrndUpClient } from '../client';\nimport type { Social } from '../types';\n\nexport class SocialModule {\n constructor(private client: TrndUpClient) {}\n\n /**\n * Link YouTube account\n * POST /social/link/youtube\n */\n async linkYouTube(data: Social.LinkYouTubeRequest): Promise<Social.LinkAccountResponse> {\n return this.client.post<Social.LinkAccountResponse>('/social/link/youtube', data);\n }\n\n /**\n * Link Instagram account\n * POST /social/link/instagram\n */\n async linkInstagram(data: Social.LinkInstagramRequest): Promise<Social.LinkAccountResponse> {\n return this.client.post<Social.LinkAccountResponse>('/social/link/instagram', data);\n }\n\n /**\n * Get all connected social accounts\n * GET /social/accounts\n */\n async getConnectedAccounts(): Promise<Social.ConnectedAccount[]> {\n return this.client.get<Social.ConnectedAccount[]>('/social/accounts');\n }\n\n /**\n * Unlink a social account\n * DELETE /social/unlink/:provider\n */\n async unlinkAccount(provider: string): Promise<{ message: string }> {\n return this.client.delete<{ message: string }>(`/social/unlink/${provider}`);\n }\n}\n","/**\n * TrndUp SDK - Insights Module\n * \n * Cross-platform insights and analytics\n */\n\nimport type { TrndUpClient } from '../client';\nimport type { Insights } from '../types';\n\nexport class InsightsModule {\n constructor(private client: TrndUpClient) {}\n\n /**\n * Get cross-platform metrics\n * GET /v1/insights/overview\n */\n async getOverview(): Promise<Insights.CrossPlatformMetrics> {\n return this.client.get<Insights.CrossPlatformMetrics>('/v1/insights/overview');\n }\n\n /**\n * Get trending content across all platforms\n * GET /v1/insights/trending\n */\n async getTrending(): Promise<Insights.TrendingContent> {\n return this.client.get<Insights.TrendingContent>('/v1/insights/trending');\n }\n\n // =========================================================================\n // YOUTUBE MOMENTUM\n // =========================================================================\n\n /**\n * Get YouTube channel momentum status\n * \n * Returns a 0-100 score indicating channel momentum:\n * - 50 = Steady (no change week over week)\n * - 0 = Severe decline\n * - 100 = Explosive growth\n * \n * Results are cached for 12 hours. Use `refreshYouTubeMomentum()` to force recalculation.\n * \n * @example\n * ```typescript\n * const momentum = await client.insights.getYouTubeMomentum();\n * \n * console.log(`Score: ${momentum.score}/100`);\n * console.log(`Status: ${momentum.status}`); // 'surging' | 'rising' | 'steady' | 'cooling' | 'dropping'\n * console.log(`Trend: ${momentum.trend}`); // 'accelerating' | 'holding' | 'decelerating'\n * console.log(momentum.insight); // Human-readable summary\n * \n * // Show breakdown\n * console.log(`Views: ${momentum.breakdown.views.change}%`);\n * console.log(`Engagement: ${momentum.breakdown.engagement.change}%`);\n * ```\n * \n * GET /v1/insights/youtube/momentum\n */\n async getYouTubeMomentum(): Promise<Insights.MomentumStatusResponse> {\n return this.client.get<Insights.MomentumStatusResponse>('/v1/insights/youtube/momentum');\n }\n\n /**\n * Get YouTube momentum history for graphing\n * \n * Returns historical momentum scores, oldest first.\n * Use this to plot momentum trends over time.\n * \n * @param params - Query parameters\n * @param params.limit - Max data points (default: 30, max: 90)\n * \n * @example\n * ```typescript\n * const { history } = await client.insights.getYouTubeMomentumHistory({ limit: 30 });\n * \n * // Plot on a chart\n * const chartData = history.map(point => ({\n * x: new Date(point.date),\n * y: point.score,\n * label: point.status\n * }));\n * ```\n * \n * GET /v1/insights/youtube/momentum/history\n */\n async getYouTubeMomentumHistory(\n params?: Insights.GetMomentumHistoryParams\n ): Promise<Insights.MomentumHistoryResponse> {\n const query = params?.limit ? `?limit=${params.limit}` : '';\n return this.client.get<Insights.MomentumHistoryResponse>(\n `/v1/insights/youtube/momentum/history${query}`\n );\n }\n\n}\n","/**\n * TrndUp SDK Types\n * \n * Type definitions for the TrndUp API SDK\n */\n\n// =============================================================================\n// OAUTH SCOPES\n// =============================================================================\n\n/**\n * Required OAuth scopes for YouTube account linking\n * Use these when configuring Google Sign-In in your mobile app\n * \n * @example\n * ```typescript\n * import { GoogleSignin } from '@react-native-google-signin/google-signin';\n * import { YOUTUBE_SCOPES } from '@dracoonghost/trndup-sdk';\n * \n * GoogleSignin.configure({\n * webClientId: 'YOUR_WEB_CLIENT_ID',\n * offlineAccess: true,\n * scopes: YOUTUBE_SCOPES\n * });\n * ```\n */\nexport const YOUTUBE_SCOPES = [\n 'openid',\n 'https://www.googleapis.com/auth/userinfo.profile',\n 'https://www.googleapis.com/auth/userinfo.email',\n 'https://www.googleapis.com/auth/youtube.readonly',\n 'https://www.googleapis.com/auth/yt-analytics.readonly',\n 'https://www.googleapis.com/auth/yt-analytics-monetary.readonly',\n 'https://www.googleapis.com/auth/youtube.force-ssl',\n 'https://www.googleapis.com/auth/youtubepartner'\n];\n\n/**\n * Required OAuth scopes for Instagram account linking\n */\nexport const INSTAGRAM_SCOPES = [\n 'instagram_business_basic',\n 'instagram_business_manage_insights',\n 'instagram_business_manage_comments'\n];\n\n// =============================================================================\n// API RESPONSE WRAPPER\n// =============================================================================\n\nexport interface ApiSuccessResponse<T = unknown> {\n success: true;\n data: T;\n meta?: {\n page?: number;\n limit?: number;\n total?: number;\n };\n}\n\nexport interface ApiErrorResponse {\n success: false;\n error: string;\n code?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport type ApiResponse<T = unknown> = ApiSuccessResponse<T> | ApiErrorResponse;\n\n// =============================================================================\n// AUTH TYPES\n// =============================================================================\n\nexport namespace Auth {\n export interface User {\n id: string;\n firebaseUid?: string;\n email?: string;\n phoneNumber?: string;\n username: string;\n picture?: string;\n emailVerified?: boolean;\n signInProvider?: string; // 'phone', 'google.com', 'password', 'apple.com'\n createdAt: string;\n updatedAt: string;\n }\n\n export interface SocialAccount {\n provider: string;\n accountName?: string;\n username?: string;\n profilePictureUrl?: string;\n connectedAt: string;\n }\n\n // Phone OTP Authentication (Twilio)\n export interface SendOTPRequest {\n phoneNumber: string;\n }\n\n export interface SendOTPResponse {\n message: string;\n expiresIn: number;\n }\n\n export interface VerifyOTPRequest {\n phoneNumber: string;\n code: string;\n }\n\n export interface VerifyOTPResponse {\n customToken: string;\n isNewUser: boolean;\n user: User;\n platformStatus: PlatformStatus;\n }\n\n // Firebase ID Token Login\n export interface LoginRequest {\n idToken: string;\n }\n\n export interface LoginResponse {\n user: User;\n platformStatus: PlatformStatus;\n idToken: string;\n }\n\n export interface CurrentUserResponse {\n user: User;\n socialAccounts: SocialAccount[];\n }\n\n export interface UpdateProfileRequest {\n username?: string;\n picture?: string;\n }\n\n export interface UpdateProfileResponse {\n user: User;\n }\n\n /**\n * Status for a single sync step\n */\n export interface SyncStepStatus {\n status: 'never_synced' | 'pending' | 'in_progress' | 'completed' | 'failed';\n lastSyncAt?: string | null;\n }\n\n /**\n * Sync status for a single platform\n */\n export interface PlatformSyncInfo {\n connected: boolean;\n /** Overall sync status (completed only if ALL steps done) */\n syncStatus?: 'never_synced' | 'pending' | 'in_progress' | 'completed' | 'failed';\n lastSyncAt?: string | null;\n /** True if any sync step incomplete - show loading/sync screen */\n needsInit?: boolean;\n /** Next step UI should perform */\n nextStep?: 'channel' | 'videos' | 'channel_analytics' | 'video_analytics' | null;\n /** Per-step status for UI progress display */\n steps?: {\n channel: SyncStepStatus;\n videos: SyncStepStatus;\n channelAnalytics: SyncStepStatus;\n videoAnalytics: SyncStepStatus;\n };\n }\n\n /**\n * Platform connection and sync status returned in login responses\n * Used by UI to decide navigation: onboarding vs loading screen vs dashboard\n */\n export interface PlatformStatus {\n hasConnectedPlatforms: boolean;\n connectedPlatforms: Array<'youtube' | 'instagram'>;\n youtube: PlatformSyncInfo;\n instagram: PlatformSyncInfo;\n }\n}\n\n// =============================================================================\n// SOCIAL TYPES\n// =============================================================================\n\nexport namespace Social {\n /**\n * Link YouTube account request\n * \n * Two options:\n * 1. serverAuthCode (recommended): Backend exchanges for tokens with proper expiry\n * 2. Direct tokens: Frontend provides accessToken (expiresAt defaults to 1 hour)\n */\n export interface LinkYouTubeRequest {\n idToken: string; // Google ID token for verification (always required)\n serverAuthCode?: string; // Google server auth code - backend exchanges for tokens (recommended)\n accessToken?: string; // Google access token (alternative to serverAuthCode)\n refreshToken?: string; // Google refresh token (only with accessToken flow)\n expiresAt?: number; // Token expiry timestamp in ms (only with accessToken flow)\n grantedScopes?: string; // Actual scopes user granted (space-separated)\n platform?: string; // 'ios' | 'android' | 'web'\n environment?: string; // 'development' | 'production'\n }\n\n export interface LinkInstagramRequest {\n accessToken: string;\n }\n\n export interface LinkAccountResponse {\n message: string;\n provider: string;\n accountName?: string;\n }\n\n export interface ConnectedAccount {\n provider: string;\n accountName?: string;\n username?: string;\n profilePictureUrl?: string;\n connectedAt: string;\n lastSyncAt?: string;\n }\n}\n\n// =============================================================================\n// YOUTUBE TYPES\n// =============================================================================\n\nexport namespace YouTube {\n /**\n * Response from GET /v1/platforms/youtube/init/status\n */\n export interface InitStatusResponse {\n needsInit: boolean;\n status: 'NEVER_SYNCED' | 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED' | 'never_synced';\n lastSyncAt: string | null;\n }\n\n /**\n * Response from POST /v1/platforms/youtube/init\n */\n export interface InitResponse {\n channel: {\n channelId: string;\n channelTitle: string;\n subscriberCount: number;\n videoCount: number;\n viewCount: number;\n thumbnailUrl: string | null;\n };\n syncStatus: {\n isFirstSync: boolean;\n lastSyncAt: string | null;\n };\n }\n\n /**\n * Response from GET /v1/platforms/youtube/videos/sync/status\n */\n export interface VideosSyncStatusResponse {\n needsSync: boolean;\n status: 'NEVER_SYNCED' | 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED' | 'never_synced';\n lastSyncAt: string | null;\n videoCount: number;\n oldestVideoDate: string | null;\n analyticsStartDate: string | null;\n }\n\n /**\n * Response from POST /v1/platforms/youtube/videos/sync\n */\n export interface VideosSyncResponse {\n videos: {\n total: number;\n synced: number;\n oldestVideoDate: string | null;\n };\n syncStatus: {\n isFirstSync: boolean;\n lastSyncAt: string;\n };\n }\n\n export interface Video {\n id: string;\n videoId: string;\n title: string;\n description?: string;\n thumbnailUrl?: string;\n publishedAt: string;\n duration?: number;\n viewCount?: number;\n likeCount?: number;\n commentCount?: number;\n engagementRate?: number;\n }\n\n export interface GetVideosParams {\n limit?: number;\n offset?: number;\n sortBy?: 'recent' | 'views' | 'engagement';\n }\n\n export interface GetVideosResponse {\n videos: Video[];\n total: number;\n hasMore: boolean;\n }\n\n export interface ChannelMetrics {\n subscriberCount: number;\n totalViews: number;\n totalVideos: number;\n averageViews: number;\n engagementRate: number;\n }\n\n export interface HealthScore {\n score: number;\n grade: 'A' | 'B' | 'C' | 'D' | 'F';\n metrics: {\n consistency: number;\n engagement: number;\n growth: number;\n quality: number;\n };\n }\n\n export interface GetHealthScoreParams {\n range?: string; // e.g., '7d', '30d', '90d'\n }\n\n // =========================================================================\n // UNIFIED SYNC STATUS\n // =========================================================================\n\n /**\n * Status of a single sync type\n */\n export interface SyncTypeStatus {\n /** Current sync status */\n status: 'never_synced' | 'pending' | 'in_progress' | 'completed' | 'failed';\n /** \n * When the backend last fetched data from YouTube API.\n * UI should store this value and compare on next call.\n * If lastSyncAt is newer than stored → refetch data from API\n */\n lastSyncAt: string | null;\n /** Last error message if sync failed */\n lastError: string | null;\n /** Number of consecutive failed attempts */\n failedAttempts: number;\n /** Whether data is older than the refresh interval (backend will auto-sync) */\n isStale: boolean;\n /** When the next automatic sync will run */\n nextSyncAt: string | null;\n /** Human-readable time until next sync (e.g., \"6 hours\") */\n timeUntilNextSync: string;\n /** Sync interval for this data type (e.g., \"12 hours\") */\n interval: string;\n }\n\n /**\n * Response from GET /v1/platforms/youtube/sync/status\n * \n * Use this endpoint to:\n * 1. Check if any sync is stale (anyStale) - backend will auto-sync\n * 2. Check if new data available (compare lastSyncAt with stored value)\n * 3. Show sync status in UI settings/debug screen\n * \n * @example\n * ```typescript\n * const status = await client.youtube.getUnifiedSyncStatus();\n * \n * // Check if backend has newer data than our cache\n * const storedLastSync = localStorage.get('videosLastSync');\n * if (status.data.syncs.videos.lastSyncAt > storedLastSync) {\n * // Backend synced since our last fetch, refetch videos\n * const videos = await client.youtube.getVideos();\n * localStorage.set('videosLastSync', status.data.syncs.videos.lastSyncAt);\n * }\n * ```\n */\n export interface UnifiedSyncStatusResponse {\n /** Whether YouTube account is connected */\n isConnected: boolean;\n /** YouTube channel ID */\n channelId: string | null;\n /** YouTube channel name */\n channelName: string | null;\n /** Total videos stored in database */\n totalVideos: number;\n \n /** Sync status for each data type */\n syncs: {\n /** Channel info (subscribers, video count) */\n channelInfo: SyncTypeStatus;\n /** Videos list */\n videos: SyncTypeStatus;\n /** Channel analytics (daily metrics, demographics) */\n channelAnalytics: SyncTypeStatus;\n /** Video analytics (per-video metrics) */\n videoAnalytics: SyncTypeStatus;\n };\n }\n\n // =========================================================================\n // ANALYTICS SYNC TYPES\n // =========================================================================\n\n /**\n * Response from GET /v1/platforms/youtube/analytics/channel/sync/status\n */\n export interface ChannelAnalyticsSyncStatusResponse {\n needsSync: boolean;\n status: 'NEVER_SYNCED' | 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED' | 'never_synced';\n dailyRecords: number;\n dateRange: {\n start: string;\n end: string;\n } | null;\n lastSyncAt: string | null;\n message: string;\n }\n\n /**\n * Response from POST /v1/platforms/youtube/analytics/channel/sync\n */\n export interface ChannelAnalyticsSyncResponse {\n syncedDays: number;\n hasRevenue: boolean;\n dateRange: {\n start: string;\n end: string;\n };\n demographics: number;\n trafficSources: number;\n countries: number;\n devices: number;\n syncStatus: 'COMPLETED' | 'FAILED';\n lastSyncAt: string;\n }\n\n /**\n * Response from GET /v1/platforms/youtube/analytics/videos/sync/status\n */\n export interface VideoAnalyticsSyncStatusResponse {\n needsSync: boolean;\n status: 'NEVER_SYNCED' | 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED';\n /** \n * Progress tracking - USE THESE FOR UI DISPLAY\n * These values reflect actual database state, not queue state\n */\n progress: {\n /** Number of videos with analytics saved to database */\n completed: number;\n /** Total number of videos to sync */\n total: number;\n /** Videos still needing sync */\n remaining: number;\n /** Progress percentage (0-100) */\n percent: number;\n };\n /** @deprecated Use progress.total instead */\n totalVideos: number;\n /** @deprecated Use progress.completed instead */\n videosWithAnalytics: number;\n /** @deprecated Use progress.remaining instead */\n videosNeedingSync: number;\n lastSyncAt: string | null;\n lastError: string | null;\n /** \n * Queue info (for debugging only, NOT for progress display)\n * Note: Queue counts reset and don't reflect true progress\n */\n queue: {\n /** Whether workers are actively processing */\n isProcessing: boolean;\n /** Jobs currently being processed */\n jobsActive: number;\n /** Jobs waiting in queue */\n jobsWaiting: number;\n /** Jobs that failed after retries */\n jobsFailed: number;\n };\n message: string;\n }\n\n /**\n * Response from POST /v1/platforms/youtube/analytics/videos/sync\n * \n * Returns immediately - sync runs in background via BullMQ queue.\n * Poll VideoAnalyticsSyncStatusResponse for progress.\n */\n export interface VideoAnalyticsSyncResponse {\n /** Status message */\n message: string;\n /** Current sync status - will be 'IN_PROGRESS' when sync starts */\n status: 'IN_PROGRESS' | 'COMPLETED' | 'FAILED';\n /** Total number of videos */\n totalVideos: number;\n /** Number of videos queued for sync (excludes already-synced) */\n queued: number;\n /** Number of videos skipped (already have analytics) */\n skipped: number;\n }\n\n /**\n * Daily analytics data for a single video\n * Stored in YouTubeVideoDaily collection\n */\n export interface VideoDailyAnalytics {\n videoId: string;\n date: string;\n views: number;\n likes: number;\n dislikes: number;\n comments: number;\n estimatedMinutesWatched: number;\n averageViewDuration: number;\n subscribersGained: number;\n subscribersLost: number;\n }\n}\n\n// =============================================================================\n// INSTAGRAM TYPES\n// =============================================================================\n\nexport namespace Instagram {\n export interface InitStatusResponse {\n needsInit: boolean;\n message: string;\n postCount?: number;\n accountId?: string;\n username?: string;\n }\n\n export interface Post {\n id: string;\n caption?: string;\n mediaType: 'IMAGE' | 'VIDEO' | 'CAROUSEL_ALBUM';\n mediaUrl: string;\n permalink: string;\n timestamp: string;\n likeCount?: number;\n commentsCount?: number;\n engagementRate?: number;\n }\n\n export interface GetPostsParams {\n limit?: number;\n offset?: number;\n }\n\n export interface GetPostsResponse {\n posts: Post[];\n total: number;\n hasMore: boolean;\n }\n\n export interface AccountMetrics {\n followersCount: number;\n followsCount: number;\n mediaCount: number;\n averageLikes: number;\n engagementRate: number;\n }\n}\n\n// =============================================================================\n// INSIGHTS TYPES\n// =============================================================================\n\nexport namespace Insights {\n export interface CrossPlatformMetrics {\n totalFollowers: number;\n totalEngagement: number;\n platforms: {\n youtube?: {\n subscribers: number;\n views: number;\n videos: number;\n };\n instagram?: {\n followers: number;\n posts: number;\n avgEngagement: number;\n };\n };\n }\n\n export interface TrendingContent {\n youtube?: YouTube.Video[];\n instagram?: Instagram.Post[];\n }\n\n // =========================================================================\n // MOMENTUM STATUS\n // =========================================================================\n\n /** Momentum status labels */\n export type MomentumStatusLabel = 'surging' | 'rising' | 'steady' | 'cooling' | 'dropping';\n \n /** Momentum trend direction */\n export type MomentumTrend = 'accelerating' | 'holding' | 'decelerating';\n\n /**\n * Metric breakdown showing current vs previous period\n */\n export interface MetricComparison {\n /** Current period value */\n current: number;\n /** Previous period value */\n previous: number;\n /** Percentage change (-100 to +100+) */\n change: number;\n }\n\n /**\n * Momentum Status Response\n * \n * Provides a 0-100 score indicating channel momentum:\n * - 50 = Steady (no change week over week)\n * - 0 = Severe decline (-100% or worse)\n * - 100 = Explosive growth (+100% or better)\n * \n * Status labels:\n * - 80-100: Surging 🚀\n * - 60-79: Rising 📈\n * - 40-59: Steady ➡️\n * - 20-39: Cooling 📉\n * - 0-19: Dropping 🔻\n */\n export interface MomentumStatusResponse {\n /** Normalized score 0-100 (50 = no change) */\n score: number;\n /** Raw weighted average change before normalization */\n rawChange: number;\n /** Human-friendly status label */\n status: MomentumStatusLabel;\n /** Trend direction compared to previous period */\n trend: MomentumTrend;\n /** Previous period's score (null if first calculation) */\n previousScore: number | null;\n /** Time periods being compared */\n period: {\n current: { start: string; end: string };\n previous: { start: string; end: string };\n };\n /** Breakdown of individual metrics */\n breakdown: {\n views: MetricComparison;\n engagement: MetricComparison;\n watchTime: MetricComparison;\n subscribers: MetricComparison;\n };\n /** Weights used in calculation */\n weights: {\n views: number;\n engagement: number;\n watchTime: number;\n subscribers: number;\n };\n /** Human-readable insight summary */\n insight: string;\n /** Cache metadata */\n _meta?: {\n fromCache: boolean;\n refreshed?: boolean;\n cacheValidFor: string;\n algorithmVersion: string;\n };\n }\n\n /**\n * Single history point for momentum graphing\n */\n export interface MomentumHistoryPoint {\n /** Date (YYYY-MM-DD) */\n date: string;\n /** Momentum score at that time */\n score: number;\n /** Status label at that time */\n status: MomentumStatusLabel;\n }\n\n /**\n * Momentum history response for graphing\n */\n export interface MomentumHistoryResponse {\n /** Historical data points (oldest first) */\n history: MomentumHistoryPoint[];\n /** Number of points returned */\n count: number;\n }\n\n /**\n * Parameters for momentum history request\n */\n export interface GetMomentumHistoryParams {\n /** Max number of data points (default: 30, max: 90) */\n limit?: number;\n }\n}\n","/**\n * TrndUp API SDK\n * \n * Official TypeScript SDK for the TrndUp API.\n * Provides type-safe methods for all API endpoints with Firebase authentication.\n * \n * @example\n * ```typescript\n * import { TrndUpSDK, YOUTUBE_SCOPES } from '@dracoonghost/trndup-sdk';\n * import auth from '@react-native-firebase/auth';\n * import { GoogleSignin } from '@react-native-google-signin/google-signin';\n * \n * // Configure Google Sign-In with YouTube scopes\n * GoogleSignin.configure({\n * webClientId: 'YOUR_WEB_CLIENT_ID',\n * offlineAccess: true,\n * scopes: YOUTUBE_SCOPES\n * });\n * \n * const sdk = new TrndUpSDK({\n * baseUrl: 'https://api.trndup.app',\n * getToken: async () => {\n * const user = auth().currentUser;\n * return user ? await user.getIdToken() : null;\n * },\n * onAuthFailure: () => {\n * // Handle auth failure - navigate to login\n * navigation.navigate('Login');\n * },\n * });\n * \n * // Type-safe API calls\n * const user = await sdk.auth.getCurrentUser();\n * const videos = await sdk.youtube.getVideos({ limit: 10 });\n * const healthScore = await sdk.youtube.getHealthScore({ range: '30d' });\n * ```\n */\n\nimport { TrndUpClient, TrndUpClientConfig } from './client';\nimport { AuthModule } from './modules/auth';\nimport { YouTubeModule } from './modules/youtube';\nimport { InstagramModule } from './modules/instagram';\nimport { SocialModule } from './modules/social';\nimport { InsightsModule } from './modules/insights';\n\nexport class TrndUpSDK extends TrndUpClient {\n public auth: AuthModule;\n public youtube: YouTubeModule;\n public instagram: InstagramModule;\n public social: SocialModule;\n public insights: InsightsModule;\n\n constructor(config: TrndUpClientConfig) {\n super(config);\n\n // Initialize all modules\n this.auth = new AuthModule(this);\n this.youtube = new YouTubeModule(this);\n this.instagram = new InstagramModule(this);\n this.social = new SocialModule(this);\n this.insights = new InsightsModule(this);\n }\n}\n\n// Re-export types and classes\nexport type { TrndUpClientConfig, RequestOptions } from './client';\nexport { TrndUpApiError, TrndUpNetworkError } from './client';\n\n// Re-export all type namespaces\nexport type { Auth, Social, YouTube, Instagram, Insights } from './types';\n\n// Re-export OAuth scopes for configuring Google Sign-In\nexport { YOUTUBE_SCOPES, INSTAGRAM_SCOPES } from './types';\n\n// Version\nexport const SDK_VERSION = '1.0.0';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dracoonghost/trndup-sdk",
3
- "version": "1.3.13",
3
+ "version": "1.3.15",
4
4
  "description": "Official TypeScript SDK for TrndUp API with Firebase authentication",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",