Complete Guide to Compute Functions¶
Overview¶
Compute Functions are serverless JavaScript functions that run in Centrali's secure cloud environment. They enable you to:
- Process and transform data
- Implement business logic
- Integrate with external APIs
- Respond to events and triggers
- Generate reports and analytics
- Send notifications
Functions run in a secure SES (Secure ECMAScript) sandbox with controlled access to specific APIs.
Function Anatomy¶
Every Centrali function follows this structure:
exports.handler = async (event, context) => {
// Your function logic here
return {
success: true,
data: {
// Your response data
}
};
};
Input Parameters¶
Event Object¶
The event object contains the input data:
{
// Direct invocation
"data": {
"userId": "user123",
"action": "process"
},
// Trigger invocation (record change)
"trigger": {
"type": "record.created",
"structureId": "str_orders",
"record": {
"id": "rec_abc123",
"data": { /* record data */ }
}
},
// HTTP trigger
"http": {
"method": "POST",
"path": "/webhook",
"headers": { /* headers */ },
"body": { /* parsed body */ },
"query": { /* query params */ }
},
// Schedule trigger
"schedule": {
"expression": "0 9 * * MON",
"lastRun": "2024-01-15T09:00:00Z"
}
}
Context Object¶
The context object provides execution context:
{
"functionId": "fn_abc123",
"functionName": "processOrder",
"workspace": "acme",
"executionId": "exec_xyz789",
"timeout": 30000, // milliseconds
"memoryLimit": 256, // MB
"environment": {
// Your function's environment variables
"API_KEY": "secret_key",
"SERVICE_URL": "https://api.example.com"
}
}
Return Value¶
Functions must return an object with:
{
success: boolean, // Required: indicates success/failure
data: any, // Optional: response data
error: string, // Optional: error message if success=false
logs: string[], // Optional: debug logs
metrics: object // Optional: custom metrics
}
TriggerParams vs ExecutionParams¶
Understanding the difference between triggerParams and executionParams is crucial for writing flexible compute functions.
TriggerParams¶
triggerParams are static configuration parameters defined when creating a trigger. They remain constant across all executions of that trigger.
Use cases: - API keys and secrets - Configuration settings - Service endpoints - Default values - Feature flags
Example - Setting triggerParams:
curl -X POST "https://api.centrali.io/data/workspace/acme/api/v1/function-triggers" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "payment-processor",
"functionId": "fn_abc123",
"executionType": "on-demand",
"triggerMetadata": {
"params": {
"stripeApiKey": {
"value": "sk_live_abc123",
"encrypt": true
},
"webhookEndpoint": "https://api.example.com/webhook",
"retryAttempts": 3,
"enableLogging": true
}
}
}'
ExecutionParams¶
executionParams are dynamic parameters passed at the time of execution. They change with each function invocation.
Use cases: - Record IDs to process - User input data - Transaction amounts - Timestamps - Event-specific data
Example - Passing executionParams:
curl -X POST "https://api.centrali.io/data/workspace/acme/api/v1/function-triggers/{triggerId}/execute" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"params": {
"orderId": "order_123",
"amount": 99.99,
"customerId": "cust_456",
"couponCode": "SAVE20"
}
}'
Using in Your Function¶
Both parameter types are available as global variables in your function:
exports.handler = async (event, context) => {
// Access trigger params (static configuration)
const apiKey = triggerParams.stripeApiKey; // Automatically decrypted
const webhookUrl = triggerParams.webhookEndpoint;
const maxRetries = triggerParams.retryAttempts || 3;
// Access execution params (dynamic runtime data)
const orderId = executionParams.orderId;
const amount = executionParams.amount;
const customerId = executionParams.customerId;
// Use both together
const { http, centrali } = context.apis;
// Use static config with dynamic data
const paymentResult = await http.post('https://api.stripe.com/v1/charges', {
headers: {
'Authorization': `Bearer ${apiKey}` // triggerParam
},
body: {
amount: amount * 100, // executionParam (converted to cents)
currency: 'usd',
customer: customerId // executionParam
}
});
// Update record with result
await centrali.records.update(orderId, {
paymentId: paymentResult.body.id,
status: 'paid'
});
// Notify webhook if configured
if (webhookUrl) {
await http.post(webhookUrl, {
body: {
orderId,
paymentId: paymentResult.body.id,
amount
}
});
}
return {
success: true,
data: {
paymentId: paymentResult.body.id,
processed: true
}
};
};
Event-Driven Triggers¶
For event-driven triggers, the event data is automatically provided as executionParams:
exports.handler = async (event, context) => {
// For record.created event, executionParams contains:
// {
// event: "record_created",
// workspaceSlug: "acme",
// recordSlug: "orders",
// recordId: "rec_123",
// data: { /* record data */ },
// timestamp: "2025-01-15T10:30:00Z"
// }
const recordData = executionParams.data;
const recordId = executionParams.recordId;
// Use triggerParams for configuration
const shouldNotify = triggerParams.sendNotifications;
const emailTemplate = triggerParams.emailTemplate;
if (shouldNotify && recordData.customerEmail) {
await centrali.notifications.email({
to: recordData.customerEmail,
template: emailTemplate,
data: {
orderId: recordId,
...recordData
}
});
}
return { success: true };
};
Parameter Encryption¶
Sensitive triggerParams can be encrypted at rest:
// When creating trigger
{
"triggerMetadata": {
"params": {
"apiKey": {
"value": "sk_live_secret",
"encrypt": true // This will be encrypted
},
"endpoint": "https://api.example.com" // Not encrypted
}
}
}
// In your function
exports.handler = async (event, context) => {
// Encrypted params are automatically decrypted
const apiKey = triggerParams.apiKey; // Decrypted value
const endpoint = triggerParams.endpoint;
// Use normally
const response = await http.get(endpoint, {
headers: { 'Authorization': `Bearer ${apiKey}` }
});
return { success: true };
};
Re-run with Parameter Override¶
When re-running a function, you can override both types of parameters:
curl -X POST "https://api.centrali.io/data/workspace/acme/api/v1/functions/{functionId}/rerun/{runId}" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"triggerParams": {
"enableLogging": false // Override trigger param
},
"executionParams": {
"amount": 150.00 // Override execution param
}
}'
Best Practices¶
- Use triggerParams for:
- API credentials
- Service endpoints
- Feature flags
- Default configurations
-
Retry settings
-
Use executionParams for:
- Record/entity IDs
- User inputs
- Transaction data
- Timestamps
-
Event payloads
-
Security:
- Always encrypt sensitive triggerParams
- Never log sensitive parameters
-
Validate executionParams before use
-
Example Pattern:
exports.handler = async (event, context) => { // Validate execution params if (!executionParams.recordId) { return { success: false, error: 'recordId is required in executionParams' }; } // Use trigger params with defaults const retries = triggerParams.maxRetries || 3; const timeout = triggerParams.timeout || 5000; // Combine both for processing const result = await processWithRetry( executionParams.recordId, { apiKey: triggerParams.apiKey, endpoint: triggerParams.endpoint, retries, timeout } ); return { success: true, data: result }; };
Available APIs¶
Centrali SDK¶
Access Centrali's services within your function:
exports.handler = async (event, context) => {
const { centrali } = context.apis;
// Query records
const products = await centrali.records.query({
structure: 'Product',
filter: { inStock: true },
limit: 10
});
// Create a record
const order = await centrali.records.create({
structure: 'Order',
data: {
customerId: event.data.customerId,
total: 99.99,
status: 'pending'
}
});
// Update a record
await centrali.records.update('rec_xyz789', {
status: 'processing'
});
// Execute another function
const result = await centrali.functions.execute('calculateShipping', {
orderId: order.id
});
// Send email
await centrali.notifications.email({
to: 'customer@example.com',
subject: 'Order Confirmation',
body: 'Your order has been confirmed!'
});
return {
success: true,
data: { orderId: order.id }
};
};
HTTP Client¶
Make external API calls:
exports.handler = async (event, context) => {
const { http } = context.apis;
// GET request
const response = await http.get('https://api.example.com/data', {
headers: {
'Authorization': `Bearer ${context.environment.API_KEY}`
}
});
// POST request
const result = await http.post('https://api.example.com/process', {
body: {
data: event.data
},
headers: {
'Content-Type': 'application/json'
}
});
// With error handling
try {
const data = await http.get('https://api.example.com/resource');
return {
success: true,
data: data.body
};
} catch (error) {
return {
success: false,
error: `API call failed: ${error.message}`
};
}
};
Utilities¶
Built-in utilities for common tasks:
exports.handler = async (event, context) => {
const { utils } = context.apis;
// Crypto functions
const hash = utils.crypto.sha256('data');
const hmac = utils.crypto.hmac('sha256', 'data', 'secret');
const uuid = utils.crypto.uuid();
// Date/time
const now = utils.date.now();
const formatted = utils.date.format(now, 'YYYY-MM-DD');
const parsed = utils.date.parse('2024-01-15', 'YYYY-MM-DD');
// Data manipulation
const encoded = utils.base64.encode('Hello World');
const decoded = utils.base64.decode(encoded);
const urlEncoded = utils.url.encode('hello world');
// Validation
const isValid = utils.validate.email('user@example.com');
const isUrl = utils.validate.url('https://example.com');
return {
success: true,
data: { hash, uuid, formatted }
};
};
Common Patterns¶
Data Processing¶
Process and transform records:
exports.handler = async (event, context) => {
const { centrali } = context.apis;
// Get records to process
const records = await centrali.records.query({
structure: 'Invoice',
filter: { status: 'pending' }
});
// Process each record
const processed = await Promise.all(
records.map(async (record) => {
// Calculate tax
const tax = record.data.amount * 0.1;
const total = record.data.amount + tax;
// Update record
await centrali.records.update(record.id, {
tax,
total,
status: 'processed',
processedAt: new Date().toISOString()
});
return { id: record.id, total };
})
);
return {
success: true,
data: {
processedCount: processed.length,
records: processed
}
};
};
External API Integration¶
Integrate with third-party services:
exports.handler = async (event, context) => {
const { http, centrali } = context.apis;
// Get customer data
const customer = await centrali.records.get(event.data.customerId);
// Call external CRM API
const crmResponse = await http.post('https://crm.example.com/api/customers', {
body: {
email: customer.data.email,
name: customer.data.name,
source: 'centrali'
},
headers: {
'Authorization': `Bearer ${context.environment.CRM_API_KEY}`,
'Content-Type': 'application/json'
}
});
// Store CRM ID back in Centrali
await centrali.records.update(event.data.customerId, {
crmId: crmResponse.body.id,
syncedAt: new Date().toISOString()
});
return {
success: true,
data: {
crmId: crmResponse.body.id
}
};
};
Webhook Handler¶
Handle incoming webhooks:
exports.handler = async (event, context) => {
const { centrali, utils } = context.apis;
// Verify webhook signature
const signature = event.http.headers['x-webhook-signature'];
const expectedSignature = utils.crypto.hmac(
'sha256',
JSON.stringify(event.http.body),
context.environment.WEBHOOK_SECRET
);
if (signature !== expectedSignature) {
return {
success: false,
error: 'Invalid signature'
};
}
// Process webhook data
const webhookData = event.http.body;
switch (webhookData.event) {
case 'payment.completed':
await centrali.records.update(webhookData.orderId, {
paymentStatus: 'completed',
paymentId: webhookData.paymentId
});
break;
case 'payment.failed':
await centrali.records.update(webhookData.orderId, {
paymentStatus: 'failed',
error: webhookData.error
});
break;
}
return {
success: true,
data: { processed: true }
};
};
Batch Processing¶
Process large datasets efficiently:
exports.handler = async (event, context) => {
const { centrali } = context.apis;
// Process in batches to avoid timeout
const BATCH_SIZE = 100;
let offset = 0;
let hasMore = true;
let totalProcessed = 0;
while (hasMore) {
// Get batch of records
const batch = await centrali.records.query({
structure: 'User',
filter: { needsProcessing: true },
limit: BATCH_SIZE,
offset
});
if (batch.length === 0) {
hasMore = false;
break;
}
// Process batch
await Promise.all(
batch.map(async (user) => {
// Your processing logic
await processUser(user);
// Mark as processed
await centrali.records.update(user.id, {
needsProcessing: false,
processedAt: new Date().toISOString()
});
})
);
totalProcessed += batch.length;
offset += BATCH_SIZE;
// Check if we're approaching timeout
if (context.getRemainingTime() < 5000) {
// Less than 5 seconds left, stop processing
hasMore = false;
}
}
return {
success: true,
data: {
processed: totalProcessed,
hasMore
}
};
};
async function processUser(user) {
// Processing logic here
}
Error Handling¶
Implement robust error handling:
exports.handler = async (event, context) => {
const { centrali, http } = context.apis;
try {
// Validate input
if (!event.data.orderId) {
throw new Error('Order ID is required');
}
// Get order with error handling
let order;
try {
order = await centrali.records.get(event.data.orderId);
} catch (error) {
if (error.code === 'NOT_FOUND') {
return {
success: false,
error: `Order ${event.data.orderId} not found`
};
}
throw error; // Re-throw other errors
}
// External API call with retry
let apiResponse;
let retries = 3;
while (retries > 0) {
try {
apiResponse = await http.post('https://api.example.com/process', {
body: { orderId: order.id },
timeout: 5000
});
break; // Success, exit retry loop
} catch (error) {
retries--;
if (retries === 0) {
// Log error and continue with fallback
console.error('API call failed after 3 retries:', error);
apiResponse = { body: { status: 'pending' } };
} else {
// Wait before retry (exponential backoff)
await new Promise(resolve => setTimeout(resolve, Math.pow(2, 3 - retries) * 1000));
}
}
}
return {
success: true,
data: {
orderId: order.id,
status: apiResponse.body.status
}
};
} catch (error) {
// Log error for debugging
console.error('Function error:', error);
// Return user-friendly error
return {
success: false,
error: 'An error occurred processing your request',
logs: [error.stack]
};
}
};
Environment Variables¶
Store sensitive configuration as environment variables:
Setting Environment Variables¶
Via API:
curl -X PATCH "https://api.centrali.io/workspace/acme/api/v1/functions/fn_abc123" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"environment": {
"API_KEY": "sk_live_abc123",
"SERVICE_URL": "https://api.example.com",
"FEATURE_FLAG": "true"
}
}'
Using in Functions¶
exports.handler = async (event, context) => {
// Access via context.environment
const apiKey = context.environment.API_KEY;
const serviceUrl = context.environment.SERVICE_URL;
const featureEnabled = context.environment.FEATURE_FLAG === 'true';
// Use in API calls
const { http } = context.apis;
const response = await http.get(`${serviceUrl}/data`, {
headers: {
'Authorization': `Bearer ${apiKey}`
}
});
return {
success: true,
data: response.body
};
};
Testing Functions¶
Local Testing¶
Test your functions locally before deployment:
// test.js
const functionCode = require('./myFunction');
async function test() {
// Mock event
const event = {
data: {
userId: 'user123',
action: 'process'
}
};
// Mock context
const context = {
functionId: 'fn_test',
workspace: 'test',
environment: {
API_KEY: 'test_key'
},
apis: {
centrali: {
records: {
get: async (id) => ({ id, data: { name: 'Test' } }),
create: async (data) => ({ id: 'rec_new', ...data }),
update: async (id, data) => ({ id, ...data })
}
},
http: {
get: async (url) => ({ body: { success: true } }),
post: async (url, options) => ({ body: { id: 'ext_123' } })
},
utils: {
crypto: {
sha256: (data) => 'hash',
uuid: () => 'uuid-123'
}
}
}
};
// Run function
const result = await functionCode.handler(event, context);
console.log('Result:', result);
}
test().catch(console.error);
Test via API¶
# Direct execution
curl -X POST "https://api.centrali.io/workspace/acme/api/v1/functions/fn_abc123/execute" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"test": true,
"userId": "user123"
}
}'
# Dry run (doesn't save changes)
curl -X POST "https://api.centrali.io/workspace/acme/api/v1/functions/fn_abc123/test" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"event": {
"data": { "test": true }
},
"context": {
"environment": { "API_KEY": "test_key" }
}
}'
Performance Optimization¶
Best Practices¶
-
Minimize Cold Starts
-
Parallel Processing
exports.handler = async (event, context) => { const { centrali } = context.apis; // Parallel queries - faster const [users, orders, products] = await Promise.all([ centrali.records.query({ structure: 'User', limit: 100 }), centrali.records.query({ structure: 'Order', limit: 100 }), centrali.records.query({ structure: 'Product', limit: 100 }) ]); // vs Sequential - slower // const users = await centrali.records.query(...); // const orders = await centrali.records.query(...); // const products = await centrali.records.query(...); return { success: true, data: { users, orders, products } }; }; -
Efficient Queries
exports.handler = async (event, context) => { const { centrali } = context.apis; // Good: Specific fields and filters const records = await centrali.records.query({ structure: 'Order', filter: { status: 'pending', createdAt: { $gt: '2024-01-01' } }, fields: ['id', 'total', 'customerId'], limit: 50 }); // Bad: Getting all data when you need specific fields // const records = await centrali.records.query({ // structure: 'Order' // }); return { success: true, data: records }; }; -
Caching
// Simple in-memory cache const cache = new Map(); exports.handler = async (event, context) => { const { http } = context.apis; const cacheKey = `api_${event.data.endpoint}`; // Check cache if (cache.has(cacheKey)) { const cached = cache.get(cacheKey); if (cached.expiry > Date.now()) { return { success: true, data: cached.data, cached: true }; } } // Fetch fresh data const response = await http.get(event.data.endpoint); // Cache for 5 minutes cache.set(cacheKey, { data: response.body, expiry: Date.now() + 5 * 60 * 1000 }); return { success: true, data: response.body, cached: false }; };
Limitations & Quotas¶
Execution Limits¶
| Resource | Limit | Description |
|---|---|---|
| Timeout | 30 seconds | Maximum execution time |
| Memory | 256 MB | Maximum memory allocation |
| Payload Size | 6 MB | Maximum input/output size |
| Environment Variables | 4 KB | Total size of all env vars |
| Concurrent Executions | 100 | Per workspace |
API Rate Limits¶
| API | Limit | Window |
|---|---|---|
| Centrali Records | 1000 | Per minute |
| HTTP Requests | 100 | Per minute |
| Function Calls | 500 | Per minute |
Restricted Operations¶
Functions run in a secure sandbox and cannot: - Access the file system - Execute system commands - Open network sockets directly - Use Node.js modules (only provided APIs) - Access global objects like process, require, __dirname
Debugging & Monitoring¶
Logging¶
exports.handler = async (event, context) => {
console.log('Function started', {
eventData: event.data,
functionId: context.functionId
});
try {
// Your logic
const result = await processData(event.data);
console.log('Processing complete', {
resultCount: result.length
});
return {
success: true,
data: result,
logs: ['Processing completed successfully']
};
} catch (error) {
console.error('Function error:', error);
return {
success: false,
error: error.message,
logs: [
'Error occurred during processing',
error.stack
]
};
}
};
Viewing Logs¶
# Get recent executions
curl "https://api.centrali.io/workspace/acme/api/v1/functions/fn_abc123/executions" \
-H "Authorization: Bearer YOUR_API_KEY"
# Get specific execution logs
curl "https://api.centrali.io/workspace/acme/api/v1/functions/executions/exec_xyz789" \
-H "Authorization: Bearer YOUR_API_KEY"
Metrics¶
Track custom metrics:
exports.handler = async (event, context) => {
const startTime = Date.now();
let recordsProcessed = 0;
// Process records
for (const record of event.data.records) {
await processRecord(record);
recordsProcessed++;
}
const duration = Date.now() - startTime;
return {
success: true,
data: { processed: recordsProcessed },
metrics: {
duration,
recordsProcessed,
averageTime: duration / recordsProcessed
}
};
};
Security Best Practices¶
Input Validation¶
Always validate input data:
exports.handler = async (event, context) => {
// Validate required fields
if (!event.data.email || !event.data.userId) {
return {
success: false,
error: 'Email and userId are required'
};
}
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(event.data.email)) {
return {
success: false,
error: 'Invalid email format'
};
}
// Sanitize input
const sanitizedData = {
email: event.data.email.toLowerCase().trim(),
userId: event.data.userId.replace(/[^a-zA-Z0-9-]/g, '')
};
// Continue processing...
};
Secrets Management¶
Never hardcode secrets:
// BAD - Never do this
const API_KEY = 'sk_live_abc123xyz';
// GOOD - Use environment variables
exports.handler = async (event, context) => {
const API_KEY = context.environment.API_KEY;
if (!API_KEY) {
return {
success: false,
error: 'API key not configured'
};
}
// Use the API key
};
Rate Limiting¶
Implement rate limiting for expensive operations:
const rateLimits = new Map();
exports.handler = async (event, context) => {
const userId = event.data.userId;
const now = Date.now();
// Check rate limit (10 requests per minute)
const userLimits = rateLimits.get(userId) || [];
const recentRequests = userLimits.filter(time => now - time < 60000);
if (recentRequests.length >= 10) {
return {
success: false,
error: 'Rate limit exceeded. Try again later.'
};
}
// Record this request
recentRequests.push(now);
rateLimits.set(userId, recentRequests);
// Process request
// ...
};
Common Use Cases¶
1. Data Enrichment¶
Enrich records with external data:
exports.handler = async (event, context) => {
const { centrali, http } = context.apis;
// Get the newly created user
const user = event.trigger.record;
// Lookup additional data from external service
const enrichmentData = await http.get(
`https://api.clearbit.com/v2/people/find?email=${user.data.email}`,
{
headers: {
'Authorization': `Bearer ${context.environment.CLEARBIT_KEY}`
}
}
);
// Update user with enriched data
await centrali.records.update(user.id, {
company: enrichmentData.body.company.name,
title: enrichmentData.body.employment.title,
location: enrichmentData.body.geo.city,
enrichedAt: new Date().toISOString()
});
return { success: true };
};
2. Notification System¶
Send notifications based on events:
exports.handler = async (event, context) => {
const { centrali } = context.apis;
// New order created
const order = event.trigger.record;
// Get customer details
const customer = await centrali.records.get(order.data.customerId);
// Send order confirmation email
await centrali.notifications.email({
to: customer.data.email,
template: 'order-confirmation',
data: {
customerName: customer.data.name,
orderNumber: order.id,
total: order.data.total,
items: order.data.items
}
});
// Send SMS if phone number exists
if (customer.data.phone) {
await centrali.notifications.sms({
to: customer.data.phone,
message: `Your order ${order.id} has been confirmed! Total: $${order.data.total}`
});
}
// Notify admin for high-value orders
if (order.data.total > 1000) {
await centrali.notifications.slack({
channel: '#sales',
message: `High-value order received: ${order.id} - $${order.data.total}`
});
}
return { success: true };
};
3. Data Validation & Cleanup¶
Validate and clean data before saving:
exports.handler = async (event, context) => {
const { utils } = context.apis;
// Get the record being created/updated
const record = event.trigger.record;
// Validation rules
const errors = [];
// Validate email
if (record.data.email && !utils.validate.email(record.data.email)) {
errors.push('Invalid email address');
}
// Validate phone number
if (record.data.phone) {
const cleanPhone = record.data.phone.replace(/\D/g, '');
if (cleanPhone.length !== 10) {
errors.push('Phone number must be 10 digits');
}
// Update with cleaned phone
record.data.phone = cleanPhone;
}
// Validate URL
if (record.data.website && !utils.validate.url(record.data.website)) {
errors.push('Invalid website URL');
}
// Normalize data
if (record.data.email) {
record.data.email = record.data.email.toLowerCase().trim();
}
if (record.data.name) {
record.data.name = record.data.name.trim()
.split(' ')
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ');
}
if (errors.length > 0) {
return {
success: false,
error: errors.join(', ')
};
}
return {
success: true,
data: record.data // Return cleaned data
};
};
4. Scheduled Reports¶
Generate and send reports on schedule:
exports.handler = async (event, context) => {
const { centrali } = context.apis;
// Get yesterday's date range
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const startDate = new Date(yesterday.setHours(0, 0, 0, 0)).toISOString();
const endDate = new Date(yesterday.setHours(23, 59, 59, 999)).toISOString();
// Query orders from yesterday
const orders = await centrali.records.query({
structure: 'Order',
filter: {
createdAt: { $gte: startDate, $lte: endDate }
}
});
// Calculate metrics
const metrics = {
totalOrders: orders.length,
totalRevenue: orders.reduce((sum, o) => sum + o.data.total, 0),
averageOrderValue: orders.length > 0 ?
orders.reduce((sum, o) => sum + o.data.total, 0) / orders.length : 0,
topProducts: calculateTopProducts(orders),
ordersByStatus: groupByStatus(orders)
};
// Generate report
const report = generateHTMLReport(metrics, startDate);
// Email report to stakeholders
await centrali.notifications.email({
to: ['admin@example.com', 'sales@example.com'],
subject: `Daily Sales Report - ${yesterday.toDateString()}`,
html: report,
attachments: [
{
filename: 'orders.csv',
content: generateCSV(orders)
}
]
});
// Store report as record
await centrali.records.create({
structure: 'Report',
data: {
type: 'daily-sales',
date: startDate,
metrics,
sentTo: ['admin@example.com', 'sales@example.com']
}
});
return {
success: true,
data: metrics
};
};
function calculateTopProducts(orders) { /* ... */ }
function groupByStatus(orders) { /* ... */ }
function generateHTMLReport(metrics, date) { /* ... */ }
function generateCSV(orders) { /* ... */ }
Migration Guide¶
From AWS Lambda¶
// AWS Lambda
exports.handler = async (event, context) => {
const body = JSON.parse(event.body);
return {
statusCode: 200,
body: JSON.stringify({ message: 'Success' })
};
};
// Centrali Function
exports.handler = async (event, context) => {
const body = event.http.body; // Already parsed
return {
success: true,
data: { message: 'Success' }
};
};
From Vercel Functions¶
// Vercel Function
export default async function handler(req, res) {
const data = req.body;
res.status(200).json({ success: true });
}
// Centrali Function
exports.handler = async (event, context) => {
const data = event.http.body;
return {
success: true,
data: { success: true }
};
};
From Netlify Functions¶
// Netlify Function
exports.handler = async (event, context) => {
return {
statusCode: 200,
body: JSON.stringify({ message: 'Hello' })
};
};
// Centrali Function
exports.handler = async (event, context) => {
return {
success: true,
data: { message: 'Hello' }
};
};
Troubleshooting¶
Common Issues¶
-
Function Timeout
// Problem: Function times out after 30 seconds // Solution: Process in smaller batches exports.handler = async (event, context) => { const BATCH_SIZE = 50; const records = event.data.records; // Process in batches for (let i = 0; i < records.length; i += BATCH_SIZE) { const batch = records.slice(i, i + BATCH_SIZE); await processBatch(batch); // Check remaining time if (context.getRemainingTime() < 5000) { // Queue remaining for next execution await queueForProcessing(records.slice(i + BATCH_SIZE)); break; } } return { success: true }; }; -
Memory Exceeded
// Problem: Memory limit exceeded // Solution: Stream or paginate data exports.handler = async (event, context) => { const { centrali } = context.apis; // Instead of loading all records // const allRecords = await centrali.records.query({ structure: 'LargeTable' }); // Process in pages let page = 0; let hasMore = true; while (hasMore) { const batch = await centrali.records.query({ structure: 'LargeTable', limit: 100, offset: page * 100 }); if (batch.length === 0) { hasMore = false; } else { await processBatch(batch); page++; } } return { success: true }; }; -
Rate Limit Errors
// Problem: External API rate limits // Solution: Implement backoff and retry async function callAPIWithRetry(url, options, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await http.get(url, options); } catch (error) { if (error.status === 429 && i < maxRetries - 1) { // Rate limited, wait and retry const delay = Math.pow(2, i) * 1000; await new Promise(resolve => setTimeout(resolve, delay)); } else { throw error; } } } }
Next Steps¶
- Function Triggers - Automate function execution
- API Reference - Complete API documentation
- Examples - Real-world examples
- Query Guide - Learn about querying data