npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sinequa/atomic

v2.6.0

Published

<div align="center">

Readme

@sinequa/atomic

The TypeScript-first SDK for the Sinequa REST API.
Authentication, search, aggregations, datasets, preview — everything typed, tree-shakable, zero runtime dependencies.
SPFx-ready via the @sinequa/atomic/spfx subpath export.

Docs · Get started · Reference


Installation

npm install @sinequa/atomic

Requires Node ≥ 18 or any modern browser. Ships as ESM (atomic.js) and CJS (atomic.cjs) with full .d.ts declarations.

For SharePoint Framework web parts, @microsoft/sp-http is an optional peer dependency — install it only if you need it:

npm install @microsoft/sp-http --save-peer

At a glance

import { setGlobalConfig, isAuthenticated, login, fetchQuery } from '@sinequa/atomic';

setGlobalConfig({ app: 'my-app', backendUrl: 'https://my-sinequa-server.example.com' });

if (!isAuthenticated()) {
  await login({ username: 'alice', password: 's3cr3t' });
}

// A query is a plain object; only `name` — the query web service — is required.
const result = await fetchQuery({ name: '<your-query>', text: 'knowledge management', pageSize: 10 });

// One round-trip: the documents are in `records`, the facet counts in `aggregations`.
// Check `$error` before concluding anything from an empty `records`.
const { records, aggregations, $error } = result;

fetchAggregation(aggregation, query) is for paging an aggregation you already received — it takes the Aggregation object off result.aggregations and the same query, as two positional arguments. See Aggregations.


SharePoint Framework (SPFx)

In an SPFx web part, all HTTP requests must go through the AadHttpClient so the Azure AD bearer token is attached automatically. Import from the ./spfx subpath and call initializeAadHttpClient once during web part initialization:

import { initializeAadHttpClient, fetchQuery } from '@sinequa/atomic/spfx';
import { AadHttpClient } from '@microsoft/sp-http';

// In your web part's onInit():
const client = await this.context.aadHttpClientFactory
  .getClient('https://my-sinequa-server.example.com');
initializeAadHttpClient(client);

// All subsequent API calls route through AadHttpClient automatically:
const results = await fetchQuery({ name: '<your-query>', text: 'knowledge management' });

Call initializeAadHttpClient(null) to detach the client (e.g. in onDispose).

The ./spfx subpath re-exports everything from @sinequa/atomic and adds initializeAadHttpClient, aadHttpClientManager, createSpfxHttpAdapter, AadHttpClientLike and AadHttpResponseLike. initializeAadHttpClient registers the client globally for the free functions; createSpfxHttpAdapter(aadHttpClient) is the same transport as an httpAdapter for createAtomicClient. Standard (non-SPFx) consumers use @sinequa/atomic directly — no changes needed. See SPFx.


Modules

Client (experimental)

createAtomicClient(config) returns an isolated client — its own configuration, session and event subscribers — exposing client.config, client.configure(), client.initializeConfig(), client.auth, client.http and client.api (the typed endpoints, e.g. client.api.query.search(query)). Several clients can target different backends from the same page, and each takes its transport and auth knobs as configuration (httpAdapter, and auth: { fetch, storage, logger }), which removes the need for module mocks in tests.

import { createAtomicClient } from '@sinequa/atomic';

const client = createAtomicClient({ app: 'myapp', backendUrl: 'https://backend' });
client.auth.on('unauthorized', () => client.auth.login());
const results = await client.http.post('api/v1/search.query', { text: 'hello' });

The free functions below keep working unchanged: they run on a default client whose configuration is globalConfig, and getDefaultClient() hands back that very instance — useful from code that cannot receive one (a distributed web component, a plugin). See The client and Multiple backends.

Authentication

Full auth lifecycle — from first handshake to logout.

| Export | Description | |---|---| | isAuthenticated() | Synchronous. true when a CSRF token is stored or a token-less session was recorded (ambient SSO issues no client token) | | hasSession() | Asks the server when nothing is stored, and never redirects — unlike login() | | login(credentials?) | Login — switches on the resolved authMode (credentials / SSO / OAuth / SAML / bearer / unknown) | | logout() | Clears session, returns the server's redirectUrl | | clearSessionTokens() | Drops the local session (tokens, token-less flag) and emits authenticated: false, without a server round-trip | | detectAuthMode(config, preLogin?) | Pure resolution of the AuthMode from config + server pre-login (no I/O) | | getToken() / setToken(token) | Read/write the stored CSRF token | | requestWebTokenSession(credentials?) | POSTs security.webtoken. The JWT lands in a cookie; the returned (and stored) value is the CSRF token. Omit the credentials to use the configured bearer token | | getCsrfToken() | CSRF token for mutating requests | | deleteWebTokenCookie() | Explicitly removes the web token cookie | | onAuthEvent(event, handler) | Typed subscription to the auth lifecycle (authenticated, unauthorized, requestFailed, tokenRefreshed, loggedOut) — returns the unsubscribe function | | tryAutoAuthentication() | Probes a protected endpoint for server-side auto-authentication (OIDC, IIS Negotiate) — used by login() in sso, unknown and bearer modes. Never throws | | isRedirectPending() | Whether an OAuth/SAML redirect is in flight and has not established a session yet | | tryOAuthAuthentication() / trySAMLAuthentication() | Redirect to the configured identity provider (with a one-shot redirect-loop guard) | | emitAuthenticatedEvent(bool) | Dispatches the DOM 'authenticated' CustomEvent (on document, bubbles to window) |

getJWToken is @deprecated: it is an alias of requestWebTokenSession, renamed because the old name suggested it returned the JWT. It will be removed in the next major.

The authentication layer is configurable via setGlobalConfig({ auth: { … } }) (AuthOptions): timeoutMs, probeEndpoint, injectable storage/logger/fetch, the requests' credentials mode, and recoverFromUnauthorized — the hook that re-establishes a session after a 401 and gets the request replayed once. See Authentication and Handling 401.

import { isAuthenticated, login, logout } from '@sinequa/atomic';

if (!isAuthenticated()) {
  // Auto-resolve: reuse an existing session if any, otherwise follow the resolved
  // `authMode` — in `unknown` mode, probe for SSO then fall back to the credentials form.
  const ok = await login();

  // — or — explicit credentials, which bypass mode detection entirely.
  const okWithCredentials = await login({ username: 'alice', password: 's3cr3t' });
}

const redirectUrl = await logout();

Auth mode (AuthMode) — 2.0

globalConfig.authMode is the single source of truth for the authentication method. It is a typed discriminated union — no more juggling overlapping booleans:

import { AuthMode, globalConfig, setGlobalConfig } from '@sinequa/atomic';

setGlobalConfig({ authMode: AuthMode.credentials() });        // username/password form
setGlobalConfig({ authMode: AuthMode.sso() });                // browser/proxy-injected auth
setGlobalConfig({ authMode: AuthMode.oauth('my-provider') }); // redirect to an OAuth provider
setGlobalConfig({ authMode: AuthMode.saml('my-provider') });  // redirect to a SAML provider
setGlobalConfig({ authMode: AuthMode.bearer(token) });        // server-to-server bearer token
setGlobalConfig({ authMode: AuthMode.unknown() });            // try SSO, then fall back to credentials

// `login()` and `detectAuthMode()` branch on `authMode.kind`. Every key of `globalConfig` is
// optional, so read it through `?.` — the default is `AuthMode.unknown()`.
const kind = globalConfig.authMode?.kind; // 'credentials' | 'sso' | 'oauth' | 'saml' | 'bearer' | 'unknown'

Migrating from 1.x: the booleans useCredentials / useSSO / useSAML are now @deprecated read-only getters derived from authMode. Reading them still works, and writing them through setGlobalConfig({ useSSO: true }) is translated into the matching authMode — so existing code keeps working. Prefer authMode.kind (and the AuthMode.* constructors) in new code. Full upgrade notes: Migrating from 1.x.


Web API — v1

Every Sinequa v1 endpoint has a typed wrapper.

import {
  fetchApp,
  fetchAppPreLogin,
  fetchQuery,
  fetchBulkQuery,
  fetchAggregation,
  fetchDataset,
  fetchPreview,
  fetchPrincipal,
  fetchSimilarDocuments,
  fetchSponsoredLinks,
  fetchSuggest,
  fetchTextChunks,
  fetchUserSettings,
  fetchLabels,
  fetchQueryExport,
  fetchQueryIntent,
  searchPrincipals,
} from '@sinequa/atomic';

Auditing is not a fetch* function: use Audit.notify(...), plus the record decorators addAuditAdditionalInfo, addSessionId and addUrl. See Audit.

Web API — v2

import {
  fetchUserProfile,
  fetchVersion,
  fetchChangePassword,
  fetchSendPasswordResetEmail,
} from '@sinequa/atomic';

The full endpoint list — with the client.api method and the free function for each — is in Endpoints.


Helpers

Utilities for transforming Sinequa data structures.

import {
  getMetadata,           // metadata of an article as `string[]` (splits comma-separated values)
  getMetadataWithValues, // same, as `{ display, value }[]`
  makeColumn,
  extraColumns,
  resolveToColumnName,
  resultPages,
  escapeExpr,
  guid,
  isObject,
} from '@sinequa/atomic';

More in Helpers.

Filter builder

Build structured query.filters without hand-writing filter objects. Combinators ignore falsy operands and collapse trivial cases (0 → undefined, 1 → the operand itself), so filters compose cleanly from optional UI state:

import { filter, fetchQuery } from '@sinequa/atomic';

const filters = filter.and(
  filter.gt('size', 1000),
  filter.or(
    filter.eq('treepath', '/HR/*'),
    filter.in('authors', ['alice', 'bob']),
  ),
  onlyRecent && filter.between('modified', '2024-01-01', '2024-12-31'),
);

await fetchQuery({ name: '<your-query>', text: 'report', filters });

Leaf operators: eq · neq · gt · gte · lt · lte · like · contains · regex · isNull · isNotNull · in · between. Combinators: and · or · not.

filter.distribution(field, item.value) handles the one case the leaf operators cannot: a distribution aggregation item (a date or size bucket), whose value is an expression the server built rather than a value. Passing it to filter.eq makes the backend answer 500 — "Field type error". See Filters.


Utilities

import {
  bisect,
  sysLang,                     // resolves `all[fr]tous[de]alle` against a locale
  getRelativeDate,             // and `getOffsetFromDates`
  getQueryParamsFromUrl,       // and `getUrlParamsFromQueryParams`, `getFiltersFromUrl`, …
  addConcepts,                 // and `getConcepts`, `parseText`, `rewriteText`, `removeConcept(s)`
  notify,
  configureLogger,             // and the `debug` / `info` / `warn` / `error` bindings, `LogLevel`
} from '@sinequa/atomic';

The library's verbosity is set with configureLogger({ level: LogLevel.DEBUG }), not through a configuration key. See Logging and URL state.


Types

The package re-exports every Sinequa domain type so you never need to cast:

import type {
  CCApp, CCQuery, CCColumn, CCIndex,
  Aggregation, AggregationItem, ListAggregation, TreeAggregation,
  Filter, SimpleFilter, InFilter, BetweenFilter, ExprFilter, NullFilter, NotNullFilter,
  AuthMode, AuthModeKind,
  PreviewData,
  Principal,
  AuditEvent, AuditRecord,
  TextChunk,
} from '@sinequa/atomic';

Which type comes back from which call: Type map.


Configuration

Set the backend URL before making any API call (required when the Sinequa server is not same-origin). setGlobalConfig performs a shallow merge into globalConfig:

import { setGlobalConfig, globalConfig, AuthMode } from '@sinequa/atomic';

setGlobalConfig({
  app: 'my-app',
  backendUrl: 'https://my-sinequa-server.example.com',
  authMode: AuthMode.unknown(), // optional — defaults to `unknown`
});

// read it back — every key is optional on `globalConfig`, so `?.` is required
console.log(globalConfig.backendUrl, globalConfig.authMode?.kind);

Call initializeAppConfig() once at bootstrap: it fills in backendUrl/app from the browser URL when they were not provided, then resolves the authMode from the server's pre-login response.

appInitializerFn is a @deprecated alias of initializeAppConfig — use the latter.

Three declared keys are read by nothing, and setting them has no effect: logLevel (set the level with configureLogger({ level })), loginPath (routing is yours — use recoverFromUnauthorized or the unauthorized event) and createRoutes. Every key, with its default: Configuration keys.


Testing

npm test                    # vitest, watch mode
npm test -- --watch=false   # single run (what CI does)

--ui is not available out of the box: @vitest/ui is an optional peer of vitest and is not installed here — npm i -D @vitest/ui first.

Coverage is deepest on authentication (AuthMode detection, OAuth, SAML, credentials, tokens, 401 recovery) and on the HTTP helpers. On the endpoints it is targeted rather than exhaustive — fetchQuery, fetchAggregation, fetchPrincipal, searchPrincipals, fetchApp and the document-upload containers. Also covered: the filter builder, metadata parsing, column resolution, pagination, date utilities and the SQL value helper.

Injecting fetch, storage and logger through the configuration is what lets a test avoid module mocks — see Testing.


Development

npm run build        # production build (vite + tsc)
npm run watch        # rebuild on change
npm run dev:pack     # build + pack locally

Contributing

  1. All tests must pass.
  2. Lint with Biomenpx @biomejs/biome check --write .
  3. Follow Conventional Commits.
  4. Any change touching src/ needs a changeset in the same commit (npm run changeset, then npm run changeset:status to check) — the changeset-check CI job blocks the merge without one. Add #skip-changeset to the MR title for a change that does not touch the published library. Full guide, with examples: CONTRIBUTING-changesets.md.
  5. New public API → add tests + JSDoc, and export it from src/index.ts (a helper that is written and tested but not re-exported from the barrel is not part of the public API).

Built for the Sinequa ecosystem · Report an issue