InCountry logo
mobile-nav
Search
  • Products
    • Products
      • InCountry for Salesforce
      • Data Residency-as-a-Service
      • Alibaba Cloud InCountry Service
      • Compliance and security
    • Gateways
      • Email
      • Payment Vault
      • Web Forms
      • HTML
    • Developers
      • REST API
      • SDK
  • Solutions
    • Automotive
    • Energy
    • Financial services
    • Healthcare
    • Retail
    • Technology
    • Latest success story
      • IBM Consulting
  • Integrations
    • Cegid
    • Intertrust
    • MuleSoft
    • PayPal
    • Salesforce
    • ServiceNow
    • Stripe
    • Veeva Systems
    • Yandex
  • Resources
    • Country compliance
    • Documentation
    • Library
    • Partners
    • Pricing
  • About
    • News and Blog
    • Careers
    • Contact Us
    • FAQ
    • Leadership
  • Login
  • Schedule a Demo

›Resident Functions

Home
  • InCountry Platform
Portal
  • Getting started
  • Documentation
    • Dashboard
    • Managing environments
    • Managing SDK credentials and services
    • Managing Border configuration
    • Managing payment vaults
    • Managing email gateways
    • Managing resident functions
    • Managing file imports
    • Managing profile and organization
    • Managing users
    • Managing encryption keys
  • Release notes
Border
  • Documentation
  • Release notes
REST API
  • Documentation
  • How to test CRUD requests through REST API
  • Release notes
POP API
  • Documentation
  • Release notes
Resident Functions
  • Documentation
Salesforce
  • About
  • Overview
  • Quick start guide for three-model package
  • Quick start guide for legacy package
  • Administrator's guide
    • Managing the package
    • Managing permissions
    • Managing OAuth2 authentication and authorization
    • Managing certificates
    • Registering CSP Trusted Sites
    • Managing InCountry Endpoints
    • Managing REST endpoints
    • Managing InCountry flags
    • Loading the application
    • Managing data regulation policies
    • Managing protected fields
    • Hashing the UserName field
    • Managing custom objects
    • Replacing standard elements
    • Configuring record search
    • Managing components
    • Managing web components
    • Setting up Salesforce Experience Cloud
    • Managing resident functions
    • Managing InCountry cache
    • Managing Apex triggers
    • Managing record synchronization
    • Managing web forms
    • Tracking changes to data regulation policies and regulated fields
    • Using formula fields
    • Using frontend validations
    • Using Email-to-Case feature
    • Debugging
    • Migrating data from one Salesforce organization to another
  • Developer’s guide
    • Apex SDK
    • JavaScript API
    • Retrieving record statistics
    • Tracking field history
  • User's guide
    • Working with protected fields
    • Sending compliant email messages
    • Importing data into Salesforce
    • Migrating records
    • Managing audit reports
    • Converting leads
    • Managing reports
    • FAQ
    • Release notes
Payment Vault
  • Documentation
BYOK
  • Documentation
FAQ
  • Get started with the platform
  • Integration options
  • Data regulation models
  • Limits and quotas
  • Video tutorials
Service Status
  • Status

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.

Resident functions diagram

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:

  1. Sign up at InCountry Portal.

  2. Create an environment for the country where you want to execute resident functions.

  3. Create a service of the Resident Functions type.

  4. Use the provided credentials (Client ID and Client Secret) to manage resident functions with REST API.

  5. 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
ParametersTypeDescription
scriptNamestringName of a resident function that is published.
scriptBodystringBody of the resident function.
optionsobjectA JSON object with the country and forceUpdate parameters
countrystringA country to where a resident function is published.
forceUpdatebooleanA 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
ParametersTypeDescription
scriptNamestringName of a resident function that is executed.
optionsobjectObject with the country parameter.
countrystringA 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
ParametersTypeDescription
offsetobjectSome items to skip before returning a list of resident functions.
limitstringThe 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
ParametersTypeDescription
scriptNamestringName 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
ParametersTypeDescription
scriptNamestringName 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;
}
← Release notesAbout →
  • About
  • Get started with resident functions
  • Authorization of requests to REST API
  • Management of resident functions
    • Publishing a resident function
    • Executing a resident functions
    • Getting a list of resident functions
    • Getting a resident function
    • Deleting a resident function
  • Resident function example
    • Example #1 - Checking the existence of the record with a specific email
    • Example #2 - Showing how to use all the methods in a junction
    • Example #3 - Showing how to manage records in InCountry Point of Presence
InCountry logo blue
© InCountry 2022.
All rights reserved. InCountry, Inc
  • PRIVACY POLICY
  • TERMS OF SERVICE
  • Social share
    • YouTube logo
    • Facebook logo
    • Twitter logo
    • LinkedIn
  • Column 1
    • Products
      • Products
        • InCountry for Salesforce
        • Data Residency-as-a-Service
        • Alibaba Cloud InCountry Service
        • Compliance and security
      • Gateways
        • Email
        • Payment Vault
        • Web Forms
        • HTML
      • Developers
        • REST API
        • SDK
  • Column 2
    • Solutions
      • Automotive
      • Energy
      • Financial services
      • Healthcare
      • Retail
      • Technology
    • Integrations
      • Cegid
      • Intertrust
      • MuleSoft
      • PayPal
      • Salesforce
      • ServiceNow
      • Stripe
      • Veeva Systems
      • Yandex
  • Column 3
    • Resources
      • Country compliance
      • Documentation
      • Library
      • Partners
      • Pricing
    • About
      • News and Blog
      • Careers
      • Contact Us
      • FAQ
      • Leadership