AACsearch
API de recherche

Filtres, tri & pagination

Référence complète pour les expressions de filtre, les options de tri, les facettes et la pagination dans les requêtes AACsearch.

AACsearch utilise la syntaxe de filtre et de tri de AACSearch avec quelques conventions spécifiques au schéma produit. Cette page est une référence complète pour la construction d'expressions de filtre, la configuration des ordres de tri et la pagination des résultats.

Syntaxe des expressions de filtre

Les filtres sont spécifiés comme une chaîne de caractères utilisant la logique booléenne.

Égalité

availability:=in_stock
brand:=Sony
categories:=Electronics
locale:=en

Négation

availability:!=out_of_stock
brand:!=Competitors

Plage (numérique)

price:>50
price:<200
price:[50..200]     # plage inclusive
price:(50..200)     # plage exclusive

Appartenance à un tableau

categories:=[Electronics, Audio]
brand:=[Sony, Bose, Sennheiser]

Contient une chaîne / préfixe

AACSearch ne supporte que la correspondance exacte de chaîne pour les champs de facette. Pour la recherche par préfixe, utilisez q avec queryBy.

Logique booléenne

# ET (les deux conditions doivent correspondre)
availability:=in_stock && price:<200

# OU (l'une ou l'autre condition correspond)
brand:=Sony || brand:=Bose

# Regroupement
(brand:=Sony || brand:=Bose) && availability:=in_stock && price:<200

Filtre de locataire (automatique)

Le handler public ajoute automatiquement :

organization_id:={organizationId}

Vous n'avez pas besoin d'ajouter ce filtre manuellement — il est toujours appliqué.

Champs de filtre disponibles

Le schéma produit par défaut inclut ces champs filtrables (marqués facet: true) :

ChampTypeExemple de filtre
brandstringbrand:=Sony
categoriesstring[]categories:=[Electronics, Audio]
availabilitystringavailability:=in_stock
pricefloatprice:[10..500]
sale_pricefloatsale_price:&lt;100
localestringlocale:=en

Facettes

Les facettes calculent la distribution des valeurs pour un champ sur les documents correspondants. Utilisez-les pour afficher les barres latérales de filtres.

{
	"q": "headphones",
	"facetBy": "brand,categories,availability"
}

La réponse inclut facetCounts :

{
	"facetCounts": [
		{
			"fieldName": "brand",
			"counts": [
				{ "value": "Sony", "count": 45 },
				{ "value": "Bose", "count": 28 },
				{ "value": "Sennheiser", "count": 19 }
			]
		},
		{
			"fieldName": "availability",
			"counts": [
				{ "value": "in_stock", "count": 89 },
				{ "value": "out_of_stock", "count": 23 }
			]
		}
	]
}

Facettes avec filtres appliqués

Lorsqu'un filtre est actif, les comptages de facettes reflètent l'ensemble de résultats filtré, pas l'index entier :

{
	"q": "*",
	"filterBy": "brand:=Sony",
	"facetBy": "categories,availability"
}

Cela retourne les comptages de catégories et de disponibilité pour les produits Sony uniquement.

Tri

Tri sur un seul champ

{ "sortBy": "price:asc" }
{ "sortBy": "price:desc" }
{ "sortBy": "created_at:desc" }
{ "sortBy": "sale_price:asc" }

Tri par pertinence d'abord (par défaut)

{ "sortBy": "_text_match:desc" }

C'est le comportement par défaut lorsque sortBy est omis.

Tri multi-champ (départage)

{ "sortBy": "_text_match:desc,price:asc" }

Trie par score de pertinence d'abord ; à score égal, trie par prix croissant.

Préréglages de tri courants

LibelléValeur sortBy
Le plus pertinent_text_match:desc
Prix : du moins cherprice:asc
Prix : du plus cherprice:desc
Les plus récentscreated_at:desc
Prix promotionnel : du moins chersale_price:asc

Pagination

{
	"page": 1, // indexé à partir de 1, défaut : 1
	"perPage": 20 // défaut : 10, max : 100
}

Champs de réponse pour la pagination :

{
	"found": 142, // total de documents correspondants
	"page": 1, // page actuelle
	"outOf": 5000 // total de documents dans l'index (non filtré)
}

Calcul du nombre total de pages :

const totalPages = Math.ceil(results.found / perPage);
const hasMore = results.page * perPage < results.found;

Avertissement sur la pagination profonde

Les performances de AACSearch se dégradent avec de grandes valeurs de page pour les indexes de plus de 100 000 documents. Pour la pagination profonde ou les exports :

  1. Utilisez des valeurs de page croissantes avec un perPage fixe
  2. Ou implémentez une pagination basée sur curseur en triant sur un champ unique (id:asc) et en utilisant filterBy: "id:>lastSeenId"

Sélection de champs

Réduisez la taille de la réponse en sélectionnant uniquement les champs dont vous avez besoin :

{
	"includeFields": "id,title,price,brand,availability",
	"excludeFields": "description,tags"
}

Utilisez excludeFields pour supprimer les grands champs de texte des réponses lorsque vous ne les affichez pas (par ex. description dans une autocomplétion de recherche au fil de la frappe qui n'affiche que les titres).

Combiner les filtres avec les tokens limités

Lorsqu'un token limité est utilisé, son scopedFilter est combiné avec ET avec le filterBy de l'appelant :

Token scopedFilter :   "price:<100"
Appelant filterBy :    "brand:=Sony && availability:=in_stock"
filterBy effectif :    "brand:=Sony && availability:=in_stock && price:<100"

Le filtre du token ne peut pas être remplacé par le filterBy de l'appelant.

Escaping values

Filter values are parsed by AACSearch's filter expression engine. Most strings work as-is, but a few characters have special meaning and must be backtick-quoted (or backslash-escaped) when they appear inside a value:

CharacterMeaning in the grammarInside a value, write as
,Array element separatorWrap value in `…`
&&AND operatorWrap value in `…`
||OR operatorWrap value in `…`
( )GroupingWrap value in `…`
[ ]Range / array delimitersWrap value in `…`
:Field/operator separatorWrap value in `…`
`The quote character itselfEscape with backslash: \`

Examples:

# Brand name contains a comma — must be quoted
brand:=`Ben & Jerry's`

# Category contains the AND operator literally
categories:=`Home && Garden`

User-controlled values must always be escaped before concatenation. Never interpolate user input into the filter string directly — use the safe filter builder.

Safe filter builder

Hand-concatenating a filter string from user input is a category-1 injection bug. Always build filters from a structured representation. A minimal TypeScript builder:

type AtomicFilter =
  | { field: string; op: "="; value: string | number | boolean }
  | { field: string; op: "!="; value: string | number | boolean }
  | { field: string; op: ">" | "<" | ">=" | "<="; value: number }
  | { field: string; op: "in"; values: Array<string | number> }
  | { field: string; op: "range"; min: number; max: number; inclusive?: boolean };

type FilterTree = AtomicFilter | { and: FilterTree[] } | { or: FilterTree[] };

const FIELD_RE = /^[a-z_][a-z0-9_]*$/;

function escapeValue(v: string | number | boolean): string {
  if (typeof v === "number" || typeof v === "boolean") return String(v);
  return "`" + String(v).replace(/`/g, "\\`") + "`";
}

function fieldOrThrow(field: string): string {
  if (!FIELD_RE.test(field)) throw new Error(`invalid field name: ${field}`);
  return field;
}

export function buildFilter(node: FilterTree): string {
  if ("and" in node) return node.and.map(buildFilter).join(" && ");
  if ("or" in node) return "(" + node.or.map(buildFilter).join(" || ") + ")";
  const f = fieldOrThrow(node.field);
  if (node.op === "in") return `${f}:=[${node.values.map(escapeValue).join(", ")}]`;
  if (node.op === "range") {
    const [lo, hi] = node.inclusive === false ? ["(", ")"] : ["[", "]"];
    return `${f}:${lo}${node.min}..${node.max}${hi}`;
  }
  return `${f}:${node.op}${escapeValue(node.value)}`;
}

The builder enforces two invariants: field names match a strict identifier regex, and every string value is backtick-quoted with inner backticks escaped. Replicate the same shape in your server SDK.

E-commerce filter recipes

Category page (in-stock only)

{
  "filterBy": "categories:=`Audio` && availability:=in_stock",
  "facetBy": "brand,price",
  "sortBy": "_text_match:desc,popularity_score:desc"
}

Multi-brand drill-down

filterBy: "categories:=[`Electronics`, `Audio`] && brand:=[`Sony`, `Bose`] && availability:=in_stock"

Price range with "on sale" branch

filterBy: "categories:=`Audio` && availability:=in_stock && (sale_price:[10..100] || (sale_price:<0 && price:[10..100]))"

Locale-aware listing

filterBy: "locale:=`en` && categories:=`Audio` && availability:=in_stock"

"More like this" (vector hybrid)

{
  "q": "*",
  "vectorQuery": "embedding:([…current vector…], k:50)",
  "filterBy": "id:!=`product-123` && availability:=in_stock && price:[1000..15000]",
  "perPage": 12
}

On this page