@sinequa/atomic 2.4.0 → 2.5.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.
- package/README.md +406 -406
- package/dist/atomic.cjs +1 -1
- package/dist/atomic.js +2 -2
- package/dist/client/create-api.d.ts +3 -3
- package/dist/helpers/column-resolver.d.ts.map +1 -1
- package/dist/helpers/utils.d.ts +13 -3
- package/dist/helpers/utils.d.ts.map +1 -1
- package/dist/spfx/index.cjs +1 -1
- package/dist/spfx/index.js +2 -2
- package/dist/src-BcyahlCg.cjs +1 -0
- package/dist/{src-DQM5GZF-.js → src-vDryIxd0.js} +594 -567
- package/dist/types/search/Article.d.ts +8 -0
- package/dist/types/search/Article.d.ts.map +1 -1
- package/dist/types/search/DidYouMeanItem.d.ts +29 -1
- package/dist/types/search/DidYouMeanItem.d.ts.map +1 -1
- package/dist/types/search/QueryIntent.d.ts +8 -1
- package/dist/types/search/QueryIntent.d.ts.map +1 -1
- package/dist/types/search/QueryIntentDatasets.d.ts +0 -3
- package/dist/types/search/QueryIntentDatasets.d.ts.map +1 -1
- package/dist/types/search/index.d.ts +3 -1
- package/dist/types/search/index.d.ts.map +1 -1
- package/dist/utils/date.d.ts +10 -2
- package/dist/utils/date.d.ts.map +1 -1
- package/dist/utils/query-params.d.ts +20 -3
- package/dist/utils/query-params.d.ts.map +1 -1
- package/dist/web-api/helpers/handle-response.d.ts.map +1 -1
- package/dist/web-api/v1/fetch-labels.d.ts.map +1 -1
- package/dist/web-api/v1/fetch-preview.d.ts +11 -4
- package/dist/web-api/v1/fetch-preview.d.ts.map +1 -1
- package/dist/web-api/v1/fetch-query.d.ts +10 -3
- package/dist/web-api/v1/fetch-query.d.ts.map +1 -1
- package/dist/web-api/v2/fetch-forgot-password.d.ts +5 -1
- package/dist/web-api/v2/fetch-forgot-password.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/src-yvCcJDJS.cjs +0 -1
- package/dist/types/interfaces/json.d.ts +0 -7
- package/dist/types/interfaces/json.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -1,406 +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-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: [`
|
|
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>
|
|
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: [`CONTRIBUTING-changesets.md`](./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>
|