JavaScript Client
Install, call and configure @clinia/context-engine-js, the client for JavaScript and TypeScript.
@clinia/context-engine-js is the client for JavaScript and TypeScript. It runs on Node 20 or later and in modern browsers. Each of its methods returns a promise.
Install and authenticate
Every call to a workspace needs an access token. The client gets that token for you once you give it the client ID and secret of a credential.
You need three things before you start:
- A workspace and its URL,
https://<workspace-id>.w.clinia.cloud. See Organizations, Workspaces and Environments. - An OAuth client credential for that workspace, created in the Console. See Authentication and Credentials.
- Node 20 or later.
Install the client.
npm install @clinia/context-engine-jsCreate the client. Replace CLINIA_WORKSPACE_URL with the workspace URL and CLINIA_CLIENT_ID and CLINIA_CLIENT_SECRET with the credential's client ID and secret, and keep the secret out of source control.
import { ContextEngineClient } from "@clinia/context-engine-js";
const client = new ContextEngineClient({
baseUrl: "CLINIA_WORKSPACE_URL",
auth: {
clientId: "CLINIA_CLIENT_ID",
clientSecret: "CLINIA_CLIENT_SECRET",
},
});Check the connection by reading the service info, which returns the service name, its status (healthy when it can serve requests) and the version of the running build.
const { data, response } = await client.info.getServiceInfo();
console.log(response.status, data?.name, data?.status);
// 200 Clinia Context Engine healthyThe client requests an access token on the first call and requests a new one a minute before that token expires. Calls made while a token request is in flight wait for that same request.
Share one token across clients
A client keeps its token in its own memory, so every client you create requests a token of its own. To let several clients use the same token, create one token provider and pass it as auth to each client. The provider caches the token and renews it a minute before it expires.
Create the provider with createClientCredentialsTokenProvider.
import {
ContextEngineClient,
createClientCredentialsTokenProvider,
} from "@clinia/context-engine-js";
const baseUrl = "CLINIA_WORKSPACE_URL";
const getAccessToken = createClientCredentialsTokenProvider({
clientId: "CLINIA_CLIENT_ID",
clientSecret: "CLINIA_CLIENT_SECRET",
});
const ingestClient = new ContextEngineClient({ baseUrl, auth: getAccessToken });
const readClient = new ContextEngineClient({ baseUrl, auth: getAccessToken });A provider is a function that returns a token, and the client calls it before every request. To keep the token in a cache that several processes share, or to decide yourself when it is renewed, pass auth your own function instead. It should return the cached token and request a new one only when the cached token is about to expire.
/**
* Returns the cached access token, and requests a new one when it is about to expire.
* @returns {Promise<string>}
*/
async function getCachedAccessToken() {
// ...
}
const client = new ContextEngineClient({ baseUrl, auth: getCachedAccessToken });Test against a local stub
To run tests against a local stub or recorded responses, create the client without auth. It then sends no Authorization header and accepts an http:// URL.
const client = new ContextEngineClient({ baseUrl: "http://localhost:8000" });Call the API
Methods are grouped by resource on the client: info, patients, ingest, sessions, vfs and graph. The REST reference page of a method's operation documents its parameters, body and response.
Arguments
A method takes its arguments in this order:
- Path parameters come first, as positional arguments in the order they appear in the path, such as the patient ID and then the node type for
graph.listNodes. - The request body comes next, as one argument.
- Query parameters come last, as
{ query: { ... } }.
Methods
Every method has a page in the reference, under its resource, with the call and a link to the operation's REST reference page, which documents its parameters. For example, Get patient is patients.getPatient.
Operations still in Technical preview are marked as such in the REST reference. Changes to any operation are announced in the changelog.
Types
Each schema of the API is a type exported by name from @clinia/context-engine-js, such as Patient.
/**
* @param {import("@clinia/context-engine-js").Patient} patient
* @returns {string}
*/
export function describePatient(patient) {
return `${patient.id}, registered ${patient.createdAt}`;
}Responses and errors
A call can fail in three ways: the API answers with an error status, the request gets no response, or the client cannot get an access token.
Failures
| Failure | What the call does |
|---|---|
| The API answers with a non-2xx status | Resolves with error set on the result |
| The request gets no response | Throws |
| The token request fails | Throws an Error whose message starts with client_credentials token request failed |
Handle a failed call
Every method resolves to an object with three fields.
| Field | Value |
|---|---|
data | The parsed response body on a 2xx, otherwise undefined |
error | The parsed error body on a non-2xx, otherwise undefined |
response | The raw Response, for its status and headers |
Check error before you read data, which the type checker then knows is set.
const { data, error, response } = await client.patients.getPatient("patient-123");
if (error) {
console.error(response.status, error.type, error.message);
} else {
console.log(data.id, data.name);
}The error body
The client parses the error body into the ApiError type, whose fields and type values are documented in the API reference.
Customize the HTTP layer
Middleware and the fetch option give you control over how the client sends its requests, for logging, tracing, a proxy, a timeout or mocked responses in tests.
Log requests and add headers
client.http.use() adds a middleware object that runs on every request, and client.http.eject() removes it again. The example logs each request's method, URL and status.
/** @type {Parameters<ContextEngineClient["http"]["use"]>[0]} */
const logRequests = {
onResponse({ request, response }) {
console.log(request.method, request.url, response.status);
},
};
client.http.use(logRequests);A middleware object defines any of three hooks.
| Hook | Runs | Can return |
|---|---|---|
onRequest({ request }) | Before each request is sent | A modified Request to send instead, or a Response to skip the network |
onResponse({ request, response }) | After each response arrives, whatever its status | A modified Response to use instead |
onError({ request, error }) | When the request gets no response | A Response to use instead, or an Error to throw instead |
Send requests through a proxy
In Node, the client sends its requests through the global fetch, so a proxy set with undici's setGlobalDispatcher applies to every call and to the token request.
import { ProxyAgent, setGlobalDispatcher } from "undici";
setGlobalDispatcher(new ProxyAgent("http://proxy.example.com:3128"));Mock the API in tests
Pass the fetch option a function that returns the responses your test expects.
import { ContextEngineClient } from "@clinia/context-engine-js";
/** @type {typeof fetch} */
async function mockFetch() {
return Response.json({
name: "Clinia Context Engine",
status: "healthy",
version: { number: "1.0.0", buildSha: "unknown", buildDate: "unknown" },
});
}
const client = new ContextEngineClient({ baseUrl: "http://localhost:8000", fetch: mockFetch });A mocked client usually runs without auth, as described in Test against a local stub, so that it makes no token request.
Set a timeout
The client has no timeout option, so pass the fetch option a function that adds AbortSignal.timeout() to each request. A request that runs past the limit then makes the call throw.
import { ContextEngineClient } from "@clinia/context-engine-js";
const client = new ContextEngineClient({
baseUrl: "CLINIA_WORKSPACE_URL",
auth: {
clientId: "CLINIA_CLIENT_ID",
clientSecret: "CLINIA_CLIENT_SECRET",
},
fetch: (input, init) => fetch(input, { ...init, signal: AbortSignal.timeout(30_000) }),
});Gotchas
- The package is ESM-only, so
require()fails withERR_REQUIRE_ESM. Useimport, orawait import()from a CommonJS module. - A client created without
authstarts without error. Every call it then makes to a workspace returns a 401. - A client secret must never reach code that runs in a browser. In a browser, pass
autha function that gets the token from your own backend, as described in Share one token across clients. - The
fetchyou pass also sends the token request, so a mock that only answers API routes fails the first call unless the client runs withoutauth.