n8n-nodes-netsuite-markival
Version:
NetSuite integration node for n8n with SuiteQL, RESTlet, and Raw REST support
480 lines • 22.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MrkNetsuite = void 0;
const n8n_workflow_1 = require("n8n-workflow");
const NetsuiteAuth_1 = require("./NetsuiteAuth");
const supabase_1 = require("../../credentials/config/supabase");
const LicenseCache_1 = require("./LicenseCache");
class MrkNetsuite {
constructor() {
this.description = {
displayName: 'MRK NetSuite',
name: 'mrkNetsuite',
icon: 'file:NetSuite.svg',
group: ['transform'],
defaults: {
name: 'NetSuite',
},
version: 1,
subtitle: '={{$parameter["operation"]}}',
description: 'Make NetSuite REST requests',
inputs: [{ type: "main" }],
outputs: [{ type: "main" }],
credentials: [{ name: 'netSuiteOAuth1Api', required: true }],
properties: [
{
displayName: 'Operation',
noDataExpression: true,
name: 'operation',
type: 'options',
options: [
{
name: 'SuiteQL',
value: 'suiteql',
description: 'Execute SuiteQL queries against NetSuite data',
action: 'Execute suiteql queries against netsuite data',
noDataExpression: true,
},
{
name: 'RESTlet',
value: 'restlet',
description: 'Make requests to NetSuite RESTlets',
action: 'Make requests to netsuite restlets',
noDataExpression: true,
},
{
name: 'Raw Request',
value: 'raw',
description: 'Make raw REST API requests to NetSuite endpoints',
action: 'Make raw rest api requests to netsuite endpoints',
noDataExpression: true,
},
],
default: 'suiteql',
},
{
displayName: 'Endpoint',
name: 'commonEndpoint',
type: 'options',
options: [
{ name: 'Custom', value: 'custom' },
{ name: 'Customer', value: '/services/rest/record/v1/customer' },
{ name: 'Employee', value: '/services/rest/record/v1/employee' },
{ name: 'Invoice', value: '/services/rest/record/v1/invoice' },
{ name: 'Item', value: '/services/rest/record/v1/item' },
{ name: 'Journal Entry', value: '/services/rest/record/v1/journalEntry' },
{ name: 'Sales Order', value: '/services/rest/record/v1/salesOrder' },
{ name: 'Vendor', value: '/services/rest/record/v1/vendor' },
],
displayOptions: { show: { operation: ['raw'] } },
default: 'custom',
description: 'Common NetSuite REST endpoints or choose custom to enter your own',
},
{
displayName: 'SQL Query',
name: 'sql',
type: 'string',
typeOptions: { rows: 4 },
default: "SELECT id, companyName, email FROM customer WHERE isInactive = 'F'",
displayOptions: { show: { operation: ['suiteql'] } },
description: 'SuiteQL query to execute. Use SQL-like syntax to query NetSuite records.',
},
{
displayName: 'Result Limit',
name: 'limit',
type: 'number',
typeOptions: { minValue: 1 },
default: 50,
description: 'Max number of results to return',
displayOptions: { show: { operation: ['suiteql'] } },
},
{
displayName: 'Result Offset',
name: 'offset',
type: 'number',
typeOptions: { minValue: 0 },
default: 0,
description: 'Number of results to skip (for pagination)',
displayOptions: { show: { operation: ['suiteql'] } },
},
{
displayName: 'HTTP Method',
name: 'method',
type: 'options',
options: [
{ name: 'DELETE', value: 'DELETE', description: 'Delete a record' },
{ name: 'GET', value: 'GET', description: 'Retrieve data' },
{ name: 'POST', value: 'POST', description: 'Create or update data' },
{ name: 'PUT', value: 'PUT', description: 'Update data' },
],
default: 'GET',
displayOptions: { show: { operation: ['restlet'] } },
description: 'HTTP method to use for the RESTlet request',
},
{
displayName: 'Script ID',
name: 'scriptId',
type: 'string',
default: '',
required: true,
displayOptions: { show: { operation: ['restlet'] } },
description: 'The Script ID of the NetSuite RESTlet (e.g., customscript_my_restlet)',
},
{
displayName: 'Deploy ID',
name: 'deployId',
type: 'string',
default: '',
required: true,
displayOptions: { show: { operation: ['restlet'] } },
description: 'The Deployment ID of the NetSuite RESTlet (e.g., customdeploy_my_restlet)',
},
{
displayName: 'JSON Body',
name: 'body',
type: 'json',
default: '',
displayOptions: {
show: {
operation: ['restlet'],
method: ['POST', 'PUT'],
},
},
description: 'The data to send to the RESTlet',
},
{
displayName: 'HTTP Method',
name: 'method',
type: 'options',
options: [
{ name: 'DELETE', value: 'DELETE', description: 'Delete a record' },
{ name: 'GET', value: 'GET', description: 'Retrieve records' },
{ name: 'PATCH', value: 'PATCH', description: 'Update specific fields' },
{ name: 'POST', value: 'POST', description: 'Create a new record' },
{ name: 'PUT', value: 'PUT', description: 'Update entire record' },
],
default: 'GET',
displayOptions: { show: { operation: ['raw'] } },
description: 'HTTP method to use for the request',
},
{
displayName: 'Custom Endpoint',
name: 'endpoint',
type: 'string',
default: '/services/rest/record/v1/customer',
required: true,
displayOptions: {
show: {
operation: ['raw'],
commonEndpoint: ['custom'],
},
},
description: 'Enter a custom REST API endpoint path (e.g. /services/rest/record/v1/customer)',
},
{
displayName: 'Resource Path',
name: 'resourcePath',
type: 'string',
default: '',
placeholder: 'e.g. 123 or eid:CID001/subscriptions/eid:SUID042',
displayOptions: {
show: {
operation: ['raw'],
},
},
description: 'Additional resource path to append to the endpoint (e.g. record ID, subresources)',
noDataExpression: false,
},
{
displayName: 'Query Parameters',
name: 'queryParametersUi',
type: 'fixedCollection',
typeOptions: { multipleValues: true },
placeholder: 'Add Parameter',
default: {},
options: [
{
name: 'parameter',
displayName: 'Parameter',
values: [
{ displayName: 'Name', name: 'name', type: 'string', default: '' },
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
noDataExpression: false,
},
],
},
],
description: 'Add custom query parameters to the request',
},
{
displayName: 'JSON Body',
name: 'body',
type: 'json',
default: '',
displayOptions: {
show: {
operation: ['raw'],
method: ['POST', 'PUT', 'PATCH'],
},
},
},
{
displayName: 'Response Format',
name: 'responseFormat',
type: 'options',
options: [
{
name: 'Simple',
value: 'simple',
description: 'Returns only the response body',
},
{
name: 'Full Response',
value: 'fullResponse',
description: 'Returns the full response data including headers, status code, and body',
},
],
default: 'simple',
description: 'The format of the response data',
},
{
displayName: 'Additional Headers',
name: 'headerParametersUi',
type: 'fixedCollection',
typeOptions: { multipleValues: true },
placeholder: 'Add Header',
default: {},
options: [
{
name: 'header',
displayName: 'Header',
values: [
{ displayName: 'Name', name: 'name', type: 'string', default: '' },
{ displayName: 'Value', name: 'value', type: 'string', default: '' },
],
},
],
description: 'Add custom headers to the request',
},
],
};
}
async execute() {
var _a, _b, _c, _d, _e;
const credentials = await this.getCredentials('netSuiteOAuth1Api');
const accountId = credentials.accountId;
const licenseKey = credentials.licenseKey;
try {
const isValid = await (0, LicenseCache_1.checkAndUpdateLicense)(accountId, licenseKey, async () => {
const response = await supabase_1.supabase.schema('api').rpc('validate_netsuite_license', {
p_account_id: accountId,
p_license_key: licenseKey,
});
this.logger.debug(`licenseResponse:${JSON.stringify(response)}`);
if (response.error) {
if (response.error.code === 'P0001') {
return false;
}
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `License validation failed: ${response.error.message}`);
}
return true;
}, this.logger);
if (!isValid) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid or expired license key', {
description: 'Please contact support to renew your license.',
itemIndex: 0,
});
}
}
catch (error) {
if (error instanceof n8n_workflow_1.NodeOperationError) {
throw error;
}
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `License validation error: ${error.message}`, {
description: 'An unexpected error occurred during license validation.',
itemIndex: 0,
});
}
const items = this.getInputData();
const returnData = [];
const credName = 'netSuiteOAuth1Api';
let creds;
try {
creds = await this.getCredentials(credName);
if (!(creds === null || creds === void 0 ? void 0 : creds.realm)) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No valid credentials provided! Please check your NetSuite OAuth configuration.');
}
}
catch (error) {
if (this.continueOnFail()) {
return [returnData];
}
throw error;
}
const operation = this.getNodeParameter('operation', 0);
const host = operation === 'restlet'
? `https://${creds.accountId}.restlets.api.netsuite.com`
: `https://${creds.accountId}.suitetalk.api.netsuite.com`;
for (let i = 0; i < items.length; i++) {
const operation = this.getNodeParameter('operation', i);
const responseFormat = this.getNodeParameter('responseFormat', i);
try {
let method;
let endpoint;
let body;
const qs = {};
const headers = {
'Content-Type': 'application/json',
Accept: 'application/json',
Cookie: 'NS_ROUTING_VERSION=LAGGING',
Prefer: 'transient',
};
this.getNodeParameter('headerParametersUi.header', i, []).forEach((h) => (headers[h.name] = h.value));
this.getNodeParameter('queryParametersUi.parameter', i, []).forEach((p) => (qs[p.name] = p.value));
if (operation === 'suiteql') {
method = 'POST';
endpoint = '/services/rest/query/v1/suiteql';
const sql = this.getNodeParameter('sql', i);
const limit = this.getNodeParameter('limit', i, 50);
const offset = this.getNodeParameter('offset', i);
qs.limit = limit.toString();
qs.offset = offset.toString();
body = { q: sql };
}
else if (operation === 'restlet') {
const methodStr = this.getNodeParameter('method', i);
method = methodStr;
const scriptId = this.getNodeParameter('scriptId', i);
const deployId = this.getNodeParameter('deployId', i);
endpoint = '/app/site/hosting/restlet.nl';
qs.script = scriptId;
qs.deploy = deployId;
if (['POST', 'PUT'].includes(method)) {
const bodyJson = this.getNodeParameter('body', i, '');
if (bodyJson) {
try {
body = JSON.parse(bodyJson);
}
catch (e) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid JSON in request body', {
itemIndex: i,
});
}
}
}
}
else {
const methodStr = this.getNodeParameter('method', i);
method = methodStr;
const commonEndpoint = this.getNodeParameter('commonEndpoint', i);
let path = commonEndpoint === 'custom'
? this.getNodeParameter('endpoint', i)
: commonEndpoint;
const resourcePath = this.getNodeParameter('resourcePath', i, '');
if (resourcePath && typeof resourcePath === 'string') {
path = path.replace(/\/+$/, '');
path = `${path}/${resourcePath.replace(/^\/+/, '')}`;
}
endpoint = path;
if (['POST', 'PUT', 'PATCH'].includes(method)) {
const bodyJson = this.getNodeParameter('body', i, '');
if (bodyJson) {
try {
body = JSON.parse(bodyJson);
}
catch (e) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid JSON in request body', {
itemIndex: i,
});
}
}
}
}
const url = `${host}${endpoint}`;
const { consumerKey, consumerSecret, accessToken, tokenSecret, realm } = creds;
if (!consumerKey || !consumerSecret || !accessToken || !tokenSecret || !realm) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Missing required OAuth credentials', {
itemIndex: i,
});
}
const authHeaders = (0, NetsuiteAuth_1.generateNetSuiteAuth)(method, url, consumerKey, consumerSecret, accessToken, tokenSecret, realm, qs);
const options = {
method,
url,
headers: {
...headers,
...authHeaders,
},
body,
qs,
json: true,
resolveWithFullResponse: true,
};
let response;
try {
response = await this.helpers.request(options);
const responseData = {
statusCode: response.statusCode,
headers: response.headers,
body: response.body,
};
if (response.statusCode === 204 && ((_a = response.headers) === null || _a === void 0 ? void 0 : _a.location)) {
responseData.body = {
success: true,
location: response.headers.location,
};
}
if (responseFormat === 'simple') {
returnData.push({
json: responseData.body || { success: true },
pairedItem: { item: i },
});
}
else {
returnData.push({
json: responseData,
pairedItem: { item: i },
});
}
}
catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: {
statusCode: error.statusCode || 500,
headers: ((_b = error.response) === null || _b === void 0 ? void 0 : _b.headers) || {},
body: ((_c = error.response) === null || _c === void 0 ? void 0 : _c.body) || null,
error: error.message,
},
pairedItem: { item: i },
});
continue;
}
throw error;
}
}
catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: {
error: error.message,
statusCode: error.statusCode || 500,
headers: ((_d = error.response) === null || _d === void 0 ? void 0 : _d.headers) || {},
body: ((_e = error.response) === null || _e === void 0 ? void 0 : _e.body) || null,
},
pairedItem: { item: i },
});
continue;
}
throw error;
}
}
return [returnData];
}
}
exports.MrkNetsuite = MrkNetsuite;
//# sourceMappingURL=MrkNetsuite.node.js.map