Version: 6.2.0
Last Updated: January 2025
This comprehensive guide documents the JavaScript integration in JDBX, powered by QuickJS engine.
- Overview
- Architecture
- Core API
- HTTP Endpoints
- Functions and Scripts
- Path Resolution
- Security
- Examples
JDBX integrates the QuickJS JavaScript engine to provide powerful scripting capabilities for data manipulation, validation, and transformation.
- Query Language: JavaScript-based query expressions
- User-Defined Functions: Create reusable JavaScript functions
- Document Validation: Custom validation logic
- Document Transformation: Transform data during operations
- File-based Scripts: Load and execute JavaScript files
- Secure Execution: Sandboxed JavaScript environment
JDBX uses QuickJS, a small and embeddable JavaScript engine that supports:
- ES2020 specification
- Modules (ES6 modules)
- Async/await
- TypedArrays
- BigInt
- Proxies
JavaScript support can be enabled/disabled at compile time:
# Enable JavaScript support (default)
make
# Disable JavaScript support
make ENABLE_JS=0Each JavaScript execution runs in an isolated context with:
- Limited execution time (configurable timeout)
- Restricted memory usage
- No access to system calls
- Database API exposed through global
dbobject
The database is accessible through a global db object with the following methods:
// Get all documents in a collection
const users = db.getCollection('users');
console.log(`Found ${users.length} users`);
// List all collections
const collections = db.listCollections();
console.log('Collections:', collections);// Get a document by ID
const user = db.getDocument('users', 'user123');
// Insert a new document
const newUser = {
name: 'John Doe',
email: 'john@example.com',
age: 30
};
const result = db.insertDocument('users', newUser);
console.log(`Created user with ID: ${result.id}`);
// Update a document
const updated = db.updateDocument('users', 'user123', {
age: 31,
updated_at: new Date().toISOString()
});
// Delete a document
const deleted = db.deleteDocument('users', 'user123');// Query documents with JavaScript expressions
const adults = db.queryDocuments('users', {
filter: 'doc.age >= 18'
});
// Complex queries
const results = db.queryDocuments('users', {
filter: 'doc.age > 25 && doc.status === "active"',
sort: { age: -1 },
limit: 10,
offset: 0
});// Logging
console.log('Info message');
console.error('Error message');
// JSON operations
const obj = JSON.parse('{"key": "value"}');
const str = JSON.stringify(obj);
// Array operations
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
const sum = numbers.reduce((a, b) => a + b, 0);
// Date operations
const now = new Date();
const timestamp = now.toISOString();Execute JavaScript-based queries:
POST /api/js/query
Authorization: Bearer <token>
Content-Type: application/json
{
"collection": "users",
"query": "doc.age > 30 && doc.status === 'active'"
}Response:
{
"documents": [
{"id": "user123", "name": "John", "age": 35, "status": "active"}
],
"count": 1
}Execute arbitrary JavaScript code:
POST /api/js/eval
Authorization: Bearer <token>
Content-Type: application/json
{
"code": "const users = db.getCollection('users'); users.length;"
}Response:
{
"success": true,
"result": 42
}Register a reusable JavaScript function:
POST /api/js/functions
Authorization: Bearer <token>
Content-Type: application/json
{
"name": "calculateAge",
"code": "function calculateAge(birthYear) { return new Date().getFullYear() - birthYear; }"
}Execute a registered function:
POST /api/js/functions/{name}/call
Authorization: Bearer <token>
Content-Type: application/json
{
"args": [1990]
}Get all registered functions:
GET /api/js/functions
Authorization: Bearer <token>Functions can be stored and reused:
// Register a validator function
db.registerFunction('validateEmail', `
function validateEmail(doc) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(doc.email)) {
throw new Error('Invalid email format');
}
return true;
}
`);
// Register a transformer function
db.registerFunction('normalizeUser', `
function normalizeUser(doc) {
return {
...doc,
email: doc.email.toLowerCase(),
name: doc.name.trim(),
createdAt: doc.createdAt || new Date().toISOString()
};
}
`);Load and execute JavaScript files:
// validators/user.js
function validateUser(doc) {
if (!doc.name || doc.name.length < 2) {
throw new Error('Name must be at least 2 characters');
}
if (!doc.email) {
throw new Error('Email is required');
}
return true;
}
// Register the validator
db.registerValidator('users', validateUser);Execute script file:
./bin/jdbxd -js_eval_file validators/user.jsJavaScript files are searched in the following order:
- Absolute paths: Used as-is if they start with
/ - Binary-relative paths: Relative to the server binary location
- Share directory:
<binary_dir>/../share/js/ - Current directory: As a fallback
/opt/jdbx/
├── bin/
│ └── jdbxd
├── share/
│ └── js/
│ ├── functions/
│ ├── validators/
│ └── transforms/
└── var/
└── js/
└── user/
JavaScript files are cached in memory:
- Cache is enabled by default
- Files are reloaded if modified
- Clear cache with:
db.clearJSCache()
JavaScript runs in a sandboxed environment with:
- No filesystem access
- No network access
- No process spawning
- Limited memory usage
- Execution timeout
JavaScript execution requires appropriate RBAC permissions:
js:eval- Execute arbitrary JavaScriptjs:function:create- Create functionsjs:function:call- Call functionsjs:query- Execute JavaScript queries
- Validate Input: Always validate user-provided JavaScript
- Use Functions: Prefer registered functions over eval
- Set Timeouts: Configure appropriate execution timeouts
- Limit Memory: Set memory limits for JavaScript execution
- Audit Usage: Log all JavaScript executions
// Register a comprehensive validator
db.registerFunction('validateProduct', `
function validateProduct(doc) {
const errors = [];
if (!doc.name || doc.name.trim().length < 3) {
errors.push('Product name must be at least 3 characters');
}
if (typeof doc.price !== 'number' || doc.price < 0) {
errors.push('Price must be a positive number');
}
if (!doc.category || !['electronics', 'clothing', 'food'].includes(doc.category)) {
errors.push('Invalid category');
}
if (errors.length > 0) {
throw new Error(errors.join(', '));
}
return true;
}
`);// Transform documents during query
const transformedUsers = db.queryDocuments('users', {
filter: 'doc.age >= 18',
transform: `
({
id: doc.id,
displayName: doc.firstName + ' ' + doc.lastName,
isAdult: doc.age >= 18,
ageGroup: doc.age < 30 ? 'young' : doc.age < 60 ? 'middle' : 'senior'
})
`
});// Calculate statistics
const stats = db.eval(`
const users = db.getCollection('users');
const ages = users.map(u => u.age).filter(age => age != null);
({
count: users.length,
avgAge: ages.reduce((a, b) => a + b, 0) / ages.length,
minAge: Math.min(...ages),
maxAge: Math.max(...ages)
})
`);// Bulk update with conditions
db.eval(`
const users = db.getCollection('users');
let updated = 0;
users.forEach(user => {
if (user.lastLogin < '2024-01-01') {
db.updateDocument('users', user.id, {
...user,
status: 'inactive',
notified: false
});
updated++;
}
});
console.log(\`Updated \${updated} inactive users\`);
`);// Register a custom query function
db.registerFunction('search', `
function search(collection, criteria) {
const docs = db.getCollection(collection);
return docs.filter(doc => {
// Text search
if (criteria.text) {
const searchText = criteria.text.toLowerCase();
const docText = JSON.stringify(doc).toLowerCase();
if (!docText.includes(searchText)) return false;
}
// Date range
if (criteria.dateFrom || criteria.dateTo) {
const docDate = new Date(doc.createdAt);
if (criteria.dateFrom && docDate < new Date(criteria.dateFrom)) return false;
if (criteria.dateTo && docDate > new Date(criteria.dateTo)) return false;
}
// Custom fields
for (const [key, value] of Object.entries(criteria.fields || {})) {
if (doc[key] !== value) return false;
}
return true;
});
}
`);{
"javascript": {
"enabled": true,
"timeout_ms": 5000,
"memory_limit_mb": 128,
"cache_enabled": true,
"functions_dir": "share/js/functions",
"validators_dir": "share/js/validators",
"transforms_dir": "share/js/transforms"
}
}| Variable | Description | Default |
|---|---|---|
JDBX_JS_ENABLED |
Enable JavaScript | true |
JDBX_JS_TIMEOUT |
Execution timeout (ms) | 5000 |
JDBX_JS_MEMORY_LIMIT |
Memory limit (MB) | 128 |
JDBX_JS_CACHE |
Enable file caching | true |
-
Script Not Found
- Check file path and search directories
- Ensure proper permissions on script files
- Verify JDBX_SHARE_DIR is set correctly
-
Execution Timeout
- Optimize JavaScript code
- Increase timeout setting
- Break large operations into chunks
-
Memory Limit Exceeded
- Process data in batches
- Increase memory limit
- Optimize data structures
-
Permission Denied
- Check user has js:eval permission
- Verify RBAC configuration
- Check function permissions