https://github.com/iagocalazans/twilio-functions-utils
This lib was created with the aim of simplifying the use of serverless Twilio.
https://github.com/iagocalazans/twilio-functions-utils
functions nodejs serverless twilio utils
Last synced: 3 months ago
JSON representation
This lib was created with the aim of simplifying the use of serverless Twilio.
- Host: GitHub
- URL: https://github.com/iagocalazans/twilio-functions-utils
- Owner: iagocalazans
- License: mit
- Created: 2022-06-10T03:45:13.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2025-07-06T04:02:04.000Z (4 months ago)
- Last Synced: 2025-08-11T00:39:16.083Z (3 months ago)
- Topics: functions, nodejs, serverless, twilio, utils
- Language: TypeScript
- Homepage: https://iagocalazans.github.io/twilio-functions-utils
- Size: 1.27 MB
- Stars: 11
- Watchers: 3
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# Twilio Functions Utils โก

   
## ๐ **Next-Generation Twilio Functions Development**
A powerful, **RxJS-powered** utility library that revolutionizes Twilio serverless function development with reactive streams, functional composition, and zero-boilerplate dependency injection.
**โจ What's New in v2.4+:**
- ๐ **Reactive Streams**: Built on RxJS for composable, testable functions
- ๐ฏ **Zero Breaking Changes**: 100% backward compatible with existing code
- ๐งช **Enhanced Testing**: Marble testing and advanced mocking capabilities
- ๐ **Two API Levels**: Simple injection API + powerful Effects API
- โก **Better Performance**: Optimized stream processing
- ๐ **Type Safe**: Full TypeScript support with proper inference
```bash
npm install twilio-functions-utils
```
## ๐ **Requirements & Compatibility**
- **Node.js**: >= 14.0.0
- **Twilio Runtime**: Compatible with Twilio Functions runtime
- **TypeScript**: >= 5.0.0 (for TypeScript projects)
- **Dependencies**: RxJS 7.8.2+, Twilio SDK 3.77.2+
### **Peer Dependencies**
- `twilio` - Twilio JavaScript SDK
- `@twilio/runtime-handler` - For local development
---
## ๐ **Development Setup**
### **Building the Project**
```bash
npm run build # Compile TypeScript to JavaScript
npm run prebuild # Clean build directory before building
```
### **Testing**
```bash
npm run test # Run Jest test suite with coverage
NODE_ENV=test npm test # Ensure test environment
```
### **Documentation**
```bash
npm run docs # Generate JSDoc documentation
```
### **Project Structure**
The library supports flexible directory structures:
- `./functions/` or `./src/functions/` for your Twilio Functions
- `./assets/` or `./src/assets/` for static assets
- Automatically detected during testing
---
## ๐ฏ **Quick Start**
### **Option 1: Simple API (Familiar & Easy)**
```javascript
const { useInjection, Response, BadRequestError, Result } = require('twilio-functions-utils');
// Provider: Your business logic
const sendSmsProvider = async function (to, message) {
const { client } = this;
try {
const result = await client.messages.create({
to,
from: '+1234567890',
body: message
});
return Result.ok({ sid: result.sid, status: 'sent' });
} catch (error) {
return Result.failed(error.message);
}
};
// Handler: Your Twilio Function
async function sendSmsHandler(event) {
const { env, providers } = this;
const { to, message } = event;
if (!to || !message) {
return new BadRequestError('Missing "to" or "message" parameters');
}
const result = await providers.sendSms(to, message);
if (result.isError) {
return new BadRequestError(result.error);
}
return new Response(result.data, 201);
}
// Export for Twilio
exports.handler = useInjection(sendSmsHandler, {
providers: { sendSms: sendSmsProvider }
});
```
### **Option 2: RxJS Effects API (Advanced & Powerful)**
```javascript
const {
twilioEffect,
injectEvent,
injectClient,
requireFields,
ok,
handleError
} = require('twilio-functions-utils');
const { switchMap, map } = require('rxjs/operators');
const sendSmsEffect = context$ =>
context$.pipe(
requireFields('to', 'message'),
injectEvent(),
injectClient(),
switchMap(([event, client]) =>
client.messages.create({
to: event.to,
from: '+1234567890',
body: event.message
})
),
map(result => ({ sid: result.sid, status: 'sent' })),
ok(),
handleError()
);
exports.handler = twilioEffect(sendSmsEffect);
```
### **TypeScript Examples**
```typescript
import {
useInjection,
Response,
BadRequestError,
Result,
InjectorFunction,
ProviderFunction
} from 'twilio-functions-utils';
// Type your environment variables
interface MyEnv {
ACCOUNT_SID: string;
AUTH_TOKEN: string;
FROM_NUMBER: string;
}
// Type your event data
interface SmsEvent {
to: string;
message: string;
}
// Type your providers
interface MyProviders {
sendSms: (to: string, message: string) => Promise>;
}
// Provider with full typing
const sendSmsProvider: ProviderFunction = async function (
to: string,
message: string
) {
const { client, env } = this;
try {
const result = await client.messages.create({
to,
from: env.FROM_NUMBER,
body: message
});
return Result.ok({ sid: result.sid });
} catch (error: any) {
return Result.failed(error.message);
}
};
// Handler with full typing
const sendSmsHandler: InjectorFunction = async function (event) {
const { env, providers } = this;
const { to, message } = event;
if (!to || !message) {
return new BadRequestError('Missing required parameters');
}
const result = await providers.sendSms(to, message);
if (result.isError) {
return new BadRequestError(result.error);
}
return new Response(result.data, 201);
};
// Export with types
export const handler = useInjection(sendSmsHandler, {
providers: { sendSms: sendSmsProvider }
});
```
### **RxJS Effects with TypeScript**
```typescript
import {
twilioEffect,
EffectWithContext,
EffectContext,
injectEvent,
injectClient,
requireFields,
ok
} from 'twilio-functions-utils';
import { switchMap, map } from 'rxjs/operators';
interface SmsEnv {
FROM_NUMBER: string;
}
interface SmsEvent {
to: string;
message: string;
}
const sendSmsEffect: EffectWithContext = (context$) =>
context$.pipe(
requireFields('to', 'message'),
injectEvent(),
injectClient(),
switchMap(([event, client]) =>
client.messages.create({
to: event.to,
from: process.env.FROM_NUMBER,
body: event.message
})
),
map(result => ({ sid: result.sid, status: 'sent' })),
ok()
);
export const handler = twilioEffect(sendSmsEffect);
```
---
## ๐ฅ **Core Features**
### **๐ญ Dependency Injection Made Simple**
Access everything you need through clean `this` context:
```javascript
async function myHandler(event) {
const {
env, // Environment variables
providers, // Your business logic
request, // HTTP headers & data
cookies // Request cookies
} = this;
// Your logic here...
}
```
### **๐ฆ Result Pattern (No More Try-Catch Hell)**
```javascript
// In your providers
const fetchUser = async function (userId) {
const { client, env } = this;
try {
const user = await client.api.accounts(env.ACCOUNT_SID)
.calls
.list({ limit: 1 });
return Result.ok(user[0]);
} catch (error) {
return Result.failed('User not found');
}
};
// In your handlers
const userResult = await this.providers.fetchUser(event.userId);
if (userResult.isError) {
return new NotFoundError(userResult.error);
}
return new Response(userResult.data);
```
### **๐ฏ Smart Response Handling**
```javascript
// JSON Responses
return new Response({ success: true, data: results }, 201);
// TwiML Responses
const twiml = new Twilio.twiml.VoiceResponse();
twiml.say('Hello from RxJS-powered Twilio!');
return new TwiMLResponse(twiml.toString());
// Error Responses
return new BadRequestError('Invalid input');
return new NotFoundError('Resource not found');
return new UnauthorizedError('Access denied');
return new InternalServerError('Something went wrong');
```
---
## ๐ **RxJS Effects API**
For advanced use cases, leverage the full power of reactive programming:
### **Composition with Operators**
```javascript
const complexWorkflow = context$ =>
context$.pipe(
// Validation
requireFields('customerId', 'action'),
authenticated(ctx => ctx.event.token),
// Data fetching
switchMap(ctx =>
ctx.providers.customerService.getProfile(ctx.event.customerId)
),
// Business logic
map(customer => ({
id: customer.id,
name: customer.name,
tier: customer.subscriptions.length > 0 ? 'premium' : 'basic'
})),
// Response formatting
apiResponse({ message: 'Profile retrieved successfully' }),
// Error handling
handleError(error => {
if (error.code === 'CUSTOMER_NOT_FOUND') {
return new NotFoundError('Customer not found');
}
return null; // Use default error handling
})
);
```
### **Built-in Operators**
```javascript
// Validation
requireFields('email', 'phone')
validateEvent(event => event.email.includes('@'))
authenticated(ctx => checkApiKey(ctx.event.apiKey))
// Data injection
injectEvent() // Get event data
injectEnv() // Get environment vars
injectClient() // Get Twilio client
injectProviders() // Get all providers
injectProvider('userService') // Get specific provider
// Response formatting
ok() // 200 response
created() // 201 response
apiResponse({ meta: { version: '1.0' } })
toTwiMLResponse() // Convert TwiML to response
// Error handling
handleError() // Comprehensive error handling
retryWithBackoff(3) // Retry failed operations
timeoutWithError(5000) // Timeout after 5 seconds
fallback(defaultValue) // Provide fallback value
```
---
## ๐งช **Testing Made Easy**
### **Simple Testing (Original API)**
```javascript
require('twilio-functions-utils/dist/lib/twilio.mock.js');
const { useMock, Response } = require('twilio-functions-utils');
const { myHandler } = require('../functions/myHandler');
const mockFn = useMock(myHandler, {
providers: {
sendSms: async (to, message) => ({ sid: 'SM123', status: 'sent' })
},
env: { ACCOUNT_SID: 'AC123' },
client: { /* mock Twilio client */ }
});
test('should send SMS successfully', async () => {
const result = await mockFn({ to: '+1234567890', message: 'Hello!' });
expect(result).toBeInstanceOf(Response);
expect(result.statusCode).toBe(201);
});
```
### **Advanced Testing (RxJS Effects)**
```javascript
const { testEffect, marbleTest, expectEmissions } = require('twilio-functions-utils');
test('should handle SMS sending with marble testing', () => {
marbleTest(({ cold, expectObservable }) => {
const context$ = cold('a|', {
a: { event: { to: '+1234567890', message: 'Test' } }
});
const result$ = sendSmsEffect(context$);
expectObservable(result$).toBe('a|', {
a: expect.objectContaining({ statusCode: 200 })
});
});
});
```
---
## ๐ **Flex Integration**
Built-in support for Twilio Flex token validation:
```javascript
// Simple API
exports.handler = useInjection(myHandler, {
providers: { taskService },
validateToken: true // Automatically validates Flex tokens
});
// RxJS API
const flexEffect = context$ =>
context$.pipe(
validateFlexToken(), // Validates token from event.Token
// ... rest of your logic
);
// Custom token validation
const customFlexEffect = context$ =>
context$.pipe(
validateFlexTokenWithOptions({
tokenField: 'customToken',
onValidation: (result) => console.log('Token validated:', result)
}),
// ... rest of your logic
);
```
---
## ๐ **Migration Guide**
### **From v2.4.x to v2.5.0: Zero Breaking Changes! ๐**
Your existing code works without any modifications:
```javascript
// This code works exactly the same in v2.5.0
const { useInjection, Response, Result } = require('twilio-functions-utils');
async function existingHandler(event) {
const result = await this.providers.existingProvider(event);
return new Response(result.data);
}
exports.handler = useInjection(existingHandler, {
providers: { existingProvider }
});
```
### **What's New in v2.5.0:**
- โ
**RxJS-Powered Architecture** - Enhanced reactive stream processing under the hood
- โ
**Advanced Effects API** - Optional RxJS Effects for complex workflows
- โ
**Enhanced Testing** - Marble testing and improved mocking capabilities
- โ
**Better Performance** - Optimized stream processing and error handling
- โ
**Full TypeScript Support** - Complete type safety with proper inference
---
## ๐ **API Reference**
### **Core Functions**
| Function | Description |
|----------|-------------|
| `useInjection(fn, options)` | Main dependency injection wrapper |
| `twilioEffect(effect, options)` | RxJS Effects wrapper |
| `useMock(fn, options)` | Testing utility (test environment only) |
### **Response Classes**
| Class | Status Code | Usage |
|-------|-------------|-------|
| `Response(body, statusCode)` | Custom | General responses |
| `TwiMLResponse(twiml)` | 200 | TwiML responses |
| `BadRequestError(message)` | 400 | Invalid input |
| `UnauthorizedError(message)` | 401 | Authentication required |
| `NotFoundError(message)` | 404 | Resource not found |
| `InternalServerError(message)` | 500 | Server errors |
### **Utility Classes**
| Class | Description |
|-------|-------------|
| `Result.ok(data)` | Success result wrapper |
| `Result.failed(error)` | Error result wrapper |
| `typeOf(value)` | Enhanced type checking |
---
## ๐ง **Troubleshooting**
### **Common Issues**
#### **"Module not found" Error**
```bash
# Ensure you've installed the package
npm install twilio-functions-utils
# For TypeScript projects, ensure proper types
npm install --save-dev typescript @types/node
```
#### **Testing Issues**
```javascript
// โ Wrong - Missing mock import
const { useMock } = require('twilio-functions-utils');
// โ
Correct - Import mock first
require('twilio-functions-utils/dist/lib/twilio.mock.js');
const { useMock } = require('twilio-functions-utils');
// Ensure NODE_ENV=test
process.env.NODE_ENV = 'test';
```
#### **RxJS Operator Issues**
```bash
# Ensure RxJS is installed
npm install rxjs@^7.8.2
# Import operators correctly
const { switchMap, map } = require('rxjs/operators');
```
#### **TypeScript Compilation Errors**
```json
// tsconfig.json - Ensure proper configuration
{
"compilerOptions": {
"target": "ES2018",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
```
#### **Runtime Context Issues**
```javascript
// โ Wrong - Arrow functions lose 'this' context
const handler = (event) => {
// 'this' is undefined here
};
// โ
Correct - Use regular functions
async function handler(event) {
// 'this' context available here
const { env, providers } = this;
}
```
### **Performance Tips**
- Use `injectMany()` instead of multiple `inject()` calls
- Leverage RxJS operators for complex data transformations
- Use `Result` pattern to avoid try-catch overhead
- Enable TypeScript strict mode for better optimization
### **Getting Help**
- ๐ Check [documentation](https://iagocalazans.github.io/twilio-functions-utils)
- ๐ Report bugs on [GitHub Issues](https://github.com/iagocalazans/twilio-functions-utils/issues)
- ๐ฌ Ask questions in [Twilio Community](https://www.twilio.com/community)
---
## ๐ค **Contributing**
We welcome contributions! Here's how you can help:
1. **๐ Report bugs** - Open an issue with reproduction steps
2. **๐ก Suggest features** - Describe your use case and proposed solution
3. **๐ Improve docs** - Help make our documentation clearer
4. **๐งช Write tests** - Add test cases for new features
5. **๐ง Submit PRs** - Follow our coding standards and include tests
---
## ๐ **Community & Support**
### **๐ Resources**
- ๐ **[Project Homepage](https://iagocalazans.github.io/twilio-functions-utils)** - Documentation and guides
- ๐ฆ **[NPM Package](https://www.npmjs.com/package/twilio-functions-utils)** - Package details and versions
- ๐ **[GitHub Repository](https://github.com/iagocalazans/twilio-functions-utils)** - Source code and issues
### **๐ Getting Help**
- ๐ **[Report Issues](https://github.com/iagocalazans/twilio-functions-utils/issues)** - Bug reports and feature requests
- ๐ฌ **[Twilio Community](https://www.twilio.com/community)** - General Twilio development discussions
- ๐ง **[Contact Author](mailto:iago.calazans@gmail.com)** - Direct support for complex issues
### **๐ค Contributing**
- ๐ **[Pull Requests](https://github.com/iagocalazans/twilio-functions-utils/pulls)** - Code contributions welcome
- ๐ **[Contributing Guide](https://github.com/iagocalazans/twilio-functions-utils/blob/master/CONTRIBUTING.md)** - How to contribute
- ๐งช **[Running Tests](https://github.com/iagocalazans/twilio-functions-utils#testing)** - Test your changes
---
## ๐ **License**
MIT License - see [LICENSE](LICENSE) file for details.
---
## ๐จโ๐ป **Author**
**[Iago Calazans](https://github.com/iagocalazans)** - Senior Node.js Engineer
---
**โญ If this library helps you build amazing Twilio Functions, give it a star! โญ**
Made with โค๏ธ and โ for the Twilio community