Custom Functions
Because BucketDB deliberately excludes complex querying languages (like SQL) to keep the engine lightweight, it provides a native mechanism to execute pure JavaScript functions directly against your data during a query.
The Philosophy
BucketDB stores rows as highly compressed binary blocks. When a QueryBuilder executes a table scan, it deserializes the binary into a raw JavaScript object. You can register custom functions that run against this deserialized object in memory.
When to Use Custom Functions
- Complex regex matching on strings.
- Iterating over deeply nested
jsonobjects to find a specific key. - Executing business logic that cannot be expressed via simple operators (
>,<,=).
When NOT to Use Custom Functions
- When a simple equality operator (
=) on an indexed field would suffice. - Any function that requires a full table scan will be significantly slower than an indexed exact match.
Registering a Function
Before a query can use a function, you must register it on the db instance.
// A simple function that returns true if the user's age is 18 or older
db.registerFunction('isAdult', (row) => row.age >= 18);
// A complex function that iterates over a json field
db.registerFunction('hasActiveSubscription', (row) => {
if (!row.subscriptions) return false;
return row.subscriptions.some(sub => sub.status === 'active' && sub.expiresAt > Date.now());
});
Technical Limitations
Functions execute synchronously using the new Function() constructor under the hood. They are serialized and stored permanently in Block 0 so that all nodes in the cluster have access to them immediately.
Because of this, functions MUST BE:
- Pure: They cannot mutate the
rowobject. - Synchronous: No
async/await, noPromisereturns. - Isolated: They cannot reference closures, external variables,
require(), or network calls. The function body is serialized to a string and executed in a sandboxed context. It only has access to therowargument.
Querying with Functions
Once registered, you can reference the function by its string name in the QueryBuilder.where() clause.
// The operator must always be '=' when calling a function.
// The third argument is the expected return value of the function.
const adults = await db.query('users')
.where('isAdult', '=', true)
.execute();
// Or, use the shorthand
const activeSubs = await db.query('users')
.eq('hasActiveSubscription', true)
.execute();
Function Versioning
If you need to change the behavior of a function, you can register a new version.
// Update the logic to require age 21
db.registerFunction('isAdult', (row) => row.age >= 21, 2);
Functions are stored with their version number. Queries will automatically use the latest registered version of the function when executed.