BeamJS allows you to customize operation naming conventions to match your business domain using the operations configuration object. This enables you to create domain-specific languages for your behaviours.
// Define custom operation names for a payment domain
var paymentOperations = {
authenticate: {
key: 'Authentication',
chain: {
authenticatePayment: 'parameters',
gateway: 'service',
}
},
request: {
key: 'Request',
chain: {
checkout: 'parameters',
gateway: 'service'
}
},
query: {
key: 'Query',
chain: {
findCart: 'query',
orders: 'entity',
}
},
delete: { ... },
insert: { ... }
}
// Apply to specific behaviour
module.exports.processPayment = behaviour({
name: 'processPayment',
operations: paymentOperations,
// ... other options
}, function(init) {
return function() {
var self = init.apply(this, arguments).self();
self.catch(function(e) {
return e;
}).next().gateway().authenticatePayment([ ... ]).then(function(result, error) {
// result
}).next().gateway().checkout([ ... ]).then(function(result, error) {
// result
}).next().orders().findCart(() => [ ... ]).then(function(cart, error) {
// cart
}).next().map(function(response) {
response.cart = cart;
}).end();
};
});// server.js - Apply domain conventions globally
beam.database('main', {
type: 'mongodb',
name: 'ecommerce'
}).app(__dirname + '/behaviours', {
path: '/api/v1',
parser: 'json',
port: 8282,
operations: { ... }
});BeamJS provides flexible options for piping operation results directly to the response, with or without property mapping.
module.exports.getUsers = behaviour({
name: 'getUsers',
version: '1',
type: 'database',
path: '/users',
method: 'GET',
// Direct piping - no mapping needed
}, function(init) {
return function() {
var self = init.apply(this, arguments).self();
self.catch(function(e) {
return e;
}).next().entity(new User({
readonly: true
})).query(() => []).pipe(); // Enable direct piping
// No .map() needed - results go directly to response
};
});module.exports.getOrderSummary = behaviour({
name: 'getOrderSummary',
version: '1',
type: 'database',
path: '/orders/summary',
method: 'GET',
// Property mapping configuration
map: function(property, superProperty) {
var mappings = {
'orders': 'orderData',
'totalAmount': 'total',
'customerInfo': 'customer.details'
};
return mappings[property] || property;
}
}, function(init) {
return function() {
var self = init.apply(this, arguments).self();
self.catch(function(e) {
return e;
}).next().entity(new Order({
readonly: true
})).query(() => []).pipe();
};
});module.exports.getProducts = behaviour({
name: 'getProducts',
version: '1',
type: 'database',
path: '/products',
method: 'GET',
returns: {
products: {
key: 'products',
type: 'body'
},
count: {
key: 'totalCount',
type: 'body'
}
}
}, function(init) {
return function() {
var self = init.apply(this, arguments).self();
var {
includeCount
} = self.parameters;
self.catch(function(e) {
return e;
}).next().entity(new Product({
readonly: true
})).query(() => []).then(function(products, error) {
// Products retrieved
});
// If piping is disabled
if (!includeCount) self.pipe();
// If piping is enabled, use manual mapping
if (includeCount) {
self.next().map(function(response) {
response.products = products;
response.count = count;
}).end();
}
};
});BeamJS supports sophisticated property mapping for transforming operation results before sending to response.
// Map database fields to API response fields
map: function(property, superProperty) {
var fieldMappings = {
'_id': 'id',
'createdAt': 'created_date',
'updatedAt': 'modified_date',
'userName': 'display_name'
};
return fieldMappings[property] || property;
}// Handle nested object property mapping
map: function(property, superProperty) {
if (superProperty === 'user') {
var userMappings = {
'firstName': 'first_name',
'lastName': 'last_name',
'emailAddress': 'email'
};
return userMappings[property] || property;
}
if (superProperty === 'address') {
var addressMappings = {
'streetAddress': 'street',
'postalCode': 'zip_code'
};
return addressMappings[property] || property;
}
return property;
}map: function(property, superProperty) {
// Role-based field mapping
if (userRole === 'admin') {
var adminMappings = {
'internalId': 'id',
'secretKey': 'api_key'
};
if (adminMappings[property]) {
return adminMappings[property];
}
}
// Version-based mapping
if (apiVersion === 'v2') {
var v2Mappings = {
'user_id': 'userId',
'created_at': 'createdAt'
};
if (v2Mappings[property]) {
return v2Mappings[property];
}
}
return property;
}module.exports.myBehaviour = behaviour({
// Core identification
name: 'myBehaviour', // Required: Unique behaviour name for routes
version: '1', // Required: Behaviour version
type: 'database_with_action', // Execution type (default: database)
// Routing (for route behaviours)
path: '/api/endpoint', // URL path
method: 'POST', // Required: HTTP method for routes
host: 'api.domain.com', // Virtual host binding
direct: true // Direct endpoint operates outside policies
// Security and filtering
unless: ['login', 'register'], // Skip for these behaviours
for: ['authenticated'], // Only run for these behaviours
origins: '*', // CORS origins
maxAge: 86400, // CORS preflight cache duration
// Performance and scaling
queue: function(name, parameters) { // Custom queueing logic
return parameters.userId; // Queue by user ID
},
priority: 1, // Execution priority (0-10)
timeout: 30000, // Timeout in milliseconds
memory: 5, // 5mb a memory usage note for memory optimization
// Data handling
paginate: true, // Enable built-in pagination
storage: 'redis', // Storage selection
database: function(req) { // Dynamic database selection
return req.tenantId;
},
// Event system
events: [ // Real-time event definitions
function(name, parameters) {
return { userId: parameters.userId };
},
'global_notifications'
],
event: function(name, parameters) { // Single event definition
return `user_${parameters.userId}`;
},
// Scheduling (for job behaviours)
schedule: '0 */6 * * *', // Cron expression
// Custom operations and naming
operations: { ... },
// Input/Output definition
parameters: { // Input parameters
userId: {
key: 'userId',
type: 'path',
alternativeKey: 'user_id',
alternativeType: 'query'
},
data: {
key: 'data',
type: 'body'
},
token: {
key: 'X-Access-Token',
type: 'header'
}
},
returns: { // Output mapping
success: {
key: 'success',
type: 'body'
},
data: {
key: 'responseData',
type: 'body'
},
'X-Rate-Limit': {
key: 'rateLimit',
type: 'header'
}
},
// Property mapping
map: function(property, superProperty) {
// Custom property transformation logic
return property;
},
// Middleware and plugins
plugins: [ // Request/response plugins
function(req, res, next) {
// Pre-processing plugin
next();
}
],
plugin: function(req, res, next) { // Single plugin
// Custom processing
next();
},
// Advanced features
fetcher: 'customFetcher', // Custom data fetcher
fetching: 'externalAPI', // External data source
logger: 'customLogger', // Custom logging
// Inheritance
inherits: ParentBehaviour // Inherit from parent behaviour
}, function(init) {
return function() {
var self = init.apply(this, arguments).self();
// Behaviour implementation
};
});parameters: {
// Path parameters from URL segments
userId: {
key: 'userId',
type: 'path'
},
// Query string parameters
limit: {
key: 'limit',
type: 'query'
},
// Request body data
userData: {
key: 'userData',
type: 'body'
},
// HTTP headers
authToken: {
key: 'X-Auth-Token',
type: 'header'
},
// Middleware injected data
currentUser: {
key: 'user',
type: 'middleware'
},
// Alternative parameter sources
identifier: {
key: 'id',
type: 'path',
alternativeKey: 'identifier',
alternativeType: 'query'
}
}returns: {
// Response body data
users: {
key: 'users',
type: 'body'
},
// HTTP response headers
'X-Total-Count': {
key: 'totalCount',
type: 'header'
},
// Middleware data (for policy behaviours)
authenticatedUser: {
key: 'user',
type: 'middleware'
},
// Purpose-driven returns with conditions
token: {
key: 'accessToken',
type: 'header',
purpose: ['constant', {
as: 'parameter',
unless: ['login', 'register']
}]
}
}// Global tenant configuration
beam.app(__dirname + '/behaviours', {
tenants: {
'tenant1': {
host: 'tenant1.api.com',
path: '/tenant1/*',
id: 'tenant_1'
},
'tenant2': function(req) {
return req.get('X-Tenant-ID') === 'tenant2';
}
}
});
// Behaviour-specific database selection
database: function(req) {
var tenantId = req.get('X-Tenant-ID');
return tenantId ? `db_${tenantId}` : 'default_db';
}// remotes are other BeamJS services
beam.app({
local: __dirname + '/behaviours',
notificationService: 'http://localhost:8192/api/v1'
});// Base behaviour with common functionality
var BaseCRUDBehaviour = behaviour({
version: '1',
type: 'database_with_action',
operations: { ... }
}, function(init) {
return function() {
var self = init.apply(this, arguments).self();
// Common error handling
self.catch(function(e) {
return e;
});
return self;
};
});
// Child behaviour inheriting from base
module.exports.userBehaviour = behaviour({
name: 'userBehaviour',
inherits: BaseCRUDBehaviour,
path: '/users',
method: 'GET'
}, function(init) {
return function() {
var self = init.apply(this, arguments).self();
// Inherits common functionality
self.next().entity(new User()).query(() => [])
.then(function(users, error) {
// User-specific logic
}).next().map(function(response) {
response.users = users;
}).end();
};
});Continue reading the documentation: