The fastest and simplest library for SQLite3 in Node.js with synchronous API for high-performance database operations
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Extension system for creating custom SQL functions, aggregate functions, and virtual tables to extend SQLite functionality.
Create custom SQL functions that can be called from within SQL queries.
/**
* Define user-defined function
* @param {string} name - Function name (used in SQL queries)
* @param {Object} [options] - Function configuration options
* @param {Function} fn - JavaScript function implementation
* @returns {Database} Database instance for chaining
*/
function(name, options, fn);
interface FunctionOptions {
safeIntegers?: boolean; // Use safe integers for this function (default: database setting)
deterministic?: boolean; // Function is deterministic (default: false)
directOnly?: boolean; // Function can only be called directly, not in triggers/views (default: false)
varargs?: boolean; // Function accepts variable arguments (default: false)
}Usage Examples:
// Simple string function
db.function('reverse', (str) => {
return typeof str === 'string' ? str.split('').reverse().join('') : null;
});
// Use in SQL query
const result = db.prepare('SELECT reverse(name) as reversed_name FROM users').all();
console.log(result); // [{ reversed_name: 'ecilA' }, { reversed_name: 'boB' }, ...]
// Math function with multiple parameters
db.function('pythagoras', (a, b) => {
if (typeof a !== 'number' || typeof b !== 'number') return null;
return Math.sqrt(a * a + b * b);
});
const distance = db.prepare('SELECT pythagoras(3, 4) as distance').get();
console.log(distance.distance); // 5
// Function with options
db.function('random_int', {
deterministic: false, // Result can vary between calls
directOnly: true // Can't be used in triggers or views
}, (min, max) => {
if (typeof min !== 'number' || typeof max !== 'number') return null;
return Math.floor(Math.random() * (max - min + 1)) + min;
});
// Variable arguments function
db.function('concat_with_separator', {
varargs: true
}, (separator, ...args) => {
if (typeof separator !== 'string') return null;
return args.filter(arg => arg != null).join(separator);
});
const concatenated = db.prepare("SELECT concat_with_separator(' | ', name, email, city) as info FROM users").all();Create custom aggregate functions for GROUP BY operations and window functions.
/**
* Define user-defined aggregate function
* @param {string} name - Aggregate function name
* @param {Object} options - Aggregate configuration (required)
* @returns {Database} Database instance for chaining
*/
aggregate(name, options);
interface AggregateOptions {
start?: any; // Initial accumulator value (default: null)
step: Function; // Step function called for each row (required)
inverse?: Function; // Inverse function for window functions (optional)
result?: Function; // Final result transformation function (optional)
safeIntegers?: boolean; // Use safe integers (default: database setting)
deterministic?: boolean; // Function is deterministic (default: false)
directOnly?: boolean; // Function can only be called directly (default: false)
varargs?: boolean; // Function accepts variable arguments (default: false)
}Usage Examples:
// String concatenation aggregate
db.aggregate('group_concat_custom', {
start: '',
step: (accumulator, value) => {
if (value == null) return accumulator;
return accumulator === '' ? String(value) : accumulator + ', ' + String(value);
},
result: (accumulator) => accumulator || null
});
const groupedNames = db.prepare('SELECT group_concat_custom(name) as names FROM users').get();
console.log(groupedNames.names); // "Alice, Bob, Charlie"
// Mathematical aggregate - geometric mean
db.aggregate('geometric_mean', {
start: { product: 1, count: 0 },
step: (accumulator, value) => {
if (typeof value !== 'number' || value <= 0) return accumulator;
return {
product: accumulator.product * value,
count: accumulator.count + 1
};
},
result: (accumulator) => {
if (accumulator.count === 0) return null;
return Math.pow(accumulator.product, 1 / accumulator.count);
}
});
const geometricMean = db.prepare('SELECT geometric_mean(value) as geo_mean FROM measurements').get();
// Window function with inverse (for sliding windows)
db.aggregate('running_average', {
start: { sum: 0, count: 0 },
step: (accumulator, value) => {
if (typeof value !== 'number') return accumulator;
return {
sum: accumulator.sum + value,
count: accumulator.count + 1
};
},
inverse: (accumulator, value) => {
if (typeof value !== 'number') return accumulator;
return {
sum: accumulator.sum - value,
count: accumulator.count - 1
};
},
result: (accumulator) => {
return accumulator.count > 0 ? accumulator.sum / accumulator.count : null;
}
});
// Use as window function
const runningAverage = db.prepare(`
SELECT value,
running_average(value) OVER (ORDER BY id ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) as avg_3
FROM measurements
ORDER BY id
`).all();
// Complex aggregate with multiple parameters
db.aggregate('weighted_average', {
start: { weightedSum: 0, totalWeight: 0 },
step: (accumulator, value, weight) => {
if (typeof value !== 'number' || typeof weight !== 'number' || weight <= 0) {
return accumulator;
}
return {
weightedSum: accumulator.weightedSum + (value * weight),
totalWeight: accumulator.totalWeight + weight
};
},
result: (accumulator) => {
return accumulator.totalWeight > 0 ? accumulator.weightedSum / accumulator.totalWeight : null;
}
});Create virtual tables that generate data dynamically or interface with external data sources.
/**
* Define virtual table module
* @param {string} name - Module name
* @param {Function|Object} factory - Factory function or eponymous table definition
* @returns {Database} Database instance for chaining
*/
table(name, factory);
// Factory function signature
interface TableFactory {
(moduleName: string, databaseName: string, tableName: string, ...args): TableDefinition;
}
// Table definition object
interface TableDefinition {
columns: string[]; // Column names (required)
rows: GeneratorFunction; // Generator function yielding rows (required)
parameters?: string[]; // Parameter names (optional, inferred from rows.length)
safeIntegers?: boolean; // Use safe integers (default: database setting)
directOnly?: boolean; // Table can only be accessed directly (default: false)
}Usage Examples:
// Simple eponymous-only virtual table (no factory)
db.table('fibonacci', {
columns: ['value', 'position'],
*rows(limit = 10) {
let a = 0, b = 1, position = 0;
while (position < limit) {
yield [a, position];
[a, b] = [b, a + b];
position++;
}
}
});
// Use the virtual table
const fibNumbers = db.prepare('SELECT * FROM fibonacci(15)').all();
console.log(fibNumbers); // First 15 Fibonacci numbers
// Factory-based virtual table for flexibility
db.table('sequence_generator', function(moduleName, databaseName, tableName, type) {
return {
columns: ['value', 'index'],
parameters: ['start', 'end', 'step'],
*rows(start, end, step = 1) {
let index = 0;
for (let value = start; value <= end; value += step) {
if (type === 'even' && value % 2 !== 0) continue;
if (type === 'odd' && value % 2 === 0) continue;
yield { value, index: index++ };
}
}
};
});
// Create specific table instances
db.exec('CREATE VIRTUAL TABLE even_numbers USING sequence_generator(even)');
db.exec('CREATE VIRTUAL TABLE odd_numbers USING sequence_generator(odd)');
// Query the virtual tables
const evenNumbers = db.prepare('SELECT * FROM even_numbers(2, 20, 2)').all();
const oddNumbers = db.prepare('SELECT * FROM odd_numbers(1, 19, 2)').all();
// Complex virtual table with external data
db.table('file_system', {
columns: ['name', 'size', 'type', 'modified'],
parameters: ['directory'],
*rows(directory = '.') {
const fs = require('fs');
const path = require('path');
try {
const entries = fs.readdirSync(directory, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(directory, entry.name);
const stats = fs.statSync(fullPath);
yield {
name: entry.name,
size: stats.size,
type: entry.isDirectory() ? 'directory' : 'file',
modified: stats.mtime.toISOString()
};
}
} catch (error) {
// Handle directory access errors gracefully
console.warn(`Cannot read directory ${directory}:`, error.message);
}
}
});
// Query file system
const files = db.prepare(`
SELECT name, size, type
FROM file_system('/usr/local/bin')
WHERE type = 'file' AND size > 1000
ORDER BY size DESC
`).all();
// Virtual table with JSON data processing
db.table('json_parser', {
columns: ['key', 'value', 'type'],
parameters: ['json_string', 'path'],
*rows(jsonString, path = '$') {
try {
const data = JSON.parse(jsonString);
function* processObject(obj, currentPath) {
for (const [key, value] of Object.entries(obj)) {
const fullPath = currentPath === '$' ? `$.${key}` : `${currentPath}.${key}`;
if (path === '$' || fullPath.startsWith(path)) {
yield {
key: fullPath,
value: typeof value === 'object' ? JSON.stringify(value) : String(value),
type: Array.isArray(value) ? 'array' : typeof value
};
}
if (typeof value === 'object' && value !== null) {
yield* processObject(value, fullPath);
}
}
}
yield* processObject(data, '$');
} catch (error) {
yield { key: 'error', value: error.message, type: 'error' };
}
}
});
// Parse JSON data
const jsonData = '{"users": [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]}';
const parsedData = db.prepare(`
SELECT key, value, type
FROM json_parser(?, '$.users')
WHERE type != 'object'
`).all(jsonData);Utilities for managing user-defined functions and virtual tables.
Function Replacement:
// Functions can be replaced by redefining
db.function('my_function', () => 'version 1');
let result1 = db.prepare('SELECT my_function() as result').get();
console.log(result1.result); // "version 1"
db.function('my_function', () => 'version 2');
let result2 = db.prepare('SELECT my_function() as result').get();
console.log(result2.result); // "version 2"
// Remove function by providing null
db.function('my_function', null);
// Now my_function() is no longer availableError Handling in Functions:
// Handle errors gracefully in user-defined functions
db.function('safe_divide', (a, b) => {
if (typeof a !== 'number' || typeof b !== 'number') return null;
if (b === 0) return null; // Return null for division by zero
return a / b;
});
// Function that throws errors
db.function('strict_divide', (a, b) => {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new Error('Arguments must be numbers');
}
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
});
// Errors in functions become SQL errors
try {
db.prepare('SELECT strict_divide(10, 0)').get();
} catch (error) {
console.log(error.message); // "Division by zero"
}Performance Considerations:
// Use deterministic flag for cacheable functions
db.function('expensive_calculation', {
deterministic: true // SQLite can cache results
}, (input) => {
// Expensive computation that always returns same result for same input
return heavyMathOperation(input);
});
// Use directOnly for security-sensitive functions
db.function('get_secret', {
directOnly: true // Prevents use in triggers, views, or stored procedures
}, () => {
return process.env.SECRET_KEY;
});
// Optimize virtual tables for large datasets
db.table('large_dataset', {
columns: ['id', 'data'],
*rows(limit = 1000000) {
for (let i = 0; i < limit; i++) {
// Yield data incrementally to avoid memory issues
yield { id: i, data: `record_${i}` };
// Optional: yield control periodically for very large datasets
if (i % 10000 === 0) {
setImmediate(() => {}); // Allow event loop to process other tasks
}
}
}
});