@umituz/react-native-ai-generation-content 1.41.12 → 1.42.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.41.12",
3
+ "version": "1.42.0",
4
4
  "description": "Provider-agnostic AI generation orchestration for React Native with result preview components",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Domain Types
3
+ * Core type definitions for the AI generation content package
4
+ */
5
+
6
+ export type {
7
+ Result,
8
+ Success,
9
+ Failure,
10
+ } from "./result.types";
11
+
12
+ export {
13
+ success,
14
+ failure,
15
+ isSuccess,
16
+ isFailure,
17
+ mapResult,
18
+ andThen,
19
+ unwrap,
20
+ unwrapOr,
21
+ } from "./result.types";
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Result Type Pattern for Functional Error Handling
3
+ * Inspired by Rust's Result<T, E> type
4
+ *
5
+ * @see https://arg-software.medium.com/functional-error-handling-in-typescript-with-the-result-pattern-5b96a5abb6d3
6
+ */
7
+
8
+ /**
9
+ * Success result containing a value of type T
10
+ */
11
+ export interface Success<T> {
12
+ success: true;
13
+ value: T;
14
+ }
15
+
16
+ /**
17
+ * Failure result containing an error of type E
18
+ */
19
+ export interface Failure<E> {
20
+ success: false;
21
+ error: E;
22
+ }
23
+
24
+ /**
25
+ * Result type that can be either Success or Failure
26
+ * Forces explicit error handling at compile time
27
+ */
28
+ export type Result<T, E = string> = Success<T> | Failure<E>;
29
+
30
+ /**
31
+ * Create a successful result
32
+ */
33
+ export function success<T>(value: T): Success<T> {
34
+ return { success: true, value };
35
+ }
36
+
37
+ /**
38
+ * Create a failed result
39
+ */
40
+ export function failure<E>(error: E): Failure<E> {
41
+ return { success: false, error };
42
+ }
43
+
44
+ /**
45
+ * Type guard to check if result is successful
46
+ */
47
+ export function isSuccess<T, E>(result: Result<T, E>): result is Success<T> {
48
+ return result.success === true;
49
+ }
50
+
51
+ /**
52
+ * Type guard to check if result is a failure
53
+ */
54
+ export function isFailure<T, E>(result: Result<T, E>): result is Failure<E> {
55
+ return result.success === false;
56
+ }
57
+
58
+ /**
59
+ * Map a successful result to a new value
60
+ * If result is failure, returns the failure unchanged
61
+ */
62
+ export function mapResult<T, U, E>(
63
+ result: Result<T, E>,
64
+ fn: (value: T) => U,
65
+ ): Result<U, E> {
66
+ if (isSuccess(result)) {
67
+ return success(fn(result.value));
68
+ }
69
+ return result;
70
+ }
71
+
72
+ /**
73
+ * Chain async operations on Result types
74
+ * Similar to Promise.then() but for Result
75
+ */
76
+ export async function andThen<T, U, E>(
77
+ result: Result<T, E>,
78
+ fn: (value: T) => Promise<Result<U, E>>,
79
+ ): Promise<Result<U, E>> {
80
+ if (isSuccess(result)) {
81
+ return fn(result.value);
82
+ }
83
+ return result;
84
+ }
85
+
86
+ /**
87
+ * Unwrap a result, throwing if it's a failure
88
+ * Use only when you're certain the result is successful
89
+ */
90
+ export function unwrap<T, E>(result: Result<T, E>): T {
91
+ if (isSuccess(result)) {
92
+ return result.value;
93
+ }
94
+ throw new Error(`Called unwrap on a failure: ${String(result.error)}`);
95
+ }
96
+
97
+ /**
98
+ * Unwrap a result or return a default value
99
+ */
100
+ export function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T {
101
+ if (isSuccess(result)) {
102
+ return result.value;
103
+ }
104
+ return defaultValue;
105
+ }
@@ -41,7 +41,7 @@ import type {
41
41
  export function useAIFeatureGate(
42
42
  options: AIFeatureGateOptions,
43
43
  ): AIFeatureGateReturn {
44
- const { creditCost, featureName, onSuccess, onError } = options;
44
+ const { creditCost, onSuccess, onError } = options;
45
45
 
46
46
  // Auth state
47
47
  const { isAuthenticated: rawIsAuthenticated, isAnonymous } = useAuth();
@@ -1,22 +1,10 @@
1
1
  /**
2
2
  * Image-to-Video Executor
3
- * Provider-agnostic image-to-video execution using active AI provider
4
- * Uses progress mapper for consistent progress reporting
3
+ * Provider-agnostic image-to-video execution using Template Method pattern
5
4
  */
6
5
 
7
- import { providerRegistry } from "../../../../infrastructure/services";
8
-
9
- /** Map job status to progress percentage */
10
- const getProgressFromJobStatus = (status: string): number => {
11
- switch (status.toLowerCase()) {
12
- case "queued": return 10;
13
- case "in_queue": return 15;
14
- case "processing": return 50;
15
- case "in_progress": return 60;
16
- case "completed": return 100;
17
- default: return 30;
18
- }
19
- };
6
+ import { BaseExecutor } from "../../../../infrastructure/executors/base-executor";
7
+ import { isSuccess, type Result } from "../../../../domain/types/result.types";
20
8
  import type {
21
9
  ImageToVideoRequest,
22
10
  ImageToVideoResult,
@@ -24,8 +12,9 @@ import type {
24
12
  ImageToVideoResultExtractor,
25
13
  } from "../../domain/types";
26
14
 
27
- declare const __DEV__: boolean;
28
-
15
+ /**
16
+ * Options for image-to-video execution
17
+ */
29
18
  export interface ExecuteImageToVideoOptions {
30
19
  model: string;
31
20
  buildInput: ImageToVideoInputBuilder;
@@ -33,9 +22,40 @@ export interface ExecuteImageToVideoOptions {
33
22
  onProgress?: (progress: number) => void;
34
23
  }
35
24
 
25
+ /**
26
+ * Extracted result structure from provider response
27
+ */
28
+ interface ExtractedVideoResult {
29
+ videoUrl?: string;
30
+ thumbnailUrl?: string;
31
+ }
32
+
33
+ /**
34
+ * Map job status to progress percentage
35
+ */
36
+ const getProgressFromJobStatus = (status: string): number => {
37
+ switch (status.toLowerCase()) {
38
+ case "queued":
39
+ return 10;
40
+ case "in_queue":
41
+ return 15;
42
+ case "processing":
43
+ return 50;
44
+ case "in_progress":
45
+ return 60;
46
+ case "completed":
47
+ return 100;
48
+ default:
49
+ return 30;
50
+ }
51
+ };
52
+
53
+ /**
54
+ * Default extractor for image-to-video results
55
+ */
36
56
  function defaultExtractResult(
37
57
  result: unknown,
38
- ): { videoUrl?: string; thumbnailUrl?: string } | undefined {
58
+ ): ExtractedVideoResult | undefined {
39
59
  if (typeof result !== "object" || result === null) return undefined;
40
60
 
41
61
  const r = result as Record<string, unknown>;
@@ -58,108 +78,116 @@ function defaultExtractResult(
58
78
  return undefined;
59
79
  }
60
80
 
61
- export async function executeImageToVideo(
62
- request: ImageToVideoRequest,
63
- options: ExecuteImageToVideoOptions,
64
- ): Promise<ImageToVideoResult> {
65
- if (typeof __DEV__ !== "undefined" && __DEV__) {
66
-
67
- console.log("[ImageToVideoExecutor] executeImageToVideo() called");
68
- }
69
-
70
- const provider = providerRegistry.getActiveProvider();
71
-
72
- if (!provider) {
73
- if (typeof __DEV__ !== "undefined" && __DEV__) {
74
-
75
- console.error("[ImageToVideoExecutor] No AI provider configured");
76
- }
77
- return { success: false, error: "No AI provider configured" };
81
+ /**
82
+ * Image-to-Video Executor using Template Method pattern
83
+ * Eliminates code duplication through BaseExecutor
84
+ */
85
+ class ImageToVideoExecutor extends BaseExecutor<
86
+ ImageToVideoRequest,
87
+ ImageToVideoResult,
88
+ ExtractedVideoResult
89
+ > {
90
+ constructor() {
91
+ super("ImageToVideo");
78
92
  }
79
93
 
80
- if (!provider.isInitialized()) {
81
- if (typeof __DEV__ !== "undefined" && __DEV__) {
82
-
83
- console.error("[ImageToVideoExecutor] AI provider not initialized");
94
+ protected validateRequest(
95
+ request: ImageToVideoRequest,
96
+ ): string | undefined {
97
+ if (!request.imageBase64) {
98
+ return "Image base64 is required";
84
99
  }
85
- return { success: false, error: "AI provider not initialized" };
86
- }
87
-
88
- if (!request.imageBase64) {
89
- return { success: false, error: "Image base64 is required" };
100
+ return undefined;
90
101
  }
91
102
 
92
- const { model, buildInput, extractResult, onProgress } = options;
93
-
94
- if (typeof __DEV__ !== "undefined" && __DEV__) {
95
-
96
- console.log(`[ImageToVideoExecutor] Provider: ${provider.providerId}, Model: ${model}`);
97
- }
98
-
99
- try {
100
- onProgress?.(5);
101
-
102
- if (typeof __DEV__ !== "undefined" && __DEV__) {
103
-
104
- console.log("[ImageToVideoExecutor] Starting provider.subscribe()...");
105
- }
106
-
107
- // Build input directly - let buildInput handle base64 format
108
- const input = buildInput(request.imageBase64, request.motionPrompt, request.options);
103
+ protected async executeProvider(
104
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
+ provider: any,
106
+ model: string,
107
+ input: unknown,
108
+ onProgress?: (progress: number) => void,
109
+ ): Promise<unknown> {
110
+ this.logInfo("Starting provider.subscribe()...");
109
111
 
110
112
  // Use subscribe for video generation (long-running operation with queue)
111
- // subscribe provides progress updates unlike run()
113
+ // Provider reports real progress via onQueueUpdate callback
112
114
  const result = await provider.subscribe(model, input, {
113
- onQueueUpdate: (status) => {
114
- if (typeof __DEV__ !== "undefined" && __DEV__) {
115
-
116
- console.log("[ImageToVideoExecutor] Queue status:", status.status, "position:", status.queuePosition);
117
- }
118
- // Map provider status to progress using centralized mapper
115
+ onQueueUpdate: (status: { status: string; queuePosition?: number }) => {
116
+ this.logInfo(
117
+ `Queue status: ${status.status}, position: ${status.queuePosition}`,
118
+ );
119
+ // Map provider status to progress
119
120
  const progress = getProgressFromJobStatus(status.status);
120
121
  onProgress?.(progress);
121
122
  },
122
123
  timeoutMs: 300000, // 5 minutes timeout for video generation
123
124
  });
124
125
 
125
- if (typeof __DEV__ !== "undefined" && __DEV__) {
126
-
127
- console.log("[ImageToVideoExecutor] Subscribe resolved, result keys:", result ? Object.keys(result as object) : "null");
128
- }
129
-
130
- if (typeof __DEV__ !== "undefined" && __DEV__) {
131
-
132
- console.log("[ImageToVideoExecutor] provider.subscribe() completed");
133
- }
134
-
135
- const extractor = extractResult || defaultExtractResult;
136
- const extracted = extractor(result);
137
- onProgress?.(100);
126
+ this.logInfo(
127
+ `Subscribe resolved, result keys: ${result ? Object.keys(result as object) : "null"}`,
128
+ );
129
+ return result;
130
+ }
138
131
 
132
+ protected validateExtractedResult(
133
+ extracted: ExtractedVideoResult | undefined,
134
+ ): string | undefined {
139
135
  if (!extracted?.videoUrl) {
140
- if (typeof __DEV__ !== "undefined" && __DEV__) {
141
-
142
- console.error("[ImageToVideoExecutor] No video URL in response");
143
- }
144
- return { success: false, error: "No video in response" };
136
+ return "No video in response";
145
137
  }
138
+ return undefined;
139
+ }
146
140
 
141
+ protected transformResult(
142
+ extracted: ExtractedVideoResult,
143
+ ): ImageToVideoResult {
147
144
  return {
148
145
  success: true,
149
146
  videoUrl: extracted.videoUrl,
150
147
  thumbnailUrl: extracted.thumbnailUrl,
151
148
  };
152
- } catch (error) {
153
- const message = error instanceof Error ? error.message : String(error);
154
- if (typeof __DEV__ !== "undefined" && __DEV__) {
155
-
156
- console.error("[ImageToVideoExecutor] Error:", message);
157
- }
158
- return { success: false, error: message };
149
+ }
150
+
151
+ protected getDefaultExtractor(): (
152
+ result: unknown,
153
+ ) => ExtractedVideoResult | undefined {
154
+ return defaultExtractResult;
159
155
  }
160
156
  }
161
157
 
158
+ // Singleton instance
159
+ const executor = new ImageToVideoExecutor();
160
+
161
+ /**
162
+ * Execute image-to-video generation
163
+ * Public API maintained for backwards compatibility
164
+ */
165
+ export async function executeImageToVideo(
166
+ request: ImageToVideoRequest,
167
+ options: ExecuteImageToVideoOptions,
168
+ ): Promise<ImageToVideoResult> {
169
+ const result: Result<ImageToVideoResult, string> = await executor.execute(
170
+ request,
171
+ {
172
+ model: options.model,
173
+ buildInput: (req) =>
174
+ options.buildInput(req.imageBase64!, req.motionPrompt, req.options),
175
+ extractResult: options.extractResult,
176
+ onProgress: options.onProgress,
177
+ },
178
+ );
179
+
180
+ // Convert Result<T, E> back to legacy format for backwards compatibility
181
+ if (isSuccess(result)) {
182
+ return result.value;
183
+ }
184
+ return { success: false, error: result.error };
185
+ }
186
+
187
+ /**
188
+ * Check if image-to-video is supported
189
+ * Public API maintained for backwards compatibility
190
+ */
162
191
  export function hasImageToVideoSupport(): boolean {
163
- const provider = providerRegistry.getActiveProvider();
164
- return provider !== null && provider.isInitialized();
192
+ return executor.hasSupport();
165
193
  }
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Text-to-Image Executor
3
- * Provider-agnostic text-to-image execution using active AI provider
3
+ * Provider-agnostic text-to-image execution using Template Method pattern
4
4
  */
5
5
 
6
- import { providerRegistry } from "../../../../infrastructure/services";
6
+ import { BaseExecutor } from "../../../../infrastructure/executors/base-executor";
7
+ import { isSuccess, type Result } from "../../../../domain/types/result.types";
7
8
  import type {
8
9
  TextToImageRequest,
9
10
  TextToImageResult,
@@ -11,8 +12,9 @@ import type {
11
12
  TextToImageResultExtractor,
12
13
  } from "../../domain/types";
13
14
 
14
- declare const __DEV__: boolean;
15
-
15
+ /**
16
+ * Options for text-to-image execution
17
+ */
16
18
  export interface ExecuteTextToImageOptions {
17
19
  model: string;
18
20
  buildInput: TextToImageInputBuilder;
@@ -20,6 +22,17 @@ export interface ExecuteTextToImageOptions {
20
22
  onProgress?: (progress: number) => void;
21
23
  }
22
24
 
25
+ /**
26
+ * Extracted result structure from provider response
27
+ */
28
+ interface ExtractedImageResult {
29
+ imageUrl?: string;
30
+ imageUrls?: string[];
31
+ }
32
+
33
+ /**
34
+ * Extract images from provider response object
35
+ */
23
36
  function extractImagesFromObject(
24
37
  obj: Record<string, unknown>,
25
38
  ): string[] | null {
@@ -40,9 +53,12 @@ function extractImagesFromObject(
40
53
  return null;
41
54
  }
42
55
 
56
+ /**
57
+ * Default extractor for text-to-image results
58
+ */
43
59
  function defaultExtractResult(
44
60
  result: unknown,
45
- ): { imageUrl?: string; imageUrls?: string[] } | undefined {
61
+ ): ExtractedImageResult | undefined {
46
62
  if (typeof result !== "object" || result === null) {
47
63
  return undefined;
48
64
  }
@@ -84,64 +100,93 @@ function defaultExtractResult(
84
100
  return undefined;
85
101
  }
86
102
 
87
- export async function executeTextToImage(
88
- request: TextToImageRequest,
89
- options: ExecuteTextToImageOptions,
90
- ): Promise<TextToImageResult> {
91
- const provider = providerRegistry.getActiveProvider();
92
-
93
- if (!provider) {
94
- return { success: false, error: "No AI provider configured" };
95
- }
96
-
97
- if (!provider.isInitialized()) {
98
- return { success: false, error: "AI provider not initialized" };
103
+ /**
104
+ * Text-to-Image Executor using Template Method pattern
105
+ * Eliminates code duplication through BaseExecutor
106
+ */
107
+ class TextToImageExecutor extends BaseExecutor<
108
+ TextToImageRequest,
109
+ TextToImageResult,
110
+ ExtractedImageResult
111
+ > {
112
+ constructor() {
113
+ super("TextToImage");
99
114
  }
100
115
 
101
- if (!request.prompt) {
102
- return { success: false, error: "Prompt is required" };
116
+ protected validateRequest(request: TextToImageRequest): string | undefined {
117
+ if (!request.prompt) {
118
+ return "Prompt is required";
119
+ }
120
+ return undefined;
103
121
  }
104
122
 
105
- const { model, buildInput, extractResult, onProgress } = options;
106
-
107
- if (typeof __DEV__ !== "undefined" && __DEV__) {
108
-
109
- console.log(`[TextToImage] Provider: ${provider.providerId}, Model: ${model}`);
123
+ protected async executeProvider(
124
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
125
+ provider: any,
126
+ model: string,
127
+ input: unknown,
128
+ ): Promise<unknown> {
129
+ return provider.run(model, input);
110
130
  }
111
131
 
112
- try {
113
- onProgress?.(10);
114
-
115
- const input = buildInput(request.prompt, request.options);
116
- onProgress?.(20);
117
-
118
- const result = await provider.run(model, input);
119
- onProgress?.(90);
120
-
121
- const extractor = extractResult || defaultExtractResult;
122
- const extracted = extractor(result);
123
- onProgress?.(100);
124
-
132
+ protected validateExtractedResult(
133
+ extracted: ExtractedImageResult | undefined,
134
+ ): string | undefined {
125
135
  if (!extracted?.imageUrl) {
126
- return { success: false, error: "No image in response" };
136
+ return "No image in response";
127
137
  }
138
+ return undefined;
139
+ }
128
140
 
141
+ protected transformResult(
142
+ extracted: ExtractedImageResult,
143
+ ): TextToImageResult {
129
144
  return {
130
145
  success: true,
131
146
  imageUrl: extracted.imageUrl,
132
147
  imageUrls: extracted.imageUrls,
133
148
  };
134
- } catch (error) {
135
- const message = error instanceof Error ? error.message : String(error);
136
- if (typeof __DEV__ !== "undefined" && __DEV__) {
137
-
138
- console.error("[TextToImage] Error:", message);
139
- }
140
- return { success: false, error: message };
149
+ }
150
+
151
+ protected getDefaultExtractor(): (
152
+ result: unknown,
153
+ ) => ExtractedImageResult | undefined {
154
+ return defaultExtractResult;
141
155
  }
142
156
  }
143
157
 
158
+ // Singleton instance
159
+ const executor = new TextToImageExecutor();
160
+
161
+ /**
162
+ * Execute text-to-image generation
163
+ * Public API maintained for backwards compatibility
164
+ */
165
+ export async function executeTextToImage(
166
+ request: TextToImageRequest,
167
+ options: ExecuteTextToImageOptions,
168
+ ): Promise<TextToImageResult> {
169
+ const result: Result<TextToImageResult, string> = await executor.execute(
170
+ request,
171
+ {
172
+ model: options.model,
173
+ buildInput: (req) => options.buildInput(req.prompt, req.options),
174
+ extractResult: options.extractResult,
175
+ onProgress: options.onProgress,
176
+ },
177
+ );
178
+
179
+ // Convert Result<T, E> back to legacy format for backwards compatibility
180
+ if (isSuccess(result)) {
181
+ return result.value;
182
+ }
183
+ return { success: false, error: result.error };
184
+ }
185
+
186
+ /**
187
+ * Check if text-to-image is supported
188
+ * Public API maintained for backwards compatibility
189
+ */
144
190
  export function hasTextToImageSupport(): boolean {
145
- const provider = providerRegistry.getActiveProvider();
146
- return provider !== null && provider.isInitialized();
191
+ return executor.hasSupport();
147
192
  }
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Text-to-Video Executor
3
- * Single Responsibility: Execute text-to-video using active AI provider
3
+ * Provider-agnostic text-to-video execution using Template Method pattern
4
4
  */
5
5
 
6
- import { providerRegistry } from "../../../../infrastructure/services";
6
+ import { BaseExecutor } from "../../../../infrastructure/executors/base-executor";
7
+ import { isSuccess, type Result } from "../../../../domain/types/result.types";
7
8
  import type {
8
9
  TextToVideoRequest,
9
10
  TextToVideoResult,
@@ -13,6 +14,9 @@ import type {
13
14
 
14
15
  declare const __DEV__: boolean;
15
16
 
17
+ /**
18
+ * Options for text-to-video execution
19
+ */
16
20
  export interface ExecuteTextToVideoOptions {
17
21
  model: string;
18
22
  buildInput: TextToVideoInputBuilder;
@@ -20,9 +24,20 @@ export interface ExecuteTextToVideoOptions {
20
24
  onProgress?: (progress: number) => void;
21
25
  }
22
26
 
27
+ /**
28
+ * Extracted result structure from provider response
29
+ */
30
+ interface ExtractedVideoResult {
31
+ videoUrl?: string;
32
+ thumbnailUrl?: string;
33
+ }
34
+
35
+ /**
36
+ * Default extractor for text-to-video results
37
+ */
23
38
  function defaultExtractResult(
24
39
  result: unknown,
25
- ): { videoUrl?: string; thumbnailUrl?: string } | undefined {
40
+ ): ExtractedVideoResult | undefined {
26
41
  if (typeof result !== "object" || result === null) return undefined;
27
42
 
28
43
  const r = result as Record<string, unknown>;
@@ -45,97 +60,108 @@ function defaultExtractResult(
45
60
  return undefined;
46
61
  }
47
62
 
48
- export async function executeTextToVideo(
49
- request: TextToVideoRequest,
50
- options: ExecuteTextToVideoOptions,
51
- ): Promise<TextToVideoResult> {
52
- if (typeof __DEV__ !== "undefined" && __DEV__) {
53
-
54
- console.log("[TextToVideoExecutor] executeTextToVideo() called");
55
- }
56
-
57
- const provider = providerRegistry.getActiveProvider();
58
-
59
- if (!provider) {
60
- if (typeof __DEV__ !== "undefined" && __DEV__) {
61
-
62
- console.error("[TextToVideoExecutor] No AI provider configured");
63
- }
64
- return { success: false, error: "No AI provider configured" };
63
+ /**
64
+ * Text-to-Video Executor using Template Method pattern
65
+ * Eliminates code duplication through BaseExecutor
66
+ */
67
+ class TextToVideoExecutor extends BaseExecutor<
68
+ TextToVideoRequest,
69
+ TextToVideoResult,
70
+ ExtractedVideoResult
71
+ > {
72
+ constructor() {
73
+ super("TextToVideo");
65
74
  }
66
75
 
67
- if (!provider.isInitialized()) {
68
- if (typeof __DEV__ !== "undefined" && __DEV__) {
69
-
70
- console.error("[TextToVideoExecutor] AI provider not initialized");
76
+ protected validateRequest(request: TextToVideoRequest): string | undefined {
77
+ if (!request.prompt) {
78
+ return "Prompt is required";
71
79
  }
72
- return { success: false, error: "AI provider not initialized" };
73
- }
74
-
75
- if (!request.prompt) {
76
- return { success: false, error: "Prompt is required" };
77
- }
78
-
79
- const { model, buildInput, extractResult, onProgress } = options;
80
-
81
- if (typeof __DEV__ !== "undefined" && __DEV__) {
82
-
83
- console.log(`[TextToVideoExecutor] Provider: ${provider.providerId}, Model: ${model}`);
80
+ return undefined;
84
81
  }
85
82
 
86
- try {
87
- onProgress?.(5);
88
- if (typeof __DEV__ !== "undefined" && __DEV__) {
89
-
90
- console.log("[TextToVideoExecutor] Starting provider.run()...");
91
- }
92
-
93
- const input = buildInput(request.prompt, request.options);
83
+ protected async executeProvider(
84
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
85
+ provider: any,
86
+ model: string,
87
+ input: unknown,
88
+ onProgress?: (progress: number) => void,
89
+ ): Promise<unknown> {
90
+ this.logInfo("Starting provider.run()...");
94
91
 
92
+ // Provider reports real progress via callback
95
93
  const result = await provider.run(model, input, {
96
- onProgress: (progressInfo) => {
94
+ onProgress: (progressInfo: { progress: number }) => {
97
95
  const progressValue = progressInfo.progress;
98
96
  if (typeof __DEV__ !== "undefined" && __DEV__) {
99
-
100
- console.log("[TextToVideoExecutor] Progress:", progressValue);
97
+ console.log("[TextToVideo] Progress:", progressValue);
101
98
  }
102
99
  onProgress?.(progressValue);
103
100
  },
104
101
  });
105
102
 
106
- if (typeof __DEV__ !== "undefined" && __DEV__) {
107
-
108
- console.log("[TextToVideoExecutor] provider.run() completed", result);
109
- }
110
-
111
- const extractor = extractResult || defaultExtractResult;
112
- const extracted = extractor(result);
113
- onProgress?.(100);
103
+ this.logInfo("provider.run() completed");
104
+ return result;
105
+ }
114
106
 
107
+ protected validateExtractedResult(
108
+ extracted: ExtractedVideoResult | undefined,
109
+ ): string | undefined {
115
110
  if (!extracted?.videoUrl) {
116
- if (typeof __DEV__ !== "undefined" && __DEV__) {
117
-
118
- console.error("[TextToVideoExecutor] No video URL in response");
119
- }
120
- return { success: false, error: "No video in response" };
111
+ return "No video in response";
121
112
  }
113
+ return undefined;
114
+ }
122
115
 
116
+ protected transformResult(
117
+ extracted: ExtractedVideoResult,
118
+ ): TextToVideoResult {
123
119
  return {
124
120
  success: true,
125
121
  videoUrl: extracted.videoUrl,
126
122
  thumbnailUrl: extracted.thumbnailUrl,
127
123
  };
128
- } catch (error) {
129
- const message = error instanceof Error ? error.message : String(error);
130
- if (typeof __DEV__ !== "undefined" && __DEV__) {
131
-
132
- console.error("[TextToVideoExecutor] Error:", message);
133
- }
134
- return { success: false, error: message };
124
+ }
125
+
126
+ protected getDefaultExtractor(): (
127
+ result: unknown,
128
+ ) => ExtractedVideoResult | undefined {
129
+ return defaultExtractResult;
135
130
  }
136
131
  }
137
132
 
133
+ // Singleton instance
134
+ const executor = new TextToVideoExecutor();
135
+
136
+ /**
137
+ * Execute text-to-video generation
138
+ * Public API maintained for backwards compatibility
139
+ */
140
+ export async function executeTextToVideo(
141
+ request: TextToVideoRequest,
142
+ options: ExecuteTextToVideoOptions,
143
+ ): Promise<TextToVideoResult> {
144
+ const result: Result<TextToVideoResult, string> = await executor.execute(
145
+ request,
146
+ {
147
+ model: options.model,
148
+ buildInput: (req) => options.buildInput(req.prompt, req.options),
149
+ extractResult: options.extractResult,
150
+ onProgress: options.onProgress,
151
+ },
152
+ );
153
+
154
+ // Convert Result<T, E> back to legacy format for backwards compatibility
155
+ if (isSuccess(result)) {
156
+ return result.value;
157
+ }
158
+ return { success: false, error: result.error };
159
+ }
160
+
161
+ /**
162
+ * Check if text-to-video is supported
163
+ * Public API maintained for backwards compatibility
164
+ */
138
165
  export function hasTextToVideoSupport(): boolean {
139
- const provider = providerRegistry.getActiveProvider();
140
- return provider !== null && provider.isInitialized();
166
+ return executor.hasSupport();
141
167
  }
package/src/index.ts CHANGED
@@ -5,6 +5,18 @@
5
5
 
6
6
  if (typeof __DEV__ !== "undefined" && __DEV__) console.log("📍 [LIFECYCLE] @umituz/react-native-ai-generation-content/index.ts - Module loading");
7
7
 
8
+ // Result Type Pattern - Functional error handling
9
+ export type {
10
+ Result, Success, Failure,
11
+ } from "./domain/types";
12
+ export {
13
+ success, failure, isSuccess, isFailure, mapResult, andThen, unwrap, unwrapOr,
14
+ } from "./domain/types";
15
+
16
+ // Base Executor - Template Method Pattern
17
+ export { BaseExecutor } from "./infrastructure/executors";
18
+ export type { BaseExecutorOptions } from "./infrastructure/executors";
19
+
8
20
  export type {
9
21
  AIProviderConfig, IAIProvider, JobSubmission, JobStatus, AIJobStatusType, AILogEntry,
10
22
  SubscribeOptions, RunOptions, ImageFeatureType, VideoFeatureType, ImageFeatureInputData,
@@ -0,0 +1,169 @@
1
+ /**
2
+ * BaseExecutor - Template Method Pattern
3
+ * Eliminates code duplication across 8+ executor implementations
4
+ *
5
+ * @see https://refactoring.guru/design-patterns/template-method/typescript/example
6
+ *
7
+ * Template Method Pattern defines skeleton of algorithm in base class,
8
+ * letting subclasses override specific steps without changing structure.
9
+ */
10
+
11
+ import { providerRegistry } from "../services";
12
+ import { failure, success, type Result } from "../../domain/types/result.types";
13
+
14
+ declare const __DEV__: boolean;
15
+
16
+ /**
17
+ * Base options that all executors share
18
+ */
19
+ export interface BaseExecutorOptions<TInput, TOutput> {
20
+ model: string;
21
+ buildInput: (request: TInput) => unknown;
22
+ extractResult?: (result: unknown) => TOutput | undefined;
23
+ onProgress?: (progress: number) => void;
24
+ }
25
+
26
+ /**
27
+ * Abstract base class for all AI feature executors
28
+ * Implements Template Method pattern for execution flow
29
+ */
30
+ export abstract class BaseExecutor<TRequest, TResult, TOutput> {
31
+ protected readonly logPrefix: string;
32
+
33
+ constructor(logPrefix: string) {
34
+ this.logPrefix = logPrefix;
35
+ }
36
+
37
+ /**
38
+ * Template Method - defines the execution algorithm skeleton
39
+ * This is the main method that orchestrates the execution flow
40
+ */
41
+ public async execute(
42
+ request: TRequest,
43
+ options: BaseExecutorOptions<TRequest, TOutput>,
44
+ ): Promise<Result<TResult, string>> {
45
+ // Step 1: Get provider
46
+ const provider = providerRegistry.getActiveProvider();
47
+ if (!provider) {
48
+ this.logError("No AI provider configured");
49
+ return failure("No AI provider configured");
50
+ }
51
+
52
+ // Step 2: Check initialization
53
+ if (!provider.isInitialized()) {
54
+ this.logError("AI provider not initialized");
55
+ return failure("AI provider not initialized");
56
+ }
57
+
58
+ // Step 3: Validate request (subclass-specific)
59
+ const validationError = this.validateRequest(request);
60
+ if (validationError) {
61
+ this.logError(validationError);
62
+ return failure(validationError);
63
+ }
64
+
65
+ // Step 4: Log execution start
66
+ this.logInfo(`Provider: ${provider.providerId}, Model: ${options.model}`);
67
+
68
+ // Step 5: Execute with error handling
69
+ try {
70
+ // Build input
71
+ const input = options.buildInput(request);
72
+
73
+ // Execute provider call (subclass-specific)
74
+ // Provider handles all progress reporting via callbacks
75
+ const providerResult = await this.executeProvider(
76
+ provider,
77
+ options.model,
78
+ input,
79
+ options.onProgress,
80
+ );
81
+
82
+ // Extract result
83
+ const extractor = options.extractResult || this.getDefaultExtractor();
84
+ const extracted = extractor(providerResult);
85
+
86
+ // Validate extracted result (subclass-specific)
87
+ const resultValidationError = this.validateExtractedResult(extracted);
88
+ if (resultValidationError) {
89
+ this.logError(resultValidationError);
90
+ return failure(resultValidationError);
91
+ }
92
+
93
+ // Transform to final result (subclass-specific)
94
+ return success(this.transformResult(extracted as TOutput));
95
+ } catch (error) {
96
+ const message = error instanceof Error ? error.message : String(error);
97
+ this.logError(`Error: ${message}`);
98
+ return failure(message);
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Check if feature is supported by current provider
104
+ */
105
+ public hasSupport(): boolean {
106
+ const provider = providerRegistry.getActiveProvider();
107
+ return provider !== null && provider.isInitialized();
108
+ }
109
+
110
+ // ==================== Abstract Methods (Must Override) ====================
111
+
112
+ /**
113
+ * Validate the incoming request
114
+ * Return error message if invalid, undefined if valid
115
+ */
116
+ protected abstract validateRequest(request: TRequest): string | undefined;
117
+
118
+ /**
119
+ * Execute the provider call (run or subscribe)
120
+ * Subclasses implement their specific provider interaction
121
+ */
122
+ protected abstract executeProvider(
123
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
124
+ provider: any,
125
+ model: string,
126
+ input: unknown,
127
+ onProgress?: (progress: number) => void,
128
+ ): Promise<unknown>;
129
+
130
+ /**
131
+ * Validate the extracted result
132
+ * Return error message if invalid, undefined if valid
133
+ */
134
+ protected abstract validateExtractedResult(
135
+ extracted: TOutput | undefined,
136
+ ): string | undefined;
137
+
138
+ /**
139
+ * Transform extracted output to final result type
140
+ */
141
+ protected abstract transformResult(extracted: TOutput): TResult;
142
+
143
+ /**
144
+ * Get default result extractor for this executor type
145
+ */
146
+ protected abstract getDefaultExtractor(): (
147
+ result: unknown,
148
+ ) => TOutput | undefined;
149
+
150
+ // ==================== Utility Methods ====================
151
+
152
+ /**
153
+ * Log info message (development only)
154
+ */
155
+ protected logInfo(message: string): void {
156
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
157
+ console.log(`[${this.logPrefix}] ${message}`);
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Log error message (development only)
163
+ */
164
+ protected logError(message: string): void {
165
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
166
+ console.error(`[${this.logPrefix}] ${message}`);
167
+ }
168
+ }
169
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Executors
3
+ * Base executor abstraction for AI features
4
+ */
5
+
6
+ export { BaseExecutor } from "./base-executor";
7
+ export type { BaseExecutorOptions } from "./base-executor";