blackcat.js-database 1.0.6 → 1.0.7

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/dist/index.mjs CHANGED
@@ -305,9 +305,7 @@ var Database = class {
305
305
  const data = await this.get(key);
306
306
  if (!query) return data;
307
307
  if (!Array.isArray(data)) return null;
308
- const result = data.find((item) => {
309
- return Object.entries(query).every(([k, v]) => item[k] == v);
310
- });
308
+ const result = data.find((item) => Object.entries(query).every(([k, v]) => item[k] == v));
311
309
  return result ?? null;
312
310
  }
313
311
  /**
@@ -378,23 +376,69 @@ var Database = class {
378
376
  await this.driver.set(allDatabase);
379
377
  }
380
378
  /**
381
- * Xóa một key khỏi database.
379
+ * Đảm bảo một key tồn tại với giá trị mặc định.
380
+ * Nếu key đã tồn tại → giữ nguyên và trả về giá trị hiện tại.
381
+ * Nếu chưa tồn tại → set giá trị default và trả về default đó.
382
+ *
383
+ * Khác với `set()` — không bao giờ ghi đè dữ liệu đã có.
384
+ *
385
+ * @template P Key path trong database.
386
+ *
387
+ * @param key Đường dẫn cần đảm bảo tồn tại.
388
+ * @param defaultValue Giá trị mặc định nếu key chưa có.
389
+ *
390
+ * @returns Giá trị tại key sau khi ensure (current hoặc default).
391
+ *
392
+ * @example
393
+ * // "config.maxRetries" chưa tồn tại → set về 3, trả về 3
394
+ * const retries = await db.ensure("config.maxRetries", 3);
395
+ *
396
+ * @example
397
+ * // "config.maxRetries" đã là 10 → không đổi, trả về 10
398
+ * const retries = await db.ensure("config.maxRetries", 3);
382
399
  *
383
- * Hỗ trợ nested path bằng dot notation.
400
+ * @example
401
+ * // Dùng với object — đảm bảo cấu trúc user tồn tại
402
+ * const user = await db.ensure("users.123", {
403
+ * balance: 0,
404
+ * level: 1,
405
+ * joinedAt: Date.now(),
406
+ * });
407
+ */
408
+ async ensure(key, defaultValue) {
409
+ const current = await this.get(key);
410
+ if (current !== null && current !== void 0) return current;
411
+ await this.set(key, defaultValue);
412
+ return defaultValue;
413
+ }
414
+ /**
415
+ * Xóa dữ liệu khỏi database.
416
+ * Hỗ trợ truy cập dữ liệu lồng nhau bằng dot notation.
417
+ * Nếu key không tồn tại hoặc đường dẫn không hợp lệ, hàm sẽ trả về `false`.
384
418
  *
385
419
  * @typeParam P - Path của object.
386
420
  *
387
- * @param key - Đường dẫn key cần xóa.
421
+ * @param key - Đường dẫn dữ liệu cần xóa.
388
422
  *
389
423
  * @returns
390
- * - `true` nếu xóa thành công
391
- * - `false` nếu key không tồn tại
424
+ * - `true` nếu xóa thành công.
425
+ * - `false` nếu key không tồn tại hoặc đường dẫn không hợp lệ.
392
426
  *
393
427
  * @throws BlackCatError
394
- * - INVALID_TYPE nếu key không phải string
428
+ * - INVALID_TYPE nếu `key` không phải là chuỗi.
395
429
  *
396
430
  * @example
431
+ * // Xóa một thuộc tính
397
432
  * await db.delete("user.name");
433
+ *
434
+ * @example
435
+ * // Xóa object lồng nhau
436
+ * await db.delete("users.0.profile");
437
+ *
438
+ * @example
439
+ * // Key không tồn tại
440
+ * const deleted = await db.delete("user.address");
441
+ * console.log(deleted); // false
398
442
  */
399
443
  async delete(key) {
400
444
  if (typeof key !== "string") {
@@ -426,8 +470,8 @@ var Database = class {
426
470
  * await db.deleteAll();
427
471
  */
428
472
  async deleteAll() {
429
- await this.driver.delete();
430
- return true;
473
+ const result = await this.driver.delete();
474
+ return result;
431
475
  }
432
476
  /**
433
477
  * Xóa nhiều bản ghi trong một mảng dữ liệu dựa trên điều kiện truy vấn.
@@ -483,25 +527,57 @@ var Database = class {
483
527
  return originalLength - filtered.length;
484
528
  }
485
529
  /**
486
- * Cập nhật dữ liệu trong database theo đường dẫn key.
530
+ * Cập nhật dữ liệu trong database theo đường dẫn (`key`).
531
+ *
532
+ * ## Chức năng
533
+ * - Cập nhật giá trị tại một key hoặc nested key trong database.
534
+ * - Tự động tạo object trung gian nếu đường dẫn chưa tồn tại.
535
+ * - Hỗ trợ gộp dữ liệu (merge) đối với object và mảng nhằm hạn chế ghi đè dữ liệu đã có.
536
+ *
537
+ * ## Mô tả hoạt động
538
+ * Hàm sẽ đọc toàn bộ dữ liệu database, tìm đến vị trí tương ứng với `key`
539
+ * và cập nhật giá trị mới theo các quy tắc sau:
540
+ *
541
+ * - **Đối với Array**
542
+ * - Nếu cả giá trị hiện tại và giá trị mới đều là mảng thì sẽ **không ghi đè**.
543
+ * - Các phần tử mới sẽ được thêm vào cuối mảng.
544
+ * - Chỉ thêm những phần tử chưa tồn tại ví dụ dựa trên cặp thuộc tính:
545
+ * - `username`
546
+ * - `guild`
547
+ * - Điều này giúp tránh lưu trùng dữ liệu thành viên giữa nhiều lần cập nhật.
548
+ *
549
+ * - **Đối với Object**
550
+ * - Nếu cả hai đều là object sẽ thực hiện **shallow merge** (`...old, ...new`).
551
+ * - Các thuộc tính mới sẽ ghi đè thuộc tính cùng tên.
552
+ * - Những thuộc tính khác vẫn được giữ nguyên.
487
553
  *
488
- * Hàm này sẽ tìm đến vị trí của `key` trong database và gán `value` vào đó.
489
- * Nếu `value` và giá trị hiện tại tại key đều object thì sẽ thực hiện
490
- * **shallow merge** thay vì ghi đè toàn bộ object.
554
+ * - **Các kiểu dữ liệu khác**
555
+ * - Giá trị mới sẽ ghi đè hoàn toàn giá trị cũ.
491
556
  *
492
- * Điều này giúp tránh việc làm mất các thuộc tính đã tồn tại.
557
+ * Sau khi cập nhật xong, toàn bộ database sẽ được ghi lại thông qua driver.
493
558
  *
494
- * @template P - Đường dẫn key hợp lệ trong object database.
559
+ * @template P
560
+ * Đường dẫn hợp lệ của object trong database.
495
561
  *
496
- * @param key - Đường dẫn tới giá trị cần cập nhật.
497
- * Hỗ trợ nested path dạng `"a.b.c"`.
562
+ * @param key
563
+ * Đường dẫn cần cập nhật.
498
564
  *
499
- * @param value - Giá trị mới sẽ được cập nhật tại key.
565
+ * Hỗ trợ nested path:
566
+ * ```ts
567
+ * "guilds.123.settings.prefix"
568
+ * "users.456.profile.name"
569
+ * ```
570
+ *
571
+ * @param value
572
+ * Giá trị mới cần cập nhật.
500
573
  *
501
574
  * @returns
502
- * Trả về giá trị cuối cùng sau khi cập nhật.
575
+ * Trả về giá trị cuối cùng sau khi được lưu vào database.
576
+ *
577
+ * ## Cách sử dụng
578
+ *
579
+ * ### Ví dụ 1 - Cập nhật object
503
580
  *
504
- * @example
505
581
  * Database ban đầu:
506
582
  * ```json
507
583
  * {
@@ -513,10 +589,9 @@ var Database = class {
513
589
  * }
514
590
  * ```
515
591
  *
516
- * Cập nhật:
517
592
  * ```ts
518
593
  * await db.update("123", {
519
- * history: [1,2,3]
594
+ * history: [1, 2, 3]
520
595
  * });
521
596
  * ```
522
597
  *
@@ -526,14 +601,120 @@ var Database = class {
526
601
  * "123": {
527
602
  * "guildID": "123",
528
603
  * "guildName": "BlackCat",
529
- * "history": [1,2,3]
604
+ * "history": [1, 2, 3]
530
605
  * }
531
606
  * }
532
607
  * ```
533
608
  *
609
+ * ---
610
+ *
611
+ * ### Ví dụ 2 - Cập nhật nested key
612
+ *
613
+ * ```ts
614
+ * await db.update("guilds.123.settings.prefix", "!");
615
+ * ```
616
+ *
617
+ * Nếu đường dẫn chưa tồn tại:
618
+ *
619
+ * ```json
620
+ * {}
621
+ * ```
622
+ *
623
+ * Sau khi cập nhật:
624
+ *
625
+ * ```json
626
+ * {
627
+ * "guilds": {
628
+ * "123": {
629
+ * "settings": {
630
+ * "prefix": "!"
631
+ * }
632
+ * }
633
+ * }
634
+ * }
635
+ * ```
636
+ *
637
+ * ---
638
+ *
639
+ * ### Ví dụ 3 - Merge object
640
+ *
641
+ * Giá trị hiện tại:
642
+ *
643
+ * ```json
644
+ * {
645
+ * "name": "BlackCat",
646
+ * "language": "vi",
647
+ * "prefix": "!"
648
+ * }
649
+ * ```
650
+ *
651
+ * ```ts
652
+ * await db.update("config", {
653
+ * prefix: "?",
654
+ * color: "#5865F2"
655
+ * });
656
+ * ```
657
+ *
658
+ * Kết quả:
659
+ *
660
+ * ```json
661
+ * {
662
+ * "name": "BlackCat",
663
+ * "language": "vi",
664
+ * "prefix": "?",
665
+ * "color": "#5865F2"
666
+ * }
667
+ * ```
668
+ *
669
+ * ---
670
+ *
671
+ * ### Ví dụ 4 - Merge mảng và loại bỏ phần tử trùng
672
+ *
673
+ * Giá trị hiện tại:
674
+ *
675
+ * ```json
676
+ * [
677
+ * {
678
+ * "username": "Alice",
679
+ * "guild": "123"
680
+ * }
681
+ * ]
682
+ * ```
683
+ *
684
+ * ```ts
685
+ * await db.update("history", [
686
+ * {
687
+ * username: "Alice",
688
+ * guild: "123"
689
+ * },
690
+ * {
691
+ * username: "Bob",
692
+ * guild: "123"
693
+ * }
694
+ * ]);
695
+ * ```
696
+ *
697
+ * Kết quả:
698
+ *
699
+ * ```json
700
+ * [
701
+ * {
702
+ * "username": "Alice",
703
+ * "guild": "123"
704
+ * },
705
+ * {
706
+ * "username": "Bob",
707
+ * "guild": "123"
708
+ * }
709
+ * ]
710
+ * ```
711
+ *
712
+ * Phần tử `"Alice"` sẽ không được thêm lần nữa vì đã tồn tại với cùng
713
+ * `username` và `guild`.
714
+ *
534
715
  * @throws {BlackCatError}
535
- * - `REQUIRED_PARAMETER_MISSING` nếu key không được cung cấp
536
- * - `INVALID_TYPE` nếu key không phải string
716
+ * - `REQUIRED_PARAMETER_MISSING` nếu không truyền `key`.
717
+ * - `INVALID_TYPE` nếu `key` không phải kiểu `string`.
537
718
  */
538
719
  async update(key, value) {
539
720
  if (!key) {
@@ -553,9 +734,13 @@ var Database = class {
553
734
  current = current[part];
554
735
  }
555
736
  const lastKey = keys[keys.length - 1];
556
- if (isObject(value) && isObject(current[lastKey])) {
737
+ const currentValue = current[lastKey];
738
+ if (Array.isArray(value) && Array.isArray(currentValue)) {
739
+ const unique = value.filter((item) => !currentValue.some((existing) => existing.username === item.username && existing.guild === item.guild));
740
+ current[lastKey] = [...currentValue, ...unique];
741
+ } else if (isObject(value) && isObject(currentValue)) {
557
742
  current[lastKey] = {
558
- ...current[lastKey],
743
+ ...currentValue,
559
744
  ...value
560
745
  };
561
746
  } else {
@@ -564,6 +749,114 @@ var Database = class {
564
749
  await this.driver.set(allDatabase);
565
750
  return current[lastKey];
566
751
  }
752
+ /**
753
+ * ## Đồng bộ dữ liệu với cơ sở dữ liệu
754
+ *
755
+ * ### Chức năng
756
+ * Đồng bộ dữ liệu tại một đường dẫn (`keys`) với giá trị mới (`value`).
757
+ *
758
+ * Hàm sẽ tự động:
759
+ * - Tạo mới dữ liệu nếu chưa tồn tại.
760
+ * - So sánh dữ liệu cũ và mới.
761
+ * - Chỉ cập nhật các trường thực sự thay đổi đối với Object.
762
+ * - Không thực hiện ghi nếu dữ liệu không thay đổi.
763
+ *
764
+ * Điều này giúp giảm số lần ghi xuống cơ sở dữ liệu và tối ưu hiệu năng.
765
+ *
766
+ * ---
767
+ *
768
+ * ### Cơ chế hoạt động
769
+ *
770
+ * 1. Lấy dữ liệu hiện tại bằng `get()`.
771
+ * 2. Nếu chưa tồn tại (`undefined` hoặc `null`)
772
+ * → gọi `set()` để tạo mới.
773
+ * 3. Nếu truyền hàm `compare` và hàm này trả về `true`
774
+ * → bỏ qua đồng bộ.
775
+ * 4. Nếu dữ liệu không phải Object (hoặc là Array)
776
+ * → so sánh bằng `Object.is()`, nếu khác thì gọi `update()`.
777
+ * 5. Nếu dữ liệu là Object
778
+ * → chỉ lấy các thuộc tính thay đổi và cập nhật đúng các trường đó.
779
+ * 6. Nếu không có trường nào thay đổi thì không ghi dữ liệu.
780
+ *
781
+ * ---
782
+ *
783
+ * ### Giá trị trả về
784
+ *
785
+ * - `undefined`
786
+ * - Khi tạo mới dữ liệu.
787
+ * - Khi không có thay đổi.
788
+ * - Khi dữ liệu nguyên thủy được cập nhật.
789
+ * - `Partial<ObjectValue<V, P>>`
790
+ * - Trả về object chỉ chứa các trường đã được cập nhật khi đồng bộ Object.
791
+ *
792
+ * ---
793
+ *
794
+ * ### Ví dụ
795
+ *
796
+ * ```ts
797
+ * // Tạo mới nếu chưa tồn tại
798
+ * await db.syncDatabase({
799
+ * keys: "users.1",
800
+ * value: {
801
+ * name: "BlackCat",
802
+ * age: 20
803
+ * }
804
+ * });
805
+ *
806
+ * // Chỉ cập nhật trường age
807
+ * await db.syncDatabase({
808
+ * keys: "users.1",
809
+ * value: {
810
+ * name: "BlackCat",
811
+ * age: 21
812
+ * }
813
+ * });
814
+ *
815
+ * // Sử dụng compare để bỏ qua đồng bộ
816
+ * await db.syncDatabase({
817
+ * keys: "users.1",
818
+ * value: newData,
819
+ * compare(oldData, newData) {
820
+ * return oldData.version >= newData.version;
821
+ * }
822
+ * });
823
+ * ```
824
+ *
825
+ * @template P Đường dẫn tới dữ liệu trong cơ sở dữ liệu.
826
+ *
827
+ * @param options Thông tin đồng bộ.
828
+ * @param options.keys Đường dẫn của dữ liệu cần đồng bộ.
829
+ * @param options.value Giá trị mới cần ghi.
830
+ * @param options.compare Hàm so sánh tùy chỉnh. Nếu trả về `true` thì sẽ không thực hiện đồng bộ.
831
+ *
832
+ * @returns
833
+ * - `undefined` nếu không có thay đổi hoặc dữ liệu được tạo mới.
834
+ * - `Partial<ObjectValue<V, P>>` chứa các trường đã thay đổi khi cập nhật Object.
835
+ */
836
+ async syncDatabase({ keys, value, compare }) {
837
+ const oldValue = await this.get(keys);
838
+ if (oldValue === void 0 || oldValue === null) {
839
+ await this.set(keys, value);
840
+ return;
841
+ }
842
+ if (compare?.(oldValue, value)) return;
843
+ if (!isObject(oldValue) || !isObject(value) || Array.isArray(oldValue) || Array.isArray(value)) {
844
+ if (!Object.is(oldValue, value)) {
845
+ await this.update(keys, value);
846
+ }
847
+ return;
848
+ }
849
+ const changed = {};
850
+ for (const k in value) {
851
+ const field = k;
852
+ if (!Object.is(oldValue[field], value[field])) {
853
+ changed[field] = value[field];
854
+ }
855
+ }
856
+ if (Object.keys(changed).length === 0) return;
857
+ await this.update(keys, changed);
858
+ return changed;
859
+ }
567
860
  /**
568
861
  * Cộng thêm giá trị vào một key dạng number.
569
862
  *
@@ -626,6 +919,46 @@ var Database = class {
626
919
  await this.set(key, targetNumber - numberToSubtract);
627
920
  return targetNumber - numberToSubtract;
628
921
  }
922
+ /**
923
+ * Thêm một hoặc nhiều phần tử vào đầu array.
924
+ *
925
+ * Chỉ thêm những phần tử chưa tồn tại trong array.
926
+ *
927
+ * @typeParam P - Path trỏ tới array.
928
+ *
929
+ * @param key - Đường dẫn array.
930
+ * @param values - Một hoặc nhiều phần tử cần thêm.
931
+ *
932
+ * @returns Array sau khi thêm.
933
+ *
934
+ * @example
935
+ * await db.unshift("users", newUser);
936
+ */
937
+ async unshift(key, ...values) {
938
+ if (!values.length) {
939
+ throw new BlackCatError("REQUIRED_PARAMETER_MISSING", "values");
940
+ }
941
+ const root = await this.all();
942
+ const pathParts = key.split(".");
943
+ let target = root;
944
+ for (let i = 0; i < pathParts.length; i++) {
945
+ const part = pathParts[i];
946
+ if (!(part in target)) {
947
+ throw new BlackCatError("INVALID_KEY", key);
948
+ }
949
+ if (i === pathParts.length - 1) {
950
+ if (!Array.isArray(target[part])) {
951
+ throw new BlackCatError("INVALID_TARGET", "array", typeOf(target[part]));
952
+ }
953
+ const uniqueValues = values.filter((v) => !target[part].includes(v));
954
+ target[part].unshift(...uniqueValues);
955
+ } else {
956
+ target = target[part];
957
+ }
958
+ }
959
+ await this.set(pathParts[0], root[pathParts[0]]);
960
+ return target[pathParts[pathParts.length - 1]];
961
+ }
629
962
  /**
630
963
  * Thêm một hoặc nhiều phần tử vào cuối array tại key-path.
631
964
  *