A custom transport
Replacing what actually sends the requests — for retries, tracing, or a host that owns the network.
Two levels of replacement, for two different needs:
| Replace | When | |
|---|---|---|
auth.fetch | The fetch function | Tests, a polyfill, a wrapper around the platform's own |
httpAdapter | The whole send step | A host SDK owns the network — SharePoint being the built-in case |
Both are configuration, not subclassing: hand in a function or an object and the library uses it.
Wrapping fetch
The lighter option. It stays a fetch, so everything downstream is unchanged — you see every request and every
response.
import { createAtomicClient } from "@sinequa/atomic";
/**
* A fetch that logs each request and retries once on a network failure.
*
* Note what it must not do: retry a 4xx or a 5xx. A 401 is the recovery hook's
* business, and a 500 usually means the request itself is wrong — replaying it
* only doubles the load.
*/
function tracedFetch(base: typeof globalThis.fetch): typeof globalThis.fetch {
return async (input, init) => {
const started = performance.now();
try {
const response = await base(input, init);
console.debug(`${init?.method ?? "GET"} ${String(input)} → ${response.status}`,
`${Math.round(performance.now() - started)}ms`);
return response;
} catch (error) {
// A transport-level failure only: DNS, connection reset, offline.
console.warn("network failure, retrying once:", error);
return base(input, init);
}
};
}
export const client = createAtomicClient({
app: "<your-app>",
backendUrl: "https://<your-sinequa-server>",
auth: {
fetch: tracedFetch(globalThis.fetch.bind(globalThis)),
},
});import { InjectionToken } from "@angular/core";
import { type AtomicClient, createAtomicClient } from "@sinequa/atomic";
/**
* A fetch that logs each request and retries once on a network failure.
*
* It must not retry a 4xx or a 5xx: a 401 is the recovery hook's business, and a
* 500 usually means the request itself is wrong.
*/
function tracedFetch(base: typeof globalThis.fetch): typeof globalThis.fetch {
return async (input, init) => {
const started = performance.now();
try {
const response = await base(input, init);
console.debug(
`${init?.method ?? "GET"} ${String(input)} → ${response.status}`,
`${Math.round(performance.now() - started)}ms`,
);
return response;
} catch (error) {
// A transport-level failure only: DNS, connection reset, offline.
console.warn("network failure, retrying once:", error);
return base(input, init);
}
};
}
export const SINEQUA = new InjectionToken<AtomicClient>("Sinequa client", {
factory: () =>
createAtomicClient({
app: "<your-app>",
backendUrl: "https://<your-sinequa-server>",
auth: { fetch: tracedFetch(globalThis.fetch.bind(globalThis)) },
}),
});import { createAtomicClient } from "@sinequa/atomic";
/**
* A fetch that logs each request and retries once on a network failure.
*
* It must not retry a 4xx or a 5xx: a 401 is the recovery hook's business, and a
* 500 usually means the request itself is wrong.
*/
function tracedFetch(base: typeof globalThis.fetch): typeof globalThis.fetch {
return async (input, init) => {
const started = performance.now();
try {
const response = await base(input, init);
console.debug(
`${init?.method ?? "GET"} ${String(input)} → ${response.status}`,
`${Math.round(performance.now() - started)}ms`,
);
return response;
} catch (error) {
// A transport-level failure only: DNS, connection reset, offline.
console.warn("network failure, retrying once:", error);
return base(input, init);
}
};
}
export const sinequa = createAtomicClient({
app: "<your-app>",
backendUrl: "https://<your-sinequa-server>",
auth: { fetch: tracedFetch(globalThis.fetch.bind(globalThis)) },
});Replacing the send step
httpAdapter goes further: the library builds the URL, the headers and the body, then hands the whole thing to
you. The contract is two types:
interface HttpAdapterRequest {
method: string;
headers: Headers;
body?: string;
signal?: AbortSignal;
}
interface HttpAdapter {
fetch(url: string, request: HttpAdapterRequest): Promise<AadHttpResponseLike | Response>;
}Return a real Response and everything downstream behaves normally. The union's other member exists for the
SharePoint SDK, whose responses are not Response instances.
import type { HttpAdapter } from "@sinequa/atomic";
const adapter: HttpAdapter = {
async fetch(url, { method, headers, body, signal }) {
// Whatever the host gives you, as long as it resolves to a Response.
return hostSdk.request({ url, method, headers, body, signal });
},
};
const client = createAtomicClient({ app: "<your-app>", httpAdapter: adapter });An adapter changes how the auth mode resolves
A configured transport is taken as evidence that requests authenticate themselves, so an ambiguous mode settles
as sso and noAutoAuthentication follows. That is right for SharePoint and wrong for an adapter you added for
logging or retries.
If your adapter does not authenticate, declare authMode explicitly.