firestore-batch-updater 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 exiivy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.ko.md ADDED
@@ -0,0 +1,334 @@
1
+ # Firestore Batch Updater
2
+
3
+ 쿼리 기반 필터링과 진행 상황 추적 기능을 제공하는 Firebase Firestore 대량 업데이트 라이브러리입니다.
4
+
5
+ [English](./README.md) | 한국어
6
+
7
+ ## 주요 기능
8
+
9
+ - 쿼리 기반 업데이트 - `where()` 조건으로 문서 필터링
10
+ - 500개 제한 없음 - Firebase Admin SDK의 BulkWriter 활용
11
+ - 변경 사항 미리보기 - 업데이트 전 Before/After 비교
12
+ - 진행 상황 추적 - 실시간 진행률 콜백
13
+ - 일괄 생성/Upsert - 여러 문서를 한 번에 생성 또는 upsert
14
+ - 로그 파일 생성 - 감사를 위한 상세 작업 로그 (선택사항)
15
+
16
+ ## 설치
17
+
18
+ ```bash
19
+ # npm
20
+ npm install firestore-batch-updater
21
+
22
+ # yarn
23
+ yarn add firestore-batch-updater
24
+
25
+ # pnpm
26
+ pnpm add firestore-batch-updater
27
+ ```
28
+
29
+ **필수 peer dependency:**
30
+
31
+ ```bash
32
+ # npm
33
+ npm install firebase-admin
34
+
35
+ # yarn
36
+ yarn add firebase-admin
37
+
38
+ # pnpm
39
+ pnpm add firebase-admin
40
+ ```
41
+
42
+ ## 빠른 시작
43
+
44
+ ```typescript
45
+ import { BatchUpdater } from "firestore-batch-updater";
46
+ import { getFirestore } from "firebase-admin/firestore";
47
+
48
+ const firestore = getFirestore();
49
+ const updater = new BatchUpdater(firestore);
50
+
51
+ // 변경 사항 미리보기
52
+ const preview = await updater
53
+ .collection("users")
54
+ .where("status", "==", "inactive")
55
+ .preview({ status: "archived" });
56
+
57
+ console.log(`${preview.affectedCount}개 문서가 영향을 받습니다`);
58
+
59
+ // 업데이트 실행
60
+ const result = await updater
61
+ .collection("users")
62
+ .where("status", "==", "inactive")
63
+ .update({ status: "archived" });
64
+
65
+ console.log(`${result.successCount}개 문서 업데이트 완료`);
66
+ ```
67
+
68
+ ## API 레퍼런스
69
+
70
+ ### 메서드 개요
71
+
72
+ | 메서드 | 설명 | 반환값 |
73
+ |--------|------|--------|
74
+ | `collection(path)` | 작업할 컬렉션 선택 | `this` |
75
+ | `where(field, op, value)` | 필터 조건 추가 (체이닝 가능) | `this` |
76
+ | `preview(data)` | 업데이트 전 미리보기 | `PreviewResult` |
77
+ | `update(data, options?)` | 매칭되는 문서 업데이트 | `UpdateResult` |
78
+ | `create(docs, options?)` | 새 문서 생성 | `CreateResult` |
79
+ | `upsert(data, options?)` | 업데이트 또는 생성 (set with merge) | `UpsertResult` |
80
+ | `getFields(field)` | 특정 필드 값 조회 | `FieldValue[]` |
81
+
82
+ ### 옵션
83
+
84
+ 모든 쓰기 작업은 선택적 `options` 매개변수를 지원합니다:
85
+
86
+ ```typescript
87
+ {
88
+ onProgress?: (progress: ProgressInfo) => void;
89
+ log?: LogOptions;
90
+ batchSize?: number; // update/upsert 전용
91
+ }
92
+
93
+ // ProgressInfo
94
+ {
95
+ current: number; // 처리된 문서 수
96
+ total: number; // 전체 문서 수
97
+ percentage: number; // 0-100
98
+ }
99
+
100
+ // LogOptions
101
+ {
102
+ enabled: boolean; // 로그 파일 생성 여부
103
+ path?: string; // 로그 디렉토리 경로 (기본값: ./logs)
104
+ filename?: string; // 파일명 (기본값: 자동 생성)
105
+ }
106
+ ```
107
+
108
+ **batchSize 옵션 (대용량 컬렉션용):**
109
+ - 미설정: 모든 문서를 메모리에 한 번에 로드 (소규모 컬렉션에 적합)
110
+ - 설정 시 (예: `batchSize: 1000`): 커서 페이지네이션을 사용하여 배치 단위로 처리 (대규모 컬렉션의 메모리 문제 방지)
111
+
112
+ ### 반환 타입
113
+
114
+ | 타입 | 필드 |
115
+ |------|------|
116
+ | `PreviewResult` | `affectedCount`, `samples[]`, `affectedFields[]` |
117
+ | `UpdateResult` | `successCount`, `failureCount`, `totalCount`, `failedDocIds?`, `logFilePath?` |
118
+ | `CreateResult` | `successCount`, `failureCount`, `totalCount`, `createdIds[]`, `failedDocIds?`, `logFilePath?` |
119
+ | `UpsertResult` | `successCount`, `failureCount`, `totalCount`, `failedDocIds?`, `logFilePath?` |
120
+ | `FieldValue` | `id`, `value` |
121
+
122
+ ## 사용 예시
123
+
124
+ ### 문서 업데이트
125
+
126
+ ```typescript
127
+ const result = await updater
128
+ .collection("users")
129
+ .where("status", "==", "inactive")
130
+ .update({ status: "archived" });
131
+ ```
132
+
133
+ ### 문서 생성
134
+
135
+ ```typescript
136
+ // 자동 생성 ID
137
+ const result = await updater.collection("users").create([
138
+ { data: { name: "Alice", age: 30 } },
139
+ { data: { name: "Bob", age: 25 } },
140
+ ]);
141
+ console.log("생성된 ID:", result.createdIds);
142
+
143
+ // 지정 ID
144
+ const result2 = await updater.collection("users").create([
145
+ { id: "user-001", data: { name: "Charlie" } },
146
+ { id: "user-002", data: { name: "Diana" } },
147
+ ]);
148
+ ```
149
+
150
+ ### 문서 Upsert
151
+
152
+ ```typescript
153
+ const result = await updater
154
+ .collection("users")
155
+ .where("status", "==", "active")
156
+ .upsert({ tier: "premium", updatedAt: new Date() });
157
+ ```
158
+
159
+ ### 업데이트 전 미리보기
160
+
161
+ ```typescript
162
+ const preview = await updater
163
+ .collection("orders")
164
+ .where("status", "==", "pending")
165
+ .preview({ status: "cancelled" });
166
+
167
+ if (preview.affectedCount > 1000) {
168
+ console.log("문서가 너무 많습니다. 중단합니다.");
169
+ } else {
170
+ await updater
171
+ .collection("orders")
172
+ .where("status", "==", "pending")
173
+ .update({ status: "cancelled" });
174
+ }
175
+ ```
176
+
177
+ ### 진행 상황 추적
178
+
179
+ ```typescript
180
+ const result = await updater
181
+ .collection("products")
182
+ .where("inStock", "==", false)
183
+ .update(
184
+ { status: "discontinued" },
185
+ {
186
+ onProgress: (progress) => {
187
+ console.log(`${progress.percentage}% 완료`);
188
+ },
189
+ }
190
+ );
191
+ ```
192
+
193
+ ### 필드 값 조회
194
+
195
+ ```typescript
196
+ const emails = await updater
197
+ .collection("users")
198
+ .where("status", "==", "active")
199
+ .getFields("email");
200
+
201
+ // [{ id: 'user1', value: 'user1@example.com' }, ...]
202
+ ```
203
+
204
+ ### 다중 조건
205
+
206
+ ```typescript
207
+ const ninetyDaysAgo = new Date();
208
+ ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);
209
+
210
+ const result = await updater
211
+ .collection("users")
212
+ .where("status", "==", "inactive")
213
+ .where("lastLoginAt", "<", ninetyDaysAgo)
214
+ .where("accountType", "==", "free")
215
+ .update({ status: "archived" });
216
+ ```
217
+
218
+ > **참고:** 서로 다른 필드에 여러 `where()` 조건을 사용할 경우, Firestore에서 [복합 인덱스](https://firebase.google.com/docs/firestore/query-data/indexing)가 필요할 수 있습니다. `FAILED_PRECONDITION` 오류가 발생하면 오류 메시지의 링크를 통해 필요한 인덱스를 생성하세요.
219
+
220
+ ### 에러 처리
221
+
222
+ ```typescript
223
+ const result = await updater
224
+ .collection("users")
225
+ .where("status", "==", "test")
226
+ .update({ status: "verified" });
227
+
228
+ if (result.failureCount > 0) {
229
+ console.log(`${result.failureCount}개 문서 실패`);
230
+ console.log("실패한 ID:", result.failedDocIds);
231
+ }
232
+ ```
233
+
234
+ ### 대용량 컬렉션 페이지네이션
235
+
236
+ ```typescript
237
+ // 메모리 문제 방지를 위해 1000개씩 배치 처리
238
+ const result = await updater
239
+ .collection("users")
240
+ .where("status", "==", "inactive")
241
+ .update(
242
+ { status: "archived" },
243
+ {
244
+ batchSize: 1000,
245
+ onProgress: (progress) => {
246
+ console.log(`${progress.percentage}% 완료`);
247
+ },
248
+ }
249
+ );
250
+ ```
251
+
252
+ ### 로그 파일 생성
253
+
254
+ ```typescript
255
+ const result = await updater
256
+ .collection("users")
257
+ .where("status", "==", "inactive")
258
+ .update(
259
+ { status: "archived" },
260
+ {
261
+ log: {
262
+ enabled: true,
263
+ path: "./logs", // 선택사항
264
+ },
265
+ }
266
+ );
267
+
268
+ if (result.logFilePath) {
269
+ console.log(`로그 저장 경로: ${result.logFilePath}`);
270
+ }
271
+ ```
272
+
273
+ 로그 파일 예시:
274
+ ```
275
+ ============================================================
276
+ FIRESTORE BATCH OPERATION LOG
277
+ ============================================================
278
+
279
+ Operation: UPDATE
280
+ Collection: users
281
+ Started: 2024-01-15T10:30:00.000Z
282
+ Completed: 2024-01-15T10:30:05.000Z
283
+
284
+ Conditions:
285
+ - status == "inactive"
286
+
287
+ ============================================================
288
+ SUMMARY
289
+ ============================================================
290
+ Total: 150
291
+ Success: 148
292
+ Failure: 2
293
+
294
+ ============================================================
295
+ DETAILS
296
+ ============================================================
297
+
298
+ 2024-01-15T10:30:01.000Z [SUCCESS] user-001
299
+ 2024-01-15T10:30:01.100Z [SUCCESS] user-002
300
+ 2024-01-15T10:30:01.200Z [FAILURE] user-003
301
+ Error: Document not found
302
+ ...
303
+ ```
304
+
305
+ ## 요구 사항
306
+
307
+ - Node.js 18+
308
+ - Firebase Admin SDK 13.x
309
+ - 서버 사이드 환경 전용 (Admin SDK 필요)
310
+
311
+ ## BulkWriter를 사용하는 이유?
312
+
313
+ 이 라이브러리는 Firebase의 `BulkWriter`를 사용합니다:
314
+
315
+ - 500개 문서 제한 없음 (배치 쓰기와 달리)
316
+ - 자동 속도 제한
317
+ - 내장 재시도 로직
318
+ - 대규모 작업에 더 나은 성능
319
+
320
+ ## 예제
321
+
322
+ 더 자세한 예제는 [examples](./examples) 폴더를 확인하세요:
323
+
324
+ - [basic.ts](./examples/basic.ts) - 기본 사용 워크플로우
325
+ - [api-route.ts](./examples/api-route.ts) - API 엔드포인트에서 사용하기
326
+ - [advanced.ts](./examples/advanced.ts) - 고급 기능 및 패턴
327
+
328
+ ## 면책 조항
329
+
330
+ 이 패키지는 별도의 보증 없이 제공되며, 사용으로 인해 발생하는 데이터 손실, 손상 또는 기타 문제에 대해 작성자는 책임지지 않습니다. 프로덕션 환경에서 사용하기 전에 반드시 개발 환경에서 충분히 테스트하고, 데이터 백업을 확보하시기 바랍니다.
331
+
332
+ ## 라이선스
333
+
334
+ MIT
package/README.md ADDED
@@ -0,0 +1,334 @@
1
+ # Firestore Batch Updater
2
+
3
+ Easy batch updates for Firebase Firestore with query-based filtering and progress tracking.
4
+
5
+ English | [한국어](./README.ko.md)
6
+
7
+ ## Features
8
+
9
+ - Query-based updates - Filter documents with `where()` conditions
10
+ - No 500 document limit - Uses Firebase Admin SDK's BulkWriter
11
+ - Preview changes - See before/after comparison before updating
12
+ - Progress tracking - Real-time progress callbacks
13
+ - Batch create/upsert - Create or upsert multiple documents at once
14
+ - Log file generation - Optional detailed operation logs for auditing
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ # npm
20
+ npm install firestore-batch-updater
21
+
22
+ # yarn
23
+ yarn add firestore-batch-updater
24
+
25
+ # pnpm
26
+ pnpm add firestore-batch-updater
27
+ ```
28
+
29
+ **Required peer dependency:**
30
+
31
+ ```bash
32
+ # npm
33
+ npm install firebase-admin
34
+
35
+ # yarn
36
+ yarn add firebase-admin
37
+
38
+ # pnpm
39
+ pnpm add firebase-admin
40
+ ```
41
+
42
+ ## Quick Start
43
+
44
+ ```typescript
45
+ import { BatchUpdater } from "firestore-batch-updater";
46
+ import { getFirestore } from "firebase-admin/firestore";
47
+
48
+ const firestore = getFirestore();
49
+ const updater = new BatchUpdater(firestore);
50
+
51
+ // Preview changes
52
+ const preview = await updater
53
+ .collection("users")
54
+ .where("status", "==", "inactive")
55
+ .preview({ status: "archived" });
56
+
57
+ console.log(`Will affect ${preview.affectedCount} documents`);
58
+
59
+ // Execute update
60
+ const result = await updater
61
+ .collection("users")
62
+ .where("status", "==", "inactive")
63
+ .update({ status: "archived" });
64
+
65
+ console.log(`Updated ${result.successCount} documents`);
66
+ ```
67
+
68
+ ## API Reference
69
+
70
+ ### Methods Overview
71
+
72
+ | Method | Description | Returns |
73
+ |--------|-------------|---------|
74
+ | `collection(path)` | Select collection to operate on | `this` |
75
+ | `where(field, op, value)` | Add filter condition (chainable) | `this` |
76
+ | `preview(data)` | Preview changes before update | `PreviewResult` |
77
+ | `update(data, options?)` | Update matching documents | `UpdateResult` |
78
+ | `create(docs, options?)` | Create new documents | `CreateResult` |
79
+ | `upsert(data, options?)` | Update or create (set with merge) | `UpsertResult` |
80
+ | `getFields(field)` | Get specific field values | `FieldValue[]` |
81
+
82
+ ### Options
83
+
84
+ All write operations support an optional `options` parameter:
85
+
86
+ ```typescript
87
+ {
88
+ onProgress?: (progress: ProgressInfo) => void;
89
+ log?: LogOptions;
90
+ batchSize?: number; // For update/upsert only
91
+ }
92
+
93
+ // ProgressInfo
94
+ {
95
+ current: number; // Documents processed
96
+ total: number; // Total documents
97
+ percentage: number; // 0-100
98
+ }
99
+
100
+ // LogOptions
101
+ {
102
+ enabled: boolean; // Enable log file generation
103
+ path?: string; // Custom log directory (default: ./logs)
104
+ filename?: string; // Custom filename (default: auto-generated)
105
+ }
106
+ ```
107
+
108
+ **batchSize option (for large collections):**
109
+ - When not set: All documents are loaded into memory at once (suitable for small collections)
110
+ - When set (e.g., `batchSize: 1000`): Documents are processed in batches using cursor pagination (suitable for large collections to prevent memory issues)
111
+
112
+ ### Return Types
113
+
114
+ | Type | Fields |
115
+ |------|--------|
116
+ | `PreviewResult` | `affectedCount`, `samples[]`, `affectedFields[]` |
117
+ | `UpdateResult` | `successCount`, `failureCount`, `totalCount`, `failedDocIds?`, `logFilePath?` |
118
+ | `CreateResult` | `successCount`, `failureCount`, `totalCount`, `createdIds[]`, `failedDocIds?`, `logFilePath?` |
119
+ | `UpsertResult` | `successCount`, `failureCount`, `totalCount`, `failedDocIds?`, `logFilePath?` |
120
+ | `FieldValue` | `id`, `value` |
121
+
122
+ ## Usage Examples
123
+
124
+ ### Update Documents
125
+
126
+ ```typescript
127
+ const result = await updater
128
+ .collection("users")
129
+ .where("status", "==", "inactive")
130
+ .update({ status: "archived" });
131
+ ```
132
+
133
+ ### Create Documents
134
+
135
+ ```typescript
136
+ // Auto-generated IDs
137
+ const result = await updater.collection("users").create([
138
+ { data: { name: "Alice", age: 30 } },
139
+ { data: { name: "Bob", age: 25 } },
140
+ ]);
141
+ console.log("Created IDs:", result.createdIds);
142
+
143
+ // With specific IDs
144
+ const result2 = await updater.collection("users").create([
145
+ { id: "user-001", data: { name: "Charlie" } },
146
+ { id: "user-002", data: { name: "Diana" } },
147
+ ]);
148
+ ```
149
+
150
+ ### Upsert Documents
151
+
152
+ ```typescript
153
+ const result = await updater
154
+ .collection("users")
155
+ .where("status", "==", "active")
156
+ .upsert({ tier: "premium", updatedAt: new Date() });
157
+ ```
158
+
159
+ ### Preview Before Update
160
+
161
+ ```typescript
162
+ const preview = await updater
163
+ .collection("orders")
164
+ .where("status", "==", "pending")
165
+ .preview({ status: "cancelled" });
166
+
167
+ if (preview.affectedCount > 1000) {
168
+ console.log("Too many documents. Aborting.");
169
+ } else {
170
+ await updater
171
+ .collection("orders")
172
+ .where("status", "==", "pending")
173
+ .update({ status: "cancelled" });
174
+ }
175
+ ```
176
+
177
+ ### Progress Tracking
178
+
179
+ ```typescript
180
+ const result = await updater
181
+ .collection("products")
182
+ .where("inStock", "==", false)
183
+ .update(
184
+ { status: "discontinued" },
185
+ {
186
+ onProgress: (progress) => {
187
+ console.log(`${progress.percentage}% complete`);
188
+ },
189
+ }
190
+ );
191
+ ```
192
+
193
+ ### Get Field Values
194
+
195
+ ```typescript
196
+ const emails = await updater
197
+ .collection("users")
198
+ .where("status", "==", "active")
199
+ .getFields("email");
200
+
201
+ // [{ id: 'user1', value: 'user1@example.com' }, ...]
202
+ ```
203
+
204
+ ### Multiple Conditions
205
+
206
+ ```typescript
207
+ const ninetyDaysAgo = new Date();
208
+ ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);
209
+
210
+ const result = await updater
211
+ .collection("users")
212
+ .where("status", "==", "inactive")
213
+ .where("lastLoginAt", "<", ninetyDaysAgo)
214
+ .where("accountType", "==", "free")
215
+ .update({ status: "archived" });
216
+ ```
217
+
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.
219
+
220
+ ### Error Handling
221
+
222
+ ```typescript
223
+ const result = await updater
224
+ .collection("users")
225
+ .where("status", "==", "test")
226
+ .update({ status: "verified" });
227
+
228
+ if (result.failureCount > 0) {
229
+ console.log(`${result.failureCount} documents failed`);
230
+ console.log("Failed IDs:", result.failedDocIds);
231
+ }
232
+ ```
233
+
234
+ ### Pagination for Large Collections
235
+
236
+ ```typescript
237
+ // Process documents in batches of 1000 to prevent memory issues
238
+ const result = await updater
239
+ .collection("users")
240
+ .where("status", "==", "inactive")
241
+ .update(
242
+ { status: "archived" },
243
+ {
244
+ batchSize: 1000,
245
+ onProgress: (progress) => {
246
+ console.log(`${progress.percentage}% complete`);
247
+ },
248
+ }
249
+ );
250
+ ```
251
+
252
+ ### Log File Generation
253
+
254
+ ```typescript
255
+ const result = await updater
256
+ .collection("users")
257
+ .where("status", "==", "inactive")
258
+ .update(
259
+ { status: "archived" },
260
+ {
261
+ log: {
262
+ enabled: true,
263
+ path: "./logs", // optional
264
+ },
265
+ }
266
+ );
267
+
268
+ if (result.logFilePath) {
269
+ console.log(`Log saved to: ${result.logFilePath}`);
270
+ }
271
+ ```
272
+
273
+ Log file example:
274
+ ```
275
+ ============================================================
276
+ FIRESTORE BATCH OPERATION LOG
277
+ ============================================================
278
+
279
+ Operation: UPDATE
280
+ Collection: users
281
+ Started: 2024-01-15T10:30:00.000Z
282
+ Completed: 2024-01-15T10:30:05.000Z
283
+
284
+ Conditions:
285
+ - status == "inactive"
286
+
287
+ ============================================================
288
+ SUMMARY
289
+ ============================================================
290
+ Total: 150
291
+ Success: 148
292
+ Failure: 2
293
+
294
+ ============================================================
295
+ DETAILS
296
+ ============================================================
297
+
298
+ 2024-01-15T10:30:01.000Z [SUCCESS] user-001
299
+ 2024-01-15T10:30:01.100Z [SUCCESS] user-002
300
+ 2024-01-15T10:30:01.200Z [FAILURE] user-003
301
+ Error: Document not found
302
+ ...
303
+ ```
304
+
305
+ ## Requirements
306
+
307
+ - Node.js 18+
308
+ - Firebase Admin SDK 13.x
309
+ - Server-side environment only (Admin SDK required)
310
+
311
+ ## Why BulkWriter?
312
+
313
+ This library uses Firebase's `BulkWriter` which:
314
+
315
+ - No 500 document limit (unlike batch writes)
316
+ - Automatic rate limiting
317
+ - Built-in retry logic
318
+ - Better performance for large operations
319
+
320
+ ## Examples
321
+
322
+ Check out the [examples](./examples) folder:
323
+
324
+ - [basic.ts](./examples/basic.ts) - Basic usage workflow
325
+ - [api-route.ts](./examples/api-route.ts) - Using in API endpoints
326
+ - [advanced.ts](./examples/advanced.ts) - Advanced features and patterns
327
+
328
+ ## Disclaimer
329
+
330
+ This package is provided "as is" without warranty of any kind. The author is not responsible for any data loss, corruption, or other issues that may arise from using this package. Always test thoroughly in a development environment before using in production, and ensure you have proper backups of your data.
331
+
332
+ ## License
333
+
334
+ MIT