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 +163 -6
- package/README.md +160 -6
- package/dist/index.d.mts +118 -14
- package/dist/index.d.ts +118 -14
- package/dist/index.js +239 -7
- package/dist/index.mjs +238 -7
- package/package.json +1 -1
package/README.ko.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Firestore Batch Updater
|
|
2
2
|
|
|
3
|
+
[](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 - 여러 문서를 한 번에
|
|
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
|
-
| `
|
|
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
|
-
| `
|
|
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
|
-
|
|
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
|
+
[](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
|
|
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
|
-
| `
|
|
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
|
|
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
|
-
| `
|
|
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
|
-
*
|
|
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
|
|
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<
|
|
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
|
|
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 };
|