InCountry Resident Functions Documentation
About
Resident Functions are a part of the InCountry platform. It lets you execute resident functions (JavaScript code) on protected data so that it does not leave its country of origin. All the calculations, aggregations, and validations are performed within this country and do not reach your application server located in another country. This way, you do not violate the local regulations and keep compliance with all data protection and localization laws of the data originating country.
Get started with resident functions
The Resident Functions component of the InCountry platform allows you to execute JavaScript functions against regulated data stored in the country of origin.
Management of resident functions is available on the InCountry Portal. If you need to automate the management and execution of resident functions, you can use REST API.
To get started with resident functions, please follow these steps:
Create an environment for the country where you want to execute resident functions.
Create a service of the Resident Functions type.
Use the provided credentials (Client ID and Client Secret) to manage resident functions with REST API.
Execute resident functions remotely.
Authorization of requests to REST API
For the details on how to properly authorize requests to REST API, please check our documentation.
Management of resident functions
Please check the documentation for REST API before proceeding to this section.
Management of resident functions allows you to create additional record validation and data calculation mechanisms for protected data stored in countries that impose enforced data regulation.
You can manage resident functions within the InCountry platform as follows:
publish resident functions
execute resident functions
get the list of resident functions
get a specific resident function
delete the no longer needed resident functions
Publishing a resident function
POST /serverless/publish
- Request parameters
Parameters | Type | Description |
---|---|---|
scriptName | string | Name of a resident function that is published. |
scriptBody | string | Body of the resident function. |
options | object | A JSON object with the country and forceUpdate parameters |
country | string | A country to where a resident function is published. |
forceUpdate | boolean | A flag that the resident function must be forcedly updated. |
- cURL request
curl --request POST \
--url https://{restApiURLAddress}/serverless/publish \
--header 'Authorization: Bearer <ACCESS_TOKEN>' \
--header 'Content-Type: application/json' \
--data '{
"scriptName": "{FUNCTION-NAME}",
"scriptBody": "module.exports.handler = async (storage, country, params, modules) => { const recordData = { recordKey: '\''UniqRecordKey'\'', body: params.bodyParam, }; const writeResponse = await storage.write(country, recordData); return { result: '\''ok'\'' }; };",
"options": {
"country": "se",
"forceUpdate": false
}
}'
- Responses
STATUS 201 - plain
This response is returned when the resident function has been successfully published.
{"scriptName":"{FUNCTION-NAME}","scriptBody":"module.exports.handler = async (storage, country, params, modules) => { const recordData = { recordKey: 'UniqRecordKey', body: params.bodyParam, }; const writeResponse = await storage.write(country, recordData); return { result: 'ok' }; };"}
STATUS 401 - This response is returned when the request is unauthorized.
STATUS 409 - This response is returned when the resident function with the current name already exists.
STATUS 5** - Server error.
Executing a resident functions
The endpoint executes the published resident function synchronously and returns its output and meta information.
POST /serverless/execute
- Request parameters
Parameters | Type | Description |
---|---|---|
scriptName | string | Name of a resident function that is executed. |
options | object | Object with the country parameter. |
country | string | A country where a resident function is executed. |
- cURL request
curl --request POST \
--url https://{restApiURLAddress}/serverless/execute \
--header 'Authorization: Bearer <ACCESS_TOKEN>' \
--header 'Content-Type: application/json' \
--data '{
"scriptName": "{FUNCTION-NAME}",
"options": {
"country": "se"
},
"scriptParams": {
"bodyParam": "test"
}
}'
- Responses
STATUS 201 - plain
This response is returned when the resident function has been successfully executed.
{"result":{"result":"ok"},"duration":1381,"error":null}
STATUS 400 - This response is returned when the request is incorrect.
{"result":null,"duration":775,"error":"InputValidationError: delete() Validation Error: <RecordKey> should be RecordKey but got {\"recordKey\":\"UniqRecordKey\",\"body\":\"test\"}: Record key must be a non-empty string"}
STATUS 401 - This response is returned when the request is unauthorized.
STATUS 404 - This response is returned when the resident function has not been found.
STATUS 5** - Server error.
Getting a list of resident functions
The endpoint returns a list of published resident functions. It supports pagination and does not return bodies of resident functions.
GET /serverless/functions
- Request parameters
Parameters | Type | Description |
---|---|---|
offset | object | Some items to skip before returning a list of resident functions. |
limit | string | The maximal number of resident functions to return. The maximal number is limited to 100. |
- cURL request
curl --request GET \
--url "https://{restApiURLAddress}/serverless/functions?limit=10&offset=0" \
--header 'Authorization: Bearer <ACCESS_TOKEN>'
- Responses
STATUS 201 - plain
This response returns a list of resident functions.
{"data":[{"script_name":"{FUNCTION-NAME}","created_at":"2021-03-30T12:50:45.000Z","updated_at":"2021-03-30T12:59:47.000Z"},{"script_name":"e2e-script-1-53203","created_at":"2021-03-26T10:13:30.000Z","updated_at":"2021-03-26T10:13:30.000Z"},{"script_name":"e2e-script-66046","created_at":"2021-03-26T10:13:29.000Z","updated_at":"2021-03-26T10:13:29.000Z"},{"script_name":"e2e-script-1-30449","created_at":"2021-03-26T09:02:30.000Z","updated_at":"2021-03-26T09:02:30.000Z"},{"script_name":"e2e-script-26244","created_at":"2021-03-26T09:02:29.000Z","updated_at":"2021-03-26T09:02:29.000Z"}],"meta":{"count":5,"limit":10,"offset":0,"total":5}}
STATUS 400 - This response is returned when specified parameters are incorrect.
STATUS 401 - This response is returned when the request is unauthorized.
STATUS 5** - Server error.
Getting a resident function
The endpoint returns information about a specific resident function.
GET /serverless/functions/{scriptName}
- Request parameters
Parameters | Type | Description |
---|---|---|
scriptName | string | Name of a resident function whose information should be returned. |
- cURL request
curl --request GET \
--url https://{restApiURLAddress}/serverless/functions/{FUNCTION-NAME} \
--header 'Authorization: Bearer <ACCESS_TOKEN>'
- Responses
STATUS 201 - plain
This response returns a resident function with its information.
{"script_name":"{SCRIPT-NAME}","script_body":"module.exports.handler = async (storage, country, params, modules) => { const recordData = { recordKey: 'UniqRecordKey', body: params.bodyParam, }; const writeResponse = await storage.write(country, recordData); return { result: 'ok' }; };","created_at":"2021-03-30T12:50:45.000Z","updated_at":"2021-03-30T12:59:47.000Z"}%
STATUS 401 - This response is returned when the request is unauthorized.
STATUS 404 - This response is returned when the specified resident function has not been found.
STATUS 5** - Server error.
Deleting a resident function
The endpoint deletes a specifiс resident function.
DELETE /serverless/scripts/{scriptName}
- Request parameters
Parameters | Type | Description |
---|---|---|
scriptName | string | Name of a resident function that should be deleted. |
- cURL request
curl --request DELETE \
--url https://{restApiURLAddress}/serverless/functions/{FUNCTION-NAME} \
--header 'Authorization: Bearer <ACCESS_TOKEN>'
- Responses
STATUS 204 - plain
This response is returned when the resident function has been successfully deleted.
STATUS 401 - This response is returned when the request is unauthorized.
STATUS 404 - This response is returned when the specified resident function has not been found.
STATUS 5** - Server error.
Resident function example
All resident functions should be written as a JavaScript function.
Below you can find an example of the resident function.
In the current examples, storage
is an instance of the Storage
class.
Example #1 - Checking the existence of the record with a specific email
module.exports.handler = async (storage, country, params, modules) => {
const result = await storage.find(country, { key1: params.email });
if (result.records.length > 0) {
return true;
}
return false;
};
Example #2 - Showing how to use all the methods in a junction
module.exports.handler = async (storage, country, params, modules) => {
let result;
const validateRecord = (expectedRecord, actualRecord, methodValue) => { // method to compare actual record with expected
if (expectedRecord.recordKey !== actualRecord.recordKey) {
return `bad recordKey ${methodValue}`;
}
if (expectedRecord.body !== actualRecord.body) {
return `bad body ${methodValue}`;
}
if (expectedRecord.key1 !== actualRecord.key1) {
return `bad key1 ${methodValue}`;
}
if (expectedRecord.key2 !== actualRecord.key2) {
return `bad key2 ${methodValue}`;
}
if (expectedRecord.key3 !== actualRecord.key3) {
return `bad key3 ${methodValue}`;
}
if (expectedRecord.key10 !== actualRecord.key10) {
return `bad key10 ${methodValue}`;
}
if (expectedRecord.profileKey !== actualRecord.profileKey) {
return `bad profileKey ${methodValue}`;
}
if (expectedRecord.serviceKey1 !== actualRecord.serviceKey1) {
return `bad serviceKey1 ${methodValue}`;
}
if (expectedRecord.rangeKey1 !== actualRecord.rangeKey1) {
return `bad rangeKey1 ${methodValue}`;
}
if (expectedRecord.rangeKey10 !== actualRecord.rangeKey10) {
return `bad rangeKey10 ${methodValue}`;
}
return 'ok';
};
const validateMeta = (expectedCount, expectedTotal, expectedOffset, expectedLimit, actualMeta, methodValue) => { // method to compare actual metadata with expected
if (expectedCount !== actualMeta.count) {
return `bad count ${methodValue}`;
}
if (expectedTotal !== actualMeta.total) {
return `bad total ${methodValue}`;
}
if (expectedOffset !== actualMeta.offset) {
return `bad offset ${methodValue}`;
}
if (expectedLimit !== actualMeta.limit) {
return `bad limit ${methodValue}`;
}
return 'ok';
};
const recordData = {
recordKey: params.recordKeyParam,
body: params.bodyParam,
key1: params.key1Param,
key2: params.key2Param,
key3: params.key3Param,
key10: params.key10Param,
profileKey: params.profileKeyParam,
serviceKey1: params.serviceKey1Param,
rangeKey1: params.rangeKey1Param,
rangeKey10: params.rangeKey10Param,
};
const writeResult = await storage.write(country, recordData);
const readRecord = await storage.read(country, params.recordKeyParam);
result = validateRecord(recordData, readRecord.record, 'read'); // compares if the written record matches the read one
if (result !== 'ok') {
return result;
}
const findOneRecord = await storage.findOne(country, { recordKey: params.recordKeyParam });
result = validateRecord(recordData, findOneRecord.record, 'findOne by recordKey'); // compares if the written record matches the found one by the recordKey with the findOne method
if (result !== 'ok') {
return result;
}
const findOneRecord2 = await storage.findOne(country, { key2: params.key2Param });
result = validateRecord(recordData, findOneRecord2.record, 'findOne by key2'); // compares if the written record matches the found one by the key2 with the findOne method
if (result !== 'ok') {
return result;
}
const limit = 10;
const offset = 0;
const findResponse = await storage.find(country, { recordKey: params.recordKeyParam }, { limit, offset });
const actualMeta = findResponse.meta;
result = validateMeta(1, 1, offset, limit, actualMeta, 'find by recordKey'); // compares if the written record meta matches the found record by the recordKey meta
if (result !== 'ok') {
return result;
}
const findRecord = findResponse.records[0];
result = validateRecord(recordData, findRecord, 'find by recordKey'); // compares if the written record matches the found one by the recordKey with the find method
if (result !== 'ok') {
return result;
}
const findResponse2 = await storage.find(country, { rangeKey10: params.rangeKey10Param }, { limit, offset });
const actualMeta2 = findResponse2.meta;
result = validateMeta(1, 1, offset, limit, actualMeta2, 'find by rangeKey10'); // compares if the written record meta matches the found record by the rangeKey10 meta
if (result !== 'ok') {
return result;
}
const findRecord2 = findResponse2.records[0];
result = validateRecord(recordData, findRecord2, 'find by rangeKey10'); // compares if the written record matches the found one by the rangeKey10 with the find method
if (result !== 'ok') {
return result;
}
await storage.delete(country, params.recordKeyParam); // delete record
return result;
};
Example #3 - Showing how to manage records in InCountry Point of Presence
read a record from the InCountry platform
use the record and its fields
create a new record on the InCountry platform
update the existing record on the InCountry platform
module.exports.handler = async function(storage, country, params, modules) {
// The current resident function copies all values from the parent record to its child record
const mainRecordSFDCId = params.Id;
let mainRecordICObj;
let mainRecordICBodyObj;
const childSFDCId = params.Duplicate__c;
let childRecordICObj;
let childRecordICBodyObj;
const result = {
data: null,
success: false,
error: false,
errorMsg: null,
};
try {
({
record: mainRecordICObj
} = await storage.findOne(country, {
profileKey: mainRecordSFDCId
}));
if (mainRecordICObj == null) {
result.errorMsg = "Record with Id [" + mainRecordSFDCId + "] was not found.";
} else {
mainRecordICBodyObj = JSON.parse(mainRecordICObj.body);
if (childSFDCId != null && childSFDCId.length > 0) {
({
record: childRecordICObj
} = await storage.findOne(country, {
profileKey: childSFDCId
}));
if (childRecordICObj == null) {
// Defines the case when a duplicate record is not stored on the InCountry platform
// we need to copy everything and set new unique identifiers for the duplicate record
let childRecordICObj = {
...mainRecordICObj
};
// Sets the keys fields with Salesforce Id as a unique identifier that is not yet present on the InCountry platform
childRecordICObj.key = childSFDCId;
childRecordICObj.profileKey = childSFDCId;
childRecordICObj.recordKey = childSFDCId;
childRecordICObj.body = JSON.stringify(mainRecordICBodyObj);
await storage.write(country, childRecordICObj);
} else {
// Defines the case when a duplicate record is already stored on the InCountry platform
// and all needed fields should be updated
childRecordICBodyObj = JSON.parse(childRecordICObj.body);
if (childRecordICBodyObj.Email != mainRecordICBodyObj.Email) {
childRecordICBodyObj.Email = mainRecordICBodyObj.Email;
}
if (childRecordICBodyObj.MobilePhone != mainRecordICBodyObj.MobilePhone) {
childRecordICBodyObj.MobilePhone = mainRecordICBodyObj.MobilePhone;
}
// the goal here is to update all necessary keys
// for example, the MobilePhone field was configured as key2
childRecordICObj.key2 = mainRecordICBodyObj.MobilePhone;
// this should be done separately from updating the body of the record
childRecordICObj.body = JSON.stringify(childRecordICBodyObj);
await storage.write(country, childRecordICObj);
}
}
}
} catch (error) {
result.errorMsg = error.message;
}
if (result.errorMsg != null) {
result.success = true;
}
return result;
}