tangerine 2.0.1 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -15
- package/index.d.ts +653 -0
- package/index.js +170 -1
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -550,7 +550,7 @@ We have written extensive benchmarks to show that :tangerine: Tangerine is as fa
|
|
|
550
550
|
|
|
551
551
|
**lookup:**
|
|
552
552
|
|
|
553
|
-
```
|
|
553
|
+
```text
|
|
554
554
|
Started: lookup
|
|
555
555
|
tangerine.lookup POST with caching using Cloudflare x 757 ops/sec ±195.51% (88 runs sampled)
|
|
556
556
|
tangerine.lookup POST without caching using Cloudflare x 120 ops/sec ±1.43% (81 runs sampled)
|
|
@@ -563,7 +563,7 @@ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
|
|
|
563
563
|
|
|
564
564
|
**resolve:**
|
|
565
565
|
|
|
566
|
-
```
|
|
566
|
+
```text
|
|
567
567
|
Started: resolve
|
|
568
568
|
tangerine.resolve POST with caching using Cloudflare x 953 ops/sec ±195.82% (88 runs sampled)
|
|
569
569
|
tangerine.resolve POST without caching using Cloudflare x 116 ops/sec ±1.16% (80 runs sampled)
|
|
@@ -580,7 +580,7 @@ Fastest without caching is: tangerine.resolve POST without caching using Google
|
|
|
580
580
|
|
|
581
581
|
**reverse:**
|
|
582
582
|
|
|
583
|
-
```
|
|
583
|
+
```text
|
|
584
584
|
Started: reverse
|
|
585
585
|
tangerine.reverse GET with caching x 628 ops/sec ±195.51% (84 runs sampled)
|
|
586
586
|
tangerine.reverse GET without caching x 118 ops/sec ±1.12% (80 runs sampled)
|
|
@@ -595,7 +595,7 @@ Fastest without caching is: dns.promises.reverse without caching
|
|
|
595
595
|
|
|
596
596
|
**lookup:**
|
|
597
597
|
|
|
598
|
-
```
|
|
598
|
+
```text
|
|
599
599
|
Started: lookup
|
|
600
600
|
tangerine.lookup POST with caching using Cloudflare x 795 ops/sec ±195.51% (87 runs sampled)
|
|
601
601
|
tangerine.lookup POST without caching using Cloudflare x 306 ops/sec ±1.81% (83 runs sampled)
|
|
@@ -608,7 +608,7 @@ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
|
|
|
608
608
|
|
|
609
609
|
**resolve:**
|
|
610
610
|
|
|
611
|
-
```
|
|
611
|
+
```text
|
|
612
612
|
Started: resolve
|
|
613
613
|
tangerine.resolve POST with caching using Cloudflare x 1,016 ops/sec ±195.82% (89 runs sampled)
|
|
614
614
|
tangerine.resolve POST without caching using Cloudflare x 303 ops/sec ±3.25% (79 runs sampled)
|
|
@@ -625,7 +625,7 @@ Fastest without caching is: tangerine.resolve GET without caching using Google
|
|
|
625
625
|
|
|
626
626
|
**reverse:**
|
|
627
627
|
|
|
628
|
-
```
|
|
628
|
+
```text
|
|
629
629
|
Started: reverse
|
|
630
630
|
tangerine.reverse GET with caching x 1,002 ops/sec ±195.36% (87 runs sampled)
|
|
631
631
|
tangerine.reverse GET without caching x 323 ops/sec ±1.36% (85 runs sampled)
|
|
@@ -640,7 +640,7 @@ Fastest without caching is: tangerine.reverse GET without caching
|
|
|
640
640
|
|
|
641
641
|
**lookup:**
|
|
642
642
|
|
|
643
|
-
```
|
|
643
|
+
```text
|
|
644
644
|
Started: lookup
|
|
645
645
|
tangerine.lookup POST with caching using Cloudflare x 330,006 ops/sec ±7.57% (90 runs sampled)
|
|
646
646
|
tangerine.lookup POST without caching using Cloudflare x 287 ops/sec ±1.96% (84 runs sampled)
|
|
@@ -653,7 +653,7 @@ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
|
|
|
653
653
|
|
|
654
654
|
**resolve:**
|
|
655
655
|
|
|
656
|
-
```
|
|
656
|
+
```text
|
|
657
657
|
Started: resolve
|
|
658
658
|
tangerine.resolve POST with caching using Cloudflare x 1,150,690 ops/sec ±0.43% (90 runs sampled)
|
|
659
659
|
tangerine.resolve POST without caching using Cloudflare x 284 ops/sec ±1.36% (82 runs sampled)
|
|
@@ -670,7 +670,7 @@ Fastest without caching is: tangerine.resolve GET without caching using Google
|
|
|
670
670
|
|
|
671
671
|
**reverse:**
|
|
672
672
|
|
|
673
|
-
```
|
|
673
|
+
```text
|
|
674
674
|
spawnSync /bin/sh ETIMEDOUT
|
|
675
675
|
```
|
|
676
676
|
|
|
@@ -678,7 +678,7 @@ spawnSync /bin/sh ETIMEDOUT
|
|
|
678
678
|
|
|
679
679
|
**lookup:**
|
|
680
680
|
|
|
681
|
-
```
|
|
681
|
+
```text
|
|
682
682
|
Started: lookup
|
|
683
683
|
tangerine.lookup POST with caching using Cloudflare x 1,775 ops/sec ±194.98% (90 runs sampled)
|
|
684
684
|
tangerine.lookup POST without caching using Cloudflare x 295 ops/sec ±10.41% (81 runs sampled)
|
|
@@ -691,7 +691,7 @@ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
|
|
|
691
691
|
|
|
692
692
|
**resolve:**
|
|
693
693
|
|
|
694
|
-
```
|
|
694
|
+
```text
|
|
695
695
|
Started: resolve
|
|
696
696
|
tangerine.resolve POST with caching using Cloudflare x 1,164,729 ops/sec ±0.27% (90 runs sampled)
|
|
697
697
|
tangerine.resolve POST without caching using Cloudflare x 316 ops/sec ±1.55% (82 runs sampled)
|
|
@@ -708,7 +708,7 @@ Fastest without caching is: tangerine.resolve GET without caching using Google
|
|
|
708
708
|
|
|
709
709
|
**reverse:**
|
|
710
710
|
|
|
711
|
-
```
|
|
711
|
+
```text
|
|
712
712
|
spawnSync /bin/sh ETIMEDOUT
|
|
713
713
|
```
|
|
714
714
|
|
|
@@ -716,7 +716,7 @@ spawnSync /bin/sh ETIMEDOUT
|
|
|
716
716
|
|
|
717
717
|
**lookup:**
|
|
718
718
|
|
|
719
|
-
```
|
|
719
|
+
```text
|
|
720
720
|
Started: lookup
|
|
721
721
|
tangerine.lookup POST with caching using Cloudflare x 1,504 ops/sec ±195.19% (89 runs sampled)
|
|
722
722
|
tangerine.lookup POST without caching using Cloudflare x 118 ops/sec ±2.46% (81 runs sampled)
|
|
@@ -729,7 +729,7 @@ Fastest without caching is: dns.promises.lookup without caching using Cloudflare
|
|
|
729
729
|
|
|
730
730
|
**resolve:**
|
|
731
731
|
|
|
732
|
-
```
|
|
732
|
+
```text
|
|
733
733
|
Started: resolve
|
|
734
734
|
tangerine.resolve POST with caching using Cloudflare x 1,200,168 ops/sec ±1.70% (90 runs sampled)
|
|
735
735
|
tangerine.resolve POST without caching using Cloudflare x 132 ops/sec ±0.46% (88 runs sampled)
|
|
@@ -746,7 +746,7 @@ Fastest without caching is: resolver.resolve without caching using Cloudflare
|
|
|
746
746
|
|
|
747
747
|
**reverse:**
|
|
748
748
|
|
|
749
|
-
```
|
|
749
|
+
```text
|
|
750
750
|
Started: reverse
|
|
751
751
|
tangerine.reverse GET with caching x 342,190 ops/sec ±8.40% (90 runs sampled)
|
|
752
752
|
tangerine.reverse GET without caching x 128 ops/sec ±0.83% (86 runs sampled)
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
// Type definitions for tangerine
|
|
2
|
+
// Project: https://github.com/forwardemail/tangerine
|
|
3
|
+
// Definitions by: Forward Email <https://forwardemail.net>
|
|
4
|
+
|
|
5
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
6
|
+
/* eslint-disable @typescript-eslint/member-ordering */
|
|
7
|
+
|
|
8
|
+
import { Resolver } from 'node:dns/promises';
|
|
9
|
+
import type { LookupAddress, LookupOptions } from 'node:dns';
|
|
10
|
+
|
|
11
|
+
export type TangerineOptions = {
|
|
12
|
+
/**
|
|
13
|
+
* Timeout in milliseconds for DNS queries.
|
|
14
|
+
* @default 5000
|
|
15
|
+
*/
|
|
16
|
+
timeout?: number;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Number of retry attempts for DNS queries.
|
|
20
|
+
* @default 4
|
|
21
|
+
*/
|
|
22
|
+
tries?: number;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* DNS-over-HTTPS servers to use.
|
|
26
|
+
* @default new Set(['1.1.1.1', '1.0.0.1'])
|
|
27
|
+
*/
|
|
28
|
+
servers?: Set<string> | string[];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Request options passed to the HTTP client.
|
|
32
|
+
*/
|
|
33
|
+
requestOptions?: {
|
|
34
|
+
method?: 'GET' | 'POST' | 'get' | 'post';
|
|
35
|
+
headers?: Record<string, string>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Protocol to use for DNS-over-HTTPS requests.
|
|
40
|
+
* @default 'https'
|
|
41
|
+
*/
|
|
42
|
+
protocol?: 'http' | 'https';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* DNS result ordering.
|
|
46
|
+
* @default 'verbatim' for Node.js >= 17.0.0, 'ipv4first' otherwise
|
|
47
|
+
*/
|
|
48
|
+
dnsOrder?: 'verbatim' | 'ipv4first';
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Logger instance for debugging.
|
|
52
|
+
* Set to `false` to disable logging.
|
|
53
|
+
* @default false
|
|
54
|
+
*/
|
|
55
|
+
logger?:
|
|
56
|
+
| false
|
|
57
|
+
| {
|
|
58
|
+
info: (...args: unknown[]) => void;
|
|
59
|
+
warn: (...args: unknown[]) => void;
|
|
60
|
+
error: (...args: unknown[]) => void;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* ID generator for DNS packets.
|
|
65
|
+
* Can be a number or a function that returns a number (sync or async).
|
|
66
|
+
* @default 0
|
|
67
|
+
*/
|
|
68
|
+
id?: number | (() => number) | (() => Promise<number>);
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Concurrency limit for resolveAny queries.
|
|
72
|
+
* @default os.cpus().length
|
|
73
|
+
*/
|
|
74
|
+
concurrency?: number;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Default IPv4 address for local binding.
|
|
78
|
+
* @default '0.0.0.0'
|
|
79
|
+
*/
|
|
80
|
+
ipv4?: string;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Default IPv6 address for local binding.
|
|
84
|
+
* @default '::0'
|
|
85
|
+
*/
|
|
86
|
+
ipv6?: string;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Port for IPv4 local binding.
|
|
90
|
+
*/
|
|
91
|
+
ipv4Port?: number;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Port for IPv6 local binding.
|
|
95
|
+
*/
|
|
96
|
+
ipv6Port?: number;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Cache instance for storing DNS results.
|
|
100
|
+
* Set to `false` to disable caching.
|
|
101
|
+
* @default new Map()
|
|
102
|
+
*/
|
|
103
|
+
cache?: Map<string, unknown> | false;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Default TTL in seconds for cached DNS results.
|
|
107
|
+
* @default 300
|
|
108
|
+
*/
|
|
109
|
+
defaultTTLSeconds?: number;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Maximum TTL in seconds for cached DNS results.
|
|
113
|
+
* @default 86400
|
|
114
|
+
*/
|
|
115
|
+
maxTTLSeconds?: number;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Function to generate cache arguments.
|
|
119
|
+
*/
|
|
120
|
+
setCacheArgs?: (
|
|
121
|
+
key: string,
|
|
122
|
+
result: { expires: number; ttl: number }
|
|
123
|
+
) => unknown[];
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Whether to return HTTP errors as DNS errors.
|
|
127
|
+
* @default false
|
|
128
|
+
*/
|
|
129
|
+
returnHTTPErrors?: boolean;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Whether to rotate servers on errors.
|
|
133
|
+
* @default true
|
|
134
|
+
*/
|
|
135
|
+
smartRotate?: boolean;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Default error message for unsuccessful HTTP responses.
|
|
139
|
+
* @default 'Unsuccessful HTTP response'
|
|
140
|
+
*/
|
|
141
|
+
defaultHTTPErrorMessage?: string;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export type DnsRecordType =
|
|
145
|
+
| 'A'
|
|
146
|
+
| 'A6'
|
|
147
|
+
| 'AAAA'
|
|
148
|
+
| 'AFSDB'
|
|
149
|
+
| 'AMTRELAY'
|
|
150
|
+
| 'ANY'
|
|
151
|
+
| 'APL'
|
|
152
|
+
| 'ATMA'
|
|
153
|
+
| 'AVC'
|
|
154
|
+
| 'AXFR'
|
|
155
|
+
| 'CAA'
|
|
156
|
+
| 'CDNSKEY'
|
|
157
|
+
| 'CDS'
|
|
158
|
+
| 'CERT'
|
|
159
|
+
| 'CNAME'
|
|
160
|
+
| 'CSYNC'
|
|
161
|
+
| 'DHCID'
|
|
162
|
+
| 'DLV'
|
|
163
|
+
| 'DNAME'
|
|
164
|
+
| 'DNSKEY'
|
|
165
|
+
| 'DOA'
|
|
166
|
+
| 'DS'
|
|
167
|
+
| 'EID'
|
|
168
|
+
| 'EUI48'
|
|
169
|
+
| 'EUI64'
|
|
170
|
+
| 'GID'
|
|
171
|
+
| 'GPOS'
|
|
172
|
+
| 'HINFO'
|
|
173
|
+
| 'HIP'
|
|
174
|
+
| 'HTTPS'
|
|
175
|
+
| 'IPSECKEY'
|
|
176
|
+
| 'ISDN'
|
|
177
|
+
| 'IXFR'
|
|
178
|
+
| 'KEY'
|
|
179
|
+
| 'KX'
|
|
180
|
+
| 'L32'
|
|
181
|
+
| 'L64'
|
|
182
|
+
| 'LOC'
|
|
183
|
+
| 'LP'
|
|
184
|
+
| 'MAILA'
|
|
185
|
+
| 'MAILB'
|
|
186
|
+
| 'MB'
|
|
187
|
+
| 'MD'
|
|
188
|
+
| 'MF'
|
|
189
|
+
| 'MG'
|
|
190
|
+
| 'MINFO'
|
|
191
|
+
| 'MR'
|
|
192
|
+
| 'MX'
|
|
193
|
+
| 'NAPTR'
|
|
194
|
+
| 'NID'
|
|
195
|
+
| 'NIMLOC'
|
|
196
|
+
| 'NINFO'
|
|
197
|
+
| 'NS'
|
|
198
|
+
| 'NSAP'
|
|
199
|
+
| 'NSAP-PTR'
|
|
200
|
+
| 'NSEC'
|
|
201
|
+
| 'NSEC3'
|
|
202
|
+
| 'NSEC3PARAM'
|
|
203
|
+
| 'NULL'
|
|
204
|
+
| 'NXT'
|
|
205
|
+
| 'OPENPGPKEY'
|
|
206
|
+
| 'OPT'
|
|
207
|
+
| 'PTR'
|
|
208
|
+
| 'PX'
|
|
209
|
+
| 'RKEY'
|
|
210
|
+
| 'RP'
|
|
211
|
+
| 'RRSIG'
|
|
212
|
+
| 'RT'
|
|
213
|
+
| 'Reserved'
|
|
214
|
+
| 'SIG'
|
|
215
|
+
| 'SINK'
|
|
216
|
+
| 'SMIMEA'
|
|
217
|
+
| 'SOA'
|
|
218
|
+
| 'SPF'
|
|
219
|
+
| 'SRV'
|
|
220
|
+
| 'SSHFP'
|
|
221
|
+
| 'SVCB'
|
|
222
|
+
| 'TA'
|
|
223
|
+
| 'TALINK'
|
|
224
|
+
| 'TKEY'
|
|
225
|
+
| 'TLSA'
|
|
226
|
+
| 'TSIG'
|
|
227
|
+
| 'TXT'
|
|
228
|
+
| 'UID'
|
|
229
|
+
| 'UINFO'
|
|
230
|
+
| 'UNSPEC'
|
|
231
|
+
| 'URI'
|
|
232
|
+
| 'WKS'
|
|
233
|
+
| 'X25'
|
|
234
|
+
| 'ZONEMD';
|
|
235
|
+
|
|
236
|
+
export type MxRecord = {
|
|
237
|
+
priority: number;
|
|
238
|
+
exchange: string;
|
|
239
|
+
type?: 'MX';
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
export type NaptrRecord = {
|
|
243
|
+
flags: string;
|
|
244
|
+
service: string;
|
|
245
|
+
regexp: string;
|
|
246
|
+
replacement: string;
|
|
247
|
+
order: number;
|
|
248
|
+
preference: number;
|
|
249
|
+
type?: 'NAPTR';
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
export type SoaRecord = {
|
|
253
|
+
nsname: string;
|
|
254
|
+
hostmaster: string;
|
|
255
|
+
serial: number;
|
|
256
|
+
refresh: number;
|
|
257
|
+
retry: number;
|
|
258
|
+
expire: number;
|
|
259
|
+
minttl: number;
|
|
260
|
+
type?: 'SOA';
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
export type SrvRecord = {
|
|
264
|
+
priority: number;
|
|
265
|
+
weight: number;
|
|
266
|
+
port: number;
|
|
267
|
+
name: string;
|
|
268
|
+
type?: 'SRV';
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
export type CaaRecord = {
|
|
272
|
+
critical: number;
|
|
273
|
+
issue?: string;
|
|
274
|
+
issuewild?: string;
|
|
275
|
+
iodef?: string;
|
|
276
|
+
contactemail?: string;
|
|
277
|
+
contactphone?: string;
|
|
278
|
+
type?: 'CAA';
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
export type CertRecord = {
|
|
282
|
+
certType: number | string;
|
|
283
|
+
keyTag: number;
|
|
284
|
+
algorithm: number;
|
|
285
|
+
certificate: Uint8Array | string;
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
export type TlsaRecord = {
|
|
289
|
+
usage: number;
|
|
290
|
+
selector: number;
|
|
291
|
+
mtype: number;
|
|
292
|
+
matchingType: number;
|
|
293
|
+
cert: Uint8Array;
|
|
294
|
+
certificate: Uint8Array;
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
export type HttpsRecord = {
|
|
298
|
+
name: string;
|
|
299
|
+
ttl: number;
|
|
300
|
+
type: 'HTTPS';
|
|
301
|
+
priority: number;
|
|
302
|
+
target: string;
|
|
303
|
+
params: Record<string, unknown>;
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
export type SvcbRecord = {
|
|
307
|
+
name: string;
|
|
308
|
+
ttl: number;
|
|
309
|
+
type: 'SVCB';
|
|
310
|
+
priority: number;
|
|
311
|
+
target: string;
|
|
312
|
+
params: Record<string, unknown>;
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
export type AnyRecord = {
|
|
316
|
+
type: string;
|
|
317
|
+
[key: string]: unknown;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
export type ResolveOptions = {
|
|
321
|
+
ttl?: boolean;
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
export type RecordWithTtl = {
|
|
325
|
+
address: string;
|
|
326
|
+
ttl: number;
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
export type SpoofPacket = {
|
|
330
|
+
id: number;
|
|
331
|
+
type: 'response';
|
|
332
|
+
flags: number;
|
|
333
|
+
flag_qr: boolean;
|
|
334
|
+
opcode: string;
|
|
335
|
+
flag_aa: boolean;
|
|
336
|
+
flag_tc: boolean;
|
|
337
|
+
flag_rd: boolean;
|
|
338
|
+
flag_ra: boolean;
|
|
339
|
+
flag_z: boolean;
|
|
340
|
+
flag_ad: boolean;
|
|
341
|
+
flag_cd: boolean;
|
|
342
|
+
rcode: string;
|
|
343
|
+
questions: Array<{ name: string; type: string; class: string }>;
|
|
344
|
+
answers: Array<{
|
|
345
|
+
name: string;
|
|
346
|
+
type: string;
|
|
347
|
+
ttl: number;
|
|
348
|
+
class: string;
|
|
349
|
+
flush: boolean;
|
|
350
|
+
data: unknown;
|
|
351
|
+
}>;
|
|
352
|
+
authorities: unknown[];
|
|
353
|
+
additionals: unknown[];
|
|
354
|
+
ttl: number;
|
|
355
|
+
expires: number;
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
export type RequestFunction = (
|
|
359
|
+
url: string,
|
|
360
|
+
options: Record<string, unknown>
|
|
361
|
+
) => Promise<{
|
|
362
|
+
statusCode: number;
|
|
363
|
+
headers: Record<string, string>;
|
|
364
|
+
body: {
|
|
365
|
+
arrayBuffer: () => Promise<ArrayBuffer>;
|
|
366
|
+
};
|
|
367
|
+
}>;
|
|
368
|
+
|
|
369
|
+
declare class Tangerine extends Resolver {
|
|
370
|
+
/**
|
|
371
|
+
* Path to the hosts file.
|
|
372
|
+
*/
|
|
373
|
+
static HOSTFILE: string;
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Parsed hosts from the hosts file.
|
|
377
|
+
*/
|
|
378
|
+
static HOSTS: Array<{ ip: string; hosts: string[] }>;
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Set of valid DNS record types.
|
|
382
|
+
*/
|
|
383
|
+
static TYPES: Set<string>;
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Set of DNS error codes.
|
|
387
|
+
*/
|
|
388
|
+
static CODES: Set<string>;
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Record types supported by resolveAny.
|
|
392
|
+
*/
|
|
393
|
+
static ANY_TYPES: string[];
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Record types supported by native DNS.
|
|
397
|
+
*/
|
|
398
|
+
static NATIVE_TYPES: Set<string>;
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Check if a port number is valid.
|
|
402
|
+
*/
|
|
403
|
+
static isValidPort(port: number): boolean;
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Get address configuration types based on network interfaces.
|
|
407
|
+
*/
|
|
408
|
+
static getAddrConfigTypes(): 0 | 4 | 6;
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Get a random integer between min and max (inclusive).
|
|
412
|
+
*/
|
|
413
|
+
static getRandomInt(min: number, max: number): number;
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Combine multiple errors into a single error.
|
|
417
|
+
*/
|
|
418
|
+
static combineErrors(errors: Error[]): Error;
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Create a DNS error with the specified code.
|
|
422
|
+
*/
|
|
423
|
+
static createError(
|
|
424
|
+
name: string,
|
|
425
|
+
rrtype: string,
|
|
426
|
+
code: string,
|
|
427
|
+
errno?: number
|
|
428
|
+
): Error;
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Options passed to the constructor.
|
|
432
|
+
*/
|
|
433
|
+
options: TangerineOptions;
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Set of active abort controllers.
|
|
437
|
+
*/
|
|
438
|
+
abortControllers: Set<AbortController>;
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Create a new Tangerine DNS resolver instance.
|
|
442
|
+
* @param options - Configuration options
|
|
443
|
+
* @param request - HTTP request function (default: undici.request)
|
|
444
|
+
*/
|
|
445
|
+
constructor(options?: TangerineOptions, request?: RequestFunction);
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Set local addresses for DNS queries.
|
|
449
|
+
*/
|
|
450
|
+
setLocalAddress(ipv4?: string, ipv6?: string): void;
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Resolve a hostname to IP addresses.
|
|
454
|
+
*/
|
|
455
|
+
lookup(
|
|
456
|
+
name: string,
|
|
457
|
+
options?: LookupOptions
|
|
458
|
+
): Promise<LookupAddress | LookupAddress[]>;
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Perform a reverse DNS lookup.
|
|
462
|
+
*/
|
|
463
|
+
lookupService(
|
|
464
|
+
address: string,
|
|
465
|
+
port: number,
|
|
466
|
+
abortController?: AbortController,
|
|
467
|
+
purgeCache?: boolean
|
|
468
|
+
): Promise<{ hostname: string; service: string }>;
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Reverse DNS lookup for an IP address.
|
|
472
|
+
*/
|
|
473
|
+
reverse(
|
|
474
|
+
ip: string,
|
|
475
|
+
abortController?: AbortController,
|
|
476
|
+
purgeCache?: boolean
|
|
477
|
+
): Promise<string[]>;
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Resolve IPv4 addresses.
|
|
481
|
+
*/
|
|
482
|
+
resolve4(
|
|
483
|
+
name: string,
|
|
484
|
+
options?: ResolveOptions,
|
|
485
|
+
abortController?: AbortController
|
|
486
|
+
): Promise<string[] | RecordWithTtl[]>;
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Resolve IPv6 addresses.
|
|
490
|
+
*/
|
|
491
|
+
resolve6(
|
|
492
|
+
name: string,
|
|
493
|
+
options?: ResolveOptions,
|
|
494
|
+
abortController?: AbortController
|
|
495
|
+
): Promise<string[] | RecordWithTtl[]>;
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Resolve CAA records.
|
|
499
|
+
*/
|
|
500
|
+
resolveCaa(
|
|
501
|
+
name: string,
|
|
502
|
+
options?: ResolveOptions,
|
|
503
|
+
abortController?: AbortController
|
|
504
|
+
): Promise<CaaRecord[]>;
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Resolve CNAME records.
|
|
508
|
+
*/
|
|
509
|
+
resolveCname(
|
|
510
|
+
name: string,
|
|
511
|
+
options?: ResolveOptions,
|
|
512
|
+
abortController?: AbortController
|
|
513
|
+
): Promise<string[]>;
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Resolve MX records.
|
|
517
|
+
*/
|
|
518
|
+
resolveMx(
|
|
519
|
+
name: string,
|
|
520
|
+
options?: ResolveOptions,
|
|
521
|
+
abortController?: AbortController
|
|
522
|
+
): Promise<MxRecord[]>;
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Resolve NAPTR records.
|
|
526
|
+
*/
|
|
527
|
+
resolveNaptr(
|
|
528
|
+
name: string,
|
|
529
|
+
options?: ResolveOptions,
|
|
530
|
+
abortController?: AbortController
|
|
531
|
+
): Promise<NaptrRecord[]>;
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Resolve NS records.
|
|
535
|
+
*/
|
|
536
|
+
resolveNs(
|
|
537
|
+
name: string,
|
|
538
|
+
options?: ResolveOptions,
|
|
539
|
+
abortController?: AbortController
|
|
540
|
+
): Promise<string[]>;
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Resolve PTR records.
|
|
544
|
+
*/
|
|
545
|
+
resolvePtr(
|
|
546
|
+
name: string,
|
|
547
|
+
options?: ResolveOptions,
|
|
548
|
+
abortController?: AbortController
|
|
549
|
+
): Promise<string[]>;
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Resolve SOA records.
|
|
553
|
+
*/
|
|
554
|
+
resolveSoa(
|
|
555
|
+
name: string,
|
|
556
|
+
options?: ResolveOptions,
|
|
557
|
+
abortController?: AbortController
|
|
558
|
+
): Promise<SoaRecord>;
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Resolve SRV records.
|
|
562
|
+
*/
|
|
563
|
+
resolveSrv(
|
|
564
|
+
name: string,
|
|
565
|
+
options?: ResolveOptions,
|
|
566
|
+
abortController?: AbortController
|
|
567
|
+
): Promise<SrvRecord[]>;
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Resolve TXT records.
|
|
571
|
+
*/
|
|
572
|
+
resolveTxt(
|
|
573
|
+
name: string,
|
|
574
|
+
options?: ResolveOptions,
|
|
575
|
+
abortController?: AbortController
|
|
576
|
+
): Promise<string[][]>;
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Resolve CERT records.
|
|
580
|
+
*/
|
|
581
|
+
resolveCert(
|
|
582
|
+
name: string,
|
|
583
|
+
options?: ResolveOptions,
|
|
584
|
+
abortController?: AbortController
|
|
585
|
+
): Promise<CertRecord[]>;
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Resolve TLSA records.
|
|
589
|
+
*/
|
|
590
|
+
resolveTlsa(
|
|
591
|
+
name: string,
|
|
592
|
+
options?: ResolveOptions,
|
|
593
|
+
abortController?: AbortController
|
|
594
|
+
): Promise<TlsaRecord[]>;
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Get the list of DNS servers.
|
|
598
|
+
*/
|
|
599
|
+
getServers(): string[];
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Cancel all pending DNS queries.
|
|
603
|
+
*/
|
|
604
|
+
cancel(): void;
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Resolve any record type.
|
|
608
|
+
*/
|
|
609
|
+
resolveAny(
|
|
610
|
+
name: string,
|
|
611
|
+
options?: ResolveOptions,
|
|
612
|
+
abortController?: AbortController
|
|
613
|
+
): Promise<AnyRecord[]>;
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Set the default result order for DNS queries.
|
|
617
|
+
*/
|
|
618
|
+
setDefaultResultOrder(dnsOrder: 'verbatim' | 'ipv4first'): void;
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Set the DNS servers to use.
|
|
622
|
+
*/
|
|
623
|
+
setServers(servers: string[]): void;
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Create a spoofed DNS packet for testing.
|
|
627
|
+
* @param name - The hostname
|
|
628
|
+
* @param rrtype - The record type
|
|
629
|
+
* @param answers - Array of answers
|
|
630
|
+
* @param json - Whether to return JSON string (default: false)
|
|
631
|
+
* @param expires - Expiration time in milliseconds (default: 300000 = 5 minutes)
|
|
632
|
+
*/
|
|
633
|
+
spoofPacket(
|
|
634
|
+
name: string,
|
|
635
|
+
rrtype: DnsRecordType,
|
|
636
|
+
answers?: unknown[],
|
|
637
|
+
json?: boolean,
|
|
638
|
+
expires?: number | Date
|
|
639
|
+
): SpoofPacket | string;
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Resolve DNS records of a specific type.
|
|
643
|
+
*/
|
|
644
|
+
resolve(
|
|
645
|
+
name: string,
|
|
646
|
+
rrtype?: DnsRecordType,
|
|
647
|
+
options?: ResolveOptions,
|
|
648
|
+
abortController?: AbortController
|
|
649
|
+
): Promise<unknown>;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
export default Tangerine;
|
|
653
|
+
export { Tangerine };
|
package/index.js
CHANGED
|
@@ -57,6 +57,13 @@ const NODE_MAJOR_VERSION = Number.parseInt(
|
|
|
57
57
|
10
|
|
58
58
|
);
|
|
59
59
|
|
|
60
|
+
// HTTPS and SVCB record types are not yet supported by dns-packet
|
|
61
|
+
// We map them to UNKNOWN_65 and UNKNOWN_64 respectively
|
|
62
|
+
// See: https://github.com/mafintosh/dns-packet/pull/104
|
|
63
|
+
const HTTPS_SVCB_TYPE_MAP = {
|
|
64
|
+
HTTPS: 'UNKNOWN_65',
|
|
65
|
+
SVCB: 'UNKNOWN_64'
|
|
66
|
+
};
|
|
60
67
|
// <https://github.com/szmarczak/cacheable-lookup/pull/76>
|
|
61
68
|
class Tangerine extends dns.promises.Resolver {
|
|
62
69
|
static HOSTFILE = HOSTFILE;
|
|
@@ -279,6 +286,8 @@ class Tangerine extends dns.promises.Resolver {
|
|
|
279
286
|
'TXT',
|
|
280
287
|
'UID',
|
|
281
288
|
'UINFO',
|
|
289
|
+
'UNKNOWN_64',
|
|
290
|
+
'UNKNOWN_65',
|
|
282
291
|
'UNSPEC',
|
|
283
292
|
'URI',
|
|
284
293
|
'WKS',
|
|
@@ -1542,7 +1551,7 @@ class Tangerine extends dns.promises.Resolver {
|
|
|
1542
1551
|
}
|
|
1543
1552
|
|
|
1544
1553
|
// eslint-disable-next-line max-params
|
|
1545
|
-
spoofPacket(name, rrtype, answers = [], json = false, expires =
|
|
1554
|
+
spoofPacket(name, rrtype, answers = [], json = false, expires = 300000) {
|
|
1546
1555
|
if (typeof name !== 'string') {
|
|
1547
1556
|
const err = new TypeError('The "name" argument must be of type string.');
|
|
1548
1557
|
err.code = 'ERR_INVALID_ARG_TYPE';
|
|
@@ -1635,6 +1644,13 @@ class Tangerine extends dns.promises.Resolver {
|
|
|
1635
1644
|
throw err;
|
|
1636
1645
|
}
|
|
1637
1646
|
|
|
1647
|
+
// Map HTTPS/SVCB to UNKNOWN_65/UNKNOWN_64 for dns-packet compatibility
|
|
1648
|
+
// Store original rrtype for later use
|
|
1649
|
+
const originalRrtype = rrtype;
|
|
1650
|
+
if (HTTPS_SVCB_TYPE_MAP[rrtype]) {
|
|
1651
|
+
rrtype = HTTPS_SVCB_TYPE_MAP[rrtype];
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1638
1654
|
// edge case where c-ares detects "." as start of string or malformed hostnames
|
|
1639
1655
|
// <https://github.com/c-ares/c-ares/blob/38b30bc922c21faa156939bde15ea35332c30e08/src/lib/ares_getaddrinfo.c#L829>
|
|
1640
1656
|
if (
|
|
@@ -2189,6 +2205,159 @@ class Tangerine extends dns.promises.Resolver {
|
|
|
2189
2205
|
});
|
|
2190
2206
|
}
|
|
2191
2207
|
|
|
2208
|
+
// HTTPS and SVCB records (types 65 and 64)
|
|
2209
|
+
// These are mapped from HTTPS/SVCB to UNKNOWN_65/UNKNOWN_64 for dns-packet compatibility
|
|
2210
|
+
// <https://datatracker.ietf.org/doc/html/rfc9460>
|
|
2211
|
+
case 'UNKNOWN_65':
|
|
2212
|
+
case 'UNKNOWN_64': {
|
|
2213
|
+
return result.answers.map((answer) => {
|
|
2214
|
+
// The data is in wire format, we need to parse it
|
|
2215
|
+
// SVCB/HTTPS RDATA format:
|
|
2216
|
+
// - SvcPriority (2 bytes)
|
|
2217
|
+
// - TargetName (variable length, DNS name format)
|
|
2218
|
+
// - SvcParams (variable length, key-value pairs)
|
|
2219
|
+
const obj = {
|
|
2220
|
+
name: answer.name,
|
|
2221
|
+
ttl: answer.ttl,
|
|
2222
|
+
type: originalRrtype // Use the original type (HTTPS or SVCB)
|
|
2223
|
+
};
|
|
2224
|
+
|
|
2225
|
+
if (Buffer.isBuffer(answer.data)) {
|
|
2226
|
+
// Parse the wire format
|
|
2227
|
+
obj.priority = answer.data.subarray(0, 2).readUInt16BE();
|
|
2228
|
+
|
|
2229
|
+
// Parse the target name (DNS name format)
|
|
2230
|
+
let offset = 2;
|
|
2231
|
+
const labels = [];
|
|
2232
|
+
while (offset < answer.data.length) {
|
|
2233
|
+
const labelLen = answer.data[offset];
|
|
2234
|
+
if (labelLen === 0) {
|
|
2235
|
+
offset++;
|
|
2236
|
+
break;
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
labels.push(
|
|
2240
|
+
answer.data
|
|
2241
|
+
.subarray(offset + 1, offset + 1 + labelLen)
|
|
2242
|
+
.toString()
|
|
2243
|
+
);
|
|
2244
|
+
offset += 1 + labelLen;
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
obj.target = labels.length > 0 ? labels.join('.') : '.';
|
|
2248
|
+
|
|
2249
|
+
// Parse SvcParams if there's remaining data
|
|
2250
|
+
obj.params = {};
|
|
2251
|
+
while (offset + 4 <= answer.data.length) {
|
|
2252
|
+
const paramKey = answer.data
|
|
2253
|
+
.subarray(offset, offset + 2)
|
|
2254
|
+
.readUInt16BE();
|
|
2255
|
+
const paramLen = answer.data
|
|
2256
|
+
.subarray(offset + 2, offset + 4)
|
|
2257
|
+
.readUInt16BE();
|
|
2258
|
+
offset += 4;
|
|
2259
|
+
|
|
2260
|
+
if (offset + paramLen > answer.data.length) break;
|
|
2261
|
+
|
|
2262
|
+
const paramValue = answer.data.subarray(
|
|
2263
|
+
offset,
|
|
2264
|
+
offset + paramLen
|
|
2265
|
+
);
|
|
2266
|
+
offset += paramLen;
|
|
2267
|
+
|
|
2268
|
+
// Map known SvcParam keys to names
|
|
2269
|
+
// <https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml>
|
|
2270
|
+
const paramNames = {
|
|
2271
|
+
0: 'mandatory',
|
|
2272
|
+
1: 'alpn',
|
|
2273
|
+
2: 'no-default-alpn',
|
|
2274
|
+
3: 'port',
|
|
2275
|
+
4: 'ipv4hint',
|
|
2276
|
+
5: 'ech',
|
|
2277
|
+
6: 'ipv6hint',
|
|
2278
|
+
7: 'dohpath'
|
|
2279
|
+
};
|
|
2280
|
+
|
|
2281
|
+
const paramName = paramNames[paramKey] || `key${paramKey}`;
|
|
2282
|
+
|
|
2283
|
+
// Parse specific param types
|
|
2284
|
+
switch (paramKey) {
|
|
2285
|
+
case 1: {
|
|
2286
|
+
// alpn - list of ALPN protocol IDs
|
|
2287
|
+
const alpns = [];
|
|
2288
|
+
let alpnOffset = 0;
|
|
2289
|
+
while (alpnOffset < paramValue.length) {
|
|
2290
|
+
const alpnLen = paramValue[alpnOffset];
|
|
2291
|
+
alpns.push(
|
|
2292
|
+
paramValue
|
|
2293
|
+
.subarray(alpnOffset + 1, alpnOffset + 1 + alpnLen)
|
|
2294
|
+
.toString()
|
|
2295
|
+
);
|
|
2296
|
+
alpnOffset += 1 + alpnLen;
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
obj.params[paramName] = alpns;
|
|
2300
|
+
break;
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
case 3: {
|
|
2304
|
+
// port
|
|
2305
|
+
obj.params[paramName] = paramValue.readUInt16BE();
|
|
2306
|
+
break;
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
case 4: {
|
|
2310
|
+
// ipv4hint - list of IPv4 addresses
|
|
2311
|
+
const ips = [];
|
|
2312
|
+
for (let i = 0; i < paramValue.length; i += 4) {
|
|
2313
|
+
ips.push(
|
|
2314
|
+
`${paramValue[i]}.${paramValue[i + 1]}.${paramValue[i + 2]}.${paramValue[i + 3]}`
|
|
2315
|
+
);
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
obj.params[paramName] = ips;
|
|
2319
|
+
break;
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
case 6: {
|
|
2323
|
+
// ipv6hint - list of IPv6 addresses
|
|
2324
|
+
const ip6s = [];
|
|
2325
|
+
for (let i = 0; i < paramValue.length; i += 16) {
|
|
2326
|
+
const parts = [];
|
|
2327
|
+
for (let j = 0; j < 16; j += 2) {
|
|
2328
|
+
parts.push(
|
|
2329
|
+
paramValue.subarray(i + j, i + j + 2).toString('hex')
|
|
2330
|
+
);
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
ip6s.push(parts.join(':'));
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
obj.params[paramName] = ip6s;
|
|
2337
|
+
break;
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
case 7: {
|
|
2341
|
+
// dohpath
|
|
2342
|
+
obj.params[paramName] = paramValue.toString();
|
|
2343
|
+
break;
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
default: {
|
|
2347
|
+
// Store as hex for unknown params
|
|
2348
|
+
obj.params[paramName] = paramValue.toString('hex');
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
} else {
|
|
2353
|
+
// Data is already parsed (from cache or JSON)
|
|
2354
|
+
Object.assign(obj, answer.data);
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
return obj;
|
|
2358
|
+
});
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2192
2361
|
default: {
|
|
2193
2362
|
this.options.logger.error(
|
|
2194
2363
|
new Error(
|
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.
|
|
4
|
+
"version": "2.0.2",
|
|
5
5
|
"author": "Forward Email (https://forwardemail.net)",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/forwardemail/nodejs-dns-over-https-tangerine/issues"
|
|
@@ -59,7 +59,8 @@
|
|
|
59
59
|
"node": ">=18"
|
|
60
60
|
},
|
|
61
61
|
"files": [
|
|
62
|
-
"index.js"
|
|
62
|
+
"index.js",
|
|
63
|
+
"index.d.ts"
|
|
63
64
|
],
|
|
64
65
|
"homepage": "https://github.com/forwardemail/nodejs-dns-over-https-tangerine",
|
|
65
66
|
"keywords": [
|
|
@@ -162,5 +163,6 @@
|
|
|
162
163
|
"prepare": "husky install",
|
|
163
164
|
"pretest": "npm run lint",
|
|
164
165
|
"test": "npm run nyc"
|
|
165
|
-
}
|
|
166
|
+
},
|
|
167
|
+
"types": "index.d.ts"
|
|
166
168
|
}
|