A powerful JavaScript SDK for building custom applications within the Domo platform
ryuu.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.
Choose your version:
- v6+ Documentation ⭐ Latest (recommended)
- v4 and Earlier Documentation 📦 Legacy
- Quick Start
- Features
- Installation
- Core Concepts
- API Reference
- TypeScript Support
- Mobile Platform Support
- Error Handling
- Advanced Usage
- Complete Example
- Breaking Changes (v6.0)
- Migration Guide
- Contributing
- License
Get started with ryuu.js in just a few lines:
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
});- Data API - Query datasets with typed options (fields, filters, aggregations, date graining, beast modes)
- AppDB - Full CRUD, MongoDB queries, bulk operations, partial updates, collection management
- Code Engine - Run Code Engine functions by manifest alias
- Workflows - Start and monitor Domo Workflows
- AI Services - Text generation and text-to-SQL via Domo's AI Service Layer
- 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
- Typed Environment -
Domo.envwith userId, userName, host, platform, enriched from environment API - Navigation - Programmatically navigate within Domo
- Mobile Support - Full iOS and Android compatibility
- TypeScript Ready - Complete type definitions with typed callbacks and generics
- Zero Dependencies - ~28KB UMD bundle with no runtime dependencies
- Extensible -
extend()propagates overrides to all services (data, appdb, ai, etc.) - Structured Errors - Typed error classes (
DomoHttpError,DomoAuthError,DomoConnectionError,DomoValidationError) withinstanceofsupport - Schema Validation - Optional runtime response validation via
{ schema: { parse } }option (works with zod, valibot, etc.) - Request Interceptors - Middleware pattern via
Domo.intercept()for logging, retry, caching - Debug Mode -
Domo.debug.enable()for category-based logging of HTTP, messages, filters, variables
npm install ryuu.jsThen import in your application:
// ES modules
import Domo from 'ryuu.js';
// CommonJS
const Domo = require('ryuu.js').default;<script src="https://unpkg.com/ryuu.js"></script>
<script>
// Domo is available globally
Domo.get('/data/v1/sales').then(data => {
console.log(data);
});
</script>TypeScript definitions are included automatically:
import Domo, { Filter, Variable, RequestOptions } from 'ryuu.js';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
Async operations (like updating filters) use an ASK-ACK-REPLY pattern:
- ASK - Your app sends a request with a unique ID
- ACK - Parent acknowledges receipt (optional callback)
- REPLY - Parent sends response when operation completes (optional callback)
Domo.requestFiltersUpdate(
filters,
true,
() => console.log('Request acknowledged'),
(response) => console.log('Request completed:', response)
);Domo.env provides typed access to the current user, instance, and page context. Properties are populated immediately from iframe query parameters, then enriched in the background from GET /domo/environment/v1 (which provides authoritative server-side values like host).
// Available immediately
console.log(Domo.env.userId); // "481303514"
console.log(Domo.env.userName); // "JSON"
console.log(Domo.env.userEmail); // "Jason.Hansen@domo.com"
console.log(Domo.env.customer); // "domo"
console.log(Domo.env.locale); // "en-US"
console.log(Domo.env.platform); // "desktop" | "mobile"
console.log(Domo.env.pageId); // "1185508957"
// Available after environment API loads
console.log(Domo.env.host); // "domo.demo.domo.com"
console.log(Domo.env.loaded); // trueThe loaded property indicates whether the environment API has been fetched. If it fails (e.g. running locally), query params serve as the fallback.
All HTTP methods return Promises and support multiple data formats.
Fetch data from a Domo dataset or API endpoint.
Parameters:
url(string) - API endpoint URLoptions(object, optional) - Request options
Returns: Promise<ResponseBody>
Basic Usage:
// Returns array of objects by default
const data = await Domo.get('/data/v1/sales');
console.log(data); // [{ id: 1, amount: 100, ... }, ...]Format Options:
// 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) - ReturnsObjectResponseBody[]'array-of-arrays'- ReturnsArrayResponseBodywith metadata'csv'- Returns CSV string'excel'- Returns Excel Blob'plain'- Returns plain text string
Query Parameters:
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:
const data = await Domo.get('/data/v1/sales', {
query: {
limit: 1000,
offset: 0,
filter: 'date > "2024-01-01"'
}
});Fetch multiple datasets or endpoints in parallel.
Parameters:
urls(string[]) - Array of API endpoint URLsoptions(object, optional) - Request options applied to all requests
Returns: Promise<ResponseBody[]>
Example:
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 datasetWith Options:
const results = await Domo.getAll(
['/data/v1/sales', '/data/v1/inventory'],
{ format: 'csv' }
);
// All results will be CSV stringsSend a POST request to create data.
Parameters:
url(string) - API endpoint URLbody(object | string, optional) - Request bodyoptions(object, optional) - Request options
Returns: Promise<ResponseBody>
Example:
// 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 IDSend a PUT request to update data.
Parameters:
url(string) - API endpoint URLbody(object | string, optional) - Request bodyoptions(object, optional) - Request options
Returns: Promise<ResponseBody>
Example:
// Update an existing document
const result = await Domo.put(
'/domo/datastores/v1/collections/users/documents/abc123',
{
name: 'Jane Doe',
role: 'Manager'
}
);Send a DELETE request to remove data.
Parameters:
url(string) - API endpoint URLoptions(object, optional) - Request options
Returns: Promise<ResponseBody>
Example:
// Delete a document
await Domo.delete('/domo/datastores/v1/collections/users/documents/abc123');Low-level HTTP method for full control over requests. All other HTTP methods use this internally.
Parameters:
method(RequestMethods) - HTTP methodurl(string) - API endpoint URLoptions(object, optional) - Request optionsbody(object | string, optional) - Request body
Returns: Promise<ResponseBody>
Example:
import { RequestMethods } from 'ryuu.js';
const result = await Domo.domoHttp(
RequestMethods.PATCH,
'/custom/endpoint',
{ format: 'array-of-objects' },
{ data: 'custom body' }
);High-level helpers for querying datasets. Supports all Data API query operators.
Query a dataset by its manifest alias.
// Simple query
const rows = await Domo.data.query("sales");
// With filtering, aggregation, and pagination
const totals = await Domo.data.query("sales", {
fields: ["region", "amount"],
filter: "amount > 100",
sum: ["amount"],
groupBy: ["region"],
orderBy: "amount descending",
limit: 50,
});
// Date graining
const monthly = await Domo.data.query("sales", {
dateGrain: "orderDate by month",
sum: ["amount"],
calendar: "fiscal",
});
// Beast modes
const bm = await Domo.data.query("sales", {
useBeastMode: true,
fields: ["myBeastMode", "reps"],
sum: ["myBeastMode"],
groupBy: ["reps"],
});
// CSV format
const csv = await Domo.data.query("sales", { format: "csv" });Supported options: fields, filter, avg, count, max, min, sum, unique, groupBy, dateGrain, calendar, orderBy, limit, offset, useBeastMode, format.
Execute a SQL query against a dataset via POST /sql/v1/{alias}.
const rows = await Domo.data.sql("sales", "SELECT region, SUM(amount) as total FROM sales GROUP BY region");Note: The SQL API does not support page filters or JOINs.
Full CRUD, MongoDB-style queries, bulk operations, and collection management for AppDB.
const docs = await Domo.appdb.list("Users");
const doc = await Domo.appdb.get("Users", docId);
const created = await Domo.appdb.create("Users", { username: "Bill" }); // auto-wraps in { content: ... }
await Domo.appdb.update("Users", docId, { username: "Ted" });
await Domo.appdb.remove("Users", docId);const docs = await Domo.appdb.query("Users", { "content.region": "West" });
// With aggregations
const results = await Domo.appdb.query("campaigns", {}, {
groupby: "content.campaignName, content.month",
count: "documentCount",
sum: "content.clicks sumClicks",
orderby: "sumClicks descending",
});await Domo.appdb.partialUpdate("Users",
{ "content.username": "Bill" },
{ "$set": { "content.band": "Wyld Stallyns" } }
);await Domo.appdb.bulkCreate("Users", [{ username: "Bill" }, { username: "Ted" }]);
await Domo.appdb.bulkUpsert("Users", [
{ id: "existing-id", username: "Bill", band: "Wyld Stallyns" },
{ username: "Rufus" },
]);
await Domo.appdb.bulkDelete("Users", ["id-1", "id-2", "id-3"]);await Domo.appdb.listCollections();
await Domo.appdb.createCollection({ name: "Users", schema: { columns: [{ name: "username", type: "STRING" }] }, syncEnabled: true });
await Domo.appdb.export(); // manually trigger sync to Domo DataSetsRun Code Engine functions by their manifest alias.
const result = await Domo.codeEngine("awesomeFunction", { number1AppInput: 5, number2AppInput: 10 });Requires a packageMapping entry in your manifest.json.
Start and monitor Domo Workflows.
const instance = await Domo.workflow.start("myWorkflow", { param1: "hello" });
const current = await Domo.workflow.getInstance("myWorkflow", instance.id);
// current.status: "IN_PROGRESS" | "COMPLETED" | "CANCELED" | "FAILED" | nullRequires a workflowMapping entry in your manifest.json.
Access Domo's AI Service Layer for text generation and text-to-SQL.
const res = await Domo.ai.generateText("Tell me a joke about data");
console.log(res.choices[0].output);
const sql = await Domo.ai.textToSQL("Show total sales by region", {
dataSourceSchemas: [{ dataSourceName: "Sales", columns: [{ name: "Region", type: "string" }, { name: "Amount", type: "number" }] }],
});
console.log(sql.choices[0].output); // "SELECT Region, SUM(Amount) ..."Note: AI services consume AI credits. See your Domo instance rate card for details.
Event listeners enable real-time reactivity to changes in the Domo platform. All listener methods return an unsubscribe function.
Listen for dataset update events.
Parameters:
callback(function) - Called when a dataset is updated- Receives:
datasetAlias(string) - Alias of the updated dataset
- Receives:
Returns: function - Unsubscribe function
Example:
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.
Listen for page filter changes.
Parameters:
callback(function) - Called when filters change- Receives:
filters(Filter[]) - Array of filter objects
- Receives:
Returns: function - Unsubscribe function
Example:
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:
{
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
Listen for page variable changes.
Parameters:
callback(function) - Called when variables change- Receives:
variables(object) - Variables object with IDs as keys
- Receives:
Returns: function - Unsubscribe function
Example:
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:
{
"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.
Listen for custom app data updates.
Parameters:
callback(function) - Called when app data is received- Receives:
data(any) - Custom data object
- Receives:
Returns: function - Unsubscribe function
Example:
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 send messages to the parent Domo platform to trigger actions or update state.
Update page-level filters programmatically.
Parameters:
filters(Filter[]) - Array of filter objectspageStateUpdate(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 acknowledgedonReply(function, optional) - Called when request completes- Receives:
response(any) - Response data
- Receives:
Returns: string - Request ID for tracking
Example:
// Basic usage
Domo.requestFiltersUpdate([
{
column: 'category',
operator: 'IN',
values: ['ALERT', 'WARNING'],
dataType: 'STRING'
},
{
column: 'amount',
operator: 'GREATER_THAN',
values: [1000],
dataType: 'NUMERIC'
}
]);With Callbacks:
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 onoperator(string, required) - Filter operator (see supported operators above)values(array, required) - Values to filter bydataType(string, required) - Data type:"STRING","NUMERIC","DATE", or"DATETIME"
Update page variables programmatically.
Parameters:
variables(Variable[]) - Array of variable objectsonAck(function, optional) - Called when request is acknowledgedonReply(function, optional) - Called when request completes
Returns: string - Request ID for tracking
Example:
Domo.requestVariablesUpdate([
{
functionId: 123,
value: 100
},
{
functionId: 124,
value: 'dark'
}
]);Variable Object Structure:
{
functionId: number, // Variable function ID from Domo
value: any // New value for the variable
}Send custom app data to other apps on the same page.
Parameters:
data(any) - Custom data objectonAck(function, optional) - Called when request is acknowledgedonReply(function, optional) - Called when request completes
Returns: void
Example:
// 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.
Navigate to a different page within Domo.
Parameters:
url(string) - Domo page URL or routeisNewWindow(boolean, optional) - Open in new tab/window (default: false)
Example:
// 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)
Access environment information about the current context.
Type: QueryParams (object)
Available Properties:
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:
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:
const authenticatedUser = await Domo.get('/domo/environment/v1/');
console.log('Verified user:', authenticatedUser);Extend or override static methods and properties of the Domo class.
Parameters:
overrides(object) - Object with methods/properties to override
Returns: void
Example:
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
Get all tracked requests (ASK-ACK-REPLY pattern).
Returns: AskReplyMap - Object with request IDs as keys
Example:
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: {...} }
// }Get a specific tracked request by ID.
Parameters:
id(string) - Request ID
Returns: Request object or undefined
Example:
const requestId = Domo.requestFiltersUpdate(filters);
const request = Domo.getRequest(requestId);
console.log(request.request.status); // 'PENDING', 'SENT'Internal utilities exposed for advanced use cases.
Available Utilities:
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 iOSNote: These are internal utilities and may change between versions. Use at your own risk.
ryuu.js includes comprehensive TypeScript definitions for a better development experience.
import Domo, {
// Interfaces
Filter,
Variable,
RequestOptions,
ObjectResponseBody,
ArrayResponseBody,
QueryParams,
// Enums
DataFormats,
DomoDataTypes,
RequestMethods,
// Type Guards
isFilter,
isFilterArray,
isVariable,
isVariableArray,
// Error Types
DomoHttpError,
DomoAuthError,
DomoConnectionError,
DomoValidationError,
DomoTimeoutError,
} from 'ryuu.js';// 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' });const filters: Filter[] = [
{
column: 'category',
operator: 'IN',
values: ['ALERT', 'WARNING'],
dataType: 'STRING'
},
{
column: 'amount',
operator: 'GREATER_THAN',
values: [1000],
dataType: 'NUMERIC'
}
];
Domo.requestFiltersUpdate(filters);const variables: Variable[] = [
{ functionId: 123, value: 100 },
{ functionId: 124, value: 'dark' }
];
Domo.requestVariablesUpdate(variables);// 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}`);
});ryuu.js provides full support for iOS and Android mobile platforms through platform-specific APIs.
On iOS, ryuu.js uses webkit.messageHandlers for native communication:
// Automatically handled by ryuu.js
Domo.requestFiltersUpdate(filters);
// Internally uses: webkit.messageHandlers.domofilter.postMessage()
Domo.requestVariablesUpdate(variables);
// Internally uses: webkit.messageHandlers.domovariable.postMessage()On Android, ryuu.js uses global objects injected by the native app:
// Automatically handled by ryuu.js
Domo.requestVariablesUpdate(variables);
// Internally uses: window.domovariable.postMessage()
Domo.requestFiltersUpdate(filters);
// Internally uses: window.domofilter.postMessage()ryuu.js automatically detects the platform and uses the appropriate communication method:
// 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
}- Navigation: Routes are automatically prefixed with
/m#on mobile web - Touch Events: Consider touch-friendly UI for mobile apps
- Performance: Mobile devices may have limited resources - optimize data fetching
- Testing: Test on actual devices or simulators for best results
All HTTP methods return Promises. ryuu.js throws structured, typed error classes that support instanceof checks.
| Error Class | Thrown When | Properties |
|---|---|---|
DomoHttpError |
Non-2xx HTTP response | status, statusText, body, headers |
DomoAuthError |
401 or 403 response (extends DomoHttpError) |
Same as above |
DomoConnectionError |
Network failure (fetch rejects) | message |
DomoValidationError |
Invalid filters/variables, schema parse failure | message, errors[] |
DomoTimeoutError |
Request or message timeout | message, url |
try {
const data = await Domo.get('/data/v1/sales');
console.log('Data loaded:', data);
} catch (error) {
console.error('Failed to load data:', error);
}import Domo, { DomoHttpError, DomoAuthError, DomoConnectionError } from 'ryuu.js';
try {
const data = await Domo.get('/data/v1/sales');
} catch (error) {
if (error instanceof DomoAuthError) {
// 401 or 403 — session expired or no permission
console.error('Auth failed:', error.status); // 401 or 403
showLoginPrompt();
} else if (error instanceof DomoHttpError) {
// Any other non-2xx response
console.error('HTTP error:', error.status, error.statusText);
console.error('Response body:', error.body);
console.error('Response headers:', error.headers);
} else if (error instanceof DomoConnectionError) {
// Network failure — no response at all
console.error('Network error:', error.message);
showOfflineMessage();
} else {
console.error('Unexpected error:', error);
}
}When you pass malformed data to filters or variables, ryuu.js throws a DomoValidationError and logs the expected data model to console.error:
import { DomoValidationError } from 'ryuu.js';
try {
Domo.requestFiltersUpdate([{ bad: 'data' }]);
} catch (error) {
if (error instanceof DomoValidationError) {
console.error(error.message); // "All filters must be valid Filter objects."
console.error(error.errors); // Array of the invalid items
// console.error also shows the expected object shape automatically
}
}Implement retry logic for transient errors:
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);
}Validate API responses at runtime using any schema library with a .parse() method (zod, valibot, etc.):
import Domo from 'ryuu.js';
import { z } from 'zod';
const UserSchema = z.array(z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
}));
// Throws DomoValidationError if response doesn't match
const users = await Domo.get<z.infer<typeof UserSchema>>('/data/v1/users', {
schema: UserSchema,
});Zero cost when schema is not provided — a single falsy check.
Register middleware that wraps every HTTP request. Interceptors use an onion model — the last registered runs outermost.
// Logging interceptor
const removeLogger = Domo.intercept(async (config, next) => {
console.log(`[${config.method}] ${config.url}`);
const start = Date.now();
const response = await next(config);
console.log(`[${config.method}] ${config.url} - ${Date.now() - start}ms`);
return response;
});
// Add custom headers
Domo.intercept((config, next) => {
config.headers['X-Custom'] = 'value';
return next(config);
});
// Short-circuit with a cached response
Domo.intercept((config, next) => {
if (cache.has(config.url)) return Promise.resolve(cache.get(config.url));
return next(config);
});
// Remove an interceptor
removeLogger();The config object has { method, url, headers, body }. Headers and auth tokens are already set when interceptors run.
Enable category-based debug logging to trace HTTP requests, MessageChannel messages, filter and variable updates:
// Enable all categories
Domo.debug.enable();
// Enable specific categories: 'http', 'messages', 'filters', 'variables', 'all'
Domo.debug.enable(['http', 'messages']);
// Disable
Domo.debug.disable();Debug state persists to localStorage — it survives page reloads. You can also enable it from the browser console:
localStorage.__domo_debug__ = '["all"]';
// Refresh the page — debug logging is now activeOutput looks like:
[domo:http] GET /data/v1/sales
[domo:http] 200 OK /data/v1/sales
[domo:messages] received filtersUpdated { event: 'filtersUpdated', filters: [...] }
[domo:filters] filtersUpdated [{ column: 'region', ... }]
Provide your own fetch implementation for testing or custom behavior:
const data = await Domo.get('/data/v1/sales', {
fetch: myCustomFetch,
});Use type guards to validate runtime data:
import { isFilter, isFilterArray, guardAgainstInvalidFilters, DomoValidationError } from 'ryuu.js';
// Validate individual filter
if (isFilter(data)) {
console.log(data.column); // TypeScript knows data is a Filter
}
// Validate array — throws DomoValidationError with expected model logged to console
try {
guardAgainstInvalidFilters(data);
} catch (error) {
if (error instanceof DomoValidationError) {
console.error(error.errors); // The invalid items
}
}Here's a comprehensive example showing a real-world custom app:
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();Domo.env previously returned raw query parameters (QueryParams with string | number | undefined values). It now returns a typed DomoEnv object:
userId,userName,userEmail,customer,locale,pageIdare now alwaysstring(previously could benumberorundefined)- New properties:
host(from environment API),loaded(boolean indicating if the API fetch completed) platformis now typed as"desktop" | "mobile"(defaults to"desktop")- A background
GET /domo/environment/v1request is made on initialization to enrich the environment. This is new network traffic that fails silently if the endpoint is unavailable.
Migration: If your code checks typeof Domo.env.userId === 'number', update it to treat userId as a string. All other existing Domo.env.* usage is backwards compatible.
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 |
- Find and Replace:
// 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);- Update Dependencies:
npm update ryuu.js- Test Thoroughly:
After migration, test all functionality to ensure everything works correctly.
Contributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Make your changes
- Add tests for new functionality
- Run tests:
npm test - Build:
npm run build - Commit:
git commit -m "Add my feature" - Push:
git push origin feature/my-feature - Open a Pull Request
# 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 demoSEE LICENSE IN LICENSE
Copyright (c) Domo
For issues, questions, or contributions:
- Issues: Open an issue on GitHub
- Documentation: See CLAUDE.md for developer documentation
- Domo Developer Portal: Visit the Domo developer documentation
See CHANGELOG.md for version history and changes.
Note: This documentation is for ryuu.js v4.x and earlier. For the latest version, see the v6+ Documentation.
- Quick Start
- Features
- Installation
- Core Concepts
- API Reference
- TypeScript Support
- Mobile Platform Support
- Error Handling
- Complete Example
- Upgrading to v6+
- Contributing
- License
Get started with ryuu.js in just a few lines:
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.onDataUpdate((alias) => {
console.log(`Dataset ${alias} was updated!`);
// Refresh your visualization
});
// Listen for filter changes
domo.onFiltersUpdate((filters) => {
console.log('Filters changed:', filters);
// Apply filters to your data
});- 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
- Mobile Support - iOS and Android compatibility
- TypeScript Ready - Type definitions included
- Zero Dependencies - Minimal bundle size
npm install ryuu.js@4Then import in your application:
// ES modules
import domo from 'ryuu.js';
// CommonJS
const domo = require('ryuu.js');<script src="https://unpkg.com/ryuu.js@4"></script>
<script>
// domo is available globally
domo.get('/data/v1/sales').then(data => {
console.log(data);
});
</script>TypeScript definitions are included automatically:
import domo, { Filter, Variable, RequestOptions } from 'ryuu.js';Your custom app runs in an iframe within the Domo platform. ryuu.js v4 establishes bidirectional communication using:
- window.postMessage API - Primary communication mechanism for desktop/web
- webkit.messageHandlers - iOS native integration
- Global objects - Android integration
Access information about the current user and environment via domo.env:
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:
const authenticatedUser = await domo.get('/domo/environment/v1/');All HTTP methods return Promises and support multiple data formats.
Fetch data from a Domo dataset or API endpoint.
Parameters:
url(string) - API endpoint URLoptions(object, optional) - Request options
Returns: Promise<ResponseBody>
Basic Usage:
// Returns array of objects by default
const data = await domo.get('/data/v1/sales');
console.log(data); // [{ id: 1, amount: 100, ... }, ...]Format Options:
// 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) - ReturnsObjectResponseBody[]'array-of-arrays'- ReturnsArrayResponseBodywith metadata'csv'- Returns CSV string'excel'- Returns Excel Blob'plain'- Returns plain text string
Query Parameters:
const data = await domo.get('/data/v1/sales', {
query: {
limit: 100,
offset: 0,
fields: 'id,amount,date'
}
});Fetch multiple datasets or endpoints in parallel.
Parameters:
urls(string[]) - Array of API endpoint URLsoptions(object, optional) - Request options applied to all requests
Returns: Promise<ResponseBody[]>
Example:
const [sales, inventory, customers] = await domo.getAll([
'/data/v1/sales',
'/data/v1/inventory',
'/data/v1/customers'
]);Send a POST request to create data.
Parameters:
url(string) - API endpoint URLbody(object | string, optional) - Request bodyoptions(object, optional) - Request options
Returns: Promise<ResponseBody>
Example:
// 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'
}
);Send a PUT request to update data.
Example:
// Update an existing document
const result = await domo.put(
'/domo/datastores/v1/collections/users/documents/abc123',
{
name: 'Jane Doe',
role: 'Manager'
}
);Send a DELETE request to remove data.
Example:
// Delete a document
await domo.delete('/domo/datastores/v1/collections/users/documents/abc123');Event listeners enable real-time reactivity to changes in the Domo platform. All listener methods return an unsubscribe function.
Listen for dataset update events.
Parameters:
callback(function) - Called when a dataset is updated- Receives:
datasetAlias(string) - Alias of the updated dataset
- Receives:
Returns: function - Unsubscribe function
Example:
const unsubscribe = domo.onDataUpdate((datasetAlias) => {
console.log(`Dataset ${datasetAlias} was updated`);
// Reload data for this dataset
loadData();
});
// Later, stop listening
unsubscribe();Listen for page filter changes.
Parameters:
callback(function) - Called when filters change- Receives:
filters(Filter[]) - Array of filter objects
- Receives:
Returns: function - Unsubscribe function
Example:
domo.onFiltersUpdate((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:
{
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"/"NOT_IN""CONTAINS"/"NOT_CONTAINS""STARTS_WITH"/"NOT_STARTS_WITH""ENDS_WITH"/"NOT_ENDS_WITH"
Numeric/Date Operators:
"EQUALS"/"NOT_EQUALS""GREATER_THAN"/"GREAT_THAN_EQUALS_TO""LESS_THAN"/"LESS_THAN_EQUALS_TO""BETWEEN"
Listen for custom app data updates.
Parameters:
callback(function) - Called when app data is received- Receives:
data(any) - Custom data object
- Receives:
Returns: function - Unsubscribe function
Example:
domo.onAppData((data) => {
console.log('Received app data:', data);
// Handle custom data from other apps
if (data.action === 'highlight') {
highlightRow(data.rowId);
}
});Emitters send messages to the parent Domo platform to trigger actions or update state.
Update page-level filters programmatically.
Parameters:
filters(Filter[]) - Array of filter objectspageStateUpdate(boolean, optional) - Whether to update page state (default: true)
Returns: void
Example:
// Basic usage
domo.filterContainer([
{
column: 'category',
operator: 'IN',
values: ['ALERT', 'WARNING'],
dataType: 'STRING'
},
{
column: 'amount',
operator: 'GREATER_THAN',
values: [1000],
dataType: 'NUMERIC'
}
]);Update page variables programmatically.
Parameters:
variables(Variable[]) - Array of variable objects
Returns: void
Example:
domo.sendVariables([
{
functionId: 123,
value: 100
},
{
functionId: 124,
value: 'dark'
}
]);Variable Object Structure:
{
functionId: number, // Variable function ID from Domo
value: any // New value for the variable
}Send custom app data to other apps on the same page.
Parameters:
data(any) - Custom data object
Returns: void
Example:
// Send custom data
domo.sendAppData({
action: 'highlight',
rowId: 123,
timestamp: Date.now()
});Navigation in v4 is available through the parent window. For programmatic navigation, you can use:
// Navigate within Domo
window.parent.postMessage({
event: 'navigate',
data: { route: '/page/123456789' }
}, '*');Access environment information about the current context.
Type: QueryParams (object)
Available Properties:
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')ryuu.js v4 includes TypeScript definitions.
import domo, {
// Interfaces
Filter,
Variable,
RequestOptions,
ObjectResponseBody,
ArrayResponseBody,
QueryParams,
// Enums
DataFormats,
DomoDataTypes,
RequestMethods
} from 'ryuu.js';// 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 filters: Filter[] = [
{
column: 'category',
operator: 'IN',
values: ['ALERT', 'WARNING'],
dataType: 'STRING'
}
];
domo.filterContainer(filters);ryuu.js v4 provides support for iOS and Android mobile platforms.
On iOS, ryuu.js uses webkit.messageHandlers for native communication:
// Automatically handled by ryuu.js
domo.filterContainer(filters);
// Internally uses: webkit.messageHandlers.domofilter.postMessage()
domo.sendVariables(variables);
// Internally uses: webkit.messageHandlers.domovariable.postMessage()On Android, ryuu.js uses global objects injected by the native app:
// Automatically handled by ryuu.js
domo.sendVariables(variables);
// Internally uses: window.domovariable.postMessage()
domo.filterContainer(filters);
// Internally uses: window.domofilter.postMessage()All HTTP methods return Promises. Use try/catch with async/await for error handling.
try {
const data = await domo.get('/data/v1/sales');
console.log('Data loaded:', data);
} catch (error) {
console.error('Failed to load data:', error);
}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
}try {
const data = await domo.get('/data/v1/sales');
} catch (error) {
if (error.status === 404) {
console.error('Dataset not found');
} else if (error.status === 403) {
console.error('Access denied');
} else if (error.status === 401) {
console.error('Authentication failed');
} else if (error.status >= 500) {
console.error('Server error');
}
}Here's a comprehensive example showing a real-world custom app:
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.onDataUpdate((datasetAlias) => {
console.log(`Dataset ${datasetAlias} updated`);
this.loadData();
});
// Listen for filter changes
domo.onFiltersUpdate((filters) => {
console.log('Filters updated:', filters);
this.filters = filters;
this.applyFilters();
});
// 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.filterContainer([
{
column: 'category',
operator: 'IN',
values: selectedCategories,
dataType: 'STRING'
}
]);
}
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');
}
}
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>
`;
}
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();If you're using v4 and want to upgrade to the latest v6+, here's what you need to know.
v5 introduced method renames for consistency. All old method names are still supported as deprecated aliases, so your code will continue to work.
| v4 Method | v5 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 |
The main class was renamed from domo (lowercase) to Domo (PascalCase):
// v4
import domo from 'ryuu.js';
domo.get('/data/v1/sales');
// v5
import Domo from 'ryuu.js';
Domo.get('/data/v1/sales');- Request Tracking -
Domo.getRequests()andDomo.getRequest(id)for tracking async operations - Navigate Method -
Domo.navigate(route, isNewWindow)for programmatic navigation - Variables Event -
Domo.onVariablesUpdated()listener - Extend Method -
Domo.extend()for overriding methods - Better Mobile Support - Enhanced iOS and Android integration
- Fetch API - Modern replacement for XMLHttpRequest
- MessageChannel - More reliable bidirectional communication
-
Update Package:
npm install ryuu.js@latest
-
Update Imports:
// Old import domo from 'ryuu.js'; // New import Domo from 'ryuu.js';
-
Update Method Names (Optional):
// Old (still works) domo.onDataUpdate((alias) => { ... }); domo.onFiltersUpdate((filters) => { ... }); domo.filterContainer(filters); // New (recommended) Domo.onDataUpdated((alias) => { ... }); Domo.onFiltersUpdated((filters) => { ... }); Domo.requestFiltersUpdate(filters);
-
Test Thoroughly - After migration, test all functionality.
For complete v6 documentation, see v6+ Documentation.
Contributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Make your changes
- Add tests for new functionality
- Run tests:
npm test - Build:
npm run build - Commit:
git commit -m "Add my feature" - Push:
git push origin feature/my-feature - Open a Pull Request
SEE LICENSE IN LICENSE
Copyright (c) Domo
Made with ❤️ by the Domo team