---
title: "JS/TS SDK"
canonical: https://docs.qvac.tether.io/sdk/v0.18/js-ts-sdk/
collection: "SDK"
package: "@qvac/sdk"
line: v0.18
current_line: false
---

# JS/TS SDK (/sdk/v0.18/js-ts-sdk)



import { TrackCopy } from '@/components/track-copy'

## Overview

The JS/TS SDK (`@qvac/sdk` on npm) is the JavaScript/TypeScript client for QVAC.
It drives a Bare worker over `bare-rpc`, and exposes every
QVAC capability through a unified, type-safe API.

## Requirements

* Node.js `>= v22.17`
* npm `>= v10.9`
* [Bare](https://bare.pears.com) `>= v1.24` (if targeting Bare)
* [Expo](https://expo.dev) `>= v54` (if targeting mobile)

For host-level requirements (OS versions, GPU drivers, Vulkan runtime), see [System requirements](/sdk/v0.18/system-requirements).

## Install

<TrackCopy name="npm_install">
  ```bash
  npm i @qvac/sdk
  ```
</TrackCopy>

### Expo

<Steps>
  <Step>
    Install peer dependencies:

    ```bash
    npm i 'react-native-bare-kit@^0.11.5'
    npm i -D 'bare-pack@^1.5.1'
    npx expo install expo-file-system expo-build-properties expo-device
    ```

    <Callout type="success">
      **Tip:** use `npx expo install` for all `expo-*` packages to ensure compatibility with your project's Expo SDK version.
    </Callout>
  </Step>

  <Step>
    Configure `expo-build-properties` and add `@qvac/sdk/expo-plugin` to the `plugins` array in your `app.json`:

    ```json title="app.json"
    {
      "expo": {
        "plugins": [
          ["expo-build-properties", { // [!code ++]
            "android": { "minSdkVersion": 29 } // [!code ++]
          }], // [!code ++]
          "@qvac/sdk/expo-plugin" // [!code ++]
        ]
      }
    }
    ```

    Speech addons (`tts-ggml`, `asr-ggml`, `audiogen-ggml`) ship Android and iOS binaries in a separate package. If you enable those plugins, add that package as a direct dependency, pinned to the same exact version as the JavaScript package — for example `"@qvac/tts-ggml": "x.y.z"` and `"@qvac/tts-ggml-android-arm64": "x.y.z"` (iOS uses `@qvac/tts-ggml-ios`). Llama does not need an extra package. `npx expo prebuild` names the missing pin if you forget.
  </Step>

  <Step>
    Prebuild your project to generate the native files:

    ```bash
    npx expo prebuild
    ```
  </Step>

  <Step>
    Build and run it on a **physical device**:

    ```bash
    npx expo run:ios --device
    # or
    npx expo run:android --device
    ```
  </Step>
</Steps>

<Callout type="info">
  Due to limitations with `llamacpp`, QVAC currently does not run on emulators.
  You **must** use a physical device.
</Callout>

## Quickstart

Run your first example using the JS/TS SDK.

<Steps>
  <Step>
    Create the examples workspace:

    ```bash
    mkdir qvac-examples
    cd qvac-examples
    npm init -y && npm pkg set type=module
    ```
  </Step>

  <Step>
    Install the SDK:

    <TrackCopy name="npm_install">
      ```bash
      npm i @qvac/sdk
      ```
    </TrackCopy>
  </Step>

  <Step>
    Save this config in your workspace to enable client and server logs during the run:

    ```json file=<rootDir>/packages/sdk/examples/config/default/default.config.json title="qvac.config.json"
    {
      "loggerLevel": "info",
      "loggerConsoleOutput": true,
      "httpDownloadConcurrency": 3,
      "httpConnectionTimeoutMs": 10000
    }
    ```
  </Step>

  <Step>
    Create the quickstart script:

    <WrapCode>
      ```js file=<rootDir>/packages/sdk/dist/examples/quickstart.js title="quickstart.js" lineNumbers
      // The SDK prints no logs by default. To see its client and server logs, run with
      // QVAC_CONFIG_PATH pointing at a config that sets "loggerConsoleOutput": true
      // (see the Quickstart docs).
      import { loadModel, LLAMA_3_2_1B_INST_Q4_0, completion, unloadModel } from '@qvac/sdk';
      try {
          // Load a model into memory
          const modelId = await loadModel({
              modelSrc: LLAMA_3_2_1B_INST_Q4_0,
              onProgress: (p) => {
                  const mb = (n) => (n / 1e6).toFixed(1);
                  const line = `▸ Downloading ${p.percentage.toFixed(0)}% (${mb(p.downloaded)}/${mb(p.total)} MB)`;
                  process.stderr.write(process.stderr.isTTY ? `\r${line}` : `${line}\n`);
                  if (p.percentage >= 100)
                      process.stderr.write('\n');
              }
          });
          // You can use the loaded model multiple times
          const history = [
              {
                  role: 'user',
                  content: 'Explain quantum computing in one sentence'
              }
          ];
          const result = completion({ modelId, history, stream: true });
          for await (const token of result.tokenStream) {
              process.stdout.write(token);
          }
          // Unload model to free up system resources
          await unloadModel({ modelId });
      }
      catch (error) {
          console.error('✖', error);
          process.exit(1);
      }
      ```
    </WrapCode>
  </Step>

  <Step>
    Run the quickstart script:

    ```bash
    QVAC_CONFIG_PATH=./qvac.config.json node quickstart.js
    ```

    Or on [Bare](https://bare.pears.com) with `@qvac/inference` — see [Running on Bare](#running-on-bare).
  </Step>
</Steps>

## Running examples

Follow these instructions to run any example in this documentation:

* All examples are self-contained, runnable JavaScript scripts. Use the `qvac-examples` workspace created in this quickstart to store and run them as you explore this documentation.
* Run each example with the indicated compatible JavaScript environment. QVAC supports Node.js, Expo, and in-process Bare via `@qvac/inference`. The examples are written in Node style and run on Node.js or Bun directly; to run them on Bare, see [Running on Bare](#running-on-bare).
* More examples can be found in the [SDK examples directory](https://github.com/tetherto/qvac/tree/main/packages/sdk/examples).
* Some examples need companion files — sample audio, an image, or a config — that aren't part of the embedded code. These can be found in the same examples directory.
* Some examples also provide a TypeScript version. If you want to run TS directly, install the required dev dependencies:
  ```bash
  npm i -D tsx typescript
  ```

## Configuration

The SDK reads `qvac.config.*` from the project root, or from the path in `QVAC_CONFIG_PATH`:

```bash
QVAC_CONFIG_PATH=./qvac.config.json node app.js
```

See [Configuration](/sdk/v0.18/configuration) for options and schema.

### File formats

The `qvac.config.*` format you can use depends on the JS environment where QVAC runs:

| Format             | Node.js | Bare | Expo |
| ------------------ | ------- | ---- | ---- |
| `qvac.config.json` | ✅       | ✅    | ✅    |
| `qvac.config.js`   | ✅       | ✅    | ❌    |
| `qvac.config.ts`   | ✅       | ❌    | ❌    |

<Callout type="info">
  `qvac.config.ts` requires installing [`tsx`](https://www.npmjs.com/package/tsx) as a devDependency.
</Callout>

<Tabs>
  <Tab value="js" label="JavaScript" default>
    <Callout type="warn">
      Supported on Node.js and Bare. Not supported in Expo — use JSON instead.
    </Callout>

    <WrapCode>
      ```js title="qvac.config.js" lineNumbers
      module.exports = {
        plugins: ["<builtin_plugin_1>", "<custom_plugin_2>"],
        loggerConsoleOutput: true,
        loggerLevel: "info",
        swarmRelays: ["<hyperbee_key_1>", "<hyperbee_key_2>"],
        cacheDirectory: "</absolute/path/to/.qvac/models>",
        httpDownloadConcurrency: 3,
        httpConnectionTimeoutMs: 10000,
        registryDownloadMaxRetries: 3,
        registryStreamTimeoutMs: 60000,
        rpcInitTimeoutMs: 30000,
        deviceDefaults: [
          {
            name: "Samsung Galaxy force CPU",
            match: { platform: "android", deviceBrand: "samsung" },
            defaults: { llm: { device: "cpu" } },
          },
        ],
        bareRuntimeVersion: "<x.y.z>",
        serve: {
          models: {
            "<model_alias>": {
              model: "<SDK_MODEL_CONSTANT>",
              default: true,
              preload: true,
              config: {},
            },
          },
        },
      };
      ```
    </WrapCode>
  </Tab>

  <Tab value="ts" label="TypeScript">
    <Callout type="info">
      Supported on Node.js only, and requires installing [`tsx`](https://www.npmjs.com/package/tsx) as a devDependency.
    </Callout>

    <WrapCode>
      ```ts title="qvac.config.ts" lineNumbers
      import type { QvacConfig } from "@qvac/sdk";

      const config: QvacConfig = {
        plugins: ["<builtin_plugin_1>", "<custom_plugin_2>"],
        loggerConsoleOutput: true,
        loggerLevel: "info",
        swarmRelays: ["<hyperbee_key_1>", "<hyperbee_key_2>"],
        cacheDirectory: "</absolute/path/to/.qvac/models>",
        httpDownloadConcurrency: 3,
        httpConnectionTimeoutMs: 10000,
        registryDownloadMaxRetries: 3,
        registryStreamTimeoutMs: 60000,
        rpcInitTimeoutMs: 30000,
        deviceDefaults: [
          {
            name: "Samsung Galaxy force CPU",
            match: { platform: "android", deviceBrand: "samsung" },
            defaults: { llm: { device: "cpu" } },
          },
        ],
        bareRuntimeVersion: "<x.y.z>",
        serve: {
          models: {
            "<model_alias>": {
              model: "<SDK_MODEL_CONSTANT>",
              default: true,
              preload: true,
              config: {},
            },
          },
        },
      };

      export default config;
      ```
    </WrapCode>
  </Tab>
</Tabs>

## Running on Bare

To run an example on Bare:

1. Provide a `process` global. Install `bare-process` and set it before using `@qvac/inference`:
   ```js
   import process from "bare-process";
   globalThis.process = process;
   ```
2. Register the plugins the example uses — Bare runs in-process and nothing auto-registers. See [Runtime registration on Bare](/sdk/v0.18/configuration/plugins#runtime-registration-on-bare).

Here is the SDK quickstart adapted for Bare with `@qvac/inference`:

```ts file=<rootDir>/packages/inference/examples/quickstart-adapted.ts title="quickstart-adapted.ts" lineNumbers
// The SDK's quickstart, adapted for Bare with @qvac/inference. Three edits: import
// from `@qvac/inference` instead of `@qvac/sdk`, import `process` from bare-process
// (Bare has no `process` global), and register the plugins this example uses
// via `plugins([...])`.

import process from 'bare-process'
import { plugins, LLAMA_3_2_1B_INST_Q4_0 } from '@qvac/inference'
import { llmPlugin } from '@qvac/inference/llamacpp-completion/plugin'

const { loadModel, completion, unloadModel } = plugins([llmPlugin])

// From here it is the same as the SDK's quickstart.
const modelId = await loadModel({ modelSrc: LLAMA_3_2_1B_INST_Q4_0 })

const history = [{ role: 'user', content: 'Explain quantum computing in one sentence' }]
const result = completion({ modelId, history, stream: true })
for await (const token of result.tokenStream) {
  process.stdout.write(token)
}

await unloadModel({ modelId, autoClose: true })
```

For running QVAC on Bare in production, use **[@qvac/inference](https://github.com/tetherto/qvac/tree/main/packages/inference)** — the Bare-only in-process engine. Select addons and register plugins explicitly. `@qvac/bare-sdk` is deprecated; last release is 0.18.2.

## API reference

<Card href="/sdk/v0.18/reference/api" title="API reference">
  `@qvac/sdk` npm package exposes a function-centric, typed JS API.
</Card>
