@nosto/search-js 0.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/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025, Nosto
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ * Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ * Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # Search JS
2
+
3
+ Search JS is a wrapper for the Nosto Search functionality with some extended functionality such as
4
+ * Nosto currency formatting
5
+ * Nosto product thumbnails
6
+ * Retry logic
7
+
8
+ ## Installation
9
+
10
+ To install the package, use your preferred package manager:
11
+
12
+ ```bash
13
+ yarn add @nosto/search-js
14
+ # or
15
+ npm install @nosto/search-js --save
16
+ ```
17
+
18
+ ## Nosto stub
19
+
20
+ When using this library, it is not necessary to create the Nosto stub. It will be created automatically as soon as the library is imported for the first time.
21
+
22
+ ## Usage
23
+
24
+ The main export of this library is the `search` function. It is compatible with the search function of the Nosto JS API and adds a couple of additional options
25
+
26
+ ```ts
27
+ import { search } from "@nosto/search-js"
28
+ import { priceDecorator } from "@nosto/search-js/currencies"
29
+
30
+ const response = await search({
31
+ query: 'my search',
32
+ products: {
33
+ fields: [
34
+ "productId",
35
+ "name",
36
+ "price",
37
+ "listPrice",
38
+ "priceCurrencyCode"
39
+ ]
40
+ }
41
+ }, {
42
+ track: 'serp',
43
+ hitDecorators: [
44
+ priceDecorator()
45
+ ]
46
+ })
47
+
48
+ ```
@@ -0,0 +1,11 @@
1
+ import { CurrencySettingsDTO } from "@nosto/nosto-js/client";
2
+ type CurrencyFormats = Record<string, CurrencySettingsDTO>;
3
+ export interface CurrencyConfig {
4
+ defaultCurrency: string;
5
+ defaultLocale: string;
6
+ currencySettings: CurrencyFormats;
7
+ }
8
+ export declare function getCurrencyFormatting(overrides?: Partial<CurrencyConfig>): {
9
+ formatCurrency: (value: number, currency?: string) => string;
10
+ };
11
+ export {};
@@ -0,0 +1,14 @@
1
+ import { SearchProduct, SearchProductSku } from "@nosto/nosto-js/client";
2
+ import { CurrencyConfig } from "./getCurrencyFormatting";
3
+ type FormattedPrices = {
4
+ priceText?: string;
5
+ listPriceText?: string;
6
+ };
7
+ /**
8
+ * Exposes currency formatting logic as a SearchProduct decorator
9
+ * Sets priceText and listPriceText fields on product and SKU level
10
+ */
11
+ export declare function priceDecorator(config?: Partial<CurrencyConfig>): (hit: SearchProduct) => SearchProduct & FormattedPrices & {
12
+ skus?: (SearchProductSku & FormattedPrices)[];
13
+ };
14
+ export {};
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const y=require("./index.es-D_7T9vdv.cjs"),g={defaultCurrency:"EUR",defaultLocale:"en-US",currencySettings:{}},p={EUR:"de-DE",GBP:"en-GB",USD:"en-US",AUD:"en-AU",CAD:"en-CA",INR:"en-IN",AFN:"en-IN",BDT:"en-IN",BTN:"en-IN",MMK:"en-IN",NPR:"en-IN",PKR:"en-IN"};function d(o={}){const u={...g,...o};o.currencySettings||y.s(c=>{u.currencySettings=c.internal.getSettings().currencySettings});function a(c,n){const{defaultCurrency:t,currencySettings:e,defaultLocale:i}=u,s=n??t,m=p[s]??i;if(s in e){const r=e[s],l=new Intl.NumberFormat(m,{useGrouping:!!r.groupingSeparator,minimumFractionDigits:r.decimalPlaces,maximumFractionDigits:r.decimalPlaces}).formatToParts(c).map(f=>f.type==="group"?r.groupingSeparator:f.type==="decimal"?r.decimalCharacter:f.value).join("");return r!=null&&r.currencyBeforeAmount?`${r.currencyToken}${l}`:`${l}${r==null?void 0:r.currencyToken}`}return new Intl.NumberFormat(m,{style:"currency",currency:s}).format(c)}return{formatCurrency:a}}function C(o){const{formatCurrency:u}=d(o);function a(n,t){const e={};return n.price!==void 0&&(e.priceText=u(n.price,t)),n.listPrice!==void 0&&(e.listPriceText=u(n.listPrice,t)),Object.assign({},n,e)}function c(n){return n.price!==void 0||n.listPrice!==void 0}return function(t){if(!c(t))return t;const e=a(t,t.priceCurrencyCode);return e.skus&&e.skus.some(c)&&(e.skus=e.skus.map(i=>c(i)?a(i,t.priceCurrencyCode):i)),e}}exports.getCurrencyFormatting=d;exports.priceDecorator=C;
@@ -0,0 +1,2 @@
1
+ export { getCurrencyFormatting, type CurrencyConfig } from "./currencies/getCurrencyFormatting";
2
+ export { priceDecorator } from "./currencies/priceDecorator";
@@ -0,0 +1,68 @@
1
+ import { s as d } from "./index.es-Bcd5IQh9.js";
2
+ const y = {
3
+ defaultCurrency: "EUR",
4
+ defaultLocale: "en-US",
5
+ /** @hidden */
6
+ currencySettings: {}
7
+ }, p = {
8
+ EUR: "de-DE",
9
+ GBP: "en-GB",
10
+ USD: "en-US",
11
+ AUD: "en-AU",
12
+ CAD: "en-CA",
13
+ //India, Afghanistan, Bangladesh, Bhutan, Myanmar, Nepal, and Pakistan uses lakhs and crores notation
14
+ INR: "en-IN",
15
+ AFN: "en-IN",
16
+ BDT: "en-IN",
17
+ BTN: "en-IN",
18
+ MMK: "en-IN",
19
+ NPR: "en-IN",
20
+ PKR: "en-IN"
21
+ };
22
+ function g(o = {}) {
23
+ const u = {
24
+ ...y,
25
+ ...o
26
+ };
27
+ o.currencySettings || d((c) => {
28
+ u.currencySettings = c.internal.getSettings().currencySettings;
29
+ });
30
+ function a(c, n) {
31
+ const { defaultCurrency: t, currencySettings: e, defaultLocale: i } = u, s = n ?? t, m = p[s] ?? i;
32
+ if (s in e) {
33
+ const r = e[s], l = new Intl.NumberFormat(m, {
34
+ useGrouping: !!r.groupingSeparator,
35
+ minimumFractionDigits: r.decimalPlaces,
36
+ maximumFractionDigits: r.decimalPlaces
37
+ }).formatToParts(c).map((f) => f.type === "group" ? r.groupingSeparator : f.type === "decimal" ? r.decimalCharacter : f.value).join("");
38
+ return r != null && r.currencyBeforeAmount ? `${r.currencyToken}${l}` : `${l}${r == null ? void 0 : r.currencyToken}`;
39
+ }
40
+ return new Intl.NumberFormat(m, {
41
+ style: "currency",
42
+ currency: s
43
+ }).format(c);
44
+ }
45
+ return {
46
+ formatCurrency: a
47
+ };
48
+ }
49
+ function S(o) {
50
+ const { formatCurrency: u } = g(o);
51
+ function a(n, t) {
52
+ const e = {};
53
+ return n.price !== void 0 && (e.priceText = u(n.price, t)), n.listPrice !== void 0 && (e.listPriceText = u(n.listPrice, t)), Object.assign({}, n, e);
54
+ }
55
+ function c(n) {
56
+ return n.price !== void 0 || n.listPrice !== void 0;
57
+ }
58
+ return function(t) {
59
+ if (!c(t))
60
+ return t;
61
+ const e = a(t, t.priceCurrencyCode);
62
+ return e.skus && e.skus.some(c) && (e.skus = e.skus.map((i) => c(i) ? a(i, t.priceCurrencyCode) : i)), e;
63
+ };
64
+ }
65
+ export {
66
+ g as getCurrencyFormatting,
67
+ S as priceDecorator
68
+ };
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a=require("./index.es-D_7T9vdv.cjs");async function i(t,{hitDecorators:r,...u}={}){const c=await new Promise(a.s);return r!=null&&r.length?d(await c.search(t,u),r):await c.search(t,u)}function d(t,r){if(!t.products)return t;const u=c=>r.reduce((e,n)=>n(e),c);return{...t,products:{...t.products,hits:t.products.hits.map(u)}}}exports.search=i;
@@ -0,0 +1 @@
1
+ export { search, type Options, type HitDecorator } from "./search";
@@ -0,0 +1,20 @@
1
+ function o() {
2
+ window.nostojs = window.nostojs ?? function(n) {
3
+ (window.nostojs.q = window.nostojs.q ?? []).push(n);
4
+ };
5
+ }
6
+ async function i(n) {
7
+ return window.nostojs(n);
8
+ }
9
+ let t = null;
10
+ typeof window < "u" && (o(), i((n) => {
11
+ t = n.internal.getSettings();
12
+ }));
13
+ function s() {
14
+ return t;
15
+ }
16
+ typeof window < "u" && o();
17
+ export {
18
+ s as i,
19
+ i as s
20
+ };
@@ -0,0 +1 @@
1
+ "use strict";function o(){window.nostojs=window.nostojs??function(n){(window.nostojs.q=window.nostojs.q??[]).push(n)}}async function t(n){return window.nostojs(n)}let s=null;typeof window<"u"&&(o(),t(n=>{s=n.internal.getSettings()}));function i(){return s}typeof window<"u"&&o();exports.i=i;exports.s=t;
@@ -0,0 +1,20 @@
1
+ import { s as d } from "./index.es-Bcd5IQh9.js";
2
+ async function p(r, { hitDecorators: t, ...u } = {}) {
3
+ const a = await new Promise(d);
4
+ return t != null && t.length ? i(await a.search(r, u), t) : await a.search(r, u);
5
+ }
6
+ function i(r, t) {
7
+ if (!r.products)
8
+ return r;
9
+ const u = (a) => t.reduce((c, n) => n(c), a);
10
+ return {
11
+ ...r,
12
+ products: {
13
+ ...r.products,
14
+ hits: r.products.hits.map(u)
15
+ }
16
+ };
17
+ }
18
+ export {
19
+ p as search
20
+ };
@@ -0,0 +1,17 @@
1
+ import { SearchOptions, SearchProduct, SearchQuery, SearchResult } from "@nosto/nosto-js/client";
2
+ export type Options = SearchOptions & {
3
+ /**
4
+ * Hit decorators to apply to the search results.
5
+ */
6
+ hitDecorators?: HitDecorator[];
7
+ };
8
+ /**
9
+ * Performs a search operation using the provided query and options.
10
+ *
11
+ * @param query - The search query to be executed.
12
+ * @param options - An object containing optional parameters for the search.
13
+ * @param options.hitDecorators - An optional array of decorators to be applied to the search results.
14
+ * @returns A promise that resolves to the search result.
15
+ */
16
+ export declare function search(query: SearchQuery, { hitDecorators, ...options }?: Options): Promise<SearchResult>;
17
+ export type HitDecorator = (hit: SearchProduct) => SearchProduct;
@@ -0,0 +1,8 @@
1
+ import { ThumbnailSize } from "./types";
2
+ type Props = {
3
+ size: ThumbnailSize;
4
+ productId: string;
5
+ hash: string;
6
+ };
7
+ export declare function generateThumbnailUrl({ size, productId, hash }: Props): string;
8
+ export {};
@@ -0,0 +1,11 @@
1
+ import { SearchProduct } from "@nosto/nosto-js/client";
2
+ import { ShopifySize } from "./types";
3
+ type Config = {
4
+ size: ShopifySize;
5
+ };
6
+ /**
7
+ * Replaces full size images with specified Shopify thumbnail size.
8
+ * This decorator will only affect the image URLs from Shopify CDN.
9
+ */
10
+ export declare function shopifyThumbnailDecorator({ size }: Config): (hit: SearchProduct) => SearchProduct;
11
+ export {};
@@ -0,0 +1,10 @@
1
+ import { SearchProduct } from "@nosto/nosto-js/client";
2
+ import { ThumbnailSize } from "./types";
3
+ type Config = {
4
+ size: ThumbnailSize;
5
+ };
6
+ /**
7
+ * Replaces full size images with thumbnail sized versions.
8
+ */
9
+ export declare function thumbnailDecorator({ size }: Config): (hit: SearchProduct) => SearchProduct;
10
+ export {};
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Nosto thumbnail size code.
3
+ *
4
+ * "1": 170x170 px
5
+ * "2": 100x100 px
6
+ * "3": 90x70 px
7
+ * "4": 50x50 px
8
+ * "5": 30x30 px
9
+ * "6": 100x140 px
10
+ * "7": 200x200 px
11
+ * "8": 400x400 px
12
+ * "9": 750x750 px
13
+ * "10": Original (Square)
14
+ * "11": 200x200 px (Square)
15
+ * "12": 400x400 px (Square)
16
+ * "13": 750x750 px (Square)
17
+ */
18
+ export type ThumbnailSize = "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "10" | "11" | "12" | "13" | "orig";
19
+ /**
20
+ * Shopify thumbnail size code.
21
+ *
22
+ * Pico (16×16 px)
23
+ * Icon (32×32 px)
24
+ * Thumb (50×50 px)
25
+ * Small (100×100 px)
26
+ * Compact (160×160 px)
27
+ * Medium (240×240 px)
28
+ * Large (480×480 px)
29
+ * Grande (600×600 px)
30
+ * Original (1024×1024 px)
31
+ * Master (full size)
32
+ */
33
+ export type ShopifySize = "pico" | "icon" | "thumb" | "small" | "compact" | "medium" | "large" | "grande" | "original" | "master";
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("./index.es-D_7T9vdv.cjs");function i({size:o,productId:n,hash:t}){const u=c.i();if(!u)throw new Error("Client script settings are not yet available");return`https://${u.thumbnailHost}/${u.account}/${o}/${n}/${t}/A`}function s({size:o}){function n(a,r){if(r)return i({size:o,productId:a,hash:r})}function t(a,r){if(r)return r.map(e=>({...e,imageUrl:n(a,e.imageHash)??e.imageUrl}))}function u(a,r,e){if(!e)return r;if(r)return e.map(l=>i({size:o,productId:a,hash:l}))}return function(r){const e=r.productId;return e?{...r,imageUrl:n(e,r.imageHash)??r.imageUrl,thumbUrl:n(e,r.thumbHash)??r.thumbUrl,skus:t(e,r.skus),alternateImageUrls:u(e,r.alternateImageUrls,r.alternateImageHashes)}:r}}const m=/cdn\.shopify\.com/;function f({size:o}){function n(r){return r?new URL(r).hostname.match(m):!1}function t(r){return r?n(r)?r.replace(/(\.jpg|\.png|\.jpeg|\.gif|\.webp)/,`_${o}$1`):r:""}function u(r){if(r)return r.map(e=>({...e,imageUrl:t(e.imageUrl)}))}function a(r){if(r)return r.map(e=>t(e))}return function(e){return n(e.imageUrl)?{...e,imageUrl:t(e.imageUrl),thumbUrl:t(e.thumbUrl),skus:u(e.skus),alternateImageUrls:a(e.alternateImageUrls)}:e}}exports.generateThumbnailUrl=i;exports.shopifyThumbnailDecorator=f;exports.thumbnailDecorator=s;
@@ -0,0 +1,4 @@
1
+ export { generateThumbnailUrl } from "./thumbnails/generateThumbnailUrl";
2
+ export { thumbnailDecorator } from "./thumbnails/thumbnailDecorator";
3
+ export { shopifyThumbnailDecorator } from "./thumbnails/shopifyThumbnailDecorator";
4
+ export type { ThumbnailSize, ShopifySize } from "./thumbnails/types";
@@ -0,0 +1,80 @@
1
+ import { i as m } from "./index.es-Bcd5IQh9.js";
2
+ function i({ size: o, productId: n, hash: t }) {
3
+ const u = m();
4
+ if (!u)
5
+ throw new Error("Client script settings are not yet available");
6
+ return `https://${u.thumbnailHost}/${u.account}/${o}/${n}/${t}/A`;
7
+ }
8
+ function s({ size: o }) {
9
+ function n(a, r) {
10
+ if (r)
11
+ return i({
12
+ size: o,
13
+ productId: a,
14
+ hash: r
15
+ });
16
+ }
17
+ function t(a, r) {
18
+ if (r)
19
+ return r.map((e) => ({
20
+ ...e,
21
+ imageUrl: n(a, e.imageHash) ?? e.imageUrl
22
+ }));
23
+ }
24
+ function u(a, r, e) {
25
+ if (!e)
26
+ return r;
27
+ if (r)
28
+ return e.map(
29
+ (f) => i({
30
+ size: o,
31
+ productId: a,
32
+ hash: f
33
+ })
34
+ );
35
+ }
36
+ return function(r) {
37
+ const e = r.productId;
38
+ return e ? {
39
+ ...r,
40
+ imageUrl: n(e, r.imageHash) ?? r.imageUrl,
41
+ thumbUrl: n(e, r.thumbHash) ?? r.thumbUrl,
42
+ skus: t(e, r.skus),
43
+ alternateImageUrls: u(e, r.alternateImageUrls, r.alternateImageHashes)
44
+ } : r;
45
+ };
46
+ }
47
+ const l = /cdn\.shopify\.com/;
48
+ function g({ size: o }) {
49
+ function n(r) {
50
+ return r ? new URL(r).hostname.match(l) : !1;
51
+ }
52
+ function t(r) {
53
+ return r ? n(r) ? r.replace(/(\.jpg|\.png|\.jpeg|\.gif|\.webp)/, `_${o}$1`) : r : "";
54
+ }
55
+ function u(r) {
56
+ if (r)
57
+ return r.map((e) => ({
58
+ ...e,
59
+ imageUrl: t(e.imageUrl)
60
+ }));
61
+ }
62
+ function a(r) {
63
+ if (r)
64
+ return r.map((e) => t(e));
65
+ }
66
+ return function(e) {
67
+ return n(e.imageUrl) ? {
68
+ ...e,
69
+ imageUrl: t(e.imageUrl),
70
+ thumbUrl: t(e.thumbUrl),
71
+ skus: u(e.skus),
72
+ alternateImageUrls: a(e.alternateImageUrls)
73
+ } : e;
74
+ };
75
+ }
76
+ export {
77
+ i as generateThumbnailUrl,
78
+ g as shopifyThumbnailDecorator,
79
+ s as thumbnailDecorator
80
+ };
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@nosto/search-js",
3
+ "version": "0.1.0",
4
+ "license": "ISC",
5
+ "type": "module",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "main": "./dist/index.cjs.js",
10
+ "module": "./dist/index.es.js",
11
+ "types": "./dist/index.d.ts",
12
+ "directories": {
13
+ "doc": "docs",
14
+ "test": "test"
15
+ },
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.es.js",
20
+ "require": "./dist/index.cjs.js"
21
+ },
22
+ "./currencies": {
23
+ "types": "./dist/currencies.d.ts",
24
+ "import": "./dist/currencies.es.js",
25
+ "require": "./dist/currencies.cjs.js"
26
+ },
27
+ "./thumbnails": {
28
+ "types": "./dist/thumbnails.d.ts",
29
+ "import": "./dist/thumbnails.es.js",
30
+ "require": "./dist/thumbnails.cjs.js"
31
+ }
32
+ },
33
+ "keywords": [],
34
+ "author": "Nosto",
35
+ "scripts": {
36
+ "dev": "vite",
37
+ "build": "tsc && vite build && npm run build-dts && npm run typedoc",
38
+ "build-dts": "tsc --noEmit false --emitDeclarationOnly",
39
+ "test": "vitest --run",
40
+ "lint": "eslint '{src,test}/**/*.ts'",
41
+ "preview": "vite preview",
42
+ "typedoc": "typedoc src/index.ts"
43
+ },
44
+ "devDependencies": {
45
+ "@nosto/nosto-js": "^1.4.4",
46
+ "@testing-library/dom": "^10.4.0",
47
+ "@types/eslint-config-prettier": "^6.11.3",
48
+ "@types/node": "^22.10.10",
49
+ "copyfiles": "^2.4.1",
50
+ "eslint": "^9.19.0",
51
+ "eslint-config-prettier": "^10.0.1",
52
+ "eslint-plugin-barrel-files": "^2.1.0",
53
+ "eslint-plugin-prettier": "^5.2.3",
54
+ "jsdom": "^26.0.0",
55
+ "prettier": "^3.3.3",
56
+ "typedoc": "^0.27.2",
57
+ "typescript": "^5.7.3",
58
+ "typescript-eslint": "^8.22.0",
59
+ "vite": "^6.0.11",
60
+ "vitest": "^3.0.4"
61
+ },
62
+ "publishConfig": {
63
+ "access": "public"
64
+ }
65
+ }