@umituz/react-native-ai-fal-provider 3.2.51 → 3.2.53

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 (27) hide show
  1. package/package.json +1 -4
  2. package/src/domain/services/ErrorClassificationService.ts +201 -0
  3. package/src/domain/services/ImageProcessingService.ts +89 -0
  4. package/src/domain/services/PricingService.ts +101 -0
  5. package/src/domain/services/ValidationService.ts +132 -0
  6. package/src/domain/services/index.ts +13 -0
  7. package/src/index.ts +24 -22
  8. package/src/infrastructure/utils/fal-error-handler.util.ts +14 -146
  9. package/src/infrastructure/utils/fal-storage.util.ts +2 -3
  10. package/src/infrastructure/utils/helpers/index.ts +2 -4
  11. package/src/infrastructure/utils/image-helpers.util.ts +13 -27
  12. package/src/infrastructure/utils/input-preprocessor.util.ts +6 -12
  13. package/src/infrastructure/utils/input-validator.util.ts +17 -156
  14. package/src/infrastructure/utils/pricing/fal-pricing.util.ts +26 -53
  15. package/src/infrastructure/utils/type-guards/index.ts +2 -2
  16. package/src/shared/helpers.ts +149 -0
  17. package/src/shared/index.ts +8 -0
  18. package/src/shared/type-guards.ts +122 -0
  19. package/src/shared/validators.ts +281 -0
  20. package/src/infrastructure/utils/helpers/calculation-helpers.util.ts +0 -168
  21. package/src/infrastructure/utils/helpers/error-helpers.util.ts +0 -65
  22. package/src/infrastructure/utils/helpers/object-helpers.util.ts +0 -44
  23. package/src/infrastructure/utils/helpers/timing-helpers.util.ts +0 -11
  24. package/src/infrastructure/utils/type-guards/model-type-guards.util.ts +0 -56
  25. package/src/infrastructure/utils/type-guards/validation-guards.util.ts +0 -101
  26. package/src/infrastructure/utils/validators/data-uri-validator.util.ts +0 -91
  27. package/src/infrastructure/utils/validators/string-validator.util.ts +0 -64
@@ -1,174 +1,42 @@
1
1
  /**
2
- * FAL Error Handler
3
- * Uses @fal-ai/client error types (ApiError, ValidationError) for proper error handling
4
- * No silent fallbacks - errors are categorized explicitly
2
+ * FAL Error Handler (Infrastructure Layer)
3
+ * Delegates to domain ErrorClassificationService for error handling logic
4
+ *
5
+ * This file now serves as a thin adapter layer for backward compatibility.
6
+ * The actual error handling logic has been moved to the domain layer.
5
7
  */
6
8
 
7
- import { ApiError, ValidationError } from "@fal-ai/client";
9
+ import { ErrorClassificationService } from "../../domain/services/ErrorClassificationService";
8
10
  import type { FalErrorInfo, FalErrorCategory } from "../../domain/entities/error.types";
9
- import { FalErrorType } from "../../domain/entities/error.types";
10
-
11
- /**
12
- * HTTP status code to error type mapping
13
- */
14
- const STATUS_TO_ERROR_TYPE: Record<number, FalErrorType> = {
15
- 400: FalErrorType.VALIDATION,
16
- 401: FalErrorType.AUTHENTICATION,
17
- 402: FalErrorType.QUOTA_EXCEEDED,
18
- 403: FalErrorType.AUTHENTICATION,
19
- 404: FalErrorType.MODEL_NOT_FOUND,
20
- 422: FalErrorType.VALIDATION,
21
- 429: FalErrorType.RATE_LIMIT,
22
- 500: FalErrorType.API_ERROR,
23
- 502: FalErrorType.API_ERROR,
24
- 503: FalErrorType.API_ERROR,
25
- 504: FalErrorType.API_ERROR,
26
- };
27
-
28
- const RETRYABLE_TYPES = new Set<FalErrorType>([
29
- FalErrorType.NETWORK,
30
- FalErrorType.TIMEOUT,
31
- FalErrorType.RATE_LIMIT,
32
- ]);
33
-
34
- /**
35
- * Message-based error type detection (for non-ApiError errors)
36
- */
37
- const MESSAGE_PATTERNS: Array<{ type: FalErrorType; patterns: string[] }> = [
38
- { type: FalErrorType.NETWORK, patterns: ["network", "fetch", "econnrefused", "enotfound", "etimedout"] },
39
- { type: FalErrorType.TIMEOUT, patterns: ["timeout", "timed out"] },
40
- { type: FalErrorType.CONTENT_POLICY, patterns: ["nsfw", "content_policy", "content policy", "policy violation"] },
41
- { type: FalErrorType.IMAGE_TOO_SMALL, patterns: ["image_too_small", "image dimensions are too small", "minimum dimensions"] },
42
- ];
43
-
44
- /**
45
- * Categorize error using @fal-ai/client error types
46
- * Priority: ApiError status code > message pattern matching
47
- */
48
- function categorizeError(error: unknown): FalErrorCategory {
49
- // 1. ApiError (includes ValidationError) - use HTTP status code
50
- if (error instanceof ApiError) {
51
- const typeFromStatus = STATUS_TO_ERROR_TYPE[error.status];
52
- if (typeFromStatus) {
53
- return {
54
- type: typeFromStatus,
55
- messageKey: typeFromStatus,
56
- retryable: RETRYABLE_TYPES.has(typeFromStatus),
57
- };
58
- }
59
- // Unknown status code - still an API error
60
- return { type: FalErrorType.API_ERROR, messageKey: "api_error", retryable: false };
61
- }
62
-
63
- // 2. Standard Error - match message patterns
64
- const message = (error instanceof Error ? error.message : error != null ? String(error) : "unknown").toLowerCase();
65
-
66
- for (const { type, patterns } of MESSAGE_PATTERNS) {
67
- if (patterns.some((p) => message.includes(p))) {
68
- return { type, messageKey: type, retryable: RETRYABLE_TYPES.has(type) };
69
- }
70
- }
71
-
72
- // 3. No match - UNKNOWN, not retryable
73
- return { type: FalErrorType.UNKNOWN, messageKey: "unknown", retryable: false };
74
- }
75
-
76
- /**
77
- * Extract user-readable message from error
78
- * Uses @fal-ai/client types for structured extraction
79
- */
80
- function extractMessage(error: unknown): string {
81
- // ValidationError - extract field-level messages
82
- if (error instanceof ValidationError) {
83
- const fieldErrors = error.fieldErrors;
84
- if (Array.isArray(fieldErrors) && fieldErrors.length > 0) {
85
- // Safely extract messages from field errors with validation
86
- const messages = fieldErrors
87
- .map((e) => (e && typeof e === 'object' && 'msg' in e && typeof e.msg === 'string' ? e.msg : null))
88
- .filter((msg): msg is string => msg !== null);
89
- if (messages.length > 0) return messages.join("; ");
90
- }
91
- return error.message;
92
- }
93
-
94
- // ApiError - extract from body or message
95
- if (error instanceof ApiError) {
96
- // body may contain detail array - validate structure before access
97
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
98
- const body = error.body;
99
-
100
- // Type guard for detail array structure
101
- interface DetailItem {
102
- msg?: string;
103
- }
104
- interface BodyWithDetail {
105
- detail?: DetailItem[];
106
- }
107
-
108
- const validatedBody = body as BodyWithDetail | undefined;
109
-
110
- if (
111
- validatedBody &&
112
- validatedBody.detail &&
113
- Array.isArray(validatedBody.detail) &&
114
- validatedBody.detail.length > 0
115
- ) {
116
- const firstItem = validatedBody.detail[0];
117
- if (
118
- firstItem &&
119
- typeof firstItem === "object" &&
120
- typeof firstItem.msg === "string" &&
121
- firstItem.msg
122
- ) {
123
- return firstItem.msg;
124
- }
125
- }
126
- return error.message;
127
- }
128
-
129
- // Standard Error
130
- if (error instanceof Error) {
131
- return error.message;
132
- }
133
-
134
- return error != null ? String(error) : "Unknown error";
135
- }
136
11
 
137
12
  /**
138
13
  * Map error to FalErrorInfo with full categorization
14
+ * Delegates to domain ErrorClassificationService
139
15
  */
140
16
  export function mapFalError(error: unknown): FalErrorInfo {
141
- const category = categorizeError(error);
142
- const message = extractMessage(error);
143
-
144
- return {
145
- type: category.type,
146
- messageKey: `fal.errors.${category.messageKey}`,
147
- retryable: category.retryable,
148
- originalError: message,
149
- originalErrorName: error instanceof Error ? error.name : undefined,
150
- stack: error instanceof Error ? error.stack : undefined,
151
- statusCode: error instanceof ApiError ? error.status : undefined,
152
- };
17
+ return ErrorClassificationService.mapError(error);
153
18
  }
154
19
 
155
20
  /**
156
21
  * Parse FAL error and return user-friendly message
22
+ * Delegates to domain ErrorClassificationService
157
23
  */
158
24
  export function parseFalError(error: unknown): string {
159
- return extractMessage(error);
25
+ return ErrorClassificationService.extractMessage(error);
160
26
  }
161
27
 
162
28
  /**
163
29
  * Categorize FAL error
30
+ * Delegates to domain ErrorClassificationService
164
31
  */
165
32
  export function categorizeFalError(error: unknown): FalErrorCategory {
166
- return categorizeError(error);
33
+ return ErrorClassificationService.categorizeError(error);
167
34
  }
168
35
 
169
36
  /**
170
37
  * Check if FAL error is retryable
38
+ * Delegates to domain ErrorClassificationService
171
39
  */
172
40
  export function isFalErrorRetryable(error: unknown): boolean {
173
- return categorizeError(error).retryable;
41
+ return ErrorClassificationService.isRetryable(error);
174
42
  }
@@ -9,8 +9,7 @@ import {
9
9
  base64ToTempFile,
10
10
  deleteTempFile,
11
11
  } from "@umituz/react-native-design-system/filesystem";
12
- import { getErrorMessage } from './helpers/error-helpers.util';
13
- import { getElapsedTime, getActualSizeKB } from './helpers';
12
+ import { getErrorMessage, getElapsedTime, getActualSizeKB, sleep } from "../../shared/helpers";
14
13
  import { generationLogCollector } from './log-collector';
15
14
  import { UPLOAD_CONFIG } from '../services/fal-provider.constants';
16
15
 
@@ -43,7 +42,7 @@ async function withRetry<T>(
43
42
  if (attempt > 0) {
44
43
  const delay = baseDelay * Math.pow(2, attempt - 1);
45
44
  generationLogCollector.warn(sessionId, TAG, `Retry ${attempt}/${maxRetries} for ${label} after ${delay}ms`);
46
- await new Promise(resolve => setTimeout(resolve, delay));
45
+ await sleep(delay);
47
46
  }
48
47
  return await fn();
49
48
  } catch (error) {
@@ -1,8 +1,6 @@
1
1
  /**
2
2
  * Helper Utilities - Centralized Exports
3
+ * Re-exports from shared utilities layer for backward compatibility
3
4
  */
4
5
 
5
- export * from './timing-helpers.util';
6
- export * from './object-helpers.util';
7
- export * from './calculation-helpers.util';
8
- export * from './error-helpers.util';
6
+ export * from '../../../shared/helpers';
@@ -1,47 +1,33 @@
1
1
  /**
2
- * Image Helper Utilities
3
- * Functions for image data URI manipulation
2
+ * Image Helper Utilities (Infrastructure Layer)
3
+ * Delegates to domain ImageProcessingService for image data manipulation
4
+ *
5
+ * This file now serves as a thin adapter layer for backward compatibility.
6
+ * The actual image processing logic has been moved to the domain layer.
4
7
  */
5
8
 
9
+ import { ImageProcessingService } from "../../domain/services/ImageProcessingService";
10
+
6
11
  /**
7
12
  * Format image as data URI if not already formatted
13
+ * Delegates to domain ImageProcessingService
8
14
  */
9
15
  export function formatImageDataUri(base64: string): string {
10
- if (base64.startsWith("data:")) {
11
- return base64;
12
- }
13
- return `data:image/jpeg;base64,${base64}`;
16
+ return ImageProcessingService.formatImageDataUri(base64);
14
17
  }
15
18
 
16
19
  /**
17
20
  * Extract base64 from data URI
18
- * Uses indexOf instead of split to handle edge cases where comma might appear in base64
21
+ * Delegates to domain ImageProcessingService
19
22
  */
20
23
  export function extractBase64(dataUri: string): string {
21
- if (!dataUri.startsWith("data:")) {
22
- return dataUri;
23
- }
24
-
25
- // Find the first comma which separates header from data
26
- const commaIndex = dataUri.indexOf(",");
27
- if (commaIndex === -1) {
28
- throw new Error(`Invalid data URI format (no comma separator): ${dataUri.substring(0, 50)}...`);
29
- }
30
-
31
- // Extract everything after the first comma
32
- const base64Part = dataUri.substring(commaIndex + 1);
33
-
34
- if (!base64Part || base64Part.length === 0) {
35
- throw new Error(`Empty base64 data in URI: ${dataUri.substring(0, 50)}...`);
36
- }
37
-
38
- return base64Part;
24
+ return ImageProcessingService.extractBase64(dataUri);
39
25
  }
40
26
 
41
27
  /**
42
28
  * Get file extension from data URI
29
+ * Delegates to domain ImageProcessingService
43
30
  */
44
31
  export function getDataUriExtension(dataUri: string): string | null {
45
- const match = dataUri.match(/^data:image\/(\w+);base64/);
46
- return match ? match[1] : null;
32
+ return ImageProcessingService.getDataUriExtension(dataUri);
47
33
  }
@@ -8,19 +8,13 @@
8
8
  */
9
9
 
10
10
  import { uploadToFalStorage, uploadLocalFileToFalStorage } from "./fal-storage.util";
11
- import { getErrorMessage } from './helpers/error-helpers.util';
11
+ import { getErrorMessage, getElapsedTime } from "../../shared/helpers";
12
12
  import { IMAGE_URL_FIELDS } from './constants/image-fields.constants';
13
- import { isImageDataUri as isBase64DataUri } from './validators/data-uri-validator.util';
13
+ import { isLocalFileUri, isImageDataUri as isBase64DataUri } from "../../shared/type-guards";
14
14
  import { generationLogCollector } from './log-collector';
15
15
 
16
16
  const TAG = 'preprocessor';
17
17
 
18
- function isLocalFileUri(value: unknown): value is string {
19
- return typeof value === "string" && (
20
- value.startsWith("file://") || value.startsWith("content://")
21
- );
22
- }
23
-
24
18
  /**
25
19
  * Classify a network error into a user-friendly message.
26
20
  * Technical details are preserved in Firestore logs/session subcollection.
@@ -130,7 +124,7 @@ export async function preprocessInput(
130
124
  processedUrls.push(url);
131
125
  generationLogCollector.log(sessionId, TAG, `${arrayField}[${i}/${imageUrls.length}]: upload OK`);
132
126
  } catch (error) {
133
- const elapsed = Date.now() - arrayStartTime;
127
+ const elapsed = getElapsedTime(arrayStartTime);
134
128
  const technicalMsg = getErrorMessage(error);
135
129
  generationLogCollector.error(sessionId, TAG, `${arrayField}[${i}] upload FAILED after ${elapsed}ms: ${technicalMsg}`);
136
130
  throw new Error(classifyUploadError(technicalMsg));
@@ -143,7 +137,7 @@ export async function preprocessInput(
143
137
  processedUrls.push(url);
144
138
  generationLogCollector.log(sessionId, TAG, `${arrayField}[${i}/${imageUrls.length}]: local file upload OK`);
145
139
  } catch (error) {
146
- const elapsed = Date.now() - arrayStartTime;
140
+ const elapsed = getElapsedTime(arrayStartTime);
147
141
  const technicalMsg = getErrorMessage(error);
148
142
  generationLogCollector.error(sessionId, TAG, `${arrayField}[${i}] local file upload FAILED after ${elapsed}ms: ${technicalMsg}`);
149
143
  throw new Error(classifyUploadError(technicalMsg));
@@ -156,7 +150,7 @@ export async function preprocessInput(
156
150
  }
157
151
  }
158
152
 
159
- const arrayElapsed = Date.now() - arrayStartTime;
153
+ const arrayElapsed = getElapsedTime(arrayStartTime);
160
154
  generationLogCollector.log(sessionId, TAG, `${arrayField}: all ${processedUrls.length} upload(s) succeeded in ${arrayElapsed}ms`);
161
155
  result[arrayField] = processedUrls;
162
156
  }
@@ -179,7 +173,7 @@ export async function preprocessInput(
179
173
  }
180
174
  }
181
175
 
182
- const totalElapsed = Date.now() - startTime;
176
+ const totalElapsed = getElapsedTime(startTime);
183
177
  generationLogCollector.log(sessionId, TAG, `Preprocessing complete in ${totalElapsed}ms`);
184
178
  return result;
185
179
  }
@@ -1,168 +1,29 @@
1
1
  /**
2
- * Input Validator Utility
3
- * Validates input parameters before API calls
2
+ * Input Validator Utility (Infrastructure Layer)
3
+ * Delegates to domain ValidationService for actual validation logic
4
+ *
5
+ * This file now serves as a thin adapter layer for backward compatibility.
6
+ * The actual validation logic has been moved to the domain layer.
4
7
  */
5
8
 
6
- import { isValidPrompt } from "./type-guards";
7
- import { IMAGE_URL_FIELDS } from './constants/image-fields.constants';
8
- import { isImageDataUri } from './validators/data-uri-validator.util';
9
- import { isNonEmptyString } from './validators/string-validator.util';
9
+ import { ValidationService } from "../../domain/services";
10
+ import type { ValidationError } from "../../domain/services/ValidationService";
10
11
 
11
- const SUSPICIOUS_PATTERNS = [
12
- /<script/i,
13
- /javascript:/i,
14
- /on\w+\s*=/i,
15
- /<iframe/i,
16
- /<embed/i,
17
- /<object/i,
18
- /data:(?!image\/)/i,
19
- /vbscript:/i,
20
- ] as const;
21
-
22
- function hasSuspiciousContent(value: string): boolean {
23
- return SUSPICIOUS_PATTERNS.some(pattern => pattern.test(value));
24
- }
25
-
26
- /**
27
- * Validate URL format and protocol
28
- * Rejects malicious URLs and unsafe protocols
29
- */
30
- function isValidAndSafeUrl(value: string): boolean {
31
- // Allow http/https URLs
32
- if (value.startsWith('http://') || value.startsWith('https://')) {
33
- try {
34
- const url = new URL(value);
35
- // Reject URLs with @ (potential auth bypass: http://attacker.com@internal.server/)
36
- if (url.href.includes('@') && url.username) {
37
- return false;
38
- }
39
- // Ensure domain exists
40
- if (!url.hostname || url.hostname.length === 0) {
41
- return false;
42
- }
43
- return true;
44
- } catch {
45
- return false;
46
- }
47
- }
48
-
49
- // Allow local file URIs (file://, content://) — preprocessInput uploads them to FAL storage
50
- if (value.startsWith('file://') || value.startsWith('content://')) {
51
- return true;
52
- }
53
-
54
- // Allow base64 image data URIs only
55
- if (isImageDataUri(value)) {
56
- // Check for suspicious content in data URI
57
- const dataContent = value.substring(0, 200); // Check first 200 chars
58
- if (hasSuspiciousContent(dataContent)) {
59
- return false;
60
- }
61
- return true;
62
- }
63
-
64
- return false;
65
- }
66
-
67
- export interface ValidationError {
68
- field: string;
69
- message: string;
70
- }
71
-
72
- export class InputValidationError extends Error {
73
- public readonly errors: readonly ValidationError[];
74
-
75
- constructor(errors: ValidationError[]) {
76
- const message = errors.map((e) => `${e.field}: ${e.message}`).join("; ");
77
- super(`Input validation failed: ${message}`);
78
- this.name = "InputValidationError";
79
- this.errors = errors;
80
- }
81
- }
12
+ // Re-export for backward compatibility
13
+ export { InputValidationError } from "../../domain/services/ValidationService";
14
+ export type { ValidationError };
82
15
 
83
16
  /**
84
17
  * Validate model and input parameters
18
+ * Delegates to domain ValidationService
19
+ *
20
+ * @param model - Model ID
21
+ * @param input - Input parameters to validate
22
+ * @throws InputValidationError if validation fails
85
23
  */
86
24
  export function validateInput(
87
- _model: string,
25
+ model: string,
88
26
  input: Record<string, unknown>
89
27
  ): void {
90
- const errors: ValidationError[] = [];
91
-
92
- // Validate input is not empty
93
- if (!input || typeof input !== "object" || Object.keys(input).length === 0) {
94
- errors.push({ field: "input", message: "Input must be a non-empty object" });
95
- }
96
-
97
- // BLOCK sync_mode:true — it causes FAL to return base64 data URIs instead of CDN URLs
98
- if (input.sync_mode === true) {
99
- errors.push({
100
- field: "sync_mode",
101
- message: "sync_mode:true is forbidden. It returns base64 data URIs instead of HTTPS CDN URLs, which breaks Firestore persistence (2048 char limit). Use falProvider.subscribe() for CDN URLs.",
102
- });
103
- }
104
-
105
- // Validate and check prompt for malicious content
106
- if (input.prompt !== undefined) {
107
- if (!isValidPrompt(input.prompt)) {
108
- errors.push({
109
- field: "prompt",
110
- message: "Prompt must be a non-empty string (max 5000 characters)",
111
- });
112
- } else if (typeof input.prompt === "string") {
113
- // Check for suspicious content (defense in depth)
114
- if (hasSuspiciousContent(input.prompt)) {
115
- errors.push({
116
- field: "prompt",
117
- message: "Prompt contains potentially unsafe content (script tags, event handlers, or suspicious protocols)",
118
- });
119
- }
120
- }
121
- }
122
-
123
- // Validate and check negative_prompt for malicious content
124
- if (input.negative_prompt !== undefined) {
125
- if (!isValidPrompt(input.negative_prompt)) {
126
- errors.push({
127
- field: "negative_prompt",
128
- message: "Negative prompt must be a non-empty string (max 5000 characters)",
129
- });
130
- } else if (typeof input.negative_prompt === "string") {
131
- // Check for suspicious content (defense in depth)
132
- if (hasSuspiciousContent(input.negative_prompt)) {
133
- errors.push({
134
- field: "negative_prompt",
135
- message: "Negative prompt contains potentially unsafe content (script tags, event handlers, or suspicious protocols)",
136
- });
137
- }
138
- }
139
- }
140
-
141
- // Validate all image_url fields
142
- for (const field of IMAGE_URL_FIELDS) {
143
- const value = input[field];
144
- if (value !== undefined) {
145
- if (typeof value !== "string") {
146
- errors.push({
147
- field,
148
- message: `${field} must be a string`,
149
- });
150
- } else if (!isNonEmptyString(value)) {
151
- // Explicitly check for empty/whitespace-only strings
152
- errors.push({
153
- field,
154
- message: `${field} cannot be empty`,
155
- });
156
- } else if (!isValidAndSafeUrl(value)) {
157
- errors.push({
158
- field,
159
- message: `${field} must be a valid and safe URL (http/https) or image data URI. Suspicious content or unsafe protocols detected.`,
160
- });
161
- }
162
- }
163
- }
164
-
165
- if (errors.length > 0) {
166
- throw new InputValidationError(errors);
167
- }
28
+ ValidationService.validateInput(model, input);
168
29
  }
@@ -1,71 +1,44 @@
1
- import type { VideoModelConfig } from "@umituz/react-native-ai-generation-content";
2
-
3
1
  /**
4
- * FAL AI Pricing Utilities
5
- * Single Responsibility: Credit calculations for FAL AI generation
2
+ * FAL AI Pricing Utilities (Infrastructure Layer)
3
+ * Delegates to domain PricingService for credit calculation logic
6
4
  *
7
- * Pricing:
8
- * - 480p video: $0.05/sec
9
- * - 720p video: $0.07/sec
10
- * - Image with input: +$0.002
11
- * - Image generation: $0.03
12
- * Markup: 3.5x, Credit price: $0.10
5
+ * This file now serves as a thin adapter layer for backward compatibility.
6
+ * The actual pricing logic has been moved to the domain layer.
13
7
  */
14
8
 
15
- const COSTS = {
16
- VIDEO_480P_PER_SECOND: 0.05,
17
- VIDEO_720P_PER_SECOND: 0.07,
18
- IMAGE_INPUT: 0.002,
19
- IMAGE: 0.03,
20
- } as const;
21
-
22
- const MARKUP = 3.5;
23
- const CREDIT_PRICE = 0.1;
9
+ import { PricingService } from "../../../domain/services/PricingService";
24
10
 
25
- export type GenerationResolution = "480p" | "720p";
11
+ // Re-export types for backward compatibility
12
+ export type { GenerationResolution } from "../../../domain/services/PricingService";
26
13
 
14
+ /**
15
+ * Calculate credits for video generation
16
+ * Delegates to domain PricingService
17
+ */
27
18
  export function calculateVideoCredits(
28
19
  duration: number,
29
- resolution: GenerationResolution,
30
- hasImageInput: boolean = false,
20
+ resolution: "480p" | "720p",
21
+ hasImageInput?: boolean
31
22
  ): number {
32
- const costPerSec =
33
- resolution === "480p"
34
- ? COSTS.VIDEO_480P_PER_SECOND
35
- : COSTS.VIDEO_720P_PER_SECOND;
36
- let cost = costPerSec * duration;
37
- if (hasImageInput) cost += COSTS.IMAGE_INPUT;
38
- return Math.max(1, Math.ceil((cost * MARKUP) / CREDIT_PRICE));
23
+ return PricingService.calculateVideoCredits(duration, resolution, hasImageInput);
39
24
  }
40
25
 
26
+ /**
27
+ * Calculate credits for image generation
28
+ * Delegates to domain PricingService
29
+ */
41
30
  export function calculateImageCredits(): number {
42
- return Math.max(1, Math.ceil((COSTS.IMAGE * MARKUP) / CREDIT_PRICE));
31
+ return PricingService.calculateImageCredits();
43
32
  }
44
33
 
34
+ /**
35
+ * Calculate credits from video model config
36
+ * Delegates to domain PricingService
37
+ */
45
38
  export function calculateCreditsFromConfig(
46
- config: VideoModelConfig,
39
+ config: import("@umituz/react-native-ai-generation-content").VideoModelConfig,
47
40
  duration: number,
48
- resolution: string,
41
+ resolution: string
49
42
  ): number {
50
- // Validate config structure before accessing nested properties
51
- if (
52
- !config ||
53
- typeof config !== "object" ||
54
- !config.pricing ||
55
- typeof config.pricing !== "object" ||
56
- !config.pricing.costPerSecond ||
57
- typeof config.pricing.costPerSecond !== "object"
58
- ) {
59
- throw new Error("Invalid VideoModelConfig: pricing structure is missing or invalid");
60
- }
61
-
62
- const costPerSecondMap = config.pricing.costPerSecond;
63
- const costPerSec = costPerSecondMap[resolution] ?? 0;
64
-
65
- if (typeof costPerSec !== "number" || costPerSec < 0) {
66
- throw new Error(`Invalid cost per second for resolution "${resolution}": must be a non-negative number`);
67
- }
68
-
69
- const cost = costPerSec * duration;
70
- return Math.max(1, Math.ceil((cost * MARKUP) / CREDIT_PRICE));
43
+ return PricingService.calculateCreditsFromConfig(config, duration, resolution);
71
44
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Type Guards - Centralized Exports
3
+ * Re-exports from shared utilities layer for backward compatibility
3
4
  */
4
5
 
5
- export * from './model-type-guards.util';
6
- export * from './validation-guards.util';
6
+ export * from '../../../shared/type-guards';