Server configuration
What the administrator decided — facets, tabs, sorting, column aliases — and how your interface reads it instead of hardcoding it.
Almost nothing about a Sinequa interface is your decision. Which facets exist, which tabs, which sort options,
which columns are searchable and what they are called — all of it is configured on the server, and GET api/v1/app hands you the whole tree.
An interface that hardcodes that list works until the administrator changes it. One that reads it adapts.
Concept — the CC prefix
Types starting with CC are configuration types: CCApp, CCQuery, CCColumn. They describe what the
administrator set up, as your code reads it. You never construct one.
Reading it
import { createAtomicClient, sysLang } from "@sinequa/atomic";
import type { CCApp, CCQuery } from "@sinequa/atomic";
const client = createAtomicClient({
app: "<your-app>",
backendUrl: "https://<your-sinequa-server>",
});
await client.initializeConfig();
await client.auth.login();
const app: CCApp = await client.api.app.get();
// Which query web service to search through. Do not assume "<your-query>".
const queryName = app.defaultQueryName ?? Object.keys(app.queries)[0];
const query: CCQuery = app.queries[queryName];
describe(query);
function describe(query: CCQuery) {
console.log("page size:", query.pageSize);
// Facets to render, in the order the administrator put them.
for (const aggregation of query.aggregations ?? []) {
console.log(`facet ${aggregation.name} on column ${aggregation.column}`);
}
// Sort options: `sort` on a query takes one of these names, not an SQL clause.
for (const choice of query.sortingChoices ?? []) {
// Labels come in Sinequa's multilingual format; decode before displaying.
console.log(`sort ${choice.name}: ${sysLang(choice.display ?? choice.name, "en-US")}`);
}
// Tabs, each of which the server counts separately.
for (const tab of query.tabSearch?.tabs ?? []) {
console.log(`tab ${tab.value}${tab.isDefault ? " (default)" : ""}`);
}
}import { Injectable, computed, inject, resource } from "@angular/core";
import { sysLang } from "@sinequa/atomic";
import { SINEQUA } from "./sinequa";
/**
* The server's configuration, fetched once and shared. Facets, tabs and sort
* options are read from here rather than hardcoded, so an administrator's change
* reaches the interface without a release.
*/
@Injectable({ providedIn: "root" })
export class ServerConfigService {
private readonly client = inject(SINEQUA);
readonly app = resource({ loader: () => this.client.api.app.get() });
readonly query = computed(() => {
const app = this.app.value();
if (!app) return undefined;
return app.queries[app.defaultQueryName ?? Object.keys(app.queries)[0]];
});
readonly facets = computed(() => this.query()?.aggregations ?? []);
readonly tabs = computed(() => this.query()?.tabSearch?.tabs ?? []);
/** Sort labels are multilingual strings; decode them for the current locale. */
readonly sortOptions = computed(() =>
(this.query()?.sortingChoices ?? []).map((choice) => ({
name: choice.name,
label: sysLang(choice.display ?? choice.name, navigator.language),
})),
);
}import { sysLang } from "@sinequa/atomic";
import type { CCApp, CCQuery } from "@sinequa/atomic";
import { useEffect, useMemo, useState } from "react";
import { ready, sinequa } from "./sinequa";
/**
* The server's configuration, fetched once. Facets, tabs and sort options are read
* from here rather than hardcoded, so an administrator's change reaches the
* interface without a release.
*/
export function useServerConfig() {
const [app, setApp] = useState<CCApp>();
useEffect(() => {
let active = true;
ready
.then(() => sinequa.api.app.get())
.then((next) => active && setApp(next));
return () => {
active = false;
};
}, []);
const query: CCQuery | undefined = useMemo(() => {
if (!app) return undefined;
return app.queries[app.defaultQueryName ?? Object.keys(app.queries)[0]];
}, [app]);
const sortOptions = useMemo(
() =>
(query?.sortingChoices ?? []).map((choice) => ({
name: choice.name,
// Labels are multilingual strings; decode for the current locale.
label: sysLang(choice.display ?? choice.name, navigator.language),
})),
[query],
);
return {
app,
query,
facets: query?.aggregations ?? [],
tabs: query?.tabSearch?.tabs ?? [],
sortOptions,
};
}What CCApp contains
Prop
Type
CCQuery is where a search's shape comes from: pageSize, aggregations, columnsInfo, sortingChoices,
scopes, tabSearch, plus relevance settings (sCMode, questionPrecision, questionStrategy, isNeural)
and which indexes it searches.
Columns and their aliases
An interface talks about recordType; the index stores sourcestr1. That mapping is configuration, and it
exists at two levels — per query web service, and in the index schema.
resolveToColumnName walks both, in the right order:
import { resolveToColumnName } from "@sinequa/atomic";
const column = resolveToColumnName("recordType", app, "<your-query>");
// "sourcestr1" — the query web service's aliases win over the index schema
filters = filter.eq(column ?? "recordType", "email");Passing the query name matters: an alias declared on the query web service takes precedence over the same alias in the index schema. Without it, only the schema is consulted.
The two `aliases` fields have different types
CCColumnInfo.aliases is a comma-separated string; CCColumn.aliases is a string[]. That is why
resolution has two branches, and why reading one as the other silently finds nothing.
makeColumn(name, type, typeModifier?, aliases?) builds a CCColumn from the textual type the server reports,
translating it into the engine's enums. extraColumns holds the fourteen system columns a record carries but
the index schema does not declare — id, text, matchlocations, accesslists and the like.