Datasets and bulk queries
Named, parameterised queries for dashboards — and running several searches in one round trip.
Two ways to get more than one answer at a time, for two different needs.
| Use it for | |
|---|---|
| Datasets | Named queries the administrator wrote, with parameters. Dashboards, analytics, counters. |
| Bulk queries | Several ordinary searches your code composed, in one round trip. |
Concept — a dataset
A dataset is a query stored on the server, in a dataset web service, and invoked by name. The SQL and the tuning live server-side; your code passes parameters and renders the result. It is how a dashboard tile gets its numbers without your application knowing how they are computed.
Datasets
Three calls, and the first thing to do is discover what exists:
Prop
Type
import { createAtomicClient } from "@sinequa/atomic";
import type { DatasetError, Result } from "@sinequa/atomic";
const client = createAtomicClient({
app: "<your-app>",
backendUrl: "https://<your-sinequa-server>",
});
await client.initializeConfig();
await client.auth.login();
// The dataset web service is named in the configuration, like every other service.
const app = await client.api.app.get();
const service = Object.entries(app.webServices).find(
([, ws]) => ws.webServiceType === "dataset",
)?.[0];
if (!service) throw new Error("This application has no dataset web service.");
// What can we ask for?
for (const { name, description } of await client.api.dataset.list(service)) {
console.log(name, description ?? "");
}
// One request, several datasets, shared parameters.
const tiles = await client.api.dataset.getMany(service, {
datasets: ["documentsPerSource", "searchesPerDay"],
parameters: { period: "last30days" },
});
for (const [name, value] of Object.entries(tiles)) {
// getMany does not throw: a failed dataset arrives as an error entry.
if (isError(value)) {
console.error(`${name} failed: ${value.errorMessage}`);
continue;
}
console.log(`${name}: ${value.rowCount} row(s)`);
}
function isError(value: Result | DatasetError): value is DatasetError {
return "errorCode" in value;
}import { Injectable, inject, resource, signal } from "@angular/core";
import type { DatasetError, Result } from "@sinequa/atomic";
import { SINEQUA } from "./sinequa";
@Injectable({ providedIn: "root" })
export class DashboardService {
private readonly client = inject(SINEQUA);
/** The dataset web service name, read from the configuration. */
readonly service = signal("");
readonly period = signal("last30days");
readonly tiles = resource({
params: () => ({ service: this.service(), period: this.period() }),
loader: ({ params }) => {
if (!params.service) return Promise.resolve({});
// One request for the whole dashboard rather than one per tile.
return this.client.api.dataset.getMany(params.service, {
datasets: ["documentsPerSource", "searchesPerDay"],
parameters: { period: params.period },
});
},
});
/** getMany reports per-dataset failures inline instead of rejecting. */
isError(value: Result | DatasetError): value is DatasetError {
return "errorCode" in value;
}
}import type { DataSet, DatasetError, Result } from "@sinequa/atomic";
import { useEffect, useState } from "react";
import { ready, sinequa } from "./sinequa";
export function isDatasetError(value: Result | DatasetError): value is DatasetError {
return "errorCode" in value;
}
export function useDashboard(service: string, period: string) {
const [tiles, setTiles] = useState<DataSet<Result>>({});
useEffect(() => {
if (!service) return;
let active = true;
ready
.then(() =>
// One request for the whole dashboard rather than one per tile.
sinequa.api.dataset.getMany(service, {
datasets: ["documentsPerSource", "searchesPerDay"],
parameters: { period },
}),
)
.then((next) => active && setTiles(next));
return () => {
active = false;
};
}, [service, period]);
return tiles;
}Failures behave differently between the two
This asymmetry is worth remembering:
dataset.getthrows when the server answers with an error envelope for that dataset — anErrorcarrying the server'serrorMessage.dataset.getManydoes not. Each entry of the returned record is either aResultor aDatasetError({ errorCode, errorMessage }), so one broken tile does not take the dashboard down.
Narrow with "errorCode" in value, as the examples do.
The dataset name is lowercased on its first letter
dataset.get lowercases the first character of the dataset name before building the URL. A dataset the
administrator named DocumentsPerSource is requested as documentsPerSource. Harmless most of the time, but it
explains a 404 that looks impossible.
Bulk queries
client.api.query.bulk(queries, mode?) sends several ordinary queries in one request and returns one
result per query, in order. Use it when a view needs two or three searches that would otherwise be sequential
round trips — a result list plus a sidebar counter, say.
const [documents, people] = await client.api.query.bulk([
{ name: "<your-query>", text: "onboarding", pageSize: 10 },
{ name: "people", text: "onboarding", pageSize: 5 },
]);Passing "parallel" as the mode asks the server to run them concurrently rather than in sequence.
Each result gets the same post-processing as a single search — pagination fields, the aggregations' $hasMore,
tree paths — so results are interchangeable with those of query.search.