twilio-functions-injector-v2
v1.0.4
Published
Twilio Functions Utility Library
Downloads
5
Maintainers
Readme
Twilio Functions Utils
ABOUT
This lib was created with the aim of simplifying the use of serverless Twilio, reducing the need to apply frequent try-catches and improving context management, making it no longer necessary to return the callback() method in all functions.
Install
npm install twilio-functions-utils
HOW IT WORKS
The lib provides a function useInjection
who returns a brand function for every execution. This returned function is ready to receive the Twilio Handler arguments and make them available as this
properties as this.request
, this.cookies
, this.twilio
and this.env
at the Function level.
# useInjection(Function, Options) Function
The useInjection method takes two parameters. The first to apply as a handler and the last is an object of configuration options.
[useInjection] Function Function
Must be writen in standard format, this will be your handler
function.
function createSomeThing (event) {
...
}
[useInjection] Options.validateToken Boolean
You can pass validateToken
equal true to force Flex Token validation using Twilio Flex Token Validator
useInjection(yourFunction,
{
validateToken: true
}
);
When using Token Validator, the Request body must contain a valid Token from Twilio Flex.
// Event
{
Token: "Twilio-Token-Here"
}
# useTwilioImport(objectThis) Function
This captures the this object and prepare it for optimizing imports from Twilio Functions as Runtime.getFunctions()[path].path
.
[useTwilioImport] objectThis This
The injected through handler function this
object.
[useTwilioImport] Usage
The common way to import a function is like this:
// In other way the code with Twilio should be like this.
const { functionToImportName: functionThatUsesTwilio } = require(Runtime.getFunctions()['path-to-your-function'].path)
And with the useTwilioImport
function you should start doing like this:
export const yourHandlerFunction = async function (event) {
const { prop1, prop2 } = event;
const twilio = useTwilioImport(this);
const functionThatUsesTwilio = twilio(
TWILIO_TYPES.Functions, // Could be "TWILIO_TYPES.Assets"
'functionToImportName',
'path-to-your-function'
);
const result = await functionThatUsesTwilio({ [prop1]: prop2 });
When using this approach your functionToImportName
have an object this
with { twilio: TwilioClient }
.
async function functionThatUsesTwilio (prop) {
return this.twilio.sync.v1 // And the rest of your code.
}
# pipe(...functions) Function
The pipe method could receive as many parameters as you desire. They will be called one after another. For async methods use pipeAsync
.
[pipe] ...functions Function[]
Any sync function.
[pipe] Usage
const sum1 = (x) => x + 1;
const sum2 = (x) => x + 2;
const sum3 = (x) => x + 3;
const sum = pipe(sum1, sum2, sum3);
const result = sum(1) // return 7
// For async methods:
const asyncPiped = pipeAsync(async1, async2, async3);
const result = await asyncPiped(1) // return Promise { YOUR-VALUE }
# transformListTo(TwilioInstanceList, Function) Function
The transformListTo method takes two parameters. The first to apply as a handler and the last is a transformation function.
[transformListTo] TwilioInstanceList TwilioInstanceList
A Twilio Instance List method as twilio.calls.list
or twilio.records.list
.
[transformListTo] Function Function
A transformation function. You could use one of lib defaults as extract
or factory
.
[transformListTo] Usage
const getCallSidList = transformListTo(twilio.calls.list, extract('sid'));
const callSidList = await getCallSidList(); // returns ['CA****', 'CA****', 'CA****', 'CA****']
# transformInstanceTo(TwilioInstance, Function) Function
The transformInstanceTo method takes two parameters. The first to apply as a handler and the last is a transformation function.
[transformInstanceTo] TwilioInstance TwilioInstance
A Twilio Instance method as twilio.calls
or twilio.records
.
[transformInstanceTo] Function Function
A transformation function. You could use one of lib defaults as extract
or factory
.
[transformInstanceTo] Usage
const getToNumber = transformInstanceTo(twilio.calls, extract('to'));
const toNumber = await getToNumber('CA****'); // returns "+956798915489"
Response Class
The responses coming from the function destined to the handler must be returned as an instance of Response.
Response receives a string and a number (status code):
return new Response('Your pretty answer.', 200);
There are two failure response models, BadRequest and NotFound. Its use follows the same model.
const notFound = new NotFoundError('Your error message here.');
const badRequest = new BadRequestError('Your error message here.');
TwiMLResponse Class
There is a proper response template to use with the TwiML format:
const twimlVoice = new Twilio.twiml
.VoiceResponse();
const enqueueVoice = twimlVoice
.enqueue({
action,
workflowSid,
})
.task('{}');
return new TwiMLResponse(twimlVoice, 201)
Usage
IMPORTANT TO USE REGULAR FUNCTIONS ➜ With arrow functions it doesn't work as expected as this
cannot be injected correctly.
function yourFunctionName() {
// ...
}
Get the context
, event
and request data
just by deconstructing the object this:
const { cookies, request, env, twilio, ...YOUR_BODY_VALUES } = this
Combine multiple functions to change the final result using one of the new
transformListTo
and transformInstanceTo
methods:
// File: functions/create-action.js
const { useInjection, Response, transformListTo, extract } = require('twilio-functions-utils');
async function createAction(event) {
const { cookies, request, env, twilio, ...attributes } = this
const findFromAttributes = transformListTo(twilio.calls.list, extract('sid'))
const calls = await findFromAttributes(attributes);
return new Response(calls, 200);
}
exports.handler = useInjection(createAction, {
validateToken: true, // When using Token Validator, the Request body must contain a valid Token from Twilio.
});
EXTRAS
# typeOf(Value) Function
A simple method to discovery a value type. This is more specific then the original JavaScript typeof
.
It will return as Array
, Object
, String
, Number
, Symbol
.
[typeOf] Value *
Could be any JavaScript primitive value to be type checked.
Usage
const { typeOf } = require('twilio-functions-utils');
const type = typeOf('my name is Lorem');
const typeArray = typeOf(['one', 'two']);
const original = typeof ['one', 'two']
console.log(type) // String
console.log(typeArray) // Array
console.log(original) // object
# factory(Class) Function
A Factory method for your desired Class. It returns a function that works as the Class constructor and receive the Class constructor params.
[factory] Class Prototype
Could be any JavaScript primitive value to be type checked.
Usage
class CustomCall {
constructor({ from, to, price }) {
this.from = from;
this.to = to;
this.price = price;
}
}
const customCallFactory = factory(CustomCall);
const call = customCallFactory({ from: '+987566498965', to: '+485797955646', price: 65 })
console.log(call.price) // return 65
TESTING
# useMock(Function, Options) Function
The Twilio Serverless structure make it hard for testing sometimes. So this provides a method that works perfectly with useInjection ready functions. The useMock
act like useInjection but mocking some required fragments as getAssets
and getFunctions
.
[useMock] Function Function
The same function as used in useInjection
.
Usage
(Required) Set your jest
testing script with NODE_ENV=test
:
"scripts": {
"test": "NODE_ENV=test jest --collect-coverage --watchAll",
"start": "twilio-run",
"deploy": "twilio-run deploy"
}
Your files structures must be have assets
and functions
into first or second levels starting from src
(when in second level):
app/
├─ package.json
├─ node_modules/
├─ src/
│ ├─ functions/
│ ├─ assets/
or:
app/
├─ package.json
├─ functions/
├─ assets/
├─ node_modules/
Exports your function to be tested and your handler so it can be used by Twilio when in runtime:
async function functionToBeTested(event) {
const something = await someFunction(event)
return Response(something)
}
const handler = useInjection(functionToBeTested);
module.exports = { functionToBeTested, handler }; // <--
(Required) You always need to import the twilio.mock
for Response Twilio Global object on your testing files begining.
require('twilio-functions-utils/lib/twilio.mock');
Use Twilio Functions Utils useMock
to do the hard job and just write your tests with the generated function.
You can use Twilio.mockRequestResolvedValue
, Twilio.mockRequestImplementation
, Twilio.mockRequestRejectedValue
to Mock your Twilio API requests.
/* global describe, it, expect */
require('twilio-functions-utils/lib/twilio.mock');
const { useMock, Response } = require('twilio-functions-utils');
const { functionToBeTested } = require('../../functions/functionToBeTested'); // <-- Import here!
// Create the test function from the function to be tested
const fn = useMock(functionToBeTested, {
env: {
YOUR_ENV_VAR: 'value'
},
twilio: {
functionToMock: {}
}
});
describe('Function functionToBeTested', () => {
it('if {"someValue": true}', async () => {
const request = { TaskSid: '1234567', TaskAttributes: '{"someValue": true}' };
Twilio.mockRequestResolvedValue({
statusCode: 200,
body: {
sid: '1234567'
}
})
Twilio.mockRequestResolvedValue({
statusCode: 200,
body: {
key: "MP****",
data: { sid: '7654321' }
}
})
const res = await fn(request);
const customMap = await Runtime.getSync().maps("MP****").fetch();
expect(res).toBeInstanceOf(Response);
expect(res.body).not.toEqual(request);
expect(res.data).toEqual({ sid: '7654321' });
expect(res.body).toEqual({ sid: '1234567' });
});
});
AUTHOR
- Iago Calazans - 🛠 Senior Node.js Engineer at Stone