@haatlabs-core/storefront 0.13.0 → 0.14.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.
@@ -0,0 +1,6 @@
1
+ import type { PagePrice } from '../price.js';
2
+ /**
3
+ * An Alibaba product page: its tier prices from the page's own data, in the currency the page
4
+ * shows (its product data can name another one), and the minimum order. Null without tiers.
5
+ */
6
+ export declare function alibabaPagePrice(doc: Document, _url: string): PagePrice | null;
@@ -0,0 +1,74 @@
1
+ import { priceOfText } from '../amazon/money.js';
2
+ import { jsonLdProduct } from '../jsonld.js';
3
+ import { minorPerMajor } from '../price.js';
4
+ /** The JSON array that starts at `start` in `text` (brackets matched, strings respected). */
5
+ function arrayAt(text, start) {
6
+ let depth = 0;
7
+ let inString = false;
8
+ for (let i = start; i < text.length; i += 1) {
9
+ const c = text[i];
10
+ if (inString) {
11
+ if (c === '\\')
12
+ i += 1;
13
+ else if (c === '"')
14
+ inString = false;
15
+ }
16
+ else if (c === '"')
17
+ inString = true;
18
+ else if (c === '[')
19
+ depth += 1;
20
+ else if (c === ']' && --depth === 0) {
21
+ try {
22
+ return JSON.parse(text.slice(start, i + 1));
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ }
29
+ return null;
30
+ }
31
+ const isLadder = (value) => {
32
+ const l = value;
33
+ return (typeof l?.minQuantity === 'number' &&
34
+ typeof l.price === 'number' &&
35
+ typeof l.formatPrice === 'string');
36
+ };
37
+ /**
38
+ * An Alibaba product page: its tier prices from the page's own data, in the currency the page
39
+ * shows (its product data can name another one), and the minimum order. Null without tiers.
40
+ */
41
+ export function alibabaPagePrice(doc, _url) {
42
+ const html = doc.documentElement.innerHTML;
43
+ const at = html.indexOf('"priceList":[');
44
+ if (at < 0)
45
+ return null;
46
+ const list = arrayAt(html, at + '"priceList":'.length);
47
+ if (!Array.isArray(list))
48
+ return null;
49
+ const ladders = list.filter(isLadder).sort((a, b) => a.minQuantity - b.minQuantity);
50
+ const first = ladders[0];
51
+ if (!first)
52
+ return null;
53
+ const currency = priceOfText(first.formatPrice, 'USD')?.currency ?? 'USD';
54
+ const unit = minorPerMajor(currency);
55
+ const tiers = ladders.map((l) => ({
56
+ minQuantity: l.minQuantity,
57
+ amountMinor: Math.round(l.price * unit),
58
+ }));
59
+ const minimum = Number(/"minOrderQuantity":(\d+(?:\.\d+)?)/.exec(html)?.[1] ?? 0);
60
+ const product = jsonLdProduct(doc);
61
+ const title = product?.title ?? doc.title.replace(/ - Buy Product on Alibaba\.com$/, '').trim();
62
+ if (!title)
63
+ return null;
64
+ return {
65
+ title,
66
+ amountMinor: tiers[0]?.amountMinor ?? 0,
67
+ currency,
68
+ image: product?.image ?? null,
69
+ source: 'alibaba',
70
+ tiers,
71
+ ...(minimum > 1 ? { minimumOrder: Math.round(minimum) } : {}),
72
+ };
73
+ }
74
+ //# sourceMappingURL=price.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"price.js","sourceRoot":"","sources":["../../src/alibaba/price.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { PagePrice } from '../price.js';
2
+ /**
3
+ * An AliExpress product page: the price it shows, in its currency. A new-buyer welcome deal
4
+ * (a banner with the regular price struck out) is not what a seller pays when restocking: the
5
+ * regular price is taken then. The product data's price is that deal too, so it is not used.
6
+ */
7
+ export declare function aliexpressPagePrice(doc: Document, _url: string): PagePrice | null;
@@ -0,0 +1,33 @@
1
+ import { priceOfText } from '../amazon/money.js';
2
+ import { jsonLdProduct } from '../jsonld.js';
3
+ import { minorPerMajor } from '../price.js';
4
+ const textOf = (doc, part) => doc.querySelector(`[class*="price-default--${part}--"]`)?.textContent?.trim() || null;
5
+ /**
6
+ * An AliExpress product page: the price it shows, in its currency. A new-buyer welcome deal
7
+ * (a banner with the regular price struck out) is not what a seller pays when restocking: the
8
+ * regular price is taken then. The product data's price is that deal too, so it is not used.
9
+ */
10
+ export function aliexpressPagePrice(doc, _url) {
11
+ const product = jsonLdProduct(doc);
12
+ const current = textOf(doc, 'current');
13
+ const original = textOf(doc, 'original');
14
+ const deal = doc.querySelector('[class*="price-default--bannerContent--"]') !== null;
15
+ const shown = deal && original ? original : current;
16
+ if (!shown)
17
+ return null;
18
+ const parsed = priceOfText(shown, product?.currency ?? 'USD');
19
+ if (!parsed)
20
+ return null;
21
+ const currency = product?.currency ?? parsed.currency;
22
+ const title = product?.title ?? doc.title.replace(/ - AliExpress.*$/, '').trim();
23
+ if (!title)
24
+ return null;
25
+ return {
26
+ title,
27
+ amountMinor: Math.round(parsed.amount * minorPerMajor(currency)),
28
+ currency,
29
+ image: product?.image ?? null,
30
+ source: 'aliexpress',
31
+ };
32
+ }
33
+ //# sourceMappingURL=price.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"price.js","sourceRoot":"","sources":["../../src/aliexpress/price.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG5C,MAAM,MAAM,GAAG,CAAC,GAAa,EAAE,IAAY,EAAiB,EAAE,CAC5D,GAAG,CAAC,aAAa,CAAC,2BAA2B,IAAI,MAAM,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC;AAExF;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAa,EAAE,IAAY;IAC7D,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACvC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,GAAG,CAAC,aAAa,CAAC,2CAA2C,CAAC,KAAK,IAAI,CAAC;IACrF,MAAM,KAAK,GAAG,IAAI,IAAI,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC;IACpD,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,IAAI,KAAK,CAAC,CAAC;IAC9D,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC;IACtD,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACjF,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,OAAO;QACL,KAAK;QACL,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;QAChE,QAAQ;QACR,KAAK,EAAE,OAAO,EAAE,KAAK,IAAI,IAAI;QAC7B,MAAM,EAAE,YAAY;KACrB,CAAC;AACJ,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export type { PagePrice } from './price.js';
2
2
  export { minorPerMajor } from './price.js';
3
+ export { jsonLdPrice, jsonLdProduct } from './jsonld.js';
4
+ export { priceForQuantity } from './tiers.js';
3
5
  export { parseHtml } from './amazon/parse-html.js';
4
6
  export { parseAmazonProduct, jsonAfter } from './amazon/parse-product.js';
5
7
  export type { AmazonPrice, AmazonProductPage } from './amazon/parse-product.js';
@@ -9,3 +11,9 @@ export { amazonMarketOf, amazonPageOf } from './amazon/page-kind.js';
9
11
  export type { AmazonMarket, AmazonPageKind } from './amazon/page-kind.js';
10
12
  export { AMAZON_IMAGE_HOSTS, AMAZON_ORIGINS } from './amazon/origins.js';
11
13
  export { shopifyPagePrice, shopifyProductPath } from './shopify/product.js';
14
+ export { indiamartPagePrice } from './indiamart/price.js';
15
+ export { meeshoPagePrice } from './meesho/price.js';
16
+ export { aliexpressPagePrice } from './aliexpress/price.js';
17
+ export { alibabaPagePrice } from './alibaba/price.js';
18
+ export { storefrontReaderFor } from './sites.js';
19
+ export type { PageReader } from './sites.js';
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  export { minorPerMajor } from './price.js';
2
+ export { jsonLdPrice, jsonLdProduct } from './jsonld.js';
3
+ export { priceForQuantity } from './tiers.js';
2
4
  export { parseHtml } from './amazon/parse-html.js';
3
5
  export { parseAmazonProduct, jsonAfter } from './amazon/parse-product.js';
4
6
  export { amazonPagePrice } from './amazon/price.js';
@@ -6,4 +8,9 @@ export { currencyOfSymbol, parseAmount, priceOfText } from './amazon/money.js';
6
8
  export { amazonMarketOf, amazonPageOf } from './amazon/page-kind.js';
7
9
  export { AMAZON_IMAGE_HOSTS, AMAZON_ORIGINS } from './amazon/origins.js';
8
10
  export { shopifyPagePrice, shopifyProductPath } from './shopify/product.js';
11
+ export { indiamartPagePrice } from './indiamart/price.js';
12
+ export { meeshoPagePrice } from './meesho/price.js';
13
+ export { aliexpressPagePrice } from './aliexpress/price.js';
14
+ export { alibabaPagePrice } from './alibaba/price.js';
15
+ export { storefrontReaderFor } from './sites.js';
9
16
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAE1E,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErE,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAE1E,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErE,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { PagePrice } from '../price.js';
2
+ /**
3
+ * An IndiaMART product page: its price (or price range) from the product data, the minimum
4
+ * order when the supplier sets one, and "price on request" when the product shows no price.
5
+ */
6
+ export declare function indiamartPagePrice(doc: Document, _url: string): PagePrice | null;
@@ -0,0 +1,27 @@
1
+ import { jsonLdPrice, jsonLdProduct } from '../jsonld.js';
2
+ /** IndiaMART's own page data: the supplier's minimum order quantity ("" when not set). */
3
+ const MINIMUM_ORDER = /"PC_ITEM_MIN_ORDER_QUANTITY":"(\d+)"/;
4
+ /**
5
+ * An IndiaMART product page: its price (or price range) from the product data, the minimum
6
+ * order when the supplier sets one, and "price on request" when the product shows no price.
7
+ */
8
+ export function indiamartPagePrice(doc, _url) {
9
+ const minimum = Number(MINIMUM_ORDER.exec(doc.documentElement.innerHTML)?.[1] ?? 0);
10
+ const extra = minimum > 1 ? { minimumOrder: minimum } : {};
11
+ const priced = jsonLdPrice(doc, 'indiamart');
12
+ if (priced)
13
+ return { ...priced, ...extra };
14
+ const product = jsonLdProduct(doc);
15
+ if (!product)
16
+ return null;
17
+ return {
18
+ title: product.title,
19
+ amountMinor: 0,
20
+ currency: product.currency ?? 'INR',
21
+ image: product.image,
22
+ source: 'indiamart',
23
+ askPrice: true,
24
+ ...extra,
25
+ };
26
+ }
27
+ //# sourceMappingURL=price.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"price.js","sourceRoot":"","sources":["../../src/indiamart/price.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAG1D,0FAA0F;AAC1F,MAAM,aAAa,GAAG,sCAAsC,CAAC;AAE7D;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAa,EAAE,IAAY;IAC5D,MAAM,OAAO,GAAG,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACpF,MAAM,KAAK,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3D,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;IAC7C,IAAI,MAAM;QAAE,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IAC3C,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,OAAO;QACL,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,WAAW,EAAE,CAAC;QACd,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,KAAK;QACnC,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,MAAM,EAAE,WAAW;QACnB,QAAQ,EAAE,IAAI;QACd,GAAG,KAAK;KACT,CAAC;AACJ,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { PagePrice } from './price.js';
2
+ /**
3
+ * The price from the page's product data (schema.org JSON-LD): one Offer's price, or an
4
+ * AggregateOffer's low to high as a range. Null when the page publishes no priced product.
5
+ */
6
+ export declare function jsonLdPrice(doc: Document, source: PagePrice['source']): PagePrice | null;
7
+ /** The page's product (schema.org JSON-LD), priced or not: its name, image and offer currency. */
8
+ export declare function jsonLdProduct(doc: Document): {
9
+ title: string;
10
+ image: string | null;
11
+ currency: string | null;
12
+ } | null;
package/dist/jsonld.js ADDED
@@ -0,0 +1,94 @@
1
+ import { minorPerMajor } from './price.js';
2
+ const asArray = (value) => Array.isArray(value) ? value : value == null ? [] : [value];
3
+ const typeOf = (node) => asArray(node['@type']).map(String);
4
+ const amount = (value) => {
5
+ const n = typeof value === 'number'
6
+ ? value
7
+ : typeof value === 'string'
8
+ ? Number(value.replace(/,/g, ''))
9
+ : NaN;
10
+ return Number.isFinite(n) && n >= 0 ? n : null;
11
+ };
12
+ /** Every node in a JSON-LD block: the block, its array items and its @graph. */
13
+ function nodesOf(data) {
14
+ return asArray(data).flatMap((item) => {
15
+ if (typeof item !== 'object' || item === null)
16
+ return [];
17
+ const node = item;
18
+ return [node, ...nodesOf(node['@graph'])];
19
+ });
20
+ }
21
+ function imageOf(value) {
22
+ const first = asArray(value)[0];
23
+ if (typeof first === 'string')
24
+ return first;
25
+ if (typeof first === 'object' && first !== null && typeof first.url === 'string') {
26
+ return first.url;
27
+ }
28
+ return null;
29
+ }
30
+ /**
31
+ * The price from the page's product data (schema.org JSON-LD): one Offer's price, or an
32
+ * AggregateOffer's low to high as a range. Null when the page publishes no priced product.
33
+ */
34
+ export function jsonLdPrice(doc, source) {
35
+ for (const script of Array.from(doc.querySelectorAll('script[type="application/ld+json"]'))) {
36
+ let data;
37
+ try {
38
+ data = JSON.parse(script.textContent ?? '');
39
+ }
40
+ catch {
41
+ continue;
42
+ }
43
+ for (const node of nodesOf(data)) {
44
+ if (!typeOf(node).includes('Product') || typeof node.name !== 'string')
45
+ continue;
46
+ for (const offer of asArray(node.offers)) {
47
+ if (typeof offer !== 'object' || offer === null)
48
+ continue;
49
+ const o = offer;
50
+ const currency = typeof o.priceCurrency === 'string' ? o.priceCurrency : null;
51
+ if (!currency || !/^[A-Z]{3}$/.test(currency))
52
+ continue;
53
+ const unit = minorPerMajor(currency);
54
+ const low = amount(o.lowPrice);
55
+ const high = amount(o.highPrice);
56
+ const price = amount(o.price) ?? low;
57
+ if (price === null)
58
+ continue;
59
+ return {
60
+ title: node.name.trim(),
61
+ amountMinor: Math.round(price * unit),
62
+ currency,
63
+ image: imageOf(node.image),
64
+ source,
65
+ ...(low !== null && high !== null && high > low
66
+ ? { range: { lowMinor: Math.round(low * unit), highMinor: Math.round(high * unit) } }
67
+ : {}),
68
+ };
69
+ }
70
+ }
71
+ }
72
+ return null;
73
+ }
74
+ /** The page's product (schema.org JSON-LD), priced or not: its name, image and offer currency. */
75
+ export function jsonLdProduct(doc) {
76
+ for (const script of Array.from(doc.querySelectorAll('script[type="application/ld+json"]'))) {
77
+ let data;
78
+ try {
79
+ data = JSON.parse(script.textContent ?? '');
80
+ }
81
+ catch {
82
+ continue;
83
+ }
84
+ for (const node of nodesOf(data)) {
85
+ if (!typeOf(node).includes('Product') || typeof node.name !== 'string')
86
+ continue;
87
+ const offer = asArray(node.offers)[0];
88
+ const currency = typeof offer?.priceCurrency === 'string' ? offer.priceCurrency : null;
89
+ return { title: node.name.trim(), image: imageOf(node.image), currency };
90
+ }
91
+ }
92
+ return null;
93
+ }
94
+ //# sourceMappingURL=jsonld.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonld.js","sourceRoot":"","sources":["../src/jsonld.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { PagePrice } from '../price.js';
2
+ /** A Meesho product page: its price from the product data the page publishes. */
3
+ export declare function meeshoPagePrice(doc: Document, _url: string): PagePrice | null;
@@ -0,0 +1,6 @@
1
+ import { jsonLdPrice } from '../jsonld.js';
2
+ /** A Meesho product page: its price from the product data the page publishes. */
3
+ export function meeshoPagePrice(doc, _url) {
4
+ return jsonLdPrice(doc, 'meesho');
5
+ }
6
+ //# sourceMappingURL=price.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"price.js","sourceRoot":"","sources":["../../src/meesho/price.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAG3C,iFAAiF;AACjF,MAAM,UAAU,eAAe,CAAC,GAAa,EAAE,IAAY;IACzD,OAAO,WAAW,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AACpC,CAAC"}
package/dist/price.d.ts CHANGED
@@ -4,7 +4,21 @@ export interface PagePrice {
4
4
  amountMinor: number;
5
5
  currency: string;
6
6
  image: string | null;
7
- source: 'amazon' | 'shopify';
7
+ source: 'amazon' | 'shopify' | 'indiamart' | 'meesho' | 'aliexpress' | 'alibaba';
8
+ /** Wholesale prices per piece, ascending by the quantity they start at. */
9
+ tiers?: {
10
+ minQuantity: number;
11
+ amountMinor: number;
12
+ }[];
13
+ /** The fewest pieces the supplier sells, when the page says. */
14
+ minimumOrder?: number;
15
+ /** A price range ("₹200 - ₹350"); `amountMinor` is its low end. */
16
+ range?: {
17
+ lowMinor: number;
18
+ highMinor: number;
19
+ };
20
+ /** The supplier gives prices on request only; `amountMinor` is 0. */
21
+ askPrice?: true;
8
22
  }
9
23
  /** How many minor units make one major unit of `currency` (100 for INR, 1 for JPY). */
10
24
  export declare function minorPerMajor(currency: string): number;
package/dist/price.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"price.js","sourceRoot":"","sources":["../src/price.ts"],"names":[],"mappings":"AASA,uFAAuF;AACvF,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC,eAAe,EAAE;SAC1F,qBAAqB,CAAC;IACzB,OAAO,EAAE,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;AAC7B,CAAC"}
1
+ {"version":3,"file":"price.js","sourceRoot":"","sources":["../src/price.ts"],"names":[],"mappings":"AAiBA,uFAAuF;AACvF,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC,eAAe,EAAE;SAC1F,qBAAqB,CAAC;IACzB,OAAO,EAAE,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { PagePrice } from './price.js';
2
+ export type PageReader = (doc: Document, url: string) => PagePrice | null;
3
+ /** The wholesale site's page reader for a host (the domain or any of its subdomains), or null. */
4
+ export declare function storefrontReaderFor(host: string): PageReader | null;
package/dist/sites.js ADDED
@@ -0,0 +1,17 @@
1
+ import { alibabaPagePrice } from './alibaba/price.js';
2
+ import { aliexpressPagePrice } from './aliexpress/price.js';
3
+ import { indiamartPagePrice } from './indiamart/price.js';
4
+ import { meeshoPagePrice } from './meesho/price.js';
5
+ const SITES = [
6
+ ['indiamart.com', indiamartPagePrice],
7
+ ['meesho.com', meeshoPagePrice],
8
+ ['aliexpress.com', aliexpressPagePrice],
9
+ ['aliexpress.us', aliexpressPagePrice],
10
+ ['alibaba.com', alibabaPagePrice],
11
+ ];
12
+ /** The wholesale site's page reader for a host (the domain or any of its subdomains), or null. */
13
+ export function storefrontReaderFor(host) {
14
+ const h = host.toLowerCase();
15
+ return SITES.find(([domain]) => h === domain || h.endsWith(`.${domain}`))?.[1] ?? null;
16
+ }
17
+ //# sourceMappingURL=sites.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sites.js","sourceRoot":"","sources":["../src/sites.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAKpD,MAAM,KAAK,GAA2C;IACpD,CAAC,eAAe,EAAE,kBAAkB,CAAC;IACrC,CAAC,YAAY,EAAE,eAAe,CAAC;IAC/B,CAAC,gBAAgB,EAAE,mBAAmB,CAAC;IACvC,CAAC,eAAe,EAAE,mBAAmB,CAAC;IACtC,CAAC,aAAa,EAAE,gBAAgB,CAAC;CAClC,CAAC;AAEF,kGAAkG;AAClG,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAC7B,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,MAAM,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;AACzF,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { PagePrice } from './price.js';
2
+ /**
3
+ * The price per piece for `quantity`: the tier with the largest minimum not above it. Below
4
+ * the minimum order (or the first tier), the lowest tier is used and `belowMinimum` says so.
5
+ */
6
+ export declare function priceForQuantity(price: PagePrice, quantity: number): {
7
+ amountMinor: number;
8
+ tier: {
9
+ minQuantity: number;
10
+ } | null;
11
+ belowMinimum: boolean;
12
+ };
package/dist/tiers.js ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The price per piece for `quantity`: the tier with the largest minimum not above it. Below
3
+ * the minimum order (or the first tier), the lowest tier is used and `belowMinimum` says so.
4
+ */
5
+ export function priceForQuantity(price, quantity) {
6
+ const tiers = price.tiers ?? [];
7
+ const first = tiers[0];
8
+ const minimum = Math.max(price.minimumOrder ?? 1, first?.minQuantity ?? 1);
9
+ const belowMinimum = quantity < minimum;
10
+ if (!first)
11
+ return { amountMinor: price.amountMinor, tier: null, belowMinimum };
12
+ const tier = tiers.filter((t) => t.minQuantity <= quantity).at(-1) ?? first;
13
+ return { amountMinor: tier.amountMinor, tier: { minQuantity: tier.minQuantity }, belowMinimum };
14
+ }
15
+ //# sourceMappingURL=tiers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tiers.js","sourceRoot":"","sources":["../src/tiers.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAAgB,EAChB,QAAgB;IAEhB,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACvB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,IAAI,CAAC,EAAE,KAAK,EAAE,WAAW,IAAI,CAAC,CAAC,CAAC;IAC3E,MAAM,YAAY,GAAG,QAAQ,GAAG,OAAO,CAAC;IACxC,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;IAChF,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,IAAI,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC;IAC5E,OAAO,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,EAAE,YAAY,EAAE,CAAC;AAClG,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haatlabs-core/storefront",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Read a product's price from a shop page in the browser: Amazon product pages and Shopify products.",
5
5
  "keywords": [
6
6
  "haatlabs",
@@ -37,13 +37,13 @@
37
37
  "runtime": "browser"
38
38
  },
39
39
  "dependencies": {
40
- "@haatlabs-core/provider-amazon": "0.13.0"
40
+ "@haatlabs-core/provider-amazon": "0.14.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "jsdom": "^30.1.2",
44
44
  "@types/node": "^24.0.0",
45
- "@haatlabs-core/vitest-config": "0.13.0",
46
- "@haatlabs-core/tsconfig": "0.13.0"
45
+ "@haatlabs-core/tsconfig": "0.14.0",
46
+ "@haatlabs-core/vitest-config": "0.14.0"
47
47
  },
48
48
  "scripts": {
49
49
  "build": "rm -rf dist && tsc -p tsconfig.build.json",