Streaming to a File Line-by-Line in NetSuite with SuiteScript
Created: February 6, 2024
This is a sample chapter adapted from the "Managing Files with SuiteScript 2.1" Cookbook, part of the SuiteScript by Example series.
NetSuite's
N/file
module
enables us to manipulate files within the File Cabinet in a variety of ways.
Generally, we're usually copying, receiving, or writing the entire contents
all at once.
Other times, it's more convenient to work one line at a time. Say we want to generate a CSV from some data we've retrieved from elsewhere. It could be from a search or an external system or any other source. It's much easier to work with a CSV one line at a time, and we can use NetSuite's streaming API to do so.
N/file is Server-Side Only
Before we can get to working with those files, there's a little setup work to do
first. N/file
can only be used in server-side scripts, meaning we can't
drop code in the browser console and expect it to work.
Instead, we're going to build a Suitelet that will interact with our files for us.
We'll start by building a custom module file. This is where we'll be adding and changing the code from the upcoming examples:
/**
* Custom module for executing N/file cookbook examples
*
* @NApiVersion 2.1
* @NModuleScope SameAccount
*
* @author Eric T Grubaugh <eric@stoic.software> (https://stoic.software/)
*/
define(['N/file'], (f) => {
// This is where our example code will go
})
- Create a folder in the File Cabinet at
/SuiteScripts/file-cookbook/
- Upload the above source code into the new folder in a file
named
file-cookbook.js
. - From now on, I will refer to this file as the "
file-cookbook
module".
/**
* Suitelet for testing File interactions
*
* @NApiVersion 2.1
* @NModuleScope SameAccount
* @NScriptType Suitelet
*
* @author Eric T Grubaugh <eric@stoic.software> (https://stoic.software/)
*/
define(['./file-cookbook', 'N/https'], function (fileCookbook, https) {
/** @see https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4407987288.html */
const onRequest = (context) => {
log.audit({ title: context.request.method + ' request received' })
// Ignore POST requests
if (context.request.method !== https.Method.GET) {
return
}
fileCookbook.writeFile(context.response)
log.audit({ title: 'Request complete.' })
}
return { onRequest }
})
Next:
- Use the above source code to create a second file in the same folder as before.
- Use this second file
to create a new
Suitelet
named File Interaction. - Create a Deployment
for the Suitelet; leave it in
Testing
status. - On the
Deployment, add a new
Link
in theLinks
sublist. Locate it somewhere accessible to the Role you'll be using to test the examples in this book. For me, using the Administrator Role, I choseClassic Center > Setup > Custom > File Interaction
.
⚠️ If either of these code files is named differently or is not in the same folder, you will likely receive
MODULE_NOT_FOUND
errors when attempting to access the Suitelet.
This Suitelet is our test runner for working with files. Whenever we need to test one of our code examples, we access the link for the Suitelet in NetSuite's main navigation, and we can monitor the resulting Execution Logs on the Suitelet record.
For the remainder of this article, you should not need to make any modifications to the Suitelet or its source code.
Streaming Lines to a File
/**
* Stream data to a file line-by-line
*
* @NApiVersion 2.1
* @NModuleScope SameAccount
*
* @author Eric T Grubaugh <eric@stoic.software> (https://stoic.software/)
*/
define(['N/file'], (f) => {
const WeatherData = [
{ date: '01/01/2020', high: 51, low: 19 },
{ date: '01/02/2020', high: 45, low: 27 },
{ date: '01/03/2020', high: 43, low: 20 },
{ date: '01/04/2020', high: 55, low: 22 },
{ date: '01/05/2020', high: 41, low: 26 },
{ date: '01/06/2020', high: 43, low: 30 },
{ date: '01/07/2020', high: 57, low: 31 },
{ date: '01/08/2020', high: 55, low: 23 },
{ date: '01/09/2020', high: 42, low: 26 },
{ date: '01/10/2020', high: 31, low: 13 }
]
const writeFile = (response) => {
const weatherFile = f.create({
name: 'weather.csv',
fileType: f.Type.CSV,
description: 'Stream data to a file, line by line',
folder: -15
})
const lines = WeatherData.map((w) =>
[w.date, w.low, w.high].join(',')
)
lines.forEach((w) => {
weatherFile.appendLine({ value: w })
})
weatherFile.save()
response.write({ output: weatherFile.getContents() })
}
return { writeFile }
})
Create the File
We create the File
using the
file.create
method,
except we do not specify any contents
:
const weatherFile = f.create({
name: 'weather.csv',
fileType: f.Type.CSV,
description: 'Stream data to a file, line by line',
folder: -15
})
Prepare contents
As stated previously, our data could be from any imaginable source; for this example I've created it statically within our script.
const WeatherData = [
{ date: '01/01/2020', high: 51, low: 19 },
{ date: '01/02/2020', high: 45, low: 27 },
{ date: '01/03/2020', high: 43, low: 20 },
{ date: '01/04/2020', high: 55, low: 22 },
{ date: '01/05/2020', high: 41, low: 26 },
{ date: '01/06/2020', high: 43, low: 30 },
{ date: '01/07/2020', high: 57, low: 31 },
{ date: '01/08/2020', high: 55, low: 23 },
{ date: '01/09/2020', high: 42, low: 26 },
{ date: '01/10/2020', high: 31, low: 13 }
]
From there, we map
over the Objects, turning each into a comma-separated
string of date,low,high
.
const lines = WeatherData.map((w) =>
[w.date, w.low, w.high].join(',')
)
This gives us all the lines of our CSV in an Array, each element of the Array represents one line of the file.
Stream contents to file, line-by-line
File
instances have an
appendLine
method
for writing data into them one line at a time.
We already have our Array of
lines
, so we iterate over the Array with forEach
, and invoke appendLine()
for each element in the Array.
lines.forEach((w) => {
weatherFile.appendLine({ value: w })
})
Note that
appendLine()
can only be used on Text or CSV file types, and each line can be no more than 10MB. It is also not limited to new files. We can use this method to load an existing file and append data to the end of it without disturbing the original contents.
Once all the lines are written, we use the save
method to store the File
in the File Cabinet, and we write the contents to our Suitelet response.
weatherFile.save()
response.write({ output: weatherFile.getContents() })
Refreshing the Suitelet page should now display our newly created CSV data.