Query anatomy
The object you send on every search — what each property does, and which one changes the shape of the response.
A query is a plain object. You build it, you pass it, and nothing is hidden: there is no builder to learn and no serialisation step of your own.
Only name is required. It identifies the query web service to run — the server-side configuration that
decides which indexes are searched, which facets are computed and which tabs exist. It does not name your
query.
const query: Query = { name: "<your-query>", text: "quarterly report" };action decides what comes back
This is the one property that changes the type of the response, and the one that surprises people.
search— the default. Documents and facet counts, with pagination.aggregate— pages through one aggregation's items, as a facet's "load more" does. The response carriesid,queryName,aggregationsandattributesand nothing else: norecords, norowCount, nopage. Type it asAggregateResult.open— expands one node of a tree aggregation, listing its children.
Typing an aggregate response as a Result is how twelve absent fields come to look present. See
Result anatomy.
Properties
Identity and text
Prop
Type
Narrowing the results
Prop
Type
Paging and ordering
Prop
Type
Aggregations
Prop
Type
Relevance tuning
Raw engine clauses, passed through untouched. Reach for them only with a reason from whoever tuned the index:
documentWeight, textPartWeights, relevanceTransforms, spellingCorrectionMode,
spellingCorrectionFilter.
spellingCorrectionMode is the one with a visible effect: it decides whether the corrected query is run or
merely suggested, which is what result.didYouMean reports.
Recipes
Page through the results
import { createAtomicClient } from "@sinequa/atomic";
import type { Query, Result, ResultPages } from "@sinequa/atomic";
const client = createAtomicClient({
app: "<your-app>",
backendUrl: "https://<your-sinequa-server>",
});
await client.initializeConfig();
await client.auth.login();
const base: Query = { name: "<your-query>", text: "quarterly report", pageSize: 10 };
let page = 1;
let result: Result & ResultPages = await client.api.query.search({ ...base, page });
// fetchQuery computes these for you from the response — no arithmetic needed, and
// they are undefined rather than NaN when the response carries no pagination.
while (result.nextPage) {
page = result.nextPage;
result = await client.api.query.search({ ...base, page });
console.log(`page ${result.page}: ${result.records.length} of ${result.rowCount}`);
}import { ChangeDetectionStrategy, Component, inject, resource, signal } from "@angular/core";
import { SINEQUA } from "./sinequa";
@Component({
selector: "app-paged-results",
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
@if (results.value(); as result) {
<p>Page {{ result.page }} — {{ result.rowCount }} document(s)</p>
<button type="button" [disabled]="!result.previousPage" (click)="go(result.previousPage)">
Previous
</button>
<button type="button" [disabled]="!result.nextPage" (click)="go(result.nextPage)">Next</button>
<ol>
@for (record of result.records; track record.id) {
<li>{{ record.title }}</li>
}
</ol>
}
`,
})
export class PagedResultsComponent {
private readonly client = inject(SINEQUA);
readonly page = signal(1);
readonly results = resource({
params: () => ({ page: this.page() }),
loader: ({ params }) =>
this.client.api.query.search({
name: "<your-query>",
text: "quarterly report",
pageSize: 10,
page: params.page,
}),
});
/** nextPage and previousPage are undefined at the ends, so the guard is enough. */
go(page: number | undefined) {
if (page) this.page.set(page);
}
}import type { Result } from "@sinequa/atomic";
import { useEffect, useState } from "react";
import { ready, sinequa } from "./sinequa";
export function PagedResults() {
const [page, setPage] = useState(1);
const [result, setResult] = useState<Result & { nextPage?: number; previousPage?: number }>();
useEffect(() => {
let active = true;
ready
.then(() =>
sinequa.api.query.search({
name: "<your-query>",
text: "quarterly report",
pageSize: 10,
page,
}),
)
.then((next) => active && setResult(next));
return () => {
active = false;
};
}, [page]);
if (!result) return <p>Searching…</p>;
return (
<>
<p>
Page {result.page} — {result.rowCount} document(s)
</p>
{/* undefined at the ends, so disabling on falsy is correct */}
<button type="button" disabled={!result.previousPage} onClick={() => setPage(result.previousPage!)}>
Previous
</button>
<button type="button" disabled={!result.nextPage} onClick={() => setPage(result.nextPage!)}>
Next
</button>
<ol>
{result.records.map((record) => (
<li key={record.id}>{record.title}</li>
))}
</ol>
</>
);
}Facets without documents
A home page showing only facet counts, or an advanced-search form populating its value lists:
const result = await client.api.query.search({
name: "<your-query>",
isFirstPage: true, // no records come back; aggregations do
});Load more items of one facet
This is the aggregate action, and the response is not a Result:
import type { Aggregation } from "@sinequa/atomic";
// `aggregate` on the client returns the aggregation, refilled with the next slice.
const more: Aggregation = await client.api.query.aggregate(aggregation, {
name: "<your-query>",
text: "quarterly report",
});See Aggregations for skip, count and the $hasMore flag.