{"_id":"@area37/vendure-plugin-default-search-plus","_rev":"2-e1f9fc5232a0c3fcb897685038acbd03","name":"@area37/vendure-plugin-default-search-plus","dist-tags":{"latest":"1.1.0"},"versions":{"1.0.0":{"name":"@area37/vendure-plugin-default-search-plus","version":"1.0.0","keywords":["vendure","vendure-plugins","vendure-packages","search","default-search","elasticsearch-alternative","price-filter","price-range","price-facets","fuzzy-search","postgres","pg_trgm"],"license":"MIT","_id":"@area37/vendure-plugin-default-search-plus@1.0.0","maintainers":[{"name":"winne4r","email":"winne4r@gmail.com"}],"homepage":"https://github.com/Area37/vendure-plugins#readme","bugs":{"url":"https://github.com/Area37/vendure-plugins/issues"},"dist":{"shasum":"5bdb345b956dfc9db5558f4fdd9d6e37c918a3c2","tarball":"https://registry.npmjs.org/@area37/vendure-plugin-default-search-plus/-/vendure-plugin-default-search-plus-1.0.0.tgz","fileCount":17,"integrity":"sha512-HOyP1S1ZpXUonBexp/93gqW84t5kn3xSSwhSg0/4izkSwRuKeNQvm/rCYbBrbrtj9NyY9yTjJP8dmXWnCXx1cw==","signatures":[{"sig":"MEYCIQDuMp/q9ZecCk+CkpTjGEhkUKfi2DXxbkk9H3Q/1H5nqwIhAMvfesNBUfcLZiPE0bftpJ6hRd7MteepNDamnzcJ2cuF","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":38056},"main":"./dist/index.js","types":"./dist/index.d.ts","gitHead":"8f678c3c1a91dbe031626b30e97d2927d21662cc","scripts":{"e2e":"cross-env DB=postgres PACKAGE=default-search-plus-plugin vitest -c ../../utils/e2e/vitest.config.mts","build":"yarn clean && tsc -p ./tsconfig.build.json","clean":"node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\""},"_npmUser":{"name":"winne4r","email":"winne4r@gmail.com"},"repository":{"url":"git+https://github.com/Area37/vendure-plugins.git","type":"git"},"_npmVersion":"10.9.2","description":"Adds price-range filtering, price facets (range + histogram) and optional pg_trgm fuzzy search to Vendure's DefaultSearchPlugin (Postgres) — the Elasticsearch-only search features, without Elasticsearch","directories":{},"_nodeVersion":"22.16.0","dependencies":{"graphql-tag":"^2.12.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"peerDependencies":{"graphql":"^16.0.0","typeorm":"^0.3.0","@vendure/core":"^3.1.0","@nestjs/graphql":"^12.0.0 || ^13.0.0","@vendure/common":"^3.1.0"},"_npmOperationalInternal":{"tmp":"tmp/vendure-plugin-default-search-plus_1.0.0_1784127594047_0.8401425130450246","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@area37/vendure-plugin-default-search-plus","description":"Adds price-range filtering, price facets (range + histogram) and optional pg_trgm fuzzy search to Vendure's DefaultSearchPlugin (Postgres) — the Elasticsearch-only search features, without Elasticsearch","repository":{"type":"git","url":"git+https://github.com/Area37/vendure-plugins.git"},"version":"1.1.0","license":"MIT","publishConfig":{"access":"public"},"keywords":["vendure","vendure-plugins","vendure-packages","search","default-search","elasticsearch-alternative","price-filter","price-range","price-facets","fuzzy-search","postgres","pg_trgm"],"main":"./dist/index.js","types":"./dist/index.d.ts","scripts":{"clean":"node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"","build":"yarn clean && tsc -p ./tsconfig.build.json","e2e":"cross-env DB=postgres PACKAGE=default-search-plus-plugin vitest -c ../../utils/e2e/vitest.config.mts"},"peerDependencies":{"@nestjs/graphql":"^12.0.0 || ^13.0.0","@vendure/common":"^3.1.0","@vendure/core":"^3.1.0","graphql":"^16.0.0","typeorm":"^0.3.0"},"dependencies":{"graphql-tag":"^2.12.6"},"_id":"@area37/vendure-plugin-default-search-plus@1.1.0","gitHead":"142b88a1e572d6f0eb3d97efb79835e16ef9c27e","bugs":{"url":"https://github.com/Area37/vendure-plugins/issues"},"homepage":"https://github.com/Area37/vendure-plugins#readme","_nodeVersion":"22.16.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-x4WSnUfuT9O07npUWp+MiB388+HWEqqWKLbyIt+2+fvZwqX4RF8WLTQhGiLlf9uCagKzYGWhlnsEytm81J/gZQ==","shasum":"d46f208d1353aa8eae8e11b7c4b0178e7d03e3a7","tarball":"https://registry.npmjs.org/@area37/vendure-plugin-default-search-plus/-/vendure-plugin-default-search-plus-1.1.0.tgz","fileCount":19,"unpackedSize":51420,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCd+fCFGVl3LJXC37JplJ8kSau2NG0fHfLVLVogkGvNUAIhAMS1+ObweBJkpaKrnc90qrsbVKjgZDXjT8aS8XIDlXqu"}]},"_npmUser":{"name":"winne4r","email":"winne4r@gmail.com"},"directories":{},"maintainers":[{"name":"winne4r","email":"winne4r@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/vendure-plugin-default-search-plus_1.1.0_1786216731655_0.6231015429410394"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-15T14:59:53.878Z","modified":"2026-08-08T19:18:51.944Z","1.0.0":"2026-07-15T14:59:54.175Z","1.1.0":"2026-08-08T19:18:51.786Z"},"bugs":{"url":"https://github.com/Area37/vendure-plugins/issues"},"license":"MIT","homepage":"https://github.com/Area37/vendure-plugins#readme","keywords":["vendure","vendure-plugins","vendure-packages","search","default-search","elasticsearch-alternative","price-filter","price-range","price-facets","fuzzy-search","postgres","pg_trgm"],"repository":{"type":"git","url":"git+https://github.com/Area37/vendure-plugins.git"},"description":"Adds price-range filtering, price facets (range + histogram) and optional pg_trgm fuzzy search to Vendure's DefaultSearchPlugin (Postgres) — the Elasticsearch-only search features, without Elasticsearch","maintainers":[{"name":"winne4r","email":"winne4r@gmail.com"}],"readme":"# Vendure DefaultSearch Plus\n\nExtends Vendure's built-in **`DefaultSearchPlugin`** (Postgres) with the search\nfeatures that otherwise only the Elasticsearch plugin provides — **without an\nexternal search server**:\n\n- **Price-range filtering** — `priceRange` / `priceRangeWithTax` on `SearchInput`\n- **Price aggregation** — `prices { range, rangeWithTax, buckets, bucketsWithTax }`\n  on `SearchResponse` (for price sliders / histograms), resolved lazily\n- **Optional fuzzy (typo-tolerant) matching** via the PostgreSQL `pg_trgm` extension\n- **Optional cross-language matching** — a term in one language finds products\n  named in another, still displayed in the visitor's language\n\nIt plugs into `DefaultSearchPlugin` as a custom `searchStrategy` (extending the\npublic `PostgresSearchStrategy`) plus a small set of shop-api resolvers. The\nstandard `search` query, facets, collections, sorting and `inStock` keep working\nunchanged.\n\n> Postgres only. Requires `@vendure/core` ^3.1.0.\n\n## Install\n\n```bash\nnpm install @area37/vendure-plugin-default-search-plus\n```\n\n## Usage\n\n```ts\nimport { DefaultSearchPlugin } from '@vendure/core';\nimport { PostgresPriceSearchPlugin } from '@area37/vendure-plugin-default-search-plus';\n\n// Call .init() first so `searchStrategy` exists when DefaultSearchPlugin reads it.\nconst priceSearch = PostgresPriceSearchPlugin.init({\n  indexStockStatus: true,   // must match DefaultSearchPlugin below\n  fuzzy: true,              // requires the pg_trgm extension (see below)\n  fuzzyThreshold: 0.25,\n  priceRangeBucketCount: 10,\n  crossLanguage: true,      // search every language, display the current one\n  textSearchConfig: 'simple',\n});\n\nexport const config: VendureConfig = {\n  plugins: [\n    DefaultSearchPlugin.init({\n      indexStockStatus: true,\n      searchStrategy: PostgresPriceSearchPlugin.searchStrategy,\n    }),\n    priceSearch,\n  ],\n};\n```\n\nQuery:\n\n```graphql\nquery {\n  search(input: { collectionSlug: \"watches\", priceRangeWithTax: { min: 0, max: 500000 } }) {\n    totalItems\n    items { productName priceWithTax { ... on PriceRange { min max } } inStock }\n    prices { rangeWithTax { min max } buckets { to count } }\n  }\n}\n```\n\n## Fuzzy search (pg_trgm)\n\n`fuzzy: true` adds a `pg_trgm` `word_similarity` fallback to the term match so\ntypos still return results (exact/prefix matches still rank highest). It requires\nthe extension — run once (e.g. in a migration):\n\n```sql\nCREATE EXTENSION IF NOT EXISTS pg_trgm;\n```\n\nWith `fuzzy: false` (the default) the strategy behaves exactly like the stock\n`PostgresSearchStrategy` term matching and `pg_trgm` is **not** required.\n\n## Cross-language search\n\nOn a multilingual shop the stock strategy searches **one** language: the row for\nthe current language, or — outside the channel's default language — the current\nlanguage falling back to the default. So on a Russian storefront `\"minecraft\"`\nfinds nothing, even though the product is indexed as `Minecraft` in English and\n`Mainkrafts` in Latvian.\n\n`crossLanguage: true` matches the term against the same variant's rows in **every**\nlanguage while still returning the row for the current one. The visitor types in\nwhatever language they think in and gets results labelled in theirs.\n\n```ts\nPostgresPriceSearchPlugin.init({ crossLanguage: true, textSearchConfig: 'simple' });\n```\n\nVendure already writes one index row per available language, so there is nothing\nextra to index — but do create the indexes below, and note:\n\n- **No dilution.** A term in the page's own language returns exactly the same set\n  as before; the extra branch only adds products that were unreachable.\n- **Ranking.** Cross-language-only hits get no `ts_rank_cd` score, so they sort\n  after same-language matches.\n- **Fuzzy stays same-language.** `word_similarity` inside a correlated subquery\n  cannot use an index (measured ~8x slower), and typos in a language the visitor\n  does not write are not worth that. The cross-language branch is exact/prefix.\n\n## Indexes\n\n`to_tsvector(col)` — the single-argument form the stock strategy emits — is only\n`STABLE`, so it can never be indexed. Setting `textSearchConfig` switches to the\n`IMMUTABLE` two-argument form, which makes GIN expression indexes possible:\n\n```ts\nimport { searchIndexDdl } from '@area37/vendure-plugin-default-search-plus';\n\n// in a Vendure migration\nfor (const sql of searchIndexDdl({ textSearchConfig: 'simple', crossLanguage: true })) {\n  await queryRunner.query(sql);\n}\n```\n\nThe DDL is generated from the same config the strategy uses, because Postgres\nonly picks up an expression index when the expressions match exactly. On a\n2,600-variant catalogue this took a cross-language term query from **650 ms to\n~15 ms**. `searchIndexDdlDown()` returns the `DROP INDEX` counterparts.\n\nNothing breaks without the indexes — queries just fall back to a sequential scan.\n\n## Options\n\n| Option | Default | Description |\n| --- | --- | --- |\n| `indexStockStatus` | `false` | Set to match `DefaultSearchPlugin.init({ indexStockStatus })`. |\n| `fuzzy` | `false` | Enable pg_trgm typo tolerance (requires the extension). |\n| `fuzzyThreshold` | `0.25` | `word_similarity` threshold `[0..1]`; lower = more permissive. Values below ~0.4 pull in unrelated products. |\n| `crossLanguage` | `false` | Match the term against every language's index rows; display the current language. |\n| `textSearchConfig` | `'simple'` | Text-search config for `to_tsvector`/`to_tsquery`. `null` emits the un-indexable single-argument form (pre-1.1 behaviour). |\n| `priceRangeBucketCount` | `10` | Buckets returned in `prices.buckets`. |\n\n## Notes / limitations\n\n- Postgres only (uses `width_bucket`, `to_tsquery`, and — for fuzzy —\n  `word_similarity`).\n- After enabling, **reindex** the search index (`reindex` mutation / dashboard).\n- `fuzzy` matching is never index-backed (`word_similarity(a, b) > t` is not an\n  indexable predicate, and OR-ing it in prevents the GIN indexes from being used\n  for the whole term clause). Budget for a sequential scan when it is on.\n\n## Compatibility & maintenance\n\nTested against **`@vendure/core` 3.1.x and 3.7.x** (e2e). The strategy extends\nthe **public** `PostgresSearchStrategy` and references only the `search_index_item`\ntable name — no deep `dist/` imports — so it is reasonably portable across\nVendure 3.x.\n\n**One thing to know:** Vendure's `PostgresSearchStrategy.applyTermAndFilters` is\n`private`, so this plugin overrides it with a copy of that method (based on the\n3.7 implementation) plus the price/fuzzy additions. If a future Vendure release\nchanges term/facet/collection query building in that method, the copy can drift\nand would need to be re-synced. The e2e test guards against regressions — run it\nagainst the Vendure version you target before upgrading:\n\n```bash\nDB=postgres yarn e2e\n```\n","readmeFilename":"README.md"}