@sinequa/atomic 2.3.1 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +406 -343
  2. package/dist/atomic.cjs +1 -1
  3. package/dist/atomic.js +2 -2
  4. package/dist/authentication/events/emit-authenticated-event.d.ts.map +1 -1
  5. package/dist/authentication/session/logout.d.ts.map +1 -1
  6. package/dist/authentication/session/signed-out-guard.d.ts +23 -0
  7. package/dist/authentication/session/signed-out-guard.d.ts.map +1 -0
  8. package/dist/client/create-api.d.ts +1 -1
  9. package/dist/configurations/auth-mode.d.ts +2 -2
  10. package/dist/configurations/global.config.d.ts +33 -2
  11. package/dist/configurations/global.config.d.ts.map +1 -1
  12. package/dist/helpers/aggregation-count.d.ts +68 -0
  13. package/dist/helpers/aggregation-count.d.ts.map +1 -0
  14. package/dist/helpers/index.d.ts +1 -0
  15. package/dist/helpers/index.d.ts.map +1 -1
  16. package/dist/helpers/utils.d.ts +29 -4
  17. package/dist/helpers/utils.d.ts.map +1 -1
  18. package/dist/spfx/index.cjs +1 -1
  19. package/dist/spfx/index.js +2 -2
  20. package/dist/{src-BzPBKdDO.js → src-DQM5GZF-.js} +693 -658
  21. package/dist/src-yvCcJDJS.cjs +1 -0
  22. package/dist/utils/index.d.ts +1 -0
  23. package/dist/utils/index.d.ts.map +1 -1
  24. package/dist/web-api/helpers/methods/send-request.d.ts +0 -4
  25. package/dist/web-api/helpers/methods/send-request.d.ts.map +1 -1
  26. package/dist/web-api/v1/fetch-aggregation.d.ts +26 -5
  27. package/dist/web-api/v1/fetch-aggregation.d.ts.map +1 -1
  28. package/dist/web-api/v1/fetch-audit.d.ts +7 -0
  29. package/dist/web-api/v1/fetch-audit.d.ts.map +1 -1
  30. package/dist/web-api/v1/fetch-query.d.ts.map +1 -1
  31. package/package.json +2 -1
  32. package/dist/src-DeaVS0Tt.cjs +0 -1
package/README.md CHANGED
@@ -1,343 +1,406 @@
1
- <div align="center">
2
-
3
- <img src="https://img.shields.io/npm/v/@sinequa/atomic?style=for-the-badge&color=0ea5e9&label=@sinequa%2Fatomic" alt="npm version" />
4
- <img src="https://img.shields.io/badge/TypeScript-5.x-3178c6?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript" />
5
- <img src="https://img.shields.io/badge/ESM-ready-f59e0b?style=for-the-badge" alt="ESM" />
6
- <img src="https://img.shields.io/badge/SPFx-ready-0078d4?style=for-the-badge&logo=microsoftsharepoint&logoColor=white" alt="SPFx" />
7
- <img src="https://img.shields.io/npm/l/@sinequa/atomic?style=for-the-badge&color=22c55e" alt="MIT License" />
8
-
9
- <br /><br />
10
-
11
- # `@sinequa/atomic`
12
-
13
- **The TypeScript-first SDK for the Sinequa REST API.**
14
- Authentication, search, aggregations, datasets, preview — everything typed, tree-shakable, zero runtime dependencies.
15
- SPFx-ready via the `@sinequa/atomic/spfx` subpath export.
16
-
17
- [Docs](https://sinequa.github.io/sba-mint/atomic/intro) · [API Reference](https://sinequa.github.io/sba-mint/atomic/api/app)
18
-
19
- </div>
20
-
21
- ---
22
-
23
- ## Installation
24
-
25
- ```bash
26
- npm install @sinequa/atomic
27
- ```
28
-
29
- > Requires **Node ≥ 18** or any modern browser. Ships as ESM (`atomic.js`) and CJS (`atomic.cjs`) with full `.d.ts` declarations.
30
-
31
- For **SharePoint Framework** web parts, `@microsoft/sp-http` is an optional peer dependency — install it only if you need it:
32
-
33
- ```bash
34
- npm install @microsoft/sp-http --save-peer
35
- ```
36
-
37
- ---
38
-
39
- ## At a glance
40
-
41
- ```typescript
42
- import { login, isAuthenticated, fetchQuery, fetchAggregation } from '@sinequa/atomic';
43
-
44
- if (!isAuthenticated()) {
45
- await login({ username: 'alice', password: 's3cr3t' });
46
- }
47
-
48
- const results = await fetchQuery({ query: 'knowledge management', pageSize: 10 });
49
- const facets = await fetchAggregation({ aggregation: 'company', query: 'knowledge management' });
50
- ```
51
-
52
- ---
53
-
54
- ## SharePoint Framework (SPFx)
55
-
56
- In an SPFx web part, all HTTP requests must go through the `AadHttpClient` so the Azure AD bearer
57
- token is attached automatically. Import from the `./spfx` subpath and call
58
- `initializeAadHttpClient` once during web part initialization:
59
-
60
- ```typescript
61
- import { initializeAadHttpClient, fetchQuery } from '@sinequa/atomic/spfx';
62
- import { AadHttpClient } from '@microsoft/sp-http';
63
-
64
- // In your web part's onInit():
65
- const client = await this.context.aadHttpClientFactory
66
- .getClient('https://my-sinequa-server.example.com');
67
- initializeAadHttpClient(client);
68
-
69
- // All subsequent API calls route through AadHttpClient automatically:
70
- const results = await fetchQuery({ name: '_query', text: 'knowledge management' });
71
- ```
72
-
73
- Call `initializeAadHttpClient(null)` to detach the client (e.g. in `onDispose`).
74
-
75
- > The `./spfx` subpath re-exports everything from `@sinequa/atomic` and adds
76
- > `initializeAadHttpClient`, `AadHttpClientLike`, and `AadHttpResponseLike`.
77
- > Standard (non-SPFx) consumers use `@sinequa/atomic` directly — no changes needed.
78
-
79
- ---
80
-
81
- ## Modules
82
-
83
- ### Client (experimental)
84
-
85
- `createAtomicClient(config)` returns an isolated client — its own configuration, session and event
86
- subscribers — exposing `client.config`, `client.auth` and `client.http`. Several clients can target
87
- different backends from the same page, and each takes its `fetch`/`storage`/`logger` as
88
- configuration, which removes the need for module mocks in tests.
89
-
90
- ```typescript
91
- import { createAtomicClient } from '@sinequa/atomic';
92
-
93
- const client = createAtomicClient({ app: 'myapp', backendUrl: 'https://backend' });
94
- client.auth.on('unauthorized', () => client.auth.login());
95
- const results = await client.http.post('api/v1/search.query', { text: 'hello' });
96
- ```
97
-
98
- The free functions below keep working unchanged: they run on a default client whose configuration
99
- **is** `globalConfig`, and `getDefaultClient()` hands back that very instance — useful from code
100
- that cannot receive one (a distributed web component, a plugin).
101
-
102
- ### Authentication
103
-
104
- Full auth lifecycle — from first handshake to logout.
105
-
106
- | Export | Description |
107
- |---|---|
108
- | `isAuthenticated()` | Returns `true` when a valid token is present |
109
- | `login(credentials?)` | Login — switches on the resolved `authMode` (credentials / SSO / OAuth / SAML / bearer) |
110
- | `logout()` | Clears session, returns the server's `redirectUrl` |
111
- | `clearSessionTokens()` | Drops the in-memory/stored session tokens without a server round-trip |
112
- | `detectAuthMode(config, preLogin?)` | Pure resolution of the `AuthMode` from config + server pre-login (no I/O) |
113
- | `getToken()` / `setToken(token)` | Read/write the stored CSRF token |
114
- | `getJWToken(credentials?)` | Fetches a JWT from the server, returns the CSRF token |
115
- | `getCsrfToken()` | CSRF token for mutating requests |
116
- | `deleteWebTokenCookie()` | Explicitly removes the web token cookie |
117
- | `onAuthEvent(event, handler)` | Typed subscription to the auth lifecycle (`authenticated`, `unauthorized`, `tokenRefreshed`, `loggedOut`) — returns the unsubscribe function |
118
- | `tryAutoAuthentication()` | Probes the server for auto-authentication (OIDC) — used by `login()` in `unknown` mode |
119
- | `tryOAuthAuthentication()` / `trySAMLAuthentication()` | Redirect to the configured identity provider (with a one-shot redirect-loop guard) |
120
- | `emitAuthenticatedEvent(bool)` | Dispatches the DOM `'authenticated'` CustomEvent (on `document`, bubbles to `window`) |
121
-
122
- The authentication layer is configurable via `setGlobalConfig({ auth: { … } })` (`AuthOptions`):
123
- request timeout, auto-auth probe endpoint, injectable storage/logger/fetch, and the requests'
124
- `credentials` mode.
125
-
126
- ```typescript
127
- import { isAuthenticated, login, logout } from '@sinequa/atomic';
128
-
129
- if (!isAuthenticated()) {
130
- // Auto-resolve: reuse an existing session if any, otherwise try SSO and
131
- // fall back to the credentials form. Provider resolved from server config.
132
- const ok = await login();
133
-
134
- // — or — credential-based login, bypasses SSO
135
- const ok = await login({ username: 'alice', password: 's3cr3t' });
136
- }
137
-
138
- const redirectUrl = await logout();
139
- ```
140
-
141
- #### Auth mode (`AuthMode`) — **2.0**
142
-
143
- `globalConfig.authMode` is the **single source of truth** for the authentication method. It is a
144
- typed discriminated union — no more juggling overlapping booleans:
145
-
146
- ```typescript
147
- import { AuthMode, setGlobalConfig } from '@sinequa/atomic';
148
-
149
- setGlobalConfig({ authMode: AuthMode.credentials() }); // username/password form
150
- setGlobalConfig({ authMode: AuthMode.sso() }); // browser/proxy-injected auth
151
- setGlobalConfig({ authMode: AuthMode.oauth('my-provider') }); // redirect to an OAuth provider
152
- setGlobalConfig({ authMode: AuthMode.saml('my-provider') }); // redirect to a SAML provider
153
- setGlobalConfig({ authMode: AuthMode.bearer(token) }); // server-to-server bearer token
154
- setGlobalConfig({ authMode: AuthMode.unknown() }); // try SSO, then fall back to credentials
155
-
156
- // `login()` and `detectAuthMode()` branch on `authMode.kind`:
157
- const kind = globalConfig.authMode.kind; // 'credentials' | 'sso' | 'oauth' | 'saml' | 'bearer' | 'unknown'
158
- ```
159
-
160
- > **Migrating from 1.x:** the booleans `useCredentials` / `useSSO` / `useSAML` are now
161
- > **`@deprecated` read-only getters** derived from `authMode`. Reading them still works, and writing
162
- > them through `setGlobalConfig({ useSSO: true })` is translated into the matching `authMode` — so
163
- > existing code keeps working. Prefer `authMode.kind` (and the `AuthMode.*` constructors) in new code.
164
-
165
- ---
166
-
167
- ### Web API — v1
168
-
169
- Every Sinequa v1 endpoint has a typed wrapper.
170
-
171
- ```typescript
172
- import {
173
- fetchApp,
174
- fetchQuery,
175
- fetchAggregation,
176
- fetchDataset,
177
- fetchPreview,
178
- fetchPrincipal,
179
- fetchSimilarDocuments,
180
- fetchSponsoredLinks,
181
- fetchSuggest,
182
- fetchTextChunk,
183
- fetchUserSettings,
184
- fetchAudit,
185
- fetchLabels,
186
- fetchQueryExport,
187
- fetchQueryIntent,
188
- } from '@sinequa/atomic';
189
- ```
190
-
191
- ### Web API — v2
192
-
193
- ```typescript
194
- import {
195
- fetchUserProfile,
196
- fetchVersion,
197
- fetchChangePassword,
198
- fetchForgotPassword,
199
- } from '@sinequa/atomic';
200
- ```
201
-
202
- ---
203
-
204
- ### Helpers
205
-
206
- Utilities for transforming Sinequa data structures.
207
-
208
- ```typescript
209
- import {
210
- getMetadata,
211
- getMetadataWithValues, // parses comma-separated metadata strings
212
- makeColumn,
213
- resolveToColumnName,
214
- escapeExpr,
215
- guid,
216
- isObject,
217
- } from '@sinequa/atomic';
218
- ```
219
-
220
- #### Filter builder
221
-
222
- Build structured `query.filters` without hand-writing filter objects. Combinators ignore falsy
223
- operands and collapse trivial cases (0 → `undefined`, 1 → the operand itself), so filters compose
224
- cleanly from optional UI state:
225
-
226
- ```typescript
227
- import { filter, fetchQuery } from '@sinequa/atomic';
228
-
229
- const filters = filter.and(
230
- filter.gt('size', 1000),
231
- filter.or(
232
- filter.eq('treepath', '/HR/*'),
233
- filter.in('authors', ['alice', 'bob']),
234
- ),
235
- onlyRecent && filter.between('modified', '2024-01-01', '2024-12-31'),
236
- );
237
-
238
- await fetchQuery({ name: '_query', text: 'report', filters });
239
- ```
240
-
241
- Leaf operators: `eq` · `neq` · `gt` · `gte` · `lt` · `lte` · `like` · `contains` · `regex` · `isNull` · `isNotNull` · `in` · `between`. Combinators: `and` · `or` · `not`.
242
-
243
- ---
244
-
245
- ### Utilities
246
-
247
- ```typescript
248
- import {
249
- bisect,
250
- formatDate,
251
- parseDate,
252
- logger,
253
- queryParamsToString,
254
- toSQL,
255
- syslang,
256
- } from '@sinequa/atomic';
257
- ```
258
-
259
- ---
260
-
261
- ## Types
262
-
263
- The package re-exports every Sinequa domain type so you never need to cast:
264
-
265
- ```typescript
266
- import type {
267
- CCApp, CCQuery, CCColumn, CCIndex,
268
- Aggregation, AggregationItem, ListAggregation, TreeAggregation,
269
- Filter, SimpleFilter, InFilter, BetweenFilter, ExprFilter, NullFilter, NotNullFilter,
270
- AuthMode, AuthModeKind,
271
- PreviewData,
272
- Principal,
273
- AuditEvent, AuditRecord,
274
- TextChunk,
275
- } from '@sinequa/atomic';
276
- ```
277
-
278
- ---
279
-
280
- ## Configuration
281
-
282
- Set the backend URL before making any API call (required when the Sinequa server is not
283
- same-origin). `setGlobalConfig` performs a shallow merge into `globalConfig`:
284
-
285
- ```typescript
286
- import { setGlobalConfig, globalConfig, AuthMode } from '@sinequa/atomic';
287
-
288
- setGlobalConfig({
289
- app: 'my-app',
290
- backendUrl: 'https://my-sinequa-server.example.com',
291
- authMode: AuthMode.unknown(), // optional — defaults to `unknown`
292
- });
293
-
294
- // read it back
295
- console.log(globalConfig.backendUrl, globalConfig.authMode.kind);
296
- ```
297
-
298
- > `appInitializerFn` is also exported as an alias of `initializeAppConfig()`, which resolves the
299
- > `authMode` from the server pre-login response during app bootstrap.
300
-
301
- ---
302
-
303
- ## Testing
304
-
305
- ```bash
306
- npm test # run the vitest suite
307
- npm test -- --ui # interactive UI
308
- ```
309
-
310
- Coverage spans authentication flows (`AuthMode` detection, OAuth, SAML, credentials), all API endpoints, error handling, the filter builder, metadata parsing, date utilities, and SQL helpers.
311
-
312
- ---
313
-
314
- ## Development
315
-
316
- ```bash
317
- npm run build # production build (vite + tsc)
318
- npm run watch # rebuild on change
319
- npm run dev:pack # build + pack locally
320
- ```
321
-
322
- ---
323
-
324
- ## Contributing
325
-
326
- 1. All tests must pass.
327
- 2. Lint with [Biome](https://biomejs.dev/) — `npx @biomejs/biome check --write .`
328
- 3. Follow [Conventional Commits](https://www.conventionalcommits.org/).
329
- 4. New public API → add tests + JSDoc.
330
-
331
- ---
332
-
333
- ## License
334
-
335
- MIT — see [LICENSE](./LICENSE).
336
-
337
- ---
338
-
339
- <div align="center">
340
-
341
- Built for the [Sinequa](https://www.sinequa.com/) ecosystem · [Report an issue](https://github.com/Product/sba-atomic-js/issues)
342
-
343
- </div>
1
+ <div align="center">
2
+
3
+ <img src="https://img.shields.io/npm/v/@sinequa/atomic?style=for-the-badge&color=0ea5e9&label=@sinequa%2Fatomic" alt="npm version" />
4
+ <img src="https://img.shields.io/badge/TypeScript-7.x-3178c6?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript" />
5
+ <img src="https://img.shields.io/badge/ESM-ready-f59e0b?style=for-the-badge" alt="ESM" />
6
+ <img src="https://img.shields.io/badge/SPFx-ready-0078d4?style=for-the-badge&logo=microsoftsharepoint&logoColor=white" alt="SPFx" />
7
+
8
+ <br /><br />
9
+
10
+ # `@sinequa/atomic`
11
+
12
+ **The TypeScript-first SDK for the Sinequa REST API.**
13
+ Authentication, search, aggregations, datasets, preview — everything typed, tree-shakable, zero runtime dependencies.
14
+ SPFx-ready via the `@sinequa/atomic/spfx` subpath export.
15
+
16
+ [Docs](./docs/index.mdx) · [Get started](./docs/start/index.mdx) · [Reference](./docs/reference/index.mdx)
17
+
18
+ </div>
19
+
20
+ ---
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install @sinequa/atomic
26
+ ```
27
+
28
+ > Requires **Node ≥ 18** or any modern browser. Ships as ESM (`atomic.js`) and CJS (`atomic.cjs`) with full `.d.ts` declarations.
29
+
30
+ For **SharePoint Framework** web parts, `@microsoft/sp-http` is an optional peer dependency — install it only if you need it:
31
+
32
+ ```bash
33
+ npm install @microsoft/sp-http --save-peer
34
+ ```
35
+
36
+ ---
37
+
38
+ ## At a glance
39
+
40
+ ```typescript
41
+ import { setGlobalConfig, isAuthenticated, login, fetchQuery } from '@sinequa/atomic';
42
+
43
+ setGlobalConfig({ app: 'my-app', backendUrl: 'https://my-sinequa-server.example.com' });
44
+
45
+ if (!isAuthenticated()) {
46
+ await login({ username: 'alice', password: 's3cr3t' });
47
+ }
48
+
49
+ // A query is a plain object; only `name` — the query web service — is required.
50
+ const result = await fetchQuery({ name: '<your-query>', text: 'knowledge management', pageSize: 10 });
51
+
52
+ // One round-trip: the documents are in `records`, the facet counts in `aggregations`.
53
+ // Check `$error` before concluding anything from an empty `records`.
54
+ const { records, aggregations, $error } = result;
55
+ ```
56
+
57
+ `fetchAggregation(aggregation, query)` is for **paging** an aggregation you already received — it
58
+ takes the `Aggregation` object off `result.aggregations` and the same query, as two positional
59
+ arguments. See [Aggregations](./docs/search/aggregations.mdx).
60
+
61
+ ---
62
+
63
+ ## SharePoint Framework (SPFx)
64
+
65
+ In an SPFx web part, all HTTP requests must go through the `AadHttpClient` so the Azure AD bearer
66
+ token is attached automatically. Import from the `./spfx` subpath and call
67
+ `initializeAadHttpClient` once during web part initialization:
68
+
69
+ ```typescript
70
+ import { initializeAadHttpClient, fetchQuery } from '@sinequa/atomic/spfx';
71
+ import { AadHttpClient } from '@microsoft/sp-http';
72
+
73
+ // In your web part's onInit():
74
+ const client = await this.context.aadHttpClientFactory
75
+ .getClient('https://my-sinequa-server.example.com');
76
+ initializeAadHttpClient(client);
77
+
78
+ // All subsequent API calls route through AadHttpClient automatically:
79
+ const results = await fetchQuery({ name: '<your-query>', text: 'knowledge management' });
80
+ ```
81
+
82
+ Call `initializeAadHttpClient(null)` to detach the client (e.g. in `onDispose`).
83
+
84
+ > The `./spfx` subpath re-exports everything from `@sinequa/atomic` and adds
85
+ > `initializeAadHttpClient`, `aadHttpClientManager`, `createSpfxHttpAdapter`, `AadHttpClientLike`
86
+ > and `AadHttpResponseLike`. `initializeAadHttpClient` registers the client globally for the free
87
+ > functions; `createSpfxHttpAdapter(aadHttpClient)` is the same transport as an `httpAdapter` for
88
+ > `createAtomicClient`. Standard (non-SPFx) consumers use `@sinequa/atomic` directly — no changes
89
+ > needed. See [SPFx](./docs/integration/spfx.mdx).
90
+
91
+ ---
92
+
93
+ ## Modules
94
+
95
+ ### Client (experimental)
96
+
97
+ `createAtomicClient(config)` returns an isolated client — its own configuration, session and event
98
+ subscribers — exposing `client.config`, `client.configure()`, `client.initializeConfig()`,
99
+ `client.auth`, `client.http` and `client.api` (the typed endpoints, e.g.
100
+ `client.api.query.search(query)`). Several clients can target different backends from the same page,
101
+ and each takes its transport and auth knobs as configuration (`httpAdapter`, and
102
+ `auth: { fetch, storage, logger }`), which removes the need for module mocks in tests.
103
+
104
+ ```typescript
105
+ import { createAtomicClient } from '@sinequa/atomic';
106
+
107
+ const client = createAtomicClient({ app: 'myapp', backendUrl: 'https://backend' });
108
+ client.auth.on('unauthorized', () => client.auth.login());
109
+ const results = await client.http.post('api/v1/search.query', { text: 'hello' });
110
+ ```
111
+
112
+ The free functions below keep working unchanged: they run on a default client whose configuration
113
+ **is** `globalConfig`, and `getDefaultClient()` hands back that very instance — useful from code
114
+ that cannot receive one (a distributed web component, a plugin).
115
+ See [The client](./docs/core/client.mdx) and [Multiple backends](./docs/integration/multiple-backends.mdx).
116
+
117
+ ### Authentication
118
+
119
+ Full auth lifecycle — from first handshake to logout.
120
+
121
+ | Export | Description |
122
+ |---|---|
123
+ | `isAuthenticated()` | Synchronous. `true` when a CSRF token is stored **or** a token-less session was recorded (ambient SSO issues no client token) |
124
+ | `hasSession()` | Asks the server when nothing is stored, and never redirects — unlike `login()` |
125
+ | `login(credentials?)` | Login — switches on the resolved `authMode` (credentials / SSO / OAuth / SAML / bearer / unknown) |
126
+ | `logout()` | Clears session, returns the server's `redirectUrl` |
127
+ | `clearSessionTokens()` | Drops the local session (tokens, token-less flag) and emits `authenticated: false`, without a server round-trip |
128
+ | `detectAuthMode(config, preLogin?)` | Pure resolution of the `AuthMode` from config + server pre-login (no I/O) |
129
+ | `getToken()` / `setToken(token)` | Read/write the stored CSRF token |
130
+ | `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 |
131
+ | `getCsrfToken()` | CSRF token for mutating requests |
132
+ | `deleteWebTokenCookie()` | Explicitly removes the web token cookie |
133
+ | `onAuthEvent(event, handler)` | Typed subscription to the auth lifecycle (`authenticated`, `unauthorized`, `requestFailed`, `tokenRefreshed`, `loggedOut`) — returns the unsubscribe function |
134
+ | `tryAutoAuthentication()` | Probes a protected endpoint for server-side auto-authentication (OIDC, IIS Negotiate) — used by `login()` in `sso`, `unknown` and `bearer` modes. Never throws |
135
+ | `isRedirectPending()` | Whether an OAuth/SAML redirect is in flight and has not established a session yet |
136
+ | `tryOAuthAuthentication()` / `trySAMLAuthentication()` | Redirect to the configured identity provider (with a one-shot redirect-loop guard) |
137
+ | `emitAuthenticatedEvent(bool)` | Dispatches the DOM `'authenticated'` CustomEvent (on `document`, bubbles to `window`) |
138
+
139
+ > `getJWToken` is **`@deprecated`**: it is an alias of `requestWebTokenSession`, renamed because the
140
+ > old name suggested it returned the JWT. It will be removed in the next major.
141
+
142
+ The authentication layer is configurable via `setGlobalConfig({ auth: { … } })` (`AuthOptions`):
143
+ `timeoutMs`, `probeEndpoint`, injectable `storage`/`logger`/`fetch`, the requests' `credentials`
144
+ mode, and `recoverFromUnauthorized` — the hook that re-establishes a session after a 401 and gets
145
+ the request replayed once. See [Authentication](./docs/auth/index.mdx) and
146
+ [Handling 401](./docs/auth/handling-401.mdx).
147
+
148
+ ```typescript
149
+ import { isAuthenticated, login, logout } from '@sinequa/atomic';
150
+
151
+ if (!isAuthenticated()) {
152
+ // Auto-resolve: reuse an existing session if any, otherwise follow the resolved
153
+ // `authMode` — in `unknown` mode, probe for SSO then fall back to the credentials form.
154
+ const ok = await login();
155
+
156
+ // — or — explicit credentials, which bypass mode detection entirely.
157
+ const okWithCredentials = await login({ username: 'alice', password: 's3cr3t' });
158
+ }
159
+
160
+ const redirectUrl = await logout();
161
+ ```
162
+
163
+ #### Auth mode (`AuthMode`) — **2.0**
164
+
165
+ `globalConfig.authMode` is the **single source of truth** for the authentication method. It is a
166
+ typed discriminated union — no more juggling overlapping booleans:
167
+
168
+ ```typescript
169
+ import { AuthMode, globalConfig, setGlobalConfig } from '@sinequa/atomic';
170
+
171
+ setGlobalConfig({ authMode: AuthMode.credentials() }); // username/password form
172
+ setGlobalConfig({ authMode: AuthMode.sso() }); // browser/proxy-injected auth
173
+ setGlobalConfig({ authMode: AuthMode.oauth('my-provider') }); // redirect to an OAuth provider
174
+ setGlobalConfig({ authMode: AuthMode.saml('my-provider') }); // redirect to a SAML provider
175
+ setGlobalConfig({ authMode: AuthMode.bearer(token) }); // server-to-server bearer token
176
+ setGlobalConfig({ authMode: AuthMode.unknown() }); // try SSO, then fall back to credentials
177
+
178
+ // `login()` and `detectAuthMode()` branch on `authMode.kind`. Every key of `globalConfig` is
179
+ // optional, so read it through `?.` — the default is `AuthMode.unknown()`.
180
+ const kind = globalConfig.authMode?.kind; // 'credentials' | 'sso' | 'oauth' | 'saml' | 'bearer' | 'unknown'
181
+ ```
182
+
183
+ > **Migrating from 1.x:** the booleans `useCredentials` / `useSSO` / `useSAML` are now
184
+ > **`@deprecated` read-only getters** derived from `authMode`. Reading them still works, and writing
185
+ > them through `setGlobalConfig({ useSSO: true })` is translated into the matching `authMode` — so
186
+ > existing code keeps working. Prefer `authMode.kind` (and the `AuthMode.*` constructors) in new code.
187
+ > Full upgrade notes: [Migrating from 1.x](./docs/reference/migrating-from-1x.mdx).
188
+
189
+ ---
190
+
191
+ ### Web API — v1
192
+
193
+ Every Sinequa v1 endpoint has a typed wrapper.
194
+
195
+ ```typescript
196
+ import {
197
+ fetchApp,
198
+ fetchAppPreLogin,
199
+ fetchQuery,
200
+ fetchBulkQuery,
201
+ fetchAggregation,
202
+ fetchDataset,
203
+ fetchPreview,
204
+ fetchPrincipal,
205
+ fetchSimilarDocuments,
206
+ fetchSponsoredLinks,
207
+ fetchSuggest,
208
+ fetchTextChunks,
209
+ fetchUserSettings,
210
+ fetchLabels,
211
+ fetchQueryExport,
212
+ fetchQueryIntent,
213
+ searchPrincipals,
214
+ } from '@sinequa/atomic';
215
+ ```
216
+
217
+ Auditing is not a `fetch*` function: use `Audit.notify(...)`, plus the record decorators
218
+ `addAuditAdditionalInfo`, `addSessionId` and `addUrl`. See [Audit](./docs/integration/audit.mdx).
219
+
220
+ ### Web API — v2
221
+
222
+ ```typescript
223
+ import {
224
+ fetchUserProfile,
225
+ fetchVersion,
226
+ fetchChangePassword,
227
+ fetchSendPasswordResetEmail,
228
+ } from '@sinequa/atomic';
229
+ ```
230
+
231
+ The full endpoint list — with the `client.api` method and the free function for each — is in
232
+ [Endpoints](./docs/reference/endpoints.mdx).
233
+
234
+ ---
235
+
236
+ ### Helpers
237
+
238
+ Utilities for transforming Sinequa data structures.
239
+
240
+ ```typescript
241
+ import {
242
+ getMetadata, // metadata of an article as `string[]` (splits comma-separated values)
243
+ getMetadataWithValues, // same, as `{ display, value }[]`
244
+ makeColumn,
245
+ extraColumns,
246
+ resolveToColumnName,
247
+ resultPages,
248
+ escapeExpr,
249
+ guid,
250
+ isObject,
251
+ } from '@sinequa/atomic';
252
+ ```
253
+
254
+ More in [Helpers](./docs/reference/helpers.mdx).
255
+
256
+ #### Filter builder
257
+
258
+ Build structured `query.filters` without hand-writing filter objects. Combinators ignore falsy
259
+ operands and collapse trivial cases (0 → `undefined`, 1 → the operand itself), so filters compose
260
+ cleanly from optional UI state:
261
+
262
+ ```typescript
263
+ import { filter, fetchQuery } from '@sinequa/atomic';
264
+
265
+ const filters = filter.and(
266
+ filter.gt('size', 1000),
267
+ filter.or(
268
+ filter.eq('treepath', '/HR/*'),
269
+ filter.in('authors', ['alice', 'bob']),
270
+ ),
271
+ onlyRecent && filter.between('modified', '2024-01-01', '2024-12-31'),
272
+ );
273
+
274
+ await fetchQuery({ name: '<your-query>', text: 'report', filters });
275
+ ```
276
+
277
+ Leaf operators: `eq` · `neq` · `gt` · `gte` · `lt` · `lte` · `like` · `contains` · `regex` · `isNull` · `isNotNull` · `in` · `between`. Combinators: `and` · `or` · `not`.
278
+
279
+ `filter.distribution(field, item.value)` handles the one case the leaf operators cannot: a
280
+ **distribution** aggregation item (a date or size bucket), whose `value` is an expression the server
281
+ built rather than a value. Passing it to `filter.eq` makes the backend answer
282
+ `500 — "Field type error"`. See [Filters](./docs/search/filters.mdx).
283
+
284
+ ---
285
+
286
+ ### Utilities
287
+
288
+ ```typescript
289
+ import {
290
+ bisect,
291
+ sysLang, // resolves `all[fr]tous[de]alle` against a locale
292
+ getRelativeDate, // and `getOffsetFromDates`
293
+ getQueryParamsFromUrl, // and `getUrlParamsFromQueryParams`, `getFiltersFromUrl`, …
294
+ addConcepts, // and `getConcepts`, `parseText`, `rewriteText`, `removeConcept(s)`
295
+ notify,
296
+ configureLogger, // and the `debug` / `info` / `warn` / `error` bindings, `LogLevel`
297
+ } from '@sinequa/atomic';
298
+ ```
299
+
300
+ > The library's verbosity is set with `configureLogger({ level: LogLevel.DEBUG })`, **not** through
301
+ > a configuration key. See [Logging](./docs/integration/logging.mdx) and
302
+ > [URL state](./docs/search/url-state.mdx).
303
+
304
+ ---
305
+
306
+ ## Types
307
+
308
+ The package re-exports every Sinequa domain type so you never need to cast:
309
+
310
+ ```typescript
311
+ import type {
312
+ CCApp, CCQuery, CCColumn, CCIndex,
313
+ Aggregation, AggregationItem, ListAggregation, TreeAggregation,
314
+ Filter, SimpleFilter, InFilter, BetweenFilter, ExprFilter, NullFilter, NotNullFilter,
315
+ AuthMode, AuthModeKind,
316
+ PreviewData,
317
+ Principal,
318
+ AuditEvent, AuditRecord,
319
+ TextChunk,
320
+ } from '@sinequa/atomic';
321
+ ```
322
+
323
+ Which type comes back from which call: [Type map](./docs/reference/types-map.mdx).
324
+
325
+ ---
326
+
327
+ ## Configuration
328
+
329
+ Set the backend URL before making any API call (required when the Sinequa server is not
330
+ same-origin). `setGlobalConfig` performs a shallow merge into `globalConfig`:
331
+
332
+ ```typescript
333
+ import { setGlobalConfig, globalConfig, AuthMode } from '@sinequa/atomic';
334
+
335
+ setGlobalConfig({
336
+ app: 'my-app',
337
+ backendUrl: 'https://my-sinequa-server.example.com',
338
+ authMode: AuthMode.unknown(), // optional — defaults to `unknown`
339
+ });
340
+
341
+ // read it back — every key is optional on `globalConfig`, so `?.` is required
342
+ console.log(globalConfig.backendUrl, globalConfig.authMode?.kind);
343
+ ```
344
+
345
+ Call `initializeAppConfig()` once at bootstrap: it fills in `backendUrl`/`app` from the browser URL
346
+ when they were not provided, then resolves the `authMode` from the server's pre-login response.
347
+
348
+ > `appInitializerFn` is a **`@deprecated`** alias of `initializeAppConfig` — use the latter.
349
+
350
+ Three declared keys are read by nothing, and setting them has no effect: `logLevel` (set the level
351
+ with `configureLogger({ level })`), `loginPath` (routing is yours — use `recoverFromUnauthorized` or
352
+ the `unauthorized` event) and `createRoutes`. Every key, with its default:
353
+ [Configuration keys](./docs/reference/config-keys.mdx).
354
+
355
+ ---
356
+
357
+ ## Testing
358
+
359
+ ```bash
360
+ npm test # vitest, watch mode
361
+ npm test -- --watch=false # single run (what CI does)
362
+ ```
363
+
364
+ `--ui` is not available out of the box: `@vitest/ui` is an optional peer of vitest and is not
365
+ installed here — `npm i -D @vitest/ui` first.
366
+
367
+ Coverage is deepest on authentication (`AuthMode` detection, OAuth, SAML, credentials, tokens,
368
+ 401 recovery) and on the HTTP helpers. On the endpoints it is targeted rather than exhaustive —
369
+ `fetchQuery`, `fetchAggregation`, `fetchPrincipal`, `searchPrincipals`, `fetchApp` and the
370
+ document-upload containers. Also covered: the filter builder, metadata parsing, column resolution,
371
+ pagination, date utilities and the SQL value helper.
372
+
373
+ Injecting `fetch`, `storage` and `logger` through the configuration is what lets a test avoid module
374
+ mocks — see [Testing](./docs/integration/testing.mdx).
375
+
376
+ ---
377
+
378
+ ## Development
379
+
380
+ ```bash
381
+ npm run build # production build (vite + tsc)
382
+ npm run watch # rebuild on change
383
+ npm run dev:pack # build + pack locally
384
+ ```
385
+
386
+ ---
387
+
388
+ ## Contributing
389
+
390
+ 1. All tests must pass.
391
+ 2. Lint with [Biome](https://biomejs.dev/) — `npx @biomejs/biome check --write .`
392
+ 3. Follow [Conventional Commits](https://www.conventionalcommits.org/).
393
+ 4. Any change touching `src/` needs a **changeset** in the same commit (`npm run changeset`, then
394
+ `npm run changeset:status` to check) — the `changeset-check` CI job blocks the merge without one.
395
+ Add `#skip-changeset` to the MR title for a change that does not touch the published library.
396
+ Full guide, with examples: [`docs/CONTRIBUTING-changesets.md`](./docs/CONTRIBUTING-changesets.md).
397
+ 5. New public API → add tests + JSDoc, and export it from `src/index.ts` (a helper that is written
398
+ and tested but not re-exported from the barrel is not part of the public API).
399
+
400
+ ---
401
+
402
+ <div align="center">
403
+
404
+ Built for the [Sinequa](https://www.sinequa.com/) ecosystem · [Report an issue](https://gitlab.chapsvision.in/sinequa/rnd/sba-atomic-js/-/issues)
405
+
406
+ </div>