Skip to main content
Version: Next ๐Ÿšง

Utilities

Small, focused helpers that show up across a Sinequa app: relative dates, query-param (de)serialization for shareable URLs, metadata extraction, and a few string/array helpers. Each is framework-agnostic; the React snippets show idiomatic wiring.

Relative dates: getRelativeDate()โ€‹

Formats the distance between two dates as a localized, human-readable string (via Intl.RelativeTimeFormat). If base is omitted, "now" is used.

relative-date.js
import { getRelativeDate, getOffsetFromDates } from '@sinequa/atomic';

getRelativeDate('en', '2026-06-07T00:00:00Z'); // e.g. "in 7 days"
getRelativeDate('fr', '2026-05-24T00:00:00Z'); // e.g. "il y a 7 jours"

// Need the raw numbers instead of a string?
getOffsetFromDates('2026-06-07T00:00:00Z'); // { offset: 7, unit: 'day', isFuture: true }
RelativeTime.jsx
import { getRelativeDate } from '@sinequa/atomic';

export function RelativeTime({ date, locale = 'en' }) {
if (!date) return null;
return <time dateTime={date} title={new Date(date).toLocaleString(locale)}>
{getRelativeDate(locale, date)}
</time>;
}

// <RelativeTime date={record.modified} locale="fr" />

Shareable search URLs: query paramsโ€‹

Serialize a search state into a query string and parse it back โ€” useful for deep links, bookmarks, and browser history.

query-params.js
import { getUrlParamsFromQueryParams, getQueryParamsFromUrl } from '@sinequa/atomic';

// Serialize โ†’ "q=example&p=1&s=relevance&t=results&n=_query"
const qs = getUrlParamsFromQueryParams({
text: 'example',
page: 1,
sort: 'relevance',
tab: 'results',
name: '_query',
filters: [{ field: 'type', value: 'document' }],
});

// Parse back from a full URL
const params = getQueryParamsFromUrl(`${location.pathname}?${qs}`);
// โ†’ { text: 'example', page: 1, sort: 'relevance', tab: 'results', name: '_query', filters: [...] }
use-url-sync.jsx
import { useEffect } from 'react';
import { getUrlParamsFromQueryParams, getQueryParamsFromUrl } from '@sinequa/atomic';

// Reflect the current search into the URL, and hydrate from it on mount.
export function useUrlSync(queryParams, onHydrate) {
// Hydrate once from the address bar.
useEffect(() => {
const parsed = getQueryParamsFromUrl(window.location.href);
if (parsed) onHydrate(parsed);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

// Push state changes into the URL (without a full navigation).
useEffect(() => {
if (!queryParams) return;
const qs = getUrlParamsFromQueryParams(queryParams);
const next = `${window.location.pathname}${qs ? `?${qs}` : ''}`;
window.history.replaceState(null, '', next);
}, [queryParams]);
}

Extracting metadata: getMetadata()โ€‹

Normalizes a record field into a string[], whatever its underlying shape โ€” a comma-separated string, an array of strings, or an array of { display } objects.

metadata.js
import { getMetadata } from '@sinequa/atomic';

getMetadata({ tags: 'foo,bar' }, 'tags'); // ['foo', 'bar']
getMetadata({ tags: ['foo', 'bar'] }, 'tags'); // ['foo', 'bar']
getMetadata({ authors: [{ display: 'Alice' }] }, 'authors'); // ['Alice']
getMetadata({ title: 'Hello' }, 'title'); // ['Hello']
getMetadata({}, 'missing'); // []
MetaList.jsx
import { getMetadata } from '@sinequa/atomic';

// Render any record field as a list of chips, regardless of its raw format.
export function MetaList({ record, field }) {
const values = getMetadata(record, field);
if (!values.length) return null;
return (
<ul className="chips">
{values.map((v) => <li key={v} className="chip">{v}</li>)}
</ul>
);
}

// <MetaList record={record} field="authors" />

String & expression helpersโ€‹

strings.js
import { escapeExpr, guid } from '@sinequa/atomic';

// Escape a value before injecting it into a fielded expression (handles quotes/backticks).
const expr = `author:\`${escapeExpr(userInput)}\``;

// Generate a unique id (with or without hyphens) โ€” handy for keys, request ids, etc.
guid(); // "3f2504e0-4f89-41d3-9a0c-0305e82c3301"
guid(false); // "3f2504e04f8941d39a0c0305e82c3301"

Partitioning arrays: bisect()โ€‹

Splits an array into two groups by a predicate, in a single pass: matching items land in true, the rest in false.

bisect.js
import { bisect } from '@sinequa/atomic';

const { true: even, false: odd } = bisect([1, 2, 3, 4], (n) => n % 2 === 0);
// even โ†’ [2, 4], odd โ†’ [1, 3]
SelectedAndRest.jsx
import { bisect } from '@sinequa/atomic';

// Show selected facet items first, the rest below.
export function FacetItems({ items }) {
const { true: selected, false: rest } = bisect(items, (i) => i.$selected);
return (
<>
{selected.map((i) => <Item key={String(i.value)} item={i} selected />)}
{rest.map((i) => <Item key={String(i.value)} item={i} />)}
</>
);
}

See alsoโ€‹