Skip to main content

Adaptors

An adaptor is an open-source Javascript module that provides OpenFn core users with a set of operations that help communicate with a specific external system. In the past, we often referred to them as "language packages". And the two terms are sometimes used interchangeably, but the preferred way of communicating about adaptors is as follows:

  • "What adaptor is that job using?" ("That job is using language-dhis2.")
  • "The DHIS2 adaptor is called language-dhis2."
  • "I don't have the right adaptor for this job, let me install it by running npm install @openfn/language-dhis2.

In short, most adaptors follow the naming convention @openfn/language-xyz, but not all do!

Where to find them?

On GitHub

The developed adaptors can be found in GitHub under https://github.com/openfn, starting with the prefix language-xyz. Some examples are:

On platform

When creating or editing a job on platform, you can choose the adaptor to run the job. This way you "import" the needed operations. Simply open the dropdown list of adaptors, and click on one.

On npm

Most of our adaptors are also available on npmjs.

Adaptors list in npm

Install on platform via npm

When using platform, you can install adaptors that are not part of the recommended adaptors picklist directly from npm.

To install from npm, click on the cloud download icon next to the adaptor version picklist. In the Select Unreleased Adaptor dialog box, enter the adaptor name(e.g. dhis2 for language-dhis2) and the corresponding version number(e.g. v2.3.4), as listed on npmjs, for the adaptor of your choice. The platform will attempt to install the selected adaptor version it can be used to run the specified job.

Note that, after this custom installation of the adaptor, platform will not add this adaptor version to the picklist of recommended adaptors in JobStudio, but you are guaranteed that the adaptor will be available to use in any of your jobs as long as you specify it via the Select Unreleased Adaptor dialog.

Developing adaptors

You can develop a new adaptor from scratch or extend an existing one.

Extending an adaptor

Extending an adaptor equals adding one or multiple new operations. The new operations can be added inside the src/Adaptor.js file of the adaptor.

Developing a new adaptor

Developing a new adaptor can be done by cloning the template available here via the USE THIS TEMPLATE button over on Github.

Use this template button

Default operation

This template contains a default create operation that can be customized according to the objectives of the new adaptor.

export function create(path, params, callback) {
return state => {
// expand references for the data argument with state
// do the work
// return state
};
}

General considerations

Using @openfn/language-common.http

In many cases, it's useful to be able to execute regular HTTP requests inside a specialized adaptor (e.g. posting the output of a Primero case fetch to an OpenFn inbox). Considering these types of situations, language-common implements and exports an http module. To use http in any job executed with a specific adaptor consider exporting it directly from language-common.

...
export {
alterState,
...
http,
...
sourceValue,
} from '@openfn/language-common';

Import language-common from npm

To leverage the last tested available version of our language-common adaptor, consider importing the one published in npm through @openfn/language-common.

Accordingly, your package.json should add a dependency to that version as this (snippet taken from language-postgresql):

{
"dependencies": {
"@openfn/language-common": "1.2.6",
"pg": "^8.3.2",
"pg-format": "^1.0.4"
},
...rest
}

Build and tests

Build

Building an adaptor for release is done by running the command npm run build from the root folder.

Tests

Tests can be written with nock under the path test/index.js.

describe('createPatient', () => {
before(() => {
nock('https://fakepatient.server.com')
.post('/api/patients')
.reply(200, (uri, requestBody) => {
return { ...requestBody, fullName: 'Mamadou', gender: 'M' };
});
});

it('makes a post request to the patient endpoint', async () => {
const state = {
configuration: {
baseUrl: 'https://fakepatient.server.com',
username: 'hello',
password: 'there',
},
data: {
fullName: 'Mamadou',
gender: 'M',
},
};

const finalState = await execute(
create('api/patients', {
name: dataValue('fullName')(state),
gender: dataValue('gender')(state),
})
)(state);

expect(finalState.data).to.eql({
fullName: 'Mamadou',
gender: 'M',
});
});
});

Run your tests with npm run test. Tests are written to assess dummy calls on the available helper functions.

 ~/devtools/adaptors/adaptor > npm run test

> language-template@1.0.0 test /home/taylor/devtools/adaptors/adaptor
> mocha --require @babel/register



execute
✓ executes each operation in sequence
✓ assigns references, data to the initialState

create
✓ makes a post request to the right endpoint
✓ throws an error for a 404
✓ handles and throws different kinds of errors

createPatient
✓ makes a post request to the patient endpoint


6 passing (16ms)

~/devtools/adaptors/adaptor >

When writing tests, bear in mind as well for scenarios that could trigger errors.

describe('create', () => {
before(() => {
nock('https://fake.server.com')
.post('/api/noAccess')
.reply(404, (uri, requestBody) => {
return { detail: 'Not found.' };
});

it('throws an error for a 404', async () => {
const state = {
configuration: {
baseUrl: 'https://fake.server.com',
username: 'hello',
password: 'there',
},
};
const error = await execute(create('api/noAccess', { name: 'taylor' }))(
state
).catch(error => {
return error;
});
expect(error.message).to.eql('Request failed with status code 404');
});

});