@umituz/react-native-firebase 2.6.1 → 2.6.3

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 (94) hide show
  1. package/package.json +1 -1
  2. package/src/application/auth/ports/AuthPort_part_aa +150 -0
  3. package/src/application/auth/ports/AuthPort_part_ab +14 -0
  4. package/src/application/auth/use-cases/SignInUseCaseHelpers.ts +0 -0
  5. package/src/application/auth/use-cases/SignInUseCaseMain.ts +0 -0
  6. package/src/application/auth/use-cases/SignInUseCase_part_aa +150 -0
  7. package/src/application/auth/use-cases/SignInUseCase_part_ab +103 -0
  8. package/src/application/auth/use-cases/SignOutUseCaseCleanup.ts +0 -0
  9. package/src/application/auth/use-cases/SignOutUseCaseMain.ts +0 -0
  10. package/src/application/auth/use-cases/SignOutUseCase_part_aa +150 -0
  11. package/src/application/auth/use-cases/SignOutUseCase_part_ab +138 -0
  12. package/src/domains/account-deletion/domain/services/UserValidationHelpers.ts.bak +181 -0
  13. package/src/domains/account-deletion/domain/services/UserValidationHelpers_part_aa +150 -0
  14. package/src/domains/account-deletion/domain/services/UserValidationHelpers_part_ab +31 -0
  15. package/src/domains/account-deletion/domain/services/{UserValidationService.ts → UserValidationService.ts.bak} +1 -10
  16. package/src/domains/account-deletion/domain/services/UserValidationService_part_aa +150 -0
  17. package/src/domains/account-deletion/domain/services/UserValidationService_part_ab +136 -0
  18. package/src/domains/account-deletion/infrastructure/services/AccountDeletionExecutor_part_aa +150 -0
  19. package/src/domains/account-deletion/infrastructure/services/AccountDeletionExecutor_part_ab +80 -0
  20. package/src/domains/account-deletion/infrastructure/services/AccountDeletionReauthHandler_part_aa +150 -0
  21. package/src/domains/account-deletion/infrastructure/services/AccountDeletionReauthHandler_part_ab +24 -0
  22. package/src/domains/account-deletion/infrastructure/services/AccountDeletionRepository_part_aa +150 -0
  23. package/src/domains/account-deletion/infrastructure/services/AccountDeletionRepository_part_ab +116 -0
  24. package/src/domains/account-deletion/infrastructure/services/reauthentication.service_part_aa +150 -0
  25. package/src/domains/account-deletion/infrastructure/services/reauthentication.service_part_ab +10 -0
  26. package/src/domains/auth/infrastructure_part_aa +150 -0
  27. package/src/domains/auth/infrastructure_part_ab +6 -0
  28. package/src/domains/auth/presentation/hooks/GoogleOAuthHelpers.ts +0 -0
  29. package/src/domains/auth/presentation/hooks/GoogleOAuthHookService_part_aa +150 -0
  30. package/src/domains/auth/presentation/hooks/GoogleOAuthHookService_part_ab +97 -0
  31. package/src/domains/auth/presentation/hooks/GoogleOAuthService.ts +0 -0
  32. package/src/domains/firestore/domain/entities/Collection.ts +31 -191
  33. package/src/domains/firestore/domain/entities/Collection.ts.bak +288 -0
  34. package/src/domains/firestore/domain/entities/CollectionFactory.ts +55 -0
  35. package/src/domains/firestore/domain/entities/CollectionHelpers.ts +143 -0
  36. package/src/domains/firestore/domain/entities/CollectionUtils.ts +72 -0
  37. package/src/domains/firestore/domain/entities/CollectionValidation.ts +138 -0
  38. package/src/domains/firestore/domain/entities/Collection_part_aa +150 -0
  39. package/src/domains/firestore/domain/entities/Collection_part_ab +138 -0
  40. package/src/domains/firestore/domain/entities/DocumentHelpers.ts +0 -0
  41. package/src/domains/firestore/domain/entities/DocumentMain.ts +0 -0
  42. package/src/domains/firestore/domain/entities/Document_part_aa +150 -0
  43. package/src/domains/firestore/domain/entities/Document_part_ab +83 -0
  44. package/src/domains/firestore/domain/index.ts +35 -8
  45. package/src/domains/firestore/domain/services/QueryServiceAnalysis_part_aa +150 -0
  46. package/src/domains/firestore/domain/services/QueryServiceAnalysis_part_ab +19 -0
  47. package/src/domains/firestore/domain/services/QueryServiceHelpers_part_aa +150 -0
  48. package/src/domains/firestore/domain/services/QueryServiceHelpers_part_ab +1 -0
  49. package/src/domains/firestore/domain/services/QueryService_part_aa +150 -0
  50. package/src/domains/firestore/domain/services/QueryService_part_ab +32 -0
  51. package/src/domains/firestore/domain/value-objects/QueryOptions.ts +20 -68
  52. package/src/domains/firestore/domain/value-objects/QueryOptions.ts.bak +6 -135
  53. package/src/domains/firestore/domain/value-objects/QueryOptionsFactory.ts +95 -0
  54. package/src/domains/firestore/domain/value-objects/QueryOptionsHelpers.ts +110 -0
  55. package/src/domains/firestore/domain/value-objects/QueryOptionsSerialization_part_aa +150 -0
  56. package/src/domains/firestore/domain/value-objects/QueryOptionsSerialization_part_ab +57 -0
  57. package/src/domains/firestore/domain/value-objects/QueryOptionsValidation_part_aa +150 -0
  58. package/src/domains/firestore/domain/value-objects/QueryOptionsValidation_part_ab +32 -0
  59. package/src/domains/firestore/domain/value-objects/QueryOptions_part_aa +150 -0
  60. package/src/domains/firestore/domain/value-objects/QueryOptions_part_ab +41 -0
  61. package/src/domains/firestore/domain/value-objects/WhereClause.ts +35 -205
  62. package/src/domains/firestore/domain/value-objects/WhereClause.ts.bak +299 -0
  63. package/src/domains/firestore/domain/value-objects/WhereClauseFactory.ts +44 -150
  64. package/src/domains/firestore/domain/value-objects/WhereClauseFactory.ts.bak +207 -0
  65. package/src/domains/firestore/domain/value-objects/WhereClauseFactory_part_aa +150 -0
  66. package/src/domains/firestore/domain/value-objects/WhereClauseFactory_part_ab +57 -0
  67. package/src/domains/firestore/domain/value-objects/WhereClauseHelpers.ts +123 -0
  68. package/src/domains/firestore/domain/value-objects/WhereClauseValidation.ts +83 -0
  69. package/src/domains/firestore/domain/value-objects/WhereClause_part_aa +150 -0
  70. package/src/domains/firestore/domain/value-objects/WhereClause_part_ab +149 -0
  71. package/src/shared/infrastructure/base/ErrorHandler_part_aa +150 -0
  72. package/src/shared/infrastructure/base/ErrorHandler_part_ab +39 -0
  73. package/src/shared/infrastructure/base/ServiceBase_part_aa +150 -0
  74. package/src/shared/infrastructure/base/ServiceBase_part_ab +70 -0
  75. package/src/shared/infrastructure/config/base/ServiceClientSingleton_part_aa +150 -0
  76. package/src/shared/infrastructure/config/base/ServiceClientSingleton_part_ab +5 -0
  77. /package/src/application/auth/ports/{AuthPort.ts → AuthPort.ts.bak} +0 -0
  78. /package/src/application/auth/use-cases/{SignInUseCase.ts → SignInUseCase.ts.bak} +0 -0
  79. /package/src/application/auth/use-cases/{SignOutUseCase.ts → SignOutUseCase.ts.bak} +0 -0
  80. /package/src/domains/account-deletion/infrastructure/services/{AccountDeletionExecutor.ts → AccountDeletionExecutor.ts.bak} +0 -0
  81. /package/src/domains/account-deletion/infrastructure/services/{AccountDeletionReauthHandler.ts → AccountDeletionReauthHandler.ts.bak} +0 -0
  82. /package/src/domains/account-deletion/infrastructure/services/{AccountDeletionRepository.ts → AccountDeletionRepository.ts.bak} +0 -0
  83. /package/src/domains/account-deletion/infrastructure/services/{reauthentication.service.ts → reauthentication.service.ts.bak} +0 -0
  84. /package/src/domains/auth/{infrastructure.ts → infrastructure.ts.bak} +0 -0
  85. /package/src/domains/auth/presentation/hooks/{GoogleOAuthHookService.ts → GoogleOAuthHookService.ts.bak} +0 -0
  86. /package/src/domains/firestore/domain/entities/{Document.ts → Document.ts.bak} +0 -0
  87. /package/src/domains/firestore/domain/services/{QueryService.ts → QueryService.ts.bak} +0 -0
  88. /package/src/domains/firestore/domain/services/{QueryServiceAnalysis.ts → QueryServiceAnalysis.ts.bak} +0 -0
  89. /package/src/domains/firestore/domain/services/{QueryServiceHelpers.ts → QueryServiceHelpers.ts.bak} +0 -0
  90. /package/src/domains/firestore/domain/value-objects/{QueryOptionsSerialization.ts → QueryOptionsSerialization.ts.bak} +0 -0
  91. /package/src/domains/firestore/domain/value-objects/{QueryOptionsValidation.ts → QueryOptionsValidation.ts.bak} +0 -0
  92. /package/src/shared/infrastructure/base/{ErrorHandler.ts → ErrorHandler.ts.bak} +0 -0
  93. /package/src/shared/infrastructure/base/{ServiceBase.ts → ServiceBase.ts.bak} +0 -0
  94. /package/src/shared/infrastructure/config/base/{ServiceClientSingleton.ts → ServiceClientSingleton.ts.bak} +0 -0
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Collection Entity Helpers
3
+ * Single Responsibility: Provide utility methods for collections
4
+ *
5
+ * Helper methods separated from main Collection entity.
6
+ *
7
+ * Max lines: 150 (enforced for maintainability)
8
+ */
9
+
10
+ import { Collection } from './Collection';
11
+ import { isValidCollectionPath, isValidCollectionName } from './CollectionValidation';
12
+
13
+ /**
14
+ * Check if collection is parent of another
15
+ */
16
+ export function isParentOf(parent: Collection, child: Collection): boolean {
17
+ const childPath = child.getPath();
18
+ const parentPath = parent.getPath();
19
+ return childPath.startsWith(parentPath + '/');
20
+ }
21
+
22
+ /**
23
+ * Get collection ID (similar to name but more explicit)
24
+ */
25
+ export function getCollectionId(collection: Collection): string {
26
+ return collection.getName();
27
+ }
28
+
29
+ /**
30
+ * Check if collections are equal
31
+ */
32
+ export function collectionsEqual(col1: Collection, col2: Collection): boolean {
33
+ return col1.getPath() === col2.getPath();
34
+ }
35
+
36
+ /**
37
+ * Get collection size info (metadata)
38
+ */
39
+ export function getCollectionInfo(collection: Collection): {
40
+ readonly name: string;
41
+ readonly path: string;
42
+ readonly depth: number;
43
+ readonly isNested: boolean;
44
+ readonly isRoot: boolean;
45
+ readonly isUserCollection: boolean;
46
+ } {
47
+ return {
48
+ name: collection.getName(),
49
+ path: collection.getPath(),
50
+ depth: collection.getDepth(),
51
+ isNested: collection.isNested(),
52
+ isRoot: collection.isRootLevel(),
53
+ isUserCollection: collection.isUserCollection(),
54
+ };
55
+ }
56
+
57
+ export function isValidCollection(collection: Collection): boolean {
58
+ return isValidCollectionName(collection.getName()) &&
59
+ isValidCollectionPath(collection.getPath());
60
+ }
61
+
62
+ export function collectionFromPath(db: any, path: string): Collection | null {
63
+ if (!isValidCollectionPath(path)) {
64
+ return null;
65
+ }
66
+
67
+ try {
68
+ const ref = db.collection(path);
69
+ return Collection.fromReference(ref as any);
70
+ } catch {
71
+ return null;
72
+ }
73
+ }
74
+
75
+ export function getSubCollectionNames(path: string): string[] {
76
+ if (!isValidCollectionPath(path)) {
77
+ return [];
78
+ }
79
+
80
+ const segments = path.split('/');
81
+ const collections: string[] = [];
82
+
83
+ // Extract collection names (even indices)
84
+ for (let i = 0; i < segments.length; i += 2) {
85
+ const segment = segments[i];
86
+ if (segment) {
87
+ collections.push(segment);
88
+ }
89
+ }
90
+
91
+ return collections;
92
+ }
93
+
94
+ export function buildCollectionPath(...parts: string[]): string {
95
+ if (parts.length === 0 || parts.length % 2 !== 0) {
96
+ throw new Error('Invalid collection path parts');
97
+ }
98
+
99
+ if (!parts.every(p => isValidCollectionName(p))) {
100
+ throw new Error('Invalid collection name in parts');
101
+ }
102
+
103
+ return parts.join('/');
104
+ }
105
+
106
+ export function parseCollectionPath(path: string): {
107
+ readonly collections: string[];
108
+ readonly documents: string[];
109
+ readonly segments: string[];
110
+ } | null {
111
+ if (!isValidCollectionPath(path)) {
112
+ return null;
113
+ }
114
+
115
+ const segments = path.split('/');
116
+ const collections: string[] = [];
117
+ const documents: string[] = [];
118
+
119
+ for (let i = 0; i < segments.length; i += 2) {
120
+ const collectionSegment = segments[i];
121
+ if (collectionSegment) {
122
+ collections.push(collectionSegment);
123
+ }
124
+ if (i + 1 < segments.length) {
125
+ const documentSegment = segments[i + 1];
126
+ if (documentSegment) {
127
+ documents.push(documentSegment);
128
+ }
129
+ }
130
+ }
131
+
132
+ return { collections, documents, segments };
133
+ }
134
+
135
+ export function isDocumentPath(path: string): boolean {
136
+ const segments = path.split('/');
137
+ return segments.length % 2 === 0 && segments.length >= 2;
138
+ }
139
+
140
+ export function isCollectionPath(path: string): boolean {
141
+ const segments = path.split('/');
142
+ return segments.length % 2 !== 0 && segments.length >= 1;
143
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Collection Utilities
3
+ * Single Responsibility: Provide utility functions for collection operations
4
+ *
5
+ * Max lines: 150 (enforced for maintainability)
6
+ */
7
+
8
+ import type { CollectionReference, Query } from 'firebase/firestore';
9
+ import type { Collection, CollectionMetadata } from './Collection';
10
+
11
+ /**
12
+ * Get collection depth in hierarchy
13
+ * Root collections have depth 0
14
+ */
15
+ export function getCollectionDepth(collection: Collection): number {
16
+ if (!collection.getParentPath()) return 0;
17
+ const path = collection.getPath();
18
+ return path.split('/').length / 2 - 1;
19
+ }
20
+
21
+ /**
22
+ * Convert collection to plain object (for serialization)
23
+ */
24
+ export function collectionToObject(collection: Collection): CollectionMetadata {
25
+ return {
26
+ name: collection.getName(),
27
+ path: collection.getPath(),
28
+ parentPath: collection.getParentPath(),
29
+ };
30
+ }
31
+
32
+ /**
33
+ * Check if reference is a query (has filters/limits)
34
+ */
35
+ export function isQueryReference<TDocument>(
36
+ reference: CollectionReference<TDocument> | Query<TDocument>
37
+ ): boolean {
38
+ return 'type' in reference && reference.type === 'query';
39
+ }
40
+
41
+ /**
42
+ * Check if reference is a collection reference (no filters)
43
+ */
44
+ export function isCollectionReference<TDocument>(
45
+ reference: CollectionReference<TDocument> | Query<TDocument>
46
+ ): boolean {
47
+ return !isQueryReference(reference);
48
+ }
49
+
50
+ /**
51
+ * Get collection size info (metadata)
52
+ */
53
+ export function getCollectionInfo(collection: Collection): {
54
+ readonly name: string;
55
+ readonly path: string;
56
+ readonly depth: number;
57
+ readonly isNested: boolean;
58
+ readonly isRoot: boolean;
59
+ readonly isUserCollection: boolean;
60
+ readonly isQuery: boolean;
61
+ } {
62
+ const reference = collection.getReference();
63
+ return {
64
+ name: collection.getName(),
65
+ path: collection.getPath(),
66
+ depth: getCollectionDepth(collection),
67
+ isNested: collection.isNested(),
68
+ isRoot: collection.isRootLevel(),
69
+ isUserCollection: collection.isUserCollection(),
70
+ isQuery: isQueryReference(reference),
71
+ };
72
+ }
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Collection Validation Utilities
3
+ * Single Responsibility: Validate collection names and paths
4
+ *
5
+ * Max lines: 150 (enforced for maintainability)
6
+ */
7
+
8
+ import type { Collection } from './Collection';
9
+
10
+ /**
11
+ * Validate collection name format
12
+ */
13
+ export function isValidCollectionName(name: string): boolean {
14
+ if (!name || typeof name !== 'string' || name.trim() === '') {
15
+ return false;
16
+ }
17
+
18
+ const invalidChars = /[\/\\.\s]/;
19
+ if (invalidChars.test(name)) {
20
+ return false;
21
+ }
22
+
23
+ if (name.startsWith('__') || name.endsWith('__')) {
24
+ return false;
25
+ }
26
+
27
+ if (name.length > 100) {
28
+ return false;
29
+ }
30
+
31
+ return true;
32
+ }
33
+
34
+ /**
35
+ * Check if collection is valid
36
+ */
37
+ export function isValidCollection(collection: Collection): boolean {
38
+ return isValidCollectionName(collection.getName()) &&
39
+ isValidCollectionPath(collection.getPath());
40
+ }
41
+
42
+ /**
43
+ * Validate collection path format
44
+ */
45
+ export function isValidCollectionPath(path: string): boolean {
46
+ if (!path || typeof path !== 'string' || path.trim() === '') {
47
+ return false;
48
+ }
49
+
50
+ const segments = path.split('/');
51
+ if (segments.length < 1 || segments.length % 2 === 0) {
52
+ return false;
53
+ }
54
+
55
+ return segments.every(segment => isValidCollectionName(segment));
56
+ }
57
+
58
+ /**
59
+ * Extract collection name from path
60
+ */
61
+ export function extractCollectionNameFromPath(path: string): string | null {
62
+ if (!isValidCollectionPath(path)) {
63
+ return null;
64
+ }
65
+
66
+ const segments = path.split('/');
67
+ return segments[segments.length - 1] || null;
68
+ }
69
+
70
+ /**
71
+ * Extract parent path from collection path
72
+ */
73
+ export function extractParentCollectionPath(path: string): string | null {
74
+ if (!isValidCollectionPath(path)) {
75
+ return null;
76
+ }
77
+
78
+ const segments = path.split('/');
79
+ if (segments.length <= 1) {
80
+ return null;
81
+ }
82
+
83
+ const result = segments.slice(0, -1).join('/');
84
+ return result || null;
85
+ }
86
+
87
+ /**
88
+ * Check if path points to document
89
+ */
90
+ export function isDocumentPath(path: string): boolean {
91
+ const segments = path.split('/');
92
+ return segments.length % 2 === 0 && segments.length >= 2;
93
+ }
94
+
95
+ /**
96
+ * Check if path points to collection
97
+ */
98
+ export function isCollectionPath(path: string): boolean {
99
+ const segments = path.split('/');
100
+ return segments.length % 2 !== 0 && segments.length >= 1;
101
+ }
102
+
103
+ /**
104
+ * Check if collection is a user collection (users/{userId}/{collection})
105
+ */
106
+ export function isUserCollectionPath(path: string): boolean {
107
+ return path.startsWith('users/');
108
+ }
109
+
110
+ /**
111
+ * Extract user ID from user collection path
112
+ */
113
+ export function extractUserIdFromPath(path: string): string | null {
114
+ if (!isUserCollectionPath(path)) return null;
115
+
116
+ const segments = path.split('/');
117
+ if (segments.length >= 3 && segments[0] === 'users') {
118
+ const userId = segments[1];
119
+ return userId || null;
120
+ }
121
+
122
+ return null;
123
+ }
124
+
125
+ /**
126
+ * Create a sub-collection path
127
+ */
128
+ export function createSubCollectionPath(parentPath: string, subCollectionName: string): string | null {
129
+ if (!isValidCollectionPath(parentPath)) {
130
+ return null;
131
+ }
132
+
133
+ if (!isValidCollectionName(subCollectionName)) {
134
+ return null;
135
+ }
136
+
137
+ return `${parentPath}/${subCollectionName}`;
138
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Collection Entity
3
+ * Single Responsibility: Represent a Firestore collection with metadata
4
+ *
5
+ * Domain entity that encapsulates collection information and metadata.
6
+ * Provides business logic for collection operations.
7
+ *
8
+ * Max lines: 150 (enforced for maintainability)
9
+ */
10
+
11
+ import type { CollectionReference, Query } from 'firebase/firestore';
12
+ import type { Document } from './Document';
13
+
14
+ /**
15
+ * Collection metadata
16
+ */
17
+ export interface CollectionMetadata {
18
+ readonly name: string;
19
+ readonly path: string;
20
+ readonly parentPath?: string;
21
+ }
22
+
23
+ /**
24
+ * Collection entity
25
+ * Represents a Firestore collection with metadata
26
+ */
27
+ export class Collection<TDocument = unknown> {
28
+ readonly name: string;
29
+ readonly path: string;
30
+ readonly parentPath: string | undefined;
31
+ private readonly reference: CollectionReference<TDocument> | Query<TDocument>;
32
+
33
+ constructor(
34
+ reference: CollectionReference<TDocument> | Query<TDocument>,
35
+ metadata: CollectionMetadata
36
+ ) {
37
+ this.reference = reference;
38
+ this.name = metadata.name;
39
+ this.path = metadata.path;
40
+ this.parentPath = metadata.parentPath || undefined;
41
+ }
42
+
43
+ /**
44
+ * Create collection from collection reference
45
+ */
46
+ static fromReference<TDocument = unknown>(
47
+ reference: CollectionReference<TDocument>
48
+ ): Collection<TDocument> {
49
+ return new Collection(reference, {
50
+ name: reference.id,
51
+ path: reference.path,
52
+ parentPath: reference.parent?.path || null,
53
+ });
54
+ }
55
+
56
+ /**
57
+ * Create collection from query
58
+ */
59
+ static fromQuery<TDocument = unknown>(query: Query<TDocument>, name: string, path: string): Collection<TDocument> {
60
+ return new Collection(query, {
61
+ name,
62
+ path,
63
+ parentPath: path.split('/').slice(0, -2).join('/') || null,
64
+ });
65
+ }
66
+
67
+ /**
68
+ * Get collection name
69
+ */
70
+ getName(): string {
71
+ return this.name;
72
+ }
73
+
74
+ /**
75
+ * Get collection path
76
+ */
77
+ getPath(): string {
78
+ return this.path;
79
+ }
80
+
81
+ /**
82
+ * Get parent path if exists
83
+ */
84
+ getParentPath(): string | null {
85
+ return this.parentPath;
86
+ }
87
+
88
+ /**
89
+ * Check if collection is nested (has parent)
90
+ */
91
+ isNested(): boolean {
92
+ return this.parentPath !== null;
93
+ }
94
+
95
+ /**
96
+ * Check if collection is root level (no parent)
97
+ */
98
+ isRootLevel(): boolean {
99
+ return this.parentPath === null;
100
+ }
101
+
102
+ /**
103
+ * Get collection depth in hierarchy
104
+ * Root collections have depth 0
105
+ */
106
+ getDepth(): number {
107
+ if (!this.parentPath) return 0;
108
+ return this.path.split('/').length / 2 - 1;
109
+ }
110
+
111
+ /**
112
+ * Get the underlying reference
113
+ */
114
+ getReference(): CollectionReference<TDocument> | Query<TDocument> {
115
+ return this.reference;
116
+ }
117
+
118
+ /**
119
+ * Check if collection is a query (has filters/limits)
120
+ */
121
+ isQuery(): boolean {
122
+ return 'type' in this.reference && this.reference.type === 'query';
123
+ }
124
+
125
+ /**
126
+ * Check if collection is a collection reference (no filters)
127
+ */
128
+ isCollectionReference(): boolean {
129
+ return !this.isQuery();
130
+ }
131
+
132
+ /**
133
+ * Validate collection name format
134
+ * Collection names must match Firestore requirements
135
+ */
136
+ static isValidName(name: string): boolean {
137
+ // Collection names must be non-empty strings
138
+ if (!name || typeof name !== 'string' || name.trim() === '') {
139
+ return false;
140
+ }
141
+
142
+ // Cannot contain special characters
143
+ const invalidChars = /[\/\\.\s]/;
144
+ if (invalidChars.test(name)) {
145
+ return false;
146
+ }
147
+
148
+ // Cannot start or end with double underscore
149
+ if (name.startsWith('__') || name.endsWith('__')) {
150
+ return false;
@@ -0,0 +1,138 @@
1
+ }
2
+
3
+ // Reasonable length limit
4
+ if (name.length > 100) {
5
+ return false;
6
+ }
7
+
8
+ return true;
9
+ }
10
+
11
+ /**
12
+ * Validate collection path format
13
+ * Paths must follow Firestore path structure
14
+ */
15
+ static isValidPath(path: string): boolean {
16
+ if (!path || typeof path !== 'string' || path.trim() === '') {
17
+ return false;
18
+ }
19
+
20
+ const segments = path.split('/');
21
+ if (segments.length < 2 || segments.length % 2 !== 0) {
22
+ return false;
23
+ }
24
+
25
+ return segments.every(segment => this.isValidName(segment));
26
+ }
27
+
28
+ /**
29
+ * Extract collection name from path
30
+ */
31
+ static extractNameFromPath(path: string): string | null {
32
+ if (!this.isValidPath(path)) {
33
+ return null;
34
+ }
35
+
36
+ const segments = path.split('/');
37
+ return segments[segments.length - 1] || null;
38
+ }
39
+
40
+ /**
41
+ * Extract parent path from collection path
42
+ */
43
+ static extractParentPath(path: string): string | null {
44
+ if (!this.isValidPath(path)) {
45
+ return null;
46
+ }
47
+
48
+ const segments = path.split('/');
49
+ if (segments.length <= 2) {
50
+ return null;
51
+ }
52
+
53
+ return segments.slice(0, -1).join('/');
54
+ }
55
+
56
+ /**
57
+ * Convert to plain object (for serialization)
58
+ */
59
+ toObject(): CollectionMetadata {
60
+ return {
61
+ name: this.name,
62
+ path: this.path,
63
+ parentPath: this.parentPath || undefined,
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Create a sub-collection path
69
+ */
70
+ createSubCollectionPath(subCollectionName: string): string | null {
71
+ if (!Collection.isValidName(subCollectionName)) {
72
+ return null;
73
+ }
74
+
75
+ return `${this.path}/${subCollectionName}`;
76
+ }
77
+
78
+ /**
79
+ * Check if collection is a sub-collection of another
80
+ */
81
+ isSubCollectionOf(other: Collection): boolean {
82
+ return this.parentPath === other.path;
83
+ }
84
+
85
+ /**
86
+ * Check if collection is parent of another
87
+ */
88
+ isParentOf(other: Collection): boolean {
89
+ return other.isSubCollectionOf(this);
90
+ }
91
+
92
+ /**
93
+ * Get collection ID (similar to name but more explicit)
94
+ */
95
+ getId(): string {
96
+ return this.name;
97
+ }
98
+
99
+ /**
100
+ * Check if this is a user collection (users/{userId}/{collection})
101
+ */
102
+ isUserCollection(): boolean {
103
+ return this.parentPath?.startsWith('users/') || false;
104
+ }
105
+
106
+ /**
107
+ * Extract user ID from user collection path
108
+ * Returns null if not a user collection
109
+ */
110
+ extractUserId(): string | null {
111
+ if (!this.isUserCollection()) return null;
112
+
113
+ const segments = this.path.split('/');
114
+ if (segments.length >= 3 && segments[0] === 'users') {
115
+ return segments[1];
116
+ }
117
+
118
+ return null;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Factory function to create collection entity
124
+ */
125
+ export function createCollection<TDocument = unknown>(
126
+ reference: CollectionReference<TDocument> | Query<TDocument>,
127
+ name?: string,
128
+ path?: string
129
+ ): Collection<TDocument> {
130
+ if ('type' in reference && reference.type === 'query') {
131
+ if (!name || !path) {
132
+ throw new Error('name and path are required for query collections');
133
+ }
134
+ return Collection.fromQuery(reference, name, path);
135
+ }
136
+
137
+ return Collection.fromReference(reference as CollectionReference<TDocument>);
138
+ }