@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/dist/esm/index.js CHANGED
@@ -1,10 +1,8 @@
1
- // noinspection JSUnusedGlobalSymbols
2
- /**
3
- * @author Dario Casertano <dario@casertano.name>
4
- * @copyright Copyright (c) 2024 Casertano Dario – All rights reserved.
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] Time-to-live for cached entries in milliseconds.
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.cache = new LRUCache({
49
- max: 100,
50
- ttl,
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] Time-to-live for cached entries in milliseconds.
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
- const hostname = new URL(url).hostname;
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 (e) {
129
- throw e;
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
  }
@@ -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