@acho-inc/acho-js
v1.11.3
Published
Acho Javascript SDK
Readme
What is this repository for?
- SDK for Acho Studio API
Features
- Get data from Acho Resource by page
- Run data sync for Acho Resource
- Download data from Acho Resource
- Query Acho Resource for data
- Get Acho Resource Schema
- Create Node.js Readable Stream from Acho Resource
- Create Node.js Writable Stream from Acho Resource
- Get data from Acho Project view
- Get a list of Configured OAuth Client IDs for current organization
- Use an OAuth Client ID to issue a Bearer Token
- Connect to an Acho Published APP's instance
- Join / leave the Acho Published APP instance's socket room
- Send Event to the Acho Published APP instance
Installing
Package Manager
$ npm install @acho-inc/acho-jsIf you want to use it as a dependency in your project that needs to be built and deployed
$ npm install @acho-inc/acho-js --saveAfter the package is installed in your package.json
import { Acho } from '@acho-inc/acho-js';Example
Initializing the Acho Client
const AchoInstance = new Acho();The SDK will use the environment variables from your system
ACHO_TOKEN: The Acho develoepr API token
- If you are a current subscriber, retrieve it from your profile page
- If you want to try out the SDK without an active subscription, please contact us
ACHO_API_ENDPOINT: The service backend you are connecting to
- Default to https://kube.acho.io
- This setting is irrelevant unless you subscribe to on-premise or dedicated server
If you prefer convenience in testing, you could also initialize the instance by passing in the variables in constructor
const AchoInstance = new Acho({
apiToken: 'eyEi3oldsi....',
endpoint: 'https://kube.acho.io'
});Note: It is not recommended to expose your API token in the code base, especially on production
We highly recommend dotenv for conveniently modifying environment variables during testing
If you suspect your token might be leaked, you can invalidate the token in your profile page, or report to [email protected]
Working with Resource Endpoints
Create a new resource
const resourceResp = await AchoInstance.ResourceEndpoints.create({ name: 'test' });
/* resourceResp: {
resId: number,
assetId: number,
resource: {
id: number,
res_name: string,
res_display_name: string,
res_type: 'integration',
is_ready: 1,
create_time: unix_timestamp (UTC),
user_id: number,
update_time: unix_timestamp (UTC),
asset_id: number
}
}
*/Create a table in a resource
const resourceTableResp = await AchoInstance.ResourceEndpoints.createTable({
resId: testResId,
tableName: 'test',
schema: { col1: 'STRING', col2: 'INTEGER' }
});
/* resourceTableResp: {
resource: {
id: number,
res_name: string,
...
}
tableName: string
}
*/Stream data into a table
// JSON flavor
const writableStream = await AchoInstance.ResourceEndpoints.createWriteStream({
resId: testResId,
tableId: 'test',
dataType: 'json'
});
const testArray = [
{ col1: 'JSON_1', col2: 1 },
{ col1: 'JSON_2', col2: 2 },
{ col1: 'JSON_3', col2: 3 },
{ col1: 'JSON_4', col2: 4 }
];
await new Promise((resolve) => {
testArray.forEach((row) => {
writableStream.write(JSON.stringify(row) + '\n');
});
writableStream.end();
writableStream.on('response', (res) => {
// expect(res.statusCode).toBe(200);
resolve('done');
});
});
// CSV flavor
const writableStream = await AchoInstance.ResourceEndpoints.createWriteStream({
resId: testResId,
tableId: 'test',
dataType: 'csv'
});
const testCSV = 'CSV_1,1\nCSV_2,2\nCSV_3,3\nCSV_4,4\n';
await new Promise((resolve) => {
writableStream.write(testCSV);
writableStream.end();
writableStream.on('response', (res) => {
// expect(res.statusCode).toBe(200);
resolve('done');
});
});Note: You can also pipe readable stream into the writableStream created from a resource table
The supported formats are CSV and NDJSON.
Stream data out from the table
// create readable stream
const readableStream = await AchoInstance.ResourceEndpoints.createReadStream({
resId: testResId,
tableId: 'test'
});
readableStream
.on('data', (data) => {
// do something here with the data
// data: object
})
.on('end', () => {
readableStream.destroy();
});Use Cases
Create a Resource and a table in the Resource, then insert data into the table
Create a new resource and a resource table first
const createResourceTable = async () => {
const resourceResp = await AchoInstance.ResourceEndpoints.create({
name: 'Test Resource'
});
const { resId } = resourceResp;
// please make sure you capture the resId here if you want to do the process in two steps
console.log(resId);
const resourceTableResp = await AchoInstance.ResourceEndpoints.createTable({
resId: resId,
tableName: 'Test Table',
schema: { name: 'STRING', age: 'INTEGER' }
});
};Write data to the resource table (in this example we are using JSON)
const writeData = async () => {
const writableStream = await AchoInstance.ResourceEndpoints.createWriteStream({
// replace 1234 with the id you captured earlier
resId: 1234,
tableId: 'Test Table',
dataType: 'json'
});
const testArray = [
{ name: 'Adam', age: 28 },
{ name: 'Dan', age: 33 },
{ name: 'Jason', age: 35 },
{ name: 'Zach', age: 40 }
];
await new Promise((resolve) => {
testArray.forEach((row) => {
writableStream.write(JSON.stringify(row) + '\n');
});
writableStream.end();
writableStream.on('response', (res) => {
resolve('done');
});
});
};After finishing the previous steps, if you add "Test Table" from Resource "Test Resource" to a Project on Acho, here is what you will get.
Working with Project Endpoints
Get view data from a project by viewId
const viewData = await AchoInstance.ProjectEndpoints.getViewData({
viewId: 7869,
pageSize: 10,
page: 0
});
/* viewData: {
data: Array<object>, // Array of row data objects
schema: Array<{
name: string,
type: string
}>,
paging: {
page: number,
pageSize: number,
pageTotal: number,
totalRows: number
}
}
*/Get view data from a project by assetId
const viewData = await AchoInstance.ProjectEndpoints.getViewData({
assetId: 9242,
pageSize: 50
});
// Same response structure as aboveQuery project table data with custom SQL
const queryResult = await AchoInstance.ProjectEndpoints.queryTableData({
actionQuery: {
query: 'SELECT * FROM {{{P.9038}}} WHERE column_name = ?;',
helperInfo: {
resources: [],
projects: [],
views: [
{
view: {
id: 9038,
proj_id: 2937
}
}
]
}
},
pageSize: 100,
page: 0
});
// Returns same structure as getViewDataUsing ViewDataProducer for Efficient Data Iteration
ViewDataProducer provides an efficient way to iterate through large datasets page by page:
// Create a ViewDataProducer instance
const producer = await AchoInstance.ProjectEndpoints.getViewDataProducer({
viewId: 7869,
pageSize: 100
});
// Preview the first page without advancing
const preview = await producer.preview();
console.log('First page preview:', preview.data);
console.log('Total pages:', preview.paging.pageTotal);
// Get data page by page
const firstPage = await producer.get();
console.log('First page data:', firstPage);
// Check if there are more pages
while (producer.hasNext()) {
const nextPageData = await producer.next();
console.log('Processing page data:', nextPageData);
// Process your data here
}Advanced ViewDataProducer Usage
Process all data from a view efficiently:
const processAllViewData = async (viewId, pageSize = 1000) => {
const producer = await AchoInstance.ProjectEndpoints.getViewDataProducer({
viewId: viewId,
pageSize: pageSize
});
let processedRows = 0;
const allData = [];
// Preview to get total information
const preview = await producer.preview();
console.log(`Processing ${preview.paging.totalRows} total rows in ${preview.paging.pageTotal} pages`);
// Process first page
let pageData = await producer.get();
allData.push(...pageData);
processedRows += pageData.length;
// Process remaining pages
while (producer.hasNext()) {
pageData = await producer.next();
allData.push(...pageData);
processedRows += pageData.length;
console.log(`Processed ${processedRows} rows so far...`);
}
console.log(`Completed processing ${processedRows} total rows`);
return allData;
};
// Usage
const allViewData = await processAllViewData(7869, 500);Streaming Large Datasets with ViewDataProducer
For memory-efficient processing of large datasets:
const streamProcessViewData = async (viewId, processRowBatch) => {
const producer = await AchoInstance.ProjectEndpoints.getViewDataProducer({
viewId: viewId,
pageSize: 1000
});
// Get first batch
let batch = await producer.get();
await processRowBatch(batch);
// Process remaining batches
while (producer.hasNext()) {
batch = await producer.next();
await processRowBatch(batch);
}
};
// Usage example - transform and save data in batches
await streamProcessViewData(7869, async (rowBatch) => {
// Transform each row
const transformedRows = rowBatch.map(row => ({
...row,
processed_at: new Date().toISOString(),
// Add your transformations here
}));
// Save to database, file, or external API
await saveToDestination(transformedRows);
console.log(`Processed batch of ${transformedRows.length} rows`);
});Global Error Handling & Incident Reporting
The SDK provides comprehensive error handling and automatic incident reporting capabilities to help monitor and debug applications in production.
Quick Setup (One-Liner)
Add this single line to your application's main file (index.js, app.js, etc.):
const { enableIncidentReporting } = require('@acho-inc/acho-js');
// Automatically detects service name and environment, reports all uncaught errors
enableIncidentReporting();What it does automatically:
- Service Detection: Reads your
package.jsonname, or uses script filename - Environment Detection: Uses
NODE_ENV, container hostname, or smart defaults - Error Capture: Catches uncaught exceptions and unhandled promise rejections
- Incident Reporting: Sends detailed error reports to Acho incident management
- Non-blocking: Never crashes your app, even if reporting fails
Example auto-detection output:
[GlobalErrorHandler] Auto-detected: service="my-awesome-app", environment="production"
[GlobalErrorHandler] Installed successfullyAdvanced Configuration
const { setupGlobalErrorHandler } = require('@acho-inc/acho-js');
const errorHandler = setupGlobalErrorHandler({
service: 'my-service', // Override auto-detected service name
environment: 'production', // Override auto-detected environment
clientOptions: {
apiToken: 'your-jwt-token' // Optional: specify token explicitly
},
exitOnUncaughtException: false, // Don't exit on uncaught exceptions
beforeReport: (error, type) => {
console.log('About to report error:', error.message);
}
});
// Manual error reporting
await errorHandler.reportError(
new Error('Something went wrong'),
'processing-user-data'
);BusinessObject Error Reporting
Enable automatic error reporting for BusinessObject operations:
const businessObject = AchoInstance.businessObject({
tableName: 'employees',
enableIncidentReporting: true, // Enable automatic incident reporting
service: 'hr-system', // Optional: override service name
environment: 'staging' // Optional: override environment
});Manual Incident Reporting
For custom incident reporting:
const { IncidentReporter } = require('@acho-inc/acho-js');
const client = new AchoClient({ apiToken: 'your-token' });
const reporter = new IncidentReporter(client);
await reporter.reportIncident({
message: "Database connection failed",
severity: 'critical', // 'critical', 'high', 'medium', 'low'
source: 'database-service',
error: {
message: error.message,
stack: error.stack
},
metadata: {
connectionCount: 5,
retryAttempts: 3
}
});Environment Variables
Optional configuration via environment variables:
ACHO_TOKEN: Your API tokenACHO_ENABLE_INCIDENT_REPORTING=true: Enable reporting in BusinessObjectsSERVICE_NAME: Override service name detectionNODE_ENV/ENVIRONMENT: Override environment detection
Working with Business Objects
Business Objects provide a comprehensive interface for managing data objects within the Acho platform. This includes creating, reading, updating, and deleting data, as well as advanced features like ontology export for data relationship visualization.
Initialize a Business Object:
const businessObject = AchoInstance.businessObject({
tableName: 'your_table_name'
});Get object metadata:
const objectInfo = await businessObject.getObject();
console.log(objectInfo);Retrieve data from the object:
const data = await businessObject.getData({
pageOptions: { pageNumber: 1, pageSize: 100 },
filterOptions: {
type: 'comparison',
operator: 'stringEqualTo',
leftOperand: 'column_name',
rightOperand: 'value'
}
});Add new rows:
await businessObject.addRow({
rows: [
{ column1: 'value1', column2: 'value2' },
{ column1: 'value3', column2: 'value4' }
]
});Update existing rows:
await businessObject.updateRow({
ctid: 'row_id',
changes: {
column1: 'new_value'
}
});Export Ontology and Data Relationships
The exportOntology method allows you to export the data relationship graph for analysis and visualization:
// Export ontology as JSON (default)
const ontology = await businessObject.exportOntology();
console.log(ontology);
// Export ontology as XML with custom depth
const xmlOntology = await businessObject.exportOntology({
format: 'xml',
depth: 2
});
console.log(xmlOntology);
// Export global ontology (all relationships)
const globalOntology = await businessObject.exportOntology({
depth: 3
});Parameters:
format: Export format -'json'(default) or'xml'depth: Relationship traversal depth -number(default: 1)
The ontology export provides a comprehensive view of:
- Object relationships and dependencies
- Data structure and schema information
- Connection mappings between different data objects
- Hierarchical data organization
This feature is particularly useful for:
- Data governance and lineage tracking
- Creating data architecture diagrams
- Understanding complex data relationships
- Compliance and audit requirements
- Data migration planning
Secure Query Interface
Execute SQL queries with IAM validation. The executeQueryChecked() method validates that the user has team.arp access before executing queries:
const businessObject = AchoInstance.businessObject({
tableName: 'employees'
});
// Run secure cross-object query
const result = await businessObject.executeQueryChecked({
query: `
SELECT e.name, e.department, s.amount
FROM employees e
JOIN salaries s ON e.id = s.employee_id
WHERE e.department = 'Engineering'
`
});
console.log(result.data);
console.log(result.metadata.tablesAccessed); // ['employees', 'salaries']Features:
- ✅ Validates
team.arpaccess before execution - ✅ Parses SQL to extract table names from FROM/JOIN clauses
- ✅ Returns metadata about which tables were accessed
- ✅ Clear error messages when access is denied
Error Handling:
try {
const result = await businessObject.executeQueryChecked({
query: 'SELECT * FROM employees'
});
} catch (error) {
// Error: Access denied. You need 'team.arp' access to run queries.
// Contact your administrator to request access.
console.error(error.message);
}Note: For backward compatibility, the original executeQuery() method remains available but does not perform IAM validation.
IAM Client - Permission Checking
The SDK provides an IAM client for checking user permissions before performing operations. This enables building permission-aware UIs and improving user experience by failing fast when permissions are denied.
Initialize the Acho client:
const acho = new Acho({ apiToken: 'your-token' });Check single permission:
// Check if user has team.arp access
const canAccessArp = await acho.IAM.checkAccess('team.arp', 'read');
if (canAccessArp) {
console.log('User can access team ARP');
} else {
console.log('Access denied');
}
// Check custom business object permission
const canReadEmployees = await acho.IAM.checkAccess(
'team.arp.business_object.employees.columns',
'read'
);Batch permission checks:
// Check multiple permissions at once (more efficient)
const permissions = await acho.IAM.checkAccessBatch([
{ resource: 'team.arp', action: 'read' },
{ resource: 'team.arp.business_object.employees.rows', action: 'read' },
{ resource: 'team.arp.business_object.employees.rows', action: 'write' }
]);
permissions.forEach(perm => {
console.log(`${perm.resource} - ${perm.action}: ${perm.allowed ? 'ALLOWED' : 'DENIED'}`);
});Helper methods:
// Quick check for team.arp access
const hasArpAccess = await acho.IAM.canAccessTeamArp();
// Check business object action permission
const canDeleteEmployees = await acho.IAM.canAccessBusinessObject(
'employees',
'delete'
);Building permission-aware UIs:
import { Acho } from '@acho-inc/acho-js';
const acho = new Acho({ apiToken: 'your-token' });
// Check permissions before rendering UI
async function renderDashboard() {
const [canQuery, canEdit, canDelete] = await Promise.all([
acho.IAM.canAccessTeamArp(),
acho.IAM.checkAccess('team.arp.business_object.employees.rows', 'write'),
acho.IAM.checkAccess('team.arp.business_object.employees.rows', 'delete')
]);
// Conditionally render UI elements
if (canQuery) {
renderQueryInterface();
}
if (canEdit) {
showEditButton();
}
if (canDelete) {
showDeleteButton();
}
}Features:
- ✅ Check permissions before operations (fail fast)
- ✅ Batch permission checking for efficiency
- ✅ Helper methods for common permission checks
- ✅ Build adaptive UIs based on user permissions
Resource Discovery:
Discover available IAM resources and actions:
// Get resource tree starting from 'team' with depth 2
const resources = await acho.IAM.getResources('team', 2);
console.log(resources);
// Get available actions for business objects
const actions = await acho.IAM.getResourceActions('team.arp.business_object.employees');
console.log(actions); // ['read', 'write', 'delete', 'approve', etc.]User Roles and Permissions:
Query user roles and permissions:
// Get user's direct roles
const roles = await acho.IAM.getUserRoles('user123');
console.log(roles); // ['admin', 'analyst']
// Get user's implicit roles (including inherited)
const allRoles = await acho.IAM.getUserImplicitRoles('user123');
console.log(allRoles); // ['admin', 'analyst', 'viewer']
// Get user's implicit permissions
const permissions = await acho.IAM.getUserImplicitPermissions('user123');
console.log(permissions);Team Roles:
// Get all roles in the team
const teamRoles = await acho.IAM.getRoles();
console.log(teamRoles); // ['admin', 'analyst', 'viewer']
// Get policies for a specific role
const adminPolicies = await acho.IAM.getRolePolicies('admin');
console.log(adminPolicies);Complete API Reference:
| Method | Description |
|--------|-------------|
| checkAccess(resource, action) | Check single permission |
| checkAccessBatch(checks) | Check multiple permissions |
| canAccessTeamArp() | Quick check for team.arp access |
| canAccessBusinessObject(table, action) | Check business object permission |
| getResources(path, depth) | Get IAM resource tree |
| getResourceActions(path) | Get available actions for a resource |
| getUserRoles(userId) | Get user's direct roles |
| getUserImplicitRoles(userId) | Get user's all roles (including inherited) |
| getUserImplicitPermissions(userId) | Get user's all permissions |
| getRoles() | Get all team roles |
| getRolePolicies(role) | Get policies for a role |
