firestore-batch-updater 1.11.0 → 1.13.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 +49 -0
- package/README.md +49 -0
- package/dist/index.d.mts +23 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +60 -0
- package/dist/index.mjs +60 -0
- package/package.json +1 -1
package/README.ko.md
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
- 비어있는지 확인 - `isEmpty()`로 매칭 문서가 없는지 확인 (`exists()`의 반대)
|
|
21
21
|
- 전체 문서 조회 - `getAll()`로 매칭되는 모든 문서 데이터 조회
|
|
22
22
|
- 집계 쿼리 - `aggregate()`로 서버 사이드 `sum`, `average`, `count` 연산
|
|
23
|
+
- 간편 집계 - `sum()`과 `avg()`로 단일 필드 간편 집계
|
|
23
24
|
- 커서 페이지네이션 - `paginate()`로 메모리 효율적인 페이지 단위 조회
|
|
24
25
|
- ID 직접 조회 - `getOne()`으로 문서 ID로 빠른 조회
|
|
25
26
|
- 벌크 작업 - `bulkCreate()`, `bulkUpdate()`, `bulkDelete()`로 여러 문서에 각기 다른 데이터로 효율적 처리
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
- 고유값 조회 - `distinct()`로 특정 필드의 중복 없는 값 목록 조회
|
|
29
30
|
- JSON 내보내기/가져오기 - `toJSON()` / `fromJSON()`으로 문서 JSON 파일 내보내기/가져오기
|
|
30
31
|
- 그룹별 개수 조회 - `countBy()`로 특정 필드 값별 문서 수 집계
|
|
32
|
+
- 랜덤 샘플링 - `sample()`로 쿼리 결과에서 랜덤 문서 추출
|
|
31
33
|
- FieldValue 지원 - `increment()`, `arrayUnion()`, `delete()`, `serverTimestamp()` 등 사용 가능
|
|
32
34
|
- 서브컬렉션 & 컬렉션 그룹 - 서브컬렉션 쿼리 또는 동일 이름의 모든 컬렉션 쿼리
|
|
33
35
|
- Dry Run 모드 - 실제 변경 없이 작업 시뮬레이션
|
|
@@ -113,6 +115,8 @@ console.log(`${result.successCount}개 문서 업데이트 완료`);
|
|
|
113
115
|
| `delete(options?)` | 매칭되는 문서 삭제 | `DeleteResult` |
|
|
114
116
|
| `deleteOne()` | 첫 번째 매칭 문서 삭제 | `{ success, id }` |
|
|
115
117
|
| `aggregate(spec)` | sum/average/count 집계 쿼리 | `AggregateResult` |
|
|
118
|
+
| `sum(field)` | 숫자 필드 합계 조회 | `number \| null` |
|
|
119
|
+
| `avg(field)` | 숫자 필드 평균 조회 | `number \| null` |
|
|
116
120
|
| `paginate(options)` | 커서 기반 페이지네이션 | `PaginateResult` |
|
|
117
121
|
| `bulkCreate(docs, options?)` | 여러 문서를 각기 다른 데이터로 생성 | `BulkCreateResult` |
|
|
118
122
|
| `bulkUpdate(updates, options?)` | 여러 문서에 각기 다른 데이터 업데이트 | `BulkUpdateResult` |
|
|
@@ -120,6 +124,7 @@ console.log(`${result.successCount}개 문서 업데이트 완료`);
|
|
|
120
124
|
| `transform(fn, options?)` | 커스텀 함수로 문서 변환 | `TransformResult` |
|
|
121
125
|
| `copyTo(target, options?)` | 다른 컬렉션으로 문서 복사/이동 | `CopyToResult` |
|
|
122
126
|
| `distinct(field)` | 특정 필드의 고유값 조회 | `any[]` |
|
|
127
|
+
| `sample(n)` | 매칭 문서에서 랜덤 샘플 추출 | `{ id, data }[]` |
|
|
123
128
|
| `toJSON(path, options?)` | 문서를 JSON 파일로 내보내기 | `ToJSONResult` |
|
|
124
129
|
| `fromJSON(path, options?)` | JSON 파일에서 문서 가져오기 | `FromJSONResult` |
|
|
125
130
|
| `countBy(field)` | 필드 값별 문서 수 집계 | `CountByResult` |
|
|
@@ -511,6 +516,30 @@ console.log(`평균: ${stats.avgAmount}원`);
|
|
|
511
516
|
console.log(`주문 수: ${stats.orderCount}건`);
|
|
512
517
|
```
|
|
513
518
|
|
|
519
|
+
### 간편 합계 & 평균
|
|
520
|
+
|
|
521
|
+
```typescript
|
|
522
|
+
// 필드 합계를 바로 조회 (aggregate spec 불필요)
|
|
523
|
+
const totalRevenue = await updater
|
|
524
|
+
.collection("orders")
|
|
525
|
+
.where("status", "==", "completed")
|
|
526
|
+
.sum("amount");
|
|
527
|
+
|
|
528
|
+
console.log(`총 매출: ${totalRevenue}원`);
|
|
529
|
+
|
|
530
|
+
// 필드 평균을 바로 조회
|
|
531
|
+
const avgScore = await updater
|
|
532
|
+
.collection("users")
|
|
533
|
+
.where("status", "==", "active")
|
|
534
|
+
.avg("score");
|
|
535
|
+
|
|
536
|
+
console.log(`평균 점수: ${avgScore}`);
|
|
537
|
+
|
|
538
|
+
// aggregate()와 동일하지만 단일 필드 조회 시 더 간편
|
|
539
|
+
// aggregate({ total: { op: "sum", field: "amount" } }) → sum("amount")
|
|
540
|
+
// aggregate({ avg: { op: "average", field: "score" } }) → avg("score")
|
|
541
|
+
```
|
|
542
|
+
|
|
514
543
|
### 커서 기반 페이지네이션
|
|
515
544
|
|
|
516
545
|
```typescript
|
|
@@ -752,6 +781,26 @@ await updater.collection("users").toJSON("./backup.json");
|
|
|
752
781
|
await updater.collection("users_backup").fromJSON("./backup.json");
|
|
753
782
|
```
|
|
754
783
|
|
|
784
|
+
### 랜덤 샘플링
|
|
785
|
+
|
|
786
|
+
```typescript
|
|
787
|
+
// 랜덤으로 5개 문서 추출
|
|
788
|
+
const samples = await updater.collection("users").sample(5);
|
|
789
|
+
samples.forEach(doc => console.log(doc.id, doc.data.name));
|
|
790
|
+
|
|
791
|
+
// 필터된 결과에서 랜덤 샘플
|
|
792
|
+
const activeUsers = await updater
|
|
793
|
+
.collection("users")
|
|
794
|
+
.where("status", "==", "active")
|
|
795
|
+
.sample(3);
|
|
796
|
+
|
|
797
|
+
// select와 함께 사용하여 메모리 효율 극대화
|
|
798
|
+
const randomProducts = await updater
|
|
799
|
+
.collection("products")
|
|
800
|
+
.select("name", "price")
|
|
801
|
+
.sample(10);
|
|
802
|
+
```
|
|
803
|
+
|
|
755
804
|
### Dry Run 모드
|
|
756
805
|
|
|
757
806
|
```typescript
|
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@ English | [한국어](./README.ko.md)
|
|
|
20
20
|
- Empty check - Use `isEmpty()` to check if no matching documents exist (opposite of `exists()`)
|
|
21
21
|
- Get all documents - Use `getAll()` to retrieve all matching documents with data
|
|
22
22
|
- Aggregation - Use `aggregate()` for server-side `sum`, `average`, and `count` operations
|
|
23
|
+
- Quick aggregation - Use `sum()` and `avg()` for simple single-field aggregation
|
|
23
24
|
- Cursor pagination - Use `paginate()` for memory-efficient page-by-page iteration
|
|
24
25
|
- Direct ID lookup - Use `getOne()` for fast document retrieval by ID
|
|
25
26
|
- Bulk operations - Use `bulkCreate()`, `bulkUpdate()`, `bulkDelete()` for efficient multi-document operations with different data each
|
|
@@ -28,6 +29,7 @@ English | [한국어](./README.ko.md)
|
|
|
28
29
|
- Distinct values - Use `distinct()` to get unique field values from matching documents
|
|
29
30
|
- JSON export/import - Use `toJSON()` / `fromJSON()` to export/import documents as JSON
|
|
30
31
|
- Group counting - Use `countBy()` to count documents grouped by field value
|
|
32
|
+
- Random sampling - Use `sample()` to get random documents from query results
|
|
31
33
|
- FieldValue support - Use `increment()`, `arrayUnion()`, `delete()`, `serverTimestamp()`, etc.
|
|
32
34
|
- Subcollection & Collection Group - Query subcollections or all collections with the same name
|
|
33
35
|
- Dry run mode - Simulate operations without making changes
|
|
@@ -113,6 +115,8 @@ console.log(`Updated ${result.successCount} documents`);
|
|
|
113
115
|
| `delete(options?)` | Delete matching documents | `DeleteResult` |
|
|
114
116
|
| `deleteOne()` | Delete first matching document | `{ success, id }` |
|
|
115
117
|
| `aggregate(spec)` | Run sum/average/count queries | `AggregateResult` |
|
|
118
|
+
| `sum(field)` | Get sum of a numeric field | `number \| null` |
|
|
119
|
+
| `avg(field)` | Get average of a numeric field | `number \| null` |
|
|
116
120
|
| `paginate(options)` | Cursor-based pagination | `PaginateResult` |
|
|
117
121
|
| `bulkCreate(docs, options?)` | Create multiple docs with different data | `BulkCreateResult` |
|
|
118
122
|
| `bulkUpdate(updates, options?)` | Update multiple docs with different data | `BulkUpdateResult` |
|
|
@@ -120,6 +124,7 @@ console.log(`Updated ${result.successCount} documents`);
|
|
|
120
124
|
| `transform(fn, options?)` | Transform docs with custom function | `TransformResult` |
|
|
121
125
|
| `copyTo(target, options?)` | Copy/move docs to another collection | `CopyToResult` |
|
|
122
126
|
| `distinct(field)` | Get unique values of a field | `any[]` |
|
|
127
|
+
| `sample(n)` | Get random sample of matching documents | `{ id, data }[]` |
|
|
123
128
|
| `toJSON(path, options?)` | Export documents to JSON file | `ToJSONResult` |
|
|
124
129
|
| `fromJSON(path, options?)` | Import documents from JSON file | `FromJSONResult` |
|
|
125
130
|
| `countBy(field)` | Count documents grouped by field value | `CountByResult` |
|
|
@@ -535,6 +540,30 @@ console.log(`Average: $${stats.avgAmount}`);
|
|
|
535
540
|
console.log(`Orders: ${stats.orderCount}`);
|
|
536
541
|
```
|
|
537
542
|
|
|
543
|
+
### Quick Sum & Average
|
|
544
|
+
|
|
545
|
+
```typescript
|
|
546
|
+
// Get sum of a field directly (no need for aggregate spec)
|
|
547
|
+
const totalRevenue = await updater
|
|
548
|
+
.collection("orders")
|
|
549
|
+
.where("status", "==", "completed")
|
|
550
|
+
.sum("amount");
|
|
551
|
+
|
|
552
|
+
console.log(`Total revenue: $${totalRevenue}`);
|
|
553
|
+
|
|
554
|
+
// Get average of a field directly
|
|
555
|
+
const avgScore = await updater
|
|
556
|
+
.collection("users")
|
|
557
|
+
.where("status", "==", "active")
|
|
558
|
+
.avg("score");
|
|
559
|
+
|
|
560
|
+
console.log(`Average score: ${avgScore}`);
|
|
561
|
+
|
|
562
|
+
// Equivalent to aggregate(), but simpler for single-field queries
|
|
563
|
+
// aggregate({ total: { op: "sum", field: "amount" } }) → sum("amount")
|
|
564
|
+
// aggregate({ avg: { op: "average", field: "score" } }) → avg("score")
|
|
565
|
+
```
|
|
566
|
+
|
|
538
567
|
### Cursor-Based Pagination
|
|
539
568
|
|
|
540
569
|
```typescript
|
|
@@ -765,6 +794,26 @@ await updater.collection("users").toJSON("./backup.json");
|
|
|
765
794
|
await updater.collection("users_backup").fromJSON("./backup.json");
|
|
766
795
|
```
|
|
767
796
|
|
|
797
|
+
### Random Sampling
|
|
798
|
+
|
|
799
|
+
```typescript
|
|
800
|
+
// Get 5 random documents
|
|
801
|
+
const samples = await updater.collection("users").sample(5);
|
|
802
|
+
samples.forEach(doc => console.log(doc.id, doc.data.name));
|
|
803
|
+
|
|
804
|
+
// Random sample from filtered results
|
|
805
|
+
const activeUsers = await updater
|
|
806
|
+
.collection("users")
|
|
807
|
+
.where("status", "==", "active")
|
|
808
|
+
.sample(3);
|
|
809
|
+
|
|
810
|
+
// With select for memory efficiency
|
|
811
|
+
const randomProducts = await updater
|
|
812
|
+
.collection("products")
|
|
813
|
+
.select("name", "price")
|
|
814
|
+
.sample(10);
|
|
815
|
+
```
|
|
816
|
+
|
|
768
817
|
### Dry Run Mode
|
|
769
818
|
|
|
770
819
|
```typescript
|
package/dist/index.d.mts
CHANGED
|
@@ -641,6 +641,20 @@ declare class BatchUpdater {
|
|
|
641
641
|
* @returns Object with alias keys and numeric results
|
|
642
642
|
*/
|
|
643
643
|
aggregate(spec: AggregateSpec): Promise<AggregateResult>;
|
|
644
|
+
/**
|
|
645
|
+
* Get the sum of a numeric field from matching documents
|
|
646
|
+
* Convenience wrapper around aggregate() for simple sum queries
|
|
647
|
+
* @param field - Field path to sum
|
|
648
|
+
* @returns Sum of the field values, or null if no documents match
|
|
649
|
+
*/
|
|
650
|
+
sum(field: string): Promise<number | null>;
|
|
651
|
+
/**
|
|
652
|
+
* Get the average of a numeric field from matching documents
|
|
653
|
+
* Convenience wrapper around aggregate() for simple average queries
|
|
654
|
+
* @param field - Field path to average
|
|
655
|
+
* @returns Average of the field values, or null if no documents match
|
|
656
|
+
*/
|
|
657
|
+
avg(field: string): Promise<number | null>;
|
|
644
658
|
/**
|
|
645
659
|
* Get documents with cursor-based pagination
|
|
646
660
|
* @param options - Pagination options (pageSize, startAfter cursor)
|
|
@@ -730,6 +744,15 @@ declare class BatchUpdater {
|
|
|
730
744
|
* @returns Array of unique values
|
|
731
745
|
*/
|
|
732
746
|
distinct(field: string): Promise<any[]>;
|
|
747
|
+
/**
|
|
748
|
+
* Get a random sample of matching documents
|
|
749
|
+
* @param n - Number of documents to sample
|
|
750
|
+
* @returns Array of randomly selected documents with { id, data }
|
|
751
|
+
*/
|
|
752
|
+
sample(n: number): Promise<{
|
|
753
|
+
id: string;
|
|
754
|
+
data: Record<string, any>;
|
|
755
|
+
}[]>;
|
|
733
756
|
/**
|
|
734
757
|
* Export matching documents to a JSON file
|
|
735
758
|
* @param filePath - Path for the output JSON file
|
package/dist/index.d.ts
CHANGED
|
@@ -641,6 +641,20 @@ declare class BatchUpdater {
|
|
|
641
641
|
* @returns Object with alias keys and numeric results
|
|
642
642
|
*/
|
|
643
643
|
aggregate(spec: AggregateSpec): Promise<AggregateResult>;
|
|
644
|
+
/**
|
|
645
|
+
* Get the sum of a numeric field from matching documents
|
|
646
|
+
* Convenience wrapper around aggregate() for simple sum queries
|
|
647
|
+
* @param field - Field path to sum
|
|
648
|
+
* @returns Sum of the field values, or null if no documents match
|
|
649
|
+
*/
|
|
650
|
+
sum(field: string): Promise<number | null>;
|
|
651
|
+
/**
|
|
652
|
+
* Get the average of a numeric field from matching documents
|
|
653
|
+
* Convenience wrapper around aggregate() for simple average queries
|
|
654
|
+
* @param field - Field path to average
|
|
655
|
+
* @returns Average of the field values, or null if no documents match
|
|
656
|
+
*/
|
|
657
|
+
avg(field: string): Promise<number | null>;
|
|
644
658
|
/**
|
|
645
659
|
* Get documents with cursor-based pagination
|
|
646
660
|
* @param options - Pagination options (pageSize, startAfter cursor)
|
|
@@ -730,6 +744,15 @@ declare class BatchUpdater {
|
|
|
730
744
|
* @returns Array of unique values
|
|
731
745
|
*/
|
|
732
746
|
distinct(field: string): Promise<any[]>;
|
|
747
|
+
/**
|
|
748
|
+
* Get a random sample of matching documents
|
|
749
|
+
* @param n - Number of documents to sample
|
|
750
|
+
* @returns Array of randomly selected documents with { id, data }
|
|
751
|
+
*/
|
|
752
|
+
sample(n: number): Promise<{
|
|
753
|
+
id: string;
|
|
754
|
+
data: Record<string, any>;
|
|
755
|
+
}[]>;
|
|
733
756
|
/**
|
|
734
757
|
* Export matching documents to a JSON file
|
|
735
758
|
* @param filePath - Path for the output JSON file
|
package/dist/index.js
CHANGED
|
@@ -469,6 +469,38 @@ var BatchUpdater = class {
|
|
|
469
469
|
}
|
|
470
470
|
return result;
|
|
471
471
|
}
|
|
472
|
+
/**
|
|
473
|
+
* Get the sum of a numeric field from matching documents
|
|
474
|
+
* Convenience wrapper around aggregate() for simple sum queries
|
|
475
|
+
* @param field - Field path to sum
|
|
476
|
+
* @returns Sum of the field values, or null if no documents match
|
|
477
|
+
*/
|
|
478
|
+
async sum(field) {
|
|
479
|
+
this.validateSetup();
|
|
480
|
+
if (!field) {
|
|
481
|
+
throw new Error("Field is required for sum operation");
|
|
482
|
+
}
|
|
483
|
+
const result = await this.aggregate({
|
|
484
|
+
_sum: { op: "sum", field }
|
|
485
|
+
});
|
|
486
|
+
return result._sum;
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Get the average of a numeric field from matching documents
|
|
490
|
+
* Convenience wrapper around aggregate() for simple average queries
|
|
491
|
+
* @param field - Field path to average
|
|
492
|
+
* @returns Average of the field values, or null if no documents match
|
|
493
|
+
*/
|
|
494
|
+
async avg(field) {
|
|
495
|
+
this.validateSetup();
|
|
496
|
+
if (!field) {
|
|
497
|
+
throw new Error("Field is required for average operation");
|
|
498
|
+
}
|
|
499
|
+
const result = await this.aggregate({
|
|
500
|
+
_avg: { op: "average", field }
|
|
501
|
+
});
|
|
502
|
+
return result._avg;
|
|
503
|
+
}
|
|
472
504
|
/**
|
|
473
505
|
* Get documents with cursor-based pagination
|
|
474
506
|
* @param options - Pagination options (pageSize, startAfter cursor)
|
|
@@ -1231,6 +1263,34 @@ var BatchUpdater = class {
|
|
|
1231
1263
|
}
|
|
1232
1264
|
return values;
|
|
1233
1265
|
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Get a random sample of matching documents
|
|
1268
|
+
* @param n - Number of documents to sample
|
|
1269
|
+
* @returns Array of randomly selected documents with { id, data }
|
|
1270
|
+
*/
|
|
1271
|
+
async sample(n) {
|
|
1272
|
+
this.validateSetup();
|
|
1273
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
1274
|
+
throw new Error("Sample size must be a positive integer");
|
|
1275
|
+
}
|
|
1276
|
+
const query = this.buildQuery();
|
|
1277
|
+
const snapshot = await query.get();
|
|
1278
|
+
if (snapshot.empty) {
|
|
1279
|
+
return [];
|
|
1280
|
+
}
|
|
1281
|
+
const docs = snapshot.docs.map((doc) => ({
|
|
1282
|
+
id: doc.id,
|
|
1283
|
+
data: doc.data()
|
|
1284
|
+
}));
|
|
1285
|
+
if (docs.length <= n) {
|
|
1286
|
+
return docs;
|
|
1287
|
+
}
|
|
1288
|
+
for (let i = docs.length - 1; i > docs.length - 1 - n; i--) {
|
|
1289
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
1290
|
+
[docs[i], docs[j]] = [docs[j], docs[i]];
|
|
1291
|
+
}
|
|
1292
|
+
return docs.slice(docs.length - n);
|
|
1293
|
+
}
|
|
1234
1294
|
/**
|
|
1235
1295
|
* Export matching documents to a JSON file
|
|
1236
1296
|
* @param filePath - Path for the output JSON file
|
package/dist/index.mjs
CHANGED
|
@@ -424,6 +424,38 @@ var BatchUpdater = class {
|
|
|
424
424
|
}
|
|
425
425
|
return result;
|
|
426
426
|
}
|
|
427
|
+
/**
|
|
428
|
+
* Get the sum of a numeric field from matching documents
|
|
429
|
+
* Convenience wrapper around aggregate() for simple sum queries
|
|
430
|
+
* @param field - Field path to sum
|
|
431
|
+
* @returns Sum of the field values, or null if no documents match
|
|
432
|
+
*/
|
|
433
|
+
async sum(field) {
|
|
434
|
+
this.validateSetup();
|
|
435
|
+
if (!field) {
|
|
436
|
+
throw new Error("Field is required for sum operation");
|
|
437
|
+
}
|
|
438
|
+
const result = await this.aggregate({
|
|
439
|
+
_sum: { op: "sum", field }
|
|
440
|
+
});
|
|
441
|
+
return result._sum;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Get the average of a numeric field from matching documents
|
|
445
|
+
* Convenience wrapper around aggregate() for simple average queries
|
|
446
|
+
* @param field - Field path to average
|
|
447
|
+
* @returns Average of the field values, or null if no documents match
|
|
448
|
+
*/
|
|
449
|
+
async avg(field) {
|
|
450
|
+
this.validateSetup();
|
|
451
|
+
if (!field) {
|
|
452
|
+
throw new Error("Field is required for average operation");
|
|
453
|
+
}
|
|
454
|
+
const result = await this.aggregate({
|
|
455
|
+
_avg: { op: "average", field }
|
|
456
|
+
});
|
|
457
|
+
return result._avg;
|
|
458
|
+
}
|
|
427
459
|
/**
|
|
428
460
|
* Get documents with cursor-based pagination
|
|
429
461
|
* @param options - Pagination options (pageSize, startAfter cursor)
|
|
@@ -1186,6 +1218,34 @@ var BatchUpdater = class {
|
|
|
1186
1218
|
}
|
|
1187
1219
|
return values;
|
|
1188
1220
|
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Get a random sample of matching documents
|
|
1223
|
+
* @param n - Number of documents to sample
|
|
1224
|
+
* @returns Array of randomly selected documents with { id, data }
|
|
1225
|
+
*/
|
|
1226
|
+
async sample(n) {
|
|
1227
|
+
this.validateSetup();
|
|
1228
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
1229
|
+
throw new Error("Sample size must be a positive integer");
|
|
1230
|
+
}
|
|
1231
|
+
const query = this.buildQuery();
|
|
1232
|
+
const snapshot = await query.get();
|
|
1233
|
+
if (snapshot.empty) {
|
|
1234
|
+
return [];
|
|
1235
|
+
}
|
|
1236
|
+
const docs = snapshot.docs.map((doc) => ({
|
|
1237
|
+
id: doc.id,
|
|
1238
|
+
data: doc.data()
|
|
1239
|
+
}));
|
|
1240
|
+
if (docs.length <= n) {
|
|
1241
|
+
return docs;
|
|
1242
|
+
}
|
|
1243
|
+
for (let i = docs.length - 1; i > docs.length - 1 - n; i--) {
|
|
1244
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
1245
|
+
[docs[i], docs[j]] = [docs[j], docs[i]];
|
|
1246
|
+
}
|
|
1247
|
+
return docs.slice(docs.length - n);
|
|
1248
|
+
}
|
|
1189
1249
|
/**
|
|
1190
1250
|
* Export matching documents to a JSON file
|
|
1191
1251
|
* @param filePath - Path for the output JSON file
|