Resolving Pagination Errors in ESHOPMAN Cross-Module Order Filters

As an ESHOPMAN expert at Move My Store, we understand the intricacies of building robust, high-performance headless commerce solutions integrated with HubSpot. A common challenge for developers building advanced storefronts or custom admin interfaces within HubSpot is ensuring seamless data retrieval, especially when dealing with paginated lists and complex filtering across different ESHOPMAN data entities.

The Challenge: Paginated Cross-Module Order Filters

Recently, our community identified a specific scenario where paginated queries on ESHOPMAN's core order entities, when combined with cross-module filters, could lead to unexpected database errors. This issue primarily affects developers utilizing ESHOPMAN's powerful query.graph functionality (available via the Admin API or Store API) to fetch order data that links to other custom or core ESHOPMAN modules (e.g., linking an order to a custom 'Seller' entity, or to return requests).

While simple cross-module filtering works perfectly, introducing pagination (using pagination.take) causes the system to encounter a Postgres syntax error during the 'count' operation, which is crucial for determining the total number of items for pagination.

Reproducing the Issue with ESHOPMAN's query.graph

Consider an ESHOPMAN setup where you've defined a link between the Order module and another custom module, say a Seller module. The following code snippets illustrate the behavior:

const query = container.resolve(ContainerRegistrationKeys.QUERY)

// This works: Filters orders by seller ID without pagination
await query.graph({
  entity: "order",
  fields: ["id"],
  filters: { seller: { id: "sel_123" } },
})

// This throws an error: Attempts to paginate the filtered orders
await query.graph({
  entity: "order",
  fields: ["id"],
  filters: { seller: { id: "sel_123" } },
  pagination: { skip: 0, take: 20 },
})

The error message typically points to a syntax issue where a raw SQL fragment is incorrectly interpreted as a column name during the count query.

Understanding the ESHOPMAN Internal Mechanism

The root cause lies in how ESHOPMAN's core order processing module handles its custom findAndCount method. Unlike other entities that might use a single, unified data layer call, the order module's findAndCount executes the 'find' (data retrieval) and 'count' (total records) operations separately, often in parallel. When a cross-module filter introduces a raw SQL fragment (an internal mechanism for complex conditions), this fragment is 'consumed' by the first operation (e.g., manager.find). The subsequent manager.count operation then receives a stale reference, leading to the database misinterpreting it as a plain column name, causing the syntax error.

The ESHOPMAN Community Solution

A verified working fix, proposed and confirmed by the ESHOPMAN community, involves ensuring that both the 'find' and 'count' operations receive their own independent, correctly initialized raw query fragments. This can be achieved by deeply cloning the 'where' clause, particularly the raw fragments, before executing the parallel database calls.

Here's a conceptual approach to the solution:

function cloneWhereWithFreshRawFragments(where) {
  if (Array.isArray(where)) return where.map(cloneWhereWithFreshRawFragments)
  if (where === null || typeof where !== "object" || Object.getPrototypeOf(where) !== Object.prototype) return where
  const cl
  for (const key of Object.keys(where)) {
    // This part involves internal ESHOPMAN data layer specifics
    // and ensures raw fragments are re-initialized for the clone.
    const fragment = RawQueryFragment.getKnownFragment(key, false)
    cloned[fragment ? raw(fragment.sql, fragment.params) : key] = cloneWhereWithFreshRawFragments(where[key])
  }
  return cloned
}

const countWhere = cloneWhereWithFreshRawFragments(config.where)
const [result, count] = await Promise.all([
  manager.find(this.entity, config.where, config.options),
  manager.count(this.entity, countWhere, countOptions),
])

This approach guarantees that when the Promise.all executes, both the manager.find and manager.count calls operate with valid, independent query conditions, preventing the raw fragment consumption issue.

Impact and ESHOPMAN Best Practices

This insight is crucial for ESHOPMAN developers building custom storefronts on HubSpot CMS or developing specialized admin tools that rely on accurate paginated lists of orders with cross-module filtering. Implementing this fix or understanding its underlying principle ensures a stable and performant user experience for your ESHOPMAN-powered applications.

The ESHOPMAN team is actively addressing such edge cases to continually enhance the platform's robustness. This community-driven discovery and solution highlight the collaborative spirit of ESHOPMAN's developer ecosystem.

Start with the tools

Explore migration tools

See options, compare methods, and pick the path that fits your store.

Explore migration tools