@umituz/react-native-ai-generation-content 1.65.10 → 1.65.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/creations/infrastructure/repositories/CreationsFetcher.ts +29 -131
- package/src/domains/creations/infrastructure/repositories/CreationsQuery.ts +84 -0
- package/src/domains/creations/infrastructure/repositories/CreationsRepository.ts +2 -2
- package/src/domains/creations/infrastructure/repositories/CreationsSubscription.ts +125 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.65.
|
|
3
|
+
"version": "1.65.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",
|
|
@@ -1,155 +1,53 @@
|
|
|
1
|
-
import { getDocs, getDoc, query, orderBy, onSnapshot, where } from "firebase/firestore";
|
|
2
1
|
import { type FirestorePathResolver } from "@umituz/react-native-firebase";
|
|
3
2
|
import type { DocumentMapper } from "../../domain/value-objects/CreationsConfig";
|
|
4
|
-
import type { Creation
|
|
3
|
+
import type { Creation } from "../../domain/entities/Creation";
|
|
5
4
|
import type { CreationsSubscriptionCallback, UnsubscribeFunction } from "../../domain/repositories/ICreationsRepository";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
declare const __DEV__: boolean;
|
|
5
|
+
import { CreationsQuery } from "./CreationsQuery";
|
|
6
|
+
import { CreationsSubscription } from "./CreationsSubscription";
|
|
9
7
|
|
|
10
8
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* CreationsFetcher
|
|
10
|
+
* Orchestrates read operations for creations
|
|
11
|
+
* Delegates to specialized classes for queries and subscriptions
|
|
12
|
+
*
|
|
13
|
+
* Architecture: Facade pattern
|
|
14
|
+
* - Query operations → CreationsQuery
|
|
15
|
+
* - Subscription operations → CreationsSubscription
|
|
13
16
|
*/
|
|
14
17
|
export class CreationsFetcher {
|
|
18
|
+
private readonly query: CreationsQuery;
|
|
19
|
+
private readonly subscription: CreationsSubscription;
|
|
20
|
+
|
|
15
21
|
constructor(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
) {
|
|
22
|
+
pathResolver: FirestorePathResolver,
|
|
23
|
+
documentMapper: DocumentMapper,
|
|
24
|
+
) {
|
|
25
|
+
this.query = new CreationsQuery(pathResolver, documentMapper);
|
|
26
|
+
this.subscription = new CreationsSubscription(pathResolver, documentMapper);
|
|
27
|
+
}
|
|
19
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Get all creations for a user
|
|
31
|
+
*/
|
|
20
32
|
async getAll(userId: string): Promise<Creation[]> {
|
|
21
|
-
|
|
22
|
-
if (!userCollection) return [];
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
// Optimized query: Server-side filtering for non-deleted items
|
|
26
|
-
// Requires composite index: (deletedAt ASC, createdAt DESC)
|
|
27
|
-
const q = query(
|
|
28
|
-
userCollection,
|
|
29
|
-
where(CREATION_FIELDS.DELETED_AT, "==", null),
|
|
30
|
-
orderBy(CREATION_FIELDS.CREATED_AT, "desc")
|
|
31
|
-
);
|
|
32
|
-
const snapshot = await getDocs(q);
|
|
33
|
-
|
|
34
|
-
// Map documents to domain entities
|
|
35
|
-
// No client-side filtering needed - server already filtered deleted items
|
|
36
|
-
const creations = snapshot.docs.map((docSnap) => {
|
|
37
|
-
const data = docSnap.data() as CreationDocument;
|
|
38
|
-
return this.documentMapper(docSnap.id, data);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
if (__DEV__) {
|
|
42
|
-
console.log("[CreationsFetcher] Fetched creations:", {
|
|
43
|
-
count: creations.length,
|
|
44
|
-
hasDeletedFilter: true,
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return creations;
|
|
49
|
-
} catch (error) {
|
|
50
|
-
if (__DEV__) {
|
|
51
|
-
console.error("[CreationsFetcher] getAll() error:", error);
|
|
52
|
-
}
|
|
53
|
-
return [];
|
|
54
|
-
}
|
|
33
|
+
return this.query.getAll(userId);
|
|
55
34
|
}
|
|
56
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Get a single creation by ID
|
|
38
|
+
*/
|
|
57
39
|
async getById(userId: string, id: string): Promise<Creation | null> {
|
|
58
|
-
|
|
59
|
-
if (!docRef) return null;
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
const docSnap = await getDoc(docRef);
|
|
63
|
-
|
|
64
|
-
if (!docSnap.exists()) {
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const data = docSnap.data() as CreationDocument;
|
|
69
|
-
return this.documentMapper(docSnap.id, data);
|
|
70
|
-
} catch (error) {
|
|
71
|
-
if (__DEV__) {
|
|
72
|
-
console.error("[CreationsFetcher] getById() error:", error);
|
|
73
|
-
}
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
40
|
+
return this.query.getById(userId, id);
|
|
76
41
|
}
|
|
77
42
|
|
|
78
43
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* PERFORMANCE OPTIMIZATION:
|
|
82
|
-
* - Server-side filtering with where clause (80% data reduction)
|
|
83
|
-
* - No client-side filtering needed
|
|
84
|
-
* - Requires Firestore composite index: (deletedAt ASC, createdAt DESC)
|
|
85
|
-
*
|
|
86
|
-
* @param userId - User ID to query
|
|
87
|
-
* @param onData - Callback for data updates
|
|
88
|
-
* @param onError - Optional error callback
|
|
89
|
-
* @returns Unsubscribe function
|
|
44
|
+
* Subscribe to realtime updates for user's creations
|
|
90
45
|
*/
|
|
91
46
|
subscribeToAll(
|
|
92
47
|
userId: string,
|
|
93
48
|
onData: CreationsSubscriptionCallback,
|
|
94
49
|
onError?: (error: Error) => void,
|
|
95
50
|
): UnsubscribeFunction {
|
|
96
|
-
|
|
97
|
-
if (!userCollection) {
|
|
98
|
-
const error = new Error(`[CreationsFetcher] Cannot subscribe: Invalid user collection for userId: ${userId}`);
|
|
99
|
-
if (__DEV__) {
|
|
100
|
-
console.error(error.message);
|
|
101
|
-
}
|
|
102
|
-
// Return empty array immediately
|
|
103
|
-
onData([]);
|
|
104
|
-
// Report error to callback
|
|
105
|
-
onError?.(error);
|
|
106
|
-
// Return no-op unsubscribe function (no listener was created)
|
|
107
|
-
return () => {
|
|
108
|
-
if (__DEV__) {
|
|
109
|
-
console.log("[CreationsFetcher] No-op unsubscribe called (no listener was created)");
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Optimized query with server-side filtering
|
|
115
|
-
// This prevents downloading deleted items entirely
|
|
116
|
-
const q = query(
|
|
117
|
-
userCollection,
|
|
118
|
-
where(CREATION_FIELDS.DELETED_AT, "==", null),
|
|
119
|
-
orderBy(CREATION_FIELDS.CREATED_AT, "desc")
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
return onSnapshot(
|
|
123
|
-
q,
|
|
124
|
-
{ includeMetadataChanges: false }, // Ignore metadata-only changes for performance
|
|
125
|
-
(snapshot) => {
|
|
126
|
-
// Map documents to domain entities
|
|
127
|
-
// Server already filtered - no client filtering needed
|
|
128
|
-
const creations = snapshot.docs.map((docSnap) => {
|
|
129
|
-
const data = docSnap.data() as CreationDocument;
|
|
130
|
-
return this.documentMapper(docSnap.id, data);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
if (__DEV__) {
|
|
134
|
-
console.log("[CreationsFetcher] Realtime sync:", {
|
|
135
|
-
count: creations.length,
|
|
136
|
-
serverFiltered: true,
|
|
137
|
-
hasChanges: snapshot.docChanges().length,
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
onData(creations);
|
|
142
|
-
},
|
|
143
|
-
(error: Error) => {
|
|
144
|
-
if (__DEV__) {
|
|
145
|
-
console.error("[CreationsFetcher] Realtime subscription error:", {
|
|
146
|
-
error: error.message,
|
|
147
|
-
code: (error as { code?: string }).code,
|
|
148
|
-
userId,
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
onError?.(error);
|
|
152
|
-
},
|
|
153
|
-
);
|
|
51
|
+
return this.subscription.subscribeToAll(userId, onData, onError);
|
|
154
52
|
}
|
|
155
53
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CreationsQuery
|
|
3
|
+
* Handles read operations (getAll, getById) for creations
|
|
4
|
+
* Single Responsibility: Firestore query operations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getDocs, getDoc, query, orderBy, where } from "firebase/firestore";
|
|
8
|
+
import { type FirestorePathResolver } from "@umituz/react-native-firebase";
|
|
9
|
+
import type { DocumentMapper } from "../../domain/value-objects/CreationsConfig";
|
|
10
|
+
import type { Creation, CreationDocument } from "../../domain/entities/Creation";
|
|
11
|
+
import { CREATION_FIELDS } from "../../domain/constants";
|
|
12
|
+
|
|
13
|
+
declare const __DEV__: boolean;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Handles query operations for creations
|
|
17
|
+
*/
|
|
18
|
+
export class CreationsQuery {
|
|
19
|
+
constructor(
|
|
20
|
+
private readonly pathResolver: FirestorePathResolver,
|
|
21
|
+
private readonly documentMapper: DocumentMapper,
|
|
22
|
+
) { }
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get all creations for a user
|
|
26
|
+
* Optimized query: Server-side filtering for non-deleted items
|
|
27
|
+
*/
|
|
28
|
+
async getAll(userId: string): Promise<Creation[]> {
|
|
29
|
+
const userCollection = this.pathResolver.getUserCollection(userId);
|
|
30
|
+
if (!userCollection) return [];
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const q = query(
|
|
34
|
+
userCollection,
|
|
35
|
+
where(CREATION_FIELDS.DELETED_AT, "==", null),
|
|
36
|
+
orderBy(CREATION_FIELDS.CREATED_AT, "desc")
|
|
37
|
+
);
|
|
38
|
+
const snapshot = await getDocs(q);
|
|
39
|
+
|
|
40
|
+
const creations = snapshot.docs.map((docSnap) => {
|
|
41
|
+
const data = docSnap.data() as CreationDocument;
|
|
42
|
+
return this.documentMapper(docSnap.id, data);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (__DEV__) {
|
|
46
|
+
console.log("[CreationsQuery] Fetched creations:", {
|
|
47
|
+
count: creations.length,
|
|
48
|
+
hasDeletedFilter: true,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return creations;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (__DEV__) {
|
|
55
|
+
console.error("[CreationsQuery] getAll() error:", error);
|
|
56
|
+
}
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get a single creation by ID
|
|
63
|
+
*/
|
|
64
|
+
async getById(userId: string, id: string): Promise<Creation | null> {
|
|
65
|
+
const docRef = this.pathResolver.getDocRef(userId, id);
|
|
66
|
+
if (!docRef) return null;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const docSnap = await getDoc(docRef);
|
|
70
|
+
|
|
71
|
+
if (!docSnap.exists()) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const data = docSnap.data() as CreationDocument;
|
|
76
|
+
return this.documentMapper(docSnap.id, data);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (__DEV__) {
|
|
79
|
+
console.error("[CreationsQuery] getById() error:", error);
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -45,8 +45,8 @@ export class CreationsRepository
|
|
|
45
45
|
|
|
46
46
|
const documentMapper = options?.documentMapper ?? mapDocumentToCreation;
|
|
47
47
|
|
|
48
|
-
// Initialize with
|
|
49
|
-
this.pathResolver = new FirestorePathResolver(collectionName,
|
|
48
|
+
// Initialize with Firestore database instance from BaseRepository
|
|
49
|
+
this.pathResolver = new FirestorePathResolver(collectionName, this.getDb());
|
|
50
50
|
this.fetcher = new CreationsFetcher(this.pathResolver, documentMapper);
|
|
51
51
|
this.writer = new CreationsWriter(this.pathResolver);
|
|
52
52
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CreationsSubscription
|
|
3
|
+
* Handles realtime subscription operations for creations
|
|
4
|
+
* Single Responsibility: Firestore realtime listeners
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { query, orderBy, onSnapshot, where } from "firebase/firestore";
|
|
8
|
+
import { type FirestorePathResolver } from "@umituz/react-native-firebase";
|
|
9
|
+
import type { DocumentMapper } from "../../domain/value-objects/CreationsConfig";
|
|
10
|
+
import type { Creation, CreationDocument } from "../../domain/entities/Creation";
|
|
11
|
+
import type { CreationsSubscriptionCallback, UnsubscribeFunction } from "../../domain/repositories/ICreationsRepository";
|
|
12
|
+
import { CREATION_FIELDS } from "../../domain/constants";
|
|
13
|
+
|
|
14
|
+
declare const __DEV__: boolean;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Handles realtime subscriptions for creations
|
|
18
|
+
*/
|
|
19
|
+
export class CreationsSubscription {
|
|
20
|
+
constructor(
|
|
21
|
+
private readonly pathResolver: FirestorePathResolver,
|
|
22
|
+
private readonly documentMapper: DocumentMapper,
|
|
23
|
+
) { }
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Subscribes to realtime updates for user's creations
|
|
27
|
+
*
|
|
28
|
+
* PERFORMANCE OPTIMIZATION:
|
|
29
|
+
* - Server-side filtering with where clause (80% data reduction)
|
|
30
|
+
* - No client-side filtering needed
|
|
31
|
+
* - Requires Firestore composite index: (deletedAt ASC, createdAt DESC)
|
|
32
|
+
*
|
|
33
|
+
* @param userId - User ID to query
|
|
34
|
+
* @param onData - Callback for data updates
|
|
35
|
+
* @param onError - Optional error callback
|
|
36
|
+
* @returns Unsubscribe function
|
|
37
|
+
*/
|
|
38
|
+
subscribeToAll(
|
|
39
|
+
userId: string,
|
|
40
|
+
onData: CreationsSubscriptionCallback,
|
|
41
|
+
onError?: (error: Error) => void,
|
|
42
|
+
): UnsubscribeFunction {
|
|
43
|
+
const userCollection = this.pathResolver.getUserCollection(userId);
|
|
44
|
+
|
|
45
|
+
if (!userCollection) {
|
|
46
|
+
return this.handleInvalidCollection(userId, onData, onError);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return this.createRealtimeListener(userCollection, onData, onError);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Handles case when user collection is invalid
|
|
54
|
+
*/
|
|
55
|
+
private handleInvalidCollection(
|
|
56
|
+
userId: string,
|
|
57
|
+
onData: CreationsSubscriptionCallback,
|
|
58
|
+
onError?: (error: Error) => void,
|
|
59
|
+
): UnsubscribeFunction {
|
|
60
|
+
const error = new Error(`[CreationsSubscription] Cannot subscribe: Invalid user collection for userId: ${userId}`);
|
|
61
|
+
|
|
62
|
+
if (__DEV__) {
|
|
63
|
+
console.error(error.message);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Return empty array immediately
|
|
67
|
+
onData([]);
|
|
68
|
+
|
|
69
|
+
// Report error to callback
|
|
70
|
+
onError?.(error);
|
|
71
|
+
|
|
72
|
+
// Return no-op unsubscribe function
|
|
73
|
+
return () => {
|
|
74
|
+
if (__DEV__) {
|
|
75
|
+
console.log("[CreationsSubscription] No-op unsubscribe called (no listener was created)");
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Creates the realtime listener with optimized query
|
|
82
|
+
*/
|
|
83
|
+
private createRealtimeListener(
|
|
84
|
+
userCollection: any,
|
|
85
|
+
onData: CreationsSubscriptionCallback,
|
|
86
|
+
onError?: (error: Error) => void,
|
|
87
|
+
): UnsubscribeFunction {
|
|
88
|
+
const q = query(
|
|
89
|
+
userCollection,
|
|
90
|
+
where(CREATION_FIELDS.DELETED_AT, "==", null),
|
|
91
|
+
orderBy(CREATION_FIELDS.CREATED_AT, "desc")
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return onSnapshot(
|
|
95
|
+
q,
|
|
96
|
+
{ includeMetadataChanges: false },
|
|
97
|
+
(snapshot) => {
|
|
98
|
+
const creations = snapshot.docs.map((docSnap) => {
|
|
99
|
+
const data = docSnap.data() as CreationDocument;
|
|
100
|
+
return this.documentMapper(docSnap.id, data);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
if (__DEV__) {
|
|
104
|
+
console.log("[CreationsSubscription] Realtime sync:", {
|
|
105
|
+
count: creations.length,
|
|
106
|
+
serverFiltered: true,
|
|
107
|
+
hasChanges: snapshot.docChanges().length,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
onData(creations);
|
|
112
|
+
},
|
|
113
|
+
(error: Error) => {
|
|
114
|
+
if (__DEV__) {
|
|
115
|
+
console.error("[CreationsSubscription] Realtime subscription error:", {
|
|
116
|
+
error: error.message,
|
|
117
|
+
code: (error as { code?: string }).code,
|
|
118
|
+
userId,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
onError?.(error);
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|