📚 Bucket DB

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 json objects 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:

  1. Pure: They cannot mutate the row object.
  2. Synchronous: No async/await, no Promise returns.
  3. 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 the row argument.

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.