@umituz/react-native-ai-generation-content 1.61.56 → 1.61.58

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 (35) hide show
  1. package/package.json +1 -1
  2. package/src/domains/content-moderation/infrastructure/constants/moderation.constants.ts +4 -2
  3. package/src/domains/content-moderation/infrastructure/services/moderators/voice.moderator.ts +2 -3
  4. package/src/domains/content-moderation/infrastructure/services/pattern-matcher.service.ts +3 -2
  5. package/src/domains/generation/infrastructure/executors/image-executor.ts +2 -1
  6. package/src/domains/generation/infrastructure/executors/video-executor.ts +2 -1
  7. package/src/domains/generation/wizard/infrastructure/strategies/wizard-strategy.constants.ts +3 -2
  8. package/src/domains/generation/wizard/presentation/hooks/useVideoQueueGeneration.ts +9 -0
  9. package/src/domains/generation/wizard/presentation/hooks/useWizardFlowHandlers.ts +28 -1
  10. package/src/domains/generation/wizard/presentation/hooks/useWizardGeneration.ts +21 -2
  11. package/src/features/image-to-video/infrastructure/services/image-to-video-executor.ts +2 -1
  12. package/src/infrastructure/config/env.config.ts +67 -0
  13. package/src/infrastructure/config/index.ts +2 -0
  14. package/src/infrastructure/constants/content.constants.ts +3 -1
  15. package/src/infrastructure/constants/polling.constants.ts +11 -9
  16. package/src/infrastructure/constants/storage.constants.ts +4 -2
  17. package/src/infrastructure/constants/validation.constants.ts +14 -12
  18. package/src/infrastructure/http/api-client.types.ts +17 -0
  19. package/src/infrastructure/http/http-client.util.ts +167 -0
  20. package/src/infrastructure/http/index.ts +16 -0
  21. package/src/infrastructure/http/query-string.util.ts +40 -0
  22. package/src/infrastructure/http/timeout.util.ts +21 -0
  23. package/src/infrastructure/services/multi-image-generation.executor.ts +3 -2
  24. package/src/infrastructure/utils/api-client.util.ts +15 -204
  25. package/src/infrastructure/utils/index.ts +1 -0
  26. package/src/infrastructure/utils/progress.utils.ts +60 -0
  27. package/src/presentation/components/GenerationProgressBar.tsx +3 -2
  28. package/src/presentation/components/PendingJobProgressBar.tsx +3 -1
  29. package/src/presentation/hooks/generation/orchestrator.ts +10 -3
  30. package/src/presentation/hooks/index.ts +3 -0
  31. package/src/presentation/hooks/useProgressDismiss.ts +42 -0
  32. package/src/presentation/layouts/DualImageFeatureLayout.tsx +5 -12
  33. package/src/presentation/layouts/DualImageVideoFeatureLayout.tsx +5 -12
  34. package/src/presentation/layouts/SingleImageFeatureLayout.tsx +5 -13
  35. package/src/presentation/layouts/SingleImageWithPromptFeatureLayout.tsx +5 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.61.56",
3
+ "version": "1.61.58",
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",
@@ -3,7 +3,9 @@
3
3
  * Shared constants for content moderation validators
4
4
  */
5
5
 
6
+ import { env } from "../../../../infrastructure/config/env.config";
7
+
6
8
  export const DEFAULT_PROTOCOLS = ["http:", "https:", "file:", "data:"] as const;
7
9
  export const VIDEO_PROTOCOLS = ["http:", "https:", "file:"] as const;
8
- export const DEFAULT_MAX_URI_LENGTH = 2048;
9
- export const DEFAULT_MAX_TEXT_LENGTH = 5000;
10
+ export const DEFAULT_MAX_URI_LENGTH = env.moderationMaxUriLength;
11
+ export const DEFAULT_MAX_TEXT_LENGTH = env.moderationMaxTextLength;
@@ -7,11 +7,10 @@ import type { Violation } from "../../../domain/entities/moderation.types";
7
7
  import { patternMatcherService } from "../pattern-matcher.service";
8
8
  import { rulesRegistry } from "../../rules/rules-registry";
9
9
  import { BaseModerator, type ModerationResult } from "./base.moderator";
10
-
11
- const DEFAULT_MAX_LENGTH = 5000;
10
+ import { env } from "../../../../../infrastructure/config/env.config";
12
11
 
13
12
  class VoiceModerator extends BaseModerator {
14
- private maxLength = DEFAULT_MAX_LENGTH;
13
+ private maxLength = env.moderationVoiceMaxLength;
15
14
 
16
15
  setMaxLength(length: number): void {
17
16
  this.maxLength = length;
@@ -3,6 +3,8 @@
3
3
  * Utility service for regex pattern matching with security validations
4
4
  */
5
5
 
6
+ import { env } from "../../../../infrastructure/config/env.config";
7
+
6
8
  export interface PatternMatch {
7
9
  pattern: string;
8
10
  matched: boolean;
@@ -34,8 +36,7 @@ function isValidRegexPattern(pattern: string): boolean {
34
36
  }
35
37
 
36
38
  // Limit pattern length to prevent potential attacks
37
- const MAX_PATTERN_LENGTH = 1000;
38
- if (pattern.length > MAX_PATTERN_LENGTH) {
39
+ if (pattern.length > env.validationMaxPatternLength) {
39
40
  return false;
40
41
  }
41
42
 
@@ -11,6 +11,7 @@ import type {
11
11
  } from "../../domain/generation.types";
12
12
  import type { GenerationResult } from "../../../../domain/entities/generation.types";
13
13
  import { providerRegistry } from "../../../../infrastructure/services/provider-registry.service";
14
+ import { env } from "../../../../infrastructure/config/env.config";
14
15
 
15
16
  declare const __DEV__: boolean;
16
17
 
@@ -56,7 +57,7 @@ export class ImageExecutor
56
57
  options?.onProgress?.(10);
57
58
 
58
59
  const result = await provider.subscribe(model, modelInput, {
59
- timeoutMs: options?.timeoutMs ?? 300000,
60
+ timeoutMs: options?.timeoutMs ?? env.generationImageTimeoutMs,
60
61
  onQueueUpdate: (status) => {
61
62
  if (typeof __DEV__ !== "undefined" && __DEV__) {
62
63
  console.log("[ImageExecutor] Queue status:", status.status);
@@ -11,6 +11,7 @@ import type {
11
11
  } from "../../domain/generation.types";
12
12
  import type { GenerationResult } from "../../../../domain/entities/generation.types";
13
13
  import { providerRegistry } from "../../../../infrastructure/services/provider-registry.service";
14
+ import { env } from "../../../../infrastructure/config/env.config";
14
15
 
15
16
  declare const __DEV__: boolean;
16
17
 
@@ -48,7 +49,7 @@ export class VideoExecutor
48
49
  }
49
50
 
50
51
  const result = await provider.subscribe(model, modelInput, {
51
- timeoutMs: options?.timeoutMs ?? 300000,
52
+ timeoutMs: options?.timeoutMs ?? env.generationVideoTimeoutMs,
52
53
  onQueueUpdate: (status) => {
53
54
  if (typeof __DEV__ !== "undefined" && __DEV__) {
54
55
  console.log("[VideoExecutor] Queue status:", status.status);
@@ -4,9 +4,10 @@
4
4
  */
5
5
 
6
6
  import type { VideoFeatureType } from "../../../../../domain/interfaces";
7
+ import { env } from "../../../../../infrastructure/config/env.config";
7
8
 
8
- /** Generation timeout in milliseconds (2 minutes) */
9
- export const GENERATION_TIMEOUT_MS = 120000;
9
+ /** Generation timeout in milliseconds */
10
+ export const GENERATION_TIMEOUT_MS = env.generationImageTimeoutMs;
10
11
 
11
12
  /** Base64 image format prefix */
12
13
  export const BASE64_IMAGE_PREFIX = "data:image/jpeg;base64,";
@@ -41,6 +41,7 @@ export function useVideoQueueGeneration(
41
41
  const modelRef = useRef<string | null>(null);
42
42
  const pollingRef = useRef<ReturnType<typeof setInterval> | null>(null);
43
43
  const isGeneratingRef = useRef(false);
44
+ const isPollingRef = useRef(false);
44
45
  const [isGenerating, setIsGenerating] = useState(false);
45
46
 
46
47
  // Cleanup polling on unmount
@@ -58,6 +59,7 @@ export function useVideoQueueGeneration(
58
59
  requestIdRef.current = null;
59
60
  modelRef.current = null;
60
61
  isGeneratingRef.current = false;
62
+ isPollingRef.current = false;
61
63
  setIsGenerating(false);
62
64
  }, []);
63
65
 
@@ -98,11 +100,15 @@ export function useVideoQueueGeneration(
98
100
  );
99
101
 
100
102
  const pollQueueStatus = useCallback(async () => {
103
+ // Guard against concurrent polls
104
+ if (isPollingRef.current) return;
105
+
101
106
  const requestId = requestIdRef.current;
102
107
  const model = modelRef.current;
103
108
  const provider = providerRegistry.getActiveProvider();
104
109
  if (!requestId || !model || !provider) return;
105
110
 
111
+ isPollingRef.current = true;
106
112
  try {
107
113
  const status = await provider.getJobStatus(model, requestId);
108
114
  if (typeof __DEV__ !== "undefined" && __DEV__) console.log("[VideoQueueGeneration] Poll:", status.status);
@@ -133,6 +139,8 @@ export function useVideoQueueGeneration(
133
139
  console.error("[VideoQueueGeneration] Poll error:", errorMessage);
134
140
  }
135
141
  await handleError(errorMessage);
142
+ } finally {
143
+ isPollingRef.current = false;
136
144
  }
137
145
  }, [handleComplete, handleError]);
138
146
 
@@ -179,6 +187,7 @@ export function useVideoQueueGeneration(
179
187
  }
180
188
 
181
189
  pollingRef.current = setInterval(() => void pollQueueStatus(), DEFAULT_POLL_INTERVAL_MS);
190
+ // Immediate poll to avoid waiting for first interval tick
182
191
  void pollQueueStatus();
183
192
  },
184
193
  [userId, scenario, persistence, strategy, pollQueueStatus, onError],
@@ -11,6 +11,23 @@ import type { Creation } from "../../../../creations/domain/entities/Creation";
11
11
 
12
12
  declare const __DEV__: boolean;
13
13
 
14
+ /**
15
+ * Type guard to check if result is a valid Creation object
16
+ */
17
+ function isCreation(result: unknown): result is Creation {
18
+ if (!result || typeof result !== "object") {
19
+ return false;
20
+ }
21
+ const creation = result as Partial<Creation>;
22
+ return (
23
+ typeof creation.id === "string" &&
24
+ typeof creation.uri === "string" &&
25
+ typeof creation.type === "string" &&
26
+ creation.createdAt instanceof Date &&
27
+ typeof creation.isShared === "boolean"
28
+ );
29
+ }
30
+
14
31
  export interface UseWizardFlowHandlersProps {
15
32
  readonly currentStepIndex: number;
16
33
  readonly flowSteps: StepDefinition[];
@@ -64,7 +81,17 @@ export function useWizardFlowHandlers(props: UseWizardFlowHandlersProps) {
64
81
  console.log("[WizardFlowHandlers] Generation completed");
65
82
  }
66
83
  setResult(result);
67
- setCurrentCreation(result as Creation);
84
+
85
+ // Use type guard to safely check if result is a Creation
86
+ if (isCreation(result)) {
87
+ setCurrentCreation(result);
88
+ } else {
89
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
90
+ console.warn("[WizardFlowHandlers] Result is not a valid Creation object:", result);
91
+ }
92
+ setCurrentCreation(null);
93
+ }
94
+
68
95
  onGenerationComplete?.(result);
69
96
  if (!skipResultStep) nextStep();
70
97
  },
@@ -8,7 +8,7 @@
8
8
  * States: IDLE → PREPARING → GENERATING → COMPLETED/ERROR → IDLE
9
9
  */
10
10
 
11
- import { useEffect, useReducer, useMemo } from "react";
11
+ import { useEffect, useReducer, useMemo, useRef } from "react";
12
12
  import { createWizardStrategy, buildWizardInput } from "../../infrastructure/strategies";
13
13
  import { createCreationPersistence } from "../../infrastructure/utils/creation-persistence.util";
14
14
  import { useVideoQueueGeneration } from "./useVideoQueueGeneration";
@@ -96,6 +96,16 @@ export const useWizardGeneration = (
96
96
  // State machine: replaces multiple useRef flags
97
97
  const [state, dispatch] = useReducer(generationReducer, { status: "IDLE" });
98
98
 
99
+ // Mounted ref to prevent state updates after unmount
100
+ const isMountedRef = useRef(true);
101
+
102
+ useEffect(() => {
103
+ isMountedRef.current = true;
104
+ return () => {
105
+ isMountedRef.current = false;
106
+ };
107
+ }, []);
108
+
99
109
  const persistence = useMemo(() => createCreationPersistence(), []);
100
110
  const strategy = useMemo(
101
111
  () => createWizardStrategy({ scenario, creditCost }),
@@ -140,6 +150,9 @@ export const useWizardGeneration = (
140
150
 
141
151
  buildWizardInput(wizardData, scenario)
142
152
  .then(async (input) => {
153
+ // Check if component is still mounted
154
+ if (!isMountedRef.current) return;
155
+
143
156
  if (!input) {
144
157
  dispatch({ type: "ERROR", error: "Failed to build generation input" });
145
158
  onError?.("Failed to build generation input");
@@ -161,9 +174,15 @@ export const useWizardGeneration = (
161
174
  await photoGeneration.startGeneration(input, typedInput.prompt || "");
162
175
  }
163
176
 
164
- dispatch({ type: "COMPLETE" });
177
+ // Check again before final state update
178
+ if (isMountedRef.current) {
179
+ dispatch({ type: "COMPLETE" });
180
+ }
165
181
  })
166
182
  .catch((error) => {
183
+ // Check if component is still mounted before error handling
184
+ if (!isMountedRef.current) return;
185
+
167
186
  const errorMsg = error?.message || "error.generation.unknown";
168
187
  if (typeof __DEV__ !== "undefined" && __DEV__) {
169
188
  console.error("[WizardGeneration] Build input error:", errorMsg, error);
@@ -17,6 +17,7 @@ import type {
17
17
  ImageToVideoInputBuilder,
18
18
  ImageToVideoResultExtractor,
19
19
  } from "../../domain/types";
20
+ import { env } from "../../../../infrastructure/config/env.config";
20
21
 
21
22
  /**
22
23
  * Options for image-to-video execution
@@ -89,7 +90,7 @@ class ImageToVideoExecutor extends BaseExecutor<
89
90
  const progress = getProgressFromJobStatus(status.status);
90
91
  onProgress?.(progress);
91
92
  },
92
- timeoutMs: 300000, // 5 minutes timeout for video generation
93
+ timeoutMs: env.generationVideoTimeoutMs,
93
94
  });
94
95
 
95
96
  this.logInfo(
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Environment Configuration
3
+ * Centralized environment variable access with fallback defaults
4
+ */
5
+
6
+ type EnvValue = string | number | boolean;
7
+
8
+ function getEnvValue(key: string, defaultValue: EnvValue): EnvValue {
9
+ if (typeof process !== "undefined" && process.env) {
10
+ const value = process.env[key];
11
+ if (value !== undefined && value !== "") {
12
+ if (typeof defaultValue === "number") {
13
+ const parsed = Number(value);
14
+ return isNaN(parsed) ? defaultValue : parsed;
15
+ }
16
+ if (typeof defaultValue === "boolean") {
17
+ return value === "true" || value === "1";
18
+ }
19
+ return value;
20
+ }
21
+ }
22
+ return defaultValue;
23
+ }
24
+
25
+ export const env = {
26
+ // API Configuration
27
+ apiDefaultTimeoutMs: getEnvValue("API_DEFAULT_TIMEOUT_MS", 30000) as number,
28
+ apiRetryDelayMs: getEnvValue("API_RETRY_DELAY_MS", 1000) as number,
29
+
30
+ // Generation Timeouts
31
+ generationImageTimeoutMs: getEnvValue("GENERATION_IMAGE_TIMEOUT_MS", 120000) as number,
32
+ generationVideoTimeoutMs: getEnvValue("GENERATION_VIDEO_TIMEOUT_MS", 300000) as number,
33
+ generationMultiImageTimeoutMs: getEnvValue("GENERATION_MULTI_IMAGE_TIMEOUT_MS", 120000) as number,
34
+
35
+ // Polling Configuration
36
+ pollDefaultIntervalMs: getEnvValue("POLL_DEFAULT_INTERVAL_MS", 3000) as number,
37
+ pollGalleryIntervalMs: getEnvValue("POLL_GALLERY_INTERVAL_MS", 5000) as number,
38
+ pollMaxTimeMs: getEnvValue("POLL_MAX_TIME_MS", 300000) as number,
39
+ pollMaxAttempts: getEnvValue("POLL_MAX_ATTEMPTS", 100) as number,
40
+ pollMaxConsecutiveErrors: getEnvValue("POLL_MAX_CONSECUTIVE_ERRORS", 5) as number,
41
+ pollMinBackoffDelayMs: getEnvValue("POLL_MIN_BACKOFF_DELAY_MS", 1000) as number,
42
+ pollMaxBackoffDelayMs: getEnvValue("POLL_MAX_BACKOFF_DELAY_MS", 30000) as number,
43
+ pollBackoffMultiplier: getEnvValue("POLL_BACKOFF_MULTIPLIER", 1.5) as number,
44
+
45
+ // Validation Limits
46
+ validationMaxPromptLength: getEnvValue("VALIDATION_MAX_PROMPT_LENGTH", 10000) as number,
47
+ validationMinPromptLength: getEnvValue("VALIDATION_MIN_PROMPT_LENGTH", 1) as number,
48
+ validationMaxUserIdLength: getEnvValue("VALIDATION_MAX_USER_ID_LENGTH", 128) as number,
49
+ validationMaxCreationIdLength: getEnvValue("VALIDATION_MAX_CREATION_ID_LENGTH", 128) as number,
50
+ validationMaxUrlLength: getEnvValue("VALIDATION_MAX_URL_LENGTH", 2048) as number,
51
+ validationMaxBase64SizeMb: getEnvValue("VALIDATION_MAX_BASE64_SIZE_MB", 20) as number,
52
+ validationMaxPatternLength: getEnvValue("VALIDATION_MAX_PATTERN_LENGTH", 1000) as number,
53
+ validationMaxImageSizeMb: getEnvValue("VALIDATION_MAX_IMAGE_SIZE_MB", 10) as number,
54
+ validationMaxVideoDurationSeconds: getEnvValue("VALIDATION_MAX_VIDEO_DURATION_SECONDS", 60) as number,
55
+ validationMinVideoDurationSeconds: getEnvValue("VALIDATION_MIN_VIDEO_DURATION_SECONDS", 3) as number,
56
+ validationMaxMultiImageCount: getEnvValue("VALIDATION_MAX_MULTI_IMAGE_COUNT", 10) as number,
57
+ validationMinMultiImageCount: getEnvValue("VALIDATION_MIN_MULTI_IMAGE_COUNT", 2) as number,
58
+
59
+ // Content Moderation
60
+ moderationMaxTextLength: getEnvValue("MODERATION_MAX_TEXT_LENGTH", 5000) as number,
61
+ moderationMaxUriLength: getEnvValue("MODERATION_MAX_URI_LENGTH", 2048) as number,
62
+ moderationVoiceMaxLength: getEnvValue("MODERATION_VOICE_MAX_LENGTH", 5000) as number,
63
+
64
+ // Storage Configuration
65
+ storageDbOperationTimeoutMs: getEnvValue("STORAGE_DB_OPERATION_TIMEOUT_MS", 10000) as number,
66
+ storageMaxCacheSize: getEnvValue("STORAGE_MAX_CACHE_SIZE", 100) as number,
67
+ } as const;
@@ -14,3 +14,5 @@ export {
14
14
  getAuthService,
15
15
  getAnalyticsService,
16
16
  } from "./app-services.config";
17
+
18
+ export { env } from "./env.config";
@@ -2,5 +2,7 @@
2
2
  * Content Related Constants
3
3
  */
4
4
 
5
+ import { env } from "../config/env.config";
6
+
5
7
  /** Default maximum length for text content */
6
- export const DEFAULT_MAX_TEXT_LENGTH = 10000;
8
+ export const DEFAULT_MAX_TEXT_LENGTH = env.moderationMaxTextLength;
@@ -3,29 +3,31 @@
3
3
  * All timeout, interval, and retry related constants
4
4
  */
5
5
 
6
+ import { env } from "../config/env.config";
7
+
6
8
  /** Default polling interval for job status checks (milliseconds) */
7
- export const DEFAULT_POLL_INTERVAL_MS = 3000;
9
+ export const DEFAULT_POLL_INTERVAL_MS = env.pollDefaultIntervalMs;
8
10
 
9
11
  /** Gallery polling interval (slower than wizard) */
10
- export const GALLERY_POLL_INTERVAL_MS = 5000;
12
+ export const GALLERY_POLL_INTERVAL_MS = env.pollGalleryIntervalMs;
11
13
 
12
14
  /** Maximum number of polling attempts before timeout */
13
- export const DEFAULT_MAX_POLL_ATTEMPTS = 100;
15
+ export const DEFAULT_MAX_POLL_ATTEMPTS = env.pollMaxAttempts;
14
16
 
15
17
  /** Maximum consecutive transient errors before aborting */
16
- export const DEFAULT_MAX_CONSECUTIVE_ERRORS = 5;
18
+ export const DEFAULT_MAX_CONSECUTIVE_ERRORS = env.pollMaxConsecutiveErrors;
17
19
 
18
- /** Maximum total time for polling (milliseconds) - ~5 minutes */
19
- export const DEFAULT_MAX_POLL_TIME_MS = 5 * 60 * 1000;
20
+ /** Maximum total time for polling (milliseconds) */
21
+ export const DEFAULT_MAX_POLL_TIME_MS = env.pollMaxTimeMs;
20
22
 
21
23
  /** Minimum backoff delay (milliseconds) */
22
- export const MIN_BACKOFF_DELAY_MS = 1000;
24
+ export const MIN_BACKOFF_DELAY_MS = env.pollMinBackoffDelayMs;
23
25
 
24
26
  /** Maximum backoff delay (milliseconds) */
25
- export const MAX_BACKOFF_DELAY_MS = 30000;
27
+ export const MAX_BACKOFF_DELAY_MS = env.pollMaxBackoffDelayMs;
26
28
 
27
29
  /** Exponential backoff base multiplier */
28
- export const BACKOFF_MULTIPLIER = 1.5;
30
+ export const BACKOFF_MULTIPLIER = env.pollBackoffMultiplier;
29
31
 
30
32
  /** Progress percentage to report when job completes */
31
33
  export const COMPLETION_PROGRESS = 100;
@@ -3,6 +3,8 @@
3
3
  * Database and storage related constants
4
4
  */
5
5
 
6
+ import { env } from "../config/env.config";
7
+
6
8
  /** Maximum number of creations to fetch in one query */
7
9
  export const MAX_CREATIONS_FETCH_LIMIT = 100;
8
10
 
@@ -13,10 +15,10 @@ export const DEFAULT_CREATIONS_PAGE_SIZE = 20;
13
15
  export const MAX_LOG_ENTRIES = 1000;
14
16
 
15
17
  /** Timeout for database operations (milliseconds) */
16
- export const DB_OPERATION_TIMEOUT_MS = 10000;
18
+ export const DB_OPERATION_TIMEOUT_MS = env.storageDbOperationTimeoutMs;
17
19
 
18
20
  /** Maximum cache size for frequently accessed data */
19
- export const MAX_CACHE_SIZE = 100;
21
+ export const MAX_CACHE_SIZE = env.storageMaxCacheSize;
20
22
 
21
23
  /** Cache TTL for creations (milliseconds) - 5 minutes */
22
24
  export const CREATIONS_CACHE_TTL_MS = 5 * 60 * 1000;
@@ -3,38 +3,40 @@
3
3
  * All validation limits and constraints
4
4
  */
5
5
 
6
+ import { env } from "../config/env.config";
7
+
6
8
  /** Maximum length for AI prompt text */
7
- export const MAX_PROMPT_LENGTH = 10000;
9
+ export const MAX_PROMPT_LENGTH = env.validationMaxPromptLength;
8
10
 
9
11
  /** Minimum length for AI prompt text */
10
- export const MIN_PROMPT_LENGTH = 1;
12
+ export const MIN_PROMPT_LENGTH = env.validationMinPromptLength;
11
13
 
12
14
  /** Maximum length for user ID */
13
- export const MAX_USER_ID_LENGTH = 128;
15
+ export const MAX_USER_ID_LENGTH = env.validationMaxUserIdLength;
14
16
 
15
17
  /** Maximum length for creation ID */
16
- export const MAX_CREATION_ID_LENGTH = 128;
18
+ export const MAX_CREATION_ID_LENGTH = env.validationMaxCreationIdLength;
17
19
 
18
20
  /** Maximum base64 string length (before encoding) */
19
- export const MAX_BASE64_SIZE_MB = 20;
21
+ export const MAX_BASE64_SIZE_MB = env.validationMaxBase64SizeMb;
20
22
 
21
23
  /** Maximum regex pattern length for validation */
22
- export const MAX_PATTERN_LENGTH = 1000;
24
+ export const MAX_PATTERN_LENGTH = env.validationMaxPatternLength;
23
25
 
24
26
  /** Maximum duration for video generation (seconds) */
25
- export const MAX_VIDEO_DURATION_SECONDS = 60;
27
+ export const MAX_VIDEO_DURATION_SECONDS = env.validationMaxVideoDurationSeconds;
26
28
 
27
29
  /** Minimum duration for video generation (seconds) */
28
- export const MIN_VIDEO_DURATION_SECONDS = 3;
30
+ export const MIN_VIDEO_DURATION_SECONDS = env.validationMinVideoDurationSeconds;
29
31
 
30
32
  /** Maximum image file size (MB) */
31
- export const MAX_IMAGE_SIZE_MB = 10;
33
+ export const MAX_IMAGE_SIZE_MB = env.validationMaxImageSizeMb;
32
34
 
33
35
  /** Maximum URL length */
34
- export const MAX_URL_LENGTH = 2048;
36
+ export const MAX_URL_LENGTH = env.validationMaxUrlLength;
35
37
 
36
38
  /** Maximum number of images in multi-image generation */
37
- export const MAX_MULTI_IMAGE_COUNT = 10;
39
+ export const MAX_MULTI_IMAGE_COUNT = env.validationMaxMultiImageCount;
38
40
 
39
41
  /** Minimum number of images for multi-image generation */
40
- export const MIN_MULTI_IMAGE_COUNT = 2;
42
+ export const MIN_MULTI_IMAGE_COUNT = env.validationMinMultiImageCount;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * API Client Types
3
+ * Type definitions for HTTP client utilities
4
+ */
5
+
6
+ export interface RequestOptions extends RequestInit {
7
+ timeout?: number;
8
+ retries?: number;
9
+ headers?: Record<string, string>;
10
+ }
11
+
12
+ export interface ApiResponse<T> {
13
+ data: T | null;
14
+ error: string | null;
15
+ status: number;
16
+ headers: Headers;
17
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * HTTP Client Utilities
3
+ * Core HTTP request handling with error handling and retry logic
4
+ */
5
+
6
+ import { withErrorHandling, getErrorMessage, retryWithBackoff, isNetworkError } from "../utils/error-handling.util";
7
+ import { env } from "../config/env.config";
8
+ import type { RequestOptions, ApiResponse } from "./api-client.types";
9
+ import { createTimeoutController } from "./timeout.util";
10
+
11
+ /**
12
+ * Makes an HTTP request with error handling and retry logic
13
+ * @param url - Request URL
14
+ * @param options - Request options
15
+ * @returns API response with data or error
16
+ */
17
+ export async function fetchWithTimeout<T>(
18
+ url: string,
19
+ options: RequestOptions = {}
20
+ ): Promise<ApiResponse<T>> {
21
+ const {
22
+ timeout = env.apiDefaultTimeoutMs,
23
+ retries = 0,
24
+ headers = {},
25
+ ...fetchOptions
26
+ } = options;
27
+
28
+ const controller = createTimeoutController(timeout);
29
+
30
+ const operation = async (): Promise<T> => {
31
+ const response = await fetch(url, {
32
+ ...fetchOptions,
33
+ headers: {
34
+ "Content-Type": "application/json",
35
+ ...headers,
36
+ },
37
+ signal: controller?.signal,
38
+ });
39
+
40
+ if (!response.ok) {
41
+ const errorText = await response.text().catch(() => "Unknown error");
42
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
43
+ }
44
+
45
+ const contentType = response.headers.get("content-type");
46
+ if (contentType?.includes("application/json")) {
47
+ return response.json();
48
+ }
49
+
50
+ return response.text() as T;
51
+ };
52
+
53
+ const result = await withErrorHandling(
54
+ async () => {
55
+ if (retries > 0) {
56
+ return retryWithBackoff(operation, {
57
+ maxRetries: retries,
58
+ delayMs: env.apiRetryDelayMs,
59
+ shouldRetry: (error) => isNetworkError(error),
60
+ });
61
+ }
62
+ return operation();
63
+ },
64
+ (error) => {
65
+ console.error("[API Client] Request failed:", error);
66
+ }
67
+ );
68
+
69
+ if (result.error) {
70
+ return {
71
+ data: null,
72
+ error: getErrorMessage(result.error),
73
+ status: 0,
74
+ headers: new Headers(),
75
+ };
76
+ }
77
+
78
+ return {
79
+ data: result.data ?? null,
80
+ error: null,
81
+ status: 200,
82
+ headers: new Headers(),
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Makes a GET request
88
+ * @param url - Request URL
89
+ * @param options - Request options
90
+ * @returns API response
91
+ */
92
+ export async function get<T>(
93
+ url: string,
94
+ options: Omit<RequestOptions, "method" | "body"> = {}
95
+ ): Promise<ApiResponse<T>> {
96
+ return fetchWithTimeout<T>(url, { ...options, method: "GET" });
97
+ }
98
+
99
+ /**
100
+ * Makes a POST request
101
+ * @param url - Request URL
102
+ * @param data - Request body data
103
+ * @param options - Request options
104
+ * @returns API response
105
+ */
106
+ export async function post<T>(
107
+ url: string,
108
+ data: unknown,
109
+ options: Omit<RequestOptions, "method"> = {}
110
+ ): Promise<ApiResponse<T>> {
111
+ return fetchWithTimeout<T>(url, {
112
+ ...options,
113
+ method: "POST",
114
+ body: JSON.stringify(data),
115
+ });
116
+ }
117
+
118
+ /**
119
+ * Makes a PUT request
120
+ * @param url - Request URL
121
+ * @param data - Request body data
122
+ * @param options - Request options
123
+ * @returns API response
124
+ */
125
+ export async function put<T>(
126
+ url: string,
127
+ data: unknown,
128
+ options: Omit<RequestOptions, "method"> = {}
129
+ ): Promise<ApiResponse<T>> {
130
+ return fetchWithTimeout<T>(url, {
131
+ ...options,
132
+ method: "PUT",
133
+ body: JSON.stringify(data),
134
+ });
135
+ }
136
+
137
+ /**
138
+ * Makes a DELETE request
139
+ * @param url - Request URL
140
+ * @param options - Request options
141
+ * @returns API response
142
+ */
143
+ export async function del<T>(
144
+ url: string,
145
+ options: Omit<RequestOptions, "method" | "body"> = {}
146
+ ): Promise<ApiResponse<T>> {
147
+ return fetchWithTimeout<T>(url, { ...options, method: "DELETE" });
148
+ }
149
+
150
+ /**
151
+ * Makes a PATCH request
152
+ * @param url - Request URL
153
+ * @param data - Request body data
154
+ * @param options - Request options
155
+ * @returns API response
156
+ */
157
+ export async function patch<T>(
158
+ url: string,
159
+ data: unknown,
160
+ options: Omit<RequestOptions, "method"> = {}
161
+ ): Promise<ApiResponse<T>> {
162
+ return fetchWithTimeout<T>(url, {
163
+ ...options,
164
+ method: "PATCH",
165
+ body: JSON.stringify(data),
166
+ });
167
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * HTTP Client Module
3
+ * Exports all HTTP client utilities
4
+ */
5
+
6
+ export type { RequestOptions, ApiResponse } from "./api-client.types";
7
+ export {
8
+ get,
9
+ post,
10
+ put,
11
+ del,
12
+ patch,
13
+ fetchWithTimeout,
14
+ } from "./http-client.util";
15
+ export { buildQueryString, appendQueryParams } from "./query-string.util";
16
+ export { createTimeoutController } from "./timeout.util";