Published on August 13, 2023.
Back in June, I posted a preview of a project that I've been working on called Aloe.js. Aloe is a JavaScript interpreter that's designed for running JavaScript applications outside of a Web browser. The project has come a long way since that preview, and in this post I'm going to provide an update on the project, and share an example of how its being used by one of my NetSuite clients.
I've developed Aloe so that I can develop JavaScript-based solutions, and do so without the complexity of other JavaScript interpreters. I'm primarily using it to automate batch processes, especially those where NetSuite integration is required. I'm also exploring using Aloe in IOT / industrial automation projects.
The JavaScript engine that Aloe uses is called Duktape. It's specifically designed with a focus on portability and a compact footprint. This makes it ideal for use in cases where the device involved has constrained storage and memory resources.
Aloe provides users with a way to run JavaScript from the command line. You specify what file to interpret, and Aloe does the rest.
Aloe extends the core JavaScript language with functionality such as a basic filesystem API (for reading and writing files), advanced support for cURL, encoding / decoding functions, encryption / decryption / hashing functions, and special functions to make it easy to integrate with NetSuite (including SuiteTalk REST and SuiteAPI) and S3 buckets.
Aloe can be used on macOS, Windows, and Linux-based computers. The supported Architectures include macOS (x86 64-bit, ARM 64-bit, Universal), Windows (x86 32-bit, x86 64-bit, ARM 64-bit), and Linux (x86 32-bit, x86 64-bit, ARM 32-bit, ARM 64-bit).
As I mentioned earlier, since that preview of Aloe back in June, I've made a lot of progress on the project.
Previously, you could make cURL calls via Aloe's "shell" function. But with Aloe's new advanced cURL support, you can perform a wide range of data transfer tasks - everything from making HTTP calls to Web APIs, to working with FTP servers, and more. Here's a code snippet that shows how you can use Aloe to get a directory listing from an FTP server.
curlProperties = { OptionURL: "ftp://ftp.ironforge.software", OptionUsername: "forthealliance@ironforge.software", OptionPassword: "5e4cfe8d-4717-4d62-9cb4-96552c02e840", OptionDirListOnly: true } curlRequest = { action: "perform", properties: curlProperties } curlResponse = curl( JSON.stringify( curlRequest, null, 5 ) ); curlResponse = JSON.parse( curlResponse ); writeln( 'Response Code: ' + curlResponse.responseCode ); writeln( 'Output Data: ' + curlResponse.outputData );
I've also developed a technique for extending Aloe via external "helper apps." My goal with these apps is to extend Aloe without adding any overhead to its core. I've developed a few interesting helper apps, including one that makes it easy to integrate Aloe with RabbitMQ, and another that makes it easy to work with Phidgets. I'll write more on this topic in future posts.
The most recent private beta of Aloe was released a few weeks ago, and it is being used in a handful of production environments. For example, I have a NetSuite ecommerce client that's using it to manage and monitor a large number of shipments that it makes on a daily basis. Another NetSuite client has been using Aloe since March, and their application downloads PDFs of sales orders, and uploads them to an Amazon S3 bucket.
Other developers have been using Aloe as well. One is using it to download orders from their ecommerce system and upload them into their custom order processing system, while another developer is using Aloe to download inventory data from NetSuite and upload that data to an FTP server.
My most recent Aloe-based project was for a NetSuite client that wanted a fast, reliable way to provide inventory and pricing data to their customers in the form of Excel files. The solution integrates with NetSuite via SuiteTalk REST. It uses a SuiteTalk REST's support for SuiteQL to execute a query that returns current inventory levels for all active inventory items, by location, including item information, and base and online prices.
Once the data has been retrieved, SheetJS is used to generate an Excel file. I'm also using the popular Moment JavaScript library to format the current date and time, which is included in the spreadsheet's header, and used in the Excel file's name.
A portion of the JavaScript file that I developed for my client is included down below. Their actual solution also involved saving the file to an FTP server, and also emailing copies of the spreadsheet to select corporate customers.
Here's an animation showing the application in use.
Click the image to view a larger version.
I'm planning to release the first production version of Aloe in early September. All that's left to do is complete the documentation, put together a Web site, and provide some example apps. I'll announce Aloe's release both here and on LinkedIn.
var appStartTime = performance.now(); writeln; writeln( "------------------------------------------------------------------------------------------"); writeln( "NetSuite Inventory to Excel via SuiteTalk" ); writeln( "------------------------------------------------------------------------------------------"); writeln; // Require SheetJS ( https://sheetjs.com ) require( "shim.min.js" ); var XLSX = require( "xlsx.full.min.js" ); // Require Moment ( https://momentjs.com ) var moment = require( 'moment.min.js' ); // Load NetSuite Suitetalk credentials. var ns = require( "netsuite-creds-ironforge-suitetalk.js" ); // SuiteQL query to retrieve current inventory levels for all active inventory items, by location, // including item information, base and online prices. var query = <<<EOQ SELECT Item.ID, BUILTIN.DF( AggregateItemLocation.Location ) AS Location, Item.ItemID, Item.Description, BasePrice.Price AS BasePrice, OnlinePrice.Price AS OnlinePrice, AggregateItemLocation.QuantityAvailable FROM Item INNER JOIN AggregateItemLocation ON ( AggregateItemLocation.Item = Item.ID ) INNER JOIN ItemPrice AS BasePrice ON ( BasePrice.Item = Item.ID ) AND ( BasePrice.PriceLevelName = 'Base Price' ) INNER JOIN ItemPrice AS OnlinePrice ON ( OnlinePrice.Item = Item.ID ) AND ( OnlinePrice.PriceLevelName = 'Online Price' ) WHERE ( Item.ItemType = 'InvtPart' ) AND ( Item.IsInactive = 'F' ) ORDER BY Item.ID, BUILTIN.DF( AggregateItemLocation.Location ) EOQ; // Define the request payload. var payload = { q: query } // Initialize the array of Excel rows. var rows = new Array(); // Add header rows to the array. rows.push( [ "Stormwind Logistics Corp." ] ); rows.push( [ "Inventory as of " + moment().format( "MMMM Do YYYY, h:mm a" ) ] ); rows.push( [] ); rows.push( [ "ID", "Location", "Item", "Base Price", "Online Price", "Qty Available" ] ); // Initialize the offset to use when sending the first request to SuiteTalk. var offset = 0; // Repeat until no additional records are available... do { // Set the URL to use when sending the request. var URL = ns.suiteTalkURL + "/services/rest/query/v1/suiteql?limit=1000&offset=" + ( offset ); writeln( "Retrieving data..." ); writeln( "• URL: " + URL ); var requestStartTime = performance.now(); // Specify the settings to use when sending the request. var nsConnectRequest = { accountNumber: ns.accountNumber, consumerKey: ns.consumerKey, consumerSecret: ns.consumerSecret, tokenID: ns.tokenID, tokenSecret: ns.tokenSecret, method: "POST", url: URL, connectionTimeout: 10, timeout: 10, payload: JSON.stringify( payload ) } // Send the request, and get the response. var nsConnectResponse = nsConnect( JSON.stringify( nsConnectRequest, null, 5 ) ); var requestElapsedTime = ( performance.now() - requestStartTime ) / 1000; writeln( "• Retrieved " + nsConnectResponse.count + " records in " + requestElapsedTime.toFixed(2) + " seconds. " ); // Parse the JSON-encoded response. nsConnectResponse = JSON.parse( nsConnectResponse ); // For each record returned... for (var i = 0; i < nsConnectResponse.items.length; i++) { // Add a row. var item = nsConnectResponse.items[i]; rows.push( [ item.id, item.location, item.itemid, item.baseprice, item.onlineprice, item.quantityavailable ] ); } // Set the offset to use for the next request. offset += 1000; } while ( nsConnectResponse.hasMore == true ); // Create a workbook. var workbook = XLSX.utils.book_new(); // Add a sheet to the workbook. workbook.SheetNames.push("Inventory"); // Create a worksheet, and add the rows. var worksheet = XLSX.utils.aoa_to_sheet(rows); // Assign the worksheet. workbook.Sheets["Inventory"] = worksheet; // Generate a Base64-encoded Excel file, and get its contents. var fileContent = XLSX.write( workbook, { bookType: "xlsx", type: "base64" } ); // Generate the file name. var fileName = "./inventory-" + moment().format( "YYYYMMDD-HHmm" ) + ".xlsx" // Save the file. // The "true" parameter indicates that the content is Base64-encoded, // and should be decoded by the function prior to writing it. var writeStatus = writeFile( fileName, fileContent, true); // Done! var appElapsedTime = ( performance.now() - appStartTime ) / 1000; writeln( "Process completed in " + appElapsedTime.toFixed(2) + " seconds. " );
Hello, I'm Tim Dietrich. I develop custom software for businesses that are running on NetSuite, including mobile apps, Web portals, Web APIs, and more.
I'm the developer of several popular NetSuite open source solutions, including the SuiteQL Query Tool, SuiteAPI, and more.
I founded SuiteStep, a NetSuite development studio, to provide custom software and AI solutions - and continue pushing the boundaries of what's possible on the NetSuite platform.
Copyright © 2025 Tim Dietrich.