@umituz/react-native-ai-generation-content 1.90.10 → 1.90.12
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 +1 -1
- package/src/domains/background/infrastructure/services/job-poller.service.ts +4 -4
- package/src/domains/content-moderation/infrastructure/services/moderators/image.moderator.ts +3 -3
- package/src/domains/content-moderation/infrastructure/services/moderators/text.moderator.ts +1 -1
- package/src/domains/content-moderation/infrastructure/services/moderators/video.moderator.ts +3 -3
- package/src/domains/content-moderation/infrastructure/services/moderators/voice.moderator.ts +1 -1
- package/src/domains/creations/domain/constants/creation-fields.constants.ts +1 -1
- package/src/domains/creations/presentation/hooks/useCreationPersistence.ts +2 -1
- package/src/domains/creations/presentation/hooks/useProcessingJobsPoller.ts +18 -7
- package/src/domains/face-detection/domain/entities/FaceDetection.ts +1 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/builder-couple-prompt.ts +1 -1
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation.strategy.ts +24 -1
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation.types.ts +14 -0
- package/src/domains/generation/wizard/presentation/hooks/photo-upload/types.ts +8 -4
- package/src/domains/generation/wizard/presentation/hooks/photo-upload/usePhotoUploadStateLogic.ts +1 -1
- package/src/domains/generation/wizard/presentation/hooks/video-queue/use-video-queue-utils.ts +9 -0
- package/src/domains/generation/wizard/presentation/hooks/video-queue/useVideoQueueGenerationStart.ts +9 -1
- package/src/domains/image-to-video/domain/types/form.types.ts +1 -0
- package/src/domains/image-to-video/presentation/hooks/useImageToVideoFeature.ts +2 -2
- package/src/domains/text-to-image/presentation/hooks/useGeneration.ts +2 -2
- package/src/domains/text-to-video/presentation/hooks/useTextToVideoFeature.ts +2 -2
- package/src/infrastructure/services/image-feature-executor.service.ts +1 -1
- package/src/infrastructure/services/multi-image-generation.executor.ts +3 -2
- package/src/infrastructure/services/video-feature-executor.service.ts +1 -1
- package/src/infrastructure/validation/advanced-validator.ts +21 -16
- package/src/infrastructure/validation/ai-validator.ts +4 -4
- package/src/infrastructure/validation/base-validator.ts +17 -75
- package/src/infrastructure/validation/entity-validator.ts +2 -2
- package/src/infrastructure/validation/input-validator.ts +1 -1
- package/src/presentation/hooks/generation/useImageGeneration.ts +1 -1
- package/src/presentation/hooks/generation/useVideoGeneration.ts +1 -1
- package/src/shared-kernel/infrastructure/validation/index.ts +3 -43
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.90.
|
|
3
|
+
"version": "1.90.12",
|
|
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",
|
|
@@ -81,13 +81,13 @@ export async function pollJob<T = unknown>(
|
|
|
81
81
|
try {
|
|
82
82
|
// Check job status
|
|
83
83
|
const statusResult = await withAbortSignal(
|
|
84
|
-
provider.
|
|
84
|
+
provider.getJobStatus(model, requestId),
|
|
85
85
|
signal,
|
|
86
86
|
config.maxTotalTimeMs ? config.maxTotalTimeMs - (Date.now() - startTime) : undefined
|
|
87
87
|
);
|
|
88
88
|
|
|
89
89
|
// Notify status change
|
|
90
|
-
if (onStatusChange && typeof statusResult === 'object' && 'status' in statusResult) {
|
|
90
|
+
if (onStatusChange && statusResult && typeof statusResult === 'object' && 'status' in statusResult) {
|
|
91
91
|
onStatusChange(statusResult as JobStatus);
|
|
92
92
|
}
|
|
93
93
|
|
|
@@ -96,7 +96,7 @@ export async function pollJob<T = unknown>(
|
|
|
96
96
|
if (statusError) {
|
|
97
97
|
return {
|
|
98
98
|
success: false,
|
|
99
|
-
error: statusError,
|
|
99
|
+
error: statusError instanceof Error ? statusError : new Error(String(statusError)),
|
|
100
100
|
attempts: attempt + 1,
|
|
101
101
|
elapsedMs: Date.now() - startTime,
|
|
102
102
|
};
|
|
@@ -106,7 +106,7 @@ export async function pollJob<T = unknown>(
|
|
|
106
106
|
consecutiveErrors = 0;
|
|
107
107
|
|
|
108
108
|
// Check if job is complete
|
|
109
|
-
if (isJobComplete(statusResult as JobStatus |
|
|
109
|
+
if (isJobComplete(statusResult as JobStatus | string)) {
|
|
110
110
|
// Validate result
|
|
111
111
|
const validationResult = validateResult(statusResult);
|
|
112
112
|
if (!validationResult.isValid) {
|
package/src/domains/content-moderation/infrastructure/services/moderators/image.moderator.ts
CHANGED
|
@@ -39,10 +39,10 @@ class ImageModerator extends BaseModerator {
|
|
|
39
39
|
// Use shared URL validation
|
|
40
40
|
const urlValidation = validateUrl(uri);
|
|
41
41
|
if (!urlValidation.isValid) {
|
|
42
|
-
if (urlValidation.errors
|
|
42
|
+
if ('required' in urlValidation.errors) {
|
|
43
43
|
return this.createViolation("empty-uri", "Image Validation", "empty URI");
|
|
44
44
|
}
|
|
45
|
-
if (urlValidation.errors
|
|
45
|
+
if ('pattern' in urlValidation.errors) {
|
|
46
46
|
return this.createViolation(
|
|
47
47
|
"invalid-protocol",
|
|
48
48
|
"Image Validation",
|
|
@@ -57,7 +57,7 @@ class ImageModerator extends BaseModerator {
|
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
if (!lengthValidation.isValid) {
|
|
60
|
-
if (lengthValidation.errors
|
|
60
|
+
if ('maxLength' in lengthValidation.errors) {
|
|
61
61
|
return this.createViolation(
|
|
62
62
|
"uri-too-long",
|
|
63
63
|
"Image Validation",
|
|
@@ -63,7 +63,7 @@ class TextModerator extends BaseModerator {
|
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
if (!stringValidation.isValid) {
|
|
66
|
-
if (stringValidation.errors
|
|
66
|
+
if ('maxLength' in stringValidation.errors) {
|
|
67
67
|
return this.createViolation("too-long", "Validation", "length exceeded");
|
|
68
68
|
}
|
|
69
69
|
return this.createViolation("empty-content", "Validation", "empty");
|
package/src/domains/content-moderation/infrastructure/services/moderators/video.moderator.ts
CHANGED
|
@@ -39,10 +39,10 @@ class VideoModerator extends BaseModerator {
|
|
|
39
39
|
// Use shared URL validation
|
|
40
40
|
const urlValidation = validateUrl(uri);
|
|
41
41
|
if (!urlValidation.isValid) {
|
|
42
|
-
if (urlValidation.errors
|
|
42
|
+
if ('required' in urlValidation.errors) {
|
|
43
43
|
return this.createViolation("empty-uri", "Video Validation", "empty URI");
|
|
44
44
|
}
|
|
45
|
-
if (urlValidation.errors
|
|
45
|
+
if ('pattern' in urlValidation.errors) {
|
|
46
46
|
return this.createViolation(
|
|
47
47
|
"invalid-protocol",
|
|
48
48
|
"Video Validation",
|
|
@@ -57,7 +57,7 @@ class VideoModerator extends BaseModerator {
|
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
if (!lengthValidation.isValid) {
|
|
60
|
-
if (lengthValidation.errors
|
|
60
|
+
if ('maxLength' in lengthValidation.errors) {
|
|
61
61
|
return this.createViolation(
|
|
62
62
|
"uri-too-long",
|
|
63
63
|
"Video Validation",
|
|
@@ -53,7 +53,8 @@ export function useCreationPersistence(
|
|
|
53
53
|
|
|
54
54
|
const validation = runAllValidations(result.imageUrl, result.videoUrl);
|
|
55
55
|
if (!validation.isValid) {
|
|
56
|
-
|
|
56
|
+
const firstError = Object.values(validation.errors)[0];
|
|
57
|
+
await markCreationAsFailed(repository, userId, result.creationId, firstError || "Validation failed");
|
|
57
58
|
return;
|
|
58
59
|
}
|
|
59
60
|
|
|
@@ -4,8 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { useEffect } from 'react';
|
|
7
|
+
import type { Creation } from '../../../../domain/entities/Creation';
|
|
7
8
|
|
|
8
9
|
export interface UseProcessingJobsPollerConfig {
|
|
10
|
+
userId: string | null;
|
|
11
|
+
creations: Creation[];
|
|
9
12
|
enabled?: boolean;
|
|
10
13
|
interval?: number;
|
|
11
14
|
}
|
|
@@ -16,24 +19,32 @@ export interface UseProcessingJobsPollerReturn {
|
|
|
16
19
|
|
|
17
20
|
/**
|
|
18
21
|
* Hook to poll processing jobs
|
|
19
|
-
* TODO: Implement actual polling logic
|
|
20
22
|
*/
|
|
21
23
|
export function useProcessingJobsPoller(
|
|
22
|
-
config: UseProcessingJobsPollerConfig
|
|
24
|
+
config: UseProcessingJobsPollerConfig
|
|
23
25
|
): UseProcessingJobsPollerReturn {
|
|
24
|
-
const { enabled = false, interval = 5000 } = config;
|
|
26
|
+
const { userId, creations, enabled = false, interval = 5000 } = config;
|
|
25
27
|
|
|
26
28
|
useEffect(() => {
|
|
27
|
-
if (!enabled) return;
|
|
29
|
+
if (!enabled || !userId) return;
|
|
28
30
|
|
|
29
31
|
const timer = setInterval(() => {
|
|
30
|
-
// Polling logic here
|
|
32
|
+
// Polling logic here - check processing creations and update their status
|
|
33
|
+
const processingCreations = creations.filter(c => c.status === 'processing');
|
|
34
|
+
processingCreations.forEach(async (creation) => {
|
|
35
|
+
try {
|
|
36
|
+
// Check job status and update creation
|
|
37
|
+
// TODO: Implement actual polling logic
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('Error polling job status:', error);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
31
42
|
}, interval);
|
|
32
43
|
|
|
33
44
|
return () => clearInterval(timer);
|
|
34
|
-
}, [enabled, interval]);
|
|
45
|
+
}, [enabled, interval, userId, creations]);
|
|
35
46
|
|
|
36
47
|
return {
|
|
37
|
-
isPolling: enabled,
|
|
48
|
+
isPolling: enabled && !!userId,
|
|
38
49
|
};
|
|
39
50
|
}
|
package/src/domains/generation/wizard/infrastructure/strategies/video-generation.strategy.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { WizardStrategy } from "./wizard-strategy.types";
|
|
8
|
-
import type { CreateVideoStrategyOptions } from "./video-generation.types";
|
|
8
|
+
import type { CreateVideoStrategyOptions, WizardVideoInput } from "./video-generation.types";
|
|
9
|
+
import type { WizardScenarioData } from "../../presentation/hooks/useWizardGeneration";
|
|
9
10
|
import { validateWizardVideoInput } from "./video-generation.validation";
|
|
10
11
|
import { executeVideoGeneration, submitVideoGenerationToQueue } from "./video-generation.executor";
|
|
11
12
|
|
|
@@ -48,3 +49,25 @@ export function createVideoStrategy(options: CreateVideoStrategyOptions): Wizard
|
|
|
48
49
|
};
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Build video input from wizard data and scenario
|
|
54
|
+
*/
|
|
55
|
+
export async function buildVideoInput(
|
|
56
|
+
wizardData: Record<string, unknown>,
|
|
57
|
+
scenario: WizardScenarioData
|
|
58
|
+
): Promise<WizardVideoInput> {
|
|
59
|
+
const prompt = (wizardData.prompt as string | undefined) || scenario.prompt || "";
|
|
60
|
+
const input: WizardVideoInput = {
|
|
61
|
+
prompt,
|
|
62
|
+
sourceImageBase64: wizardData.sourceImageBase64 as string | undefined,
|
|
63
|
+
targetImageBase64: wizardData.targetImageBase64 as string | undefined,
|
|
64
|
+
duration: wizardData.duration as number | undefined,
|
|
65
|
+
aspectRatio: wizardData.aspectRatio as string | undefined,
|
|
66
|
+
resolution: wizardData.resolution as string | undefined,
|
|
67
|
+
audioUrl: wizardData.audioUrl as string | undefined,
|
|
68
|
+
qualityMode: wizardData.qualityMode as "draft" | "normal" | undefined,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return input;
|
|
72
|
+
}
|
|
73
|
+
|
|
@@ -32,3 +32,17 @@ export interface CreateVideoStrategyOptions {
|
|
|
32
32
|
/** Credit cost for this generation - REQUIRED, determined by the app */
|
|
33
33
|
readonly creditCost: number;
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
export interface ExecutionResult {
|
|
37
|
+
success: boolean;
|
|
38
|
+
videoUrl?: string;
|
|
39
|
+
requestId?: string;
|
|
40
|
+
error?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface SubmissionResult {
|
|
44
|
+
success: boolean;
|
|
45
|
+
requestId?: string;
|
|
46
|
+
model?: string;
|
|
47
|
+
error?: string;
|
|
48
|
+
}
|
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
* Generic Photo Upload State Hook - Type Definitions
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
export interface UploadedImage {
|
|
6
|
+
uri: string;
|
|
7
|
+
base64?: string;
|
|
8
|
+
width?: number;
|
|
9
|
+
height?: number;
|
|
10
|
+
previewUrl?: string;
|
|
11
|
+
fileSize?: number;
|
|
12
|
+
}
|
|
9
13
|
|
|
10
14
|
export interface PhotoUploadConfig {
|
|
11
15
|
readonly maxFileSizeMB?: number;
|
package/src/domains/generation/wizard/presentation/hooks/photo-upload/usePhotoUploadStateLogic.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { useCallback, useRef, useEffect } from "react";
|
|
6
6
|
import { useMedia, MediaQuality, MediaValidationError, MEDIA_CONSTANTS } from "@umituz/react-native-design-system/media";
|
|
7
|
-
import type { UploadedImage } from "
|
|
7
|
+
import type { UploadedImage } from "./types";
|
|
8
8
|
import type { PhotoUploadConfig, PhotoUploadError, PhotoUploadTranslations } from "./types";
|
|
9
9
|
|
|
10
10
|
export function usePhotoUploadStateLogic(
|
package/src/domains/generation/wizard/presentation/hooks/video-queue/use-video-queue-utils.ts
CHANGED
|
@@ -109,15 +109,24 @@ export function extractInputMetadata(params: {
|
|
|
109
109
|
readonly model: string;
|
|
110
110
|
readonly prompt: string;
|
|
111
111
|
readonly imageUrls?: string[];
|
|
112
|
+
readonly duration?: number;
|
|
113
|
+
readonly resolution?: string;
|
|
114
|
+
readonly aspectRatio?: string;
|
|
112
115
|
}): {
|
|
113
116
|
readonly model: string;
|
|
114
117
|
readonly prompt: string;
|
|
115
118
|
readonly imageUrls: string[];
|
|
119
|
+
readonly duration?: number;
|
|
120
|
+
readonly resolution?: string;
|
|
121
|
+
readonly aspectRatio?: string;
|
|
116
122
|
} {
|
|
117
123
|
return {
|
|
118
124
|
model: params.model,
|
|
119
125
|
prompt: params.prompt,
|
|
120
126
|
imageUrls: params.imageUrls || [],
|
|
127
|
+
duration: params.duration,
|
|
128
|
+
resolution: params.resolution,
|
|
129
|
+
aspectRatio: params.aspectRatio,
|
|
121
130
|
};
|
|
122
131
|
}
|
|
123
132
|
|
package/src/domains/generation/wizard/presentation/hooks/video-queue/useVideoQueueGenerationStart.ts
CHANGED
|
@@ -39,7 +39,15 @@ export function useStartGeneration(
|
|
|
39
39
|
let creationId: string | null = null;
|
|
40
40
|
if (userId && prompt) {
|
|
41
41
|
try {
|
|
42
|
-
const
|
|
42
|
+
const inputRecord = input as Record<string, unknown>;
|
|
43
|
+
const { duration, resolution, aspectRatio } = extractInputMetadata({
|
|
44
|
+
model: scenario.model || "",
|
|
45
|
+
prompt,
|
|
46
|
+
imageUrls: (inputRecord.imageUrls || inputRecord.image_url) as string[] | undefined,
|
|
47
|
+
duration: inputRecord.duration as number | undefined,
|
|
48
|
+
resolution: inputRecord.resolution as string | undefined,
|
|
49
|
+
aspectRatio: inputRecord.aspectRatio as string | undefined,
|
|
50
|
+
});
|
|
43
51
|
|
|
44
52
|
const result = await persistence.saveAsProcessing(userId, {
|
|
45
53
|
scenarioId: scenario.id,
|
|
@@ -41,14 +41,14 @@ export function useImageToVideoFeature(props: UseImageToVideoFeatureProps): UseI
|
|
|
41
41
|
const orchestrator = useGenerationOrchestrator(strategy, {
|
|
42
42
|
userId,
|
|
43
43
|
alertMessages: DEFAULT_ALERT_MESSAGES,
|
|
44
|
-
onSuccess: (result: unknown) => {
|
|
44
|
+
onSuccess: async (result: unknown) => {
|
|
45
45
|
const typedResult = result as { success: boolean; videoUrl?: string; thumbnailUrl?: string };
|
|
46
46
|
if (typedResult.success && typedResult.videoUrl) {
|
|
47
47
|
config.onProcessingComplete?.(typedResult);
|
|
48
48
|
callbacks?.onGenerate?.(typedResult);
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
|
-
onError: (err) => {
|
|
51
|
+
onError: async (err) => {
|
|
52
52
|
config.onError?.(err.message);
|
|
53
53
|
callbacks?.onError?.(err.message);
|
|
54
54
|
},
|
|
@@ -72,7 +72,7 @@ export function useGeneration(options: UseGenerationOptions): UseGenerationRetur
|
|
|
72
72
|
const { generate, isGenerating, error } = useGenerationOrchestrator(strategy, {
|
|
73
73
|
userId: userId ?? undefined,
|
|
74
74
|
alertMessages: DEFAULT_ALERT_MESSAGES,
|
|
75
|
-
onSuccess: (result) => {
|
|
75
|
+
onSuccess: async (result) => {
|
|
76
76
|
const imageUrls = result as string[];
|
|
77
77
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
78
78
|
console.log("[TextToImage] Success! Generated", imageUrls.length, "image(s)");
|
|
@@ -80,7 +80,7 @@ export function useGeneration(options: UseGenerationOptions): UseGenerationRetur
|
|
|
80
80
|
callbacks.onSuccess?.(imageUrls);
|
|
81
81
|
onPromptCleared?.();
|
|
82
82
|
},
|
|
83
|
-
onError: (err) => {
|
|
83
|
+
onError: async (err) => {
|
|
84
84
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
85
85
|
console.log("[TextToImage] Error:", err.message);
|
|
86
86
|
}
|
|
@@ -71,8 +71,8 @@ export function useTextToVideoFeature(props: UseTextToVideoFeatureProps): UseTex
|
|
|
71
71
|
const orchestrator = useGenerationOrchestrator(strategy, {
|
|
72
72
|
userId,
|
|
73
73
|
alertMessages: DEFAULT_ALERT_MESSAGES,
|
|
74
|
-
onSuccess: (result) => callbacks.onGenerate?.(result as TextToVideoResult),
|
|
75
|
-
onError: (err) => callbacks.onError?.(err.message),
|
|
74
|
+
onSuccess: async (result) => callbacks.onGenerate?.(result as TextToVideoResult),
|
|
75
|
+
onError: async (err) => callbacks.onError?.(err.message),
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
const setPrompt = useCallback((prompt: string) => {
|
|
@@ -68,7 +68,7 @@ export async function executeImageFeature(
|
|
|
68
68
|
if (!urlValidation.isValid) {
|
|
69
69
|
return {
|
|
70
70
|
success: false,
|
|
71
|
-
error: `Invalid image URL received: ${urlValidation.errors.join(", ")}`
|
|
71
|
+
error: `Invalid image URL received: ${Object.values(urlValidation.errors).join(", ")}`
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -100,10 +100,11 @@ export async function executeMultiImageGeneration(
|
|
|
100
100
|
if (typeof imageUrl === "string" && imageUrl.length > 0) {
|
|
101
101
|
const urlValidation = validateURL(imageUrl);
|
|
102
102
|
if (!urlValidation.isValid) {
|
|
103
|
-
|
|
103
|
+
const errorMsg = Object.values(urlValidation.errors).join(", ");
|
|
104
|
+
addGenerationLog(sid, TAG, `Invalid URL in response: ${errorMsg}`, 'error');
|
|
104
105
|
return {
|
|
105
106
|
success: false,
|
|
106
|
-
error: `Invalid image URL received: ${
|
|
107
|
+
error: `Invalid image URL received: ${errorMsg}`,
|
|
107
108
|
logSessionId: sid,
|
|
108
109
|
};
|
|
109
110
|
}
|
|
@@ -54,7 +54,7 @@ export async function executeVideoFeature(
|
|
|
54
54
|
if (!urlValidation.isValid) {
|
|
55
55
|
return {
|
|
56
56
|
success: false,
|
|
57
|
-
error: `Invalid video URL received: ${urlValidation.errors.join(", ")}`
|
|
57
|
+
error: `Invalid video URL received: ${Object.values(urlValidation.errors).join(", ")}`
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
* Complex validators for objects, arrays, and combined validations
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { ValidationResult } from "
|
|
7
|
-
import { validateString } from "
|
|
8
|
-
import type { StringValidationOptions } from "./base-validator";
|
|
6
|
+
import type { ValidationResult, StringValidationOptions } from "../../shared-kernel/infrastructure/validation";
|
|
7
|
+
import { validateString } from "../../shared-kernel/infrastructure/validation";
|
|
9
8
|
|
|
10
9
|
/**
|
|
11
10
|
* Validates object structure
|
|
@@ -14,19 +13,19 @@ export function validateObject(
|
|
|
14
13
|
input: unknown,
|
|
15
14
|
requiredFields: readonly string[] = []
|
|
16
15
|
): ValidationResult {
|
|
17
|
-
const errors: string
|
|
16
|
+
const errors: Record<string, string> = {};
|
|
18
17
|
|
|
19
18
|
if (typeof input !== "object" || input === null) {
|
|
20
|
-
return { isValid: false, errors:
|
|
19
|
+
return { isValid: false, errors: { input: "Input must be an object" } };
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
for (const field of requiredFields) {
|
|
24
23
|
if (!(field in input)) {
|
|
25
|
-
errors
|
|
24
|
+
errors[field] = `Missing required field: ${field}`;
|
|
26
25
|
}
|
|
27
26
|
}
|
|
28
27
|
|
|
29
|
-
return { isValid: errors.length === 0, errors };
|
|
28
|
+
return { isValid: Object.keys(errors).length === 0, errors };
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
/**
|
|
@@ -40,18 +39,18 @@ export function validateArray(
|
|
|
40
39
|
readonly itemType?: "string" | "number" | "object";
|
|
41
40
|
} = {}
|
|
42
41
|
): ValidationResult {
|
|
43
|
-
const errors: string
|
|
42
|
+
const errors: Record<string, string> = {};
|
|
44
43
|
|
|
45
44
|
if (!Array.isArray(input)) {
|
|
46
|
-
return { isValid: false, errors:
|
|
45
|
+
return { isValid: false, errors: { input: "Input must be an array" } };
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
if (options.minLength !== undefined && input.length < options.minLength) {
|
|
50
|
-
errors.
|
|
49
|
+
errors.minLength = `Array must have at least ${options.minLength} items`;
|
|
51
50
|
}
|
|
52
51
|
|
|
53
52
|
if (options.maxLength !== undefined && input.length > options.maxLength) {
|
|
54
|
-
errors.
|
|
53
|
+
errors.maxLength = `Array must have at most ${options.maxLength} items`;
|
|
55
54
|
}
|
|
56
55
|
|
|
57
56
|
if (options.itemType) {
|
|
@@ -65,22 +64,28 @@ export function validateArray(
|
|
|
65
64
|
: typeof item === "object" && item !== null;
|
|
66
65
|
|
|
67
66
|
if (!isValidType) {
|
|
68
|
-
errors
|
|
67
|
+
errors[`item_${i}`] = `Item at index ${i} is not a ${options.itemType}`;
|
|
69
68
|
}
|
|
70
69
|
}
|
|
71
70
|
}
|
|
72
71
|
|
|
73
|
-
return { isValid: errors.length === 0, errors };
|
|
72
|
+
return { isValid: Object.keys(errors).length === 0, errors };
|
|
74
73
|
}
|
|
75
74
|
|
|
76
75
|
/**
|
|
77
76
|
* Combines multiple validation results
|
|
78
77
|
*/
|
|
79
78
|
export function combineValidationResults(
|
|
80
|
-
results:
|
|
79
|
+
...results: ValidationResult[]
|
|
81
80
|
): ValidationResult {
|
|
82
|
-
const allErrors = results.
|
|
83
|
-
|
|
81
|
+
const allErrors = results.reduce((acc, result) => {
|
|
82
|
+
return { ...acc, ...result.errors };
|
|
83
|
+
}, {} as Record<string, string>);
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
isValid: Object.keys(allErrors).length === 0,
|
|
87
|
+
errors: allErrors,
|
|
88
|
+
};
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
/**
|
|
@@ -26,7 +26,7 @@ export function validateAIPrompt(input: unknown): ValidationResult {
|
|
|
26
26
|
*/
|
|
27
27
|
export function validateImageData(input: unknown): ValidationResult {
|
|
28
28
|
if (typeof input !== "string") {
|
|
29
|
-
return { isValid: false, errors:
|
|
29
|
+
return { isValid: false, errors: { type: "Image data must be a string" } };
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
const trimmed = input.trim();
|
|
@@ -40,11 +40,11 @@ export function validateImageData(input: unknown): ValidationResult {
|
|
|
40
40
|
if (trimmed.startsWith("data:image/")) {
|
|
41
41
|
const parts = trimmed.split(",");
|
|
42
42
|
if (parts.length !== 2) {
|
|
43
|
-
return { isValid: false, errors:
|
|
43
|
+
return { isValid: false, errors: { format: "Invalid data URI format" } };
|
|
44
44
|
}
|
|
45
45
|
const base64Part = parts[1];
|
|
46
46
|
if (!base64Part || base64Part.length === 0) {
|
|
47
|
-
return { isValid: false, errors:
|
|
47
|
+
return { isValid: false, errors: { data: "Invalid data URI: missing base64 data" } };
|
|
48
48
|
}
|
|
49
49
|
return validateBase64(base64Part);
|
|
50
50
|
}
|
|
@@ -57,7 +57,7 @@ export function validateImageData(input: unknown): ValidationResult {
|
|
|
57
57
|
|
|
58
58
|
return {
|
|
59
59
|
isValid: false,
|
|
60
|
-
errors:
|
|
60
|
+
errors: { format: "Image data must be a URL, base64 data URI, or valid base64 string" },
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -3,89 +3,31 @@
|
|
|
3
3
|
* Core validation functions for strings, numbers, URLs, emails, and base64
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { ValidationResult, StringValidationOptions,
|
|
6
|
+
import type { ValidationResult, StringValidationOptions, NumberValidationOptions } from "../../shared-kernel/infrastructure/validation";
|
|
7
|
+
import { validateString, validateNumber } from "../../shared-kernel/infrastructure/validation";
|
|
7
8
|
|
|
8
9
|
// Re-export types for convenience
|
|
9
|
-
export type { ValidationResult, StringValidationOptions,
|
|
10
|
+
export type { ValidationResult, StringValidationOptions, NumberValidationOptions } from "../../shared-kernel/infrastructure/validation";
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*/
|
|
14
|
-
export function validateString(
|
|
15
|
-
input: unknown,
|
|
16
|
-
options: StringValidationOptions = {}
|
|
17
|
-
): ValidationResult {
|
|
18
|
-
const errors: string[] = [];
|
|
19
|
-
|
|
20
|
-
if (typeof input !== "string") {
|
|
21
|
-
return { isValid: false, errors: ["Input must be a string"] };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const value = options.trim !== false ? input.trim() : input;
|
|
25
|
-
|
|
26
|
-
if (options.minLength !== undefined && value.length < options.minLength) {
|
|
27
|
-
errors.push(`Input must be at least ${options.minLength} characters`);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (options.maxLength !== undefined && value.length > options.maxLength) {
|
|
31
|
-
errors.push(`Input must be at most ${options.maxLength} characters`);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (options.pattern && !options.pattern.test(value)) {
|
|
35
|
-
errors.push("Input format is invalid");
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (options.allowedCharacters && !options.allowedCharacters.test(value)) {
|
|
39
|
-
errors.push("Input contains invalid characters");
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return { isValid: errors.length === 0, errors };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Validates a numeric input
|
|
47
|
-
*/
|
|
48
|
-
export function validateNumber(
|
|
49
|
-
input: unknown,
|
|
50
|
-
options: NumericValidationOptions = {}
|
|
51
|
-
): ValidationResult {
|
|
52
|
-
const errors: string[] = [];
|
|
53
|
-
|
|
54
|
-
if (typeof input !== "number" || isNaN(input)) {
|
|
55
|
-
return { isValid: false, errors: ["Input must be a number"] };
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (options.integer && !Number.isInteger(input)) {
|
|
59
|
-
errors.push("Input must be an integer");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (options.min !== undefined && input < options.min) {
|
|
63
|
-
errors.push(`Input must be at least ${options.min}`);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (options.max !== undefined && input > options.max) {
|
|
67
|
-
errors.push(`Input must be at most ${options.max}`);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return { isValid: errors.length === 0, errors };
|
|
71
|
-
}
|
|
12
|
+
// Re-export validation functions for convenience
|
|
13
|
+
export { validateString, validateNumber };
|
|
72
14
|
|
|
73
15
|
/**
|
|
74
16
|
* Validates URL format
|
|
75
17
|
*/
|
|
76
18
|
export function validateURL(input: unknown): ValidationResult {
|
|
77
19
|
if (typeof input !== "string") {
|
|
78
|
-
return { isValid: false, errors:
|
|
20
|
+
return { isValid: false, errors: { type: "URL must be a string" } };
|
|
79
21
|
}
|
|
80
22
|
|
|
81
23
|
try {
|
|
82
24
|
const url = new URL(input) as URL & { protocol: string };
|
|
83
25
|
if (!["http:", "https:"].includes(url.protocol)) {
|
|
84
|
-
return { isValid: false, errors:
|
|
26
|
+
return { isValid: false, errors: { protocol: "Only HTTP and HTTPS protocols are allowed" } };
|
|
85
27
|
}
|
|
86
|
-
return { isValid: true, errors:
|
|
28
|
+
return { isValid: true, errors: {} };
|
|
87
29
|
} catch {
|
|
88
|
-
return { isValid: false, errors:
|
|
30
|
+
return { isValid: false, errors: { format: "Invalid URL format" } };
|
|
89
31
|
}
|
|
90
32
|
}
|
|
91
33
|
|
|
@@ -94,15 +36,15 @@ export function validateURL(input: unknown): ValidationResult {
|
|
|
94
36
|
*/
|
|
95
37
|
export function validateEmail(input: unknown): ValidationResult {
|
|
96
38
|
if (typeof input !== "string") {
|
|
97
|
-
return { isValid: false, errors:
|
|
39
|
+
return { isValid: false, errors: { type: "Email must be a string" } };
|
|
98
40
|
}
|
|
99
41
|
|
|
100
42
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
101
43
|
if (!emailRegex.test(input)) {
|
|
102
|
-
return { isValid: false, errors:
|
|
44
|
+
return { isValid: false, errors: { format: "Invalid email format" } };
|
|
103
45
|
}
|
|
104
46
|
|
|
105
|
-
return { isValid: true, errors:
|
|
47
|
+
return { isValid: true, errors: {} };
|
|
106
48
|
}
|
|
107
49
|
|
|
108
50
|
/**
|
|
@@ -110,21 +52,21 @@ export function validateEmail(input: unknown): ValidationResult {
|
|
|
110
52
|
*/
|
|
111
53
|
export function validateBase64(input: unknown): ValidationResult {
|
|
112
54
|
if (typeof input !== "string") {
|
|
113
|
-
return { isValid: false, errors:
|
|
55
|
+
return { isValid: false, errors: { type: "Input must be a string" } };
|
|
114
56
|
}
|
|
115
57
|
|
|
116
58
|
if (input.length === 0) {
|
|
117
|
-
return { isValid: false, errors:
|
|
59
|
+
return { isValid: false, errors: { length: "Base64 string cannot be empty" } };
|
|
118
60
|
}
|
|
119
61
|
|
|
120
62
|
const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
121
63
|
if (!base64Regex.test(input)) {
|
|
122
|
-
return { isValid: false, errors:
|
|
64
|
+
return { isValid: false, errors: { format: "Invalid base64 format" } };
|
|
123
65
|
}
|
|
124
66
|
|
|
125
67
|
if (input.length % 4 !== 0) {
|
|
126
|
-
return { isValid: false, errors:
|
|
68
|
+
return { isValid: false, errors: { length: "Base64 string length must be a multiple of 4" } };
|
|
127
69
|
}
|
|
128
70
|
|
|
129
|
-
return { isValid: true, errors:
|
|
71
|
+
return { isValid: true, errors: {} };
|
|
130
72
|
}
|
|
@@ -14,7 +14,7 @@ export function validateUserId(input: unknown): ValidationResult {
|
|
|
14
14
|
maxLength: 100,
|
|
15
15
|
pattern: /^[a-zA-Z0-9_-]+$/,
|
|
16
16
|
};
|
|
17
|
-
return validateString(input, options);
|
|
17
|
+
return validateString(typeof input === "string" ? input : String(input), options);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -25,6 +25,6 @@ export function validateCreationId(input: unknown): ValidationResult {
|
|
|
25
25
|
minLength: 1,
|
|
26
26
|
maxLength: 100,
|
|
27
27
|
};
|
|
28
|
-
return validateString(input, options);
|
|
28
|
+
return validateString(typeof input === "string" ? input : String(input), options);
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -88,7 +88,7 @@ export const useImageGeneration = <TInput extends ImageGenerationInput, TResult>
|
|
|
88
88
|
return useGenerationOrchestrator(strategy, {
|
|
89
89
|
userId,
|
|
90
90
|
alertMessages,
|
|
91
|
-
onSuccess: onSuccess ? async (result) => onSuccess(result) : undefined,
|
|
91
|
+
onSuccess: onSuccess ? async (result) => onSuccess(result as TResult) : undefined,
|
|
92
92
|
onError: async (error) => handleError(error),
|
|
93
93
|
});
|
|
94
94
|
};
|
|
@@ -103,7 +103,7 @@ export const useVideoGeneration = <TResult>(
|
|
|
103
103
|
return useGenerationOrchestrator(strategy, {
|
|
104
104
|
userId,
|
|
105
105
|
alertMessages,
|
|
106
|
-
onSuccess: onSuccess ? async (result) => onSuccess(result) : undefined,
|
|
106
|
+
onSuccess: onSuccess ? async (result) => onSuccess(result as TResult) : undefined,
|
|
107
107
|
onError: async (error) => handleError(error),
|
|
108
108
|
});
|
|
109
109
|
};
|
|
@@ -2,18 +2,9 @@
|
|
|
2
2
|
* Shared Validation Utilities
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
NumericValidationOptions,
|
|
9
|
-
} from "../../../infrastructure/validation/base-validator.types";
|
|
10
|
-
|
|
11
|
-
// Re-export types
|
|
12
|
-
export type {
|
|
13
|
-
ValidationResult,
|
|
14
|
-
StringValidationOptions,
|
|
15
|
-
NumericValidationOptions,
|
|
16
|
-
};
|
|
5
|
+
// Export all from common-validators
|
|
6
|
+
export * from "./common-validators";
|
|
7
|
+
export * from "./common-validators.types";
|
|
17
8
|
|
|
18
9
|
// Export functions from advanced-validator
|
|
19
10
|
export { combineValidationResults } from "../../../infrastructure/validation/advanced-validator";
|
|
@@ -22,37 +13,6 @@ export { combineValidationResults } from "../../../infrastructure/validation/adv
|
|
|
22
13
|
export { handleError } from "./error-handler";
|
|
23
14
|
export { ErrorType } from "./error-handler.types";
|
|
24
15
|
|
|
25
|
-
/**
|
|
26
|
-
* Validate a string is not empty
|
|
27
|
-
*/
|
|
28
|
-
export function validateString(value: string): ValidationResult {
|
|
29
|
-
if (typeof value !== 'string') {
|
|
30
|
-
return { isValid: false, errors: ['Value must be a string'] };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (value.trim().length === 0) {
|
|
34
|
-
return { isValid: false, errors: ['String cannot be empty'] };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return { isValid: true, errors: [] };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Validate URL format
|
|
42
|
-
*/
|
|
43
|
-
export function validateUrl(url: string): ValidationResult {
|
|
44
|
-
if (typeof url !== 'string') {
|
|
45
|
-
return { isValid: false, errors: ['URL must be a string'] };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
new URL(url);
|
|
50
|
-
return { isValid: true, errors: [] };
|
|
51
|
-
} catch {
|
|
52
|
-
return { isValid: false, errors: ['Invalid URL format'] };
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
16
|
/**
|
|
57
17
|
* Validate required fields in an object
|
|
58
18
|
*/
|