firestore-batch-updater 1.0.0 → 1.2.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
@@ -1,5 +1,7 @@
1
1
  # Firestore Batch Updater
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/firestore-batch-updater.svg)](https://www.npmjs.com/package/firestore-batch-updater)
4
+
3
5
  쿼리 기반 필터링과 진행 상황 추적 기능을 제공하는 Firebase Firestore 대량 업데이트 라이브러리입니다.
4
6
 
5
7
  [English](./README.md) | 한국어
@@ -10,7 +12,12 @@
10
12
  - 500개 제한 없음 - Firebase Admin SDK의 BulkWriter 활용
11
13
  - 변경 사항 미리보기 - 업데이트 전 Before/After 비교
12
14
  - 진행 상황 추적 - 실시간 진행률 콜백
13
- - 일괄 생성/Upsert - 여러 문서를 한 번에 생성 또는 upsert
15
+ - 일괄 생성/Upsert/삭제 - 여러 문서를 한 번에 생성, upsert 또는 삭제
16
+ - 정렬 및 제한 - `orderBy()`와 `limit()`으로 정밀한 제어
17
+ - FieldValue 지원 - `increment()`, `arrayUnion()`, `delete()`, `serverTimestamp()` 등 사용 가능
18
+ - 서브컬렉션 & 컬렉션 그룹 - 서브컬렉션 쿼리 또는 동일 이름의 모든 컬렉션 쿼리
19
+ - Dry Run 모드 - 실제 변경 없이 작업 시뮬레이션
20
+ - 문서 개수 조회 - 문서를 로드하지 않고 빠르게 개수 확인
14
21
  - 로그 파일 생성 - 감사를 위한 상세 작업 로그 (선택사항)
15
22
 
16
23
  ## 설치
@@ -71,13 +78,18 @@ console.log(`${result.successCount}개 문서 업데이트 완료`);
71
78
 
72
79
  | 메서드 | 설명 | 반환값 |
73
80
  |--------|------|--------|
74
- | `collection(path)` | 작업할 컬렉션 선택 | `this` |
81
+ | `collection(path)` | 작업할 컬렉션 선택 (서브컬렉션 경로 지원) | `this` |
82
+ | `collectionGroup(id)` | 동일 ID의 모든 컬렉션 쿼리 | `this` |
75
83
  | `where(field, op, value)` | 필터 조건 추가 (체이닝 가능) | `this` |
84
+ | `orderBy(field, direction?)` | 정렬 추가 (체이닝 가능) | `this` |
85
+ | `limit(count)` | 문서 수 제한 (체이닝 가능) | `this` |
86
+ | `count()` | 매칭되는 문서 개수 조회 | `CountResult` |
76
87
  | `preview(data)` | 업데이트 전 미리보기 | `PreviewResult` |
77
88
  | `update(data, options?)` | 매칭되는 문서 업데이트 | `UpdateResult` |
78
89
  | `create(docs, options?)` | 새 문서 생성 | `CreateResult` |
79
90
  | `upsert(data, options?)` | 업데이트 또는 생성 (set with merge) | `UpsertResult` |
80
- | `getFields(field)` | 특정 필드 조회 | `FieldValue[]` |
91
+ | `delete(options?)` | 매칭되는 문서 삭제 | `DeleteResult` |
92
+ | `getFields(field)` | 특정 필드 값 조회 | `FieldValueResult[]` |
81
93
 
82
94
  ### 옵션
83
95
 
@@ -87,7 +99,8 @@ console.log(`${result.successCount}개 문서 업데이트 완료`);
87
99
  {
88
100
  onProgress?: (progress: ProgressInfo) => void;
89
101
  log?: LogOptions;
90
- batchSize?: number; // update/upsert 전용
102
+ batchSize?: number; // update/upsert/delete 전용
103
+ dryRun?: boolean; // update/upsert/delete 전용 - 실제 쓰기 없이 시뮬레이션
91
104
  }
92
105
 
93
106
  // ProgressInfo
@@ -109,15 +122,21 @@ console.log(`${result.successCount}개 문서 업데이트 완료`);
109
122
  - 미설정: 모든 문서를 메모리에 한 번에 로드 (소규모 컬렉션에 적합)
110
123
  - 설정 시 (예: `batchSize: 1000`): 커서 페이지네이션을 사용하여 배치 단위로 처리 (대규모 컬렉션의 메모리 문제 방지)
111
124
 
125
+ **dryRun 옵션:**
126
+ - `true` 설정 시: 실제 변경 없이 `DryRunResult` 반환 (`wouldAffect` 개수와 `sampleIds` 포함)
127
+
112
128
  ### 반환 타입
113
129
 
114
130
  | 타입 | 필드 |
115
131
  |------|------|
132
+ | `CountResult` | `count` |
133
+ | `DryRunResult` | `wouldAffect`, `sampleIds[]`, `operation` |
116
134
  | `PreviewResult` | `affectedCount`, `samples[]`, `affectedFields[]` |
117
135
  | `UpdateResult` | `successCount`, `failureCount`, `totalCount`, `failedDocIds?`, `logFilePath?` |
118
136
  | `CreateResult` | `successCount`, `failureCount`, `totalCount`, `createdIds[]`, `failedDocIds?`, `logFilePath?` |
119
137
  | `UpsertResult` | `successCount`, `failureCount`, `totalCount`, `failedDocIds?`, `logFilePath?` |
120
- | `FieldValue` | `id`, `value` |
138
+ | `DeleteResult` | `successCount`, `failureCount`, `totalCount`, `deletedIds[]`, `failedDocIds?`, `logFilePath?` |
139
+ | `FieldValueResult` | `id`, `value` |
121
140
 
122
141
  ## 사용 예시
123
142
 
@@ -156,6 +175,20 @@ const result = await updater
156
175
  .upsert({ tier: "premium", updatedAt: new Date() });
157
176
  ```
158
177
 
178
+ ### 문서 삭제
179
+
180
+ ```typescript
181
+ // 조건에 맞는 문서 삭제
182
+ const result = await updater
183
+ .collection("users")
184
+ .where("status", "==", "inactive")
185
+ .where("lastLoginAt", "<", ninetyDaysAgo)
186
+ .delete();
187
+
188
+ console.log(`${result.successCount}개 문서 삭제됨`);
189
+ console.log("삭제된 ID:", result.deletedIds);
190
+ ```
191
+
159
192
  ### 업데이트 전 미리보기
160
193
 
161
194
  ```typescript
@@ -215,7 +248,131 @@ const result = await updater
215
248
  .update({ status: "archived" });
216
249
  ```
217
250
 
218
- > **참고:** 서로 다른 필드에 여러 `where()` 조건을 사용할 경우, Firestore에서 [복합 인덱스](https://firebase.google.com/docs/firestore/query-data/indexing)가 필요할 수 있습니다. `FAILED_PRECONDITION` 오류가 발생하면 오류 메시지의 링크를 통해 필요한 인덱스를 생성하세요.
251
+ ### 정렬 제한
252
+
253
+ ```typescript
254
+ // 상위 10명 점수 높은 사용자만 업데이트
255
+ const result = await updater
256
+ .collection("users")
257
+ .where("status", "==", "active")
258
+ .orderBy("score", "desc")
259
+ .limit(10)
260
+ .update({ tier: "premium" });
261
+
262
+ // 가장 오래된 비활성 사용자 100명 삭제
263
+ const deleteResult = await updater
264
+ .collection("users")
265
+ .where("status", "==", "inactive")
266
+ .orderBy("lastLoginAt", "asc")
267
+ .limit(100)
268
+ .delete();
269
+ ```
270
+
271
+ ### FieldValue 사용
272
+
273
+ ```typescript
274
+ import { BatchUpdater, FieldValue } from "firestore-batch-updater";
275
+
276
+ // 숫자 증가
277
+ await updater
278
+ .collection("products")
279
+ .where("id", "==", "product-1")
280
+ .update({ viewCount: FieldValue.increment(1) });
281
+
282
+ // 배열에 항목 추가
283
+ await updater
284
+ .collection("users")
285
+ .where("status", "==", "active")
286
+ .update({ tags: FieldValue.arrayUnion("premium", "verified") });
287
+
288
+ // 배열에서 항목 제거
289
+ await updater
290
+ .collection("users")
291
+ .where("id", "==", "user-1")
292
+ .update({ tags: FieldValue.arrayRemove("inactive") });
293
+
294
+ // 서버 타임스탬프
295
+ await updater
296
+ .collection("users")
297
+ .where("status", "==", "active")
298
+ .update({ updatedAt: FieldValue.serverTimestamp() });
299
+
300
+ // 필드 삭제
301
+ await updater
302
+ .collection("users")
303
+ .where("status", "==", "inactive")
304
+ .update({ temporaryData: FieldValue.delete() });
305
+ ```
306
+
307
+ ### 문서 개수 조회
308
+
309
+ ```typescript
310
+ // 문서를 로드하지 않고 빠르게 개수 조회
311
+ const result = await updater
312
+ .collection("users")
313
+ .where("status", "==", "inactive")
314
+ .count();
315
+
316
+ console.log(`${result.count}명의 비활성 사용자 발견`);
317
+ ```
318
+
319
+ ### Dry Run 모드
320
+
321
+ ```typescript
322
+ // 실제 변경 없이 작업 시뮬레이션
323
+ const simulation = await updater
324
+ .collection("users")
325
+ .where("status", "==", "inactive")
326
+ .update(
327
+ { status: "archived" },
328
+ { dryRun: true }
329
+ );
330
+
331
+ console.log(`${simulation.wouldAffect}개 문서가 영향을 받을 예정`);
332
+ console.log("샘플 ID:", simulation.sampleIds);
333
+
334
+ // 삭제에도 사용 가능
335
+ const deleteSimulation = await updater
336
+ .collection("logs")
337
+ .where("createdAt", "<", thirtyDaysAgo)
338
+ .delete({ dryRun: true });
339
+
340
+ console.log(`${deleteSimulation.wouldAffect}개 문서가 삭제될 예정`);
341
+ ```
342
+
343
+ ### 서브컬렉션
344
+
345
+ ```typescript
346
+ // 특정 서브컬렉션 경로 쿼리
347
+ const result = await updater
348
+ .collection("users/user-123/orders")
349
+ .where("status", "==", "pending")
350
+ .update({ status: "cancelled" });
351
+
352
+ // 동적 경로 사용
353
+ const userId = "user-123";
354
+ await updater
355
+ .collection(`users/${userId}/notifications`)
356
+ .where("read", "==", false)
357
+ .delete();
358
+ ```
359
+
360
+ ### 컬렉션 그룹 쿼리
361
+
362
+ ```typescript
363
+ // 모든 사용자의 "orders" 서브컬렉션을 한 번에 쿼리
364
+ const result = await updater
365
+ .collectionGroup("orders")
366
+ .where("status", "==", "pending")
367
+ .where("createdAt", "<", thirtyDaysAgo)
368
+ .update({ status: "expired" });
369
+
370
+ console.log(`${result.successCount}개 주문 업데이트 완료`);
371
+
372
+ // 참고: collectionGroup은 쿼리 필드에 대한 Firestore 인덱스가 필요합니다
373
+ ```
374
+
375
+ > **참고:** 서로 다른 필드에 여러 `where()` 조건을 사용하거나, `where()`와 `orderBy()`를 다른 필드에 사용할 경우, Firestore에서 [복합 인덱스](https://firebase.google.com/docs/firestore/query-data/indexing)가 필요할 수 있습니다. `FAILED_PRECONDITION` 오류가 발생하면 오류 메시지의 링크를 통해 필요한 인덱스를 생성하세요.
219
376
 
220
377
  ### 에러 처리
221
378
 
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Firestore Batch Updater
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/firestore-batch-updater.svg)](https://www.npmjs.com/package/firestore-batch-updater)
4
+
3
5
  Easy batch updates for Firebase Firestore with query-based filtering and progress tracking.
4
6
 
5
7
  English | [한국어](./README.ko.md)
@@ -10,7 +12,12 @@ English | [한국어](./README.ko.md)
10
12
  - No 500 document limit - Uses Firebase Admin SDK's BulkWriter
11
13
  - Preview changes - See before/after comparison before updating
12
14
  - Progress tracking - Real-time progress callbacks
13
- - Batch create/upsert - Create or upsert multiple documents at once
15
+ - Batch create/upsert/delete - Create, upsert, or delete multiple documents at once
16
+ - Sorting and limiting - Use `orderBy()` and `limit()` for precise control
17
+ - FieldValue support - Use `increment()`, `arrayUnion()`, `delete()`, `serverTimestamp()`, etc.
18
+ - Subcollection & Collection Group - Query subcollections or all collections with the same name
19
+ - Dry run mode - Simulate operations without making changes
20
+ - Count documents - Quickly count matching documents without loading them
14
21
  - Log file generation - Optional detailed operation logs for auditing
15
22
 
16
23
  ## Installation
@@ -71,13 +78,18 @@ console.log(`Updated ${result.successCount} documents`);
71
78
 
72
79
  | Method | Description | Returns |
73
80
  |--------|-------------|---------|
74
- | `collection(path)` | Select collection to operate on | `this` |
81
+ | `collection(path)` | Select collection to operate on (supports subcollection paths) | `this` |
82
+ | `collectionGroup(id)` | Query all collections with the same ID | `this` |
75
83
  | `where(field, op, value)` | Add filter condition (chainable) | `this` |
84
+ | `orderBy(field, direction?)` | Add sorting (chainable) | `this` |
85
+ | `limit(count)` | Limit number of documents (chainable) | `this` |
86
+ | `count()` | Count matching documents | `CountResult` |
76
87
  | `preview(data)` | Preview changes before update | `PreviewResult` |
77
88
  | `update(data, options?)` | Update matching documents | `UpdateResult` |
78
89
  | `create(docs, options?)` | Create new documents | `CreateResult` |
79
90
  | `upsert(data, options?)` | Update or create (set with merge) | `UpsertResult` |
80
- | `getFields(field)` | Get specific field values | `FieldValue[]` |
91
+ | `delete(options?)` | Delete matching documents | `DeleteResult` |
92
+ | `getFields(field)` | Get specific field values | `FieldValueResult[]` |
81
93
 
82
94
  ### Options
83
95
 
@@ -87,7 +99,8 @@ All write operations support an optional `options` parameter:
87
99
  {
88
100
  onProgress?: (progress: ProgressInfo) => void;
89
101
  log?: LogOptions;
90
- batchSize?: number; // For update/upsert only
102
+ batchSize?: number; // For update/upsert/delete
103
+ dryRun?: boolean; // For update/upsert/delete - simulate without writing
91
104
  }
92
105
 
93
106
  // ProgressInfo
@@ -109,15 +122,21 @@ All write operations support an optional `options` parameter:
109
122
  - When not set: All documents are loaded into memory at once (suitable for small collections)
110
123
  - When set (e.g., `batchSize: 1000`): Documents are processed in batches using cursor pagination (suitable for large collections to prevent memory issues)
111
124
 
125
+ **dryRun option:**
126
+ - When `true`: Returns `DryRunResult` with `wouldAffect` count and `sampleIds` without making any changes
127
+
112
128
  ### Return Types
113
129
 
114
130
  | Type | Fields |
115
131
  |------|--------|
132
+ | `CountResult` | `count` |
133
+ | `DryRunResult` | `wouldAffect`, `sampleIds[]`, `operation` |
116
134
  | `PreviewResult` | `affectedCount`, `samples[]`, `affectedFields[]` |
117
135
  | `UpdateResult` | `successCount`, `failureCount`, `totalCount`, `failedDocIds?`, `logFilePath?` |
118
136
  | `CreateResult` | `successCount`, `failureCount`, `totalCount`, `createdIds[]`, `failedDocIds?`, `logFilePath?` |
119
137
  | `UpsertResult` | `successCount`, `failureCount`, `totalCount`, `failedDocIds?`, `logFilePath?` |
120
- | `FieldValue` | `id`, `value` |
138
+ | `DeleteResult` | `successCount`, `failureCount`, `totalCount`, `deletedIds[]`, `failedDocIds?`, `logFilePath?` |
139
+ | `FieldValueResult` | `id`, `value` |
121
140
 
122
141
  ## Usage Examples
123
142
 
@@ -156,6 +175,18 @@ const result = await updater
156
175
  .upsert({ tier: "premium", updatedAt: new Date() });
157
176
  ```
158
177
 
178
+ ### Delete Documents
179
+
180
+ ```typescript
181
+ const result = await updater
182
+ .collection("users")
183
+ .where("status", "==", "inactive")
184
+ .delete();
185
+
186
+ console.log(`Deleted ${result.successCount} documents`);
187
+ console.log("Deleted IDs:", result.deletedIds);
188
+ ```
189
+
159
190
  ### Preview Before Update
160
191
 
161
192
  ```typescript
@@ -215,7 +246,130 @@ const result = await updater
215
246
  .update({ status: "archived" });
216
247
  ```
217
248
 
218
- > **Note:** When using multiple `where()` conditions on different fields, Firestore may require a [composite index](https://firebase.google.com/docs/firestore/query-data/indexing). If you see a `FAILED_PRECONDITION` error, follow the link in the error message to create the required index.
249
+ > **Note:** When using multiple `where()` conditions on different fields, or combining `where()` with `orderBy()` on different fields, Firestore may require a [composite index](https://firebase.google.com/docs/firestore/query-data/indexing). If you see a `FAILED_PRECONDITION` error, follow the link in the error message to create the required index.
250
+
251
+ ### Sorting and Limiting
252
+
253
+ ```typescript
254
+ // Get top 10 users by score
255
+ const result = await updater
256
+ .collection("users")
257
+ .orderBy("score", "desc")
258
+ .limit(10)
259
+ .update({ featured: true });
260
+
261
+ // Delete oldest 100 inactive users
262
+ const deleted = await updater
263
+ .collection("users")
264
+ .where("status", "==", "inactive")
265
+ .orderBy("createdAt", "asc")
266
+ .limit(100)
267
+ .delete();
268
+ ```
269
+
270
+ ### Using FieldValue
271
+
272
+ ```typescript
273
+ import { BatchUpdater, FieldValue } from "firestore-batch-updater";
274
+
275
+ // Increment a counter
276
+ await updater
277
+ .collection("users")
278
+ .where("status", "==", "active")
279
+ .update({ loginCount: FieldValue.increment(1) });
280
+
281
+ // Add to array
282
+ await updater
283
+ .collection("users")
284
+ .where("tier", "==", "premium")
285
+ .update({ tags: FieldValue.arrayUnion("vip", "priority") });
286
+
287
+ // Remove from array
288
+ await updater
289
+ .collection("users")
290
+ .where("status", "==", "inactive")
291
+ .update({ tags: FieldValue.arrayRemove("active") });
292
+
293
+ // Server timestamp
294
+ await updater
295
+ .collection("users")
296
+ .where("status", "==", "active")
297
+ .update({ lastSeen: FieldValue.serverTimestamp() });
298
+
299
+ // Delete a field
300
+ await updater
301
+ .collection("users")
302
+ .where("status", "==", "inactive")
303
+ .update({ temporaryData: FieldValue.delete() });
304
+ ```
305
+
306
+ ### Count Documents
307
+
308
+ ```typescript
309
+ // Quickly count matching documents without loading them
310
+ const result = await updater
311
+ .collection("users")
312
+ .where("status", "==", "inactive")
313
+ .count();
314
+
315
+ console.log(`Found ${result.count} inactive users`);
316
+ ```
317
+
318
+ ### Dry Run Mode
319
+
320
+ ```typescript
321
+ // Simulate an operation without making any changes
322
+ const simulation = await updater
323
+ .collection("users")
324
+ .where("status", "==", "inactive")
325
+ .update(
326
+ { status: "archived" },
327
+ { dryRun: true }
328
+ );
329
+
330
+ console.log(`Would affect ${simulation.wouldAffect} documents`);
331
+ console.log("Sample IDs:", simulation.sampleIds);
332
+
333
+ // Also works with delete
334
+ const deleteSimulation = await updater
335
+ .collection("logs")
336
+ .where("createdAt", "<", thirtyDaysAgo)
337
+ .delete({ dryRun: true });
338
+
339
+ console.log(`Would delete ${deleteSimulation.wouldAffect} documents`);
340
+ ```
341
+
342
+ ### Subcollections
343
+
344
+ ```typescript
345
+ // Query a specific subcollection path
346
+ const result = await updater
347
+ .collection("users/user-123/orders")
348
+ .where("status", "==", "pending")
349
+ .update({ status: "cancelled" });
350
+
351
+ // Or use dynamic paths
352
+ const userId = "user-123";
353
+ await updater
354
+ .collection(`users/${userId}/notifications`)
355
+ .where("read", "==", false)
356
+ .delete();
357
+ ```
358
+
359
+ ### Collection Group Queries
360
+
361
+ ```typescript
362
+ // Query ALL "orders" subcollections across all users
363
+ const result = await updater
364
+ .collectionGroup("orders")
365
+ .where("status", "==", "pending")
366
+ .where("createdAt", "<", thirtyDaysAgo)
367
+ .update({ status: "expired" });
368
+
369
+ console.log(`Updated ${result.successCount} orders across all users`);
370
+
371
+ // Note: collectionGroup requires a Firestore index on the queried fields
372
+ ```
219
373
 
220
374
  ### Error Handling
221
375
 
package/dist/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { WhereFilterOp, Firestore } from 'firebase-admin/firestore';
2
+ export { FieldValue } from 'firebase-admin/firestore';
2
3
 
3
4
  /**
4
5
  * Firestore Batch Updater Type Definitions
@@ -31,6 +32,11 @@ interface UpdateOptions {
31
32
  * When not set, all documents are loaded at once
32
33
  */
33
34
  batchSize?: number;
35
+ /**
36
+ * Dry run mode - simulate the operation without actually writing
37
+ * Returns what would happen without making any changes
38
+ */
39
+ dryRun?: boolean;
34
40
  }
35
41
  /**
36
42
  * Result of batch update operation
@@ -66,9 +72,16 @@ interface WhereCondition {
66
72
  value: any;
67
73
  }
68
74
  /**
69
- * Field value result
75
+ * OrderBy clause condition
76
+ */
77
+ interface OrderByCondition {
78
+ field: string;
79
+ direction: "asc" | "desc";
80
+ }
81
+ /**
82
+ * Field value result from getFields()
70
83
  */
71
- interface FieldValue {
84
+ interface FieldValueResult {
72
85
  id: string;
73
86
  value: any;
74
87
  }
@@ -122,6 +135,11 @@ interface UpsertOptions {
122
135
  * When not set, all documents are loaded at once
123
136
  */
124
137
  batchSize?: number;
138
+ /**
139
+ * Dry run mode - simulate the operation without actually writing
140
+ * Returns what would happen without making any changes
141
+ */
142
+ dryRun?: boolean;
125
143
  }
126
144
  /**
127
145
  * Result of batch upsert operation
@@ -132,6 +150,55 @@ interface UpsertResult {
132
150
  totalCount: number;
133
151
  failedDocIds?: string[];
134
152
  }
153
+ /**
154
+ * Options for delete operations
155
+ */
156
+ interface DeleteOptions {
157
+ /**
158
+ * Callback function for progress updates
159
+ * @param progress - Current progress information
160
+ */
161
+ onProgress?: (progress: ProgressInfo) => void;
162
+ /**
163
+ * Log file generation options
164
+ */
165
+ log?: LogOptions;
166
+ /**
167
+ * Batch size for pagination (optional)
168
+ * When set, documents are processed in batches to prevent memory issues with large collections
169
+ * When not set, all documents are loaded at once
170
+ */
171
+ batchSize?: number;
172
+ /**
173
+ * Dry run mode - simulate the operation without actually writing
174
+ * Returns what would happen without making any changes
175
+ */
176
+ dryRun?: boolean;
177
+ }
178
+ /**
179
+ * Result of batch delete operation
180
+ */
181
+ interface DeleteResult {
182
+ successCount: number;
183
+ failureCount: number;
184
+ totalCount: number;
185
+ deletedIds: string[];
186
+ failedDocIds?: string[];
187
+ }
188
+ /**
189
+ * Result of count operation
190
+ */
191
+ interface CountResult {
192
+ count: number;
193
+ }
194
+ /**
195
+ * Result of dry run operation
196
+ */
197
+ interface DryRunResult {
198
+ wouldAffect: number;
199
+ sampleIds: string[];
200
+ operation: "update" | "upsert" | "delete";
201
+ }
135
202
  /**
136
203
  * Log options for batch operations
137
204
  */
@@ -153,7 +220,7 @@ interface LogEntry {
153
220
  * Complete log data for an operation
154
221
  */
155
222
  interface OperationLog {
156
- operation: "update" | "create" | "upsert";
223
+ operation: "update" | "create" | "upsert" | "delete";
157
224
  collection: string;
158
225
  startedAt: string;
159
226
  completedAt: string;
@@ -177,7 +244,10 @@ interface OperationLog {
177
244
  declare class BatchUpdater {
178
245
  private firestore;
179
246
  private collectionPath?;
247
+ private isCollectionGroup;
180
248
  private conditions;
249
+ private orderByConditions;
250
+ private limitCount?;
181
251
  /**
182
252
  * Create a new BatchUpdater instance
183
253
  * @param firestore - Initialized Firestore instance from firebase-admin
@@ -185,10 +255,17 @@ declare class BatchUpdater {
185
255
  constructor(firestore: Firestore);
186
256
  /**
187
257
  * Select a collection to operate on
258
+ * Supports subcollection paths like "users/userId/orders"
188
259
  * @param path - Collection path
189
260
  * @returns This instance for chaining
190
261
  */
191
262
  collection(path: string): this;
263
+ /**
264
+ * Select a collection group to operate on (queries across all subcollections with the same name)
265
+ * @param collectionId - Collection ID (not a path, just the collection name)
266
+ * @returns This instance for chaining
267
+ */
268
+ collectionGroup(collectionId: string): this;
192
269
  /**
193
270
  * Add a where condition to filter documents
194
271
  * @param field - Field path
@@ -197,6 +274,24 @@ declare class BatchUpdater {
197
274
  * @returns This instance for chaining
198
275
  */
199
276
  where(field: string, operator: WhereFilterOp, value: any): this;
277
+ /**
278
+ * Add an orderBy clause to sort documents
279
+ * @param field - Field path to sort by
280
+ * @param direction - Sort direction ('asc' or 'desc'), defaults to 'asc'
281
+ * @returns This instance for chaining
282
+ */
283
+ orderBy(field: string, direction?: "asc" | "desc"): this;
284
+ /**
285
+ * Limit the number of documents to process
286
+ * @param count - Maximum number of documents
287
+ * @returns This instance for chaining
288
+ */
289
+ limit(count: number): this;
290
+ /**
291
+ * Count documents matching the query conditions
292
+ * @returns Count result with number of matching documents
293
+ */
294
+ count(): Promise<CountResult>;
200
295
  /**
201
296
  * Preview changes before executing update
202
297
  * @param updateData - Data to update
@@ -206,20 +301,21 @@ declare class BatchUpdater {
206
301
  /**
207
302
  * Execute batch update operation
208
303
  * @param updateData - Data to update
209
- * @param options - Update options (e.g., progress callback, log options, batchSize for pagination)
210
- * @returns Update result with success/failure counts and optional log file path
304
+ * @param options - Update options (e.g., progress callback, log options, batchSize for pagination, dryRun)
305
+ * @returns Update result with success/failure counts and optional log file path, or DryRunResult if dryRun is true
211
306
  */
212
- update(updateData: Record<string, any>, options?: UpdateOptions): Promise<UpdateResult & {
307
+ update(updateData: Record<string, any>, options?: UpdateOptions): Promise<(UpdateResult & {
213
308
  logFilePath?: string;
214
- }>;
309
+ }) | DryRunResult>;
215
310
  /**
216
311
  * Get specific field values from matching documents
217
312
  * @param fieldPath - Field path to retrieve
218
313
  * @returns Array of field values with document IDs
219
314
  */
220
- getFields(fieldPath: string): Promise<FieldValue[]>;
315
+ getFields(fieldPath: string): Promise<FieldValueResult[]>;
221
316
  /**
222
317
  * Create multiple documents in batch
318
+ * Note: This method does not work with collectionGroup()
223
319
  * @param documents - Array of documents to create
224
320
  * @param options - Create options (e.g., progress callback, log options)
225
321
  * @returns Create result with success/failure counts, created IDs, and optional log file path
@@ -231,12 +327,20 @@ declare class BatchUpdater {
231
327
  * Upsert documents matching query conditions
232
328
  * Updates existing documents or creates them if they don't exist
233
329
  * @param updateData - Data to set/merge
234
- * @param options - Upsert options (e.g., progress callback, log options, batchSize for pagination)
235
- * @returns Upsert result with success/failure counts and optional log file path
330
+ * @param options - Upsert options (e.g., progress callback, log options, batchSize for pagination, dryRun)
331
+ * @returns Upsert result with success/failure counts and optional log file path, or DryRunResult if dryRun is true
236
332
  */
237
- upsert(updateData: Record<string, any>, options?: UpsertOptions): Promise<UpsertResult & {
333
+ upsert(updateData: Record<string, any>, options?: UpsertOptions): Promise<(UpsertResult & {
238
334
  logFilePath?: string;
239
- }>;
335
+ }) | DryRunResult>;
336
+ /**
337
+ * Delete documents matching query conditions
338
+ * @param options - Delete options (e.g., progress callback, log options, batchSize for pagination, dryRun)
339
+ * @returns Delete result with success/failure counts, deleted IDs, and optional log file path, or DryRunResult if dryRun is true
340
+ */
341
+ delete(options?: DeleteOptions): Promise<(DeleteResult & {
342
+ logFilePath?: string;
343
+ }) | DryRunResult>;
240
344
  /**
241
345
  * Validate that collection is set
242
346
  * @private
@@ -269,7 +373,7 @@ declare function writeOperationLog(log: OperationLog, options: LogOptions): stri
269
373
  /**
270
374
  * Create a log collector for tracking operation entries
271
375
  */
272
- declare function createLogCollector(operation: "update" | "create" | "upsert", collection: string, conditions?: WhereCondition[], updateData?: Record<string, any>): {
376
+ declare function createLogCollector(operation: "update" | "create" | "upsert" | "delete", collection: string, conditions?: WhereCondition[], updateData?: Record<string, any>): {
273
377
  addEntry: (documentId: string, status: "success" | "failure", error?: string) => void;
274
378
  finalize: (options: LogOptions) => string;
275
379
  getLog: () => OperationLog;
@@ -313,4 +417,4 @@ declare function isValidUpdateData(value: any): value is Record<string, any>;
313
417
  */
314
418
  declare function formatError(error: unknown, context?: string): string;
315
419
 
316
- export { BatchUpdater, type CreateDocumentInput, type CreateOptions, type CreateResult, type DocumentSnapshot, type FieldValue, type LogEntry, type LogOptions, type OperationLog, type PreviewResult, type ProgressInfo, type UpdateOptions, type UpdateResult, type UpsertOptions, type UpsertResult, type WhereCondition, calculateProgress, createLogCollector, formatError, formatOperationLog, getAffectedFields, isValidUpdateData, mergeUpdateData, writeOperationLog };
420
+ export { BatchUpdater, type CountResult, type CreateDocumentInput, type CreateOptions, type CreateResult, type DeleteOptions, type DeleteResult, type DocumentSnapshot, type DryRunResult, type FieldValueResult, type LogEntry, type LogOptions, type OperationLog, type OrderByCondition, type PreviewResult, type ProgressInfo, type UpdateOptions, type UpdateResult, type UpsertOptions, type UpsertResult, type WhereCondition, calculateProgress, createLogCollector, formatError, formatOperationLog, getAffectedFields, isValidUpdateData, mergeUpdateData, writeOperationLog };