@umituz/react-native-ai-generation-content 1.63.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 +1 -1
- package/src/domains/creations/domain/constants/creation-errors.constants.ts +27 -0
- package/src/domains/creations/domain/constants/creation-fields.constants.ts +85 -0
- package/src/domains/creations/domain/constants/creation-query.constants.ts +37 -0
- package/src/domains/creations/domain/constants/creation-status.constants.ts +46 -0
- package/src/domains/creations/domain/constants/creation-types.constants.ts +45 -0
- package/src/domains/creations/domain/constants/creation-validation.constants.ts +60 -0
- package/src/domains/creations/domain/constants/index.ts +21 -2
- package/src/domains/creations/infrastructure/repositories/CreationsFetcher.ts +1 -1
- package/src/domains/creations/infrastructure/repositories/creations-operations.ts +1 -1
- package/src/domains/creations/presentation/hooks/creation-persistence.types.ts +27 -0
- package/src/domains/creations/presentation/hooks/creation-validators.ts +102 -0
- package/src/domains/creations/presentation/hooks/useCreationPersistence.ts +32 -219
- package/src/domains/creations/domain/constants/creation.constants.ts +0 -251
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.
|
|
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 Error Codes
|
|
3
|
+
*
|
|
4
|
+
* Domain exception codes for client-side error handling.
|
|
5
|
+
* Each code represents a specific error scenario.
|
|
6
|
+
*
|
|
7
|
+
* @module CreationErrorsConstants
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Error codes for domain exceptions
|
|
12
|
+
*/
|
|
13
|
+
export const CREATION_ERROR_CODES = {
|
|
14
|
+
NOT_FOUND: "CREATION_NOT_FOUND" as const,
|
|
15
|
+
VALIDATION_FAILED: "CREATION_VALIDATION_FAILED" as const,
|
|
16
|
+
INVALID_STATE_TRANSITION: "CREATION_INVALID_STATE_TRANSITION" as const,
|
|
17
|
+
PERSISTENCE_FAILED: "CREATION_PERSISTENCE_FAILED" as const,
|
|
18
|
+
INVALID_URI: "CREATION_INVALID_URI" as const,
|
|
19
|
+
INVALID_OUTPUT: "CREATION_INVALID_OUTPUT" as const,
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
22
|
+
/** Union type of all error codes */
|
|
23
|
+
export type CreationErrorCode =
|
|
24
|
+
typeof CREATION_ERROR_CODES[keyof typeof CREATION_ERROR_CODES];
|
|
25
|
+
|
|
26
|
+
// Freeze to prevent mutations
|
|
27
|
+
Object.freeze(CREATION_ERROR_CODES);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creation Field Names
|
|
3
|
+
*
|
|
4
|
+
* Central registry of all Firestore document field names.
|
|
5
|
+
* Prevents magic strings throughout the codebase.
|
|
6
|
+
*
|
|
7
|
+
* @module CreationFieldsConstants
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Firestore document field names
|
|
12
|
+
* Single source of truth for field naming
|
|
13
|
+
*/
|
|
14
|
+
export const CREATION_FIELDS = {
|
|
15
|
+
// Core identification
|
|
16
|
+
ID: "id" as const,
|
|
17
|
+
TYPE: "type" as const,
|
|
18
|
+
|
|
19
|
+
// Media URLs
|
|
20
|
+
URI: "uri" as const,
|
|
21
|
+
IMAGE_URL: "imageUrl" as const,
|
|
22
|
+
VIDEO_URL: "videoUrl" as const,
|
|
23
|
+
ORIGINAL_URI: "originalUri" as const,
|
|
24
|
+
|
|
25
|
+
// Structured output
|
|
26
|
+
OUTPUT: "output" as const,
|
|
27
|
+
|
|
28
|
+
// Status and metadata
|
|
29
|
+
STATUS: "status" as const,
|
|
30
|
+
METADATA: "metadata" as const,
|
|
31
|
+
PROMPT: "prompt" as const,
|
|
32
|
+
|
|
33
|
+
// Timestamps
|
|
34
|
+
CREATED_AT: "createdAt" as const,
|
|
35
|
+
UPDATED_AT: "updatedAt" as const,
|
|
36
|
+
DELETED_AT: "deletedAt" as const,
|
|
37
|
+
RATED_AT: "ratedAt" as const,
|
|
38
|
+
|
|
39
|
+
// User interactions
|
|
40
|
+
IS_FAVORITE: "isFavorite" as const,
|
|
41
|
+
IS_SHARED: "isShared" as const,
|
|
42
|
+
RATING: "rating" as const,
|
|
43
|
+
|
|
44
|
+
// AI provider metadata
|
|
45
|
+
REQUEST_ID: "requestId" as const,
|
|
46
|
+
MODEL: "model" as const,
|
|
47
|
+
} as const;
|
|
48
|
+
|
|
49
|
+
/** Union type of all field names */
|
|
50
|
+
export type CreationFieldName =
|
|
51
|
+
typeof CREATION_FIELDS[keyof typeof CREATION_FIELDS];
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Updatable fields list
|
|
55
|
+
* Fields that can be modified after creation
|
|
56
|
+
*/
|
|
57
|
+
export const UPDATABLE_FIELDS: ReadonlyArray<CreationFieldName> = [
|
|
58
|
+
CREATION_FIELDS.URI,
|
|
59
|
+
CREATION_FIELDS.STATUS,
|
|
60
|
+
CREATION_FIELDS.OUTPUT,
|
|
61
|
+
CREATION_FIELDS.IMAGE_URL,
|
|
62
|
+
CREATION_FIELDS.VIDEO_URL,
|
|
63
|
+
CREATION_FIELDS.METADATA,
|
|
64
|
+
CREATION_FIELDS.IS_SHARED,
|
|
65
|
+
CREATION_FIELDS.IS_FAVORITE,
|
|
66
|
+
CREATION_FIELDS.RATING,
|
|
67
|
+
CREATION_FIELDS.RATED_AT,
|
|
68
|
+
CREATION_FIELDS.DELETED_AT,
|
|
69
|
+
CREATION_FIELDS.REQUEST_ID,
|
|
70
|
+
CREATION_FIELDS.MODEL,
|
|
71
|
+
CREATION_FIELDS.PROMPT,
|
|
72
|
+
] as const;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Type guard for updatable fields
|
|
76
|
+
*/
|
|
77
|
+
export function isUpdatableField(
|
|
78
|
+
field: string
|
|
79
|
+
): field is CreationFieldName {
|
|
80
|
+
return UPDATABLE_FIELDS.includes(field as CreationFieldName);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Freeze to prevent mutations
|
|
84
|
+
Object.freeze(CREATION_FIELDS);
|
|
85
|
+
Object.freeze(UPDATABLE_FIELDS);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creation Query Configuration
|
|
3
|
+
*
|
|
4
|
+
* Firestore query optimization settings.
|
|
5
|
+
* Controls pagination, caching, and performance.
|
|
6
|
+
*
|
|
7
|
+
* @module CreationQueryConstants
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Firestore query configuration
|
|
12
|
+
*/
|
|
13
|
+
export const CREATION_QUERY_CONFIG = {
|
|
14
|
+
/** Default page size for pagination */
|
|
15
|
+
DEFAULT_PAGE_SIZE: 20,
|
|
16
|
+
|
|
17
|
+
/** Maximum page size (prevent memory issues) */
|
|
18
|
+
MAX_PAGE_SIZE: 100,
|
|
19
|
+
|
|
20
|
+
/** Cache TTL in milliseconds (5 minutes) */
|
|
21
|
+
CACHE_TTL_MS: 5 * 60 * 1000,
|
|
22
|
+
|
|
23
|
+
/** Include metadata changes in snapshots */
|
|
24
|
+
INCLUDE_METADATA_CHANGES: false,
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Collection names
|
|
29
|
+
*/
|
|
30
|
+
export const CREATION_COLLECTIONS = {
|
|
31
|
+
ROOT: "creations" as const,
|
|
32
|
+
USERS: "users" as const,
|
|
33
|
+
} as const;
|
|
34
|
+
|
|
35
|
+
// Freeze to prevent mutations
|
|
36
|
+
Object.freeze(CREATION_QUERY_CONFIG);
|
|
37
|
+
Object.freeze(CREATION_COLLECTIONS);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creation Status Constants
|
|
3
|
+
*
|
|
4
|
+
* Defines the lifecycle states of a creation.
|
|
5
|
+
* Follows state machine pattern for valid transitions.
|
|
6
|
+
*
|
|
7
|
+
* @module CreationStatusConstants
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Creation lifecycle status values
|
|
12
|
+
* Represents the aggregate root's state machine
|
|
13
|
+
*/
|
|
14
|
+
export const CREATION_STATUS = {
|
|
15
|
+
/** Initial state: AI generation in progress */
|
|
16
|
+
PROCESSING: "processing" as const,
|
|
17
|
+
|
|
18
|
+
/** Success state: Generation completed with result */
|
|
19
|
+
COMPLETED: "completed" as const,
|
|
20
|
+
|
|
21
|
+
/** Error state: Generation failed */
|
|
22
|
+
FAILED: "failed" as const,
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
25
|
+
/** Union type of all valid status values */
|
|
26
|
+
export type CreationStatusValue =
|
|
27
|
+
typeof CREATION_STATUS[keyof typeof CREATION_STATUS];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Type guard for creation status values
|
|
31
|
+
* @param value - Value to check
|
|
32
|
+
* @returns True if value is a valid creation status
|
|
33
|
+
*/
|
|
34
|
+
export function isCreationStatus(
|
|
35
|
+
value: unknown
|
|
36
|
+
): value is CreationStatusValue {
|
|
37
|
+
return (
|
|
38
|
+
typeof value === "string" &&
|
|
39
|
+
Object.values(CREATION_STATUS).includes(
|
|
40
|
+
value as CreationStatusValue
|
|
41
|
+
)
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Freeze to prevent mutations
|
|
46
|
+
Object.freeze(CREATION_STATUS);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creation Type Discriminators
|
|
3
|
+
*
|
|
4
|
+
* Used for polymorphic behavior based on creation type.
|
|
5
|
+
* Each type may have different metadata structure.
|
|
6
|
+
*
|
|
7
|
+
* @module CreationTypesConstants
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Creation type discriminators
|
|
12
|
+
*/
|
|
13
|
+
export const CREATION_TYPES = {
|
|
14
|
+
/** Baby face prediction generation */
|
|
15
|
+
BABY_PREDICTION: "baby-prediction" as const,
|
|
16
|
+
|
|
17
|
+
/** Text-to-video generation */
|
|
18
|
+
TEXT_TO_VIDEO: "text-to-video" as const,
|
|
19
|
+
|
|
20
|
+
/** Image generation */
|
|
21
|
+
IMAGE_GENERATION: "image-generation" as const,
|
|
22
|
+
} as const;
|
|
23
|
+
|
|
24
|
+
/** Union type of all creation types */
|
|
25
|
+
export type CreationTypeValue =
|
|
26
|
+
typeof CREATION_TYPES[keyof typeof CREATION_TYPES];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Type guard for creation type values
|
|
30
|
+
* @param value - Value to check
|
|
31
|
+
* @returns True if value is a valid creation type
|
|
32
|
+
*/
|
|
33
|
+
export function isCreationType(
|
|
34
|
+
value: unknown
|
|
35
|
+
): value is CreationTypeValue {
|
|
36
|
+
return (
|
|
37
|
+
typeof value === "string" &&
|
|
38
|
+
Object.values(CREATION_TYPES).includes(
|
|
39
|
+
value as CreationTypeValue
|
|
40
|
+
)
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Freeze to prevent mutations
|
|
45
|
+
Object.freeze(CREATION_TYPES);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creation Validation Rules
|
|
3
|
+
*
|
|
4
|
+
* Business invariants and constraints enforced at domain boundaries.
|
|
5
|
+
* All validation rules centralized here for consistency.
|
|
6
|
+
*
|
|
7
|
+
* @module CreationValidationConstants
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Validation rules and constraints
|
|
12
|
+
*/
|
|
13
|
+
export const CREATION_VALIDATION = {
|
|
14
|
+
/** Maximum URI length (Firestore: 1MB, reasonable: 2KB) */
|
|
15
|
+
MAX_URI_LENGTH: 2048,
|
|
16
|
+
|
|
17
|
+
/** Maximum metadata size in bytes (JSON stringified) */
|
|
18
|
+
MAX_METADATA_SIZE: 10240, // 10KB
|
|
19
|
+
|
|
20
|
+
/** Rating constraints (1-5 stars) */
|
|
21
|
+
MIN_RATING: 1,
|
|
22
|
+
MAX_RATING: 5,
|
|
23
|
+
|
|
24
|
+
/** Prompt length constraints */
|
|
25
|
+
MIN_PROMPT_LENGTH: 1,
|
|
26
|
+
MAX_PROMPT_LENGTH: 500,
|
|
27
|
+
|
|
28
|
+
/** Valid URI protocols */
|
|
29
|
+
VALID_URI_PROTOCOLS: ["http:", "https:", "data:"] as const,
|
|
30
|
+
|
|
31
|
+
/** Valid image MIME types */
|
|
32
|
+
VALID_IMAGE_MIMES: [
|
|
33
|
+
"image/jpeg",
|
|
34
|
+
"image/png",
|
|
35
|
+
"image/webp",
|
|
36
|
+
"image/gif",
|
|
37
|
+
] as const,
|
|
38
|
+
|
|
39
|
+
/** Valid video MIME types */
|
|
40
|
+
VALID_VIDEO_MIMES: [
|
|
41
|
+
"video/mp4",
|
|
42
|
+
"video/webm",
|
|
43
|
+
"video/quicktime",
|
|
44
|
+
] as const,
|
|
45
|
+
} as const;
|
|
46
|
+
|
|
47
|
+
/** Union type of valid URI protocols */
|
|
48
|
+
export type ValidUriProtocol =
|
|
49
|
+
typeof CREATION_VALIDATION.VALID_URI_PROTOCOLS[number];
|
|
50
|
+
|
|
51
|
+
/** Union type of valid image MIME types */
|
|
52
|
+
export type ValidImageMime =
|
|
53
|
+
typeof CREATION_VALIDATION.VALID_IMAGE_MIMES[number];
|
|
54
|
+
|
|
55
|
+
/** Union type of valid video MIME types */
|
|
56
|
+
export type ValidVideoMime =
|
|
57
|
+
typeof CREATION_VALIDATION.VALID_VIDEO_MIMES[number];
|
|
58
|
+
|
|
59
|
+
// Freeze to prevent mutations
|
|
60
|
+
Object.freeze(CREATION_VALIDATION);
|
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Creation Domain Constants Exports
|
|
2
|
+
* Creation Domain Constants - Modular Exports
|
|
3
|
+
*
|
|
4
|
+
* All constants follow 100-line rule for maintainability.
|
|
5
|
+
* Each module has single responsibility.
|
|
3
6
|
*/
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
// Status constants (40 lines)
|
|
9
|
+
export * from "./creation-status.constants";
|
|
10
|
+
|
|
11
|
+
// Field name constants (70 lines)
|
|
12
|
+
export * from "./creation-fields.constants";
|
|
13
|
+
|
|
14
|
+
// Validation rule constants (60 lines)
|
|
15
|
+
export * from "./creation-validation.constants";
|
|
16
|
+
|
|
17
|
+
// Type discriminator constants (45 lines)
|
|
18
|
+
export * from "./creation-types.constants";
|
|
19
|
+
|
|
20
|
+
// Error code constants (35 lines)
|
|
21
|
+
export * from "./creation-errors.constants";
|
|
22
|
+
|
|
23
|
+
// Query & collection constants (40 lines)
|
|
24
|
+
export * from "./creation-query.constants";
|
|
@@ -3,7 +3,7 @@ import { type FirestorePathResolver } from "@umituz/react-native-firebase";
|
|
|
3
3
|
import type { DocumentMapper } from "../../domain/value-objects/CreationsConfig";
|
|
4
4
|
import type { Creation, CreationDocument } from "../../domain/entities/Creation";
|
|
5
5
|
import type { CreationsSubscriptionCallback, UnsubscribeFunction } from "../../domain/repositories/ICreationsRepository";
|
|
6
|
-
import { CREATION_FIELDS } from "../../domain/constants
|
|
6
|
+
import { CREATION_FIELDS } from "../../domain/constants";
|
|
7
7
|
|
|
8
8
|
declare const __DEV__: boolean;
|
|
9
9
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { setDoc, updateDoc, deleteDoc } from "firebase/firestore";
|
|
7
7
|
import { type FirestorePathResolver } from "@umituz/react-native-firebase";
|
|
8
8
|
import type { Creation, CreationDocument } from "../../domain/entities/Creation";
|
|
9
|
-
import { CREATION_FIELDS, isUpdatableField, type CreationFieldName } from "../../domain/constants
|
|
9
|
+
import { CREATION_FIELDS, isUpdatableField, type CreationFieldName } from "../../domain/constants";
|
|
10
10
|
|
|
11
11
|
declare const __DEV__: boolean;
|
|
12
12
|
|
|
@@ -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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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]:
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
223
|
-
|
|
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 (
|
|
244
|
-
|
|
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
|
}
|
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Creation Domain Constants
|
|
3
|
-
*
|
|
4
|
-
* Single source of truth for all creation-related constants.
|
|
5
|
-
* Following DDD principles, these constants define the ubiquitous language
|
|
6
|
-
* of the Creations bounded context.
|
|
7
|
-
*
|
|
8
|
-
* @module CreationConstants
|
|
9
|
-
* @category Domain
|
|
10
|
-
* @see {@link https://martinfowler.com/bliki/UbiquitousLanguage.html}
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Creation lifecycle status values
|
|
15
|
-
* Represents the aggregate root's state machine transitions
|
|
16
|
-
*/
|
|
17
|
-
export const CREATION_STATUS = {
|
|
18
|
-
/** Initial state: AI generation in progress */
|
|
19
|
-
PROCESSING: "processing" as const,
|
|
20
|
-
/** Success state: Generation completed with result */
|
|
21
|
-
COMPLETED: "completed" as const,
|
|
22
|
-
/** Error state: Generation failed */
|
|
23
|
-
FAILED: "failed" as const,
|
|
24
|
-
} as const;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Creation type discriminators
|
|
28
|
-
* Used for polymorphic behavior based on creation type
|
|
29
|
-
*/
|
|
30
|
-
export const CREATION_TYPES = {
|
|
31
|
-
BABY_PREDICTION: "baby-prediction" as const,
|
|
32
|
-
TEXT_TO_VIDEO: "text-to-video" as const,
|
|
33
|
-
IMAGE_GENERATION: "image-generation" as const,
|
|
34
|
-
} as const;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Firestore document field names
|
|
38
|
-
* Central registry preventing magic strings throughout codebase
|
|
39
|
-
*/
|
|
40
|
-
export const CREATION_FIELDS = {
|
|
41
|
-
// Core identification
|
|
42
|
-
ID: "id" as const,
|
|
43
|
-
TYPE: "type" as const,
|
|
44
|
-
|
|
45
|
-
// Media URLs
|
|
46
|
-
URI: "uri" as const,
|
|
47
|
-
IMAGE_URL: "imageUrl" as const,
|
|
48
|
-
VIDEO_URL: "videoUrl" as const,
|
|
49
|
-
ORIGINAL_URI: "originalUri" as const,
|
|
50
|
-
|
|
51
|
-
// Structured output (new format)
|
|
52
|
-
OUTPUT: "output" as const,
|
|
53
|
-
|
|
54
|
-
// Status and metadata
|
|
55
|
-
STATUS: "status" as const,
|
|
56
|
-
METADATA: "metadata" as const,
|
|
57
|
-
PROMPT: "prompt" as const,
|
|
58
|
-
|
|
59
|
-
// Timestamps
|
|
60
|
-
CREATED_AT: "createdAt" as const,
|
|
61
|
-
UPDATED_AT: "updatedAt" as const,
|
|
62
|
-
DELETED_AT: "deletedAt" as const,
|
|
63
|
-
RATED_AT: "ratedAt" as const,
|
|
64
|
-
|
|
65
|
-
// User interactions
|
|
66
|
-
IS_FAVORITE: "isFavorite" as const,
|
|
67
|
-
IS_SHARED: "isShared" as const,
|
|
68
|
-
RATING: "rating" as const,
|
|
69
|
-
|
|
70
|
-
// AI provider metadata
|
|
71
|
-
REQUEST_ID: "requestId" as const,
|
|
72
|
-
MODEL: "model" as const,
|
|
73
|
-
} as const;
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Validation rules and constraints
|
|
77
|
-
* Business invariants enforced at domain boundaries
|
|
78
|
-
*/
|
|
79
|
-
export const CREATION_VALIDATION = {
|
|
80
|
-
/** Maximum allowed URI length (Firestore limit: 1MB, reasonable: 2KB) */
|
|
81
|
-
MAX_URI_LENGTH: 2048,
|
|
82
|
-
|
|
83
|
-
/** Maximum metadata size in bytes (JSON stringified) */
|
|
84
|
-
MAX_METADATA_SIZE: 10240, // 10KB
|
|
85
|
-
|
|
86
|
-
/** Rating constraints (1-5 stars) */
|
|
87
|
-
MIN_RATING: 1,
|
|
88
|
-
MAX_RATING: 5,
|
|
89
|
-
|
|
90
|
-
/** Prompt length constraints */
|
|
91
|
-
MIN_PROMPT_LENGTH: 1,
|
|
92
|
-
MAX_PROMPT_LENGTH: 500,
|
|
93
|
-
|
|
94
|
-
/** Valid URI protocols */
|
|
95
|
-
VALID_URI_PROTOCOLS: ["http:", "https:", "data:"] as const,
|
|
96
|
-
|
|
97
|
-
/** Valid image MIME types for data URIs */
|
|
98
|
-
VALID_IMAGE_MIMES: ["image/jpeg", "image/png", "image/webp", "image/gif"] as const,
|
|
99
|
-
|
|
100
|
-
/** Valid video MIME types for data URIs */
|
|
101
|
-
VALID_VIDEO_MIMES: ["video/mp4", "video/webm", "video/quicktime"] as const,
|
|
102
|
-
} as const;
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Firestore query configuration
|
|
106
|
-
* Optimization settings for efficient queries
|
|
107
|
-
*/
|
|
108
|
-
export const CREATION_QUERY_CONFIG = {
|
|
109
|
-
/** Default page size for pagination */
|
|
110
|
-
DEFAULT_PAGE_SIZE: 20,
|
|
111
|
-
|
|
112
|
-
/** Maximum page size to prevent memory issues */
|
|
113
|
-
MAX_PAGE_SIZE: 100,
|
|
114
|
-
|
|
115
|
-
/** Cache TTL in milliseconds (5 minutes) */
|
|
116
|
-
CACHE_TTL_MS: 5 * 60 * 1000,
|
|
117
|
-
|
|
118
|
-
/** Whether to include metadata changes in snapshots */
|
|
119
|
-
INCLUDE_METADATA_CHANGES: false,
|
|
120
|
-
} as const;
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Error codes for domain exceptions
|
|
124
|
-
* Enables client-side error handling logic
|
|
125
|
-
*/
|
|
126
|
-
export const CREATION_ERROR_CODES = {
|
|
127
|
-
NOT_FOUND: "CREATION_NOT_FOUND" as const,
|
|
128
|
-
VALIDATION_FAILED: "CREATION_VALIDATION_FAILED" as const,
|
|
129
|
-
INVALID_STATE_TRANSITION: "CREATION_INVALID_STATE_TRANSITION" as const,
|
|
130
|
-
PERSISTENCE_FAILED: "CREATION_PERSISTENCE_FAILED" as const,
|
|
131
|
-
INVALID_URI: "CREATION_INVALID_URI" as const,
|
|
132
|
-
INVALID_OUTPUT: "CREATION_INVALID_OUTPUT" as const,
|
|
133
|
-
} as const;
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Collection and subcollection names
|
|
137
|
-
*/
|
|
138
|
-
export const CREATION_COLLECTIONS = {
|
|
139
|
-
ROOT: "creations" as const,
|
|
140
|
-
USERS: "users" as const,
|
|
141
|
-
} as const;
|
|
142
|
-
|
|
143
|
-
// ============================================================================
|
|
144
|
-
// TYPE EXPORTS - Derived from constants for type safety
|
|
145
|
-
// ============================================================================
|
|
146
|
-
|
|
147
|
-
/** Union type of all valid status values */
|
|
148
|
-
export type CreationStatusValue = typeof CREATION_STATUS[keyof typeof CREATION_STATUS];
|
|
149
|
-
|
|
150
|
-
/** Union type of all valid creation types */
|
|
151
|
-
export type CreationTypeValue = typeof CREATION_TYPES[keyof typeof CREATION_TYPES];
|
|
152
|
-
|
|
153
|
-
/** Union type of all valid field names */
|
|
154
|
-
export type CreationFieldName = typeof CREATION_FIELDS[keyof typeof CREATION_FIELDS];
|
|
155
|
-
|
|
156
|
-
/** Union type of all valid error codes */
|
|
157
|
-
export type CreationErrorCode = typeof CREATION_ERROR_CODES[keyof typeof CREATION_ERROR_CODES];
|
|
158
|
-
|
|
159
|
-
/** Union type of all valid URI protocols */
|
|
160
|
-
export type ValidUriProtocol = typeof CREATION_VALIDATION.VALID_URI_PROTOCOLS[number];
|
|
161
|
-
|
|
162
|
-
/** Union type of all valid image MIME types */
|
|
163
|
-
export type ValidImageMime = typeof CREATION_VALIDATION.VALID_IMAGE_MIMES[number];
|
|
164
|
-
|
|
165
|
-
/** Union type of all valid video MIME types */
|
|
166
|
-
export type ValidVideoMime = typeof CREATION_VALIDATION.VALID_VIDEO_MIMES[number];
|
|
167
|
-
|
|
168
|
-
// ============================================================================
|
|
169
|
-
// UTILITY TYPE GUARDS
|
|
170
|
-
// ============================================================================
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Type guard for creation status values
|
|
174
|
-
* @param value - Value to check
|
|
175
|
-
* @returns True if value is a valid creation status
|
|
176
|
-
*/
|
|
177
|
-
export function isCreationStatus(value: unknown): value is CreationStatusValue {
|
|
178
|
-
return (
|
|
179
|
-
typeof value === "string" &&
|
|
180
|
-
Object.values(CREATION_STATUS).includes(value as CreationStatusValue)
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Type guard for creation type values
|
|
186
|
-
* @param value - Value to check
|
|
187
|
-
* @returns True if value is a valid creation type
|
|
188
|
-
*/
|
|
189
|
-
export function isCreationType(value: unknown): value is CreationTypeValue {
|
|
190
|
-
return (
|
|
191
|
-
typeof value === "string" &&
|
|
192
|
-
Object.values(CREATION_TYPES).includes(value as CreationTypeValue)
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Type guard for updatable field names
|
|
198
|
-
* @param field - Field name to check
|
|
199
|
-
* @returns True if field is allowed to be updated
|
|
200
|
-
*/
|
|
201
|
-
export function isUpdatableField(field: string): field is CreationFieldName {
|
|
202
|
-
const UPDATABLE_FIELDS: ReadonlyArray<CreationFieldName> = [
|
|
203
|
-
CREATION_FIELDS.URI,
|
|
204
|
-
CREATION_FIELDS.STATUS,
|
|
205
|
-
CREATION_FIELDS.OUTPUT,
|
|
206
|
-
CREATION_FIELDS.IMAGE_URL,
|
|
207
|
-
CREATION_FIELDS.VIDEO_URL,
|
|
208
|
-
CREATION_FIELDS.METADATA,
|
|
209
|
-
CREATION_FIELDS.IS_SHARED,
|
|
210
|
-
CREATION_FIELDS.IS_FAVORITE,
|
|
211
|
-
CREATION_FIELDS.RATING,
|
|
212
|
-
CREATION_FIELDS.RATED_AT,
|
|
213
|
-
CREATION_FIELDS.DELETED_AT,
|
|
214
|
-
CREATION_FIELDS.REQUEST_ID,
|
|
215
|
-
CREATION_FIELDS.MODEL,
|
|
216
|
-
CREATION_FIELDS.PROMPT,
|
|
217
|
-
];
|
|
218
|
-
|
|
219
|
-
return UPDATABLE_FIELDS.includes(field as CreationFieldName);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// ============================================================================
|
|
223
|
-
// COMPOSITE OBJECTS - Grouped constants for common use cases
|
|
224
|
-
// ============================================================================
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* All creation-related constants grouped for convenience
|
|
228
|
-
* Use this for imports when you need multiple constant groups
|
|
229
|
-
*/
|
|
230
|
-
export const CREATION_CONSTANTS = {
|
|
231
|
-
STATUS: CREATION_STATUS,
|
|
232
|
-
TYPES: CREATION_TYPES,
|
|
233
|
-
FIELDS: CREATION_FIELDS,
|
|
234
|
-
VALIDATION: CREATION_VALIDATION,
|
|
235
|
-
QUERY_CONFIG: CREATION_QUERY_CONFIG,
|
|
236
|
-
ERROR_CODES: CREATION_ERROR_CODES,
|
|
237
|
-
COLLECTIONS: CREATION_COLLECTIONS,
|
|
238
|
-
} as const;
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Freeze constants to prevent accidental modification
|
|
242
|
-
* This is a runtime safeguard in addition to TypeScript's readonly
|
|
243
|
-
*/
|
|
244
|
-
Object.freeze(CREATION_STATUS);
|
|
245
|
-
Object.freeze(CREATION_TYPES);
|
|
246
|
-
Object.freeze(CREATION_FIELDS);
|
|
247
|
-
Object.freeze(CREATION_VALIDATION);
|
|
248
|
-
Object.freeze(CREATION_QUERY_CONFIG);
|
|
249
|
-
Object.freeze(CREATION_ERROR_CODES);
|
|
250
|
-
Object.freeze(CREATION_COLLECTIONS);
|
|
251
|
-
Object.freeze(CREATION_CONSTANTS);
|