Develop a JavaScript Query
This section of the crash course covers Deephaven Enterprise's client APIs. This guide walks you through a simple end-to-end task:
- connecting to a Deephaven Enterprise server using JavaScript,
- opening a specific table,
- displaying its data,
- and applying a basic filter.
Note
To run these examples in your environment, you'll need to replace the following placeholder values with your actual information:
https://myserver/
- Replace with your actual Deephaven server URL- Username and password in authentication examples
'MyQuery'
- Replace with an actual Persistent Query name available on your server'my_data_table'
- Replace with an actual table name from your query
The complete example at the end of this guide contains all the necessary HTML and JavaScript code. Copy this example, make the replacements noted above, and save it as an HTML file to get started quickly.
Prerequisites
To get started, you need to load the Deephaven JS library. This file is typically loaded from the Deephaven server itself. It resides at /irisapi/irisapi.nocache.js
on the web server. In this example, we'll assume your Deephaven server is hosted at https://myserver/
.
Include this script tag in your HTML file:
<script
src="https://myserver/irisapi/irisapi.nocache.js"
type="text/javascript"
></script>
1. Connect and authenticate
First, create a dh.Client
instance and log in. This example uses password authentication.
async function connectAndAuthenticate() {
// Connect to the server (use wss for https servers)
const client = new dh.Client('wss://myserver/socket');
return new Promise((resolve, reject) => {
client.addEventListener(dh.Client.EVENT_CONNECT, () => {
// Login - replace with your actual username and password/token
client
.login({
username: 'user',
token: 'password',
type: 'password',
})
.then(
() => {
console.log('Successfully logged in!');
resolve(client);
},
(error) => {
reject(new Error('Error initializing client', { cause: error }));
}
);
});
});
}
2. Open a Specific Table
Once you have an authenticated client, you can open tables from a Persistent Query using Core+ functionality.
async function openTable(client) {
try {
// Get information about the Persistent Query
// Note: In a real app, you might fetch all known configs first
// and let the user select, or have these names configurable.
const configs = await client.getKnownConfigs();
const queryInfo = configs.find((q) => q.name === 'MyQuery');
if (!queryInfo) {
throw new Error('Persistent Query "MyQuery" not found.');
}
console.log('Opening Core+ table...');
// For Core+ queries, we need to use the Core+ API
const table = await getCorePlusTable(client, queryInfo, 'my_data_table');
console.log('Table "my_data_table" opened successfully.');
return table;
} catch (error) {
throw new Error('Error opening or processing table', { cause: error });
}
}
// Helper function to get a table from a Core+ query
async function getCorePlusTable(enterpriseClient, queryInfo, tableName) {
// Dynamically import the API from the provided URL
console.log('Importing Core+ API from:', queryInfo.jsApiUrl);
try {
const api = (await import(queryInfo.jsApiUrl)).default;
console.log('Core+ API bootstrapped successfully');
// Create an auth token for the Core+ client
const token = await enterpriseClient.createAuthToken(
'RemoteQueryProcessor'
);
// Create a Core+ client and connect
const { grpcUrl, envoyPrefix } = queryInfo;
const clientOptions = envoyPrefix
? {
headers: { 'envoy-prefix': envoyPrefix },
}
: undefined;
const corePlusClient = new api.CoreClient(grpcUrl, clientOptions);
// Login to the Core+ client
await corePlusClient.login({
type: 'io.deephaven.proto.auth.Token',
token,
});
console.log('Logged in to Core+ client');
// Get the connection and table
const connection = await corePlusClient.getAsIdeConnection();
return connection.getObject({
name: tableName,
type: 'Table',
});
} catch (error) {
throw new Error('Error getting Core+ table', { cause: error });
}
}
This code uses the Core+ API to connect to the query and fetch the table. It dynamically imports the API from the URL provided in the query information, authenticates with a token, and then retrieves the table object.
3. Display Table Data
To display data from a table, you set a viewport and get its data.
async function displayTableData(table, title = 'Table Data') {
const tableElement = document.getElementById('dataTable');
tableElement.innerHTML = ''; // Clear previous content
const caption = tableElement.createCaption();
caption.textContent = title;
const thead = tableElement.createTHead();
const headerRow = thead.insertRow();
table.columns.forEach((col) => {
const th = document.createElement('th');
th.textContent = col.name;
headerRow.appendChild(th);
});
const tbody = tableElement.createTBody();
// Set a viewport for the first 10 rows and get the subscription
const subscription = table.setViewport(0, 9, table.columns);
try {
const viewportData = await subscription.getViewportData();
viewportData.rows.forEach((dataRow) => {
const tr = tbody.insertRow();
table.columns.forEach((col) => {
const td = tr.insertCell();
td.textContent = dataRow.get(col);
});
});
console.log(`Displayed ${viewportData.rows.length} rows.`);
} finally {
subscription.close(); // Ensure subscription is closed
}
}
4. Apply a Simple Filter
async function applyFilterToTable(originalTable) {
try {
// Example: Filter for rows where a column 'Category' equals 'A'
// First, find the 'Category' column object
const categoryColumn = originalTable.columns.find(
(col) => col.name === 'Category'
);
if (!categoryColumn) {
throw new Error('Column "Category" not found for filtering.');
}
// Create a filter condition
const filterCondition = categoryColumn.filter().eq('A');
// Apply the filter. This creates a new view on the server.
// For simplicity, we'll work with the same table object which updates its underlying view.
await originalTable.applyFilter([filterCondition]);
console.log('Filter applied.');
// Re-display the table data with a new title
await displayTableData(originalTable, 'Filtered Table Data (Category A)');
// To clear filters:
// await originalTable.applyFilter([]);
} catch (error) {
throw new Error('Error applying filter', { cause: error });
}
}
5. Put it all together
async function main() {
try {
const client = await connectAndAuthenticate();
const table = await openTable(client);
// Display the unfiltered table
await displayTableData(table, 'Unfiltered Table Data');
// Apply a filter to the table (which also displays the filtered table)
await applyFilterToTable(table);
} catch (error) {
console.error('Error occurred:', error);
}
}
Complete example
Here's a full HTML page combining all the steps. Replace https://myserver/
, user
, password
, MyQuery
, my_data_table
, and column names with your actual details.
<!doctype html>
<html>
<head>
<title>Deephaven JS Crash Course</title>
<script
src="https://myserver/irisapi/irisapi.nocache.js"
type="text/javascript"
></script>
<style>
table {
border-collapse: collapse;
margin-top: 20px;
}
th,
td {
border: 1px solid black;
padding: 8px;
text-align: left;
}
caption {
font-weight: bold;
margin-bottom: 5px;
}
</style>
</head>
<body>
<h1>Deephaven JS API - Basic Table Display</h1>
<table id="dataTable"></table>
<script>
// Step 1: Connect and authenticate
async function connectAndAuthenticate() {
try {
// Connect to the server (use wss for https servers)
const client = new dh.Client('wss://myserver/socket');
// Login - replace with your actual username and password/token
await client.login({
username: 'user',
token: 'password',
type: 'password',
});
console.log('Successfully logged in!');
return client;
} catch (error) {
throw new Error('Error connecting or logging in', { cause: error });
}
}
// Step 2: Open a Specific Table
async function openTable(client) {
try {
// Get information about the Persistent Query
const queryInfo = client
.getKnownConfigs()
.find((config) => config.name === 'MyQuery');
if (!queryInfo) {
throw new Error('Persistent Query "MyQuery" not found.');
}
console.log('Opening Core+ table...');
// For Core+ queries, we need to use the Core+ API
const table = await getCorePlusTable(
client,
queryInfo,
'my_data_table'
);
console.log('Table "my_data_table" opened successfully.');
return table;
} catch (error) {
throw new Error('Error opening or processing table', {
cause: error,
});
}
}
// Helper function to get a table from a Core+ query
async function getCorePlusTable(enterpriseClient, queryInfo, tableName) {
try {
// Dynamically import the API from the provided URL
console.log('Importing Core+ API from:', queryInfo.jsApiUrl);
const api = (await import(queryInfo.jsApiUrl)).default;
console.log('Core+ API bootstrapped successfully');
// Create an auth token for the Core+ client
const token = await enterpriseClient.createAuthToken(
'RemoteQueryProcessor'
);
// Create a Core+ client and connect
const { grpcUrl, envoyPrefix } = queryInfo;
const clientOptions = envoyPrefix
? {
headers: { 'envoy-prefix': envoyPrefix },
}
: undefined;
const corePlusClient = new api.CoreClient(grpcUrl, clientOptions);
// Login to the Core+ client
await corePlusClient.login({
type: 'io.deephaven.proto.auth.Token',
token,
});
// Get the connection and table
const connection = await corePlusClient.getAsIdeConnection();
return connection.getObject({
name: tableName,
type: 'Table',
});
} catch (error) {
throw new Error('Error getting Core+ table', { cause: error });
}
}
// Step 3: Display Table Data
async function displayTableData(table, title = 'Table Data') {
const tableElement = document.getElementById('dataTable');
tableElement.innerHTML = ''; // Clear previous content
const caption = tableElement.createCaption();
caption.textContent = title;
const thead = tableElement.createTHead();
const headerRow = thead.insertRow();
table.columns.forEach((col) => {
const th = document.createElement('th');
th.textContent = col.name;
headerRow.appendChild(th);
});
const tbody = tableElement.createTBody();
// Set a viewport for the first 10 rows and get the subscription
const subscription = table.setViewport(0, 9, table.columns);
// For this basic guide, we'll use getViewportData for a one-time fetch
try {
const viewportData = await subscription.getViewportData();
viewportData.rows.forEach((dataRow) => {
const tr = tbody.insertRow();
table.columns.forEach((col) => {
const td = tr.insertCell();
td.textContent = dataRow.get(col);
});
});
console.log(`Displayed ${viewportData.rows.length} rows.`);
} finally {
subscription.close(); // Ensure subscription is closed
}
}
// Step 4: Apply a Simple Filter
async function applyFilterToTable(originalTable) {
try {
// Example: Filter for rows where a column 'Category' equals 'A'
// First, find the 'Category' column object
const categoryColumn = originalTable.columns.find(
(col) => col.name === 'Category'
);
if (!categoryColumn) {
throw new Error('Column "Category" not found for filtering.');
}
// Create a filter condition
const filterCondition = categoryColumn.filter().eq('A');
// Apply the filter. This creates a new view on the server.
await originalTable.applyFilter([filterCondition]);
console.log('Filter applied.');
// Re-display the table data with a new title
await displayTableData(
originalTable,
'Filtered Table Data (Category A)'
);
} catch (error) {
throw new Error('Error applying filter', { cause: error });
}
}
// Step 5: Main function that ties everything together
async function main() {
try {
const client = await connectAndAuthenticate();
const table = await openTable(client);
// Display the unfiltered table
await displayTableData(table, 'Unfiltered Table Data');
// Apply a filter to the table (which also displays the filtered table)
await applyFilterToTable(table);
} catch (error) {
console.error('Error occurred:', error);
}
}
// Start the application
main();
</script>
</body>
</html>
Conclusion
This crash course has walked you through the essentials of using the Deephaven JavaScript API to connect to a server, retrieve a table, display its data, and apply a simple filter. With these fundamentals, you can start exploring more advanced features and building more complex client applications.