domain-quotes 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -18
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,37 +25,37 @@ npm i domain-quotes
|
|
|
25
25
|
Usage
|
|
26
26
|
|
|
27
27
|
```ts
|
|
28
|
-
import { getDefaultQuote,
|
|
28
|
+
import { getDefaultQuote, DomainQuotes, DEFAULT_CONFIG } from 'domain-quotes';
|
|
29
29
|
|
|
30
30
|
// Quick quote (uses bundled defaults)
|
|
31
31
|
const quote = await getDefaultQuote('com', 'USD', { discountCodes: ['SAVE10'] });
|
|
32
32
|
// → { extension, currency, basePrice, discount, tax, totalPrice, symbol }
|
|
33
33
|
|
|
34
34
|
// Advanced: custom or explicit config via the class
|
|
35
|
-
const
|
|
36
|
-
const eur = await
|
|
35
|
+
const dq = new DomainQuotes(DEFAULT_CONFIG); // or provide your own DomainQuoteConfig
|
|
36
|
+
const eur = await dq.getQuote('example.com', 'EUR', { discountPolicy: 'stack' });
|
|
37
37
|
|
|
38
38
|
// Add a 15% markup before discounts/taxes
|
|
39
|
-
const withMarkup = new
|
|
40
|
-
...
|
|
39
|
+
const withMarkup = new DomainQuotes({
|
|
40
|
+
...DEFAULT_CONFIG,
|
|
41
41
|
markup: { type: 'percentage', value: 0.15 },
|
|
42
42
|
});
|
|
43
|
-
const quoteWithMarkup = await withMarkup.
|
|
43
|
+
const quoteWithMarkup = await withMarkup.getQuote('example.com', 'USD', { discountCodes: ['SAVE10'] });
|
|
44
|
+
|
|
44
45
|
```
|
|
45
46
|
|
|
46
47
|
API
|
|
47
48
|
|
|
48
|
-
- `getDefaultQuote(extension: string, currency: string, options?:
|
|
49
|
+
- `getDefaultQuote(extension: string, currency: string, options?: GetQuoteOptions): Promise<Quote>`
|
|
49
50
|
- Computes a quote for a TLD/SLD extension (e.g. `com`, `com.ng`, `.org`) using the bundled defaults.
|
|
50
|
-
- Alias: `getDefaultPrice(...)` for backward-compatibility.
|
|
51
51
|
- `options`
|
|
52
52
|
- `discountCodes?: string[]` – one or more codes; case-insensitive.
|
|
53
53
|
- `now?: number | Date` – inject time for deterministic tests.
|
|
54
54
|
- `discountPolicy?: 'stack' | 'max'` – default `'max'` (highest single discount only).
|
|
55
55
|
|
|
56
|
-
- `class
|
|
57
|
-
- `
|
|
58
|
-
- `
|
|
56
|
+
- `class DomainQuotes(config: DomainQuoteConfig)`
|
|
57
|
+
- `getQuote(extension: string, currency: string, options?: GetQuoteOptions): Promise<Quote>` – same behavior as above, but uses the provided config.
|
|
58
|
+
- `DEFAULT_CONFIG: DomainQuoteConfig` – exported snapshot config used by `getDefaultQuote`.
|
|
59
59
|
|
|
60
60
|
- `listSupportedExtensions(): string[]`
|
|
61
61
|
- All extensions with a non-zero price in the dataset.
|
|
@@ -73,21 +73,21 @@ Types
|
|
|
73
73
|
```ts
|
|
74
74
|
type DiscountPolicy = 'stack' | 'max';
|
|
75
75
|
|
|
76
|
-
interface
|
|
76
|
+
interface GetQuoteOptions {
|
|
77
77
|
discountCodes?: string[];
|
|
78
78
|
now?: number | Date;
|
|
79
79
|
discountPolicy?: DiscountPolicy;
|
|
80
80
|
transaction?: 'create' | 'renew' | 'restore' | 'transfer'; // default 'create'
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
type MarkupType = 'percentage' | '
|
|
83
|
+
type MarkupType = 'percentage' | 'fixedUsd';
|
|
84
84
|
|
|
85
|
-
interface
|
|
86
|
-
type: MarkupType; // percentage -> 0.2 === +20%,
|
|
85
|
+
interface Markup {
|
|
86
|
+
type: MarkupType; // percentage -> 0.2 === +20%, fixedUsd -> +$ value before conversion
|
|
87
87
|
value: number;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
interface
|
|
90
|
+
interface Quote {
|
|
91
91
|
extension: string;
|
|
92
92
|
currency: string;
|
|
93
93
|
basePrice: number;
|
|
@@ -114,7 +114,7 @@ interface DiscountConfig {
|
|
|
114
114
|
endAt: string; // ISO timestamp
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
interface
|
|
117
|
+
interface DomainQuoteConfig {
|
|
118
118
|
createPrices: Record<string, number>; // base USD prices for create
|
|
119
119
|
// Optional price tables per transaction type (all USD). Falls back to `createPrices` when absent.
|
|
120
120
|
renewPrices?: Record<string, number>;
|
|
@@ -123,7 +123,7 @@ interface DomainPricesConfig {
|
|
|
123
123
|
exchangeRates: ExchangeRateData[]; // currency conversion data
|
|
124
124
|
vatRate: number; // single VAT rate applied to subtotal
|
|
125
125
|
discounts: Record<string, DiscountConfig>; // discount code → config
|
|
126
|
-
markup?:
|
|
126
|
+
markup?: Markup; // optional markup applied before conversion
|
|
127
127
|
}
|
|
128
128
|
```
|
|
129
129
|
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var O={SAVE1:{rate:.01,extensions:["com","net"],startAt:"2023-01-01T00:00:00Z",endAt:"2025-12-31T23:59:59Z"},NEWUSER15:{rate:.15,extensions:["com","net","org"],startAt:"2023-01-01T00:00:00Z",endAt:"2024-12-31T23:59:59Z"},FALL20:{rate:.2,extensions:["org","info"],startAt:"2024-09-01T00:00:00Z",endAt:"2024-12-01T00:00:00Z"}};var k=.075;function Z(){return(M.supportedCurrencies??["USD","NGN"]).slice()}function ce(e){if(!e)return!1;let t=e.toUpperCase();return Z().includes(t)}function ae(){let e=N();return Object.keys(e).sort()}function w(e){if(e==null)return;if(typeof e=="number")return!Number.isFinite(e)||e<=0?void 0:{USD:e};let t={};for(let[n,r]of Object.entries(e)){let c=n?.toUpperCase();if(!c||!Number.isFinite(r)||r<=0)continue;let a=t[c];t[c]=a===void 0?r:Math.min(a,r)}return Object.keys(t).length>0?t:void 0}function V(e){return!!w(e)}function ue(e){let t=N(),n=F(e),r=t[n];return V(r)}function F(e){return e&&e.trim().toLowerCase().replace(/^\.+/,"")}function q(e){return e instanceof Date?e.getTime():typeof e=="number"?e:Date.now()}var z="https://raw.githubusercontent.com/namewiz/registrar-pricelist/refs/heads/main/data/unified-create-prices.csv",J="https://raw.githubusercontent.com/namewiz/registrar-pricelist/refs/heads/main/data/exchange-rates.json";async function X(e){let t=await fetch(e);if(!t.ok)throw new Error(`Failed to fetch ${e}: ${t.status} ${t.statusText}`);return t.text()}async function H(e){let t=await fetch(e);if(!t.ok)throw new Error(`Failed to fetch ${e}: ${t.status} ${t.statusText}`);return t.json()}function B(e){let t=e.split(/\r?\n/).map(c=>c.trim()).filter(Boolean);if(t.length===0)return{};let n=t.shift(),r={};for(let c of t){let a=c.split(",");if(a.length<4)continue;let x=a[0]?.trim().toLowerCase(),o=a[2]?.trim().toUpperCase(),E=a[3]?.trim(),d=Number(E);if(!x||!o||!Number.isFinite(d)||d<=0)continue;let i=r[x],u;i===void 0?u={}:typeof i=="number"?u={USD:i}:u=i;let p=u[o];u[o]=p===void 0?d:Math.min(p,d),r[x]=u}return r}async function W(){try{let[e,t]=await Promise.all([X(z).then(B),H(J)]);return[e,t]}catch(e){let t=e instanceof Error?e:new Error(typeof e=="string"?e:"Unknown error");throw t.message=`domain-quotes: failed to load remote pricing data: ${t.message}`,t}}var[Y,K]=await W();function N(){return Y}function ee(){return K}function te(){return O}var D=class extends Error{constructor(t,n){super(n),this.name="DomainQuoteError",this.code=t}},R=class extends D{constructor(t){super("ERR_UNSUPPORTED_EXTENSION",`Unsupported extension: ${t}`),this.name="UnsupportedExtensionError"}},P=class extends D{constructor(t){super("ERR_UNSUPPORTED_CURRENCY",`Unsupported currency: ${t}`),this.name="UnsupportedCurrencyError"}};function ne(){return{countryCode:"US",currencyName:"United States Dollar",currencySymbol:"$",currencyCode:"USD",exchangeRate:1,inverseRate:1}}function m(e){return Number(e.toFixed(2))}function re(e,t){if(!t)return e;let n=typeof t.value=="number"?t.value:0;if(!Number.isFinite(n)||n<=0)return e;switch(t.type){case"percentage":return e+e*n;case"fixedUsd":return e+n;default:return e}}var C=class{constructor(t){this.config=t}findRateInfo(t){if(t==="USD")return ne();let n=this.config.exchangeRates.find(r=>r.currencyCode===t);if(!n)throw new P(t);return n}async getQuote(t,n,r={}){let c=this.config.createPrices,a=typeof this.config.vatRate=="number"?this.config.vatRate:k,x=this.config.discounts,o=F(t),E=r.transaction||"create",d=w(c[o]);if(!d)throw new R(o);let i={...d},u=(()=>{switch(E){case"renew":return this.config.renewPrices;case"restore":return this.config.restorePrices;case"transfer":return this.config.transferPrices;case"create":default:return}})();if(u){let s=w(u[o]);s&&(i={...i,...s})}if(Object.keys(i).length===0)throw new R(o);let p=(n||"").toUpperCase();if(!(this.config.supportedCurrencies??["USD","NGN"]).includes(p))throw new P(n);let T=this.findRateInfo(p),G=T.currencySymbol,f=i.USD;f===void 0&&(f=d.USD);let b=i[p];if(f===void 0&&b!==void 0&&(f=b/T.exchangeRate),f===void 0||f<=0)throw new R(o);let S=re(f,this.config.markup),l;if(b!==void 0&&f>0){let s=b/f;l=m(S*s)}else l=m(S*T.exchangeRate);let I=a,$=Array.from(new Set((r.discountCodes||[]).map(s=>s.toUpperCase()))),U=q(r.now),y=[];for(let s of $){let g=x[s];if(!g)continue;let Q=Date.parse(g.startAt),_=Date.parse(g.endAt);Number.isNaN(Q)||Number.isNaN(_)||U<Q||U>_||g.extensions.includes(o)&&y.push(m(l*g.rate))}let h=0;y.length>0&&(r.discountPolicy==="stack"?h=m(y.reduce((s,g)=>s+g,0)):h=Math.max(...y)),h>l&&(h=l);let A=m(l-h),v=m(A*I),j=m(A+v);return{extension:o,currency:p,basePrice:l,discount:h,tax:v,totalPrice:j,symbol:G,transaction:E}}},M={createPrices:N(),exchangeRates:ee(),vatRate:k,discounts:te(),supportedCurrencies:["USD","NGN"]};async function fe(e,t,n={}){return new C(M).getQuote(e,t,n)}export{M as DEFAULT_CONFIG,k as DEFAULT_VAT_RATE,C as DomainQuotes,P as UnsupportedCurrencyError,R as UnsupportedExtensionError,fe as getDefaultQuote,ce as isSupportedCurrency,ue as isSupportedExtension,Z as listSupportedCurrencies,ae as listSupportedExtensions};
|