tangerine 2.0.2 → 2.1.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.
Files changed (4) hide show
  1. package/README.md +479 -25
  2. package/index.d.ts +7 -0
  3. package/index.js +128 -43
  4. package/package.json +3 -2
package/README.md CHANGED
@@ -398,6 +398,7 @@ Similar to the `options` argument from `new dns.promises.Resolver(options)` invo
398
398
  | `returnHTTPErrors` | `Boolean` | `false` | Whether to return HTTP errors instead of mapping them to corresponding DNS errors. |
399
399
  | `smartRotate` | `Boolean` | `true` | Whether to do smart server rotation if servers fail. |
400
400
  | `defaultHTTPErrorMessage` | `String` | `"Unsuccessful HTTP response"` | Default fallback message if `statusCode` returned from HTTP request was not found in [http.STATUS_CODES](https://nodejs.org/api/http.html#httpstatus_codes). |
401
+ | `parallelResolution` | `Boolean` | `false` | Enable parallel resolution for querying DNS servers. |
401
402
 
402
403
 
403
404
  ## Cache
@@ -533,15 +534,25 @@ We have written extensive benchmarks to show that :tangerine: Tangerine is as fa
533
534
 
534
535
  #### Latest Automated Benchmark Results
535
536
 
536
- **Last Updated:** 2025-12-21
537
+ **Last Updated:** 2026-02-26
537
538
 
538
539
  | Node Version | Platform | Arch | Timestamp |
539
540
  | ------------ | -------- | ---- | ------------ |
540
541
  | v18.20.8 | linux | x64 | Dec 21, 2025 |
541
- | v20.19.6 | linux | x64 | Dec 21, 2025 |
542
+ | v20.19.6 | linux | x64 | Jan 22, 2026 |
543
+ | v20.20.0 | linux | x64 | Feb 25, 2026 |
542
544
  | v22.21.1 | linux | x64 | Dec 21, 2025 |
545
+ | v22.22.0 | linux | x64 | Jan 23, 2026 |
543
546
  | v24.12.0 | linux | x64 | Dec 21, 2025 |
547
+ | v24.13.0 | linux | x64 | Feb 19, 2026 |
548
+ | v24.13.1 | linux | x64 | Feb 26, 2026 |
544
549
  | v25.2.1 | linux | x64 | Dec 21, 2025 |
550
+ | v25.3.0 | linux | x64 | Jan 14, 2026 |
551
+ | v25.4.0 | linux | x64 | Jan 20, 2026 |
552
+ | v25.5.0 | linux | x64 | Jan 27, 2026 |
553
+ | v25.6.0 | linux | x64 | Feb 4, 2026 |
554
+ | v25.6.1 | linux | x64 | Feb 11, 2026 |
555
+ | v25.7.0 | linux | x64 | Feb 25, 2026 |
545
556
 
546
557
  <details>
547
558
  <summary>Click to expand detailed benchmark results</summary>
@@ -597,12 +608,12 @@ Fastest without caching is: dns.promises.reverse without caching
597
608
 
598
609
  ```text
599
610
  Started: lookup
600
- tangerine.lookup POST with caching using Cloudflare x 795 ops/sec ±195.51% (87 runs sampled)
601
- tangerine.lookup POST without caching using Cloudflare x 306 ops/sec ±1.81% (83 runs sampled)
602
- tangerine.lookup GET with caching using Cloudflare x 298,127 ops/sec ±0.28% (90 runs sampled)
603
- tangerine.lookup GET without caching using Cloudflare x 307 ops/sec ±1.94% (83 runs sampled)
604
- dns.promises.lookup with caching using Cloudflare x 9,509,305 ops/sec ±0.47% (88 runs sampled)
605
- dns.promises.lookup without caching using Cloudflare x 3,250 ops/sec ±0.73% (85 runs sampled)
611
+ tangerine.lookup POST with caching using Cloudflare x 820 ops/sec ±195.45% (88 runs sampled)
612
+ tangerine.lookup POST without caching using Cloudflare x 212 ops/sec ±2.98% (79 runs sampled)
613
+ tangerine.lookup GET with caching using Cloudflare x 290,812 ops/sec ±0.74% (88 runs sampled)
614
+ tangerine.lookup GET without caching using Cloudflare x 260 ops/sec ±2.00% (82 runs sampled)
615
+ dns.promises.lookup with caching using Cloudflare x 8,961,323 ops/sec ±0.78% (86 runs sampled)
616
+ dns.promises.lookup without caching using Cloudflare x 3,143 ops/sec ±0.57% (85 runs sampled)
606
617
  Fastest without caching is: dns.promises.lookup without caching using Cloudflare
607
618
  ```
608
619
 
@@ -610,29 +621,67 @@ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
610
621
 
611
622
  ```text
612
623
  Started: resolve
613
- tangerine.resolve POST with caching using Cloudflare x 1,016 ops/sec ±195.82% (89 runs sampled)
614
- tangerine.resolve POST without caching using Cloudflare x 303 ops/sec ±3.25% (79 runs sampled)
615
- tangerine.resolve GET with caching using Cloudflare x 1,071,513 ops/sec ±0.32% (89 runs sampled)
616
- tangerine.resolve GET without caching using Cloudflare x 339 ops/sec ±1.13% (84 runs sampled)
617
- tangerine.resolve POST with caching using Google x 1,031,970 ops/sec ±0.28% (90 runs sampled)
618
- tangerine.resolve POST without caching using Google x 356 ops/sec ±13.06% (79 runs sampled)
619
- tangerine.resolve GET with caching using Google x 1,032,838 ops/sec ±0.28% (90 runs sampled)
620
- tangerine.resolve GET without caching using Google x 364 ops/sec ±5.78% (77 runs sampled)
621
- resolver.resolve with caching using Cloudflare x 7,820,907 ops/sec ±0.65% (86 runs sampled)
622
- resolver.resolve without caching using Cloudflare x 16.20 ops/sec ±189.31% (79 runs sampled)
623
- Fastest without caching is: tangerine.resolve GET without caching using Google
624
+ tangerine.resolve POST with caching using Cloudflare x 1,034 ops/sec ±195.81% (89 runs sampled)
625
+ tangerine.resolve POST without caching using Cloudflare x 221 ops/sec ±1.83% (80 runs sampled)
626
+ tangerine.resolve GET with caching using Cloudflare x 1,044,036 ops/sec ±0.36% (89 runs sampled)
627
+ tangerine.resolve GET without caching using Cloudflare x 272 ops/sec ±1.57% (83 runs sampled)
628
+ tangerine.resolve POST with caching using Google x 1,035,748 ops/sec ±0.31% (89 runs sampled)
629
+ tangerine.resolve POST without caching using Google x 236 ops/sec ±0.93% (85 runs sampled)
630
+ tangerine.resolve GET with caching using Google x 1,027,998 ops/sec ±0.25% (89 runs sampled)
631
+ tangerine.resolve GET without caching using Google x 230 ops/sec ±1.04% (83 runs sampled)
632
+ resolver.resolve with caching using Cloudflare x 7,717,855 ops/sec ±0.67% (85 runs sampled)
633
+ resolver.resolve without caching using Cloudflare x 24.10 ops/sec ±180.34% (37 runs sampled)
634
+ Fastest without caching is: tangerine.resolve GET without caching using Cloudflare
635
+ ```
636
+
637
+ **reverse:**
638
+
639
+ ```text
640
+ spawnSync /bin/sh ETIMEDOUT
641
+ ```
642
+
643
+ ##### Node.js v20.20.0
644
+
645
+ **lookup:**
646
+
647
+ ```text
648
+ Started: lookup
649
+ tangerine.lookup POST with caching using Cloudflare x 732 ops/sec ±195.51% (87 runs sampled)
650
+ tangerine.lookup POST without caching using Cloudflare x 117 ops/sec ±1.61% (79 runs sampled)
651
+ tangerine.lookup GET with caching using Cloudflare x 299,941 ops/sec ±1.15% (89 runs sampled)
652
+ tangerine.lookup GET without caching using Cloudflare x 117 ops/sec ±1.43% (80 runs sampled)
653
+ dns.promises.lookup with caching using Cloudflare x 1,383 ops/sec ±195.97% (87 runs sampled)
654
+ dns.promises.lookup without caching using Cloudflare x 2,374 ops/sec ±0.56% (86 runs sampled)
655
+ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
656
+ ```
657
+
658
+ **resolve:**
659
+
660
+ ```text
661
+ Started: resolve
662
+ tangerine.resolve POST with caching using Cloudflare x 974 ops/sec ±195.82% (89 runs sampled)
663
+ tangerine.resolve POST without caching using Cloudflare x 130 ops/sec ±1.30% (87 runs sampled)
664
+ tangerine.resolve GET with caching using Cloudflare x 1,049,956 ops/sec ±1.53% (85 runs sampled)
665
+ tangerine.resolve GET without caching using Cloudflare x 117 ops/sec ±1.79% (79 runs sampled)
666
+ tangerine.resolve POST with caching using Google x 1,019,993 ops/sec ±0.48% (88 runs sampled)
667
+ tangerine.resolve POST without caching using Google x 106 ops/sec ±4.78% (83 runs sampled)
668
+ tangerine.resolve GET with caching using Google x 1,030,533 ops/sec ±0.28% (89 runs sampled)
669
+ tangerine.resolve GET without caching using Google x 123 ops/sec ±0.59% (83 runs sampled)
670
+ resolver.resolve with caching using Cloudflare x 7,658,904 ops/sec ±0.60% (86 runs sampled)
671
+ resolver.resolve without caching using Cloudflare x 13.28 ops/sec ±201.81% (11 runs sampled)
672
+ Fastest without caching is: tangerine.resolve POST without caching using Cloudflare
624
673
  ```
625
674
 
626
675
  **reverse:**
627
676
 
628
677
  ```text
629
678
  Started: reverse
630
- tangerine.reverse GET with caching x 1,002 ops/sec ±195.36% (87 runs sampled)
631
- tangerine.reverse GET without caching x 323 ops/sec ±1.36% (85 runs sampled)
632
- resolver.reverse with caching x 78.33 ops/sec ±196.00% (88 runs sampled)
633
- resolver.reverse without caching x 0.10 ops/sec ±23.12% (5 runs sampled)
634
- dns.promises.reverse with caching x 7,759,048 ops/sec ±1.08% (79 runs sampled)
635
- dns.promises.reverse without caching x 1.25 ops/sec ±163.63% (80 runs sampled)
679
+ tangerine.reverse GET with caching x 991 ops/sec ±195.36% (87 runs sampled)
680
+ tangerine.reverse GET without caching x 129 ops/sec ±0.76% (86 runs sampled)
681
+ resolver.reverse with caching x 0.10 ops/sec ±0.01% (5 runs sampled)
682
+ resolver.reverse without caching x 2.80 ops/sec ±112.00% (74 runs sampled)
683
+ dns.promises.reverse with caching x 7,904,185 ops/sec ±0.60% (85 runs sampled)
684
+ dns.promises.reverse without caching x 8.68 ops/sec ±202.85% (14 runs sampled)
636
685
  Fastest without caching is: tangerine.reverse GET without caching
637
686
  ```
638
687
 
@@ -674,6 +723,51 @@ Fastest without caching is: tangerine.resolve GET without caching using Google
674
723
  spawnSync /bin/sh ETIMEDOUT
675
724
  ```
676
725
 
726
+ ##### Node.js v22.22.0
727
+
728
+ **lookup:**
729
+
730
+ ```text
731
+ Started: lookup
732
+ tangerine.lookup POST with caching using Cloudflare x 1,163 ops/sec ±195.31% (90 runs sampled)
733
+ tangerine.lookup POST without caching using Cloudflare x 54.38 ops/sec ±5.55% (88 runs sampled)
734
+ tangerine.lookup GET with caching using Cloudflare x 313,634 ops/sec ±0.27% (89 runs sampled)
735
+ tangerine.lookup GET without caching using Cloudflare x 55.58 ops/sec ±6.39% (71 runs sampled)
736
+ dns.promises.lookup with caching using Cloudflare x 9,649,217 ops/sec ±0.65% (88 runs sampled)
737
+ dns.promises.lookup without caching using Cloudflare x 3,178 ops/sec ±0.70% (85 runs sampled)
738
+ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
739
+ ```
740
+
741
+ **resolve:**
742
+
743
+ ```text
744
+ Started: resolve
745
+ tangerine.resolve POST with caching using Cloudflare x 1,186 ops/sec ±195.79% (89 runs sampled)
746
+ tangerine.resolve POST without caching using Cloudflare x 55.82 ops/sec ±5.02% (84 runs sampled)
747
+ tangerine.resolve GET with caching using Cloudflare x 1,099,153 ops/sec ±0.22% (90 runs sampled)
748
+ tangerine.resolve GET without caching using Cloudflare x 59.25 ops/sec ±5.73% (73 runs sampled)
749
+ tangerine.resolve POST with caching using Google x 1,095 ops/sec ±195.81% (88 runs sampled)
750
+ tangerine.resolve POST without caching using Google x 60.23 ops/sec ±10.05% (80 runs sampled)
751
+ tangerine.resolve GET with caching using Google x 1,107,393 ops/sec ±0.67% (90 runs sampled)
752
+ tangerine.resolve GET without caching using Google x 54.10 ops/sec ±10.64% (74 runs sampled)
753
+ resolver.resolve with caching using Cloudflare x 8,395,785 ops/sec ±0.65% (89 runs sampled)
754
+ resolver.resolve without caching using Cloudflare x 67.84 ops/sec ±0.83% (80 runs sampled)
755
+ Fastest without caching is: resolver.resolve without caching using Cloudflare, tangerine.resolve POST without caching using Google
756
+ ```
757
+
758
+ **reverse:**
759
+
760
+ ```text
761
+ Started: reverse
762
+ tangerine.reverse GET with caching x 1,114 ops/sec ±195.34% (89 runs sampled)
763
+ tangerine.reverse GET without caching x 55.88 ops/sec ±4.70% (89 runs sampled)
764
+ resolver.reverse with caching x 8,531,724 ops/sec ±0.82% (84 runs sampled)
765
+ resolver.reverse without caching x 68.04 ops/sec ±0.73% (81 runs sampled)
766
+ dns.promises.reverse with caching x 8,533,071 ops/sec ±0.84% (81 runs sampled)
767
+ dns.promises.reverse without caching x 67.08 ops/sec ±0.82% (79 runs sampled)
768
+ Fastest without caching is: resolver.reverse without caching
769
+ ```
770
+
677
771
  ##### Node.js v24.12.0
678
772
 
679
773
  **lookup:**
@@ -712,6 +806,96 @@ Fastest without caching is: tangerine.resolve GET without caching using Google
712
806
  spawnSync /bin/sh ETIMEDOUT
713
807
  ```
714
808
 
809
+ ##### Node.js v24.13.0
810
+
811
+ **lookup:**
812
+
813
+ ```text
814
+ Started: lookup
815
+ tangerine.lookup POST with caching using Cloudflare x 330,489 ops/sec ±1.61% (85 runs sampled)
816
+ tangerine.lookup POST without caching using Cloudflare x 248 ops/sec ±1.98% (80 runs sampled)
817
+ tangerine.lookup GET with caching using Cloudflare x 321,967 ops/sec ±0.26% (90 runs sampled)
818
+ tangerine.lookup GET without caching using Cloudflare x 224 ops/sec ±1.85% (76 runs sampled)
819
+ dns.promises.lookup with caching using Cloudflare x 734 ops/sec ±195.99% (87 runs sampled)
820
+ dns.promises.lookup without caching using Cloudflare x 2,207 ops/sec ±0.51% (85 runs sampled)
821
+ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
822
+ ```
823
+
824
+ **resolve:**
825
+
826
+ ```text
827
+ Started: resolve
828
+ tangerine.resolve POST with caching using Cloudflare x 1,158,368 ops/sec ±0.28% (90 runs sampled)
829
+ tangerine.resolve POST without caching using Cloudflare x 246 ops/sec ±1.93% (80 runs sampled)
830
+ tangerine.resolve GET with caching using Cloudflare x 1,114,486 ops/sec ±0.29% (88 runs sampled)
831
+ tangerine.resolve GET without caching using Cloudflare x 252 ops/sec ±1.20% (85 runs sampled)
832
+ tangerine.resolve POST with caching using Google x 1,134,382 ops/sec ±0.24% (88 runs sampled)
833
+ tangerine.resolve POST without caching using Google x 168 ops/sec ±36.15% (70 runs sampled)
834
+ tangerine.resolve GET with caching using Google x 1,139,056 ops/sec ±0.61% (90 runs sampled)
835
+ tangerine.resolve GET without caching using Google x 297 ops/sec ±2.73% (75 runs sampled)
836
+ resolver.resolve with caching using Cloudflare x 8,289,380 ops/sec ±7.32% (88 runs sampled)
837
+ resolver.resolve without caching using Cloudflare x 316 ops/sec ±1.92% (79 runs sampled)
838
+ Fastest without caching is: resolver.resolve without caching using Cloudflare
839
+ ```
840
+
841
+ **reverse:**
842
+
843
+ ```text
844
+ Started: reverse
845
+ tangerine.reverse GET with caching x 339,770 ops/sec ±0.42% (90 runs sampled)
846
+ tangerine.reverse GET without caching x 240 ops/sec ±2.18% (77 runs sampled)
847
+ resolver.reverse with caching x 8,523,002 ops/sec ±0.83% (87 runs sampled)
848
+ resolver.reverse without caching x 15.57 ops/sec ±194.42% (31 runs sampled)
849
+ dns.promises.reverse with caching x 8,765,472 ops/sec ±1.03% (82 runs sampled)
850
+ dns.promises.reverse without caching x 5.51 ops/sec ±179.54% (80 runs sampled)
851
+ Fastest without caching is: tangerine.reverse GET without caching
852
+ ```
853
+
854
+ ##### Node.js v24.13.1
855
+
856
+ **lookup:**
857
+
858
+ ```text
859
+ Started: lookup
860
+ tangerine.lookup POST with caching using Cloudflare x 328,178 ops/sec ±1.46% (90 runs sampled)
861
+ tangerine.lookup POST without caching using Cloudflare x 263 ops/sec ±1.89% (78 runs sampled)
862
+ tangerine.lookup GET with caching using Cloudflare x 315,952 ops/sec ±0.27% (90 runs sampled)
863
+ tangerine.lookup GET without caching using Cloudflare x 227 ops/sec ±3.20% (78 runs sampled)
864
+ dns.promises.lookup with caching using Cloudflare x 1,047 ops/sec ±195.98% (80 runs sampled)
865
+ dns.promises.lookup without caching using Cloudflare x 2,273 ops/sec ±0.36% (86 runs sampled)
866
+ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
867
+ ```
868
+
869
+ **resolve:**
870
+
871
+ ```text
872
+ Started: resolve
873
+ tangerine.resolve POST with caching using Cloudflare x 1,146,234 ops/sec ±0.38% (88 runs sampled)
874
+ tangerine.resolve POST without caching using Cloudflare x 228 ops/sec ±1.86% (83 runs sampled)
875
+ tangerine.resolve GET with caching using Cloudflare x 1,121,526 ops/sec ±0.51% (87 runs sampled)
876
+ tangerine.resolve GET without caching using Cloudflare x 293 ops/sec ±1.77% (77 runs sampled)
877
+ tangerine.resolve POST with caching using Google x 1,116,293 ops/sec ±0.94% (87 runs sampled)
878
+ tangerine.resolve POST without caching using Google x 262 ops/sec ±4.45% (83 runs sampled)
879
+ tangerine.resolve GET with caching using Google x 1,127,465 ops/sec ±0.31% (90 runs sampled)
880
+ tangerine.resolve GET without caching using Google x 265 ops/sec ±1.09% (83 runs sampled)
881
+ resolver.resolve with caching using Cloudflare x 8,518,907 ops/sec ±1.01% (86 runs sampled)
882
+ resolver.resolve without caching using Cloudflare x 82.99 ops/sec ±142.66% (36 runs sampled)
883
+ Fastest without caching is: tangerine.resolve GET without caching using Cloudflare
884
+ ```
885
+
886
+ **reverse:**
887
+
888
+ ```text
889
+ Started: reverse
890
+ tangerine.reverse GET with caching x 337,325 ops/sec ±0.52% (89 runs sampled)
891
+ tangerine.reverse GET without caching x 233 ops/sec ±2.59% (79 runs sampled)
892
+ resolver.reverse with caching x 17.19 ops/sec ±196.00% (86 runs sampled)
893
+ resolver.reverse without caching x 6.84 ops/sec ±154.89% (71 runs sampled)
894
+ dns.promises.reverse with caching x 8,315,699 ops/sec ±1.33% (78 runs sampled)
895
+ dns.promises.reverse without caching x 0.77 ops/sec ±164.54% (37 runs sampled)
896
+ Fastest without caching is: tangerine.reverse GET without caching
897
+ ```
898
+
715
899
  ##### Node.js v25.2.1
716
900
 
717
901
  **lookup:**
@@ -757,6 +941,276 @@ dns.promises.reverse without caching x 5.11 ops/sec ±189.52% (76 runs sampled)
757
941
  Fastest without caching is: resolver.reverse without caching, dns.promises.reverse without caching
758
942
  ```
759
943
 
944
+ ##### Node.js v25.3.0
945
+
946
+ **lookup:**
947
+
948
+ ```text
949
+ Started: lookup
950
+ tangerine.lookup POST with caching using Cloudflare x 1,178 ops/sec ±195.33% (88 runs sampled)
951
+ tangerine.lookup POST without caching using Cloudflare x 86.96 ops/sec ±1.37% (82 runs sampled)
952
+ tangerine.lookup GET with caching using Cloudflare x 340,510 ops/sec ±0.25% (90 runs sampled)
953
+ tangerine.lookup GET without caching using Cloudflare x 89.52 ops/sec ±1.42% (84 runs sampled)
954
+ dns.promises.lookup with caching using Cloudflare x 1,352 ops/sec ±195.97% (88 runs sampled)
955
+ dns.promises.lookup without caching using Cloudflare x 3,261 ops/sec ±0.86% (83 runs sampled)
956
+ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
957
+ ```
958
+
959
+ **resolve:**
960
+
961
+ ```text
962
+ Started: resolve
963
+ tangerine.resolve POST with caching using Cloudflare x 1,099 ops/sec ±195.82% (89 runs sampled)
964
+ tangerine.resolve POST without caching using Cloudflare x 88.57 ops/sec ±0.78% (83 runs sampled)
965
+ tangerine.resolve GET with caching using Cloudflare x 1,182,619 ops/sec ±0.51% (91 runs sampled)
966
+ tangerine.resolve GET without caching using Cloudflare x 89.35 ops/sec ±9.03% (88 runs sampled)
967
+ tangerine.resolve POST with caching using Google x 1,174,915 ops/sec ±2.35% (89 runs sampled)
968
+ tangerine.resolve POST without caching using Google x 129 ops/sec ±9.63% (75 runs sampled)
969
+ tangerine.resolve GET with caching using Google x 1,187,141 ops/sec ±0.30% (89 runs sampled)
970
+ tangerine.resolve GET without caching using Google x 195 ops/sec ±0.85% (84 runs sampled)
971
+ resolver.resolve with caching using Cloudflare x 8,288,775 ops/sec ±10.11% (89 runs sampled)
972
+ resolver.resolve without caching using Cloudflare x 105 ops/sec ±0.63% (82 runs sampled)
973
+ Fastest without caching is: tangerine.resolve GET without caching using Google
974
+ ```
975
+
976
+ **reverse:**
977
+
978
+ ```text
979
+ Started: reverse
980
+ tangerine.reverse GET with caching x 1,146 ops/sec ±195.30% (90 runs sampled)
981
+ tangerine.reverse GET without caching x 88.05 ops/sec ±0.92% (83 runs sampled)
982
+ resolver.reverse with caching x 8,812,418 ops/sec ±0.46% (85 runs sampled)
983
+ resolver.reverse without caching x 3.08 ops/sec ±203.97% (19 runs sampled)
984
+ dns.promises.reverse with caching x 8,647,756 ops/sec ±2.62% (87 runs sampled)
985
+ dns.promises.reverse without caching x 0.09 ops/sec ±139.17% (6 runs sampled)
986
+ Fastest without caching is: tangerine.reverse GET without caching
987
+ ```
988
+
989
+ ##### Node.js v25.4.0
990
+
991
+ **lookup:**
992
+
993
+ ```text
994
+ Started: lookup
995
+ tangerine.lookup POST with caching using Cloudflare x 338,506 ops/sec ±2.96% (86 runs sampled)
996
+ tangerine.lookup POST without caching using Cloudflare x 252 ops/sec ±1.53% (83 runs sampled)
997
+ tangerine.lookup GET with caching using Cloudflare x 328,384 ops/sec ±0.24% (89 runs sampled)
998
+ tangerine.lookup GET without caching using Cloudflare x 256 ops/sec ±2.15% (80 runs sampled)
999
+ dns.promises.lookup with caching using Cloudflare x 10,091,995 ops/sec ±0.78% (85 runs sampled)
1000
+ dns.promises.lookup without caching using Cloudflare x 3,114 ops/sec ±0.68% (84 runs sampled)
1001
+ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
1002
+ ```
1003
+
1004
+ **resolve:**
1005
+
1006
+ ```text
1007
+ Started: resolve
1008
+ tangerine.resolve POST with caching using Cloudflare x 1,178,120 ops/sec ±0.63% (89 runs sampled)
1009
+ tangerine.resolve POST without caching using Cloudflare x 268 ops/sec ±1.42% (84 runs sampled)
1010
+ tangerine.resolve GET with caching using Cloudflare x 1,158,024 ops/sec ±0.37% (88 runs sampled)
1011
+ tangerine.resolve GET without caching using Cloudflare x 263 ops/sec ±1.56% (82 runs sampled)
1012
+ tangerine.resolve POST with caching using Google x 1,126,115 ops/sec ±2.39% (89 runs sampled)
1013
+ tangerine.resolve POST without caching using Google x 203 ops/sec ±1.10% (85 runs sampled)
1014
+ tangerine.resolve GET with caching using Google x 1,124,263 ops/sec ±3.04% (88 runs sampled)
1015
+ tangerine.resolve GET without caching using Google x 231 ops/sec ±0.71% (85 runs sampled)
1016
+ resolver.resolve with caching using Cloudflare x 8,795,845 ops/sec ±0.45% (87 runs sampled)
1017
+ resolver.resolve without caching using Cloudflare x 299 ops/sec ±1.31% (69 runs sampled)
1018
+ Fastest without caching is: resolver.resolve without caching using Cloudflare
1019
+ ```
1020
+
1021
+ **reverse:**
1022
+
1023
+ ```text
1024
+ Started: reverse
1025
+ tangerine.reverse GET with caching x 346,432 ops/sec ±0.67% (87 runs sampled)
1026
+ tangerine.reverse GET without caching x 257 ops/sec ±1.24% (83 runs sampled)
1027
+ resolver.reverse with caching x 8,836,675 ops/sec ±0.85% (83 runs sampled)
1028
+ resolver.reverse without caching x 11.00 ops/sec ±189.21% (38 runs sampled)
1029
+ dns.promises.reverse with caching x 8,483,041 ops/sec ±1.55% (84 runs sampled)
1030
+ dns.promises.reverse without caching x 1.79 ops/sec ±162.11% (82 runs sampled)
1031
+ Fastest without caching is: tangerine.reverse GET without caching
1032
+ ```
1033
+
1034
+ ##### Node.js v25.5.0
1035
+
1036
+ **lookup:**
1037
+
1038
+ ```text
1039
+ Started: lookup
1040
+ tangerine.lookup POST with caching using Cloudflare x 336,308 ops/sec ±3.97% (90 runs sampled)
1041
+ tangerine.lookup POST without caching using Cloudflare x 142 ops/sec ±24.14% (42 runs sampled)
1042
+ tangerine.lookup GET with caching using Cloudflare x 331,518 ops/sec ±0.25% (90 runs sampled)
1043
+ tangerine.lookup GET without caching using Cloudflare x 317 ops/sec ±5.55% (84 runs sampled)
1044
+ dns.promises.lookup with caching using Cloudflare x 10,144,557 ops/sec ±0.80% (85 runs sampled)
1045
+ dns.promises.lookup without caching using Cloudflare x 3,269 ops/sec ±0.69% (84 runs sampled)
1046
+ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
1047
+ ```
1048
+
1049
+ **resolve:**
1050
+
1051
+ ```text
1052
+ Started: resolve
1053
+ tangerine.resolve POST with caching using Cloudflare x 1,173,581 ops/sec ±0.26% (90 runs sampled)
1054
+ tangerine.resolve POST without caching using Cloudflare x 336 ops/sec ±1.29% (83 runs sampled)
1055
+ tangerine.resolve GET with caching using Cloudflare x 1,154,032 ops/sec ±0.25% (90 runs sampled)
1056
+ tangerine.resolve GET without caching using Cloudflare x 211 ops/sec ±21.96% (60 runs sampled)
1057
+ tangerine.resolve POST with caching using Google x 1,143,112 ops/sec ±2.40% (87 runs sampled)
1058
+ tangerine.resolve POST without caching using Google x 247 ops/sec ±18.82% (74 runs sampled)
1059
+ tangerine.resolve GET with caching using Google x 1,169,919 ops/sec ±0.28% (90 runs sampled)
1060
+ tangerine.resolve GET without caching using Google x 312 ops/sec ±9.26% (78 runs sampled)
1061
+ resolver.resolve with caching using Cloudflare x 8,264,910 ops/sec ±3.31% (82 runs sampled)
1062
+ resolver.resolve without caching using Cloudflare x 322 ops/sec ±40.27% (68 runs sampled)
1063
+ Fastest without caching is: tangerine.resolve POST without caching using Cloudflare, tangerine.resolve GET without caching using Google
1064
+ ```
1065
+
1066
+ **reverse:**
1067
+
1068
+ ```text
1069
+ Started: reverse
1070
+ tangerine.reverse GET with caching x 337,763 ops/sec ±4.41% (90 runs sampled)
1071
+ tangerine.reverse GET without caching x 334 ops/sec ±1.57% (82 runs sampled)
1072
+ resolver.reverse with caching x 8,764,547 ops/sec ±1.05% (84 runs sampled)
1073
+ resolver.reverse without caching x 20.89 ops/sec ±188.69% (22 runs sampled)
1074
+ dns.promises.reverse with caching x 8,832,576 ops/sec ±0.52% (87 runs sampled)
1075
+ dns.promises.reverse without caching x 6.82 ops/sec ±176.67% (70 runs sampled)
1076
+ Fastest without caching is: tangerine.reverse GET without caching
1077
+ ```
1078
+
1079
+ ##### Node.js v25.6.0
1080
+
1081
+ **lookup:**
1082
+
1083
+ ```text
1084
+ Started: lookup
1085
+ tangerine.lookup POST with caching using Cloudflare x 1,022 ops/sec ±195.41% (85 runs sampled)
1086
+ tangerine.lookup POST without caching using Cloudflare x 46.93 ops/sec ±6.02% (78 runs sampled)
1087
+ tangerine.lookup GET with caching using Cloudflare x 329,068 ops/sec ±1.17% (87 runs sampled)
1088
+ tangerine.lookup GET without caching using Cloudflare x 47.00 ops/sec ±5.36% (77 runs sampled)
1089
+ dns.promises.lookup with caching using Cloudflare x 10,120,744 ops/sec ±2.31% (81 runs sampled)
1090
+ dns.promises.lookup without caching using Cloudflare x 3,219 ops/sec ±0.75% (85 runs sampled)
1091
+ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
1092
+ ```
1093
+
1094
+ **resolve:**
1095
+
1096
+ ```text
1097
+ Started: resolve
1098
+ tangerine.resolve POST with caching using Cloudflare x 1,127 ops/sec ±195.81% (89 runs sampled)
1099
+ tangerine.resolve POST without caching using Cloudflare x 47.64 ops/sec ±5.35% (78 runs sampled)
1100
+ tangerine.resolve GET with caching using Cloudflare x 1,183,153 ops/sec ±0.42% (90 runs sampled)
1101
+ tangerine.resolve GET without caching using Cloudflare x 47.85 ops/sec ±5.73% (79 runs sampled)
1102
+ tangerine.resolve POST with caching using Google x 1,167 ops/sec ±195.80% (90 runs sampled)
1103
+ tangerine.resolve POST without caching using Google x 52.32 ops/sec ±14.47% (67 runs sampled)
1104
+ tangerine.resolve GET with caching using Google x 1,168,373 ops/sec ±2.94% (89 runs sampled)
1105
+ tangerine.resolve GET without caching using Google x 61.96 ops/sec ±11.75% (66 runs sampled)
1106
+ resolver.resolve with caching using Cloudflare x 8,658,908 ops/sec ±0.86% (83 runs sampled)
1107
+ resolver.resolve without caching using Cloudflare x 53.79 ops/sec ±0.56% (84 runs sampled)
1108
+ Fastest without caching is: tangerine.resolve GET without caching using Google
1109
+ ```
1110
+
1111
+ **reverse:**
1112
+
1113
+ ```text
1114
+ Started: reverse
1115
+ tangerine.reverse GET with caching x 1,107 ops/sec ±195.37% (90 runs sampled)
1116
+ tangerine.reverse GET without caching x 47.62 ops/sec ±5.44% (78 runs sampled)
1117
+ resolver.reverse with caching x 8,843,572 ops/sec ±1.73% (86 runs sampled)
1118
+ resolver.reverse without caching x 53.71 ops/sec ±0.41% (84 runs sampled)
1119
+ dns.promises.reverse with caching x 8,869,703 ops/sec ±2.34% (85 runs sampled)
1120
+ dns.promises.reverse without caching x 54.03 ops/sec ±0.34% (85 runs sampled)
1121
+ Fastest without caching is: dns.promises.reverse without caching
1122
+ ```
1123
+
1124
+ ##### Node.js v25.6.1
1125
+
1126
+ **lookup:**
1127
+
1128
+ ```text
1129
+ Started: lookup
1130
+ tangerine.lookup POST with caching using Cloudflare x 1,041 ops/sec ±195.41% (90 runs sampled)
1131
+ tangerine.lookup POST without caching using Cloudflare x 46.90 ops/sec ±7.12% (78 runs sampled)
1132
+ tangerine.lookup GET with caching using Cloudflare x 329,597 ops/sec ±1.03% (89 runs sampled)
1133
+ tangerine.lookup GET without caching using Cloudflare x 47.84 ops/sec ±5.63% (79 runs sampled)
1134
+ dns.promises.lookup with caching using Cloudflare x 10,033,729 ops/sec ±1.66% (86 runs sampled)
1135
+ dns.promises.lookup without caching using Cloudflare x 3,175 ops/sec ±0.61% (87 runs sampled)
1136
+ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
1137
+ ```
1138
+
1139
+ **resolve:**
1140
+
1141
+ ```text
1142
+ Started: resolve
1143
+ tangerine.resolve POST with caching using Cloudflare x 1,066 ops/sec ±195.81% (86 runs sampled)
1144
+ tangerine.resolve POST without caching using Cloudflare x 48.96 ops/sec ±6.55% (81 runs sampled)
1145
+ tangerine.resolve GET with caching using Cloudflare x 1,133,219 ops/sec ±0.30% (90 runs sampled)
1146
+ tangerine.resolve GET without caching using Cloudflare x 47.66 ops/sec ±5.34% (79 runs sampled)
1147
+ tangerine.resolve POST with caching using Google x 1,132 ops/sec ±195.81% (89 runs sampled)
1148
+ tangerine.resolve POST without caching using Google x 48.55 ops/sec ±13.69% (66 runs sampled)
1149
+ tangerine.resolve GET with caching using Google x 1,130,722 ops/sec ±2.88% (89 runs sampled)
1150
+ tangerine.resolve GET without caching using Google x 53.90 ops/sec ±10.91% (67 runs sampled)
1151
+ resolver.resolve with caching using Cloudflare x 8,443,169 ops/sec ±2.56% (86 runs sampled)
1152
+ resolver.resolve without caching using Cloudflare x 53.86 ops/sec ±0.65% (84 runs sampled)
1153
+ Fastest without caching is: resolver.resolve without caching using Cloudflare
1154
+ ```
1155
+
1156
+ **reverse:**
1157
+
1158
+ ```text
1159
+ Started: reverse
1160
+ tangerine.reverse GET with caching x 1,105 ops/sec ±195.39% (89 runs sampled)
1161
+ tangerine.reverse GET without caching x 48.86 ops/sec ±6.37% (80 runs sampled)
1162
+ resolver.reverse with caching x 8,883,018 ops/sec ±0.41% (86 runs sampled)
1163
+ resolver.reverse without caching x 54.19 ops/sec ±0.53% (85 runs sampled)
1164
+ dns.promises.reverse with caching x 8,752,480 ops/sec ±2.56% (86 runs sampled)
1165
+ dns.promises.reverse without caching x 54.28 ops/sec ±0.35% (85 runs sampled)
1166
+ Fastest without caching is: dns.promises.reverse without caching, resolver.reverse without caching
1167
+ ```
1168
+
1169
+ ##### Node.js v25.7.0
1170
+
1171
+ **lookup:**
1172
+
1173
+ ```text
1174
+ Started: lookup
1175
+ tangerine.lookup POST with caching using Cloudflare x 873 ops/sec ±195.51% (87 runs sampled)
1176
+ tangerine.lookup POST without caching using Cloudflare x 58.82 ops/sec ±7.44% (74 runs sampled)
1177
+ tangerine.lookup GET with caching using Cloudflare x 333,990 ops/sec ±1.19% (86 runs sampled)
1178
+ tangerine.lookup GET without caching using Cloudflare x 53.11 ops/sec ±10.66% (70 runs sampled)
1179
+ dns.promises.lookup with caching using Cloudflare x 626 ops/sec ±195.99% (86 runs sampled)
1180
+ dns.promises.lookup without caching using Cloudflare x 2,470 ops/sec ±0.50% (87 runs sampled)
1181
+ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
1182
+ ```
1183
+
1184
+ **resolve:**
1185
+
1186
+ ```text
1187
+ Started: resolve
1188
+ tangerine.resolve POST with caching using Cloudflare x 1,243 ops/sec ±195.79% (89 runs sampled)
1189
+ tangerine.resolve POST without caching using Cloudflare x 57.08 ops/sec ±11.16% (74 runs sampled)
1190
+ tangerine.resolve GET with caching using Cloudflare x 1,132,703 ops/sec ±0.28% (90 runs sampled)
1191
+ tangerine.resolve GET without caching using Cloudflare x 56.05 ops/sec ±5.64% (80 runs sampled)
1192
+ tangerine.resolve POST with caching using Google x 1,497 ops/sec ±195.73% (88 runs sampled)
1193
+ tangerine.resolve POST without caching using Google x 60.56 ops/sec ±7.75% (77 runs sampled)
1194
+ tangerine.resolve GET with caching using Google x 1,096,216 ops/sec ±2.92% (88 runs sampled)
1195
+ tangerine.resolve GET without caching using Google x 41.16 ops/sec ±24.89% (66 runs sampled)
1196
+ resolver.resolve with caching using Cloudflare x 8,732,718 ops/sec ±2.67% (86 runs sampled)
1197
+ resolver.resolve without caching using Cloudflare x 65.84 ops/sec ±1.17% (78 runs sampled)
1198
+ Fastest without caching is: resolver.resolve without caching using Cloudflare, tangerine.resolve POST without caching using Google
1199
+ ```
1200
+
1201
+ **reverse:**
1202
+
1203
+ ```text
1204
+ Started: reverse
1205
+ tangerine.reverse GET with caching x 1,193 ops/sec ±195.33% (88 runs sampled)
1206
+ tangerine.reverse GET without caching x 56.89 ops/sec ±11.00% (73 runs sampled)
1207
+ resolver.reverse with caching x 8,938,970 ops/sec ±0.65% (88 runs sampled)
1208
+ resolver.reverse without caching x 66.14 ops/sec ±2.95% (78 runs sampled)
1209
+ dns.promises.reverse with caching x 8,783,854 ops/sec ±1.77% (87 runs sampled)
1210
+ dns.promises.reverse without caching x 67.14 ops/sec ±0.80% (79 runs sampled)
1211
+ Fastest without caching is: dns.promises.reverse without caching, resolver.reverse without caching
1212
+ ```
1213
+
760
1214
  </details>
761
1215
 
762
1216
  <!-- BENCHMARK_RESULTS_END -->
package/index.d.ts CHANGED
@@ -134,6 +134,13 @@ export type TangerineOptions = {
134
134
  */
135
135
  smartRotate?: boolean;
136
136
 
137
+ /**
138
+ * Whether to resolve against all configured servers in parallel and return
139
+ * the first successful response.
140
+ * @default false
141
+ */
142
+ parallelResolution?: boolean;
143
+
137
144
  /**
138
145
  * Default error message for unsuccessful HTTP responses.
139
146
  * @default 'Unsuccessful HTTP response'
package/index.js CHANGED
@@ -13,6 +13,7 @@ const hostile = require('hostile');
13
13
  const ipaddr = require('ipaddr.js');
14
14
  const isStream = require('is-stream');
15
15
  const mergeOptions = require('merge-options');
16
+ const pAny = require('p-any');
16
17
  const pMap = require('p-map');
17
18
  const pWaitFor = require('p-wait-for');
18
19
  const packet = require('dns-packet');
@@ -451,6 +452,8 @@ class Tangerine extends dns.promises.Resolver {
451
452
  returnHTTPErrors: false,
452
453
  // whether to smart rotate and bump-to-end servers that have issues
453
454
  smartRotate: true,
455
+ // whether to resolve all servers in parallel and use first successful result
456
+ parallelResolution: false,
454
457
  // fallback if status code was not found in http.STATUS_CODES
455
458
  defaultHTTPErrorMessage: 'Unsuccessful HTTP response'
456
459
  },
@@ -490,6 +493,12 @@ class Tangerine extends dns.promises.Resolver {
490
493
  if (!['verbatim', 'ipv4first'].includes(this.options.dnsOrder))
491
494
  throw new Error('DNS order must be either verbatim or ipv4first');
492
495
 
496
+ if (this.options.parallelResolution === undefined)
497
+ this.options.parallelResolution = false;
498
+
499
+ if (typeof this.options.parallelResolution !== 'boolean')
500
+ throw new Error('parallelResolution must be a boolean');
501
+
493
502
  // if `cache: false` then caching is disabled
494
503
  // but note that this doesn't disable `got` dnsCache which is separate
495
504
  // so to turn that off, you need to supply `dnsCache: undefined` in `got` object (?)
@@ -1131,7 +1140,7 @@ class Tangerine extends dns.promises.Resolver {
1131
1140
  }
1132
1141
 
1133
1142
  // <https://github.com/hildjj/dohdec/tree/main/pkg/dohdec>
1134
- // eslint-disable-next-line complexity
1143
+
1135
1144
  async #query(name, rrtype = 'A', ecsSubnet, abortController) {
1136
1145
  if (!dohdec) await pWaitFor(() => Boolean(dohdec));
1137
1146
  debug('query', {
@@ -1158,46 +1167,105 @@ class Tangerine extends dns.promises.Resolver {
1158
1167
  // <https://github.com/nodejs/node/issues/33353#issuecomment-627259827>
1159
1168
  let buffer;
1160
1169
  const errors = [];
1161
- // NOTE: we would have used `p-map-series` but it did not support abort/break
1162
1170
  const servers = [...this.options.servers];
1163
- for (const server of servers) {
1171
+
1172
+ const parseBody = async (body) => {
1173
+ // <https://sindresorhus.com/blog/goodbye-nodejs-buffer>
1174
+ if (Buffer.isBuffer(body)) return body;
1175
+ if (typeof body.arrayBuffer === 'function')
1176
+ return Buffer.from(await body.arrayBuffer());
1177
+ if (isStream(body)) return getStream.buffer(body);
1178
+ const err = new TypeError('Unsupported body type');
1179
+ err.body = body;
1180
+ throw err;
1181
+ };
1182
+
1183
+ const getRequestAbortController = (parallelAbortController) => {
1184
+ if (!parallelAbortController)
1185
+ return {
1186
+ requestAbortController: abortController,
1187
+ cleanupAbortController() {}
1188
+ };
1189
+
1190
+ const requestAbortController = new AbortController();
1191
+ const parentSignal = abortController?.signal;
1192
+ const parallelSignal = parallelAbortController.signal;
1193
+ const onAbort = () => {
1194
+ if (!requestAbortController.signal.aborted)
1195
+ requestAbortController.abort(parentSignal.reason);
1196
+ };
1197
+
1198
+ const onParallelAbort = () => {
1199
+ if (!requestAbortController.signal.aborted)
1200
+ requestAbortController.abort(parallelSignal.reason);
1201
+ };
1202
+
1203
+ if (parentSignal?.aborted) onAbort();
1204
+ else if (parentSignal)
1205
+ parentSignal.addEventListener('abort', onAbort, { once: true });
1206
+
1207
+ if (parallelSignal.aborted) onParallelAbort();
1208
+ else
1209
+ parallelSignal.addEventListener('abort', onParallelAbort, {
1210
+ once: true
1211
+ });
1212
+
1213
+ return {
1214
+ requestAbortController,
1215
+ cleanupAbortController() {
1216
+ parentSignal?.removeEventListener('abort', onAbort);
1217
+ parallelSignal.removeEventListener('abort', onParallelAbort);
1218
+ }
1219
+ };
1220
+ };
1221
+
1222
+ const addServerErrors = (server, ipErrors) => {
1223
+ if (ipErrors.length === 0) return;
1224
+
1225
+ // if the `server` had all errors, then remove it and add to end
1226
+ // (this ensures we don't keep retrying servers that keep timing out)
1227
+ // (which improves upon default c-ares behavior)
1228
+ if (this.options.servers.size > 1 && this.options.smartRotate) {
1229
+ const err = this.constructor.combineErrors([
1230
+ new Error('Rotating DNS servers due to issues'),
1231
+ ...ipErrors
1232
+ ]);
1233
+ this.options.logger.error(err, { server });
1234
+ this.options.servers.delete(server);
1235
+ this.options.servers.add(server);
1236
+ }
1237
+
1238
+ errors.push(...ipErrors);
1239
+ };
1240
+
1241
+ const throwOnNotFound = (err) => {
1242
+ if (err.code === dns.NOTFOUND) throw err;
1243
+ };
1244
+
1245
+ const queryServer = async (server, parallelAbortController) => {
1164
1246
  const ipErrors = [];
1247
+ let lastError;
1248
+
1165
1249
  for (let i = 0; i < this.options.tries; i++) {
1250
+ const { requestAbortController, cleanupAbortController } =
1251
+ getRequestAbortController(parallelAbortController);
1252
+
1166
1253
  try {
1167
- // <https://github.com/sindresorhus/p-map-series/blob/bc1b9f5e19ed62363bff3d7dc5ecc1fd820ccb51/index.js#L1-L11>
1168
1254
  // eslint-disable-next-line no-await-in-loop
1169
1255
  const response = await this.#request(
1170
1256
  pkt,
1171
1257
  server,
1172
- abortController,
1258
+ requestAbortController,
1173
1259
  this.options.timeout * 2 ** i
1174
1260
  );
1175
1261
 
1176
- // if aborted signal then returns early
1177
- // eslint-disable-next-line max-depth
1178
1262
  if (response) {
1179
1263
  const { body, headers } = response;
1180
1264
  const statusCode = response.status || response.statusCode;
1181
1265
  debug('response', { statusCode, headers });
1182
1266
 
1183
- // eslint-disable-next-line max-depth
1184
- if (body && statusCode >= 200 && statusCode < 300) {
1185
- // <https://sindresorhus.com/blog/goodbye-nodejs-buffer>
1186
- // eslint-disable-next-line max-depth
1187
- if (Buffer.isBuffer(body)) buffer = body;
1188
- else if (typeof body.arrayBuffer === 'function')
1189
- // eslint-disable-next-line no-await-in-loop
1190
- buffer = Buffer.from(await body.arrayBuffer());
1191
- // eslint-disable-next-line no-await-in-loop
1192
- else if (isStream(body)) buffer = await getStream.buffer(body);
1193
- else {
1194
- const err = new TypeError('Unsupported body type');
1195
- err.body = body;
1196
- throw err;
1197
- }
1198
-
1199
- break;
1200
- }
1267
+ if (body && statusCode >= 200 && statusCode < 300)
1268
+ return parseBody(body);
1201
1269
 
1202
1270
  // <https://github.com/nodejs/undici/issues/3353>
1203
1271
  if (
@@ -1221,18 +1289,18 @@ class Tangerine extends dns.promises.Resolver {
1221
1289
  }
1222
1290
  } catch (err) {
1223
1291
  debug(err);
1292
+ lastError = err;
1224
1293
 
1225
1294
  //
1226
1295
  // NOTE: if NOTFOUND error occurs then don't attempt further requests
1227
1296
  // <https://nodejs.org/api/dns.html#dnssetserversservers>
1228
1297
  //
1229
1298
 
1230
- if (err.code === dns.NOTFOUND) throw err;
1299
+ throwOnNotFound(err);
1231
1300
 
1232
1301
  if (err.status >= 429) ipErrors.push(err);
1233
1302
 
1234
1303
  // break out of the loop if status code was not retryable
1235
-
1236
1304
  if (
1237
1305
  !(
1238
1306
  err.statusCode &&
@@ -1241,26 +1309,43 @@ class Tangerine extends dns.promises.Resolver {
1241
1309
  !(err.code && this.constructor.RETRY_ERROR_CODES.has(err.code))
1242
1310
  )
1243
1311
  break;
1312
+ } finally {
1313
+ cleanupAbortController();
1244
1314
  }
1245
1315
  }
1246
1316
 
1247
- // break out if we had a response
1248
- if (buffer) break;
1249
- if (ipErrors.length > 0) {
1250
- // if the `server` had all errors, then remove it and add to end
1251
- // (this ensures we don't keep retrying servers that keep timing out)
1252
- // (which improves upon default c-ares behavior)
1253
- if (this.options.servers.size > 1 && this.options.smartRotate) {
1254
- const err = this.constructor.combineErrors([
1255
- new Error('Rotating DNS servers due to issues'),
1256
- ...ipErrors
1257
- ]);
1258
- this.options.logger.error(err, { server });
1259
- this.options.servers.delete(server);
1260
- this.options.servers.add(server);
1261
- }
1317
+ addServerErrors(server, ipErrors);
1318
+
1319
+ throw lastError || new Error(`No response from ${server}`);
1320
+ };
1262
1321
 
1263
- errors.push(...ipErrors);
1322
+ if (this.options.parallelResolution) {
1323
+ const parallelAbortController = new AbortController();
1324
+
1325
+ try {
1326
+ buffer = await pAny(
1327
+ servers.map((server) =>
1328
+ queryServer(server, parallelAbortController)
1329
+ )
1330
+ );
1331
+ } catch (err) {
1332
+ if (errors.length > 0) throw this.constructor.combineErrors(errors);
1333
+ throw err;
1334
+ } finally {
1335
+ if (!parallelAbortController.signal.aborted)
1336
+ parallelAbortController.abort('CANCELLED');
1337
+ }
1338
+ } else {
1339
+ // NOTE: we would have used `p-map-series` but it did not support abort/break
1340
+ for (const server of servers) {
1341
+ try {
1342
+ // eslint-disable-next-line no-await-in-loop
1343
+ buffer = await queryServer(server);
1344
+ break;
1345
+ } catch (err) {
1346
+ throwOnNotFound(err);
1347
+ debug(err);
1348
+ }
1264
1349
  }
1265
1350
  }
1266
1351
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tangerine",
3
3
  "description": "Tangerine is the best Node.js drop-in replacement for dns.promises.Resolver using DNS over HTTPS (\"DoH\") via undici with built-in retries, timeouts, smart server rotation, AbortControllers, and caching support for multiple backends (with TTL and purge support).",
4
- "version": "2.0.2",
4
+ "version": "2.1.0",
5
5
  "author": "Forward Email (https://forwardemail.net)",
6
6
  "bugs": {
7
7
  "url": "https://github.com/forwardemail/nodejs-dns-over-https-tangerine/issues"
@@ -18,6 +18,7 @@
18
18
  "ipaddr.js": "^2.2.0",
19
19
  "is-stream": "2.0.1",
20
20
  "merge-options": "3.0.4",
21
+ "p-any": "3",
21
22
  "p-map": "4",
22
23
  "p-wait-for": "3",
23
24
  "port-numbers": "6.0.1",
@@ -32,7 +33,7 @@
32
33
  "axios": "^1.7.3",
33
34
  "benchmark": "^2.1.4",
34
35
  "cross-env": "^7.0.3",
35
- "eslint": "^9.8.0",
36
+ "eslint": "^8.57.1",
36
37
  "eslint-config-xo-lass": "^2.0.1",
37
38
  "fetch-mock": "^10.1.1",
38
39
  "fixpack": "^4.0.0",