@umituz/react-native-ai-gemini-provider 2.1.5 → 2.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/package.json +1 -1
  2. package/src/index.ts +11 -104
  3. package/src/infrastructure/interceptors/BaseInterceptor.ts +78 -0
  4. package/src/infrastructure/interceptors/RequestInterceptors.ts +6 -62
  5. package/src/infrastructure/interceptors/ResponseInterceptors.ts +6 -61
  6. package/src/infrastructure/interceptors/index.ts +7 -13
  7. package/src/infrastructure/services/base-gemini.service.ts +82 -0
  8. package/src/infrastructure/services/gemini-streaming.service.ts +31 -61
  9. package/src/infrastructure/services/gemini-text-generation.service.ts +11 -33
  10. package/src/infrastructure/services/index.ts +7 -4
  11. package/src/infrastructure/utils/async/index.ts +1 -10
  12. package/src/infrastructure/utils/index.ts +43 -26
  13. package/src/infrastructure/utils/stream-processor.util.ts +156 -0
  14. package/src/infrastructure/utils/validation-composer.util.ts +160 -0
  15. package/src/infrastructure/utils/validation.util.ts +9 -68
  16. package/src/presentation/hooks/index.ts +6 -1
  17. package/src/presentation/hooks/use-gemini.ts +21 -72
  18. package/src/presentation/hooks/use-operation-manager.ts +88 -0
  19. package/src/providers/ConfigBuilder.ts +121 -0
  20. package/src/providers/ProviderFactory.ts +37 -49
  21. package/src/providers/index.ts +4 -13
  22. package/src/infrastructure/utils/async/debounce.util.ts +0 -100
  23. package/src/infrastructure/utils/async/memoize.util.ts +0 -55
  24. package/src/infrastructure/utils/env.util.ts +0 -175
  25. package/src/infrastructure/utils/performance.util.ts +0 -139
  26. package/src/infrastructure/utils/rate-limiter.util.ts +0 -86
  27. package/src/infrastructure/utils/retry.util.ts +0 -158
  28. package/src/providers/ProviderConfig.ts +0 -36
@@ -1,175 +0,0 @@
1
- /**
2
- * Environment Configuration Utilities
3
- * Safe loading and validation of environment variables
4
- */
5
-
6
- /**
7
- * Get environment variable with type safety
8
- */
9
- function getEnvVar(key: string): string | undefined {
10
- // Check for React Native environment
11
- if (typeof process !== "undefined" && process.env) {
12
- return process.env[key];
13
- }
14
-
15
- // For React Native, apps typically use a config file
16
- // This returns undefined if not available - app should provide config directly
17
- return undefined;
18
- }
19
-
20
- /**
21
- * Get required environment variable
22
- * @throws Error if variable is not set
23
- */
24
- export function getRequiredEnv(key: string): string {
25
- const value = getEnvVar(key);
26
-
27
- if (!value || value.trim().length === 0) {
28
- throw new Error(`Required environment variable "${key}" is not set`);
29
- }
30
-
31
- return value.trim();
32
- }
33
-
34
- /**
35
- * Get optional environment variable with fallback
36
- */
37
- export function getOptionalEnv(
38
- key: string,
39
- fallback: string,
40
- ): string {
41
- const value = getEnvVar(key);
42
- return value?.trim() || fallback;
43
- }
44
-
45
- /**
46
- * Get environment variable as number
47
- */
48
- export function getEnvNumber(key: string, fallback: number): number {
49
- const value = getEnvVar(key);
50
-
51
- if (!value) {
52
- return fallback;
53
- }
54
-
55
- const parsed = parseInt(value, 10);
56
-
57
- if (isNaN(parsed)) {
58
- return fallback;
59
- }
60
-
61
- return parsed;
62
- }
63
-
64
- /**
65
- * Get environment variable as boolean
66
- */
67
- export function getEnvBoolean(key: string, fallback: boolean): boolean {
68
- const value = getEnvVar(key);
69
-
70
- if (!value) {
71
- return fallback;
72
- }
73
-
74
- return value.toLowerCase() === "true" || value === "1";
75
- }
76
-
77
- /**
78
- * Environment configuration interface
79
- */
80
- export interface EnvConfig {
81
- /** Gemini API key */
82
- GEMINI_API_KEY?: string;
83
- /** Default timeout in milliseconds */
84
- GEMINI_TIMEOUT?: number;
85
- /** Enable debug logging */
86
- GEMINI_DEBUG?: boolean;
87
- /** Max retry attempts */
88
- GEMINI_MAX_RETRIES?: number;
89
- }
90
-
91
- /**
92
- * Load all Gemini-related environment variables
93
- */
94
- export function loadGeminiEnv(): EnvConfig {
95
- return {
96
- GEMINI_API_KEY: getEnvVar("GEMINI_API_KEY"),
97
- GEMINI_TIMEOUT: getEnvNumber("GEMINI_TIMEOUT", 30000),
98
- GEMINI_DEBUG: getEnvBoolean("GEMINI_DEBUG", false),
99
- GEMINI_MAX_RETRIES: getEnvNumber("GEMINI_MAX_RETRIES", 3),
100
- };
101
- }
102
-
103
- /**
104
- * Get API key from environment with validation
105
- * @throws Error if API key is not set or invalid
106
- */
107
- export function getApiKeyFromEnv(): string {
108
- const apiKey = getRequiredEnv("GEMINI_API_KEY");
109
-
110
- if (apiKey.length < 10) {
111
- throw new Error("GEMINI_API_KEY appears to be invalid (too short)");
112
- }
113
-
114
- return apiKey;
115
- }
116
-
117
- /**
118
- * Check if running in development mode
119
- */
120
- export function isDevelopment(): boolean {
121
- return getEnvBoolean("NODE_ENV", false) === false ||
122
- getEnvVar("NODE_ENV") === "development";
123
- }
124
-
125
- /**
126
- * Check if debug mode is enabled
127
- */
128
- export function isDebugEnabled(): boolean {
129
- return getEnvBoolean("GEMINI_DEBUG", false) ||
130
- getEnvBoolean("DEBUG", false);
131
- }
132
-
133
- /**
134
- * Validate that required environment variables are set
135
- * @returns Array of missing variable names
136
- */
137
- export function validateEnv(requiredVars: string[]): string[] {
138
- const missing: string[] = [];
139
-
140
- for (const varName of requiredVars) {
141
- if (!getEnvVar(varName)) {
142
- missing.push(varName);
143
- }
144
- }
145
-
146
- return missing;
147
- }
148
-
149
- /**
150
- * Get configuration from environment or use provided fallback
151
- * @throws Error if API key is not configured in either env or fallback
152
- */
153
- export function getGeminiConfigFromEnv(fallback?: {
154
- apiKey?: string;
155
- timeout?: number;
156
- }): {
157
- apiKey: string;
158
- timeout?: number;
159
- } {
160
- const env = loadGeminiEnv();
161
-
162
- const apiKey = env.GEMINI_API_KEY || fallback?.apiKey;
163
-
164
- if (!apiKey || apiKey.trim().length === 0) {
165
- throw new Error(
166
- "GEMINI_API_KEY must be set either in environment variables or provided as fallback. " +
167
- "Set the GEMINI_API_KEY environment variable or pass apiKey in the fallback config."
168
- );
169
- }
170
-
171
- return {
172
- apiKey,
173
- timeout: env.GEMINI_TIMEOUT || fallback?.timeout,
174
- };
175
- }
@@ -1,139 +0,0 @@
1
-
2
- export interface PerformanceMetrics {
3
- duration: number;
4
- timestamp: number;
5
- metadata?: Record<string, unknown>;
6
- }
7
-
8
- export class PerformanceTimer {
9
- private startTime: number;
10
- private endTime?: number;
11
- private metadata?: Record<string, unknown>;
12
-
13
- constructor(metadata?: Record<string, unknown>) {
14
- // Use performance.now() for higher precision when available
15
- this.startTime = typeof performance !== "undefined" && performance.now
16
- ? performance.now()
17
- : Date.now();
18
- this.metadata = metadata;
19
- }
20
-
21
- stop(): number {
22
- this.endTime = typeof performance !== "undefined" && performance.now
23
- ? performance.now()
24
- : Date.now();
25
- return this.duration;
26
- }
27
-
28
- get duration(): number {
29
- const end = this.endTime ?? (typeof performance !== "undefined" && performance.now
30
- ? performance.now()
31
- : Date.now());
32
- return end - this.startTime;
33
- }
34
-
35
- getMetrics(): PerformanceMetrics {
36
- return { duration: this.duration, timestamp: this.startTime, metadata: this.metadata };
37
- }
38
-
39
- get isRunning(): boolean {
40
- return this.endTime === undefined;
41
- }
42
- }
43
-
44
- export async function measureAsync<T>(
45
- operation: () => Promise<T>,
46
- metadata?: Record<string, unknown>,
47
- ): Promise<{ result: T; duration: number }> {
48
- const timer = new PerformanceTimer(metadata);
49
- try {
50
- const result = await operation();
51
- timer.stop();
52
- return { result, duration: timer.duration };
53
- } catch (error) {
54
- timer.stop();
55
- throw error;
56
- }
57
- }
58
-
59
- export function measureSync<T>(
60
- operation: () => T,
61
- metadata?: Record<string, unknown>,
62
- ): { result: T; duration: number } {
63
- const timer = new PerformanceTimer(metadata);
64
- try {
65
- const result = operation();
66
- timer.stop();
67
- return { result, duration: timer.duration };
68
- } catch (error) {
69
- timer.stop();
70
- throw error;
71
- }
72
- }
73
-
74
- interface DebouncedFunction<T extends (...args: never[]) => unknown> {
75
- (...args: Parameters<T>): void;
76
- cancel: () => void;
77
- }
78
-
79
- export function debounce<T extends (...args: never[]) => unknown>(
80
- func: T,
81
- wait: number,
82
- ): DebouncedFunction<T> {
83
- let timeout: ReturnType<typeof setTimeout> | undefined;
84
-
85
- const debounced = (...args: Parameters<T>) => {
86
- const later = () => {
87
- timeout = undefined;
88
- func(...args);
89
- };
90
- if (timeout) {
91
- clearTimeout(timeout);
92
- }
93
- timeout = setTimeout(later, wait);
94
- };
95
-
96
- debounced.cancel = () => {
97
- if (timeout) {
98
- clearTimeout(timeout);
99
- timeout = undefined;
100
- }
101
- };
102
-
103
- return debounced;
104
- }
105
-
106
- interface ThrottledFunction<T extends (...args: never[]) => unknown> {
107
- (...args: Parameters<T>): void;
108
- cancel: () => void;
109
- }
110
-
111
- export function throttle<T extends (...args: never[]) => unknown>(
112
- func: T,
113
- limit: number,
114
- ): ThrottledFunction<T> {
115
- let inThrottle = false;
116
- let timeout: ReturnType<typeof setTimeout> | undefined;
117
-
118
- const throttled = (...args: Parameters<T>) => {
119
- if (!inThrottle) {
120
- func(...args);
121
- inThrottle = true;
122
- timeout = setTimeout(() => {
123
- inThrottle = false;
124
- timeout = undefined;
125
- }, limit);
126
- }
127
- };
128
-
129
- throttled.cancel = () => {
130
- if (timeout) {
131
- clearTimeout(timeout);
132
- timeout = undefined;
133
- inThrottle = false;
134
- }
135
- };
136
-
137
- return throttled;
138
- }
139
-
@@ -1,86 +0,0 @@
1
-
2
- export interface RateLimiterOptions {
3
- minInterval?: number; // Minimum milliseconds between requests
4
- maxQueueSize?: number; // Maximum number of pending requests
5
- }
6
-
7
- export class RateLimiter {
8
- private queue: Array<() => Promise<void>> = [];
9
- private processing = false;
10
- private lastRequest = 0;
11
- private minInterval: number;
12
- private maxQueueSize: number;
13
-
14
- constructor(options: RateLimiterOptions = {}) {
15
- this.minInterval = options.minInterval ?? 100; // 100ms minimum interval
16
- this.maxQueueSize = options.maxQueueSize ?? 100;
17
- }
18
-
19
- async execute<T>(fn: () => Promise<T>): Promise<T> {
20
- if (this.queue.length >= this.maxQueueSize) {
21
- // Calculate estimated wait time to help users understand delay
22
- const estimatedWait = this.queue.length * this.minInterval;
23
- const waitSeconds = (estimatedWait / 1000).toFixed(1);
24
- throw new Error(
25
- `Rate limiter queue is full (${this.maxQueueSize} requests pending). ` +
26
- `Estimated wait: ~${waitSeconds}s. Please wait before retrying.`
27
- );
28
- }
29
-
30
- return new Promise((resolve, reject) => {
31
- this.queue.push(async () => {
32
- try {
33
- const result = await fn();
34
- resolve(result);
35
- } catch (error) {
36
- reject(error);
37
- }
38
- });
39
-
40
- // Start queue processing if not already running
41
- this.processQueue().catch(() => {
42
- // Individual task errors are handled above, ignore queue processing errors
43
- });
44
- });
45
- }
46
-
47
- private async processQueue(): Promise<void> {
48
- // Only one processQueue can run at a time
49
- if (this.processing) {
50
- return;
51
- }
52
-
53
- this.processing = true;
54
-
55
- try {
56
- while (this.queue.length > 0) {
57
- const elapsed = Date.now() - this.lastRequest;
58
- if (elapsed < this.minInterval) {
59
- await new Promise((r) => setTimeout(r, this.minInterval - elapsed));
60
- }
61
-
62
- const task = this.queue.shift();
63
- if (task) {
64
- this.lastRequest = Date.now();
65
- await task();
66
- }
67
- }
68
- } finally {
69
- this.processing = false;
70
- }
71
- }
72
-
73
- getQueueSize(): number {
74
- return this.queue.length;
75
- }
76
-
77
- clear(): void {
78
- this.queue = [];
79
- this.processing = false;
80
- }
81
-
82
- reset(): void {
83
- this.clear();
84
- this.lastRequest = 0;
85
- }
86
- }
@@ -1,158 +0,0 @@
1
- /**
2
- * Retry Utilities
3
- * Implements retry logic with exponential backoff for resilient API calls
4
- */
5
-
6
- import { measureAsync } from "./performance.util";
7
-
8
- export interface RetryOptions {
9
- /** Maximum number of retry attempts */
10
- maxAttempts?: number;
11
- /** Initial delay in milliseconds */
12
- initialDelay?: number;
13
- /** Maximum delay in milliseconds */
14
- maxDelay?: number;
15
- /** Exponential backoff multiplier */
16
- backoffMultiplier?: number;
17
- /** Jitter factor to add randomness (0-1) */
18
- jitterFactor?: number;
19
- /** Whether to retry on specific error types */
20
- shouldRetry?: (error: unknown) => boolean;
21
- }
22
-
23
- export interface RetryResult<T> {
24
- result: T;
25
- attempts: number;
26
- totalDuration: number;
27
- }
28
-
29
- const DEFAULT_RETRY_OPTIONS: Required<RetryOptions> = {
30
- maxAttempts: 3,
31
- initialDelay: 1000,
32
- maxDelay: 10000,
33
- backoffMultiplier: 2,
34
- jitterFactor: 0.1,
35
- shouldRetry: () => true,
36
- };
37
-
38
- /**
39
- * Calculate delay with exponential backoff and jitter
40
- */
41
- function calculateDelay(
42
- attempt: number,
43
- options: Required<RetryOptions>
44
- ): number {
45
- const exponentialDelay = options.initialDelay * Math.pow(options.backoffMultiplier, attempt);
46
-
47
- // Apply jitter to prevent thundering herd
48
- const jitter = exponentialDelay * options.jitterFactor * (Math.random() * 2 - 1);
49
-
50
- return Math.min(
51
- Math.max(exponentialDelay + jitter, options.initialDelay),
52
- options.maxDelay
53
- );
54
- }
55
-
56
- /**
57
- * Sleep for a specified duration
58
- */
59
- function sleep(ms: number): Promise<void> {
60
- return new Promise((resolve) => setTimeout(resolve, ms));
61
- }
62
-
63
- /**
64
- * Execute operation with retry logic and exponential backoff
65
- */
66
- export async function retryWithBackoff<T>(
67
- operation: () => Promise<T>,
68
- options: RetryOptions = {}
69
- ): Promise<RetryResult<T>> {
70
- const opts = { ...DEFAULT_RETRY_OPTIONS, ...options };
71
-
72
- let lastError: unknown;
73
-
74
- for (let attempt = 0; attempt < opts.maxAttempts; attempt++) {
75
- try {
76
- const { result, duration } = await measureAsync(operation);
77
-
78
- return {
79
- result,
80
- attempts: attempt + 1,
81
- totalDuration: duration,
82
- };
83
- } catch (error) {
84
- lastError = error;
85
-
86
- // Check if we should retry this error
87
- if (!opts.shouldRetry(error)) {
88
- throw error;
89
- }
90
-
91
- // Don't delay after the last attempt
92
- if (attempt < opts.maxAttempts - 1) {
93
- const delay = calculateDelay(attempt, opts);
94
- await sleep(delay);
95
- }
96
- }
97
- }
98
-
99
- throw lastError;
100
- }
101
-
102
- /**
103
- * Create a retry predicate based on error type/message
104
- */
105
- export function createRetryPredicate(
106
- retryablePatterns: string[]
107
- ): (error: unknown) => boolean {
108
- return (error: unknown) => {
109
- const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
110
- return retryablePatterns.some((pattern) => message.includes(pattern.toLowerCase()));
111
- };
112
- }
113
-
114
- /**
115
- * Default retry predicate for common retryable errors
116
- */
117
- export const shouldRetryNetworkError = createRetryPredicate([
118
- "network",
119
- "timeout",
120
- "rate limit",
121
- "too many requests",
122
- "500",
123
- "502",
124
- "503",
125
- "504",
126
- "econnreset",
127
- "etimedout",
128
- ]);
129
-
130
- /**
131
- * Retry only on specific conditions
132
- */
133
- export async function retryIf<T>(
134
- operation: () => Promise<T>,
135
- shouldRetryFn: (error: unknown) => boolean,
136
- options?: Omit<RetryOptions, "shouldRetry">
137
- ): Promise<RetryResult<T>> {
138
- return retryWithBackoff(operation, {
139
- ...options,
140
- shouldRetry: shouldRetryFn,
141
- });
142
- }
143
-
144
- /**
145
- * Execute with fixed delay between attempts (no exponential backoff)
146
- */
147
- export async function retryWithFixedDelay<T>(
148
- operation: () => Promise<T>,
149
- delay: number = 1000,
150
- maxAttempts: number = 3
151
- ): Promise<RetryResult<T>> {
152
- return retryWithBackoff(operation, {
153
- maxAttempts,
154
- initialDelay: delay,
155
- backoffMultiplier: 1,
156
- jitterFactor: 0,
157
- });
158
- }
@@ -1,36 +0,0 @@
1
-
2
- import { DEFAULT_MODELS } from "../domain/entities";
3
-
4
- export interface ProviderPreferences {
5
- /** Request timeout (ms) */
6
- timeout?: number;
7
- }
8
-
9
- export interface ProviderConfigInput {
10
- /** API key for authentication */
11
- apiKey: string;
12
- /** Optional user preferences */
13
- preferences?: ProviderPreferences;
14
- }
15
-
16
- export interface ResolvedProviderConfig {
17
- apiKey: string;
18
- textModel: string;
19
- timeout: number;
20
- }
21
-
22
- const DEFAULTS = {
23
- timeout: 30000,
24
- };
25
-
26
- export function resolveProviderConfig(
27
- input: ProviderConfigInput,
28
- ): ResolvedProviderConfig {
29
- const preferences = input.preferences || {};
30
-
31
- return {
32
- apiKey: input.apiKey,
33
- textModel: DEFAULT_MODELS.TEXT,
34
- timeout: preferences.timeout ?? DEFAULTS.timeout,
35
- };
36
- }