@umituz/react-native-firebase 1.13.121 → 1.13.122

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.
Files changed (35) hide show
  1. package/package.json +1 -1
  2. package/src/auth/infrastructure/services/account-deletion.service.ts +4 -4
  3. package/src/auth/infrastructure/services/auth-utils.service.ts +2 -1
  4. package/src/auth/infrastructure/services/reauthentication.service.ts +15 -28
  5. package/src/domain/utils/error-handler.util.ts +90 -0
  6. package/src/domain/utils/id-generator.util.ts +50 -0
  7. package/src/domain/utils/validation.util.ts +133 -0
  8. package/src/firestore/infrastructure/middleware/QueryDeduplicationMiddleware.ts +26 -117
  9. package/src/firestore/infrastructure/services/RequestLoggerService.ts +2 -8
  10. package/src/firestore/utils/deduplication/index.ts +13 -0
  11. package/src/firestore/utils/deduplication/pending-query-manager.util.ts +93 -0
  12. package/src/firestore/utils/deduplication/query-key-generator.util.ts +41 -0
  13. package/src/firestore/utils/deduplication/timer-manager.util.ts +59 -0
  14. package/src/firestore/utils/document-mapper.helper.ts +45 -37
  15. package/src/firestore/utils/firestore-helper.ts +21 -85
  16. package/src/firestore/utils/mapper/base-mapper.util.ts +42 -0
  17. package/src/firestore/utils/mapper/enrichment-mapper.util.ts +82 -0
  18. package/src/firestore/utils/mapper/index.ts +8 -0
  19. package/src/firestore/utils/mapper/multi-enrichment-mapper.util.ts +39 -0
  20. package/src/firestore/utils/operation/operation-executor.util.ts +49 -0
  21. package/src/firestore/utils/query/filters.util.ts +75 -0
  22. package/src/firestore/utils/query/index.ts +10 -0
  23. package/src/firestore/utils/query/modifiers.util.ts +65 -0
  24. package/src/firestore/utils/query-builder.ts +7 -108
  25. package/src/firestore/utils/result/result.util.ts +45 -0
  26. package/src/firestore/utils/transaction/transaction.util.ts +45 -0
  27. package/src/index.ts +0 -1
  28. package/src/infrastructure/config/FirebaseConfigLoader.ts +9 -13
  29. package/src/storage/deleter/README.md +0 -370
  30. package/src/storage/deleter.ts +0 -109
  31. package/src/storage/index.ts +0 -8
  32. package/src/storage/storage-instance.ts +0 -11
  33. package/src/storage/types/README.md +0 -313
  34. package/src/storage/types.ts +0 -19
  35. package/src/storage/uploader.ts +0 -106
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-firebase",
3
- "version": "1.13.121",
3
+ "version": "1.13.122",
4
4
  "description": "Unified Firebase package for React Native apps - Auth and Firestore services using Firebase JS SDK (no native modules).",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -9,8 +9,8 @@ import {
9
9
  reauthenticateWithApple,
10
10
  reauthenticateWithPassword,
11
11
  reauthenticateWithGoogle,
12
- toAuthError,
13
12
  } from "./reauthentication.service";
13
+ import { toAuthErrorInfo } from "../../../domain/utils/error-handler.util";
14
14
  import type { AccountDeletionResult, AccountDeletionOptions } from "./reauthentication.types";
15
15
 
16
16
  export type { AccountDeletionResult, AccountDeletionOptions } from "./reauthentication.types";
@@ -34,7 +34,7 @@ export async function deleteCurrentUser(
34
34
  await deleteUser(user);
35
35
  return { success: true };
36
36
  } catch (error: unknown) {
37
- const authErr = toAuthError(error);
37
+ const authErr = toAuthErrorInfo(error);
38
38
  if (authErr.code === "auth/requires-recent-login" && (options.autoReauthenticate || options.password || options.googleIdToken)) {
39
39
  const reauth = await attemptReauth(user, options);
40
40
  if (reauth) return reauth;
@@ -64,7 +64,7 @@ async function attemptReauth(user: User, options: AccountDeletionOptions): Promi
64
64
  await deleteUser(user);
65
65
  return { success: true };
66
66
  } catch (err: unknown) {
67
- const authErr = toAuthError(err);
67
+ const authErr = toAuthErrorInfo(err);
68
68
  return { success: false, error: { ...authErr, requiresReauth: false } };
69
69
  }
70
70
  }
@@ -77,7 +77,7 @@ export async function deleteUserAccount(user: User | null): Promise<AccountDelet
77
77
  await deleteUser(user);
78
78
  return { success: true };
79
79
  } catch (error: unknown) {
80
- const authErr = toAuthError(error);
80
+ const authErr = toAuthErrorInfo(error);
81
81
  return { success: false, error: { ...authErr, requiresReauth: authErr.code === "auth/requires-recent-login" } };
82
82
  }
83
83
  }
@@ -5,6 +5,7 @@
5
5
 
6
6
  import type { User, Auth } from 'firebase/auth';
7
7
  import { getFirebaseAuth } from '../config/FirebaseAuthClient';
8
+ import { isValidString } from '../../../domain/utils/validation.util';
8
9
 
9
10
  export interface AuthCheckResult {
10
11
  isAuthenticated: boolean;
@@ -120,7 +121,7 @@ export function isValidUser(user: unknown): user is User {
120
121
  typeof user === 'object' &&
121
122
  user !== null &&
122
123
  'uid' in user &&
123
- typeof user.uid === 'string'
124
+ isValidString(user.uid)
124
125
  );
125
126
  }
126
127
 
@@ -13,32 +13,19 @@ import {
13
13
  import * as AppleAuthentication from "expo-apple-authentication";
14
14
  import { Platform } from "react-native";
15
15
  import { generateNonce, hashNonce } from "./crypto.util";
16
- import type {
17
- ReauthenticationResult,
18
- AuthProviderType,
19
- ReauthCredentialResult
16
+ import { toAuthErrorInfo, isCancelledError } from "../../../domain/utils/error-handler.util";
17
+ import type {
18
+ ReauthenticationResult,
19
+ AuthProviderType,
20
+ ReauthCredentialResult
20
21
  } from "./reauthentication.types";
21
22
 
22
- export type {
23
- ReauthenticationResult,
24
- AuthProviderType,
25
- ReauthCredentialResult
23
+ export type {
24
+ ReauthenticationResult,
25
+ AuthProviderType,
26
+ ReauthCredentialResult
26
27
  } from "./reauthentication.types";
27
28
 
28
- export function toAuthError(error: unknown): { code: string; message: string } {
29
- if (error instanceof Error) {
30
- const firebaseErr = error as { code?: string };
31
- return {
32
- code: firebaseErr.code || "auth/failed",
33
- message: error.message,
34
- };
35
- }
36
- return {
37
- code: "auth/failed",
38
- message: typeof error === 'string' ? error : "Unknown error",
39
- };
40
- }
41
-
42
29
  export function getUserAuthProvider(user: User): AuthProviderType {
43
30
  if (user.isAnonymous) return "anonymous";
44
31
  const data = user.providerData;
@@ -54,7 +41,7 @@ export async function reauthenticateWithGoogle(user: User, idToken: string): Pro
54
41
  await reauthenticateWithCredential(user, GoogleAuthProvider.credential(idToken));
55
42
  return { success: true };
56
43
  } catch (error: unknown) {
57
- const err = toAuthError(error);
44
+ const err = toAuthErrorInfo(error);
58
45
  return { success: false, error: err };
59
46
  }
60
47
  }
@@ -65,7 +52,7 @@ export async function reauthenticateWithPassword(user: User, pass: string): Prom
65
52
  await reauthenticateWithCredential(user, EmailAuthProvider.credential(user.email, pass));
66
53
  return { success: true };
67
54
  } catch (error: unknown) {
68
- const err = toAuthError(error);
55
+ const err = toAuthErrorInfo(error);
69
56
  return { success: false, error: err };
70
57
  }
71
58
  }
@@ -73,7 +60,7 @@ export async function reauthenticateWithPassword(user: User, pass: string): Prom
73
60
  export async function getAppleReauthCredential(): Promise<ReauthCredentialResult> {
74
61
  if (Platform.OS !== "ios") return { success: false, error: { code: "auth/ios-only", message: "iOS only" } };
75
62
  try {
76
- if (!(await AppleAuthentication.isAvailableAsync()))
63
+ if (!(await AppleAuthentication.isAvailableAsync()))
77
64
  return { success: false, error: { code: "auth/unavailable", message: "Unavailable" } };
78
65
 
79
66
  const nonce = await generateNonce();
@@ -86,8 +73,8 @@ export async function getAppleReauthCredential(): Promise<ReauthCredentialResult
86
73
  if (!apple.identityToken) return { success: false, error: { code: "auth/no-token", message: "No token" } };
87
74
  return { success: true, credential: new OAuthProvider("apple.com").credential({ idToken: apple.identityToken, rawNonce: nonce }) };
88
75
  } catch (error: unknown) {
89
- const err = toAuthError(error);
90
- const code = err.message.includes("ERR_CANCELED") ? "auth/cancelled" : err.code;
76
+ const err = toAuthErrorInfo(error);
77
+ const code = isCancelledError(err) ? "auth/cancelled" : err.code;
91
78
  return { success: false, error: { code, message: err.message } };
92
79
  }
93
80
  }
@@ -99,7 +86,7 @@ export async function reauthenticateWithApple(user: User): Promise<Reauthenticat
99
86
  await reauthenticateWithCredential(user, res.credential);
100
87
  return { success: true };
101
88
  } catch (error: unknown) {
102
- const err = toAuthError(error);
89
+ const err = toAuthErrorInfo(error);
103
90
  return { success: false, error: err };
104
91
  }
105
92
  }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Error Handler Utility
3
+ * Centralized error handling utilities for Firebase operations
4
+ */
5
+
6
+ /**
7
+ * Standard error structure with code and message
8
+ */
9
+ export interface ErrorInfo {
10
+ code: string;
11
+ message: string;
12
+ }
13
+
14
+ /**
15
+ * Convert unknown error to standard error info
16
+ * Handles Error objects, strings, and unknown types
17
+ */
18
+ export function toErrorInfo(error: unknown): ErrorInfo {
19
+ if (error instanceof Error) {
20
+ const firebaseErr = error as { code?: string };
21
+ return {
22
+ code: firebaseErr.code || 'unknown',
23
+ message: error.message,
24
+ };
25
+ }
26
+ return {
27
+ code: 'unknown',
28
+ message: typeof error === 'string' ? error : 'Unknown error',
29
+ };
30
+ }
31
+
32
+ /**
33
+ * Convert unknown error to auth error info
34
+ * Auth-specific error codes are prefixed with 'auth/'
35
+ */
36
+ export function toAuthErrorInfo(error: unknown): ErrorInfo {
37
+ if (error instanceof Error) {
38
+ const firebaseErr = error as { code?: string };
39
+ return {
40
+ code: firebaseErr.code || 'auth/failed',
41
+ message: error.message,
42
+ };
43
+ }
44
+ return {
45
+ code: 'auth/failed',
46
+ message: typeof error === 'string' ? error : 'Unknown error',
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Check if error info has a specific error code
52
+ */
53
+ export function hasErrorCode(error: ErrorInfo, code: string): boolean {
54
+ return error.code === code;
55
+ }
56
+
57
+ /**
58
+ * Check if error is a cancelled/auth cancelled error
59
+ */
60
+ export function isCancelledError(error: ErrorInfo): boolean {
61
+ return error.code === 'auth/cancelled' || error.message.includes('ERR_CANCELED');
62
+ }
63
+
64
+ /**
65
+ * Check if error is a quota exceeded error
66
+ */
67
+ export function isQuotaError(error: ErrorInfo): boolean {
68
+ return (
69
+ error.code === 'quota-exceeded' ||
70
+ error.code.includes('quota') ||
71
+ error.message.toLowerCase().includes('quota')
72
+ );
73
+ }
74
+
75
+ /**
76
+ * Check if error is a network error
77
+ */
78
+ export function isNetworkError(error: ErrorInfo): boolean {
79
+ return (
80
+ error.code.includes('network') ||
81
+ error.message.toLowerCase().includes('network')
82
+ );
83
+ }
84
+
85
+ /**
86
+ * Check if error is an authentication error
87
+ */
88
+ export function isAuthError(error: ErrorInfo): boolean {
89
+ return error.code.startsWith('auth/');
90
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * ID Generator Utility
3
+ * Centralized ID generation utilities for unique identifiers
4
+ */
5
+
6
+ /**
7
+ * Generate a unique ID using timestamp and random string
8
+ * Format: timestamp-randomstring
9
+ * @returns Unique identifier string
10
+ */
11
+ export function generateUniqueId(): string {
12
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
13
+ }
14
+
15
+ /**
16
+ * Generate a short random ID
17
+ * Useful for temporary identifiers where uniqueness within scope is sufficient
18
+ * @returns Random identifier string
19
+ */
20
+ export function generateShortId(length: number = 8): string {
21
+ return Math.random().toString(36).substring(2, 2 + length);
22
+ }
23
+
24
+ /**
25
+ * Generate a UUID-like ID
26
+ * Format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
27
+ * @returns UUID-like identifier string
28
+ */
29
+ export function generateUUID(): string {
30
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
31
+ const r = (Math.random() * 16) | 0;
32
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
33
+ return v.toString(16);
34
+ });
35
+ }
36
+
37
+ /**
38
+ * Generate a nanoid-like ID
39
+ * Uses URL-safe characters for better compatibility
40
+ * @param length - Length of the ID (default: 21)
41
+ * @returns URL-safe unique identifier string
42
+ */
43
+ export function generateNanoId(length: number = 21): string {
44
+ const chars = 'ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW';
45
+ let result = '';
46
+ for (let i = 0; i < length; i++) {
47
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
48
+ }
49
+ return result;
50
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Validation Utility
3
+ * Centralized validation utilities for common patterns
4
+ */
5
+
6
+ /**
7
+ * Check if a string is a valid non-empty value
8
+ */
9
+ export function isValidString(value: unknown): value is string {
10
+ return typeof value === 'string' && value.trim().length > 0;
11
+ }
12
+
13
+ /**
14
+ * Check if a string is empty or whitespace only
15
+ */
16
+ export function isEmptyString(value: unknown): boolean {
17
+ return typeof value === 'string' && value.trim().length === 0;
18
+ }
19
+
20
+ /**
21
+ * Validate Firebase API key format
22
+ * Firebase API keys typically start with "AIza" followed by 35 characters
23
+ */
24
+ export function isValidFirebaseApiKey(apiKey: string): boolean {
25
+ if (!isValidString(apiKey)) {
26
+ return false;
27
+ }
28
+ const apiKeyPattern = /^AIza[0-9A-Za-z_-]{35}$/;
29
+ return apiKeyPattern.test(apiKey.trim());
30
+ }
31
+
32
+ /**
33
+ * Validate Firebase authDomain format
34
+ * Expected format: "projectId.firebaseapp.com" or "projectId.web.app"
35
+ */
36
+ export function isValidFirebaseAuthDomain(authDomain: string): boolean {
37
+ if (!isValidString(authDomain)) {
38
+ return false;
39
+ }
40
+ const trimmed = authDomain.trim();
41
+ return (
42
+ trimmed.includes('.firebaseapp.com') ||
43
+ trimmed.includes('.web.app')
44
+ );
45
+ }
46
+
47
+ /**
48
+ * Validate Firebase projectId format
49
+ * Project IDs must be 6-30 characters, lowercase, alphanumeric, and may contain hyphens
50
+ */
51
+ export function isValidFirebaseProjectId(projectId: string): boolean {
52
+ if (!isValidString(projectId)) {
53
+ return false;
54
+ }
55
+ const pattern = /^[a-z0-9][a-z0-9-]{4,28}[a-z0-9]$/;
56
+ return pattern.test(projectId.trim());
57
+ }
58
+
59
+ /**
60
+ * Validate URL format
61
+ */
62
+ export function isValidUrl(url: string): boolean {
63
+ if (!isValidString(url)) {
64
+ return false;
65
+ }
66
+ try {
67
+ new URL(url.trim());
68
+ return true;
69
+ } catch {
70
+ return false;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Validate HTTPS URL
76
+ */
77
+ export function isValidHttpsUrl(url: string): boolean {
78
+ if (!isValidString(url)) {
79
+ return false;
80
+ }
81
+ try {
82
+ const urlObj = new URL(url.trim());
83
+ return urlObj.protocol === 'https:';
84
+ } catch {
85
+ return false;
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Validate email format
91
+ */
92
+ export function isValidEmail(email: string): boolean {
93
+ if (!isValidString(email)) {
94
+ return false;
95
+ }
96
+ const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
97
+ return emailPattern.test(email.trim());
98
+ }
99
+
100
+ /**
101
+ * Check if value is defined (not null or undefined)
102
+ */
103
+ export function isDefined<T>(value: T | null | undefined): value is T {
104
+ return value !== null && value !== undefined;
105
+ }
106
+
107
+ /**
108
+ * Check if array is not empty
109
+ */
110
+ export function isNonEmptyArray<T>(value: unknown): value is [T, ...T[]] {
111
+ return Array.isArray(value) && value.length > 0;
112
+ }
113
+
114
+ /**
115
+ * Check if number is in range
116
+ */
117
+ export function isInRange(value: number, min: number, max: number): boolean {
118
+ return typeof value === 'number' && value >= min && value <= max;
119
+ }
120
+
121
+ /**
122
+ * Check if number is positive
123
+ */
124
+ export function isPositive(value: number): boolean {
125
+ return typeof value === 'number' && value > 0;
126
+ }
127
+
128
+ /**
129
+ * Check if number is non-negative
130
+ */
131
+ export function isNonNegative(value: number): boolean {
132
+ return typeof value === 'number' && value >= 0;
133
+ }
@@ -3,116 +3,25 @@
3
3
  * Prevents duplicate Firestore queries within a short time window
4
4
  */
5
5
 
6
- interface PendingQuery {
7
- promise: Promise<unknown>;
8
- timestamp: number;
9
- }
6
+ import type { QueryKey } from '../../utils/deduplication/query-key-generator.util';
7
+ import { generateQueryKey } from '../../utils/deduplication/query-key-generator.util';
8
+ import { PendingQueryManager } from '../../utils/deduplication/pending-query-manager.util';
9
+ import { TimerManager } from '../../utils/deduplication/timer-manager.util';
10
10
 
11
- interface QueryKey {
12
- collection: string;
13
- filters: string;
14
- limit?: number;
15
- orderBy?: string;
16
- }
11
+ const DEDUPLICATION_WINDOW_MS = 1000; // 1 second
12
+ const CLEANUP_INTERVAL_MS = 5000; // 5 seconds
17
13
 
18
14
  export class QueryDeduplicationMiddleware {
19
- private pendingQueries = new Map<string, PendingQuery>();
20
- private readonly DEDUPLICATION_WINDOW_MS = 1000; // 1 second
21
- private readonly CLEANUP_INTERVAL_MS = 5000; // 5 seconds
22
- private cleanupTimer: ReturnType<typeof setInterval> | null = null;
23
-
24
- constructor() {
25
- this.startCleanupTimer();
26
- }
27
-
28
- /**
29
- * Start cleanup timer to prevent memory leaks
30
- */
31
- private startCleanupTimer(): void {
32
- if (this.cleanupTimer) {
33
- clearInterval(this.cleanupTimer);
34
- }
35
-
36
- this.cleanupTimer = setInterval(() => {
37
- try {
38
- this.cleanupExpiredQueries();
39
- } catch {
40
- // Silently handle cleanup errors to prevent timer from causing issues
41
- // Clear all queries if cleanup fails to prevent memory leak
42
- this.pendingQueries.clear();
43
- }
44
- }, this.CLEANUP_INTERVAL_MS);
45
- }
46
-
47
- /**
48
- * Clean up expired queries to prevent memory leaks
49
- */
50
- private cleanupExpiredQueries(): void {
51
- const now = Date.now();
52
- for (const [key, query] of this.pendingQueries.entries()) {
53
- if (now - query.timestamp > this.DEDUPLICATION_WINDOW_MS) {
54
- this.pendingQueries.delete(key);
55
- }
56
- }
57
- }
58
-
59
- /**
60
- * Generate query key from query parameters
61
- */
62
- private generateQueryKey(key: QueryKey): string {
63
- const parts = [
64
- key.collection,
65
- key.filters,
66
- key.limit?.toString() || '',
67
- key.orderBy || '',
68
- ];
69
- return parts.join('|');
70
- }
71
-
72
- /**
73
- * Check if query is already pending
74
- */
75
- private isQueryPending(key: string): boolean {
76
- const pending = this.pendingQueries.get(key);
77
- if (!pending) return false;
78
-
79
- const age = Date.now() - pending.timestamp;
80
- if (age > this.DEDUPLICATION_WINDOW_MS) {
81
- this.pendingQueries.delete(key);
82
- return false;
83
- }
84
-
85
- return true;
86
- }
87
-
88
- /**
89
- * Get pending query promise
90
- */
91
- private getPendingQuery(key: string): Promise<unknown> | null {
92
- const pending = this.pendingQueries.get(key);
93
- return pending ? pending.promise : null;
94
- }
95
-
96
- /**
97
- * Add query to pending list with guaranteed cleanup
98
- */
99
- private addPendingQuery(key: string, promise: Promise<unknown>): void {
100
- // Wrap the promise to ensure cleanup happens regardless of outcome
101
- const wrappedPromise = promise
102
- .catch((error) => {
103
- // Re-throw to maintain original promise behavior
104
- throw error;
105
- })
106
- .finally(() => {
107
- // Guaranteed cleanup - runs for both resolve and reject
108
- this.pendingQueries.delete(key);
109
- });
110
-
111
- // Store the wrapped promise with the finally handler attached
112
- this.pendingQueries.set(key, {
113
- promise: wrappedPromise,
114
- timestamp: Date.now(),
15
+ private readonly queryManager: PendingQueryManager;
16
+ private readonly timerManager: TimerManager;
17
+
18
+ constructor(deduplicationWindowMs: number = DEDUPLICATION_WINDOW_MS) {
19
+ this.queryManager = new PendingQueryManager(deduplicationWindowMs);
20
+ this.timerManager = new TimerManager({
21
+ cleanupIntervalMs: CLEANUP_INTERVAL_MS,
22
+ onCleanup: () => this.queryManager.cleanup(),
115
23
  });
24
+ this.timerManager.start();
116
25
  }
117
26
 
118
27
  /**
@@ -122,17 +31,17 @@ export class QueryDeduplicationMiddleware {
122
31
  queryKey: QueryKey,
123
32
  queryFn: () => Promise<T>,
124
33
  ): Promise<T> {
125
- const key = this.generateQueryKey(queryKey);
34
+ const key = generateQueryKey(queryKey);
126
35
 
127
- if (this.isQueryPending(key)) {
128
- const pendingPromise = this.getPendingQuery(key);
36
+ if (this.queryManager.isPending(key)) {
37
+ const pendingPromise = this.queryManager.get(key);
129
38
  if (pendingPromise) {
130
39
  return pendingPromise as Promise<T>;
131
40
  }
132
41
  }
133
42
 
134
43
  const promise = queryFn();
135
- this.addPendingQuery(key, promise);
44
+ this.queryManager.add(key, promise);
136
45
 
137
46
  return promise;
138
47
  }
@@ -141,27 +50,27 @@ export class QueryDeduplicationMiddleware {
141
50
  * Clear all pending queries
142
51
  */
143
52
  clear(): void {
144
- this.pendingQueries.clear();
53
+ this.queryManager.clear();
145
54
  }
146
55
 
147
56
  /**
148
57
  * Destroy middleware and cleanup resources
149
58
  */
150
59
  destroy(): void {
151
- if (this.cleanupTimer) {
152
- clearInterval(this.cleanupTimer);
153
- this.cleanupTimer = null;
154
- }
155
- this.pendingQueries.clear();
60
+ this.timerManager.destroy();
61
+ this.queryManager.clear();
156
62
  }
157
63
 
158
64
  /**
159
65
  * Get pending queries count
160
66
  */
161
67
  getPendingCount(): number {
162
- return this.pendingQueries.size;
68
+ return this.queryManager.size();
163
69
  }
164
70
  }
165
71
 
166
72
  export const queryDeduplicationMiddleware = new QueryDeduplicationMiddleware();
167
73
 
74
+ // Re-export types for convenience
75
+ export type { QueryKey };
76
+
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import type { RequestLog, RequestStats, RequestType } from '../../domain/entities/RequestLog';
7
+ import { generateUniqueId } from '../../../domain/utils/id-generator.util';
7
8
 
8
9
  export class RequestLoggerService {
9
10
  private logs: RequestLog[] = [];
@@ -16,7 +17,7 @@ export class RequestLoggerService {
16
17
  logRequest(log: Omit<RequestLog, 'id' | 'timestamp'>): void {
17
18
  const fullLog: RequestLog = {
18
19
  ...log,
19
- id: this.generateId(),
20
+ id: generateUniqueId(),
20
21
  timestamp: Date.now(),
21
22
  };
22
23
 
@@ -92,13 +93,6 @@ export class RequestLoggerService {
92
93
  };
93
94
  }
94
95
 
95
- /**
96
- * Generate unique ID
97
- */
98
- private generateId(): string {
99
- return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
100
- }
101
-
102
96
  /**
103
97
  * Notify all listeners
104
98
  */
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Deduplication Utilities
3
+ * Utilities for query deduplication
4
+ */
5
+
6
+ export { TimerManager } from './timer-manager.util';
7
+ export type { TimerManagerOptions } from './timer-manager.util';
8
+
9
+ export { generateQueryKey, createQueryKey } from './query-key-generator.util';
10
+ export type { QueryKey } from './query-key-generator.util';
11
+
12
+ export { PendingQueryManager } from './pending-query-manager.util';
13
+ export type { PendingQuery } from './pending-query-manager.util';