@umituz/react-native-firebase 1.13.2 → 1.13.4

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 (44) hide show
  1. package/package.json +10 -2
  2. package/src/auth/domain/entities/AnonymousUser.ts +44 -0
  3. package/src/auth/domain/errors/FirebaseAuthError.ts +18 -0
  4. package/src/auth/domain/value-objects/FirebaseAuthConfig.ts +45 -0
  5. package/src/auth/index.ts +146 -0
  6. package/src/auth/infrastructure/config/FirebaseAuthClient.ts +210 -0
  7. package/src/auth/infrastructure/config/initializers/FirebaseAuthInitializer.ts +148 -0
  8. package/src/auth/infrastructure/services/account-deletion.service.ts +250 -0
  9. package/src/auth/infrastructure/services/anonymous-auth.service.ts +135 -0
  10. package/src/auth/infrastructure/services/apple-auth.service.ts +146 -0
  11. package/src/auth/infrastructure/services/auth-guard.service.ts +97 -0
  12. package/src/auth/infrastructure/services/auth-utils.service.ts +168 -0
  13. package/src/auth/infrastructure/services/firestore-utils.service.ts +155 -0
  14. package/src/auth/infrastructure/services/google-auth.service.ts +100 -0
  15. package/src/auth/infrastructure/services/reauthentication.service.ts +216 -0
  16. package/src/auth/presentation/hooks/useAnonymousAuth.ts +201 -0
  17. package/src/auth/presentation/hooks/useFirebaseAuth.ts +84 -0
  18. package/src/auth/presentation/hooks/useSocialAuth.ts +162 -0
  19. package/src/firestore/__tests__/BaseRepository.test.ts +133 -0
  20. package/src/firestore/__tests__/QueryDeduplicationMiddleware.test.ts +147 -0
  21. package/src/firestore/__tests__/mocks/react-native-firebase.ts +23 -0
  22. package/src/firestore/__tests__/setup.ts +36 -0
  23. package/src/firestore/domain/constants/QuotaLimits.ts +97 -0
  24. package/src/firestore/domain/entities/QuotaMetrics.ts +28 -0
  25. package/src/firestore/domain/entities/RequestLog.ts +30 -0
  26. package/src/firestore/domain/errors/FirebaseFirestoreError.ts +52 -0
  27. package/src/firestore/domain/services/QuotaCalculator.ts +70 -0
  28. package/src/firestore/index.ts +174 -0
  29. package/src/firestore/infrastructure/config/FirestoreClient.ts +181 -0
  30. package/src/firestore/infrastructure/config/initializers/FirebaseFirestoreInitializer.ts +46 -0
  31. package/src/firestore/infrastructure/middleware/QueryDeduplicationMiddleware.ts +153 -0
  32. package/src/firestore/infrastructure/middleware/QuotaTrackingMiddleware.ts +165 -0
  33. package/src/firestore/infrastructure/repositories/BasePaginatedRepository.ts +90 -0
  34. package/src/firestore/infrastructure/repositories/BaseQueryRepository.ts +80 -0
  35. package/src/firestore/infrastructure/repositories/BaseRepository.ts +147 -0
  36. package/src/firestore/infrastructure/services/QuotaMonitorService.ts +108 -0
  37. package/src/firestore/infrastructure/services/RequestLoggerService.ts +139 -0
  38. package/src/firestore/types/pagination.types.ts +60 -0
  39. package/src/firestore/utils/dateUtils.ts +31 -0
  40. package/src/firestore/utils/document-mapper.helper.ts +145 -0
  41. package/src/firestore/utils/pagination.helper.ts +93 -0
  42. package/src/firestore/utils/query-builder.ts +188 -0
  43. package/src/firestore/utils/quota-error-detector.util.ts +100 -0
  44. package/src/index.ts +16 -0
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Base Repository - Core Operations
3
+ *
4
+ * Provides essential Firestore operations with centralized database access.
5
+ * All Firestore repositories should extend this class.
6
+ *
7
+ * Architecture:
8
+ * - DRY: Centralized database access (getDb)
9
+ * - SOLID: Single Responsibility - Database access only
10
+ * - KISS: Simple base class with protected db property
11
+ * - App-agnostic: Works with any app, no app-specific code
12
+ *
13
+ * This class is designed to be used across hundreds of apps.
14
+ * It provides a consistent interface for Firestore operations.
15
+ */
16
+
17
+ import type { Firestore } from "firebase/firestore";
18
+ import { getFirestore } from "../config/FirestoreClient";
19
+ import {
20
+ isQuotaError as checkQuotaError,
21
+ getQuotaErrorMessage,
22
+ } from "../../utils/quota-error-detector.util";
23
+ import { FirebaseFirestoreQuotaError } from "../../domain/errors/FirebaseFirestoreError";
24
+
25
+ export class BaseRepository {
26
+ private isDestroyed = false;
27
+
28
+ /**
29
+ * Get Firestore database instance
30
+ * Returns null if Firestore is not initialized (offline mode)
31
+ * Use getDbOrThrow() if you need to throw an error instead
32
+ *
33
+ * @returns Firestore instance or null if not initialized
34
+ */
35
+ protected getDb(): Firestore | null {
36
+ if (this.isDestroyed) {
37
+ if (__DEV__) {
38
+ console.warn('[BaseRepository] Attempted to use destroyed repository');
39
+ }
40
+ return null;
41
+ }
42
+ return getFirestore();
43
+ }
44
+
45
+ /**
46
+ * Get Firestore database instance or throw error
47
+ * Throws error if Firestore is not initialized
48
+ * Use this method when Firestore is required for the operation
49
+ *
50
+ * @returns Firestore instance
51
+ * @throws Error if Firestore is not initialized
52
+ */
53
+ protected getDbOrThrow(): Firestore {
54
+ const db = getFirestore();
55
+ if (!db) {
56
+ throw new Error("Firestore is not initialized. Please initialize Firebase App first.");
57
+ }
58
+ return db;
59
+ }
60
+
61
+ /**
62
+ * Check if Firestore is initialized
63
+ * Useful for conditional operations
64
+ *
65
+ * @returns true if Firestore is initialized, false otherwise
66
+ */
67
+ protected isDbInitialized(): boolean {
68
+ try {
69
+ const db = getFirestore();
70
+ return db !== null;
71
+ } catch {
72
+ return false;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Check if error is a quota error
78
+ * Quota errors indicate daily read/write/delete limits are exceeded
79
+ *
80
+ * @param error - Error to check
81
+ * @returns true if error is a quota error
82
+ */
83
+ protected isQuotaError(error: unknown): boolean {
84
+ return checkQuotaError(error);
85
+ }
86
+
87
+ /**
88
+ * Handle quota error
89
+ * Throws FirebaseFirestoreQuotaError with user-friendly message
90
+ *
91
+ * @param error - Original error
92
+ * @throws FirebaseFirestoreQuotaError
93
+ */
94
+ protected handleQuotaError(error: unknown): never {
95
+ const message = getQuotaErrorMessage();
96
+ throw new FirebaseFirestoreQuotaError(message, error);
97
+ }
98
+
99
+ /**
100
+ * Wrap Firestore operation with quota error handling
101
+ * Automatically detects and handles quota errors
102
+ *
103
+ * @param operation - Firestore operation to execute
104
+ * @returns Result of the operation
105
+ * @throws FirebaseFirestoreQuotaError if quota error occurs
106
+ */
107
+ protected async executeWithQuotaHandling<T>(
108
+ operation: () => Promise<T>,
109
+ ): Promise<T> {
110
+ try {
111
+ return await operation();
112
+ } catch (error) {
113
+ if (this.isQuotaError(error)) {
114
+ this.handleQuotaError(error);
115
+ }
116
+ throw error;
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Track read operations (stub for analytics)
122
+ * @param collection - Collection name
123
+ * @param count - Number of reads
124
+ * @param cached - Whether read was from cache
125
+ */
126
+ protected trackRead(_collection: string, _count: number, _cached: boolean): void {
127
+ // Stub for future analytics implementation
128
+ }
129
+
130
+ protected trackWrite(_collection: string, _docId: string, _count: number): void {
131
+ // Stub for future analytics implementation
132
+ }
133
+
134
+ protected trackDelete(_collection: string, _docId: string, _count: number): void {
135
+ // Stub for future analytics implementation
136
+ }
137
+
138
+ /**
139
+ * Destroy repository and cleanup resources
140
+ */
141
+ destroy(): void {
142
+ this.isDestroyed = true;
143
+ if (__DEV__) {
144
+ console.log('[BaseRepository] Repository destroyed');
145
+ }
146
+ }
147
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Quota Monitor Service
3
+ * Infrastructure service for monitoring Firestore quota usage
4
+ */
5
+
6
+ import type { QuotaMetrics, QuotaLimits, QuotaStatus } from '../../domain/entities/QuotaMetrics';
7
+ import { QuotaCalculator } from '../../domain/services/QuotaCalculator';
8
+
9
+ export class QuotaMonitorService {
10
+ private metrics: QuotaMetrics = {
11
+ readCount: 0,
12
+ writeCount: 0,
13
+ deleteCount: 0,
14
+ timestamp: Date.now(),
15
+ };
16
+
17
+ private limits: QuotaLimits = QuotaCalculator.getDefaultLimits();
18
+ private listeners: Set<(status: QuotaStatus) => void> = new Set();
19
+
20
+ /**
21
+ * Set quota limits
22
+ */
23
+ setLimits(limits: Partial<QuotaLimits>): void {
24
+ this.limits = { ...this.limits, ...limits };
25
+ }
26
+
27
+ /**
28
+ * Increment read count
29
+ */
30
+ incrementRead(count: number = 1): void {
31
+ this.metrics.readCount += count;
32
+ this.notifyListeners();
33
+ }
34
+
35
+ /**
36
+ * Increment write count
37
+ */
38
+ incrementWrite(count: number = 1): void {
39
+ this.metrics.writeCount += count;
40
+ this.notifyListeners();
41
+ }
42
+
43
+ /**
44
+ * Increment delete count
45
+ */
46
+ incrementDelete(count: number = 1): void {
47
+ this.metrics.deleteCount += count;
48
+ this.notifyListeners();
49
+ }
50
+
51
+ /**
52
+ * Get current metrics
53
+ */
54
+ getMetrics(): QuotaMetrics {
55
+ return { ...this.metrics };
56
+ }
57
+
58
+ /**
59
+ * Get current status
60
+ */
61
+ getStatus(): QuotaStatus {
62
+ return QuotaCalculator.calculateStatus(this.metrics, this.limits);
63
+ }
64
+
65
+ /**
66
+ * Reset metrics
67
+ */
68
+ resetMetrics(): void {
69
+ this.metrics = {
70
+ readCount: 0,
71
+ writeCount: 0,
72
+ deleteCount: 0,
73
+ timestamp: Date.now(),
74
+ };
75
+ this.notifyListeners();
76
+ }
77
+
78
+ /**
79
+ * Add status change listener
80
+ */
81
+ addListener(listener: (status: QuotaStatus) => void): () => void {
82
+ this.listeners.add(listener);
83
+ return () => {
84
+ this.listeners.delete(listener);
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Notify all listeners
90
+ */
91
+ private notifyListeners(): void {
92
+ const status = this.getStatus();
93
+ this.listeners.forEach((listener) => {
94
+ try {
95
+ listener(status);
96
+ } catch (error) {
97
+ /* eslint-disable-next-line no-console */
98
+ if (__DEV__) {
99
+ /* eslint-disable-next-line no-console */
100
+ console.error('[QuotaMonitor] Listener error:', error);
101
+ }
102
+ }
103
+ });
104
+ }
105
+ }
106
+
107
+ export const quotaMonitorService = new QuotaMonitorService();
108
+
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Request Logger Service
3
+ * Infrastructure service for logging Firestore requests
4
+ */
5
+
6
+ import type { RequestLog, RequestStats, RequestType } from '../../domain/entities/RequestLog';
7
+
8
+ export class RequestLoggerService {
9
+ private logs: RequestLog[] = [];
10
+ private readonly MAX_LOGS = 1000;
11
+ private listeners: Set<(log: RequestLog) => void> = new Set();
12
+
13
+ /**
14
+ * Log a request
15
+ */
16
+ logRequest(log: Omit<RequestLog, 'id' | 'timestamp'>): void {
17
+ const fullLog: RequestLog = {
18
+ ...log,
19
+ id: this.generateId(),
20
+ timestamp: Date.now(),
21
+ };
22
+
23
+ this.logs.push(fullLog);
24
+
25
+ if (this.logs.length > this.MAX_LOGS) {
26
+ this.logs.shift();
27
+ }
28
+
29
+ // Log Firestore operations in development mode
30
+ if (__DEV__) {
31
+ const prefix = fullLog.cached ? '[Firestore Cache]' : '[Firestore]';
32
+ const operation = fullLog.type.toUpperCase();
33
+ const status = fullLog.success ? '✓' : '✗';
34
+ const details = fullLog.documentId
35
+ ? `${fullLog.collection}/${fullLog.documentId}`
36
+ : fullLog.collection;
37
+
38
+ if (fullLog.success) {
39
+ // eslint-disable-next-line no-console
40
+ console.log(`${prefix} ${status} ${operation}: ${details}`);
41
+ } else {
42
+ // eslint-disable-next-line no-console
43
+ console.error(`${prefix} ${status} ${operation}: ${details}`, fullLog.error);
44
+ }
45
+ }
46
+
47
+ this.notifyListeners(fullLog);
48
+ }
49
+
50
+ /**
51
+ * Get all logs
52
+ */
53
+ getLogs(): RequestLog[] {
54
+ return [...this.logs];
55
+ }
56
+
57
+ /**
58
+ * Get logs by type
59
+ */
60
+ getLogsByType(type: RequestType): RequestLog[] {
61
+ return this.logs.filter((log) => log.type === type);
62
+ }
63
+
64
+ /**
65
+ * Get request statistics
66
+ */
67
+ getStats(): RequestStats {
68
+ const totalRequests = this.logs.length;
69
+ const readRequests = this.logs.filter((l) => l.type === 'read').length;
70
+ const writeRequests = this.logs.filter((l) => l.type === 'write').length;
71
+ const deleteRequests = this.logs.filter((l) => l.type === 'delete').length;
72
+ const listenerRequests = this.logs.filter((l) => l.type === 'listener').length;
73
+ const cachedRequests = this.logs.filter((l) => l.cached).length;
74
+ const failedRequests = this.logs.filter((l) => !l.success).length;
75
+
76
+ const durations = this.logs
77
+ .filter((l) => l.duration !== undefined)
78
+ .map((l) => l.duration!);
79
+ const averageDuration =
80
+ durations.length > 0
81
+ ? durations.reduce((sum, d) => sum + d, 0) / durations.length
82
+ : 0;
83
+
84
+ return {
85
+ totalRequests,
86
+ readRequests,
87
+ writeRequests,
88
+ deleteRequests,
89
+ listenerRequests,
90
+ cachedRequests,
91
+ failedRequests,
92
+ averageDuration,
93
+ };
94
+ }
95
+
96
+ /**
97
+ * Clear all logs
98
+ */
99
+ clearLogs(): void {
100
+ this.logs = [];
101
+ }
102
+
103
+ /**
104
+ * Add log listener
105
+ */
106
+ addListener(listener: (log: RequestLog) => void): () => void {
107
+ this.listeners.add(listener);
108
+ return () => {
109
+ this.listeners.delete(listener);
110
+ };
111
+ }
112
+
113
+ /**
114
+ * Generate unique ID
115
+ */
116
+ private generateId(): string {
117
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
118
+ }
119
+
120
+ /**
121
+ * Notify all listeners
122
+ */
123
+ private notifyListeners(log: RequestLog): void {
124
+ this.listeners.forEach((listener) => {
125
+ try {
126
+ listener(log);
127
+ } catch (error) {
128
+ /* eslint-disable-next-line no-console */
129
+ if (__DEV__) {
130
+ /* eslint-disable-next-line no-console */
131
+ console.error('[RequestLogger] Listener error:', error);
132
+ }
133
+ }
134
+ });
135
+ }
136
+ }
137
+
138
+ export const requestLoggerService = new RequestLoggerService();
139
+
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Pagination Types
3
+ *
4
+ * Generic types for cursor-based pagination in Firestore.
5
+ * Used across hundreds of apps for consistent pagination interface.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const result: PaginatedResult<Post> = await repository.getPosts({ limit: 10 });
10
+ * console.log(result.items); // Post[]
11
+ * console.log(result.hasMore); // boolean
12
+ * console.log(result.nextCursor); // string | null
13
+ * ```
14
+ */
15
+
16
+ /**
17
+ * Pagination parameters for queries
18
+ */
19
+ export interface PaginationParams {
20
+ /**
21
+ * Maximum number of items to return
22
+ * @default 10
23
+ */
24
+ limit?: number;
25
+
26
+ /**
27
+ * Cursor for pagination (document ID)
28
+ * Use this to fetch next page
29
+ */
30
+ cursor?: string;
31
+ }
32
+
33
+ /**
34
+ * Paginated result structure
35
+ */
36
+ export interface PaginatedResult<T> {
37
+ /**
38
+ * Array of items in current page
39
+ */
40
+ items: T[];
41
+
42
+ /**
43
+ * Cursor for next page (null if no more items)
44
+ */
45
+ nextCursor: string | null;
46
+
47
+ /**
48
+ * Whether there are more items to fetch
49
+ */
50
+ hasMore: boolean;
51
+ }
52
+
53
+ /**
54
+ * Empty paginated result
55
+ */
56
+ export const EMPTY_PAGINATED_RESULT: PaginatedResult<never> = {
57
+ items: [],
58
+ nextCursor: null,
59
+ hasMore: false,
60
+ };
@@ -0,0 +1,31 @@
1
+ import { Timestamp } from 'firebase/firestore';
2
+
3
+ /**
4
+ * Convert ISO string to Firestore Timestamp
5
+ */
6
+ export function isoToTimestamp(isoString: string): Timestamp {
7
+ return Timestamp.fromDate(new Date(isoString));
8
+ }
9
+
10
+ /**
11
+ * Convert Firestore Timestamp to ISO string
12
+ */
13
+ export function timestampToISO(timestamp: Timestamp): string {
14
+ if (!timestamp) return new Date().toISOString();
15
+ return timestamp.toDate().toISOString();
16
+ }
17
+
18
+ /**
19
+ * Convert Firestore Timestamp to Date
20
+ */
21
+ export function timestampToDate(timestamp: Timestamp): Date {
22
+ if (!timestamp) return new Date();
23
+ return timestamp.toDate();
24
+ }
25
+
26
+ /**
27
+ * Get current date as ISO string
28
+ */
29
+ export function getCurrentISOString(): string {
30
+ return new Date().toISOString();
31
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Document Mapper Helper
3
+ *
4
+ * Utilities for batch document processing with enrichment.
5
+ * Handles document extraction, validation, and enrichment with related data.
6
+ *
7
+ * App-agnostic: Works with any document type and any enrichment logic.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { DocumentMapperHelper } from '@umituz/react-native-firestore';
12
+ *
13
+ * const mapper = new DocumentMapperHelper<Post, User, EnrichedPost>();
14
+ * const enriched = await mapper.mapWithEnrichment(
15
+ * postDocs,
16
+ * post => extractPost(post),
17
+ * post => post.userId,
18
+ * userId => userRepo.getById(userId),
19
+ * (post, user) => ({ ...post, user })
20
+ * );
21
+ * ```
22
+ */
23
+
24
+ import type { QueryDocumentSnapshot, DocumentData } from 'firebase/firestore';
25
+
26
+ export class DocumentMapperHelper<TSource, TEnrichment, TResult> {
27
+ /**
28
+ * Map documents with enrichment from related data
29
+ *
30
+ * Process flow:
31
+ * 1. Extract source data from document
32
+ * 2. Skip if extraction fails or source is invalid
33
+ * 3. Get enrichment key from source
34
+ * 4. Fetch enrichment data using the key
35
+ * 5. Skip if enrichment data not found
36
+ * 6. Combine source and enrichment into result
37
+ *
38
+ * @param docs - Firestore document snapshots
39
+ * @param extractSource - Extract source data from document
40
+ * @param getEnrichmentKey - Get enrichment key from source
41
+ * @param fetchEnrichment - Fetch enrichment data by key
42
+ * @param combineData - Combine source and enrichment into result
43
+ * @returns Array of enriched results
44
+ */
45
+ async mapWithEnrichment(
46
+ docs: QueryDocumentSnapshot<DocumentData>[],
47
+ extractSource: (doc: QueryDocumentSnapshot<DocumentData>) => TSource | null,
48
+ getEnrichmentKey: (source: TSource) => string,
49
+ fetchEnrichment: (key: string) => Promise<TEnrichment | null>,
50
+ combineData: (source: TSource, enrichment: TEnrichment) => TResult,
51
+ ): Promise<TResult[]> {
52
+ const results: TResult[] = [];
53
+
54
+ for (const doc of docs) {
55
+ const source = extractSource(doc);
56
+ if (!source) continue;
57
+
58
+ const enrichmentKey = getEnrichmentKey(source);
59
+ const enrichment = await fetchEnrichment(enrichmentKey);
60
+ if (!enrichment) continue;
61
+
62
+ results.push(combineData(source, enrichment));
63
+ }
64
+
65
+ return results;
66
+ }
67
+
68
+ /**
69
+ * Map documents with multiple enrichments
70
+ *
71
+ * Similar to mapWithEnrichment but supports multiple enrichment sources.
72
+ * Useful when result needs data from multiple related collections.
73
+ *
74
+ * @param docs - Firestore document snapshots
75
+ * @param extractSource - Extract source data from document
76
+ * @param getEnrichmentKeys - Get all enrichment keys from source
77
+ * @param fetchEnrichments - Fetch all enrichment data by keys
78
+ * @param combineData - Combine source and enrichments into result
79
+ * @returns Array of enriched results
80
+ */
81
+ async mapWithMultipleEnrichments<TEnrichments extends Record<string, unknown>>(
82
+ docs: QueryDocumentSnapshot<DocumentData>[],
83
+ extractSource: (doc: QueryDocumentSnapshot<DocumentData>) => TSource | null,
84
+ getEnrichmentKeys: (source: TSource) => Record<string, string>,
85
+ fetchEnrichments: (keys: Record<string, string>) => Promise<TEnrichments | null>,
86
+ combineData: (source: TSource, enrichments: TEnrichments) => TResult,
87
+ ): Promise<TResult[]> {
88
+ const results: TResult[] = [];
89
+
90
+ for (const doc of docs) {
91
+ const source = extractSource(doc);
92
+ if (!source) continue;
93
+
94
+ const enrichmentKeys = getEnrichmentKeys(source);
95
+ const enrichments = await fetchEnrichments(enrichmentKeys);
96
+ if (!enrichments) continue;
97
+
98
+ results.push(combineData(source, enrichments));
99
+ }
100
+
101
+ return results;
102
+ }
103
+
104
+ /**
105
+ * Simple document mapping without enrichment
106
+ *
107
+ * @param docs - Firestore document snapshots
108
+ * @param extractData - Extract data from document
109
+ * @returns Array of extracted data (nulls filtered out)
110
+ */
111
+ map(
112
+ docs: QueryDocumentSnapshot<DocumentData>[],
113
+ extractData: (doc: QueryDocumentSnapshot<DocumentData>) => TResult | null,
114
+ ): TResult[] {
115
+ const results: TResult[] = [];
116
+
117
+ for (const doc of docs) {
118
+ const data = extractData(doc);
119
+ if (data) {
120
+ results.push(data);
121
+ }
122
+ }
123
+
124
+ return results;
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Create document mapper helper
130
+ *
131
+ * @returns DocumentMapperHelper instance
132
+ *
133
+ * @example
134
+ * ```typescript
135
+ * const mapper = createDocumentMapper<Post, User, EnrichedPost>();
136
+ * const enriched = await mapper.mapWithEnrichment(...);
137
+ * ```
138
+ */
139
+ export function createDocumentMapper<
140
+ TSource,
141
+ TEnrichment,
142
+ TResult,
143
+ >(): DocumentMapperHelper<TSource, TEnrichment, TResult> {
144
+ return new DocumentMapperHelper<TSource, TEnrichment, TResult>();
145
+ }