The client
Create a client, understand what it owns, and know when the free-function form is the right one instead.
Everything this library does goes through a client: it holds the configuration, owns the session, carries the transport, and exposes the typed API. Create one, keep it, pass it around.
Marked @experimental
createAtomicClient and its type carry an @experimental tag in the source: "the shape may still change
before it replaces the free functions". It is nonetheless the form this documentation teaches, because it is
the one being moved towards and the only one that supports more than one backend. The
free functions are stable and remain fully supported.
Creating one
import { createAtomicClient } from "@sinequa/atomic";
/**
* Module scope, so the whole application shares one configuration and one session.
* Creating a client is inert: no request is made until you ask for one.
*/
export const sinequa = createAtomicClient({
app: "<your-app>",
backendUrl: "https://<your-sinequa-server>",
});
/** Started once; anything that queries awaits it. */
export const ready = (async () => {
await sinequa.initializeConfig();
await sinequa.auth.login();
})();import { InjectionToken, inject, provideAppInitializer } from "@angular/core";
import { type AtomicClient, createAtomicClient } from "@sinequa/atomic";
/**
* An injection token rather than a service class: the client is already a complete
* object, and wrapping it would only add a layer to keep in sync.
*/
export const SINEQUA = new InjectionToken<AtomicClient>("Sinequa client", {
factory: () =>
createAtomicClient({
app: "<your-app>",
backendUrl: "https://<your-sinequa-server>",
}),
});
/** Configuration and session resolved before the first view renders. */
export function provideSinequa() {
return provideAppInitializer(async () => {
const client = inject(SINEQUA);
await client.initializeConfig();
await client.auth.login();
});
}import { type AtomicClient, createAtomicClient } from "@sinequa/atomic";
import { createContext, type ReactNode, useContext } from "react";
const client = createAtomicClient({
app: "<your-app>",
backendUrl: "https://<your-sinequa-server>",
});
/** Started at module load, so concurrent components share one sign-in. */
export const ready = (async () => {
await client.initializeConfig();
await client.auth.login();
})();
const SinequaContext = createContext<AtomicClient>(client);
export function SinequaProvider({ children }: { children: ReactNode }) {
return <SinequaContext.Provider value={client}>{children}</SinequaContext.Provider>;
}
export function useSinequa() {
return useContext(SinequaContext);
}What a client owns
Prop
Type
The typed API
client.api groups the endpoints by what they act on. This is the canonical way to call them:
client.api.query.search(query); // POST api/v1/query
client.api.query.aggregate(agg, query); // paging through one aggregation's items
client.api.app.get(); // the server configuration
client.api.principal.get(); // the current user
client.api.preview.get(id, query); // a document's preview
client.api.labels.add(labels, ids); // tag documents
client.api.userSettings.patch(settings);Each method is the matching free function, already bound to this client. Nothing else differs — same parameters, same return types, minus the trailing context argument.
The free-function form
Every call also exists as a module-level function:
import { fetchQuery, setGlobalConfig } from "@sinequa/atomic";
setGlobalConfig({ app: "<your-app>", backendUrl: "https://<your-sinequa-server>" });
const result = await fetchQuery({ name: "<your-query>", text: "report" });These run on a default client whose configuration is globalConfig, read live. getDefaultClient()
returns that same instance, so the two forms are two views of one state and can be mixed freely.
Reach for the free functions when a client cannot be handed to you — a distributed web component, a plugin, a utility module deep in a call stack. Prefer an explicit client everywhere else: it is what makes a second backend, and testing without module mocks, possible at all.
A third form exists, and should not be used
API.principal.get() — a legacy aggregate object — is a third spelling of the same call, kept for
compatibility. It was never extended beyond principal, so it covers one endpoint family out of twelve:
learning it teaches you a pattern that does not apply anywhere else. client.api covers all of them.
Configuration read live
client.config is a view, not a snapshot. That matters after initializeConfig(), which is what resolves the
values the server decides:
import { createAtomicClient, isExternalAuthMode } from "@sinequa/atomic";
const client = createAtomicClient({ app: "<your-app>" });
// Before: backendUrl and the auth mode are not settled yet.
console.log(client.config.authMode?.kind); // 'unknown'
await client.initializeConfig();
// After: derived from the page URL and from the server's pre-login response.
console.log(client.config.backendUrl);
console.log(client.config.authMode?.kind); // 'sso' | 'oauth' | 'saml' | 'bearer' | 'credentials'
// Which decides whether a sign-in screen shows a form or a loader.
console.log(isExternalAuthMode(client.config.authMode) ? "loader" : "credentials form");import { ChangeDetectionStrategy, Component, computed, inject } from "@angular/core";
import { isExternalAuthMode } from "@sinequa/atomic";
import { SINEQUA } from "./sinequa";
@Component({
selector: "app-auth-screen",
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
@if (needsForm()) {
<form><!-- username and password --></form>
} @else {
<p>Signing you in…</p>
}
`,
})
export class AuthScreenComponent {
private readonly client = inject(SINEQUA);
/**
* The mode is resolved by initializeConfig(), which the app initializer already
* awaited — so by the time this renders, config reflects the server's answer.
*/
readonly needsForm = computed(() => !isExternalAuthMode(this.client.config.authMode));
}import { isExternalAuthMode } from "@sinequa/atomic";
import { useSinequa } from "./sinequa";
export function AuthScreen() {
const client = useSinequa();
// Resolved by initializeConfig(); config is a live view, so this is current.
const needsForm = !isExternalAuthMode(client.config.authMode);
return needsForm ? <form>{/* username and password */}</form> : <p>Signing you in…</p>;
}