@umituz/react-native-ai-gemini-provider 2.1.4 → 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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-gemini-provider",
3
- "version": "2.1.4",
3
+ "version": "2.1.6",
4
4
  "description": "Google Gemini AI text generation provider for React Native applications",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
package/src/index.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * @umituz/react-native-ai-gemini-provider
3
3
  * Google Gemini AI provider for React Native applications
4
- * Text generation only - for image/video use FAL Provider
5
4
  */
6
5
 
7
6
  // Domain Types
@@ -21,120 +20,28 @@ export type {
21
20
  GeminiApiError,
22
21
  } from "./domain/entities";
23
22
 
24
- export { GeminiErrorType, GeminiError, GEMINI_MODELS, DEFAULT_MODELS, MODEL_PRICING } from "./domain/entities";
23
+ export {
24
+ GeminiErrorType,
25
+ GeminiError,
26
+ GEMINI_MODELS,
27
+ DEFAULT_MODELS
28
+ } from "./domain/entities";
25
29
 
26
- // Services
30
+ // Main Service
27
31
  export {
28
- geminiClientCoreService,
29
- geminiTextGenerationService,
30
- geminiStructuredTextService,
31
- geminiStreamingService,
32
32
  geminiProviderService,
33
33
  GeminiProvider,
34
34
  } from "./infrastructure/services";
35
35
 
36
36
  export type { GeminiProviderConfig } from "./infrastructure/services";
37
37
 
38
- // Utils
39
- export {
40
- // Error handling
41
- mapGeminiError,
42
- isGeminiErrorRetryable,
43
- categorizeGeminiError,
44
- createGeminiError,
45
- // Data transformation
46
- extractTextFromResponse,
47
- cleanJsonText,
48
- parseJsonResponse,
49
- safeParseJson,
50
- extractJsonFromText,
51
- toSdkContent,
52
- createTextContent,
53
- transformCandidate,
54
- transformResponse,
55
- extractTextFromParts,
56
- // Performance
57
- measureAsync,
58
- measureSync,
59
- debounce,
60
- throttle,
61
- PerformanceTimer,
62
- // Rate limiting
63
- RateLimiter,
64
- // Retry logic
65
- retryWithBackoff,
66
- retryIf,
67
- retryWithFixedDelay,
68
- shouldRetryNetworkError,
69
- createRetryPredicate,
70
- // Validation
71
- validateModelName,
72
- validateApiKey,
73
- validateSchema,
74
- validatePrompt,
75
- validateTimeout,
76
- isValidObject,
77
- validateRequiredFields,
78
- // Environment
79
- getRequiredEnv,
80
- getOptionalEnv,
81
- getEnvNumber,
82
- getEnvBoolean,
83
- loadGeminiEnv,
84
- getApiKeyFromEnv,
85
- isDevelopment,
86
- isDebugEnabled,
87
- validateEnv,
88
- getGeminiConfigFromEnv,
89
- // Async state management
90
- executeWithState,
91
- createDebouncedAsync,
92
- createMemoizedAsync,
93
- } from "./infrastructure/utils";
94
-
95
- export type {
96
- PerformanceMetrics,
97
- RateLimiterOptions,
98
- RetryOptions,
99
- RetryResult,
100
- EnvConfig,
101
- AsyncStateCallbacks,
102
- AsyncStateSetters,
103
- AsyncStateConfig,
104
- } from "./infrastructure/utils";
105
-
106
- // Hooks
38
+ // React Hook
107
39
  export { useGemini } from "./presentation/hooks";
108
-
109
40
  export type { UseGeminiOptions, UseGeminiReturn } from "./presentation/hooks";
110
41
 
111
- // Telemetry
112
- export { telemetryHooks } from "./infrastructure/telemetry";
113
- export type { TelemetryEvent, TelemetryListener } from "./infrastructure/telemetry";
114
-
115
- // Interceptors
116
- export { requestInterceptors, responseInterceptors } from "./infrastructure/interceptors";
117
-
118
- export type {
119
- RequestContext,
120
- RequestInterceptor,
121
- InterceptorErrorStrategy,
122
- } from "./infrastructure/interceptors";
123
-
124
- export type {
125
- ResponseContext,
126
- ResponseInterceptor,
127
- } from "./infrastructure/interceptors/ResponseInterceptors";
128
-
129
- // Provider Config
130
- export {
131
- providerFactory,
132
- resolveProviderConfig,
133
- } from "./providers";
134
-
42
+ // Provider Configuration & Factory
43
+ export { ConfigBuilder, providerFactory } from "./providers";
135
44
  export type {
136
- ProviderPreferences,
137
- ProviderConfigInput,
138
- ResolvedProviderConfig,
45
+ ProviderConfig,
139
46
  ProviderFactoryOptions,
140
47
  } from "./providers";
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Base Interceptor Class
3
+ * Eliminates code duplication between Request and Response interceptors
4
+ */
5
+
6
+ import { telemetryHooks } from "../telemetry";
7
+
8
+ export type InterceptorErrorStrategy = "fail" | "skip" | "log";
9
+
10
+ export interface BaseContext {
11
+ model: string;
12
+ feature?: string;
13
+ timestamp: number;
14
+ }
15
+
16
+ export abstract class BaseInterceptor<TContext extends BaseContext> {
17
+ protected interceptors: Array<(context: TContext) => TContext | Promise<TContext>> = [];
18
+ protected errorStrategy: InterceptorErrorStrategy = "fail";
19
+
20
+ /**
21
+ * Register an interceptor
22
+ */
23
+ use(interceptor: (context: TContext) => TContext | Promise<TContext>): () => void {
24
+ this.interceptors.push(interceptor);
25
+
26
+ // Return unsubscribe function
27
+ return () => {
28
+ const index = this.interceptors.indexOf(interceptor);
29
+ if (index > -1) {
30
+ this.interceptors.splice(index, 1);
31
+ }
32
+ };
33
+ }
34
+
35
+ /**
36
+ * Set error handling strategy
37
+ */
38
+ setErrorStrategy(strategy: InterceptorErrorStrategy): void {
39
+ this.errorStrategy = strategy;
40
+ }
41
+
42
+ /**
43
+ * Clear all interceptors
44
+ */
45
+ clear(): void {
46
+ this.interceptors = [];
47
+ }
48
+
49
+ /**
50
+ * Get interceptor count
51
+ */
52
+ count(): number {
53
+ return this.interceptors.length;
54
+ }
55
+
56
+ /**
57
+ * Handle interceptor error based on strategy
58
+ */
59
+ protected handleError(context: TContext, error: unknown): void {
60
+ telemetryHooks.logError(
61
+ context.model,
62
+ error instanceof Error ? error : new Error(String(error)),
63
+ context.feature
64
+ );
65
+
66
+ if (this.errorStrategy === "fail") {
67
+ throw new Error(
68
+ `Interceptor failed: ${error instanceof Error ? error.message : String(error)}`
69
+ );
70
+ }
71
+ // For "skip" and "log", we just continue (error already logged)
72
+ }
73
+
74
+ /**
75
+ * Apply interceptors - to be implemented by subclasses
76
+ */
77
+ abstract apply(context: TContext): Promise<TContext>;
78
+ }
@@ -1,46 +1,15 @@
1
+ import { BaseInterceptor, type BaseContext } from "./BaseInterceptor";
1
2
 
2
- import { telemetryHooks } from "../telemetry";
3
-
4
- export interface RequestContext {
5
- model: string;
6
- feature?: string;
3
+ export interface RequestContext extends BaseContext {
7
4
  payload: Record<string, unknown>;
8
- timestamp: number;
9
5
  }
10
6
 
11
7
  export type RequestInterceptor = (context: RequestContext) => RequestContext | Promise<RequestContext>;
12
8
 
13
- export type InterceptorErrorStrategy = "fail" | "skip" | "log";
14
-
15
- class RequestInterceptors {
16
- private interceptors: RequestInterceptor[] = [];
17
- private errorStrategy: InterceptorErrorStrategy = "fail";
18
-
19
- /**
20
- * Register a request interceptor
21
- * Interceptors are called in order (first registered = first called)
22
- */
23
- use(interceptor: RequestInterceptor): () => void {
24
- this.interceptors.push(interceptor);
25
-
26
- // Return unsubscribe function
27
- return () => {
28
- const index = this.interceptors.indexOf(interceptor);
29
- if (index > -1) {
30
- this.interceptors.splice(index, 1);
31
- }
32
- };
33
- }
34
-
35
- /**
36
- * Set error handling strategy for interceptors
37
- */
38
- setErrorStrategy(strategy: InterceptorErrorStrategy): void {
39
- this.errorStrategy = strategy;
40
- }
41
-
9
+ class RequestInterceptors extends BaseInterceptor<RequestContext> {
42
10
  /**
43
11
  * Apply all interceptors to a request context
12
+ * Interceptors are called in order (first registered = first called)
44
13
  */
45
14
  async apply(context: RequestContext): Promise<RequestContext> {
46
15
  let result = context;
@@ -49,38 +18,13 @@ class RequestInterceptors {
49
18
  try {
50
19
  result = await interceptor(result);
51
20
  } catch (error) {
52
- // Log to telemetry
53
- telemetryHooks.logError(context.model, error instanceof Error ? error : new Error(String(error)), context.feature);
54
-
55
- switch (this.errorStrategy) {
56
- case "fail":
57
- throw new Error(`Request interceptor failed: ${error instanceof Error ? error.message : String(error)}`);
58
- case "skip":
59
- // Skip this interceptor and continue with previous result
60
- break;
61
- case "log":
62
- // Error already logged, continue with previous result
63
- break;
64
- }
21
+ this.handleError(context, error);
22
+ // If we get here, strategy was "skip" or "log" - continue with previous result
65
23
  }
66
24
  }
67
25
 
68
26
  return result;
69
27
  }
70
-
71
- /**
72
- * Clear all interceptors
73
- */
74
- clear(): void {
75
- this.interceptors = [];
76
- }
77
-
78
- /**
79
- * Get interceptor count
80
- */
81
- count(): number {
82
- return this.interceptors.length;
83
- }
84
28
  }
85
29
 
86
30
  export const requestInterceptors = new RequestInterceptors();
@@ -1,48 +1,18 @@
1
+ import { BaseInterceptor, type BaseContext } from "./BaseInterceptor";
1
2
 
2
- import type { InterceptorErrorStrategy } from "./RequestInterceptors";
3
- import { telemetryHooks } from "../telemetry";
4
-
5
- export interface ResponseContext<T = unknown> {
6
- model: string;
7
- feature?: string;
3
+ export interface ResponseContext<T = unknown> extends BaseContext {
8
4
  data: T;
9
5
  duration: number;
10
- timestamp: number;
11
6
  }
12
7
 
13
8
  export type ResponseInterceptor<T = unknown> = (
14
9
  context: ResponseContext<T>,
15
10
  ) => ResponseContext<T> | Promise<ResponseContext<T>>;
16
11
 
17
- class ResponseInterceptors {
18
- private interceptors: Array<ResponseInterceptor<unknown>> = [];
19
- private errorStrategy: InterceptorErrorStrategy = "fail";
20
-
21
- /**
22
- * Register a response interceptor
23
- * Interceptors are called in reverse order (last registered = first called)
24
- */
25
- use<T = unknown>(interceptor: ResponseInterceptor<T>): () => void {
26
- this.interceptors.push(interceptor as ResponseInterceptor<unknown>);
27
-
28
- // Return unsubscribe function
29
- return () => {
30
- const index = this.interceptors.indexOf(interceptor as ResponseInterceptor<unknown>);
31
- if (index > -1) {
32
- this.interceptors.splice(index, 1);
33
- }
34
- };
35
- }
36
-
37
- /**
38
- * Set error handling strategy for interceptors
39
- */
40
- setErrorStrategy(strategy: InterceptorErrorStrategy): void {
41
- this.errorStrategy = strategy;
42
- }
43
-
12
+ class ResponseInterceptors extends BaseInterceptor<ResponseContext<unknown>> {
44
13
  /**
45
14
  * Apply all interceptors to a response context
15
+ * Interceptors are called in reverse order (last registered = first called)
46
16
  */
47
17
  async apply<T>(context: ResponseContext<T>): Promise<ResponseContext<T>> {
48
18
  let result: ResponseContext<unknown> = context;
@@ -53,38 +23,13 @@ class ResponseInterceptors {
53
23
  try {
54
24
  result = await interceptor(result);
55
25
  } catch (error) {
56
- // Log to telemetry
57
- telemetryHooks.logError(context.model, error instanceof Error ? error : new Error(String(error)), context.feature);
58
-
59
- switch (this.errorStrategy) {
60
- case "fail":
61
- throw new Error(`Response interceptor failed: ${error instanceof Error ? error.message : String(error)}`);
62
- case "skip":
63
- // Skip this interceptor and continue with previous result
64
- break;
65
- case "log":
66
- // Error already logged, continue with previous result
67
- break;
68
- }
26
+ this.handleError(context, error);
27
+ // If we get here, strategy was "skip" or "log" - continue with previous result
69
28
  }
70
29
  }
71
30
 
72
31
  return result as ResponseContext<T>;
73
32
  }
74
-
75
- /**
76
- * Clear all interceptors
77
- */
78
- clear(): void {
79
- this.interceptors = [];
80
- }
81
-
82
- /**
83
- * Get interceptor count
84
- */
85
- count(): number {
86
- return this.interceptors.length;
87
- }
88
33
  }
89
34
 
90
35
  export const responseInterceptors = new ResponseInterceptors();
@@ -1,18 +1,12 @@
1
1
  /**
2
- * Interceptors Module
3
- * Allows applications to modify requests and responses
2
+ * Interceptors Module - Internal Use Only
4
3
  */
5
4
 
6
- export { requestInterceptors } from "./RequestInterceptors";
7
- export { responseInterceptors } from "./ResponseInterceptors";
5
+ export { BaseInterceptor } from "./BaseInterceptor";
6
+ export type { BaseContext, InterceptorErrorStrategy } from "./BaseInterceptor";
8
7
 
9
- export type {
10
- RequestContext,
11
- RequestInterceptor,
12
- InterceptorErrorStrategy,
13
- } from "./RequestInterceptors";
8
+ export { requestInterceptors } from "./RequestInterceptors";
9
+ export type { RequestContext, RequestInterceptor } from "./RequestInterceptors";
14
10
 
15
- export type {
16
- ResponseContext,
17
- ResponseInterceptor,
18
- } from "./ResponseInterceptors";
11
+ export { responseInterceptors } from "./ResponseInterceptors";
12
+ export type { ResponseContext, ResponseInterceptor } from "./ResponseInterceptors";
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Base Gemini Service
3
+ * Common functionality for all Gemini services to eliminate code duplication
4
+ */
5
+
6
+ import { geminiClientCoreService } from "./gemini-client-core.service";
7
+ import { toSdkContent } from "../utils/content-mapper.util";
8
+ import { createGeminiError } from "../utils/error-mapper.util";
9
+ import type { GeminiContent, GeminiGenerationConfig } from "../../domain/entities";
10
+ import type { GenerativeModel } from "@google/generative-ai";
11
+
12
+ /**
13
+ * Base request options structure
14
+ */
15
+ export interface BaseRequestOptions {
16
+ model: string;
17
+ contents: GeminiContent[];
18
+ generationConfig?: GeminiGenerationConfig;
19
+ signal?: AbortSignal;
20
+ }
21
+
22
+ /**
23
+ * Abstract base service with common patterns
24
+ */
25
+ export abstract class BaseGeminiService {
26
+ /**
27
+ * Validate and prepare request
28
+ * Eliminates duplicate validation and abort checks
29
+ */
30
+ protected validateAndPrepare(options: BaseRequestOptions): {
31
+ genModel: GenerativeModel;
32
+ sdkContents: Array<{ role: string; parts: Array<{ text: string }> }>;
33
+ } {
34
+ // Validate contents
35
+ if (!options.contents || options.contents.length === 0) {
36
+ throw new Error("Contents array cannot be empty");
37
+ }
38
+
39
+ // Check for early abort
40
+ if (options.signal?.aborted) {
41
+ throw new Error("Request was aborted");
42
+ }
43
+
44
+ const genModel = geminiClientCoreService.getModel(options.model);
45
+ const sdkContents = toSdkContent(options.contents);
46
+
47
+ return { genModel, sdkContents };
48
+ }
49
+
50
+ /**
51
+ * Handle errors uniformly across all services
52
+ * Eliminates duplicate error handling logic
53
+ */
54
+ protected handleError(error: unknown, abortMessage: string): never {
55
+ // Re-throw as GeminiError if it's already an API error
56
+ if (error instanceof Error && error.name === "GeminiError") {
57
+ throw error;
58
+ }
59
+
60
+ // Check for abort error
61
+ if (error instanceof Error && error.name === "AbortError") {
62
+ throw new Error(abortMessage);
63
+ }
64
+
65
+ // Wrap other errors
66
+ throw createGeminiError(error);
67
+ }
68
+
69
+ /**
70
+ * Create typed request options for SDK
71
+ * Type-safe request creation
72
+ */
73
+ protected createRequestOptions(
74
+ sdkContents: Array<{ role: string; parts: Array<{ text: string }> }>,
75
+ generationConfig?: GeminiGenerationConfig
76
+ ) {
77
+ return {
78
+ contents: sdkContents,
79
+ generationConfig,
80
+ };
81
+ }
82
+ }
@@ -1,14 +1,12 @@
1
-
2
- import { geminiClientCoreService } from "./gemini-client-core.service";
3
- import { toSdkContent } from "../utils/content-mapper.util";
4
- import { createGeminiError } from "../utils/error-mapper.util";
1
+ import { BaseGeminiService } from "./base-gemini.service";
5
2
  import { telemetryHooks } from "../telemetry";
3
+ import { processStream } from "../utils/stream-processor.util";
6
4
  import type {
7
5
  GeminiContent,
8
6
  GeminiGenerationConfig,
9
7
  } from "../../domain/entities";
10
8
 
11
- class GeminiStreamingService {
9
+ class GeminiStreamingService extends BaseGeminiService {
12
10
  /**
13
11
  * Stream content generation
14
12
  *
@@ -31,75 +29,47 @@ class GeminiStreamingService {
31
29
  generationConfig?: GeminiGenerationConfig,
32
30
  signal?: AbortSignal,
33
31
  ): Promise<string> {
34
- // Validate input
35
- if (!contents || contents.length === 0) {
36
- throw new Error("Contents array cannot be empty");
37
- }
38
-
32
+ // Validate callback
39
33
  if (typeof onChunk !== "function") {
40
34
  throw new Error("onChunk must be a function");
41
35
  }
42
36
 
43
- // Check for early abort
44
- if (signal?.aborted) {
45
- throw new Error("Stream generation was aborted");
46
- }
47
-
48
37
  try {
49
- const genModel = geminiClientCoreService.getModel(model);
50
- const sdkContents = toSdkContent(contents);
51
-
52
- const requestOptions = {
53
- contents: sdkContents as Parameters<typeof genModel.generateContentStream>[0] extends { contents: infer C } ? C : never,
38
+ const { genModel, sdkContents } = this.validateAndPrepare({
39
+ model,
40
+ contents,
54
41
  generationConfig,
55
- };
42
+ signal,
43
+ });
44
+
45
+ const requestOptions = this.createRequestOptions(sdkContents, generationConfig);
56
46
 
57
47
  const result = signal
58
48
  ? await genModel.generateContentStream(requestOptions, { signal })
59
49
  : await genModel.generateContentStream(requestOptions);
60
50
 
61
- let fullText = "";
62
-
63
- for await (const chunk of result.stream) {
64
- try {
65
- const chunkText = chunk.text();
66
- if (chunkText) {
67
- fullText += chunkText;
68
- // Safely call onChunk - errors in callback won't break the stream
69
- try {
70
- onChunk(chunkText);
71
- } catch (callbackError) {
72
- try {
73
- telemetryHooks.logError(model, callbackError instanceof Error ? callbackError : new Error(String(callbackError)), "stream-callback");
74
- } catch {
75
- // Silently ignore telemetry errors to prevent breaking the stream
76
- }
77
- }
78
- }
79
- } catch (chunkError) {
80
- // Log chunk error via telemetry, but don't let telemetry errors break the stream
81
- try {
82
- telemetryHooks.logError(model, chunkError instanceof Error ? chunkError : new Error(String(chunkError)), "stream-chunk");
83
- } catch {
84
- // Silently ignore telemetry errors
85
- }
86
- }
87
- }
88
-
89
- return fullText;
51
+ return await processStream(
52
+ result.stream,
53
+ onChunk,
54
+ (error, context) => this.logStreamError(model, error, context)
55
+ );
90
56
  } catch (error) {
91
- // Re-throw as GeminiError if it's an API error
92
- if (error instanceof Error && error.name === "GeminiError") {
93
- throw error;
94
- }
95
-
96
- // Check for abort error
97
- if (error instanceof Error && error.name === "AbortError") {
98
- throw new Error("Stream generation was aborted");
99
- }
57
+ return this.handleError(error, "Stream generation was aborted");
58
+ }
59
+ }
100
60
 
101
- // Wrap other errors
102
- throw createGeminiError(error);
61
+ /**
62
+ * Log stream errors via telemetry
63
+ */
64
+ private logStreamError(model: string, error: unknown, context?: string): void {
65
+ try {
66
+ telemetryHooks.logError(
67
+ model,
68
+ error instanceof Error ? error : new Error(String(error)),
69
+ context
70
+ );
71
+ } catch {
72
+ // Silently ignore telemetry errors
103
73
  }
104
74
  }
105
75
  }