

# JavaScript ADK

*The official JavaScript SDK for the [A Realtime Tech (ART)](https://www.arealtimetech.com) platform.*

[Website](https://www.arealtimetech.com) · [Documentation](https://docs.arealtimetech.com) · [About ART](https://www.arealtimetech.com/about)

---

## About ART

**A Realtime Tech (ART)** is a realtime application platform that powers low-latency messaging, presence, and collaborative state for modern apps. Build chat, live dashboards, multiplayer experiences, IoT control planes, and CRDT-backed shared documents on a single managed backbone — without standing up your own websocket, presence, or sync infrastructure. Learn more at [arealtimetech.com](https://www.arealtimetech.com).

## About the JavaScript ADK

The **JavaScript ADK (ART Development Kit)** gives your application the tools it needs to talk to ART services. It ships only the runtime needed to connect, subscribe, send, receive, and synchronise state — no separate dev/prod build is required, so you can drop the same package directly into production.

> **Note** — installing the package is not enough on its own. The ADK must also be authenticated using credentials issued by ART (see [Configuration](#configuration) below).

---

## Installation

```bash
npm install @arealtimetech/adk-js
```

---

## Configuration

The ADK supports **two ways** to provide credentials. Pick whichever fits your app.

### Option 1 — Load credentials from a JSON file (`AutoLoadCredsFromJSON`)

Set `AutoLoadCredsFromJSON: true` and the ADK will automatically fetch credentials from a JSON file at the configured path. This is the simplest setup and where you can place adk-services.json in the root of the project.

```js
import Adk from '@arealtimetech/adk-js';

const adk = new Adk({
  Uri: "",                       // ART service URL
  AutoLoadCredsFromJSON: true,   // load credentials from JSON
});

adk.connect();
```

**JSON file shape** (served at the configured config path):

```json
{
  "Client-ID":     "your-client-id",
  "Client-Secret": "your-client-secret",
  "Org-Title":     "your-org",
  "Environment":   "your-environment",
  "ProjectKey":    "your-project-key"
}
```

### Option 2 — Set credentials programmatically (`setCredentials`)

When `AutoLoadCredsFromJSON` is **off** (or omitted), you supply credentials in code via `setCredentials()`. Use this when credentials come from your own auth flow, an active user session, or any other runtime source.

```js
import Adk from '@arealtimetech/adk-js';

const adk = new Adk({
  Uri: "",   // ART service URL
});

adk.setCredentials({
  ClientID:     "your-client-id",
  ClientSecret: "your-client-secret",
  OrgTitle:     "your-org",
  Environment:  "your-environment",
  ProjectKey:   "your-project-key"
});

adk.connect();
```


| Field          | Type   | Required | Description                          |
| -------------- | ------ | -------- | ------------------------------------ |
| `ClientID`     | string | yes      | Client identifier issued by ART      |
| `ClientSecret` | string | yes      | Client secret paired with `ClientID` |
| `OrgTitle`     | string | yes      | Your organisation / tenant title     |
| `Environment`  | string | yes      | Target environment name              |
| `ProjectKey`   | string | yes      | Project key inside the environment   |


---

## Usage

### Establish and manage the connection

```js
import Adk from '@arealtimetech/adk-js';

const adk = new Adk({
  Uri: "",                      // service URL
  AuthToken: "",                // passcode generated by your server for the user
});

// Open the connection
adk.connect();

// Connection lifecycle
adk.on("open", async (event) => {
  console.log("ADK connection opened", event);
});

adk.on("close", () => {
  console.log("ADK connection closed");
});

// Tear down
adk.disconnect();
```

### Subscribe to a channel

`subscribe()` joins a specific channel. Once subscribed, the channel is a real-time stream where you can **send**, **receive**, listen to events, and observe **user presence**.

```js
const sub = await adk.subscribe("YOUR_CHANNEL_NAME");
```

### Track presence

`fetchPresence()` returns the list of users currently active in the channel.

```js
sub.fetchPresence((users) => {
  console.log("Active users:", users);
});
```

### Listen to all events

`listen()` is a catch-all — every payload published to the channel is delivered to your callback.

```js
sub.listen((data) => {
  console.log("Received:", data);
});
```

### Listen to a specific event

`bind()` filters by event name; the callback only fires for matching events.

```js
sub.bind("EVENT", (data) => {
  console.log("Event received:", data);
});
```

### Send messages

`push()` publishes an event to the channel. Pass `to` to deliver only to specific users.

```js
const payload = { message: "Hello!" };

sub.push("EVENT", payload, {
  to: ["username1", "username2"],   // optional — target specific users
});
```

---

## Threads on an orchestrator-enabled channel

On a channel that has orchestrator enabled, you can spin up isolated **threads** from a subscription. Each thread has its own id and only receives messages tagged with that id.

```js
const sub = await adk.subscribe("YOUR_CHANNEL");

const thread = sub.thread();
console.log(thread.threadId);
```

Pass an existing id to attach to a known thread (for example, one created by the server):

```js
const thread = sub.thread("thread_xxxxx");
```

### Listen to all events on the thread

```js
thread.listen((msg) => {
  console.log(msg.event, msg.content);
});
```

### Listen to a specific event

```js
thread.bind("status_update", (content) => {
  console.log(content);
});
```

### Send a message

The `thread_id` is attached automatically.

```js
await thread.push("user_input", { message: "hello" }, {
  to: ["username"],
});
```

### Stop listening

```js
thread.remove("status_update");          // remove all callbacks for the event
thread.remove("status_update", myFn);    // remove a single callback
```

### Close the thread

```js
thread.dispose();
```

---

## Orchestrator

`adk.orchestrator(id)` connects directly to an orchestrator. The SDK subscribes to the orchestrator, so you can go straight to threads.

```js
const orch = adk.orchestrator("your-orchestrator-id").connect();

const thread = await orch.thread();

thread.listen((msg) => {
  console.log(msg.event, msg.content);
});

await thread.push("user_input", { message: "hi" }, {
  to: ["username"],
});
```

Reuse an existing thread by id:

```js
const thread = await orch.thread("thread_xxxxx");
```

---

## Agent

`adk.agent(id)` connects directly to an agent. The SDK subscribes to the agent's.

```js
const agent = adk.agent("your-agent-id").connect();

const thread = agent.thread();

thread.listen((evt) => {
  console.log(evt.event, evt.content);
});

// Start a run with user input
const run = await thread.run("Hello agent");

// Await the final output
const output = await run.done();
console.log(output);
```

Reply to a follow-up question from the agent:

```js
await run.sendFeedback("yes, proceed");
```

---

## Shared Object Channel

A **Shared Object Channel** is a real-time, collaborative data structure backed by a **CRDT**. Multiple clients can update the same JSON tree concurrently and converge to a consistent state.

```js
// Subscribe like any other channel
const sub = await adk.subscribe("YOUR_SO_CHANNEL_NAME");
```

### Reading

#### Listen for live updates

Listen at a **path** inside the shared object — your callback receives plain JSON whenever that subtree changes.

```js
// path examples: "", "user", "user.profile", "todos"
const unsubscribe = await sub.query("user.profile").listen((data) => {
  console.log("profile updated:", data);
});

// later
unsubscribe();
```

**Path rules**

- `""` (empty) or `"index"` → whole document
- Use dot paths for objects (e.g. `user.profile.name`)
- For arrays, listen at the array path (e.g. `"todos"`). Item keys are internal; per-item paths are not stable.

#### Fetch once (no subscription)

Retrieve the current value at a path without subscribing for ongoing updates.

```js
const profile = await sub.query("user.profile").execute();
```

### Writing

`sub.state()` returns a live proxy. Mutate it like normal JavaScript — changes are batched and merged using CRDT rules. The proxy:

- Auto-creates missing parent objects/arrays on write
- Deletes safely (deleting a missing key is a no-op)
- Emits ops optimistically (UI updates immediately) and ships a compacted batch to the server

```js
// Get the live state proxy once and reuse it
const state = sub.state();

/* ---------- Objects ---------- */
state.user.profile.name = "Jane Doe";

// Safe delete (no error if the key doesn't exist)
delete state.settings.theme;

/* ---------- Arrays ---------- */
state.todos.push({ text: "one" }, { text: "two" });

// Mutate newly added items immediately
state.todos[0].text = "ONE";

// Replace item at index 1
state.todos.splice(1, 1, { text: "two-ish" });

// Pop last item (returns the removed value)
const last = state.todos.pop();

/* ---------- Flushing ---------- */
// The client batches & compacts ops automatically.
// Call flush() to force-send the current batch now.
await sub.flush();
```

**Array API (on any array path)**

- `push(...items)` / `pop()` / `unshift(...items)`
- `splice(start, deleteCount?, ...insert)`
- `insertAt(index, item)`
- `move(fromIndex, toIndex)`
- Numeric index get/set (`state.todos[0] = {...}`)
- `delete state.todos[i]` (remove at index)

> **Notes**
>
> - You cannot create sparse indices by assignment (e.g. `todos[5] = ...` when length is 1). Use `insertAt` / `splice`.
> - For newly pushed items you can mutate them immediately (optimistic local state).

---

## Documentation

Full API reference and guides: **[docs.arealtimetech.com](https://docs.arealtimetech.com)**

## Learn more

- **Website:** [arealtimetech.com](https://www.arealtimetech.com)
- **About ART:** [arealtimetech.com/about](https://www.arealtimetech.com/about)
- **Documentation:** [docs.arealtimetech.com](https://docs.arealtimetech.com)

---

Made with care by [A Realtime Tech](https://www.arealtimetech.com).