@darcas/smart-dns-promises 1.0.0 → 1.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.
- package/README.md +71 -6
- package/dist/commonjs/index.d.ts +114 -14
- package/dist/commonjs/index.d.ts.map +1 -1
- package/dist/commonjs/index.js +181 -35
- package/dist/commonjs/index.min.js +1 -6
- package/dist/commonjs/index.min.js.map +4 -4
- package/dist/esm/index.d.ts +114 -14
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +180 -34
- package/dist/esm/index.min.js +1 -6
- package/dist/esm/index.min.js.map +4 -4
- package/package.json +26 -29
package/dist/esm/index.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* @license MIT
|
|
1
|
+
/*
|
|
2
|
+
* Dario Casertano <dario@casertano.name>
|
|
3
|
+
* Copyright (c) 2026 Casertano Dario – All rights reserved.
|
|
4
|
+
* MIT
|
|
6
5
|
*/
|
|
7
|
-
import { LRUCache } from "lru-cache";
|
|
8
6
|
import { Resolver, setDefaultResultOrder, setServers } from 'node:dns/promises';
|
|
9
7
|
/**
|
|
10
8
|
* Enum for built-in DNS providers.
|
|
@@ -29,26 +27,84 @@ export class SmartDnsProviderError extends Error {
|
|
|
29
27
|
*/
|
|
30
28
|
export class SmartDnsResolverError extends Error {
|
|
31
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Minimal zero-dependency LRU cache with expiry support.
|
|
32
|
+
*/
|
|
33
|
+
class LruCache {
|
|
34
|
+
maxEntries;
|
|
35
|
+
entries = new Map();
|
|
36
|
+
constructor(maxEntries) {
|
|
37
|
+
this.maxEntries = maxEntries;
|
|
38
|
+
}
|
|
39
|
+
get(hostname) {
|
|
40
|
+
const entry = this.entries.get(hostname);
|
|
41
|
+
if (!entry) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
this.entries.delete(hostname);
|
|
45
|
+
this.entries.set(hostname, entry);
|
|
46
|
+
return entry;
|
|
47
|
+
}
|
|
48
|
+
set(hostname, entry) {
|
|
49
|
+
this.entries.delete(hostname);
|
|
50
|
+
this.entries.set(hostname, entry);
|
|
51
|
+
if (this.entries.size > this.maxEntries) {
|
|
52
|
+
const oldest = this.entries.keys().next().value;
|
|
53
|
+
if (oldest !== undefined) {
|
|
54
|
+
this.entries.delete(oldest);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
32
59
|
let SmartDnsInstance;
|
|
33
60
|
/**
|
|
34
61
|
* SmartDns class for managing DNS resolution with caching and configurable DNS providers.
|
|
62
|
+
*
|
|
63
|
+
* Configuration is applied to Node's process-global DNS system (`node:dns`):
|
|
64
|
+
* `setProvider` and `setServers` replace the resolver servers for the whole
|
|
65
|
+
* process, and `resultOrder` calls `dns.setDefaultResultOrder`. Every HTTP
|
|
66
|
+
* client in the application is affected by (and benefits from) this setup.
|
|
67
|
+
* Instantiate and configure SmartDns as early as possible in the application
|
|
68
|
+
* lifecycle, ideally once.
|
|
35
69
|
*/
|
|
36
70
|
export class SmartDns {
|
|
71
|
+
ttl;
|
|
37
72
|
/**
|
|
38
73
|
* Cache for hostname-IP mappings.
|
|
39
74
|
*/
|
|
40
75
|
cache;
|
|
76
|
+
inflight = new Map();
|
|
77
|
+
negative = new Map();
|
|
78
|
+
swr;
|
|
79
|
+
negativeTtl;
|
|
80
|
+
minTtl;
|
|
81
|
+
maxTtl;
|
|
82
|
+
family;
|
|
83
|
+
onStats;
|
|
84
|
+
counters = {
|
|
85
|
+
errors: 0,
|
|
86
|
+
hits: 0,
|
|
87
|
+
misses: 0,
|
|
88
|
+
resolutions: 0,
|
|
89
|
+
revalidations: 0,
|
|
90
|
+
totalResolveMs: 0,
|
|
91
|
+
};
|
|
41
92
|
/**
|
|
42
93
|
* Creates an instance of SmartDns.
|
|
43
94
|
* @param [dnsProvider] Optional DNS provider to use.
|
|
44
95
|
* @param [resultOrder='ipv4first'] Order of DNS resolution results.
|
|
45
|
-
* @param [ttl=3600000]
|
|
96
|
+
* @param [ttl=3600000] Fallback time-to-live for cached entries in milliseconds.
|
|
97
|
+
* @param [options] Advanced cache and resolver options.
|
|
46
98
|
*/
|
|
47
|
-
constructor(dnsProvider, resultOrder = 'ipv4first', ttl = 3_600_000) {
|
|
48
|
-
this.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
99
|
+
constructor(dnsProvider, resultOrder = 'ipv4first', ttl = 3_600_000, options = {}) {
|
|
100
|
+
this.ttl = ttl;
|
|
101
|
+
this.swr = options.swr ?? false;
|
|
102
|
+
this.negativeTtl = options.negativeTtl ?? 30_000;
|
|
103
|
+
this.minTtl = Math.max(options.minTtl ?? 1_000, 1);
|
|
104
|
+
this.maxTtl = Math.max(options.maxTtl ?? 3_600_000, this.minTtl);
|
|
105
|
+
this.family = options.family ?? 'ipv4';
|
|
106
|
+
this.onStats = options.onStats;
|
|
107
|
+
this.cache = new LruCache(100);
|
|
52
108
|
if (dnsProvider) {
|
|
53
109
|
this.setProvider(dnsProvider);
|
|
54
110
|
}
|
|
@@ -56,19 +112,40 @@ export class SmartDns {
|
|
|
56
112
|
}
|
|
57
113
|
/**
|
|
58
114
|
* Creates or retrieves a singleton instance of SmartDns.
|
|
115
|
+
*
|
|
116
|
+
* As in v1, configuration passed after the first call is ignored because the
|
|
117
|
+
* singleton has already been created.
|
|
118
|
+
*
|
|
59
119
|
* @param [dnsProvider] Optional DNS provider to use.
|
|
60
120
|
* @param [resultOrder] Order of DNS resolution results.
|
|
61
|
-
* @param [ttl]
|
|
121
|
+
* @param [ttl] Fallback time-to-live for cached entries in milliseconds.
|
|
122
|
+
* @param [options] Advanced cache and resolver options.
|
|
62
123
|
* @returns The singleton SmartDns instance.
|
|
63
124
|
*/
|
|
64
|
-
static factory(dnsProvider, resultOrder, ttl) {
|
|
125
|
+
static factory(dnsProvider, resultOrder, ttl, options) {
|
|
65
126
|
if (!SmartDnsInstance) {
|
|
66
|
-
SmartDnsInstance = new SmartDns(dnsProvider, resultOrder, ttl);
|
|
127
|
+
SmartDnsInstance = new SmartDns(dnsProvider, resultOrder, ttl, options);
|
|
67
128
|
}
|
|
68
129
|
return SmartDnsInstance;
|
|
69
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Current lookup statistics for this instance.
|
|
133
|
+
*/
|
|
134
|
+
get stats() {
|
|
135
|
+
return {
|
|
136
|
+
avgResolveMs: this.counters.resolutions > 0
|
|
137
|
+
? this.counters.totalResolveMs / this.counters.resolutions
|
|
138
|
+
: 0,
|
|
139
|
+
errors: this.counters.errors,
|
|
140
|
+
hits: this.counters.hits,
|
|
141
|
+
misses: this.counters.misses,
|
|
142
|
+
revalidations: this.counters.revalidations,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
70
145
|
/**
|
|
71
146
|
* Sets the DNS servers by the provider.
|
|
147
|
+
*
|
|
148
|
+
* Note: this mutates the process-global DNS configuration via `node:dns`.
|
|
72
149
|
* @param dnsProvider The DNS provider to use.
|
|
73
150
|
* @throws {SmartDnsProviderError} If the DNS provider is unsupported.
|
|
74
151
|
*/
|
|
@@ -99,41 +176,110 @@ export class SmartDns {
|
|
|
99
176
|
/**
|
|
100
177
|
* Resolves a URL and retrieves its IP address, hostname, and updated URL.
|
|
101
178
|
* @async
|
|
102
|
-
* @param url The URL to resolve.
|
|
179
|
+
* @param url The URL to resolve (must start with http/https).
|
|
103
180
|
* @returns An object containing the resolved IP address, hostname, and the URL with the hostname replaced by the IP address.
|
|
104
|
-
* @throws {SmartDnsResolverError} If the URL is invalid.
|
|
181
|
+
* @throws {SmartDnsResolverError} If the URL is invalid or the lookup was recently failing.
|
|
105
182
|
* @throws {Error} If the resolution fails.
|
|
106
183
|
*/
|
|
107
184
|
async resolver(url) {
|
|
108
185
|
if (!/^https?:\/\//.test(url)) {
|
|
109
186
|
throw new SmartDnsResolverError(`The URL must start with http/https.`);
|
|
110
187
|
}
|
|
188
|
+
let parsedUrl;
|
|
111
189
|
try {
|
|
112
|
-
|
|
113
|
-
if (!this.cache.has(hostname)) {
|
|
114
|
-
const resolver = new Resolver();
|
|
115
|
-
const [address] = await resolver.resolve4(hostname);
|
|
116
|
-
this.cache.set(hostname, address);
|
|
117
|
-
}
|
|
118
|
-
if (this.cache.has(hostname)) {
|
|
119
|
-
const address = this.cache.get(hostname);
|
|
120
|
-
return {
|
|
121
|
-
address,
|
|
122
|
-
hostname,
|
|
123
|
-
urlReplaced: url.replace(hostname, address),
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
return undefined;
|
|
190
|
+
parsedUrl = new URL(url);
|
|
127
191
|
}
|
|
128
|
-
catch
|
|
129
|
-
throw
|
|
192
|
+
catch {
|
|
193
|
+
throw new SmartDnsResolverError(`The URL "${url}" is not a valid URL.`);
|
|
130
194
|
}
|
|
195
|
+
const hostname = parsedUrl.hostname;
|
|
196
|
+
const address = await this.resolveAddress(hostname);
|
|
197
|
+
return {
|
|
198
|
+
address,
|
|
199
|
+
hostname,
|
|
200
|
+
urlReplaced: url.replace(hostname, address),
|
|
201
|
+
};
|
|
131
202
|
}
|
|
132
203
|
/**
|
|
133
204
|
* Manually sets DNS servers.
|
|
205
|
+
*
|
|
206
|
+
* Note: this mutates the process-global DNS configuration via `node:dns`.
|
|
134
207
|
* @param servers Array of DNS server IP addresses.
|
|
135
208
|
*/
|
|
136
209
|
setServers(servers) {
|
|
137
210
|
setServers(servers);
|
|
138
211
|
}
|
|
212
|
+
async resolveAddress(hostname) {
|
|
213
|
+
const now = Date.now();
|
|
214
|
+
const entry = this.cache.get(hostname);
|
|
215
|
+
if (entry && entry.expiresAt > now) {
|
|
216
|
+
this.counters.hits++;
|
|
217
|
+
this.emit();
|
|
218
|
+
return entry.address;
|
|
219
|
+
}
|
|
220
|
+
if (entry && this.swr) {
|
|
221
|
+
this.counters.hits++;
|
|
222
|
+
this.counters.revalidations++;
|
|
223
|
+
this.revalidate(hostname);
|
|
224
|
+
this.emit();
|
|
225
|
+
return entry.address;
|
|
226
|
+
}
|
|
227
|
+
const negativeUntil = this.negative.get(hostname);
|
|
228
|
+
if (negativeUntil !== undefined && negativeUntil > now) {
|
|
229
|
+
this.counters.errors++;
|
|
230
|
+
this.emit();
|
|
231
|
+
throw new SmartDnsResolverError(`A recent lookup for "${hostname}" failed; retrying is rate limited by the negative cache.`);
|
|
232
|
+
}
|
|
233
|
+
this.counters.misses++;
|
|
234
|
+
return this.fetchAddress(hostname).finally(() => {
|
|
235
|
+
this.emit();
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
fetchAddress(hostname) {
|
|
239
|
+
let pending = this.inflight.get(hostname);
|
|
240
|
+
if (!pending) {
|
|
241
|
+
pending = this.doResolve(hostname);
|
|
242
|
+
this.inflight.set(hostname, pending);
|
|
243
|
+
pending.then(() => undefined, () => undefined).finally(() => {
|
|
244
|
+
this.inflight.delete(hostname);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
return pending;
|
|
248
|
+
}
|
|
249
|
+
revalidate(hostname) {
|
|
250
|
+
void this.fetchAddress(hostname);
|
|
251
|
+
}
|
|
252
|
+
async doResolve(hostname) {
|
|
253
|
+
const startedAt = Date.now();
|
|
254
|
+
try {
|
|
255
|
+
const resolver = new Resolver();
|
|
256
|
+
const records = this.family === 'ipv6'
|
|
257
|
+
? await resolver.resolve6(hostname, { ttl: true })
|
|
258
|
+
: await resolver.resolve4(hostname, { ttl: true });
|
|
259
|
+
const [record] = records;
|
|
260
|
+
if (!record) {
|
|
261
|
+
throw new Error(`No ${this.family} records found for "${hostname}".`);
|
|
262
|
+
}
|
|
263
|
+
const rawTtl = record.ttl > 0 ? record.ttl * 1000 : this.ttl;
|
|
264
|
+
const recordTtl = Math.min(Math.max(rawTtl, this.minTtl), this.maxTtl);
|
|
265
|
+
this.cache.set(hostname, {
|
|
266
|
+
address: record.address,
|
|
267
|
+
expiresAt: Date.now() + recordTtl,
|
|
268
|
+
});
|
|
269
|
+
this.negative.delete(hostname);
|
|
270
|
+
return record.address;
|
|
271
|
+
}
|
|
272
|
+
catch (e) {
|
|
273
|
+
this.counters.errors++;
|
|
274
|
+
this.negative.set(hostname, Date.now() + this.negativeTtl);
|
|
275
|
+
throw e;
|
|
276
|
+
}
|
|
277
|
+
finally {
|
|
278
|
+
this.counters.resolutions++;
|
|
279
|
+
this.counters.totalResolveMs += Date.now() - startedAt;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
emit() {
|
|
283
|
+
this.onStats?.(this.stats);
|
|
284
|
+
}
|
|
139
285
|
}
|
package/dist/esm/index.min.js
CHANGED
|
@@ -1,7 +1,2 @@
|
|
|
1
|
-
var O=typeof performance=="object"&&performance&&typeof performance.now=="function"?performance:Date,j=new Set,D=typeof process=="object"&&process?process:{},k=(o,t,e,i)=>{typeof D.emitWarning=="function"?D.emitWarning(o,t,e,i):console.error(`[${e}] ${t}: ${o}`)},C=globalThis.AbortController,I=globalThis.AbortSignal;if(typeof C>"u"){I=class{onabort;_onabort=[];reason;aborted=!1;addEventListener(i,s){this._onabort.push(s)}},C=class{constructor(){t()}signal=new I;abort(i){if(!this.signal.aborted){this.signal.reason=i,this.signal.aborted=!0;for(let s of this.signal._onabort)s(i);this.signal.onabort?.(i)}}};let o=D.env?.LRU_CACHE_IGNORE_AC_WARNING!=="1",t=()=>{o&&(o=!1,k("AbortController is not defined. If using lru-cache in node 14, load an AbortController polyfill from the `node-abort-controller` package. A minimal polyfill is provided for use by LRUCache.fetch(), but it should not be relied upon in other contexts (eg, passing it to other APIs that use AbortController/AbortSignal might have undesirable effects). You may disable this with LRU_CACHE_IGNORE_AC_WARNING=1 in the env.","NO_ABORT_CONTROLLER","ENOTSUP",t))}}var V=o=>!j.has(o),X=Symbol("type"),A=o=>o&&o===Math.floor(o)&&o>0&&isFinite(o),H=o=>A(o)?o<=Math.pow(2,8)?Uint8Array:o<=Math.pow(2,16)?Uint16Array:o<=Math.pow(2,32)?Uint32Array:o<=Number.MAX_SAFE_INTEGER?T:null:null,T=class extends Array{constructor(t){super(t),this.fill(0)}},L=class o{heap;length;static#l=!1;static create(t){let e=H(t);if(!e)return[];o.#l=!0;let i=new o(t,e);return o.#l=!1,i}constructor(t,e){if(!o.#l)throw new TypeError("instantiate Stack using Stack.create(n)");this.heap=new e(t),this.length=0}push(t){this.heap[this.length++]=t}pop(){return this.heap[--this.length]}},R=class o{#l;#c;#p;#w;#R;#W;ttl;ttlResolution;ttlAutopurge;updateAgeOnGet;updateAgeOnHas;allowStale;noDisposeOnSet;noUpdateTTL;maxEntrySize;sizeCalculation;noDeleteOnFetchRejection;noDeleteOnStaleGet;allowStaleOnFetchAbort;allowStaleOnFetchRejection;ignoreFetchAbort;#n;#S;#s;#i;#t;#a;#u;#o;#h;#m;#r;#_;#b;#d;#y;#v;#f;static unsafeExposeInternals(t){return{starts:t.#b,ttls:t.#d,sizes:t.#_,keyMap:t.#s,keyList:t.#i,valList:t.#t,next:t.#a,prev:t.#u,get head(){return t.#o},get tail(){return t.#h},free:t.#m,isBackgroundFetch:e=>t.#e(e),backgroundFetch:(e,i,s,n)=>t.#L(e,i,s,n),moveToTail:e=>t.#C(e),indexes:e=>t.#A(e),rindexes:e=>t.#F(e),isStale:e=>t.#g(e)}}get max(){return this.#l}get maxSize(){return this.#c}get calculatedSize(){return this.#S}get size(){return this.#n}get fetchMethod(){return this.#R}get memoMethod(){return this.#W}get dispose(){return this.#p}get disposeAfter(){return this.#w}constructor(t){let{max:e=0,ttl:i,ttlResolution:s=1,ttlAutopurge:n,updateAgeOnGet:h,updateAgeOnHas:l,allowStale:r,dispose:g,disposeAfter:_,noDisposeOnSet:f,noUpdateTTL:u,maxSize:c=0,maxEntrySize:F=0,sizeCalculation:d,fetchMethod:S,memoMethod:a,noDeleteOnFetchRejection:w,noDeleteOnStaleGet:b,allowStaleOnFetchRejection:p,allowStaleOnFetchAbort:m,ignoreFetchAbort:v}=t;if(e!==0&&!A(e))throw new TypeError("max option must be a nonnegative integer");let y=e?H(e):Array;if(!y)throw new Error("invalid max value: "+e);if(this.#l=e,this.#c=c,this.maxEntrySize=F||this.#c,this.sizeCalculation=d,this.sizeCalculation){if(!this.#c&&!this.maxEntrySize)throw new TypeError("cannot set sizeCalculation without setting maxSize or maxEntrySize");if(typeof this.sizeCalculation!="function")throw new TypeError("sizeCalculation set to non-function")}if(a!==void 0&&typeof a!="function")throw new TypeError("memoMethod must be a function if defined");if(this.#W=a,S!==void 0&&typeof S!="function")throw new TypeError("fetchMethod must be a function if specified");if(this.#R=S,this.#v=!!S,this.#s=new Map,this.#i=new Array(e).fill(void 0),this.#t=new Array(e).fill(void 0),this.#a=new y(e),this.#u=new y(e),this.#o=0,this.#h=0,this.#m=L.create(e),this.#n=0,this.#S=0,typeof g=="function"&&(this.#p=g),typeof _=="function"?(this.#w=_,this.#r=[]):(this.#w=void 0,this.#r=void 0),this.#y=!!this.#p,this.#f=!!this.#w,this.noDisposeOnSet=!!f,this.noUpdateTTL=!!u,this.noDeleteOnFetchRejection=!!w,this.allowStaleOnFetchRejection=!!p,this.allowStaleOnFetchAbort=!!m,this.ignoreFetchAbort=!!v,this.maxEntrySize!==0){if(this.#c!==0&&!A(this.#c))throw new TypeError("maxSize must be a positive integer if specified");if(!A(this.maxEntrySize))throw new TypeError("maxEntrySize must be a positive integer if specified");this.#k()}if(this.allowStale=!!r,this.noDeleteOnStaleGet=!!b,this.updateAgeOnGet=!!h,this.updateAgeOnHas=!!l,this.ttlResolution=A(s)||s===0?s:1,this.ttlAutopurge=!!n,this.ttl=i||0,this.ttl){if(!A(this.ttl))throw new TypeError("ttl must be a positive integer if specified");this.#U()}if(this.#l===0&&this.ttl===0&&this.#c===0)throw new TypeError("At least one of max, maxSize, or ttl is required");if(!this.ttlAutopurge&&!this.#l&&!this.#c){let z="LRU_CACHE_UNBOUNDED";V(z)&&(j.add(z),k("TTL caching without ttlAutopurge, max, or maxSize can result in unbounded memory consumption.","UnboundedCacheWarning",z,o))}}getRemainingTTL(t){return this.#s.has(t)?1/0:0}#U(){let t=new T(this.#l),e=new T(this.#l);this.#d=t,this.#b=e,this.#G=(n,h,l=O.now())=>{if(e[n]=h!==0?l:0,t[n]=h,h!==0&&this.ttlAutopurge){let r=setTimeout(()=>{this.#g(n)&&this.#O(this.#i[n],"expire")},h+1);r.unref&&r.unref()}},this.#E=n=>{e[n]=t[n]!==0?O.now():0},this.#T=(n,h)=>{if(t[h]){let l=t[h],r=e[h];if(!l||!r)return;n.ttl=l,n.start=r,n.now=i||s();let g=n.now-r;n.remainingTTL=l-g}};let i=0,s=()=>{let n=O.now();if(this.ttlResolution>0){i=n;let h=setTimeout(()=>i=0,this.ttlResolution);h.unref&&h.unref()}return n};this.getRemainingTTL=n=>{let h=this.#s.get(n);if(h===void 0)return 0;let l=t[h],r=e[h];if(!l||!r)return 1/0;let g=(i||s())-r;return l-g},this.#g=n=>{let h=e[n],l=t[n];return!!l&&!!h&&(i||s())-h>l}}#E=()=>{};#T=()=>{};#G=()=>{};#g=()=>!1;#k(){let t=new T(this.#l);this.#S=0,this.#_=t,this.#z=e=>{this.#S-=t[e],t[e]=0},this.#M=(e,i,s,n)=>{if(this.#e(i))return 0;if(!A(s))if(n){if(typeof n!="function")throw new TypeError("sizeCalculation must be a function");if(s=n(i,e),!A(s))throw new TypeError("sizeCalculation return invalid (expect positive integer)")}else throw new TypeError("invalid size value (must be positive integer). When maxSize or maxEntrySize is used, sizeCalculation or size must be set.");return s},this.#x=(e,i,s)=>{if(t[e]=i,this.#c){let n=this.#c-t[e];for(;this.#S>n;)this.#D(!0)}this.#S+=t[e],s&&(s.entrySize=i,s.totalCalculatedSize=this.#S)}}#z=t=>{};#x=(t,e,i)=>{};#M=(t,e,i,s)=>{if(i||s)throw new TypeError("cannot set size without setting maxSize or maxEntrySize on cache");return 0};*#A({allowStale:t=this.allowStale}={}){if(this.#n)for(let e=this.#h;!(!this.#N(e)||((t||!this.#g(e))&&(yield e),e===this.#o));)e=this.#u[e]}*#F({allowStale:t=this.allowStale}={}){if(this.#n)for(let e=this.#o;!(!this.#N(e)||((t||!this.#g(e))&&(yield e),e===this.#h));)e=this.#a[e]}#N(t){return t!==void 0&&this.#s.get(this.#i[t])===t}*entries(){for(let t of this.#A())this.#t[t]!==void 0&&this.#i[t]!==void 0&&!this.#e(this.#t[t])&&(yield[this.#i[t],this.#t[t]])}*rentries(){for(let t of this.#F())this.#t[t]!==void 0&&this.#i[t]!==void 0&&!this.#e(this.#t[t])&&(yield[this.#i[t],this.#t[t]])}*keys(){for(let t of this.#A()){let e=this.#i[t];e!==void 0&&!this.#e(this.#t[t])&&(yield e)}}*rkeys(){for(let t of this.#F()){let e=this.#i[t];e!==void 0&&!this.#e(this.#t[t])&&(yield e)}}*values(){for(let t of this.#A())this.#t[t]!==void 0&&!this.#e(this.#t[t])&&(yield this.#t[t])}*rvalues(){for(let t of this.#F())this.#t[t]!==void 0&&!this.#e(this.#t[t])&&(yield this.#t[t])}[Symbol.iterator](){return this.entries()}[Symbol.toStringTag]="LRUCache";find(t,e={}){for(let i of this.#A()){let s=this.#t[i],n=this.#e(s)?s.__staleWhileFetching:s;if(n!==void 0&&t(n,this.#i[i],this))return this.get(this.#i[i],e)}}forEach(t,e=this){for(let i of this.#A()){let s=this.#t[i],n=this.#e(s)?s.__staleWhileFetching:s;n!==void 0&&t.call(e,n,this.#i[i],this)}}rforEach(t,e=this){for(let i of this.#F()){let s=this.#t[i],n=this.#e(s)?s.__staleWhileFetching:s;n!==void 0&&t.call(e,n,this.#i[i],this)}}purgeStale(){let t=!1;for(let e of this.#F({allowStale:!0}))this.#g(e)&&(this.#O(this.#i[e],"expire"),t=!0);return t}info(t){let e=this.#s.get(t);if(e===void 0)return;let i=this.#t[e],s=this.#e(i)?i.__staleWhileFetching:i;if(s===void 0)return;let n={value:s};if(this.#d&&this.#b){let h=this.#d[e],l=this.#b[e];if(h&&l){let r=h-(O.now()-l);n.ttl=r,n.start=Date.now()}}return this.#_&&(n.size=this.#_[e]),n}dump(){let t=[];for(let e of this.#A({allowStale:!0})){let i=this.#i[e],s=this.#t[e],n=this.#e(s)?s.__staleWhileFetching:s;if(n===void 0||i===void 0)continue;let h={value:n};if(this.#d&&this.#b){h.ttl=this.#d[e];let l=O.now()-this.#b[e];h.start=Math.floor(Date.now()-l)}this.#_&&(h.size=this.#_[e]),t.unshift([i,h])}return t}load(t){this.clear();for(let[e,i]of t){if(i.start){let s=Date.now()-i.start;i.start=O.now()-s}this.set(e,i.value,i)}}set(t,e,i={}){if(e===void 0)return this.delete(t),this;let{ttl:s=this.ttl,start:n,noDisposeOnSet:h=this.noDisposeOnSet,sizeCalculation:l=this.sizeCalculation,status:r}=i,{noUpdateTTL:g=this.noUpdateTTL}=i,_=this.#M(t,e,i.size||0,l);if(this.maxEntrySize&&_>this.maxEntrySize)return r&&(r.set="miss",r.maxEntrySizeExceeded=!0),this.#O(t,"set"),this;let f=this.#n===0?void 0:this.#s.get(t);if(f===void 0)f=this.#n===0?this.#h:this.#m.length!==0?this.#m.pop():this.#n===this.#l?this.#D(!1):this.#n,this.#i[f]=t,this.#t[f]=e,this.#s.set(t,f),this.#a[this.#h]=f,this.#u[f]=this.#h,this.#h=f,this.#n++,this.#x(f,_,r),r&&(r.set="add"),g=!1;else{this.#C(f);let u=this.#t[f];if(e!==u){if(this.#v&&this.#e(u)){u.__abortController.abort(new Error("replaced"));let{__staleWhileFetching:c}=u;c!==void 0&&!h&&(this.#y&&this.#p?.(c,t,"set"),this.#f&&this.#r?.push([c,t,"set"]))}else h||(this.#y&&this.#p?.(u,t,"set"),this.#f&&this.#r?.push([u,t,"set"]));if(this.#z(f),this.#x(f,_,r),this.#t[f]=e,r){r.set="replace";let c=u&&this.#e(u)?u.__staleWhileFetching:u;c!==void 0&&(r.oldValue=c)}}else r&&(r.set="update")}if(s!==0&&!this.#d&&this.#U(),this.#d&&(g||this.#G(f,s,n),r&&this.#T(r,f)),!h&&this.#f&&this.#r){let u=this.#r,c;for(;c=u?.shift();)this.#w?.(...c)}return this}pop(){try{for(;this.#n;){let t=this.#t[this.#o];if(this.#D(!0),this.#e(t)){if(t.__staleWhileFetching)return t.__staleWhileFetching}else if(t!==void 0)return t}}finally{if(this.#f&&this.#r){let t=this.#r,e;for(;e=t?.shift();)this.#w?.(...e)}}}#D(t){let e=this.#o,i=this.#i[e],s=this.#t[e];return this.#v&&this.#e(s)?s.__abortController.abort(new Error("evicted")):(this.#y||this.#f)&&(this.#y&&this.#p?.(s,i,"evict"),this.#f&&this.#r?.push([s,i,"evict"])),this.#z(e),t&&(this.#i[e]=void 0,this.#t[e]=void 0,this.#m.push(e)),this.#n===1?(this.#o=this.#h=0,this.#m.length=0):this.#o=this.#a[e],this.#s.delete(i),this.#n--,e}has(t,e={}){let{updateAgeOnHas:i=this.updateAgeOnHas,status:s}=e,n=this.#s.get(t);if(n!==void 0){let h=this.#t[n];if(this.#e(h)&&h.__staleWhileFetching===void 0)return!1;if(this.#g(n))s&&(s.has="stale",this.#T(s,n));else return i&&this.#E(n),s&&(s.has="hit",this.#T(s,n)),!0}else s&&(s.has="miss");return!1}peek(t,e={}){let{allowStale:i=this.allowStale}=e,s=this.#s.get(t);if(s===void 0||!i&&this.#g(s))return;let n=this.#t[s];return this.#e(n)?n.__staleWhileFetching:n}#L(t,e,i,s){let n=e===void 0?void 0:this.#t[e];if(this.#e(n))return n;let h=new C,{signal:l}=i;l?.addEventListener("abort",()=>h.abort(l.reason),{signal:h.signal});let r={signal:h.signal,options:i,context:s},g=(d,S=!1)=>{let{aborted:a}=h.signal,w=i.ignoreFetchAbort&&d!==void 0;if(i.status&&(a&&!S?(i.status.fetchAborted=!0,i.status.fetchError=h.signal.reason,w&&(i.status.fetchAbortIgnored=!0)):i.status.fetchResolved=!0),a&&!w&&!S)return f(h.signal.reason);let b=c;return this.#t[e]===c&&(d===void 0?b.__staleWhileFetching?this.#t[e]=b.__staleWhileFetching:this.#O(t,"fetch"):(i.status&&(i.status.fetchUpdated=!0),this.set(t,d,r.options))),d},_=d=>(i.status&&(i.status.fetchRejected=!0,i.status.fetchError=d),f(d)),f=d=>{let{aborted:S}=h.signal,a=S&&i.allowStaleOnFetchAbort,w=a||i.allowStaleOnFetchRejection,b=w||i.noDeleteOnFetchRejection,p=c;if(this.#t[e]===c&&(!b||p.__staleWhileFetching===void 0?this.#O(t,"fetch"):a||(this.#t[e]=p.__staleWhileFetching)),w)return i.status&&p.__staleWhileFetching!==void 0&&(i.status.returnedStale=!0),p.__staleWhileFetching;if(p.__returned===p)throw d},u=(d,S)=>{let a=this.#R?.(t,n,r);a&&a instanceof Promise&&a.then(w=>d(w===void 0?void 0:w),S),h.signal.addEventListener("abort",()=>{(!i.ignoreFetchAbort||i.allowStaleOnFetchAbort)&&(d(void 0),i.allowStaleOnFetchAbort&&(d=w=>g(w,!0)))})};i.status&&(i.status.fetchDispatched=!0);let c=new Promise(u).then(g,_),F=Object.assign(c,{__abortController:h,__staleWhileFetching:n,__returned:void 0});return e===void 0?(this.set(t,F,{...r.options,status:void 0}),e=this.#s.get(t)):this.#t[e]=F,F}#e(t){if(!this.#v)return!1;let e=t;return!!e&&e instanceof Promise&&e.hasOwnProperty("__staleWhileFetching")&&e.__abortController instanceof C}async fetch(t,e={}){let{allowStale:i=this.allowStale,updateAgeOnGet:s=this.updateAgeOnGet,noDeleteOnStaleGet:n=this.noDeleteOnStaleGet,ttl:h=this.ttl,noDisposeOnSet:l=this.noDisposeOnSet,size:r=0,sizeCalculation:g=this.sizeCalculation,noUpdateTTL:_=this.noUpdateTTL,noDeleteOnFetchRejection:f=this.noDeleteOnFetchRejection,allowStaleOnFetchRejection:u=this.allowStaleOnFetchRejection,ignoreFetchAbort:c=this.ignoreFetchAbort,allowStaleOnFetchAbort:F=this.allowStaleOnFetchAbort,context:d,forceRefresh:S=!1,status:a,signal:w}=e;if(!this.#v)return a&&(a.fetch="get"),this.get(t,{allowStale:i,updateAgeOnGet:s,noDeleteOnStaleGet:n,status:a});let b={allowStale:i,updateAgeOnGet:s,noDeleteOnStaleGet:n,ttl:h,noDisposeOnSet:l,size:r,sizeCalculation:g,noUpdateTTL:_,noDeleteOnFetchRejection:f,allowStaleOnFetchRejection:u,allowStaleOnFetchAbort:F,ignoreFetchAbort:c,status:a,signal:w},p=this.#s.get(t);if(p===void 0){a&&(a.fetch="miss");let m=this.#L(t,p,b,d);return m.__returned=m}else{let m=this.#t[p];if(this.#e(m)){let N=i&&m.__staleWhileFetching!==void 0;return a&&(a.fetch="inflight",N&&(a.returnedStale=!0)),N?m.__staleWhileFetching:m.__returned=m}let v=this.#g(p);if(!S&&!v)return a&&(a.fetch="hit"),this.#C(p),s&&this.#E(p),a&&this.#T(a,p),m;let y=this.#L(t,p,b,d),x=y.__staleWhileFetching!==void 0&&i;return a&&(a.fetch=v?"stale":"refresh",x&&v&&(a.returnedStale=!0)),x?y.__staleWhileFetching:y.__returned=y}}async forceFetch(t,e={}){let i=await this.fetch(t,e);if(i===void 0)throw new Error("fetch() returned undefined");return i}memo(t,e={}){let i=this.#W;if(!i)throw new Error("no memoMethod provided to constructor");let{context:s,forceRefresh:n,...h}=e,l=this.get(t,h);if(!n&&l!==void 0)return l;let r=i(t,l,{options:h,context:s});return this.set(t,r,h),r}get(t,e={}){let{allowStale:i=this.allowStale,updateAgeOnGet:s=this.updateAgeOnGet,noDeleteOnStaleGet:n=this.noDeleteOnStaleGet,status:h}=e,l=this.#s.get(t);if(l!==void 0){let r=this.#t[l],g=this.#e(r);return h&&this.#T(h,l),this.#g(l)?(h&&(h.get="stale"),g?(h&&i&&r.__staleWhileFetching!==void 0&&(h.returnedStale=!0),i?r.__staleWhileFetching:void 0):(n||this.#O(t,"expire"),h&&i&&(h.returnedStale=!0),i?r:void 0)):(h&&(h.get="hit"),g?r.__staleWhileFetching:(this.#C(l),s&&this.#E(l),r))}else h&&(h.get="miss")}#I(t,e){this.#u[e]=t,this.#a[t]=e}#C(t){t!==this.#h&&(t===this.#o?this.#o=this.#a[t]:this.#I(this.#u[t],this.#a[t]),this.#I(this.#h,t),this.#h=t)}delete(t){return this.#O(t,"delete")}#O(t,e){let i=!1;if(this.#n!==0){let s=this.#s.get(t);if(s!==void 0)if(i=!0,this.#n===1)this.#j(e);else{this.#z(s);let n=this.#t[s];if(this.#e(n)?n.__abortController.abort(new Error("deleted")):(this.#y||this.#f)&&(this.#y&&this.#p?.(n,t,e),this.#f&&this.#r?.push([n,t,e])),this.#s.delete(t),this.#i[s]=void 0,this.#t[s]=void 0,s===this.#h)this.#h=this.#u[s];else if(s===this.#o)this.#o=this.#a[s];else{let h=this.#u[s];this.#a[h]=this.#a[s];let l=this.#a[s];this.#u[l]=this.#u[s]}this.#n--,this.#m.push(s)}}if(this.#f&&this.#r?.length){let s=this.#r,n;for(;n=s?.shift();)this.#w?.(...n)}return i}clear(){return this.#j("delete")}#j(t){for(let e of this.#F({allowStale:!0})){let i=this.#t[e];if(this.#e(i))i.__abortController.abort(new Error("deleted"));else{let s=this.#i[e];this.#y&&this.#p?.(i,s,t),this.#f&&this.#r?.push([i,s,t])}}if(this.#s.clear(),this.#t.fill(void 0),this.#i.fill(void 0),this.#d&&this.#b&&(this.#d.fill(0),this.#b.fill(0)),this.#_&&this.#_.fill(0),this.#o=0,this.#h=0,this.#m.length=0,this.#S=0,this.#n=0,this.#f&&this.#r){let e=this.#r,i;for(;i=e?.shift();)this.#w?.(...i)}}};import{Resolver as B,setDefaultResultOrder as $,setServers as W}from"node:dns/promises";var E;(function(o){o[o.CloudFlare=0]="CloudFlare",o[o.Google=1]="Google",o[o.OpenDNS=2]="OpenDNS"})(E||(E={}));var G=class extends Error{},M=class extends Error{},U,P=class o{cache;constructor(t,e="ipv4first",i=36e5){this.cache=new R({max:100,ttl:i}),t&&this.setProvider(t),$(e)}static factory(t,e,i){return U||(U=new o(t,e,i)),U}setProvider(t){switch(t){case E.CloudFlare:W(["1.1.1.1","1.0.0.1"]);break;case E.Google:W(["8.8.8.8","8.8.4.4"]);break;case E.OpenDNS:W(["208.67.222.222","208.67.220.220"]);break;default:throw new G(`Unsupported DNS provider: ${t}. You can use the "setServers" method to manually set the DNS server IP addresses.`)}}async resolver(t){if(!/^https?:\/\//.test(t))throw new M("The URL must start with http/https.");try{let e=new URL(t).hostname;if(!this.cache.has(e)){let i=new B,[s]=await i.resolve4(e);this.cache.set(e,s)}if(this.cache.has(e)){let i=this.cache.get(e);return{address:i,hostname:e,urlReplaced:t.replace(e,i)}}return}catch(e){throw e}}setServers(t){W(t)}};export{E as DnsProvider,P as SmartDns,G as SmartDnsProviderError,M as SmartDnsResolverError};
|
|
2
|
-
/**
|
|
3
|
-
* @author Dario Casertano <dario@casertano.name>
|
|
4
|
-
* @copyright Copyright (c) 2024 Casertano Dario – All rights reserved.
|
|
5
|
-
* @license MIT
|
|
6
|
-
*/
|
|
1
|
+
import{Resolver as w,setDefaultResultOrder as g,setServers as o}from"node:dns/promises";var a;(function(r){r[r.CloudFlare=0]="CloudFlare",r[r.Google=1]="Google",r[r.OpenDNS=2]="OpenDNS"})(a||(a={}));var c=class extends Error{},l=class extends Error{},d=class{maxEntries;entries=new Map;constructor(t){this.maxEntries=t}get(t){let e=this.entries.get(t);if(e)return this.entries.delete(t),this.entries.set(t,e),e}set(t,e){if(this.entries.delete(t),this.entries.set(t,e),this.entries.size>this.maxEntries){let s=this.entries.keys().next().value;s!==void 0&&this.entries.delete(s)}}},h,u=class r{ttl;cache;inflight=new Map;negative=new Map;swr;negativeTtl;minTtl;maxTtl;family;onStats;counters={errors:0,hits:0,misses:0,resolutions:0,revalidations:0,totalResolveMs:0};constructor(t,e="ipv4first",s=36e5,i={}){this.ttl=s,this.swr=i.swr??!1,this.negativeTtl=i.negativeTtl??3e4,this.minTtl=Math.max(i.minTtl??1e3,1),this.maxTtl=Math.max(i.maxTtl??36e5,this.minTtl),this.family=i.family??"ipv4",this.onStats=i.onStats,this.cache=new d(100),t&&this.setProvider(t),g(e)}static factory(t,e,s,i){return h||(h=new r(t,e,s,i)),h}get stats(){return{avgResolveMs:this.counters.resolutions>0?this.counters.totalResolveMs/this.counters.resolutions:0,errors:this.counters.errors,hits:this.counters.hits,misses:this.counters.misses,revalidations:this.counters.revalidations}}setProvider(t){switch(t){case a.CloudFlare:o(["1.1.1.1","1.0.0.1"]);break;case a.Google:o(["8.8.8.8","8.8.4.4"]);break;case a.OpenDNS:o(["208.67.222.222","208.67.220.220"]);break;default:throw new c(`Unsupported DNS provider: ${t}. You can use the "setServers" method to manually set the DNS server IP addresses.`)}}async resolver(t){if(!/^https?:\/\//.test(t))throw new l("The URL must start with http/https.");let e;try{e=new URL(t)}catch{throw new l(`The URL "${t}" is not a valid URL.`)}let s=e.hostname,i=await this.resolveAddress(s);return{address:i,hostname:s,urlReplaced:t.replace(s,i)}}setServers(t){o(t)}async resolveAddress(t){let e=Date.now(),s=this.cache.get(t);if(s&&s.expiresAt>e)return this.counters.hits++,this.emit(),s.address;if(s&&this.swr)return this.counters.hits++,this.counters.revalidations++,this.revalidate(t),this.emit(),s.address;let i=this.negative.get(t);if(i!==void 0&&i>e)throw this.counters.errors++,this.emit(),new l(`A recent lookup for "${t}" failed; retrying is rate limited by the negative cache.`);return this.counters.misses++,this.fetchAddress(t).finally(()=>{this.emit()})}fetchAddress(t){let e=this.inflight.get(t);return e||(e=this.doResolve(t),this.inflight.set(t,e),e.then(()=>{},()=>{}).finally(()=>{this.inflight.delete(t)})),e}revalidate(t){this.fetchAddress(t)}async doResolve(t){let e=Date.now();try{let s=new w,i=this.family==="ipv6"?await s.resolve6(t,{ttl:!0}):await s.resolve4(t,{ttl:!0}),[n]=i;if(!n)throw new Error(`No ${this.family} records found for "${t}".`);let f=n.ttl>0?n.ttl*1e3:this.ttl,v=Math.min(Math.max(f,this.minTtl),this.maxTtl);return this.cache.set(t,{address:n.address,expiresAt:Date.now()+v}),this.negative.delete(t),n.address}catch(s){throw this.counters.errors++,this.negative.set(t,Date.now()+this.negativeTtl),s}finally{this.counters.resolutions++,this.counters.totalResolveMs+=Date.now()-e}}emit(){this.onStats?.(this.stats)}};export{a as DnsProvider,u as SmartDns,c as SmartDnsProviderError,l as SmartDnsResolverError};
|
|
7
2
|
//# sourceMappingURL=index.min.js.map
|