> ## Documentation Index
> Fetch the complete documentation index at: https://domoinc-openapi-sync-dataflows.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Domo.js (Ryuu.js)

**Version:** [v4](/portal/Apps/App-Framework/Tools/domo-js-v4) | **v5** | [v6 (latest)](/portal/Apps/App-Framework/Tools/domo-js)

> A powerful JavaScript SDK for building custom applications within the Domo platform

[![npm version](https://img.shields.io/badge/npm-v5.1.4-blue?logo=npm)](https://www.npmjs.com/package/ryuu.js/v/5.1.4)
[![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)

domo.js (published as `ryuu.js`, developed as `domo.js`) is a comprehensive JavaScript library that enables developers to build interactive custom applications within the Domo platform. It provides seamless communication between your custom app and the Domo environment, supporting data fetching, real-time events, filters, variables, and cross-platform mobile integration.

## Quick Start

Get started with ryuu.js in just a few lines:

```javascript theme={"dark"}
import Domo from 'ryuu.js';

// Fetch data from a dataset
const data = await Domo.get('/data/v1/sales');
console.log(data); // Array of objects with your data

// Listen for dataset updates
Domo.onDataUpdated((alias) => {
  console.log(`Dataset ${alias} was updated!`);
  // Refresh your visualization
});

// Listen for filter changes
Domo.onFiltersUpdated((filters) => {
  console.log('Filters changed:', filters);
  // Apply filters to your data
});
```

***

## Features

* **HTTP API Access** - Authenticated requests to Domo datasets, datastores, and APIs
* **Real-time Events** - Listen for dataset updates, filter changes, and variable updates
* **Filter Management** - Get and set page-level filters programmatically
* **Variable Management** - Access and update page variables
* **Custom App Data** - Send custom data between apps on the same page
* **Navigation** - Programmatically navigate within Domo
* **Mobile Support** - Full iOS and Android compatibility
* **TypeScript Ready** - Complete type definitions included
* **Zero Dependencies** - Minimal bundle size with no runtime dependencies
* **Extensible** - Override or extend functionality via `extend()` method

***

## Installation

### NPM

```bash theme={"dark"}
npm install ryuu.js
```

Then import in your application:

```javascript theme={"dark"}
// ES modules
import Domo from 'ryuu.js';

// CommonJS
const Domo = require('ryuu.js').default;
```

### CDN / Script Tag

```html theme={"dark"}
<script src="https://unpkg.com/ryuu.js"></script>
<script>
  // Domo is available globally
  Domo.get('/data/v1/sales').then(data => &#123;
    console.log(data);
  &#125;);
</script>
```

### TypeScript

TypeScript definitions are included automatically:

```typescript theme={"dark"}
import Domo, { Filter, Variable, RequestOptions } from 'ryuu.js';
```

***

## Core Concepts

### Communication Architecture

Your custom app runs in an iframe within the Domo platform. ryuu.js establishes a bidirectional communication channel using:

* **MessageChannel API** - Primary communication mechanism for desktop/web
* **webkit.messageHandlers** - iOS native integration
* **Global objects** - Android/Flutter integration

### Request-Reply Pattern

Async operations (like updating filters) use an **ASK-ACK-REPLY** pattern:

1. **ASK** - Your app sends a request with a unique ID
2. **ACK** - Parent acknowledges receipt (optional callback)
3. **REPLY** - Parent sends response when operation completes (optional callback)

```javascript theme={"dark"}
Domo.requestFiltersUpdate(
  filters,
  true,
  () => console.log('Request acknowledged'),
  (response) => console.log('Request completed:', response),
);
```

### Environment Context

Access information about the current user and environment via `Domo.env`:

```javascript theme={"dark"}
console.log(Domo.env.userId); // Current user ID
console.log(Domo.env.customer); // Customer name
console.log(Domo.env.pageId); // Current page ID
console.log(Domo.env.locale); // Locale (e.g., 'en-US')
console.log(Domo.env.platform); // Platform (e.g., 'desktop', 'mobile')
```

**Security Note:** Environment properties come from URL parameters and can be spoofed. Always verify with the API for secure operations:

```javascript theme={"dark"}
const authenticatedUser = await Domo.get('/domo/environment/v1/');
```

### Token Injection

A single `MutationObserver` watches `document.documentElement` with `subtree: true`. For every DOM node added at any depth, it automatically injects the Domo session token (`ryuu_sid`) into relative `href` and `src` attributes. Token is fetched once per batch — you don't need to manage auth manually.

***

## API Reference

### HTTP Methods

All HTTP methods return Promises and support multiple data formats.

#### `Domo.get(url, options?)`

Fetch data from a Domo dataset or API endpoint.

**Parameters:**

* `url` (string) - API endpoint URL
* `options` (object, optional) - Request options

**Returns:** `Promise<ResponseBody>`

**Basic Usage:**

```javascript theme={"dark"}
// Returns array of objects by default
const data = await Domo.get('/data/v1/sales');
console.log(data); // [{ id: 1, amount: 100, ... }, ...]
```

**Format Options:**

```javascript theme={"dark"}
// CSV format
const csv = await Domo.get('/data/v1/sales', { format: 'csv' });

// Array of arrays with metadata
const arrayData = await Domo.get('/data/v1/sales', {
  format: 'array-of-arrays',
});
console.log(arrayData.columns); // ['id', 'amount', 'date']
console.log(arrayData.rows); // [[1, 100, '2024-01-01'], ...]

// Excel format (returns Blob)
const excel = await Domo.get('/data/v1/sales', { format: 'excel' });
```

**Supported Formats:**

* `'array-of-objects'` (default) - Returns `ObjectResponseBody[]`
* `'array-of-arrays'` - Returns `ArrayResponseBody` with metadata
* `'csv'` - Returns CSV string
* `'excel'` - Returns Excel Blob
* `'plain'` - Returns plain text string

**Query Parameters:**

```javascript theme={"dark"}
const data = await Domo.get('/data/v1/sales', {
  query: {
    limit: 100,
    offset: 0,
    fields: 'id,amount,date',
  },
});
```

**Best Practice:** Always filter and paginate large datasets to avoid slow responses:

```javascript theme={"dark"}
const data = await Domo.get('/data/v1/sales', {
  query: {
    limit: 1000,
    offset: 0,
    filter: 'date > "2024-01-01"',
  },
});
```

***

#### `Domo.getAll(urls, options?)`

Fetch multiple datasets or endpoints in parallel.

**Parameters:**

* `urls` (string\[]) - Array of API endpoint URLs
* `options` (object, optional) - Request options applied to all requests

**Returns:** `Promise<ResponseBody[]>`

**Example:**

```javascript theme={"dark"}
const [sales, inventory, customers] = await Domo.getAll([
  '/data/v1/sales',
  '/data/v1/inventory',
  '/data/v1/customers',
]);

console.log(sales); // First dataset
console.log(inventory); // Second dataset
console.log(customers); // Third dataset
```

**With Options:**

```javascript theme={"dark"}
const results = await Domo.getAll(['/data/v1/sales', '/data/v1/inventory'], {
  format: 'csv',
});
// All results will be CSV strings
```

***

#### `Domo.post(url, body?, options?)`

Send a POST request to create data.

**Parameters:**

* `url` (string) - API endpoint URL
* `body` (object | string, optional) - Request body
* `options` (object, optional) - Request options

**Returns:** `Promise<ResponseBody>`

**Example:**

```javascript theme={"dark"}
// Create a document in Domo DataStore
const result = await Domo.post(
  '/domo/datastores/v1/collections/users/documents/',
  {
    name: 'John Doe',
    email: 'john@example.com',
    role: 'Admin',
  },
);

console.log(result); // Created document with ID
```

***

#### `Domo.put(url, body?, options?)`

Send a PUT request to update data.

**Parameters:**

* `url` (string) - API endpoint URL
* `body` (object | string, optional) - Request body
* `options` (object, optional) - Request options

**Returns:** `Promise<ResponseBody>`

**Example:**

```javascript theme={"dark"}
// Update an existing document
const result = await Domo.put(
  '/domo/datastores/v1/collections/users/documents/abc123',
  {
    name: 'Jane Doe',
    role: 'Manager',
  },
);
```

***

#### `Domo.delete(url, options?)`

Send a DELETE request to remove data.

**Parameters:**

* `url` (string) - API endpoint URL
* `options` (object, optional) - Request options

**Returns:** `Promise<ResponseBody>`

**Example:**

```javascript theme={"dark"}
// Delete a document
await Domo.delete('/domo/datastores/v1/collections/users/documents/abc123');
```

***

#### `Domo.domoHttp(method, url, options?, body?)`

Low-level HTTP method for full control over requests. All other HTTP methods use this internally.

**Parameters:**

* `method` (RequestMethods) - HTTP method
* `url` (string) - API endpoint URL
* `options` (object, optional) - Request options
* `body` (object | string, optional) - Request body

**Returns:** `Promise<ResponseBody>`

**Example:**

```javascript theme={"dark"}
import { RequestMethods } from 'ryuu.js';

const result = await Domo.domoHttp(
  RequestMethods.PATCH,
  '/custom/endpoint',
  { format: 'array-of-objects' },
  { data: 'custom body' },
);
```

***

### Event Listeners

Event listeners enable real-time reactivity to changes in the Domo platform. All listener methods return an **unsubscribe function**.

#### `Domo.onDataUpdated(callback)`

Listen for dataset update events.

**Parameters:**

* `callback` (function) - Called when a dataset is updated
  * Receives: `datasetAlias` (string) - Alias of the updated dataset

**Returns:** `function` - Unsubscribe function

**Example:**

```javascript theme={"dark"}
const unsubscribe = Domo.onDataUpdated((datasetAlias) => {
  console.log(`Dataset ${datasetAlias} was updated`);
  // Reload data for this dataset
  loadData();
});

// Later, stop listening
unsubscribe();
```

**Use Case:** Refresh your app's visualizations when underlying data changes without requiring a page reload.

***

#### `Domo.onFiltersUpdated(callback)`

Listen for page filter changes.

**Parameters:**

* `callback` (function) - Called when filters change
  * Receives: `filters` (Filter\[]) - Array of filter objects

**Returns:** `function` - Unsubscribe function

**Example:**

```javascript theme={"dark"}
Domo.onFiltersUpdated((filters) => {
  console.log('Filters updated:', filters);

  // Find specific filter
  const categoryFilter = filters.find((f) => f.column === 'category');

  if (categoryFilter) {
    console.log('Category filter:', categoryFilter.values);
    // Apply filter to your visualization
    applyFilters(categoryFilter.values);
  }
});
```

**Filter Object Structure:**

```javascript theme={"dark"}
{
  column: "category",              // Column name being filtered
  operator: "IN",                  // Filter operator
  values: ["ALERT", "WARNING"],    // Array of filter values
  dataType: "STRING",              // Data type: STRING, NUMERIC, DATE, DATETIME
  dataSourceId: "46d91556-...",    // Source dataset ID (optional)
  label: "category"                // Display label (optional)
}
```

**Supported Operators:**

**String Operators:**

* `"IN"` - Value is in list
* `"NOT_IN"` - Value is not in list
* `"CONTAINS"` - Value contains string
* `"NOT_CONTAINS"` - Value doesn't contain string
* `"STARTS_WITH"` - Value starts with string
* `"NOT_STARTS_WITH"` - Value doesn't start with string
* `"ENDS_WITH"` - Value ends with string
* `"NOT_ENDS_WITH"` - Value doesn't end with string

**Numeric/Date Operators:**

* `"EQUALS"` - Equals value
* `"NOT_EQUALS"` - Not equals value
* `"GREATER_THAN"` - Greater than value
* `"GREAT_THAN_EQUALS_TO"` - Greater than or equals value
* `"LESS_THAN"` - Less than value
* `"LESS_THAN_EQUALS_TO"` - Less than or equals value
* `"BETWEEN"` - Between two values

***

#### `Domo.onVariablesUpdated(callback)`

Listen for page variable changes.

**Parameters:**

* `callback` (function) - Called when variables change
  * Receives: `variables` (object) - Variables object with IDs as keys

**Returns:** `function` - Unsubscribe function

**Example:**

```javascript theme={"dark"}
Domo.onVariablesUpdated((variables) => {
  console.log('Variables updated:', variables);

  // Access specific variable by ID
  const themeVariable = variables['391'];
  if (themeVariable) {
    const theme = themeVariable.parsedExpression.value;
    setTheme(theme);
  }
});
```

**Variables Object Structure:**

```javascript theme={"dark"}
{
  "391": {
    "parsedExpression": {
      "exprType": "NUMERIC_VALUE",
      "value": "9"
    }
  },
  "392": {
    "parsedExpression": {
      "exprType": "STRING_VALUE",
      "value": "dark"
    }
  }
}
```

**Note:** Variable IDs (like "391") are defined by Domo. Inspect the variables object in your app to find the correct IDs.

***

#### `Domo.onAppDataUpdated(callback)`

Listen for custom app data updates.

**Parameters:**

* `callback` (function) - Called when app data is received
  * Receives: `data` (any) - Custom data object

**Returns:** `function` - Unsubscribe function

**Example:**

```javascript theme={"dark"}
Domo.onAppDataUpdated((data) => {
  console.log('Received app data:', data);

  // Handle custom data from other apps
  if (data.action === 'highlight') {
    highlightRow(data.rowId);
  }
});
```

**Use Case:** Enable communication between multiple custom apps on the same Domo page.

***

### Emitters

Emitters send messages to the parent Domo platform to trigger actions or update state.

#### `Domo.requestFiltersUpdate(filters, pageStateUpdate?, onAck?, onReply?)`

Update page-level filters programmatically.

**Parameters:**

* `filters` (Filter\[]) - Array of filter objects
* `pageStateUpdate` (boolean, optional) - Optional boolean indicating if the page state should be updated by the filter. When false, on the card level filter state will be updated. (default: true)
* `onAck` (function, optional) - Called when request is acknowledged
* `onReply` (function, optional) - Called when request completes
  * Receives: `response` (any) - Response data

**Returns:** `string` - Request ID for tracking

**Example:**

```javascript theme={"dark"}
// Basic usage
Domo.requestFiltersUpdate([
  {
    column: 'category',
    operator: 'IN',
    values: ['ALERT', 'WARNING'],
    dataType: 'STRING',
  },
  {
    column: 'amount',
    operator: 'GREATER_THAN',
    values: [1000],
    dataType: 'NUMERIC',
  },
]);
```

**With Callbacks:**

```javascript theme={"dark"}
const requestId = Domo.requestFiltersUpdate(
  filters,
  true,
  () => console.log('Filter update acknowledged'),
  (response) => console.log('Filter update completed:', response),
);

console.log('Request ID:', requestId);
```

**Filter Requirements:**

All filter objects must include:

* `column` (string, required) - Column name to filter on
* `operator` (string, required) - Filter operator (see supported operators above)
* `values` (array, required) - Values to filter by
* `dataType` (string, required) - Data type: `"STRING"`, `"NUMERIC"`, `"DATE"`, or `"DATETIME"`

***

#### `Domo.requestVariablesUpdate(variables, onAck?, onReply?)`

Update page variables programmatically.

**Parameters:**

* `variables` (Variable\[]) - Array of variable objects
* `onAck` (function, optional) - Called when request is acknowledged
* `onReply` (function, optional) - Called when request completes

**Returns:** `string` - Request ID for tracking

**Example:**

```javascript theme={"dark"}
Domo.requestVariablesUpdate([
  {
    functionId: 123,
    value: 100,
  },
  {
    functionId: 124,
    value: 'dark',
  },
]);
```

**Variable Object Structure:**

```javascript theme={"dark"}
{
  functionId: number,  // Variable function ID from Domo
  value: any          // New value for the variable
}
```

***

#### `Domo.requestAppDataUpdate(data, onAck?, onReply?)`

Send custom app data to other apps on the same page.

**Parameters:**

* `data` (any) - Custom data object
* `onAck` (function, optional) - Called when request is acknowledged
* `onReply` (function, optional) - Called when request completes

**Returns:** `void`

**Example:**

```javascript theme={"dark"}
// Send custom data
Domo.requestAppDataUpdate({
  action: 'highlight',
  rowId: 123,
  timestamp: Date.now(),
});
```

**Use Case:** Coordinate interactions between multiple custom apps on the same Domo page.

***

### Navigation

#### `Domo.navigate(url, isNewWindow?)`

Navigate to a different page within Domo.

**Parameters:**

* `url` (string) - Domo page URL or route
* `isNewWindow` (boolean, optional) - Open in new tab/window (default: false)

**Example:**

```javascript theme={"dark"}
// Navigate to a profile page
Domo.navigate('/profile/3234');

// Open in new tab
Domo.navigate('/page/123456789', true);
```

**Important Notes:**

* Use `Domo.navigate()` instead of HTML links to change the page hosting the custom app
* For mobile web platforms, routes are automatically prefixed with `/m#` (e.g., `/m#/profile/3234`)
* External links are restricted to whitelisted domains (configure in Admin > Network Security > Custom Apps authorized domains)

***

### Environment

#### `Domo.env`

Access environment information about the current context.

**Type:** `QueryParams` (object)

**Available Properties:**

```javascript theme={"dark"}
Domo.env.pageId; // Current page ID
Domo.env.userId; // Current user ID
Domo.env.customer; // Customer name
Domo.env.locale; // Locale (e.g., 'en-US')
Domo.env.environment; // Environment (e.g., 'dev3', 'prod')
Domo.env.platform; // Platform (e.g., 'desktop', 'mobile')
```

**Example:**

```javascript theme={"dark"}
console.log(`User ${Domo.env.userId} on ${Domo.env.platform}`);

// Conditional logic based on platform
if (Domo.env.platform === 'mobile') {
  renderMobileLayout();
} else {
  renderDesktopLayout();
}
```

**Security Warning:** These properties come from URL query parameters and can be spoofed. For secure user identification, always verify with the API:

```javascript theme={"dark"}
const authenticatedUser = await Domo.get('/domo/environment/v1/');
console.log('Verified user:', authenticatedUser);
```

***

### Utilities

#### `Domo.extend(overrides)`

Extend or override static methods and properties of the Domo class.

**Parameters:**

* `overrides` (object) - Object with methods/properties to override

**Returns:** `void`

**Example:**

```javascript theme={"dark"}
import Domo, { get as originalGet } from 'ryuu.js';

// Add logging to all GET requests
Domo.extend({
  get: async function (url, options) {
    console.log(`[API] Fetching: ${url}`);
    const startTime = Date.now();

    try {
      const result = await originalGet.call(this, url, options);
      console.log(`[API] Success: ${url} (${Date.now() - startTime}ms)`);
      return result;
    } catch (error) {
      console.error(`[API] Error: ${url} (${Date.now() - startTime}ms)`, error);
      throw error;
    }
  },
});

// Now all Domo.get() calls include logging
const data = await Domo.get('/data/v1/sales');
```

**Use Cases:**

* Add logging to all requests
* Implement retry logic
* Add caching layer
* Mock responses for testing
* Add custom error handling

***

#### `Domo.getRequests()`

Get all tracked requests (ASK-ACK-REPLY pattern).

**Returns:** `AskReplyMap` - Object with request IDs as keys

**Example:**

```javascript theme={"dark"}
const requestId = Domo.requestFiltersUpdate(filters);

// Check request status
const requests = Domo.getRequests();
console.log(requests[requestId]);
// {
//   request: { status: 'SENT', timestamp: 1234567890 },
//   response: { status: 'SUCCESS', timestamp: 1234567900, data: {...} }
// }
```

***

#### `Domo.getRequest(id)`

Get a specific tracked request by ID.

**Parameters:**

* `id` (string) - Request ID

**Returns:** Request object or `undefined`

**Example:**

```javascript theme={"dark"}
const requestId = Domo.requestFiltersUpdate(filters);
const request = Domo.getRequest(requestId);
console.log(request.request.status); // 'PENDING', 'SENT'
```

***

#### `Domo.__util`

Internal utilities exposed for advanced use cases.

**Available Utilities:**

```javascript theme={"dark"}
Domo.__util.isSuccess(statusCode); // Check if HTTP status is success
Domo.__util.isVerifiedOrigin(origin); // Verify origin is trusted Domo domain
Domo.__util.getQueryParams(); // Get current query parameters
Domo.__util.setFormatHeaders(format, headers); // Set Accept headers
Domo.__util.generateUniqueId(); // Generate unique request ID
Domo.__util.isIOS(); // Check if running on iOS
```

**Note:** These are internal utilities and may change between versions. Use at your own risk.

***

## TypeScript Support

ryuu.js includes comprehensive TypeScript definitions for a better development experience.

### Importing Types

```typescript theme={"dark"}
import Domo, {
  // Interfaces
  Filter,
  Variable,
  RequestOptions,
  ObjectResponseBody,
  ArrayResponseBody,
  QueryParams,

  // Enums
  DataFormats,
  DomoDataTypes,
  RequestMethods,

  // Type Guards
  isFilter,
  isFilterArray,
  isVariable,
  isVariableArray,
} from 'ryuu.js';
```

### Typed Requests

```typescript theme={"dark"}
// Type-safe request options
const options: RequestOptions<'array-of-objects'> = {
  format: 'array-of-objects',
  query: { limit: 100 },
};

// Return type is automatically inferred
const data: ObjectResponseBody[] = await Domo.get('/data/v1/sales', options);

// Format-specific types
const csv: string = await Domo.get('/data/v1/sales', { format: 'csv' });
const arrays: ArrayResponseBody = await Domo.get('/data/v1/sales', {
  format: 'array-of-arrays',
});
const excel: Blob = await Domo.get('/data/v1/sales', { format: 'excel' });
```

### Typed Filters

```typescript theme={"dark"}
const filters: Filter[] = [
  {
    column: 'category',
    operator: 'IN',
    values: ['ALERT', 'WARNING'],
    dataType: 'STRING',
  },
  {
    column: 'amount',
    operator: 'GREATER_THAN',
    values: [1000],
    dataType: 'NUMERIC',
  },
];

Domo.requestFiltersUpdate(filters);
```

### Typed Variables

```typescript theme={"dark"}
const variables: Variable[] = [
  { functionId: 123, value: 100 },
  { functionId: 124, value: 'dark' },
];

Domo.requestVariablesUpdate(variables);
```

### Custom Types

```typescript theme={"dark"}
// Define your data shape
interface SalesRecord {
  id: number;
  amount: number;
  date: string;
  category: string;
}

// Type-safe data access
const sales = (await Domo.get('/data/v1/sales')) as SalesRecord[];

sales.forEach((record) => {
  console.log(`Sale ${record.id}: $${record.amount}`);
});
```

***

## Mobile Platform Support

ryuu.js provides full support for iOS and Android mobile platforms through platform-specific APIs.

### iOS Integration

On iOS, ryuu.js uses **webkit.messageHandlers** for native communication:

```javascript theme={"dark"}
// Automatically handled by ryuu.js
Domo.requestFiltersUpdate(filters);
// Internally uses: webkit.messageHandlers.domofilter.postMessage()

Domo.requestVariablesUpdate(variables);
// Internally uses: webkit.messageHandlers.domovariable.postMessage()
```

### Android/Flutter Integration

On Android, ryuu.js uses global objects injected by the native app:

```javascript theme={"dark"}
// Automatically handled by ryuu.js
Domo.requestVariablesUpdate(variables);
// Internally uses: window.domovariable.postMessage()

Domo.requestFiltersUpdate(filters);
// Internally uses: window.domofilter.postMessage()
```

### Platform Detection

ryuu.js automatically detects the platform and uses the appropriate communication method:

```javascript theme={"dark"}
// Check if running on mobile
if (Domo.env.platform === 'mobile') {
  // Mobile-specific logic
  renderMobileView();
} else {
  // Desktop-specific logic
  renderDesktopView();
}

// Internal iOS detection (exposed via __util)
if (Domo.__util.isIOS()) {
  // iOS-specific logic
}
```

### Mobile Considerations

1. **Navigation:** Routes are automatically prefixed with `/m#` on mobile web
2. **Touch Events:** Consider touch-friendly UI for mobile apps
3. **Performance:** Mobile devices may have limited resources - optimize data fetching
4. **Testing:** Test on actual devices or simulators for best results

***

## Error Handling

All HTTP methods return Promises. Use `try/catch` with async/await for error handling.

### Basic Error Handling

```javascript theme={"dark"}
try {
  const data = await Domo.get('/data/v1/sales');
  console.log('Data loaded:', data);
} catch (error) {
  console.error('Failed to load data:', error);
}
```

### Error Object Structure

Error objects include comprehensive information:

```javascript theme={"dark"}
try {
  const data = await Domo.get('/data/v1/nonexistent');
} catch (error) {
  console.error('Status:', error.status); // 404
  console.error('Status Text:', error.statusText); // 'Not Found'
  console.error('Message:', error.message); // Error description
  console.error('Body:', error.body); // Response body
  console.error('Headers:', error.headers); // Response headers
}
```

### Handling Specific Error Types

```javascript theme={"dark"}
try {
  const data = await Domo.get('/data/v1/sales');
} catch (error) {
  if (error.status === 404) {
    console.error('Dataset not found');
    showError('The requested dataset does not exist');
  } else if (error.status === 403) {
    console.error('Access denied');
    showError('You do not have permission to access this dataset');
  } else if (error.status === 401) {
    console.error('Authentication failed');
    showError('Your session has expired. Please refresh the page.');
  } else if (error.status >= 500) {
    console.error('Server error');
    showError('A server error occurred. Please try again later.');
  } else {
    console.error('Unexpected error:', error);
    showError('An unexpected error occurred');
  }
}
```

### Retry Logic

Implement retry logic for transient errors:

```javascript theme={"dark"}
async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await Domo.get(url, options);
    } catch (error) {
      if (i === maxRetries - 1) throw error;

      // Only retry on server errors
      if (error.status >= 500) {
        console.log(`Retry ${i + 1}/${maxRetries}...`);
        await new Promise((resolve) => setTimeout(resolve, 1000 * (i + 1)));
      } else {
        throw error;
      }
    }
  }
}

// Usage
try {
  const data = await fetchWithRetry('/data/v1/sales');
} catch (error) {
  console.error('Failed after retries:', error);
}
```

***

## Advanced Usage

### Custom Fetch Implementation

Provide your own fetch implementation for testing or custom behavior:

```javascript theme={"dark"}
// Mock fetch for testing
const mockFetch = async (url, options) => {
  return {
    ok: true,
    status: 200,
    headers: new Headers(),
    json: async () => [{ id: 1, name: 'Test' }],
  };
};

const data = await Domo.get('/data/v1/sales', {
  fetchImpl: mockFetch,
});
```

### Caching Layer

Add a caching layer using `extend()`:

```javascript theme={"dark"}
const cache = new Map();

Domo.extend({
  get: async function (url, options) {
    // Check cache
    const cacheKey = `${url}:${JSON.stringify(options)}`;
    if (cache.has(cacheKey)) {
      console.log('Cache hit:', url);
      return cache.get(cacheKey);
    }

    // Fetch and cache
    const result = await originalGet.call(this, url, options);
    cache.set(cacheKey, result);

    return result;
  },
});
```

### Request Interceptors

Add interceptors for all requests:

```javascript theme={"dark"}
import Domo, { domoHttp as originalDomoHttp } from 'ryuu.js';

Domo.extend({
  domoHttp: async function (method, url, options, body) {
    // Before request
    console.log(`[${method}] ${url}`);
    const startTime = Date.now();

    // Add custom headers
    options = {
      ...options,
      headers: {
        ...options?.headers,
        'X-Custom-Header': 'value',
      },
    };

    try {
      // Make request
      const result = await originalDomoHttp.call(
        this,
        method,
        url,
        options,
        body,
      );

      // After successful request
      console.log(`[${method}] ${url} - Success (${Date.now() - startTime}ms)`);
      return result;
    } catch (error) {
      // After failed request
      console.error(
        `[${method}] ${url} - Error (${Date.now() - startTime}ms)`,
        error,
      );
      throw error;
    }
  },
});
```

### Type Guards

Use type guards to validate runtime data:

```javascript theme={"dark"}
import { isFilter, isFilterArray, guardAgainstInvalidFilters } from 'ryuu.js';

// Validate individual filter
if (isFilter(data)) {
  // TypeScript knows data is a Filter
  console.log(data.column);
}

// Validate array of filters
if (isFilterArray(data)) {
  // TypeScript knows data is Filter[]
  data.forEach((filter) => console.log(filter.column));
}

// Throw error if invalid
try {
  guardAgainstInvalidFilters(data);
  // Data is valid, proceed
} catch (error) {
  console.error('Invalid filters:', error.message);
}
```

***

## Complete Example

Here's a comprehensive example showing a real-world custom app:

```javascript theme={"dark"}
import Domo from 'ryuu.js';

class SalesDashboard {
  constructor() {
    this.data = [];
    this.filters = [];
    this.initialize();
  }

  async initialize() {
    // Set up event listeners
    this.setupEventListeners();

    // Load initial data
    await this.loadData();

    // Render dashboard
    this.render();
  }

  setupEventListeners() {
    // Listen for dataset updates
    Domo.onDataUpdated((datasetAlias) => {
      console.log(`Dataset ${datasetAlias} updated`);
      this.loadData();
    });

    // Listen for filter changes
    Domo.onFiltersUpdated((filters) => {
      console.log('Filters updated:', filters);
      this.filters = filters;
      this.applyFilters();
    });

    // Listen for variable changes
    Domo.onVariablesUpdated((variables) => {
      console.log('Variables updated:', variables);
      this.updateTheme(variables);
    });

    // Set up UI event handlers
    document.getElementById('refreshBtn').addEventListener('click', () => {
      this.loadData();
    });

    document.getElementById('exportBtn').addEventListener('click', () => {
      this.exportData();
    });

    document.getElementById('filterBtn').addEventListener('click', () => {
      this.updateFilters();
    });
  }

  async loadData() {
    try {
      // Load multiple datasets in parallel
      const [sales, customers, products] = await Domo.getAll([
        '/data/v1/sales',
        '/data/v1/customers',
        '/data/v1/products',
      ]);

      this.data = {
        sales,
        customers,
        products,
      };

      this.render();
    } catch (error) {
      console.error('Failed to load data:', error);
      this.showError('Unable to load dashboard data');
    }
  }

  applyFilters() {
    // Find category filter
    const categoryFilter = this.filters.find((f) => f.column === 'category');

    if (categoryFilter && categoryFilter.values.length > 0) {
      // Filter local data
      const filteredSales = this.data.sales.filter((sale) =>
        categoryFilter.values.includes(sale.category),
      );

      this.renderSales(filteredSales);
    } else {
      this.renderSales(this.data.sales);
    }
  }

  updateFilters() {
    // Get selected categories from UI
    const selectedCategories = Array.from(
      document.querySelectorAll('.category-checkbox:checked'),
    ).map((cb) => cb.value);

    // Update page filters
    Domo.requestFiltersUpdate(
      [
        {
          column: 'category',
          operator: 'IN',
          values: selectedCategories,
          dataType: 'STRING',
        },
      ],
      true,
      () => console.log('Filter update acknowledged'),
      (response) => console.log('Filter update completed:', response),
    );
  }

  async exportData() {
    try {
      // Export as CSV
      const csv = await Domo.get('/data/v1/sales', { format: 'csv' });

      // Download file
      const blob = new Blob([csv], { type: 'text/csv' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = `sales-export-${Date.now()}.csv`;
      a.click();
      URL.revokeObjectURL(url);

      console.log('Export successful');
    } catch (error) {
      console.error('Export failed:', error);
      this.showError('Failed to export data');
    }
  }

  updateTheme(variables) {
    // Check for theme variable (example ID: 391)
    const themeVar = variables['391'];

    if (themeVar) {
      const theme = themeVar.parsedExpression.value;
      document.body.className = `theme-${theme}`;
    }
  }

  render() {
    if (!this.data.sales) return;

    // Render sales chart
    this.renderSales(this.data.sales);

    // Render customer stats
    this.renderCustomerStats(this.data.customers);

    // Render product list
    this.renderProducts(this.data.products);
  }

  renderSales(sales) {
    const salesContainer = document.getElementById('salesChart');

    // Calculate totals
    const total = sales.reduce((sum, sale) => sum + sale.amount, 0);
    const count = sales.length;
    const average = total / count;

    salesContainer.innerHTML = `
      <div class="stats">
        <div class="stat">
          <h3>Total Sales</h3>
          <p>$${total.toLocaleString()}</p>
        </div>
        <div class="stat">
          <h3>Number of Sales</h3>
          <p>${count.toLocaleString()}</p>
        </div>
        <div class="stat">
          <h3>Average Sale</h3>
          <p>$${average.toFixed(2)}</p>
        </div>
      </div>
    `;

    // Render chart (using your charting library)
    // this.renderChart(sales);
  }

  renderCustomerStats(customers) {
    const customerContainer = document.getElementById('customerStats');

    customerContainer.innerHTML = `
      <h3>Total Customers: ${customers.length}</h3>
      <ul>
        ${customers
          .slice(0, 10)
          .map(
            (c) => `
          <li>${c.name} - ${c.email}</li>
        `,
          )
          .join('')}
      </ul>
    `;
  }

  renderProducts(products) {
    const productContainer = document.getElementById('productList');

    productContainer.innerHTML = `
      <table>
        <thead>
          <tr>
            <th>Product</th>
            <th>Price</th>
            <th>Stock</th>
          </tr>
        </thead>
        <tbody>
          ${products
            .map(
              (p) => `
            <tr>
              <td>${p.name}</td>
              <td>$${p.price}</td>
              <td>${p.stock}</td>
            </tr>
          `,
            )
            .join('')}
        </tbody>
      </table>
    `;
  }

  showError(message) {
    const errorContainer = document.getElementById('error');
    errorContainer.textContent = message;
    errorContainer.style.display = 'block';

    setTimeout(() => {
      errorContainer.style.display = 'none';
    }, 5000);
  }
}

// Initialize app
new SalesDashboard();
```

***

## Migration Guide

### Deprecated Methods

The following methods have been renamed for consistency. Old methods still work but are deprecated and will be removed in a future version.

| Deprecated Method      | New Method                    | Description                 |
| ---------------------- | ----------------------------- | --------------------------- |
| `Domo.onDataUpdate`    | `Domo.onDataUpdated`          | Listen for dataset changes  |
| `Domo.onFiltersUpdate` | `Domo.onFiltersUpdated`       | Listen for filter changes   |
| `Domo.onAppData`       | `Domo.onAppDataUpdated`       | Listen for app data changes |
| `Domo.filterContainer` | `Domo.requestFiltersUpdate`   | Set page filters            |
| `Domo.sendVariables`   | `Domo.requestVariablesUpdate` | Update page variables       |
| `Domo.sendAppData`     | `Domo.requestAppDataUpdate`   | Send custom app data        |

### Migration Steps

1. **Find and Replace:**

```javascript theme={"dark"}
// Old
Domo.onDataUpdate((alias) => { ... });
Domo.onFiltersUpdate((filters) => { ... });
Domo.onAppData((data) => { ... });
Domo.filterContainer(filters);
Domo.sendVariables(variables);
Domo.sendAppData(data);

// New
Domo.onDataUpdated((alias) => { ... });
Domo.onFiltersUpdated((filters) => { ... });
Domo.onAppDataUpdated((data) => { ... });
Domo.requestFiltersUpdate(filters);
Domo.requestVariablesUpdate(variables);
Domo.requestAppDataUpdate(data);
```

2. **Update Dependencies:**

```bash theme={"dark"}
npm update ryuu.js
```

3. **Test Thoroughly:**

After migration, test all functionality to ensure everything works correctly.

***

## Contributing

Contributions are welcome! Please follow these guidelines:

1. **Fork the repository**
2. **Create a feature branch:** `git checkout -b feature/my-feature`
3. **Make your changes**
4. **Add tests** for new functionality
5. **Run tests:** `npm test`
6. **Build:** `npm run build`
7. **Commit:** `git commit -m "Add my feature"`
8. **Push:** `git push origin feature/my-feature`
9. **Open a Pull Request**

### Development Setup

```bash theme={"dark"}
# Clone repository
git clone https://github.com/your-org/domo.js.git
cd domo.js

# Install dependencies
npm install

# Run tests
npm test

# Run tests with coverage
npm run coverage

# Build
npm run build

# Start demo server
npm run demo
```

***

**Made with ❤️ by the Domo team**
