@umituz/react-native-firebase 2.6.3 → 2.6.5
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/application/auth/index.ts +2 -34
- package/src/application/auth/use-cases/index.ts +1 -21
- package/src/domains/account-deletion/domain/index.ts +1 -8
- package/src/domains/account-deletion/index.ts +0 -42
- package/src/domains/account-deletion/infrastructure/services/AccountDeletionExecutor.ts +79 -0
- package/src/domains/account-deletion/infrastructure/services/AccountDeletionTypes.ts +0 -1
- package/src/domains/account-deletion/infrastructure/services/account-deletion.service.ts +2 -14
- package/src/domains/auth/index.ts +3 -12
- package/src/domains/auth/infrastructure/config/FirebaseAuthClient.ts +48 -60
- package/src/domains/auth/infrastructure/config/index.ts +2 -0
- package/src/domains/auth/infrastructure/config/initializers/index.ts +1 -0
- package/src/domains/auth/infrastructure/services/index.ts +16 -0
- package/src/domains/auth/infrastructure/services/utils/index.ts +1 -0
- package/src/domains/auth/infrastructure/stores/index.ts +1 -0
- package/src/domains/auth/infrastructure/utils/index.ts +1 -0
- package/src/domains/auth/infrastructure.ts +11 -0
- package/src/domains/auth/presentation/hooks/useGoogleOAuth.ts +18 -59
- package/src/domains/firestore/domain/entities/Collection.ts +0 -2
- package/src/domains/firestore/domain/index.ts +6 -2
- package/src/domains/firestore/domain/value-objects/WhereClause.ts +0 -14
- package/src/domains/firestore/index.ts +0 -1
- package/src/domains/firestore/infrastructure/config/FirestoreClient.ts +42 -60
- package/src/domains/firestore/presentation/hooks/useFirestoreMutation.ts +1 -1
- package/src/domains/firestore/presentation/hooks/useFirestoreQuery.ts +1 -1
- package/src/shared/infrastructure/base/ErrorHandler.ts +81 -0
- package/src/shared/infrastructure/base/ServiceBase.ts +62 -0
- package/src/shared/infrastructure/config/base/ServiceClientSingleton.ts +39 -0
- package/src/application/auth/ports/AuthPort.ts.bak +0 -164
- package/src/application/auth/ports/AuthPort_part_aa +0 -150
- package/src/application/auth/ports/AuthPort_part_ab +0 -14
- package/src/application/auth/use-cases/SignInUseCase.ts.bak +0 -253
- package/src/application/auth/use-cases/SignInUseCaseHelpers.ts +0 -0
- package/src/application/auth/use-cases/SignInUseCaseMain.ts +0 -0
- package/src/application/auth/use-cases/SignInUseCase_part_aa +0 -150
- package/src/application/auth/use-cases/SignInUseCase_part_ab +0 -103
- package/src/application/auth/use-cases/SignOutUseCase.ts.bak +0 -288
- package/src/application/auth/use-cases/SignOutUseCaseCleanup.ts +0 -0
- package/src/application/auth/use-cases/SignOutUseCaseMain.ts +0 -0
- package/src/application/auth/use-cases/SignOutUseCase_part_aa +0 -150
- package/src/application/auth/use-cases/SignOutUseCase_part_ab +0 -138
- package/src/domains/account-deletion/domain/services/UserValidationHelpers.ts.bak +0 -181
- package/src/domains/account-deletion/domain/services/UserValidationHelpers_part_aa +0 -150
- package/src/domains/account-deletion/domain/services/UserValidationHelpers_part_ab +0 -31
- package/src/domains/account-deletion/domain/services/UserValidationService.ts.bak +0 -286
- package/src/domains/account-deletion/domain/services/UserValidationService_part_aa +0 -150
- package/src/domains/account-deletion/domain/services/UserValidationService_part_ab +0 -136
- package/src/domains/account-deletion/infrastructure/services/AccountDeletionExecutor.ts.bak +0 -230
- package/src/domains/account-deletion/infrastructure/services/AccountDeletionExecutor_part_aa +0 -150
- package/src/domains/account-deletion/infrastructure/services/AccountDeletionExecutor_part_ab +0 -80
- package/src/domains/account-deletion/infrastructure/services/AccountDeletionReauthHandler.ts.bak +0 -174
- package/src/domains/account-deletion/infrastructure/services/AccountDeletionReauthHandler_part_aa +0 -150
- package/src/domains/account-deletion/infrastructure/services/AccountDeletionReauthHandler_part_ab +0 -24
- package/src/domains/account-deletion/infrastructure/services/AccountDeletionRepository.ts.bak +0 -266
- package/src/domains/account-deletion/infrastructure/services/AccountDeletionRepository_part_aa +0 -150
- package/src/domains/account-deletion/infrastructure/services/AccountDeletionRepository_part_ab +0 -116
- package/src/domains/account-deletion/infrastructure/services/reauthentication.service.ts.bak +0 -160
- package/src/domains/account-deletion/infrastructure/services/reauthentication.service_part_aa +0 -150
- package/src/domains/account-deletion/infrastructure/services/reauthentication.service_part_ab +0 -10
- package/src/domains/auth/infrastructure.ts.bak +0 -156
- package/src/domains/auth/infrastructure_part_aa +0 -150
- package/src/domains/auth/infrastructure_part_ab +0 -6
- package/src/domains/auth/presentation/hooks/GoogleOAuthHelpers.ts +0 -0
- package/src/domains/auth/presentation/hooks/GoogleOAuthHookService.ts.bak +0 -247
- package/src/domains/auth/presentation/hooks/GoogleOAuthHookService_part_aa +0 -150
- package/src/domains/auth/presentation/hooks/GoogleOAuthHookService_part_ab +0 -97
- package/src/domains/auth/presentation/hooks/GoogleOAuthService.ts +0 -0
- package/src/domains/firestore/domain/entities/Collection.ts.bak +0 -288
- package/src/domains/firestore/domain/entities/Collection_part_aa +0 -150
- package/src/domains/firestore/domain/entities/Collection_part_ab +0 -138
- package/src/domains/firestore/domain/entities/Document.ts.bak +0 -233
- package/src/domains/firestore/domain/entities/DocumentHelpers.ts +0 -0
- package/src/domains/firestore/domain/entities/DocumentMain.ts +0 -0
- package/src/domains/firestore/domain/entities/Document_part_aa +0 -150
- package/src/domains/firestore/domain/entities/Document_part_ab +0 -83
- package/src/domains/firestore/domain/services/QueryService.ts.bak +0 -182
- package/src/domains/firestore/domain/services/QueryServiceAnalysis.ts.bak +0 -169
- package/src/domains/firestore/domain/services/QueryServiceAnalysis_part_aa +0 -150
- package/src/domains/firestore/domain/services/QueryServiceAnalysis_part_ab +0 -19
- package/src/domains/firestore/domain/services/QueryServiceHelpers.ts.bak +0 -151
- package/src/domains/firestore/domain/services/QueryServiceHelpers_part_aa +0 -150
- package/src/domains/firestore/domain/services/QueryServiceHelpers_part_ab +0 -1
- package/src/domains/firestore/domain/services/QueryService_part_aa +0 -150
- package/src/domains/firestore/domain/services/QueryService_part_ab +0 -32
- package/src/domains/firestore/domain/value-objects/QueryOptions.ts.bak +0 -191
- package/src/domains/firestore/domain/value-objects/QueryOptionsSerialization.ts.bak +0 -207
- package/src/domains/firestore/domain/value-objects/QueryOptionsSerialization_part_aa +0 -150
- package/src/domains/firestore/domain/value-objects/QueryOptionsSerialization_part_ab +0 -57
- package/src/domains/firestore/domain/value-objects/QueryOptionsValidation.ts.bak +0 -182
- package/src/domains/firestore/domain/value-objects/QueryOptionsValidation_part_aa +0 -150
- package/src/domains/firestore/domain/value-objects/QueryOptionsValidation_part_ab +0 -32
- package/src/domains/firestore/domain/value-objects/QueryOptions_part_aa +0 -150
- package/src/domains/firestore/domain/value-objects/QueryOptions_part_ab +0 -41
- package/src/domains/firestore/domain/value-objects/WhereClause.ts.bak +0 -299
- package/src/domains/firestore/domain/value-objects/WhereClauseFactory.ts.bak +0 -207
- package/src/domains/firestore/domain/value-objects/WhereClauseFactory_part_aa +0 -150
- package/src/domains/firestore/domain/value-objects/WhereClauseFactory_part_ab +0 -57
- package/src/domains/firestore/domain/value-objects/WhereClause_part_aa +0 -150
- package/src/domains/firestore/domain/value-objects/WhereClause_part_ab +0 -149
- package/src/shared/infrastructure/base/ErrorHandler.ts.bak +0 -189
- package/src/shared/infrastructure/base/ErrorHandler_part_aa +0 -150
- package/src/shared/infrastructure/base/ErrorHandler_part_ab +0 -39
- package/src/shared/infrastructure/base/ServiceBase.ts.bak +0 -220
- package/src/shared/infrastructure/base/ServiceBase_part_aa +0 -150
- package/src/shared/infrastructure/base/ServiceBase_part_ab +0 -70
- package/src/shared/infrastructure/config/base/ServiceClientSingleton.ts.bak +0 -155
- package/src/shared/infrastructure/config/base/ServiceClientSingleton_part_aa +0 -150
- package/src/shared/infrastructure/config/base/ServiceClientSingleton_part_ab +0 -5
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Document Entity
|
|
3
|
-
* Single Responsibility: Represent a Firestore document with metadata
|
|
4
|
-
*
|
|
5
|
-
* Domain entity that encapsulates document data and metadata.
|
|
6
|
-
* Provides business logic for document operations.
|
|
7
|
-
*
|
|
8
|
-
* Max lines: 150 (enforced for maintainability)
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import type { DocumentData, DocumentSnapshot, Timestamp } from 'firebase/firestore';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Document metadata
|
|
15
|
-
*/
|
|
16
|
-
export interface DocumentMetadata {
|
|
17
|
-
readonly id: string;
|
|
18
|
-
readonly createdAt: Timestamp | null;
|
|
19
|
-
readonly updatedAt: Timestamp | null;
|
|
20
|
-
readonly exists: boolean;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Document entity
|
|
25
|
-
* Represents a Firestore document with data and metadata
|
|
26
|
-
*/
|
|
27
|
-
export class Document<T extends DocumentData = DocumentData> {
|
|
28
|
-
readonly id: string;
|
|
29
|
-
readonly data: T | null;
|
|
30
|
-
readonly metadata: DocumentMetadata;
|
|
31
|
-
readonly path: string;
|
|
32
|
-
|
|
33
|
-
constructor(snapshot: DocumentSnapshot<T>, path: string) {
|
|
34
|
-
this.id = snapshot.id;
|
|
35
|
-
this.data = snapshot.data() || null;
|
|
36
|
-
this.path = path;
|
|
37
|
-
this.metadata = {
|
|
38
|
-
id: snapshot.id,
|
|
39
|
-
createdAt: this.extractTimestamp(snapshot, 'createdAt'),
|
|
40
|
-
updatedAt: this.extractTimestamp(snapshot, 'updatedAt'),
|
|
41
|
-
exists: snapshot.exists(),
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Create document from snapshot with custom path
|
|
47
|
-
*/
|
|
48
|
-
static fromSnapshot<T extends DocumentData>(
|
|
49
|
-
snapshot: DocumentSnapshot<T>,
|
|
50
|
-
path: string
|
|
51
|
-
): Document<T> {
|
|
52
|
-
return new Document(snapshot, path);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Check if document exists
|
|
57
|
-
*/
|
|
58
|
-
exists(): boolean {
|
|
59
|
-
return this.metadata.exists && this.data !== null;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Get field value from document data
|
|
64
|
-
*/
|
|
65
|
-
getField<K extends keyof T>(field: K): T[K] | null {
|
|
66
|
-
return this.data?.[field] ?? null;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Check if document has a specific field
|
|
71
|
-
*/
|
|
72
|
-
hasField<K extends keyof T>(field: K): boolean {
|
|
73
|
-
return this.data !== null && field in this.data;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Get document age in milliseconds
|
|
78
|
-
* Returns null if createdAt is not set
|
|
79
|
-
*/
|
|
80
|
-
getAge(): number | null {
|
|
81
|
-
if (!this.metadata.createdAt) return null;
|
|
82
|
-
return Date.now() - this.metadata.createdAt.toMillis();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Check if document is recent (created within specified milliseconds)
|
|
87
|
-
*/
|
|
88
|
-
isRecent(maxAgeMs: number): boolean {
|
|
89
|
-
const age = this.getAge();
|
|
90
|
-
return age !== null && age <= maxAgeMs;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Check if document was updated after creation (modified)
|
|
95
|
-
*/
|
|
96
|
-
isModified(): boolean {
|
|
97
|
-
if (!this.metadata.createdAt || !this.metadata.updatedAt) return false;
|
|
98
|
-
return this.metadata.updatedAt.toMillis() > this.metadata.createdAt.toMillis();
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Get time since last update in milliseconds
|
|
103
|
-
* Returns null if updatedAt is not set
|
|
104
|
-
*/
|
|
105
|
-
getTimeSinceUpdate(): number | null {
|
|
106
|
-
if (!this.metadata.updatedAt) return null;
|
|
107
|
-
return Date.now() - this.metadata.updatedAt.toMillis();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Check if document is stale (not updated within specified milliseconds)
|
|
112
|
-
*/
|
|
113
|
-
isStale(maxStaleMs: number): boolean {
|
|
114
|
-
const timeSinceUpdate = this.getTimeSinceUpdate();
|
|
115
|
-
return timeSinceUpdate !== null && timeSinceUpdate > maxStaleMs;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Convert to plain object (for serialization)
|
|
120
|
-
*/
|
|
121
|
-
toObject(): { id: string; data: T | null; metadata: DocumentMetadata } {
|
|
122
|
-
return {
|
|
123
|
-
id: this.id,
|
|
124
|
-
data: this.data,
|
|
125
|
-
metadata: this.metadata,
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Extract timestamp field from document snapshot
|
|
131
|
-
*/
|
|
132
|
-
private extractTimestamp(
|
|
133
|
-
snapshot: DocumentSnapshot<T>,
|
|
134
|
-
field: string
|
|
135
|
-
): Timestamp | null {
|
|
136
|
-
const data = snapshot.data();
|
|
137
|
-
if (!data) return null;
|
|
138
|
-
|
|
139
|
-
const value = data[field as keyof T];
|
|
140
|
-
if (value && typeof value === 'object' && 'toDate' in value && typeof value.toDate === 'function') {
|
|
141
|
-
return value as unknown as Timestamp;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Create a new document with updated data
|
|
149
|
-
* Useful for immutability patterns
|
|
150
|
-
*/
|
|
151
|
-
withData<K extends keyof T>(field: K, value: T[K]): Document<T> {
|
|
152
|
-
if (!this.data) {
|
|
153
|
-
throw new Error('Cannot update null document data');
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const newData = { ...this.data, [field]: value };
|
|
157
|
-
return new Document(
|
|
158
|
-
{
|
|
159
|
-
id: this.id,
|
|
160
|
-
exists: true,
|
|
161
|
-
data() {
|
|
162
|
-
return newData;
|
|
163
|
-
},
|
|
164
|
-
} as unknown as DocumentSnapshot<T>,
|
|
165
|
-
this.path
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Validate document data against required fields
|
|
171
|
-
*/
|
|
172
|
-
validateRequiredFields(requiredFields: (keyof T)[]): { valid: boolean; missing: (keyof T)[] } {
|
|
173
|
-
if (!this.data) {
|
|
174
|
-
return { valid: false, missing: requiredFields };
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const missing = requiredFields.filter(field => !this.hasField(field));
|
|
178
|
-
return {
|
|
179
|
-
valid: missing.length === 0,
|
|
180
|
-
missing,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Check if document matches filter criteria
|
|
186
|
-
*/
|
|
187
|
-
matches(filter: Partial<T>): boolean {
|
|
188
|
-
if (!this.data) return false;
|
|
189
|
-
|
|
190
|
-
return Object.entries(filter).every(([key, value]) => {
|
|
191
|
-
const fieldValue = this.data![key as keyof T];
|
|
192
|
-
return fieldValue === value;
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Check if document matches filter criteria
|
|
198
|
-
*/
|
|
199
|
-
matches(filter: Partial<T>): boolean {
|
|
200
|
-
if (!this.data) return false;
|
|
201
|
-
|
|
202
|
-
return Object.entries(filter).every(([key, value]) => {
|
|
203
|
-
return this.data[key as keyof T] === value;
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Get document size in bytes (approximate)
|
|
209
|
-
* Useful for quota management
|
|
210
|
-
*/
|
|
211
|
-
getSize(): number {
|
|
212
|
-
if (!this.data) return 0;
|
|
213
|
-
|
|
214
|
-
return JSON.stringify(this.data).length;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Check if document size exceeds limit
|
|
219
|
-
*/
|
|
220
|
-
exceedsSizeLimit(maxBytes: number): boolean {
|
|
221
|
-
return this.getSize() > maxBytes;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Factory function to create document entity
|
|
227
|
-
*/
|
|
228
|
-
export function createDocument<T extends DocumentData>(
|
|
229
|
-
snapshot: DocumentSnapshot<T>,
|
|
230
|
-
path: string
|
|
231
|
-
): Document<T> {
|
|
232
|
-
return Document.fromSnapshot(snapshot, path);
|
|
233
|
-
}
|
|
File without changes
|
|
File without changes
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Document Entity
|
|
3
|
-
* Single Responsibility: Represent a Firestore document with metadata
|
|
4
|
-
*
|
|
5
|
-
* Domain entity that encapsulates document data and metadata.
|
|
6
|
-
* Provides business logic for document operations.
|
|
7
|
-
*
|
|
8
|
-
* Max lines: 150 (enforced for maintainability)
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import type { DocumentData, DocumentSnapshot, Timestamp } from 'firebase/firestore';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Document metadata
|
|
15
|
-
*/
|
|
16
|
-
export interface DocumentMetadata {
|
|
17
|
-
readonly id: string;
|
|
18
|
-
readonly createdAt: Timestamp | null;
|
|
19
|
-
readonly updatedAt: Timestamp | null;
|
|
20
|
-
readonly exists: boolean;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Document entity
|
|
25
|
-
* Represents a Firestore document with data and metadata
|
|
26
|
-
*/
|
|
27
|
-
export class Document<T extends DocumentData = DocumentData> {
|
|
28
|
-
readonly id: string;
|
|
29
|
-
readonly data: T | null;
|
|
30
|
-
readonly metadata: DocumentMetadata;
|
|
31
|
-
readonly path: string;
|
|
32
|
-
|
|
33
|
-
constructor(snapshot: DocumentSnapshot<T>, path: string) {
|
|
34
|
-
this.id = snapshot.id;
|
|
35
|
-
this.data = snapshot.data() || null;
|
|
36
|
-
this.path = path;
|
|
37
|
-
this.metadata = {
|
|
38
|
-
id: snapshot.id,
|
|
39
|
-
createdAt: this.extractTimestamp(snapshot, 'createdAt'),
|
|
40
|
-
updatedAt: this.extractTimestamp(snapshot, 'updatedAt'),
|
|
41
|
-
exists: snapshot.exists(),
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Create document from snapshot with custom path
|
|
47
|
-
*/
|
|
48
|
-
static fromSnapshot<T extends DocumentData>(
|
|
49
|
-
snapshot: DocumentSnapshot<T>,
|
|
50
|
-
path: string
|
|
51
|
-
): Document<T> {
|
|
52
|
-
return new Document(snapshot, path);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Check if document exists
|
|
57
|
-
*/
|
|
58
|
-
exists(): boolean {
|
|
59
|
-
return this.metadata.exists && this.data !== null;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Get field value from document data
|
|
64
|
-
*/
|
|
65
|
-
getField<K extends keyof T>(field: K): T[K] | null {
|
|
66
|
-
return this.data?.[field] ?? null;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Check if document has a specific field
|
|
71
|
-
*/
|
|
72
|
-
hasField<K extends keyof T>(field: K): boolean {
|
|
73
|
-
return this.data !== null && field in this.data;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Get document age in milliseconds
|
|
78
|
-
* Returns null if createdAt is not set
|
|
79
|
-
*/
|
|
80
|
-
getAge(): number | null {
|
|
81
|
-
if (!this.metadata.createdAt) return null;
|
|
82
|
-
return Date.now() - this.metadata.createdAt.toMillis();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Check if document is recent (created within specified milliseconds)
|
|
87
|
-
*/
|
|
88
|
-
isRecent(maxAgeMs: number): boolean {
|
|
89
|
-
const age = this.getAge();
|
|
90
|
-
return age !== null && age <= maxAgeMs;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Check if document was updated after creation (modified)
|
|
95
|
-
*/
|
|
96
|
-
isModified(): boolean {
|
|
97
|
-
if (!this.metadata.createdAt || !this.metadata.updatedAt) return false;
|
|
98
|
-
return this.metadata.updatedAt.toMillis() > this.metadata.createdAt.toMillis();
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Get time since last update in milliseconds
|
|
103
|
-
* Returns null if updatedAt is not set
|
|
104
|
-
*/
|
|
105
|
-
getTimeSinceUpdate(): number | null {
|
|
106
|
-
if (!this.metadata.updatedAt) return null;
|
|
107
|
-
return Date.now() - this.metadata.updatedAt.toMillis();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Check if document is stale (not updated within specified milliseconds)
|
|
112
|
-
*/
|
|
113
|
-
isStale(maxStaleMs: number): boolean {
|
|
114
|
-
const timeSinceUpdate = this.getTimeSinceUpdate();
|
|
115
|
-
return timeSinceUpdate !== null && timeSinceUpdate > maxStaleMs;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Convert to plain object (for serialization)
|
|
120
|
-
*/
|
|
121
|
-
toObject(): { id: string; data: T | null; metadata: DocumentMetadata } {
|
|
122
|
-
return {
|
|
123
|
-
id: this.id,
|
|
124
|
-
data: this.data,
|
|
125
|
-
metadata: this.metadata,
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Extract timestamp field from document snapshot
|
|
131
|
-
*/
|
|
132
|
-
private extractTimestamp(
|
|
133
|
-
snapshot: DocumentSnapshot<T>,
|
|
134
|
-
field: string
|
|
135
|
-
): Timestamp | null {
|
|
136
|
-
const data = snapshot.data();
|
|
137
|
-
if (!data) return null;
|
|
138
|
-
|
|
139
|
-
const value = data[field as keyof T];
|
|
140
|
-
if (value && typeof value === 'object' && 'toDate' in value && typeof value.toDate === 'function') {
|
|
141
|
-
return value as unknown as Timestamp;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Create a new document with updated data
|
|
149
|
-
* Useful for immutability patterns
|
|
150
|
-
*/
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
withData<K extends keyof T>(field: K, value: T[K]): Document<T> {
|
|
2
|
-
if (!this.data) {
|
|
3
|
-
throw new Error('Cannot update null document data');
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
const newData = { ...this.data, [field]: value };
|
|
7
|
-
return new Document(
|
|
8
|
-
{
|
|
9
|
-
id: this.id,
|
|
10
|
-
exists: true,
|
|
11
|
-
data() {
|
|
12
|
-
return newData;
|
|
13
|
-
},
|
|
14
|
-
} as unknown as DocumentSnapshot<T>,
|
|
15
|
-
this.path
|
|
16
|
-
);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Validate document data against required fields
|
|
21
|
-
*/
|
|
22
|
-
validateRequiredFields(requiredFields: (keyof T)[]): { valid: boolean; missing: (keyof T)[] } {
|
|
23
|
-
if (!this.data) {
|
|
24
|
-
return { valid: false, missing: requiredFields };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const missing = requiredFields.filter(field => !this.hasField(field));
|
|
28
|
-
return {
|
|
29
|
-
valid: missing.length === 0,
|
|
30
|
-
missing,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Check if document matches filter criteria
|
|
36
|
-
*/
|
|
37
|
-
matches(filter: Partial<T>): boolean {
|
|
38
|
-
if (!this.data) return false;
|
|
39
|
-
|
|
40
|
-
return Object.entries(filter).every(([key, value]) => {
|
|
41
|
-
const fieldValue = this.data![key as keyof T];
|
|
42
|
-
return fieldValue === value;
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Check if document matches filter criteria
|
|
48
|
-
*/
|
|
49
|
-
matches(filter: Partial<T>): boolean {
|
|
50
|
-
if (!this.data) return false;
|
|
51
|
-
|
|
52
|
-
return Object.entries(filter).every(([key, value]) => {
|
|
53
|
-
return this.data[key as keyof T] === value;
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Get document size in bytes (approximate)
|
|
59
|
-
* Useful for quota management
|
|
60
|
-
*/
|
|
61
|
-
getSize(): number {
|
|
62
|
-
if (!this.data) return 0;
|
|
63
|
-
|
|
64
|
-
return JSON.stringify(this.data).length;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Check if document size exceeds limit
|
|
69
|
-
*/
|
|
70
|
-
exceedsSizeLimit(maxBytes: number): boolean {
|
|
71
|
-
return this.getSize() > maxBytes;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Factory function to create document entity
|
|
77
|
-
*/
|
|
78
|
-
export function createDocument<T extends DocumentData>(
|
|
79
|
-
snapshot: DocumentSnapshot<T>,
|
|
80
|
-
path: string
|
|
81
|
-
): Document<T> {
|
|
82
|
-
return Document.fromSnapshot(snapshot, path);
|
|
83
|
-
}
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Query Service (Main Builder)
|
|
3
|
-
* Single Responsibility: Build and validate Firestore queries
|
|
4
|
-
*
|
|
5
|
-
* Domain service that encapsulates query building logic.
|
|
6
|
-
* Moves business logic from infrastructure to domain layer.
|
|
7
|
-
*
|
|
8
|
-
* Max lines: 150 (enforced for maintainability)
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import type { Query, Firestore, WhereFilterOp } from 'firebase/firestore';
|
|
12
|
-
import { collection, query, where, orderBy, limit, startAfter, startAt } from 'firebase/firestore';
|
|
13
|
-
import { QueryOptions, createQueryOptions } from '../value-objects/QueryOptions';
|
|
14
|
-
import { WhereClause } from '../value-objects/WhereClause';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Query builder result
|
|
18
|
-
*/
|
|
19
|
-
export interface QueryBuilderResult {
|
|
20
|
-
readonly query: Query;
|
|
21
|
-
readonly options: QueryOptions;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Query service
|
|
26
|
-
* Provides query building and validation functionality
|
|
27
|
-
*/
|
|
28
|
-
export class QueryService {
|
|
29
|
-
private readonly db: Firestore;
|
|
30
|
-
|
|
31
|
-
constructor(db: Firestore) {
|
|
32
|
-
this.db = db;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Build a query from options
|
|
37
|
-
* Main method for query construction
|
|
38
|
-
*/
|
|
39
|
-
buildQuery(collectionPath: string, options: QueryOptions): QueryBuilderResult {
|
|
40
|
-
const validation = options.validate();
|
|
41
|
-
if (!validation.valid) {
|
|
42
|
-
throw new Error(`Invalid query options: ${validation.errors.join(', ')}`);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const collectionRef = collection(this.db, collectionPath);
|
|
46
|
-
let q: Query = collectionRef;
|
|
47
|
-
|
|
48
|
-
// Apply where clauses
|
|
49
|
-
for (const clause of options.whereClauses) {
|
|
50
|
-
q = query(q, where(clause.field, clause.operator, clause.value));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Apply sort options
|
|
54
|
-
for (const sort of options.sortOptions) {
|
|
55
|
-
q = query(q, orderBy(sort.field, sort.direction));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Apply date range
|
|
59
|
-
if (options.dateRange) {
|
|
60
|
-
const { field, startDate, endDate } = options.dateRange;
|
|
61
|
-
if (startDate) {
|
|
62
|
-
q = query(q, where(field, '>=', startDate));
|
|
63
|
-
}
|
|
64
|
-
if (endDate) {
|
|
65
|
-
q = query(q, where(field, '<=', endDate));
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Apply pagination
|
|
70
|
-
if (options.pagination) {
|
|
71
|
-
const { cursor, limit: limitValue, startAfter: startAfterValue, startAt: startAtValue } = options.pagination;
|
|
72
|
-
|
|
73
|
-
if (startAfterValue !== undefined) {
|
|
74
|
-
q = query(q, startAfter(startAfterValue));
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (startAtValue !== undefined) {
|
|
78
|
-
q = query(q, startAt(startAtValue));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (limitValue !== undefined) {
|
|
82
|
-
q = query(q, limit(limitValue));
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return { query: q, options };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Build simple query with single where clause
|
|
91
|
-
*/
|
|
92
|
-
buildSimpleQuery(
|
|
93
|
-
collectionPath: string,
|
|
94
|
-
field: string,
|
|
95
|
-
operator: WhereFilterOp,
|
|
96
|
-
value: unknown
|
|
97
|
-
): Query {
|
|
98
|
-
const options = createQueryOptions({
|
|
99
|
-
where: [WhereClause.create(field, operator, value)],
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
return this.buildQuery(collectionPath, options).query;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Build query with equality filter
|
|
107
|
-
*/
|
|
108
|
-
buildEqualsQuery(collectionPath: string, field: string, value: unknown): Query {
|
|
109
|
-
return this.buildSimpleQuery(collectionPath, field, '==', value);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Build query with multiple equality filters
|
|
114
|
-
*/
|
|
115
|
-
buildMultiEqualsQuery(
|
|
116
|
-
collectionPath: string,
|
|
117
|
-
filters: Record<string, unknown>
|
|
118
|
-
): Query {
|
|
119
|
-
const whereClauses = Object.entries(filters).map(([field, value]) =>
|
|
120
|
-
WhereClause.equals(field, value)
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
const options = createQueryOptions({ where: whereClauses });
|
|
124
|
-
return this.buildQuery(collectionPath, options).query;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Build query with sorting
|
|
129
|
-
*/
|
|
130
|
-
buildSortedQuery(
|
|
131
|
-
collectionPath: string,
|
|
132
|
-
sortField: string,
|
|
133
|
-
direction: 'asc' | 'desc' = 'asc'
|
|
134
|
-
): Query {
|
|
135
|
-
const options = createQueryOptions({
|
|
136
|
-
sort: [{ field: sortField, direction }],
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
return this.buildQuery(collectionPath, options).query;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Build query with limit
|
|
144
|
-
*/
|
|
145
|
-
buildLimitedQuery(collectionPath: string, limitValue: number): Query {
|
|
146
|
-
const options = createQueryOptions({
|
|
147
|
-
pagination: { limit: limitValue },
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
return this.buildQuery(collectionPath, options).query;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Build query with date range
|
|
155
|
-
*/
|
|
156
|
-
buildDateRangeQuery(
|
|
157
|
-
collectionPath: string,
|
|
158
|
-
field: string,
|
|
159
|
-
startDate?: Date,
|
|
160
|
-
endDate?: Date
|
|
161
|
-
): Query {
|
|
162
|
-
const options = createQueryOptions({
|
|
163
|
-
dateRange: { field, startDate, endDate },
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
return this.buildQuery(collectionPath, options).query;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Validate query options
|
|
171
|
-
*/
|
|
172
|
-
validateOptions(options: QueryOptions): { valid: boolean; errors: string[] } {
|
|
173
|
-
return options.validate();
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Check if query is empty (no filters)
|
|
178
|
-
*/
|
|
179
|
-
isEmptyQuery(options: QueryOptions): boolean {
|
|
180
|
-
return options.isEmpty();
|
|
181
|
-
}
|
|
182
|
-
}
|