@umituz/react-native-ai-generation-content 1.64.0 → 1.65.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.64.0",
3
+ "version": "1.65.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,27 @@
1
+ /**
2
+ * Creation Persistence Types
3
+ * Type definitions for persistence hook
4
+ */
5
+
6
+ export interface UseCreationPersistenceConfig {
7
+ readonly type: string;
8
+ readonly collectionName?: string;
9
+ readonly creditCost?: number;
10
+ readonly onCreditDeduct?: (cost: number) => Promise<void | boolean>;
11
+ }
12
+
13
+ export interface BaseProcessingStartData {
14
+ readonly creationId: string;
15
+ }
16
+
17
+ export interface BaseProcessingResult {
18
+ readonly creationId?: string;
19
+ readonly imageUrl?: string;
20
+ readonly videoUrl?: string;
21
+ }
22
+
23
+ export interface UseCreationPersistenceReturn {
24
+ readonly onProcessingStart: <T extends BaseProcessingStartData>(data: T) => void;
25
+ readonly onProcessingComplete: <T extends BaseProcessingResult>(result: T) => void;
26
+ readonly onError: (error: string, creationId?: string) => void;
27
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Creation Validation Utilities
3
+ *
4
+ * Centralized validation logic for creation persistence.
5
+ * Keeps validation rules separate from hook logic.
6
+ *
7
+ * @module CreationValidators
8
+ */
9
+
10
+ import {
11
+ CREATION_VALIDATION,
12
+ CREATION_STATUS,
13
+ CREATION_FIELDS,
14
+ } from "../../domain/constants";
15
+ import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
16
+
17
+ declare const __DEV__: boolean;
18
+
19
+ export interface ValidationResult {
20
+ isValid: boolean;
21
+ error?: string;
22
+ }
23
+
24
+ /**
25
+ * Validates that at least one URL is present
26
+ */
27
+ export function validateHasUrl(
28
+ imageUrl?: string,
29
+ videoUrl?: string
30
+ ): ValidationResult {
31
+ if (!imageUrl && !videoUrl) {
32
+ return {
33
+ isValid: false,
34
+ error: "No output URL provided",
35
+ };
36
+ }
37
+ return { isValid: true };
38
+ }
39
+
40
+ /**
41
+ * Validates URI protocol
42
+ */
43
+ export function validateUriProtocol(uri: string): ValidationResult {
44
+ const hasValidProtocol = CREATION_VALIDATION.VALID_URI_PROTOCOLS.some(
45
+ (protocol) => uri.startsWith(protocol)
46
+ );
47
+
48
+ if (!hasValidProtocol) {
49
+ return {
50
+ isValid: false,
51
+ error: `Invalid URI protocol. Expected one of: ${CREATION_VALIDATION.VALID_URI_PROTOCOLS.join(", ")}`,
52
+ };
53
+ }
54
+
55
+ return { isValid: true };
56
+ }
57
+
58
+ /**
59
+ * Validates URI length
60
+ */
61
+ export function validateUriLength(uri: string): ValidationResult {
62
+ if (uri.length > CREATION_VALIDATION.MAX_URI_LENGTH) {
63
+ return {
64
+ isValid: false,
65
+ error: `URI length (${uri.length}) exceeds maximum (${CREATION_VALIDATION.MAX_URI_LENGTH})`,
66
+ };
67
+ }
68
+
69
+ return { isValid: true };
70
+ }
71
+
72
+ /**
73
+ * Runs all validations and returns first error
74
+ */
75
+ export function runAllValidations(
76
+ imageUrl?: string,
77
+ videoUrl?: string
78
+ ): ValidationResult {
79
+ const urlCheck = validateHasUrl(imageUrl, videoUrl);
80
+ if (!urlCheck.isValid) return urlCheck;
81
+
82
+ const uri = imageUrl || videoUrl || "";
83
+ const protocolCheck = validateUriProtocol(uri);
84
+ if (!protocolCheck.isValid) return protocolCheck;
85
+
86
+ return validateUriLength(uri);
87
+ }
88
+
89
+ /**
90
+ * Marks creation as failed
91
+ */
92
+ export function markCreationAsFailed(
93
+ repository: ICreationsRepository,
94
+ userId: string,
95
+ creationId: string,
96
+ error: string
97
+ ): void {
98
+ repository.update(userId, creationId, {
99
+ [CREATION_FIELDS.STATUS]: CREATION_STATUS.FAILED,
100
+ [CREATION_FIELDS.METADATA]: { error, failedAt: new Date().toISOString() },
101
+ });
102
+ }
@@ -1,73 +1,36 @@
1
1
  /**
2
2
  * useCreationPersistence Hook
3
- * Encapsulates Firestore persistence logic for AI generation features
4
- * Realtime listener handles UI updates automatically
5
3
  */
6
4
 
7
5
  import { useCallback, useMemo } from "react";
8
6
  import { useAuth } from "@umituz/react-native-auth";
9
7
  import { createCreationsRepository } from "../../infrastructure/adapters";
10
8
  import type { Creation } from "../../domain/entities/Creation";
11
- import {
12
- CREATION_STATUS,
13
- CREATION_VALIDATION,
14
- CREATION_FIELDS,
15
- } from "../../domain/constants";
9
+ import { CREATION_STATUS, CREATION_FIELDS } from "../../domain/constants";
10
+ import { runAllValidations, markCreationAsFailed } from "./creation-validators";
11
+ import type {
12
+ UseCreationPersistenceConfig,
13
+ UseCreationPersistenceReturn,
14
+ BaseProcessingStartData,
15
+ BaseProcessingResult,
16
+ } from "./creation-persistence.types";
16
17
 
17
- declare const __DEV__: boolean;
18
-
19
- export interface UseCreationPersistenceConfig {
20
- readonly type: string;
21
- readonly collectionName?: string;
22
- readonly creditCost?: number;
23
- readonly onCreditDeduct?: (cost: number) => Promise<void | boolean>;
24
- }
25
-
26
- export interface BaseProcessingStartData {
27
- readonly creationId: string;
28
- }
29
-
30
- export interface BaseProcessingResult {
31
- readonly creationId?: string;
32
- readonly imageUrl?: string;
33
- readonly videoUrl?: string;
34
- }
35
-
36
- export interface UseCreationPersistenceReturn {
37
- readonly onProcessingStart: <T extends BaseProcessingStartData>(data: T) => void;
38
- readonly onProcessingComplete: <T extends BaseProcessingResult>(result: T) => void;
39
- readonly onError: (error: string, creationId?: string) => void;
40
- }
18
+ export type * from "./creation-persistence.types";
41
19
 
42
20
  export function useCreationPersistence(
43
- config: UseCreationPersistenceConfig,
21
+ config: UseCreationPersistenceConfig
44
22
  ): UseCreationPersistenceReturn {
45
23
  const { type, collectionName = "creations", creditCost, onCreditDeduct } = config;
46
24
  const { userId } = useAuth();
47
-
48
- const repository = useMemo(
49
- () => createCreationsRepository(collectionName),
50
- [collectionName],
51
- );
25
+ const repository = useMemo(() => createCreationsRepository(collectionName), [collectionName]);
52
26
 
53
27
  const onProcessingStart = useCallback(
54
28
  <T extends BaseProcessingStartData>(data: T) => {
55
- if (typeof __DEV__ !== "undefined" && __DEV__) {
56
- console.log("[useCreationPersistence] onProcessingStart", { type, userId });
57
- }
58
-
59
- if (!userId) {
60
- if (typeof __DEV__ !== "undefined" && __DEV__) {
61
- console.log("[useCreationPersistence] No userId, skipping");
62
- }
63
- return;
64
- }
65
-
29
+ if (!userId) return;
66
30
  const { creationId, ...rest } = data;
67
31
  const cleanMetadata = Object.fromEntries(
68
- Object.entries(rest).filter(([, v]) => v !== undefined && v !== null),
32
+ Object.entries(rest).filter(([, v]) => v !== undefined && v !== null)
69
33
  );
70
-
71
34
  const creation: Creation = {
72
35
  id: creationId,
73
36
  uri: "",
@@ -78,199 +41,49 @@ export function useCreationPersistence(
78
41
  isFavorite: false,
79
42
  metadata: cleanMetadata,
80
43
  };
81
-
82
- if (typeof __DEV__ !== "undefined" && __DEV__) {
83
- console.log("[useCreationPersistence] Creating document", { creationId, type });
84
- }
85
-
86
44
  repository.create(userId, creation);
87
45
  },
88
- [userId, repository, type],
46
+ [userId, repository, type]
89
47
  );
90
48
 
91
- /**
92
- * Handles generation completion with comprehensive validation
93
- *
94
- * VALIDATION RULES:
95
- * 1. At least one URL (imageUrl or videoUrl) must be present
96
- * 2. URI must start with valid protocol (http/https/data:)
97
- * 3. URI length must not exceed limit
98
- *
99
- * If validation fails, marks creation as FAILED instead of silently succeeding
100
- */
101
49
  const onProcessingComplete = useCallback(
102
50
  <T extends BaseProcessingResult>(result: T) => {
103
- if (typeof __DEV__ !== "undefined" && __DEV__) {
104
- console.log("[useCreationPersistence] onProcessingComplete", {
105
- creationId: result.creationId,
106
- hasImageUrl: !!result.imageUrl,
107
- hasVideoUrl: !!result.videoUrl,
108
- });
109
- }
110
-
111
- // Validation: User ID and Creation ID
112
- if (!userId || !result.creationId) {
113
- if (typeof __DEV__ !== "undefined" && __DEV__) {
114
- console.warn("[useCreationPersistence] Missing required fields", {
115
- hasUserId: !!userId,
116
- hasCreationId: !!result.creationId,
117
- });
118
- }
119
- return;
120
- }
121
-
122
- // Validation: At least one URL must be present
123
- if (!result.imageUrl && !result.videoUrl) {
124
- if (typeof __DEV__ !== "undefined" && __DEV__) {
125
- console.error("[useCreationPersistence] No output URL provided", {
126
- creationId: result.creationId,
127
- });
128
- }
51
+ if (!userId || !result.creationId) return;
129
52
 
130
- // Mark as failed instead of silently succeeding
131
- repository.update(userId, result.creationId, {
132
- [CREATION_FIELDS.STATUS]: CREATION_STATUS.FAILED,
133
- [CREATION_FIELDS.METADATA]: {
134
- error: "No output URL provided",
135
- failedAt: new Date().toISOString(),
136
- },
137
- });
53
+ const validation = runAllValidations(result.imageUrl, result.videoUrl);
54
+ if (!validation.isValid) {
55
+ markCreationAsFailed(repository, userId, result.creationId, validation.error!);
138
56
  return;
139
57
  }
140
58
 
141
59
  const uri = result.imageUrl || result.videoUrl || "";
142
-
143
- // Validation: URI format
144
- const hasValidProtocol = CREATION_VALIDATION.VALID_URI_PROTOCOLS.some((protocol) =>
145
- uri.startsWith(protocol)
146
- );
147
-
148
- if (!hasValidProtocol) {
149
- if (typeof __DEV__ !== "undefined" && __DEV__) {
150
- console.error("[useCreationPersistence] Invalid URI protocol", {
151
- creationId: result.creationId,
152
- uri: uri.substring(0, 50) + "...",
153
- validProtocols: CREATION_VALIDATION.VALID_URI_PROTOCOLS,
154
- });
155
- }
156
-
157
- // Mark as failed
158
- repository.update(userId, result.creationId, {
159
- [CREATION_FIELDS.STATUS]: CREATION_STATUS.FAILED,
160
- [CREATION_FIELDS.METADATA]: {
161
- error: `Invalid URI protocol. Expected one of: ${CREATION_VALIDATION.VALID_URI_PROTOCOLS.join(", ")}`,
162
- failedAt: new Date().toISOString(),
163
- },
164
- });
165
- return;
166
- }
167
-
168
- // Validation: URI length
169
- if (uri.length > CREATION_VALIDATION.MAX_URI_LENGTH) {
170
- if (typeof __DEV__ !== "undefined" && __DEV__) {
171
- console.error("[useCreationPersistence] URI exceeds maximum length", {
172
- creationId: result.creationId,
173
- uriLength: uri.length,
174
- maxLength: CREATION_VALIDATION.MAX_URI_LENGTH,
175
- });
176
- }
177
-
178
- // Mark as failed
179
- repository.update(userId, result.creationId, {
180
- [CREATION_FIELDS.STATUS]: CREATION_STATUS.FAILED,
181
- [CREATION_FIELDS.METADATA]: {
182
- error: `URI length (${uri.length}) exceeds maximum (${CREATION_VALIDATION.MAX_URI_LENGTH})`,
183
- failedAt: new Date().toISOString(),
184
- },
185
- });
186
- return;
187
- }
188
-
189
- // Create output object
190
- const output = result.imageUrl
191
- ? { imageUrl: result.imageUrl }
192
- : result.videoUrl
193
- ? { videoUrl: result.videoUrl }
194
- : undefined;
195
-
196
- // Update with both nested (output) and flat (imageUrl/videoUrl) fields
197
- // This ensures compatibility with different document mappers
198
- const updates: Record<string, unknown> = {
60
+ repository.update(userId, result.creationId, {
199
61
  [CREATION_FIELDS.URI]: uri,
200
62
  [CREATION_FIELDS.STATUS]: CREATION_STATUS.COMPLETED,
201
- [CREATION_FIELDS.OUTPUT]: output,
202
- };
203
-
204
- // Add flat fields for backwards compatibility
205
- if (result.imageUrl) {
206
- updates[CREATION_FIELDS.IMAGE_URL] = result.imageUrl;
207
- }
208
- if (result.videoUrl) {
209
- updates[CREATION_FIELDS.VIDEO_URL] = result.videoUrl;
210
- }
211
-
212
- if (typeof __DEV__ !== "undefined" && __DEV__) {
213
- console.log("[useCreationPersistence] Updating creation", {
214
- creationId: result.creationId,
215
- fieldsUpdating: Object.keys(updates),
216
- uriLength: uri.length,
217
- });
218
- }
219
-
220
- repository.update(userId, result.creationId, updates);
63
+ [CREATION_FIELDS.OUTPUT]: result.imageUrl
64
+ ? { imageUrl: result.imageUrl }
65
+ : { videoUrl: result.videoUrl },
66
+ ...(result.imageUrl && { [CREATION_FIELDS.IMAGE_URL]: result.imageUrl }),
67
+ ...(result.videoUrl && { [CREATION_FIELDS.VIDEO_URL]: result.videoUrl }),
68
+ });
221
69
 
222
- // Credit deduction
223
- if (creditCost && creditCost > 0 && onCreditDeduct) {
224
- if (typeof __DEV__ !== "undefined" && __DEV__) {
225
- console.log("[useCreationPersistence] Deducting credits", { cost: creditCost });
226
- }
227
- onCreditDeduct(creditCost).catch((err) => {
228
- if (typeof __DEV__ !== "undefined" && __DEV__) {
229
- console.error("[useCreationPersistence] Credit deduction failed", err);
230
- }
231
- });
70
+ if (creditCost && onCreditDeduct) {
71
+ onCreditDeduct(creditCost).catch(() => {});
232
72
  }
233
73
  },
234
- [userId, repository, creditCost, onCreditDeduct],
74
+ [userId, repository, creditCost, onCreditDeduct]
235
75
  );
236
76
 
237
- /**
238
- * Handles generation errors
239
- * Marks creation as FAILED with error context
240
- */
241
77
  const onError = useCallback(
242
78
  (error: string, creationId?: string) => {
243
- if (typeof __DEV__ !== "undefined" && __DEV__) {
244
- console.error("[useCreationPersistence] Generation error", {
245
- error,
246
- creationId,
247
- timestamp: new Date().toISOString(),
248
- });
249
- }
250
-
251
- if (!userId || !creationId) {
252
- if (typeof __DEV__ !== "undefined" && __DEV__) {
253
- console.warn("[useCreationPersistence] Cannot mark error - missing required fields", {
254
- hasUserId: !!userId,
255
- hasCreationId: !!creationId,
256
- });
257
- }
258
- return;
259
- }
260
-
261
- repository.update(userId, creationId, {
262
- [CREATION_FIELDS.STATUS]: CREATION_STATUS.FAILED,
263
- [CREATION_FIELDS.METADATA]: {
264
- error,
265
- failedAt: new Date().toISOString(),
266
- },
267
- });
79
+ if (!userId || !creationId) return;
80
+ markCreationAsFailed(repository, userId, creationId, error);
268
81
  },
269
- [userId, repository],
82
+ [userId, repository]
270
83
  );
271
84
 
272
85
  return useMemo(
273
86
  () => ({ onProcessingStart, onProcessingComplete, onError }),
274
- [onProcessingStart, onProcessingComplete, onError],
87
+ [onProcessingStart, onProcessingComplete, onError]
275
88
  );
276
89
  }