firestore-batch-updater 1.17.0 → 1.19.0

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/README.ko.md CHANGED
@@ -24,12 +24,14 @@
24
24
  - 통합 통계 - `fieldStats()`로 sum/avg/min/max/count 한 번에 조회
25
25
  - 커서 페이지네이션 - `paginate()`로 메모리 효율적인 페이지 단위 조회
26
26
  - ID 직접 조회 - `getOne()`으로 문서 ID로 빠른 조회
27
+ - 문서 ID 존재 확인 - `has(id)`로 데이터 읽기 없이 특정 문서 ID 존재 여부 확인
27
28
  - 벌크 작업 - `bulkCreate()`, `bulkUpdate()`, `bulkDelete()`로 여러 문서에 각기 다른 데이터로 효율적 처리
28
29
  - 문서 변환 - `transform()`으로 각 문서에 커스텀 로직 적용 (가격 인상, 데이터 마이그레이션 등)
29
30
  - 복사 & 이동 - `copyTo()`로 컬렉션 간 문서 복사/이동 (데이터 변환 옵션 포함)
30
31
  - 고유값 조회 - `distinct()`로 특정 필드의 중복 없는 값 목록 조회
31
32
  - JSON 내보내기/가져오기 - `toJSON()` / `fromJSON()`으로 문서 JSON 파일 내보내기/가져오기
32
33
  - 그룹별 개수 조회 - `countBy()`로 특정 필드 값별 문서 수 집계
34
+ - 그룹별 문서 조회 - `groupBy()`로 필드 값별로 문서 그룹핑
33
35
  - 랜덤 샘플링 - `sample()`로 쿼리 결과에서 랜덤 문서 추출
34
36
  - 필드 값 추출 - `pluck()`로 특정 필드 값만 간단하게 배열로 추출
35
37
  - 문서 ID 추출 - `pluckIds()`로 매칭 문서의 ID만 배열로 추출
@@ -108,6 +110,7 @@ console.log(`${result.successCount}개 문서 업데이트 완료`);
108
110
  | `isEmpty()` | 매칭되는 문서가 없는지 확인 | `boolean` |
109
111
  | `findOne()` | 첫 번째 매칭 문서 조회 | `{ id, data } \| null` |
110
112
  | `getOne(id)` | ID로 문서 직접 조회 | `{ id, data } \| null` |
113
+ | `has(id)` | 문서 ID 존재 여부 확인 | `boolean` |
111
114
  | `getAll()` | 모든 매칭 문서 조회 | `{ id, data }[]` |
112
115
  | `preview(data)` | 업데이트 전 미리보기 | `PreviewResult` |
113
116
  | `update(data, options?)` | 매칭되는 문서 업데이트 | `UpdateResult` |
@@ -136,6 +139,7 @@ console.log(`${result.successCount}개 문서 업데이트 완료`);
136
139
  | `toJSON(path, options?)` | 문서를 JSON 파일로 내보내기 | `ToJSONResult` |
137
140
  | `fromJSON(path, options?)` | JSON 파일에서 문서 가져오기 | `FromJSONResult` |
138
141
  | `countBy(field)` | 필드 값별 문서 수 집계 | `CountByResult` |
142
+ | `groupBy(field)` | 필드 값별 문서 그룹핑 | `GroupByResult` |
139
143
  | `getFields(field)` | 특정 필드 값 조회 | `FieldValueResult[]` |
140
144
 
141
145
  ### 옵션
@@ -194,6 +198,7 @@ console.log(`${result.successCount}개 문서 업데이트 완료`);
194
198
  | `ToJSONResult` | `filePath`, `documentCount` |
195
199
  | `FromJSONResult` | `successCount`, `failureCount`, `totalCount`, `createdIds[]`, `failedDocIds?`, `logFilePath?` |
196
200
  | `CountByResult` | `{ [value]: number }` |
201
+ | `GroupByResult` | `{ [value]: { id, data }[] }` |
197
202
  | `FieldValueResult` | `id`, `value` |
198
203
 
199
204
  ## 사용 예시
@@ -643,6 +648,24 @@ const order = await updater
643
648
  .getOne("order-456");
644
649
  ```
645
650
 
651
+ ### 문서 ID 존재 확인
652
+
653
+ ```typescript
654
+ // 특정 문서 ID가 존재하는지 확인 (데이터 읽기 없이 효율적)
655
+ const exists = await updater.collection("users").has("user-123");
656
+
657
+ if (exists) {
658
+ console.log("사용자가 존재합니다!");
659
+ } else {
660
+ console.log("사용자를 찾을 수 없음");
661
+ }
662
+
663
+ // 가드 절에 유용
664
+ if (!(await updater.collection("users").has(userId))) {
665
+ throw new Error("사용자를 찾을 수 없습니다");
666
+ }
667
+ ```
668
+
646
669
  ### 벌크 업데이트
647
670
 
648
671
  ```typescript
@@ -812,6 +835,33 @@ const countryCounts = await updater.collection("users").countBy("address.country
812
835
  console.log(countryCounts); // { US: 80, KR: 45, JP: 25 }
813
836
  ```
814
837
 
838
+ ### 필드 값별 문서 그룹핑
839
+
840
+ ```typescript
841
+ // 필드 값별로 문서를 그룹핑 (전체 문서 데이터 포함)
842
+ const usersByRole = await updater.collection("users").groupBy("role");
843
+
844
+ console.log(`관리자: ${usersByRole.admin.length}명`);
845
+ usersByRole.admin.forEach(user => {
846
+ console.log(`- ${user.id}: ${user.data.name}`);
847
+ });
848
+
849
+ // where 필터와 함께 사용
850
+ const activeProducts = await updater
851
+ .collection("products")
852
+ .where("status", "==", "active")
853
+ .groupBy("category");
854
+
855
+ for (const [category, products] of Object.entries(activeProducts)) {
856
+ console.log(`${category}: ${products.length}개`);
857
+ }
858
+
859
+ // 중첩 필드 지원
860
+ const usersByCountry = await updater
861
+ .collection("users")
862
+ .groupBy("address.country");
863
+ ```
864
+
815
865
  ### JSON 가져오기
816
866
 
817
867
  ```typescript
package/README.md CHANGED
@@ -24,12 +24,14 @@ English | [한국어](./README.ko.md)
24
24
  - Combined stats - Use `fieldStats()` to get sum/avg/min/max/count in one call
25
25
  - Cursor pagination - Use `paginate()` for memory-efficient page-by-page iteration
26
26
  - Direct ID lookup - Use `getOne()` for fast document retrieval by ID
27
+ - Document ID check - Use `has(id)` to check if a specific document ID exists without reading data
27
28
  - Bulk operations - Use `bulkCreate()`, `bulkUpdate()`, `bulkDelete()` for efficient multi-document operations with different data each
28
29
  - Transform - Use `transform()` to apply custom logic to each document (e.g., price increase, data migration)
29
30
  - Copy & Move - Use `copyTo()` to copy/move documents between collections with optional data transformation
30
31
  - Distinct values - Use `distinct()` to get unique field values from matching documents
31
32
  - JSON export/import - Use `toJSON()` / `fromJSON()` to export/import documents as JSON
32
33
  - Group counting - Use `countBy()` to count documents grouped by field value
34
+ - Group documents - Use `groupBy()` to group matching documents by a field value
33
35
  - Random sampling - Use `sample()` to get random documents from query results
34
36
  - Field value extraction - Use `pluck()` to get a simple array of field values
35
37
  - Document ID extraction - Use `pluckIds()` to get an array of matching document IDs
@@ -108,6 +110,7 @@ console.log(`Updated ${result.successCount} documents`);
108
110
  | `isEmpty()` | Check if no matching documents exist | `boolean` |
109
111
  | `findOne()` | Find first matching document | `{ id, data } \| null` |
110
112
  | `getOne(id)` | Get document by ID directly | `{ id, data } \| null` |
113
+ | `has(id)` | Check if document ID exists | `boolean` |
111
114
  | `getAll()` | Get all matching documents | `{ id, data }[]` |
112
115
  | `preview(data)` | Preview changes before update | `PreviewResult` |
113
116
  | `update(data, options?)` | Update matching documents | `UpdateResult` |
@@ -136,6 +139,7 @@ console.log(`Updated ${result.successCount} documents`);
136
139
  | `toJSON(path, options?)` | Export documents to JSON file | `ToJSONResult` |
137
140
  | `fromJSON(path, options?)` | Import documents from JSON file | `FromJSONResult` |
138
141
  | `countBy(field)` | Count documents grouped by field value | `CountByResult` |
142
+ | `groupBy(field)` | Group documents by field value | `GroupByResult` |
139
143
  | `getFields(field)` | Get specific field values | `FieldValueResult[]` |
140
144
 
141
145
  ### Options
@@ -194,6 +198,7 @@ All write operations support an optional `options` parameter:
194
198
  | `ToJSONResult` | `filePath`, `documentCount` |
195
199
  | `FromJSONResult` | `successCount`, `failureCount`, `totalCount`, `createdIds[]`, `failedDocIds?`, `logFilePath?` |
196
200
  | `CountByResult` | `{ [value]: number }` |
201
+ | `GroupByResult` | `{ [value]: { id, data }[] }` |
197
202
  | `FieldValueResult` | `id`, `value` |
198
203
 
199
204
  ## Usage Examples
@@ -662,6 +667,24 @@ const profile = await updater
662
667
  .getOne("user-123");
663
668
  ```
664
669
 
670
+ ### Check Document Exists by ID
671
+
672
+ ```typescript
673
+ // Check if a specific document ID exists (without reading data)
674
+ const exists = await updater.collection("users").has("user-123");
675
+
676
+ if (exists) {
677
+ console.log("User exists!");
678
+ } else {
679
+ console.log("User not found");
680
+ }
681
+
682
+ // Useful for guard clauses before operations
683
+ if (!(await updater.collection("users").has(userId))) {
684
+ throw new Error("User not found");
685
+ }
686
+ ```
687
+
665
688
  ### Bulk Update with Different Data
666
689
 
667
690
  ```typescript
@@ -825,6 +848,33 @@ const countryCounts = await updater.collection("users").countBy("address.country
825
848
  console.log(countryCounts); // { US: 80, KR: 45, JP: 25 }
826
849
  ```
827
850
 
851
+ ### Group Documents by Field Value
852
+
853
+ ```typescript
854
+ // Group documents by a field value (with full document data)
855
+ const usersByRole = await updater.collection("users").groupBy("role");
856
+
857
+ console.log(`Admins: ${usersByRole.admin.length}`);
858
+ usersByRole.admin.forEach(user => {
859
+ console.log(`- ${user.id}: ${user.data.name}`);
860
+ });
861
+
862
+ // With where filter
863
+ const activeProducts = await updater
864
+ .collection("products")
865
+ .where("status", "==", "active")
866
+ .groupBy("category");
867
+
868
+ for (const [category, products] of Object.entries(activeProducts)) {
869
+ console.log(`${category}: ${products.length} products`);
870
+ }
871
+
872
+ // Nested field support
873
+ const usersByCountry = await updater
874
+ .collection("users")
875
+ .groupBy("address.country");
876
+ ```
877
+
828
878
  ### Import from JSON
829
879
 
830
880
  ```typescript
package/dist/index.d.mts CHANGED
@@ -472,6 +472,16 @@ interface FieldStatsResult {
472
472
  interface CountByResult {
473
473
  [value: string]: number;
474
474
  }
475
+ /**
476
+ * Result of groupBy operation
477
+ * Field value → array of matching documents
478
+ */
479
+ interface GroupByResult {
480
+ [value: string]: {
481
+ id: string;
482
+ data: Record<string, any>;
483
+ }[];
484
+ }
475
485
  /**
476
486
  * Options for fromJSON operation
477
487
  */
@@ -619,6 +629,12 @@ declare class BatchUpdater {
619
629
  id: string;
620
630
  data: Record<string, any>;
621
631
  } | null>;
632
+ /**
633
+ * Check if a document with the given ID exists in the collection
634
+ * @param id - Document ID to check
635
+ * @returns true if the document exists, false otherwise
636
+ */
637
+ has(id: string): Promise<boolean>;
622
638
  /**
623
639
  * Update the first document matching the query conditions
624
640
  * @param updateData - Data to update
@@ -811,6 +827,12 @@ declare class BatchUpdater {
811
827
  * @returns Object mapping field values to their document counts
812
828
  */
813
829
  countBy(field: string): Promise<CountByResult>;
830
+ /**
831
+ * Group matching documents by a specific field value
832
+ * @param field - Field path to group by
833
+ * @returns Object mapping field values to arrays of matching documents { id, data }
834
+ */
835
+ groupBy(field: string): Promise<GroupByResult>;
814
836
  /**
815
837
  * Import documents from a JSON file into Firestore
816
838
  * @param filePath - Path to the JSON file to import
@@ -915,4 +937,4 @@ declare function isValidUpdateData(value: any): value is Record<string, any>;
915
937
  */
916
938
  declare function formatError(error: unknown, context?: string): string;
917
939
 
918
- export { type AggregateResult, type AggregateSpec, BatchUpdater, type BulkCreateInput, type BulkCreateOptions, type BulkCreateResult, type BulkDeleteOptions, type BulkDeleteResult, type BulkUpdateInput, type BulkUpdateOptions, type BulkUpdateResult, type CopyToOptions, type CopyToResult, type CountByResult, type CountResult, type CreateDocumentInput, type CreateOneResult, type CreateOptions, type CreateResult, type DeleteOptions, type DeleteResult, type DocumentSnapshot, type DryRunResult, type FieldStatsResult, type FieldValueResult, type FromJSONOptions, type FromJSONResult, type LogEntry, type LogOptions, type OperationLog, type OrderByCondition, type PaginateOptions, type PaginateResult, type PreviewResult, type ProgressInfo, type ToJSONOptions, type ToJSONResult, type TransformFn, type TransformOptions, type TransformResult, type UpdateOptions, type UpdateResult, type UpsertOptions, type UpsertResult, type WhereCondition, calculateProgress, createLogCollector, formatError, formatOperationLog, getAffectedFields, isValidUpdateData, mergeUpdateData, writeOperationLog };
940
+ export { type AggregateResult, type AggregateSpec, BatchUpdater, type BulkCreateInput, type BulkCreateOptions, type BulkCreateResult, type BulkDeleteOptions, type BulkDeleteResult, type BulkUpdateInput, type BulkUpdateOptions, type BulkUpdateResult, type CopyToOptions, type CopyToResult, type CountByResult, type CountResult, type CreateDocumentInput, type CreateOneResult, type CreateOptions, type CreateResult, type DeleteOptions, type DeleteResult, type DocumentSnapshot, type DryRunResult, type FieldStatsResult, type FieldValueResult, type FromJSONOptions, type FromJSONResult, type GroupByResult, type LogEntry, type LogOptions, type OperationLog, type OrderByCondition, type PaginateOptions, type PaginateResult, type PreviewResult, type ProgressInfo, type ToJSONOptions, type ToJSONResult, type TransformFn, type TransformOptions, type TransformResult, type UpdateOptions, type UpdateResult, type UpsertOptions, type UpsertResult, type WhereCondition, calculateProgress, createLogCollector, formatError, formatOperationLog, getAffectedFields, isValidUpdateData, mergeUpdateData, writeOperationLog };
package/dist/index.d.ts CHANGED
@@ -472,6 +472,16 @@ interface FieldStatsResult {
472
472
  interface CountByResult {
473
473
  [value: string]: number;
474
474
  }
475
+ /**
476
+ * Result of groupBy operation
477
+ * Field value → array of matching documents
478
+ */
479
+ interface GroupByResult {
480
+ [value: string]: {
481
+ id: string;
482
+ data: Record<string, any>;
483
+ }[];
484
+ }
475
485
  /**
476
486
  * Options for fromJSON operation
477
487
  */
@@ -619,6 +629,12 @@ declare class BatchUpdater {
619
629
  id: string;
620
630
  data: Record<string, any>;
621
631
  } | null>;
632
+ /**
633
+ * Check if a document with the given ID exists in the collection
634
+ * @param id - Document ID to check
635
+ * @returns true if the document exists, false otherwise
636
+ */
637
+ has(id: string): Promise<boolean>;
622
638
  /**
623
639
  * Update the first document matching the query conditions
624
640
  * @param updateData - Data to update
@@ -811,6 +827,12 @@ declare class BatchUpdater {
811
827
  * @returns Object mapping field values to their document counts
812
828
  */
813
829
  countBy(field: string): Promise<CountByResult>;
830
+ /**
831
+ * Group matching documents by a specific field value
832
+ * @param field - Field path to group by
833
+ * @returns Object mapping field values to arrays of matching documents { id, data }
834
+ */
835
+ groupBy(field: string): Promise<GroupByResult>;
814
836
  /**
815
837
  * Import documents from a JSON file into Firestore
816
838
  * @param filePath - Path to the JSON file to import
@@ -915,4 +937,4 @@ declare function isValidUpdateData(value: any): value is Record<string, any>;
915
937
  */
916
938
  declare function formatError(error: unknown, context?: string): string;
917
939
 
918
- export { type AggregateResult, type AggregateSpec, BatchUpdater, type BulkCreateInput, type BulkCreateOptions, type BulkCreateResult, type BulkDeleteOptions, type BulkDeleteResult, type BulkUpdateInput, type BulkUpdateOptions, type BulkUpdateResult, type CopyToOptions, type CopyToResult, type CountByResult, type CountResult, type CreateDocumentInput, type CreateOneResult, type CreateOptions, type CreateResult, type DeleteOptions, type DeleteResult, type DocumentSnapshot, type DryRunResult, type FieldStatsResult, type FieldValueResult, type FromJSONOptions, type FromJSONResult, type LogEntry, type LogOptions, type OperationLog, type OrderByCondition, type PaginateOptions, type PaginateResult, type PreviewResult, type ProgressInfo, type ToJSONOptions, type ToJSONResult, type TransformFn, type TransformOptions, type TransformResult, type UpdateOptions, type UpdateResult, type UpsertOptions, type UpsertResult, type WhereCondition, calculateProgress, createLogCollector, formatError, formatOperationLog, getAffectedFields, isValidUpdateData, mergeUpdateData, writeOperationLog };
940
+ export { type AggregateResult, type AggregateSpec, BatchUpdater, type BulkCreateInput, type BulkCreateOptions, type BulkCreateResult, type BulkDeleteOptions, type BulkDeleteResult, type BulkUpdateInput, type BulkUpdateOptions, type BulkUpdateResult, type CopyToOptions, type CopyToResult, type CountByResult, type CountResult, type CreateDocumentInput, type CreateOneResult, type CreateOptions, type CreateResult, type DeleteOptions, type DeleteResult, type DocumentSnapshot, type DryRunResult, type FieldStatsResult, type FieldValueResult, type FromJSONOptions, type FromJSONResult, type GroupByResult, type LogEntry, type LogOptions, type OperationLog, type OrderByCondition, type PaginateOptions, type PaginateResult, type PreviewResult, type ProgressInfo, type ToJSONOptions, type ToJSONResult, type TransformFn, type TransformOptions, type TransformResult, type UpdateOptions, type UpdateResult, type UpsertOptions, type UpsertResult, type WhereCondition, calculateProgress, createLogCollector, formatError, formatOperationLog, getAffectedFields, isValidUpdateData, mergeUpdateData, writeOperationLog };
package/dist/index.js CHANGED
@@ -373,6 +373,25 @@ var BatchUpdater = class {
373
373
  data: docSnapshot.data()
374
374
  };
375
375
  }
376
+ /**
377
+ * Check if a document with the given ID exists in the collection
378
+ * @param id - Document ID to check
379
+ * @returns true if the document exists, false otherwise
380
+ */
381
+ async has(id) {
382
+ this.validateSetup();
383
+ if (!id || typeof id !== "string") {
384
+ throw new Error("Document ID is required");
385
+ }
386
+ if (this.isCollectionGroup) {
387
+ throw new Error(
388
+ "has() cannot be used with collectionGroup(). Use exists() with where conditions instead."
389
+ );
390
+ }
391
+ const docRef = this.firestore.collection(this.collectionPath).doc(id);
392
+ const docSnapshot = await docRef.get();
393
+ return docSnapshot.exists;
394
+ }
376
395
  /**
377
396
  * Update the first document matching the query conditions
378
397
  * @param updateData - Data to update
@@ -1450,6 +1469,31 @@ var BatchUpdater = class {
1450
1469
  }
1451
1470
  return counts;
1452
1471
  }
1472
+ /**
1473
+ * Group matching documents by a specific field value
1474
+ * @param field - Field path to group by
1475
+ * @returns Object mapping field values to arrays of matching documents { id, data }
1476
+ */
1477
+ async groupBy(field) {
1478
+ this.validateSetup();
1479
+ if (!field || typeof field !== "string") {
1480
+ throw new Error("Field path is required");
1481
+ }
1482
+ const query = this.buildQuery();
1483
+ const snapshot = await query.get();
1484
+ const groups = {};
1485
+ for (const doc of snapshot.docs) {
1486
+ const data = doc.data();
1487
+ const value = this.getNestedValue(data, field);
1488
+ if (value === void 0 || value === null) continue;
1489
+ const key = String(value);
1490
+ if (!groups[key]) {
1491
+ groups[key] = [];
1492
+ }
1493
+ groups[key].push({ id: doc.id, data });
1494
+ }
1495
+ return groups;
1496
+ }
1453
1497
  /**
1454
1498
  * Import documents from a JSON file into Firestore
1455
1499
  * @param filePath - Path to the JSON file to import
package/dist/index.mjs CHANGED
@@ -328,6 +328,25 @@ var BatchUpdater = class {
328
328
  data: docSnapshot.data()
329
329
  };
330
330
  }
331
+ /**
332
+ * Check if a document with the given ID exists in the collection
333
+ * @param id - Document ID to check
334
+ * @returns true if the document exists, false otherwise
335
+ */
336
+ async has(id) {
337
+ this.validateSetup();
338
+ if (!id || typeof id !== "string") {
339
+ throw new Error("Document ID is required");
340
+ }
341
+ if (this.isCollectionGroup) {
342
+ throw new Error(
343
+ "has() cannot be used with collectionGroup(). Use exists() with where conditions instead."
344
+ );
345
+ }
346
+ const docRef = this.firestore.collection(this.collectionPath).doc(id);
347
+ const docSnapshot = await docRef.get();
348
+ return docSnapshot.exists;
349
+ }
331
350
  /**
332
351
  * Update the first document matching the query conditions
333
352
  * @param updateData - Data to update
@@ -1405,6 +1424,31 @@ var BatchUpdater = class {
1405
1424
  }
1406
1425
  return counts;
1407
1426
  }
1427
+ /**
1428
+ * Group matching documents by a specific field value
1429
+ * @param field - Field path to group by
1430
+ * @returns Object mapping field values to arrays of matching documents { id, data }
1431
+ */
1432
+ async groupBy(field) {
1433
+ this.validateSetup();
1434
+ if (!field || typeof field !== "string") {
1435
+ throw new Error("Field path is required");
1436
+ }
1437
+ const query = this.buildQuery();
1438
+ const snapshot = await query.get();
1439
+ const groups = {};
1440
+ for (const doc of snapshot.docs) {
1441
+ const data = doc.data();
1442
+ const value = this.getNestedValue(data, field);
1443
+ if (value === void 0 || value === null) continue;
1444
+ const key = String(value);
1445
+ if (!groups[key]) {
1446
+ groups[key] = [];
1447
+ }
1448
+ groups[key].push({ id: doc.id, data });
1449
+ }
1450
+ return groups;
1451
+ }
1408
1452
  /**
1409
1453
  * Import documents from a JSON file into Firestore
1410
1454
  * @param filePath - Path to the JSON file to import
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firestore-batch-updater",
3
- "version": "1.17.0",
3
+ "version": "1.19.0",
4
4
  "description": "Batch update Firestore documents with query-based filtering and preview",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",