tangerine 2.0.2 → 2.1.1

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 +571 -25
  2. package/index.d.ts +7 -0
  3. package/index.js +142 -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,27 @@ 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-03-05
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 | Mar 4, 2026 |
549
+ | v24.14.0 | linux | x64 | Mar 5, 2026 |
544
550
  | v25.2.1 | linux | x64 | Dec 21, 2025 |
551
+ | v25.3.0 | linux | x64 | Jan 14, 2026 |
552
+ | v25.4.0 | linux | x64 | Jan 20, 2026 |
553
+ | v25.5.0 | linux | x64 | Jan 27, 2026 |
554
+ | v25.6.0 | linux | x64 | Feb 4, 2026 |
555
+ | v25.6.1 | linux | x64 | Feb 11, 2026 |
556
+ | v25.7.0 | linux | x64 | Feb 25, 2026 |
557
+ | v25.8.0 | linux | x64 | Mar 4, 2026 |
545
558
 
546
559
  <details>
547
560
  <summary>Click to expand detailed benchmark results</summary>
@@ -597,12 +610,12 @@ Fastest without caching is: dns.promises.reverse without caching
597
610
 
598
611
  ```text
599
612
  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)
613
+ tangerine.lookup POST with caching using Cloudflare x 820 ops/sec ±195.45% (88 runs sampled)
614
+ tangerine.lookup POST without caching using Cloudflare x 212 ops/sec ±2.98% (79 runs sampled)
615
+ tangerine.lookup GET with caching using Cloudflare x 290,812 ops/sec ±0.74% (88 runs sampled)
616
+ tangerine.lookup GET without caching using Cloudflare x 260 ops/sec ±2.00% (82 runs sampled)
617
+ dns.promises.lookup with caching using Cloudflare x 8,961,323 ops/sec ±0.78% (86 runs sampled)
618
+ dns.promises.lookup without caching using Cloudflare x 3,143 ops/sec ±0.57% (85 runs sampled)
606
619
  Fastest without caching is: dns.promises.lookup without caching using Cloudflare
607
620
  ```
608
621
 
@@ -610,29 +623,67 @@ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
610
623
 
611
624
  ```text
612
625
  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
626
+ tangerine.resolve POST with caching using Cloudflare x 1,034 ops/sec ±195.81% (89 runs sampled)
627
+ tangerine.resolve POST without caching using Cloudflare x 221 ops/sec ±1.83% (80 runs sampled)
628
+ tangerine.resolve GET with caching using Cloudflare x 1,044,036 ops/sec ±0.36% (89 runs sampled)
629
+ tangerine.resolve GET without caching using Cloudflare x 272 ops/sec ±1.57% (83 runs sampled)
630
+ tangerine.resolve POST with caching using Google x 1,035,748 ops/sec ±0.31% (89 runs sampled)
631
+ tangerine.resolve POST without caching using Google x 236 ops/sec ±0.93% (85 runs sampled)
632
+ tangerine.resolve GET with caching using Google x 1,027,998 ops/sec ±0.25% (89 runs sampled)
633
+ tangerine.resolve GET without caching using Google x 230 ops/sec ±1.04% (83 runs sampled)
634
+ resolver.resolve with caching using Cloudflare x 7,717,855 ops/sec ±0.67% (85 runs sampled)
635
+ resolver.resolve without caching using Cloudflare x 24.10 ops/sec ±180.34% (37 runs sampled)
636
+ Fastest without caching is: tangerine.resolve GET without caching using Cloudflare
637
+ ```
638
+
639
+ **reverse:**
640
+
641
+ ```text
642
+ spawnSync /bin/sh ETIMEDOUT
643
+ ```
644
+
645
+ ##### Node.js v20.20.0
646
+
647
+ **lookup:**
648
+
649
+ ```text
650
+ Started: lookup
651
+ tangerine.lookup POST with caching using Cloudflare x 732 ops/sec ±195.51% (87 runs sampled)
652
+ tangerine.lookup POST without caching using Cloudflare x 117 ops/sec ±1.61% (79 runs sampled)
653
+ tangerine.lookup GET with caching using Cloudflare x 299,941 ops/sec ±1.15% (89 runs sampled)
654
+ tangerine.lookup GET without caching using Cloudflare x 117 ops/sec ±1.43% (80 runs sampled)
655
+ dns.promises.lookup with caching using Cloudflare x 1,383 ops/sec ±195.97% (87 runs sampled)
656
+ dns.promises.lookup without caching using Cloudflare x 2,374 ops/sec ±0.56% (86 runs sampled)
657
+ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
658
+ ```
659
+
660
+ **resolve:**
661
+
662
+ ```text
663
+ Started: resolve
664
+ tangerine.resolve POST with caching using Cloudflare x 974 ops/sec ±195.82% (89 runs sampled)
665
+ tangerine.resolve POST without caching using Cloudflare x 130 ops/sec ±1.30% (87 runs sampled)
666
+ tangerine.resolve GET with caching using Cloudflare x 1,049,956 ops/sec ±1.53% (85 runs sampled)
667
+ tangerine.resolve GET without caching using Cloudflare x 117 ops/sec ±1.79% (79 runs sampled)
668
+ tangerine.resolve POST with caching using Google x 1,019,993 ops/sec ±0.48% (88 runs sampled)
669
+ tangerine.resolve POST without caching using Google x 106 ops/sec ±4.78% (83 runs sampled)
670
+ tangerine.resolve GET with caching using Google x 1,030,533 ops/sec ±0.28% (89 runs sampled)
671
+ tangerine.resolve GET without caching using Google x 123 ops/sec ±0.59% (83 runs sampled)
672
+ resolver.resolve with caching using Cloudflare x 7,658,904 ops/sec ±0.60% (86 runs sampled)
673
+ resolver.resolve without caching using Cloudflare x 13.28 ops/sec ±201.81% (11 runs sampled)
674
+ Fastest without caching is: tangerine.resolve POST without caching using Cloudflare
624
675
  ```
625
676
 
626
677
  **reverse:**
627
678
 
628
679
  ```text
629
680
  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)
681
+ tangerine.reverse GET with caching x 991 ops/sec ±195.36% (87 runs sampled)
682
+ tangerine.reverse GET without caching x 129 ops/sec ±0.76% (86 runs sampled)
683
+ resolver.reverse with caching x 0.10 ops/sec ±0.01% (5 runs sampled)
684
+ resolver.reverse without caching x 2.80 ops/sec ±112.00% (74 runs sampled)
685
+ dns.promises.reverse with caching x 7,904,185 ops/sec ±0.60% (85 runs sampled)
686
+ dns.promises.reverse without caching x 8.68 ops/sec ±202.85% (14 runs sampled)
636
687
  Fastest without caching is: tangerine.reverse GET without caching
637
688
  ```
638
689
 
@@ -674,6 +725,51 @@ Fastest without caching is: tangerine.resolve GET without caching using Google
674
725
  spawnSync /bin/sh ETIMEDOUT
675
726
  ```
676
727
 
728
+ ##### Node.js v22.22.0
729
+
730
+ **lookup:**
731
+
732
+ ```text
733
+ Started: lookup
734
+ tangerine.lookup POST with caching using Cloudflare x 1,163 ops/sec ±195.31% (90 runs sampled)
735
+ tangerine.lookup POST without caching using Cloudflare x 54.38 ops/sec ±5.55% (88 runs sampled)
736
+ tangerine.lookup GET with caching using Cloudflare x 313,634 ops/sec ±0.27% (89 runs sampled)
737
+ tangerine.lookup GET without caching using Cloudflare x 55.58 ops/sec ±6.39% (71 runs sampled)
738
+ dns.promises.lookup with caching using Cloudflare x 9,649,217 ops/sec ±0.65% (88 runs sampled)
739
+ dns.promises.lookup without caching using Cloudflare x 3,178 ops/sec ±0.70% (85 runs sampled)
740
+ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
741
+ ```
742
+
743
+ **resolve:**
744
+
745
+ ```text
746
+ Started: resolve
747
+ tangerine.resolve POST with caching using Cloudflare x 1,186 ops/sec ±195.79% (89 runs sampled)
748
+ tangerine.resolve POST without caching using Cloudflare x 55.82 ops/sec ±5.02% (84 runs sampled)
749
+ tangerine.resolve GET with caching using Cloudflare x 1,099,153 ops/sec ±0.22% (90 runs sampled)
750
+ tangerine.resolve GET without caching using Cloudflare x 59.25 ops/sec ±5.73% (73 runs sampled)
751
+ tangerine.resolve POST with caching using Google x 1,095 ops/sec ±195.81% (88 runs sampled)
752
+ tangerine.resolve POST without caching using Google x 60.23 ops/sec ±10.05% (80 runs sampled)
753
+ tangerine.resolve GET with caching using Google x 1,107,393 ops/sec ±0.67% (90 runs sampled)
754
+ tangerine.resolve GET without caching using Google x 54.10 ops/sec ±10.64% (74 runs sampled)
755
+ resolver.resolve with caching using Cloudflare x 8,395,785 ops/sec ±0.65% (89 runs sampled)
756
+ resolver.resolve without caching using Cloudflare x 67.84 ops/sec ±0.83% (80 runs sampled)
757
+ Fastest without caching is: resolver.resolve without caching using Cloudflare, tangerine.resolve POST without caching using Google
758
+ ```
759
+
760
+ **reverse:**
761
+
762
+ ```text
763
+ Started: reverse
764
+ tangerine.reverse GET with caching x 1,114 ops/sec ±195.34% (89 runs sampled)
765
+ tangerine.reverse GET without caching x 55.88 ops/sec ±4.70% (89 runs sampled)
766
+ resolver.reverse with caching x 8,531,724 ops/sec ±0.82% (84 runs sampled)
767
+ resolver.reverse without caching x 68.04 ops/sec ±0.73% (81 runs sampled)
768
+ dns.promises.reverse with caching x 8,533,071 ops/sec ±0.84% (81 runs sampled)
769
+ dns.promises.reverse without caching x 67.08 ops/sec ±0.82% (79 runs sampled)
770
+ Fastest without caching is: resolver.reverse without caching
771
+ ```
772
+
677
773
  ##### Node.js v24.12.0
678
774
 
679
775
  **lookup:**
@@ -712,6 +808,141 @@ Fastest without caching is: tangerine.resolve GET without caching using Google
712
808
  spawnSync /bin/sh ETIMEDOUT
713
809
  ```
714
810
 
811
+ ##### Node.js v24.13.0
812
+
813
+ **lookup:**
814
+
815
+ ```text
816
+ Started: lookup
817
+ tangerine.lookup POST with caching using Cloudflare x 330,489 ops/sec ±1.61% (85 runs sampled)
818
+ tangerine.lookup POST without caching using Cloudflare x 248 ops/sec ±1.98% (80 runs sampled)
819
+ tangerine.lookup GET with caching using Cloudflare x 321,967 ops/sec ±0.26% (90 runs sampled)
820
+ tangerine.lookup GET without caching using Cloudflare x 224 ops/sec ±1.85% (76 runs sampled)
821
+ dns.promises.lookup with caching using Cloudflare x 734 ops/sec ±195.99% (87 runs sampled)
822
+ dns.promises.lookup without caching using Cloudflare x 2,207 ops/sec ±0.51% (85 runs sampled)
823
+ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
824
+ ```
825
+
826
+ **resolve:**
827
+
828
+ ```text
829
+ Started: resolve
830
+ tangerine.resolve POST with caching using Cloudflare x 1,158,368 ops/sec ±0.28% (90 runs sampled)
831
+ tangerine.resolve POST without caching using Cloudflare x 246 ops/sec ±1.93% (80 runs sampled)
832
+ tangerine.resolve GET with caching using Cloudflare x 1,114,486 ops/sec ±0.29% (88 runs sampled)
833
+ tangerine.resolve GET without caching using Cloudflare x 252 ops/sec ±1.20% (85 runs sampled)
834
+ tangerine.resolve POST with caching using Google x 1,134,382 ops/sec ±0.24% (88 runs sampled)
835
+ tangerine.resolve POST without caching using Google x 168 ops/sec ±36.15% (70 runs sampled)
836
+ tangerine.resolve GET with caching using Google x 1,139,056 ops/sec ±0.61% (90 runs sampled)
837
+ tangerine.resolve GET without caching using Google x 297 ops/sec ±2.73% (75 runs sampled)
838
+ resolver.resolve with caching using Cloudflare x 8,289,380 ops/sec ±7.32% (88 runs sampled)
839
+ resolver.resolve without caching using Cloudflare x 316 ops/sec ±1.92% (79 runs sampled)
840
+ Fastest without caching is: resolver.resolve without caching using Cloudflare
841
+ ```
842
+
843
+ **reverse:**
844
+
845
+ ```text
846
+ Started: reverse
847
+ tangerine.reverse GET with caching x 339,770 ops/sec ±0.42% (90 runs sampled)
848
+ tangerine.reverse GET without caching x 240 ops/sec ±2.18% (77 runs sampled)
849
+ resolver.reverse with caching x 8,523,002 ops/sec ±0.83% (87 runs sampled)
850
+ resolver.reverse without caching x 15.57 ops/sec ±194.42% (31 runs sampled)
851
+ dns.promises.reverse with caching x 8,765,472 ops/sec ±1.03% (82 runs sampled)
852
+ dns.promises.reverse without caching x 5.51 ops/sec ±179.54% (80 runs sampled)
853
+ Fastest without caching is: tangerine.reverse GET without caching
854
+ ```
855
+
856
+ ##### Node.js v24.13.1
857
+
858
+ **lookup:**
859
+
860
+ ```text
861
+ Started: lookup
862
+ tangerine.lookup POST with caching using Cloudflare x 333,315 ops/sec ±1.61% (85 runs sampled)
863
+ tangerine.lookup POST without caching using Cloudflare x 227 ops/sec ±1.93% (82 runs sampled)
864
+ tangerine.lookup GET with caching using Cloudflare x 320,901 ops/sec ±0.68% (90 runs sampled)
865
+ tangerine.lookup GET without caching using Cloudflare x 262 ops/sec ±2.60% (81 runs sampled)
866
+ dns.promises.lookup with caching using Cloudflare x 10,104,926 ops/sec ±1.20% (84 runs sampled)
867
+ dns.promises.lookup without caching using Cloudflare x 2,172 ops/sec ±0.39% (86 runs sampled)
868
+ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
869
+ ```
870
+
871
+ **resolve:**
872
+
873
+ ```text
874
+ Started: resolve
875
+ tangerine.resolve POST with caching using Cloudflare x 1,155,993 ops/sec ±0.68% (89 runs sampled)
876
+ tangerine.resolve POST without caching using Cloudflare x 238 ops/sec ±1.44% (82 runs sampled)
877
+ tangerine.resolve GET with caching using Cloudflare x 1,130,246 ops/sec ±0.43% (89 runs sampled)
878
+ tangerine.resolve GET without caching using Cloudflare x 271 ops/sec ±1.13% (85 runs sampled)
879
+ tangerine.resolve POST with caching using Google x 534 ops/sec ±195.91% (89 runs sampled)
880
+ tangerine.resolve POST without caching using Google x 180 ops/sec ±23.75% (71 runs sampled)
881
+ tangerine.resolve GET with caching using Google x 1,127,220 ops/sec ±0.26% (91 runs sampled)
882
+ tangerine.resolve GET without caching using Google x 194 ops/sec ±16.84% (63 runs sampled)
883
+ resolver.resolve with caching using Cloudflare x 8,475,317 ops/sec ±0.93% (83 runs sampled)
884
+ resolver.resolve without caching using Cloudflare x 345 ops/sec ±1.00% (78 runs sampled)
885
+ Fastest without caching is: resolver.resolve without caching using Cloudflare
886
+ ```
887
+
888
+ **reverse:**
889
+
890
+ ```text
891
+ Started: reverse
892
+ tangerine.reverse GET with caching x 331,840 ops/sec ±0.57% (89 runs sampled)
893
+ tangerine.reverse GET without caching x 241 ops/sec ±1.30% (80 runs sampled)
894
+ resolver.reverse with caching x 8,769,670 ops/sec ±0.60% (87 runs sampled)
895
+ resolver.reverse without caching x 24.95 ops/sec ±193.07% (22 runs sampled)
896
+ dns.promises.reverse with caching x 8,895,968 ops/sec ±0.71% (88 runs sampled)
897
+ dns.promises.reverse without caching x 0.05 ops/sec ±112.55% (5 runs sampled)
898
+ Fastest without caching is: tangerine.reverse GET without caching
899
+ ```
900
+
901
+ ##### Node.js v24.14.0
902
+
903
+ **lookup:**
904
+
905
+ ```text
906
+ Started: lookup
907
+ tangerine.lookup POST with caching using Cloudflare x 1,403 ops/sec ±195.16% (90 runs sampled)
908
+ tangerine.lookup POST without caching using Cloudflare x 70.67 ops/sec ±3.34% (84 runs sampled)
909
+ tangerine.lookup GET with caching using Cloudflare x 317,797 ops/sec ±0.25% (91 runs sampled)
910
+ tangerine.lookup GET without caching using Cloudflare x 63.36 ops/sec ±6.51% (78 runs sampled)
911
+ dns.promises.lookup with caching using Cloudflare x 9,932,885 ops/sec ±1.21% (87 runs sampled)
912
+ dns.promises.lookup without caching using Cloudflare x 2,310 ops/sec ±0.39% (89 runs sampled)
913
+ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
914
+ ```
915
+
916
+ **resolve:**
917
+
918
+ ```text
919
+ Started: resolve
920
+ tangerine.resolve POST with caching using Cloudflare x 1,269 ops/sec ±195.78% (89 runs sampled)
921
+ tangerine.resolve POST without caching using Cloudflare x 85.54 ops/sec ±0.68% (81 runs sampled)
922
+ tangerine.resolve GET with caching using Cloudflare x 1,117,794 ops/sec ±0.40% (90 runs sampled)
923
+ tangerine.resolve GET without caching using Cloudflare x 69.12 ops/sec ±5.91% (84 runs sampled)
924
+ tangerine.resolve POST with caching using Google x 1,675 ops/sec ±195.71% (90 runs sampled)
925
+ tangerine.resolve POST without caching using Google x 57.66 ops/sec ±13.41% (79 runs sampled)
926
+ tangerine.resolve GET with caching using Google x 1,117,350 ops/sec ±0.62% (90 runs sampled)
927
+ tangerine.resolve GET without caching using Google x 56.23 ops/sec ±5.75% (72 runs sampled)
928
+ resolver.resolve with caching using Cloudflare x 8,740,674 ops/sec ±0.82% (83 runs sampled)
929
+ resolver.resolve without caching using Cloudflare x 71.31 ops/sec ±1.31% (72 runs sampled)
930
+ Fastest without caching is: tangerine.resolve POST without caching using Cloudflare
931
+ ```
932
+
933
+ **reverse:**
934
+
935
+ ```text
936
+ Started: reverse
937
+ tangerine.reverse GET with caching x 1,286 ops/sec ±195.26% (90 runs sampled)
938
+ tangerine.reverse GET without caching x 85.95 ops/sec ±0.80% (81 runs sampled)
939
+ resolver.reverse with caching x 8,753,410 ops/sec ±0.98% (85 runs sampled)
940
+ resolver.reverse without caching x 72.25 ops/sec ±1.42% (74 runs sampled)
941
+ dns.promises.reverse with caching x 8,707,156 ops/sec ±0.92% (84 runs sampled)
942
+ dns.promises.reverse without caching x 71.90 ops/sec ±1.46% (70 runs sampled)
943
+ Fastest without caching is: tangerine.reverse GET without caching
944
+ ```
945
+
715
946
  ##### Node.js v25.2.1
716
947
 
717
948
  **lookup:**
@@ -757,6 +988,321 @@ dns.promises.reverse without caching x 5.11 ops/sec ±189.52% (76 runs sampled)
757
988
  Fastest without caching is: resolver.reverse without caching, dns.promises.reverse without caching
758
989
  ```
759
990
 
991
+ ##### Node.js v25.3.0
992
+
993
+ **lookup:**
994
+
995
+ ```text
996
+ Started: lookup
997
+ tangerine.lookup POST with caching using Cloudflare x 1,178 ops/sec ±195.33% (88 runs sampled)
998
+ tangerine.lookup POST without caching using Cloudflare x 86.96 ops/sec ±1.37% (82 runs sampled)
999
+ tangerine.lookup GET with caching using Cloudflare x 340,510 ops/sec ±0.25% (90 runs sampled)
1000
+ tangerine.lookup GET without caching using Cloudflare x 89.52 ops/sec ±1.42% (84 runs sampled)
1001
+ dns.promises.lookup with caching using Cloudflare x 1,352 ops/sec ±195.97% (88 runs sampled)
1002
+ dns.promises.lookup without caching using Cloudflare x 3,261 ops/sec ±0.86% (83 runs sampled)
1003
+ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
1004
+ ```
1005
+
1006
+ **resolve:**
1007
+
1008
+ ```text
1009
+ Started: resolve
1010
+ tangerine.resolve POST with caching using Cloudflare x 1,099 ops/sec ±195.82% (89 runs sampled)
1011
+ tangerine.resolve POST without caching using Cloudflare x 88.57 ops/sec ±0.78% (83 runs sampled)
1012
+ tangerine.resolve GET with caching using Cloudflare x 1,182,619 ops/sec ±0.51% (91 runs sampled)
1013
+ tangerine.resolve GET without caching using Cloudflare x 89.35 ops/sec ±9.03% (88 runs sampled)
1014
+ tangerine.resolve POST with caching using Google x 1,174,915 ops/sec ±2.35% (89 runs sampled)
1015
+ tangerine.resolve POST without caching using Google x 129 ops/sec ±9.63% (75 runs sampled)
1016
+ tangerine.resolve GET with caching using Google x 1,187,141 ops/sec ±0.30% (89 runs sampled)
1017
+ tangerine.resolve GET without caching using Google x 195 ops/sec ±0.85% (84 runs sampled)
1018
+ resolver.resolve with caching using Cloudflare x 8,288,775 ops/sec ±10.11% (89 runs sampled)
1019
+ resolver.resolve without caching using Cloudflare x 105 ops/sec ±0.63% (82 runs sampled)
1020
+ Fastest without caching is: tangerine.resolve GET without caching using Google
1021
+ ```
1022
+
1023
+ **reverse:**
1024
+
1025
+ ```text
1026
+ Started: reverse
1027
+ tangerine.reverse GET with caching x 1,146 ops/sec ±195.30% (90 runs sampled)
1028
+ tangerine.reverse GET without caching x 88.05 ops/sec ±0.92% (83 runs sampled)
1029
+ resolver.reverse with caching x 8,812,418 ops/sec ±0.46% (85 runs sampled)
1030
+ resolver.reverse without caching x 3.08 ops/sec ±203.97% (19 runs sampled)
1031
+ dns.promises.reverse with caching x 8,647,756 ops/sec ±2.62% (87 runs sampled)
1032
+ dns.promises.reverse without caching x 0.09 ops/sec ±139.17% (6 runs sampled)
1033
+ Fastest without caching is: tangerine.reverse GET without caching
1034
+ ```
1035
+
1036
+ ##### Node.js v25.4.0
1037
+
1038
+ **lookup:**
1039
+
1040
+ ```text
1041
+ Started: lookup
1042
+ tangerine.lookup POST with caching using Cloudflare x 338,506 ops/sec ±2.96% (86 runs sampled)
1043
+ tangerine.lookup POST without caching using Cloudflare x 252 ops/sec ±1.53% (83 runs sampled)
1044
+ tangerine.lookup GET with caching using Cloudflare x 328,384 ops/sec ±0.24% (89 runs sampled)
1045
+ tangerine.lookup GET without caching using Cloudflare x 256 ops/sec ±2.15% (80 runs sampled)
1046
+ dns.promises.lookup with caching using Cloudflare x 10,091,995 ops/sec ±0.78% (85 runs sampled)
1047
+ dns.promises.lookup without caching using Cloudflare x 3,114 ops/sec ±0.68% (84 runs sampled)
1048
+ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
1049
+ ```
1050
+
1051
+ **resolve:**
1052
+
1053
+ ```text
1054
+ Started: resolve
1055
+ tangerine.resolve POST with caching using Cloudflare x 1,178,120 ops/sec ±0.63% (89 runs sampled)
1056
+ tangerine.resolve POST without caching using Cloudflare x 268 ops/sec ±1.42% (84 runs sampled)
1057
+ tangerine.resolve GET with caching using Cloudflare x 1,158,024 ops/sec ±0.37% (88 runs sampled)
1058
+ tangerine.resolve GET without caching using Cloudflare x 263 ops/sec ±1.56% (82 runs sampled)
1059
+ tangerine.resolve POST with caching using Google x 1,126,115 ops/sec ±2.39% (89 runs sampled)
1060
+ tangerine.resolve POST without caching using Google x 203 ops/sec ±1.10% (85 runs sampled)
1061
+ tangerine.resolve GET with caching using Google x 1,124,263 ops/sec ±3.04% (88 runs sampled)
1062
+ tangerine.resolve GET without caching using Google x 231 ops/sec ±0.71% (85 runs sampled)
1063
+ resolver.resolve with caching using Cloudflare x 8,795,845 ops/sec ±0.45% (87 runs sampled)
1064
+ resolver.resolve without caching using Cloudflare x 299 ops/sec ±1.31% (69 runs sampled)
1065
+ Fastest without caching is: resolver.resolve without caching using Cloudflare
1066
+ ```
1067
+
1068
+ **reverse:**
1069
+
1070
+ ```text
1071
+ Started: reverse
1072
+ tangerine.reverse GET with caching x 346,432 ops/sec ±0.67% (87 runs sampled)
1073
+ tangerine.reverse GET without caching x 257 ops/sec ±1.24% (83 runs sampled)
1074
+ resolver.reverse with caching x 8,836,675 ops/sec ±0.85% (83 runs sampled)
1075
+ resolver.reverse without caching x 11.00 ops/sec ±189.21% (38 runs sampled)
1076
+ dns.promises.reverse with caching x 8,483,041 ops/sec ±1.55% (84 runs sampled)
1077
+ dns.promises.reverse without caching x 1.79 ops/sec ±162.11% (82 runs sampled)
1078
+ Fastest without caching is: tangerine.reverse GET without caching
1079
+ ```
1080
+
1081
+ ##### Node.js v25.5.0
1082
+
1083
+ **lookup:**
1084
+
1085
+ ```text
1086
+ Started: lookup
1087
+ tangerine.lookup POST with caching using Cloudflare x 336,308 ops/sec ±3.97% (90 runs sampled)
1088
+ tangerine.lookup POST without caching using Cloudflare x 142 ops/sec ±24.14% (42 runs sampled)
1089
+ tangerine.lookup GET with caching using Cloudflare x 331,518 ops/sec ±0.25% (90 runs sampled)
1090
+ tangerine.lookup GET without caching using Cloudflare x 317 ops/sec ±5.55% (84 runs sampled)
1091
+ dns.promises.lookup with caching using Cloudflare x 10,144,557 ops/sec ±0.80% (85 runs sampled)
1092
+ dns.promises.lookup without caching using Cloudflare x 3,269 ops/sec ±0.69% (84 runs sampled)
1093
+ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
1094
+ ```
1095
+
1096
+ **resolve:**
1097
+
1098
+ ```text
1099
+ Started: resolve
1100
+ tangerine.resolve POST with caching using Cloudflare x 1,173,581 ops/sec ±0.26% (90 runs sampled)
1101
+ tangerine.resolve POST without caching using Cloudflare x 336 ops/sec ±1.29% (83 runs sampled)
1102
+ tangerine.resolve GET with caching using Cloudflare x 1,154,032 ops/sec ±0.25% (90 runs sampled)
1103
+ tangerine.resolve GET without caching using Cloudflare x 211 ops/sec ±21.96% (60 runs sampled)
1104
+ tangerine.resolve POST with caching using Google x 1,143,112 ops/sec ±2.40% (87 runs sampled)
1105
+ tangerine.resolve POST without caching using Google x 247 ops/sec ±18.82% (74 runs sampled)
1106
+ tangerine.resolve GET with caching using Google x 1,169,919 ops/sec ±0.28% (90 runs sampled)
1107
+ tangerine.resolve GET without caching using Google x 312 ops/sec ±9.26% (78 runs sampled)
1108
+ resolver.resolve with caching using Cloudflare x 8,264,910 ops/sec ±3.31% (82 runs sampled)
1109
+ resolver.resolve without caching using Cloudflare x 322 ops/sec ±40.27% (68 runs sampled)
1110
+ Fastest without caching is: tangerine.resolve POST without caching using Cloudflare, tangerine.resolve GET without caching using Google
1111
+ ```
1112
+
1113
+ **reverse:**
1114
+
1115
+ ```text
1116
+ Started: reverse
1117
+ tangerine.reverse GET with caching x 337,763 ops/sec ±4.41% (90 runs sampled)
1118
+ tangerine.reverse GET without caching x 334 ops/sec ±1.57% (82 runs sampled)
1119
+ resolver.reverse with caching x 8,764,547 ops/sec ±1.05% (84 runs sampled)
1120
+ resolver.reverse without caching x 20.89 ops/sec ±188.69% (22 runs sampled)
1121
+ dns.promises.reverse with caching x 8,832,576 ops/sec ±0.52% (87 runs sampled)
1122
+ dns.promises.reverse without caching x 6.82 ops/sec ±176.67% (70 runs sampled)
1123
+ Fastest without caching is: tangerine.reverse GET without caching
1124
+ ```
1125
+
1126
+ ##### Node.js v25.6.0
1127
+
1128
+ **lookup:**
1129
+
1130
+ ```text
1131
+ Started: lookup
1132
+ tangerine.lookup POST with caching using Cloudflare x 1,022 ops/sec ±195.41% (85 runs sampled)
1133
+ tangerine.lookup POST without caching using Cloudflare x 46.93 ops/sec ±6.02% (78 runs sampled)
1134
+ tangerine.lookup GET with caching using Cloudflare x 329,068 ops/sec ±1.17% (87 runs sampled)
1135
+ tangerine.lookup GET without caching using Cloudflare x 47.00 ops/sec ±5.36% (77 runs sampled)
1136
+ dns.promises.lookup with caching using Cloudflare x 10,120,744 ops/sec ±2.31% (81 runs sampled)
1137
+ dns.promises.lookup without caching using Cloudflare x 3,219 ops/sec ±0.75% (85 runs sampled)
1138
+ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
1139
+ ```
1140
+
1141
+ **resolve:**
1142
+
1143
+ ```text
1144
+ Started: resolve
1145
+ tangerine.resolve POST with caching using Cloudflare x 1,127 ops/sec ±195.81% (89 runs sampled)
1146
+ tangerine.resolve POST without caching using Cloudflare x 47.64 ops/sec ±5.35% (78 runs sampled)
1147
+ tangerine.resolve GET with caching using Cloudflare x 1,183,153 ops/sec ±0.42% (90 runs sampled)
1148
+ tangerine.resolve GET without caching using Cloudflare x 47.85 ops/sec ±5.73% (79 runs sampled)
1149
+ tangerine.resolve POST with caching using Google x 1,167 ops/sec ±195.80% (90 runs sampled)
1150
+ tangerine.resolve POST without caching using Google x 52.32 ops/sec ±14.47% (67 runs sampled)
1151
+ tangerine.resolve GET with caching using Google x 1,168,373 ops/sec ±2.94% (89 runs sampled)
1152
+ tangerine.resolve GET without caching using Google x 61.96 ops/sec ±11.75% (66 runs sampled)
1153
+ resolver.resolve with caching using Cloudflare x 8,658,908 ops/sec ±0.86% (83 runs sampled)
1154
+ resolver.resolve without caching using Cloudflare x 53.79 ops/sec ±0.56% (84 runs sampled)
1155
+ Fastest without caching is: tangerine.resolve GET without caching using Google
1156
+ ```
1157
+
1158
+ **reverse:**
1159
+
1160
+ ```text
1161
+ Started: reverse
1162
+ tangerine.reverse GET with caching x 1,107 ops/sec ±195.37% (90 runs sampled)
1163
+ tangerine.reverse GET without caching x 47.62 ops/sec ±5.44% (78 runs sampled)
1164
+ resolver.reverse with caching x 8,843,572 ops/sec ±1.73% (86 runs sampled)
1165
+ resolver.reverse without caching x 53.71 ops/sec ±0.41% (84 runs sampled)
1166
+ dns.promises.reverse with caching x 8,869,703 ops/sec ±2.34% (85 runs sampled)
1167
+ dns.promises.reverse without caching x 54.03 ops/sec ±0.34% (85 runs sampled)
1168
+ Fastest without caching is: dns.promises.reverse without caching
1169
+ ```
1170
+
1171
+ ##### Node.js v25.6.1
1172
+
1173
+ **lookup:**
1174
+
1175
+ ```text
1176
+ Started: lookup
1177
+ tangerine.lookup POST with caching using Cloudflare x 1,041 ops/sec ±195.41% (90 runs sampled)
1178
+ tangerine.lookup POST without caching using Cloudflare x 46.90 ops/sec ±7.12% (78 runs sampled)
1179
+ tangerine.lookup GET with caching using Cloudflare x 329,597 ops/sec ±1.03% (89 runs sampled)
1180
+ tangerine.lookup GET without caching using Cloudflare x 47.84 ops/sec ±5.63% (79 runs sampled)
1181
+ dns.promises.lookup with caching using Cloudflare x 10,033,729 ops/sec ±1.66% (86 runs sampled)
1182
+ dns.promises.lookup without caching using Cloudflare x 3,175 ops/sec ±0.61% (87 runs sampled)
1183
+ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
1184
+ ```
1185
+
1186
+ **resolve:**
1187
+
1188
+ ```text
1189
+ Started: resolve
1190
+ tangerine.resolve POST with caching using Cloudflare x 1,066 ops/sec ±195.81% (86 runs sampled)
1191
+ tangerine.resolve POST without caching using Cloudflare x 48.96 ops/sec ±6.55% (81 runs sampled)
1192
+ tangerine.resolve GET with caching using Cloudflare x 1,133,219 ops/sec ±0.30% (90 runs sampled)
1193
+ tangerine.resolve GET without caching using Cloudflare x 47.66 ops/sec ±5.34% (79 runs sampled)
1194
+ tangerine.resolve POST with caching using Google x 1,132 ops/sec ±195.81% (89 runs sampled)
1195
+ tangerine.resolve POST without caching using Google x 48.55 ops/sec ±13.69% (66 runs sampled)
1196
+ tangerine.resolve GET with caching using Google x 1,130,722 ops/sec ±2.88% (89 runs sampled)
1197
+ tangerine.resolve GET without caching using Google x 53.90 ops/sec ±10.91% (67 runs sampled)
1198
+ resolver.resolve with caching using Cloudflare x 8,443,169 ops/sec ±2.56% (86 runs sampled)
1199
+ resolver.resolve without caching using Cloudflare x 53.86 ops/sec ±0.65% (84 runs sampled)
1200
+ Fastest without caching is: resolver.resolve without caching using Cloudflare
1201
+ ```
1202
+
1203
+ **reverse:**
1204
+
1205
+ ```text
1206
+ Started: reverse
1207
+ tangerine.reverse GET with caching x 1,105 ops/sec ±195.39% (89 runs sampled)
1208
+ tangerine.reverse GET without caching x 48.86 ops/sec ±6.37% (80 runs sampled)
1209
+ resolver.reverse with caching x 8,883,018 ops/sec ±0.41% (86 runs sampled)
1210
+ resolver.reverse without caching x 54.19 ops/sec ±0.53% (85 runs sampled)
1211
+ dns.promises.reverse with caching x 8,752,480 ops/sec ±2.56% (86 runs sampled)
1212
+ dns.promises.reverse without caching x 54.28 ops/sec ±0.35% (85 runs sampled)
1213
+ Fastest without caching is: dns.promises.reverse without caching, resolver.reverse without caching
1214
+ ```
1215
+
1216
+ ##### Node.js v25.7.0
1217
+
1218
+ **lookup:**
1219
+
1220
+ ```text
1221
+ Started: lookup
1222
+ tangerine.lookup POST with caching using Cloudflare x 873 ops/sec ±195.51% (87 runs sampled)
1223
+ tangerine.lookup POST without caching using Cloudflare x 58.82 ops/sec ±7.44% (74 runs sampled)
1224
+ tangerine.lookup GET with caching using Cloudflare x 333,990 ops/sec ±1.19% (86 runs sampled)
1225
+ tangerine.lookup GET without caching using Cloudflare x 53.11 ops/sec ±10.66% (70 runs sampled)
1226
+ dns.promises.lookup with caching using Cloudflare x 626 ops/sec ±195.99% (86 runs sampled)
1227
+ dns.promises.lookup without caching using Cloudflare x 2,470 ops/sec ±0.50% (87 runs sampled)
1228
+ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
1229
+ ```
1230
+
1231
+ **resolve:**
1232
+
1233
+ ```text
1234
+ Started: resolve
1235
+ tangerine.resolve POST with caching using Cloudflare x 1,243 ops/sec ±195.79% (89 runs sampled)
1236
+ tangerine.resolve POST without caching using Cloudflare x 57.08 ops/sec ±11.16% (74 runs sampled)
1237
+ tangerine.resolve GET with caching using Cloudflare x 1,132,703 ops/sec ±0.28% (90 runs sampled)
1238
+ tangerine.resolve GET without caching using Cloudflare x 56.05 ops/sec ±5.64% (80 runs sampled)
1239
+ tangerine.resolve POST with caching using Google x 1,497 ops/sec ±195.73% (88 runs sampled)
1240
+ tangerine.resolve POST without caching using Google x 60.56 ops/sec ±7.75% (77 runs sampled)
1241
+ tangerine.resolve GET with caching using Google x 1,096,216 ops/sec ±2.92% (88 runs sampled)
1242
+ tangerine.resolve GET without caching using Google x 41.16 ops/sec ±24.89% (66 runs sampled)
1243
+ resolver.resolve with caching using Cloudflare x 8,732,718 ops/sec ±2.67% (86 runs sampled)
1244
+ resolver.resolve without caching using Cloudflare x 65.84 ops/sec ±1.17% (78 runs sampled)
1245
+ Fastest without caching is: resolver.resolve without caching using Cloudflare, tangerine.resolve POST without caching using Google
1246
+ ```
1247
+
1248
+ **reverse:**
1249
+
1250
+ ```text
1251
+ Started: reverse
1252
+ tangerine.reverse GET with caching x 1,193 ops/sec ±195.33% (88 runs sampled)
1253
+ tangerine.reverse GET without caching x 56.89 ops/sec ±11.00% (73 runs sampled)
1254
+ resolver.reverse with caching x 8,938,970 ops/sec ±0.65% (88 runs sampled)
1255
+ resolver.reverse without caching x 66.14 ops/sec ±2.95% (78 runs sampled)
1256
+ dns.promises.reverse with caching x 8,783,854 ops/sec ±1.77% (87 runs sampled)
1257
+ dns.promises.reverse without caching x 67.14 ops/sec ±0.80% (79 runs sampled)
1258
+ Fastest without caching is: dns.promises.reverse without caching, resolver.reverse without caching
1259
+ ```
1260
+
1261
+ ##### Node.js v25.8.0
1262
+
1263
+ **lookup:**
1264
+
1265
+ ```text
1266
+ Started: lookup
1267
+ tangerine.lookup POST with caching using Cloudflare x 348,234 ops/sec ±1.43% (89 runs sampled)
1268
+ tangerine.lookup POST without caching using Cloudflare x 245 ops/sec ±1.75% (83 runs sampled)
1269
+ tangerine.lookup GET with caching using Cloudflare x 340,115 ops/sec ±0.72% (89 runs sampled)
1270
+ tangerine.lookup GET without caching using Cloudflare x 223 ops/sec ±2.25% (81 runs sampled)
1271
+ dns.promises.lookup with caching using Cloudflare x 1,759 ops/sec ±195.97% (88 runs sampled)
1272
+ dns.promises.lookup without caching using Cloudflare x 2,223 ops/sec ±0.55% (87 runs sampled)
1273
+ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
1274
+ ```
1275
+
1276
+ **resolve:**
1277
+
1278
+ ```text
1279
+ Started: resolve
1280
+ tangerine.resolve POST with caching using Cloudflare x 1,224,642 ops/sec ±0.58% (87 runs sampled)
1281
+ tangerine.resolve POST without caching using Cloudflare x 248 ops/sec ±1.54% (83 runs sampled)
1282
+ tangerine.resolve GET with caching using Cloudflare x 1,168,649 ops/sec ±0.55% (85 runs sampled)
1283
+ tangerine.resolve GET without caching using Cloudflare x 264 ops/sec ±1.04% (82 runs sampled)
1284
+ tangerine.resolve POST with caching using Google x 1,411 ops/sec ±195.76% (87 runs sampled)
1285
+ tangerine.resolve POST without caching using Google x 178 ops/sec ±24.83% (67 runs sampled)
1286
+ tangerine.resolve GET with caching using Google x 1,188,395 ops/sec ±0.40% (88 runs sampled)
1287
+ tangerine.resolve GET without caching using Google x 237 ops/sec ±14.93% (74 runs sampled)
1288
+ resolver.resolve with caching using Cloudflare x 8.39 ops/sec ±196.00% (84 runs sampled)
1289
+ resolver.resolve without caching using Cloudflare x 109 ops/sec ±113.88% (68 runs sampled)
1290
+ Fastest without caching is: tangerine.resolve GET without caching using Cloudflare, tangerine.resolve POST without caching using Google
1291
+ ```
1292
+
1293
+ **reverse:**
1294
+
1295
+ ```text
1296
+ Started: reverse
1297
+ tangerine.reverse GET with caching x 346,448 ops/sec ±4.35% (86 runs sampled)
1298
+ tangerine.reverse GET without caching x 259 ops/sec ±1.42% (83 runs sampled)
1299
+ resolver.reverse with caching x 9,199,217 ops/sec ±0.52% (88 runs sampled)
1300
+ resolver.reverse without caching x 13.67 ops/sec ±188.06% (45 runs sampled)
1301
+ dns.promises.reverse with caching x 9,227,637 ops/sec ±0.33% (89 runs sampled)
1302
+ dns.promises.reverse without caching x 0.07 ops/sec ±133.35% (5 runs sampled)
1303
+ Fastest without caching is: tangerine.reverse GET without caching
1304
+ ```
1305
+
760
1306
  </details>
761
1307
 
762
1308
  <!-- 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
 
@@ -1943,6 +2028,20 @@ class Tangerine extends dns.promises.Resolver {
1943
2028
  if (result.answers.length === 0 && !options.noThrowOnNODATA)
1944
2029
  throw this.constructor.createError(name, rrtype, dns.NODATA);
1945
2030
 
2031
+ //
2032
+ // RFC 7672 Section 2.2.2: Expose DNSSEC validation status (AD flag)
2033
+ // When options.dnssecSecure is true, return an object with the answers
2034
+ // and a boolean indicating whether the response was DNSSEC-validated.
2035
+ // This allows callers (e.g. mx-connect DANE) to check if the MX host's
2036
+ // zone is signed before attempting TLSA lookups.
2037
+ //
2038
+ if (options?.dnssecSecure) {
2039
+ return {
2040
+ secure: Boolean(result.flag_ad),
2041
+ answers: result.answers
2042
+ };
2043
+ }
2044
+
1946
2045
  // filter the answers for the same type
1947
2046
  result.answers = result.answers.filter((answer) => answer.type === rrtype);
1948
2047
 
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.1",
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",