The Context Engine Studio is now open source.  View on GitHub →
Clinia

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:

Install the client.

npm install @clinia/context-engine-js

Create 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 healthy

The 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

FailureWhat the call does
The API answers with a non-2xx statusResolves with error set on the result
The request gets no responseThrows
The token request failsThrows an Error whose message starts with client_credentials token request failed

Handle a failed call

Every method resolves to an object with three fields.

FieldValue
dataThe parsed response body on a 2xx, otherwise undefined
errorThe parsed error body on a non-2xx, otherwise undefined
responseThe 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.

HookRunsCan return
onRequest({ request })Before each request is sentA modified Request to send instead, or a Response to skip the network
onResponse({ request, response })After each response arrives, whatever its statusA modified Response to use instead
onError({ request, error })When the request gets no responseA 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 with ERR_REQUIRE_ESM. Use import, or await import() from a CommonJS module.
  • A client created without auth starts 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 auth a function that gets the token from your own backend, as described in Share one token across clients.
  • The fetch you pass also sends the token request, so a mock that only answers API routes fails the first call unless the client runs without auth.

On this page