npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@commercetools-demo/search-config-runtime

v0.1.0

Published

Framework-agnostic runtime that resolves commercetools search configuration and compiles Product Search requests.

Readme

@commercetools-demo/search-config-runtime

Framework-agnostic runtime for search configuration authored in the Merchant Center. Reads a published configuration document from commercetools Custom Objects, compiles a ProductSearchRequest, and applies the merchandising that Product Search cannot express on its own.

No dependencies. Works in any JavaScript server tier.

Install

npm install @commercetools-demo/search-config-runtime

Your storefront's API client needs view_published_products (or view_products) and view_key_value_documents.

Use

import {
  createConfigLoader, compileSearchRequest, applyMerchandising,
} from '@commercetools-demo/search-config-runtime';

const loader = createConfigLoader({
  apiUrl: process.env.CTP_API_URL!,
  projectKey: process.env.CTP_PROJECT_KEY!,
  getAccessToken: () => myTokenProvider(),
});

export async function search(term: string, page = 0) {
  const resolver = await loader.getResolver('default');
  if (!resolver) throw new Error('No published search configuration.');

  const ctx = { locale: 'en-US', currency: 'USD', country: 'US' };
  const config = resolver.resolve(ctx);

  const compiled = compileSearchRequest(config, { query: term, page, ctx });

  const response = await fetch(
    `${process.env.CTP_API_URL}/${process.env.CTP_PROJECT_KEY}/products/search`,
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${await myTokenProvider()}`,
        'Content-Type': 'application/json',
        // Routing header for semantic and hybrid. compileSearchRequest supplies it.
        ...compiled.headers,
      },
      body: JSON.stringify(compiled.body),
    }
  );
  const body = await response.json();

  const { items } = applyMerchandising(body.results, compiled, {
    // Needed only for boost and bury; pin and hide work without it.
    accessor: (item, field) => readAttribute(item, field),
  });

  return { items, total: body.total };
}

What it does, and what it cannot

Configuration splits into what commercetools applies and what this package applies.

| Concern | Where it happens | | --- | --- | | Search mode (lexical, semantic, hybrid) | userQuery on the request | | Facets, sorting, pagination | Compiled into the request | | Category and store scoping | query, so facet counts inherit it | | Shopper refinements | postFilter, so facet counts stay stable | | Price sorting scoped to the request currency | sort.filter on the price path | | Boost in structured mode | Boosted expressions in query | | Boost in userQuery modes, bury, pin, hide | Post-fetch, in applyMerchandising | | Synonyms | Text substitution in userQuery.value — see below | | Field weights, embedding attributes | commercetools support applies these. Export a request with buildTuningRequest |

Three limits are worth stating plainly:

  1. Boost blends with relevance; it does not partition. Scoring is factor / (rank + 2), so a 1.6x boost nudges a product up a few places while a 6x boost can reach the top. Sorting by factor alone would put every boosted product above every unboosted one however irrelevant — an always-on 1.6x boost pushed the actual matches off page one before this was fixed.
  2. Post-fetch reordering only sees the fetched window. Boost, bury, pin and hide are all applied after the fetch, so the compiler over-fetches (pagination.merchandisingOverfetch) — but a product ranked below that window cannot be promoted into view. sort.filter is not an alternative: it selects which value to sort by within a multi-valued nested field, and pointing it at an unrelated field fails server-side.
  3. Synonyms substitute, they do not expand. userQuery requires every term to match, so appending alternatives narrows a query to nothing: against a catalogue of dressers, armoire returns 0 results and so does armoire dresser closet wardrobe. Each set's first term is canonical — the word the catalogue uses — and matched terms are rewritten onto it. Rewriting the text rather than the expressions is forced by userQuery being opaque; putting alternatives in the structured query would filter rather than broaden.
  4. Semantic matching is product-level. In semantic or hybrid mode without a variant-level filter, markMatchingVariants reports every variant as matched.

Configuration layering

A published bundle carries the whole profile chain rather than a pre-resolved snapshot, because locale, currency, customer group, and channel vary per request. resolveConfig merges root-first: each profile's base, then every matching context override in declared order.

Two merge rules:

  • Objects merge deeply.
  • Arrays merge by key, never by position. Set disabled: true to suppress an element inherited from a lower layer. Positional merging would make inheritance depend on authoring order.

Built-in defaults act as the lowest layer, so a profile that states matching.fields patches the defaults by key rather than replacing them. To drop a default field, set disabled: true or weight: 0.

Use resolveConfigVerbose to get provenance — which layer set each section — for an effective-configuration view.

Analytics

createAnalyticsRecorder buffers events and folds them into bucket-sharded daily aggregates. Sharding is the point: one daily document would serialise every write in the fleet behind a single version. summarize sums the buckets.

Await flush() in serverless handlers, or buffered events are lost on freeze. Only aggregate counts are stored, never shopper identifiers.

Testing

npm test