Platform
Data Store
BOLT_DB is a dependency-free, connectionless flat-file data store for read-heavy JSON snapshots and secured records — without reaching for MySQL.
What BOLT_DB is
includes/BOLT_DB.php is a dependency-free, connectionless flat-file data store. It is not a MySQL replacement — relational data still belongs in the database. BOLT_DB exists for two specific jobs, each with its own store:
- Public store — read-heavy JSON snapshots served to many polling clients (for example, a live metrics dashboard that a single writer updates). Files live inside the web root (
data/public/) so Apache can serve them as static bytes — no PHP boot, no database round-trip per read. - Private store — secured records stored as PHP files that
returnan array. Files live outside the web root and are reachable only throughBOLT_DB::read()/get_data(). Two optional, co-located hooks shape each read:rules.php(authorize and redact) andinit.php(observe and audit).
Load it with require_once before use:
require_once ROOT_DIR . '/includes/BOLT_DB.php';
The public store
The public store keeps small JSON records at data/public/{collection}/{key}.json. Collection and key names are restricted to [A-Za-z0-9_-]. Because the files sit inside the web root, the fastest possible read is a browser fetching the static file directly — no PHP involved.
| Method | Returns | Description |
|---|---|---|
put($collection, $key, array $data) |
bool |
Atomic write (temp file + rename) |
get($collection, $key) |
?array |
Decoded record, or null if missing or unparseable |
has($collection, $key) |
bool |
Whether the record exists |
keys($collection) |
string[] |
Keys in the collection (sorted) |
all($collection) |
array |
key => data for the whole collection |
update($collection, $key, $fn) |
array |
Locked read-modify-write; $fn receives the current record (or []) and returns the new one |
delete($collection, $key) |
bool |
Delete; false if it did not exist |
serve($collection, $key, $opts) |
void |
Emit JSON with ETag/Last-Modified; answers a conditional GET with 304. $opts: cors, cache_control |
One writer updates; many clients poll:
// Written by the app, polled by many dashboards:
BOLT_DB::put('metrics', 'pageviews', ['total' => 1840, 'updated' => time()]);
// Safe concurrent increment under a lock:
BOLT_DB::update('metrics', 'pageviews', function ($m) { $m['total']++; return $m; });
// Cheap polling endpoint (ETag + 304):
BOLT_DB::serve('metrics', 'pageviews', ['cors' => true]);
Tip: for maximum efficiency, point pollers straight at the static file (/data/public/metrics/pageviews.json) and let Apache handle the conditional GET with zero PHP. serve() exists to add CORS and a stable URL shape.
The entire public store is runtime cache, not source. data/.gitignore ignores everything but itself, so the store is never committed — it is regenerated from MySQL or from live submissions.
The private store
Private records are <?php return [...]; files stored outside the web root, so the web server can never hand them out directly. Every read runs a fixed pipeline:
- Validate the path (reject traversal or escaping).
- Fire the
init.phpcascade (root → leaf). - Load the record.
- Apply
rules.phpto produce the returned view.
| Method | Returns | Description |
|---|---|---|
read($path, array $context = []) |
array |
Validate + init + rules, then return the (possibly redacted) record |
get_data($path, array $context = []) |
array |
Convenience wrapper for read() |
write($path, array $data) |
bool |
Atomic <?php return ...; write |
forget($path) |
bool |
Delete; false if missing or escaping |
rules.php — authorize and redact
A rules.php file in the record's leaf directory (optional) returns a function (array $data, array $ctx) that returns a transformed or redacted view, or throws BoltDBAccessDenied. If it is absent, data is returned as-is. If it is present but not callable, the read is denied. Prefer an allow-list (not unset()) so a secret field added later cannot leak by accident:
// rules.php for private user/* records
return function (array $data, array $ctx) {
if (!empty($ctx['internal'])) return $data; // trusted server-side caller
return [ // public allow-list view
'id' => $data['id'],
'display_name' => $data['display_name'],
];
};
init.php — observe and audit
An optional init.php returns a function (array $event) and fires on every read, cascading root → leaf — every init.php from the private root down to the record's directory runs. It is for side effects only: the return value is ignored and it must not echo. Use it for audit and observability.
Trust model
read() is a trusted internal helper. A private directory with no rules.php returns its data as-is, so the security perimeter is whoever calls read() — not the store itself.
BOLT_DB::read('user/1-jack-davis', $ctx); // SAFE - literal path in PHP
BOLT_DB::read($_GET['path'], $ctx); // DANGER - client-controlled path
Never wire a client-controlled path into read() on a directory that lacks a rules.php; map specific routes to specific records instead. BOLT_DB stores no notion of "the current user" — rules.php and init.php only see the $context the caller passes (typically ['user' => [...]]), which is only as trustworthy as the caller supplying it. Until Bolt has an authenticated session, treat read() as callable by trusted server-side code only.
Configuration
The public store defaults to data/public. The private store is opt-in and must point outside the web root:
define('BOLT_DB_PRIVATE_DIR', '/absolute/path/outside/webroot');
// or
BOLT_DB::configure([
'private_dir' => '/abs/path/outside/webroot',
'public_dir' => ROOT_DIR . '/data/public', // optional override
'auditor' => function (array $event) { /* sink */ } // optional security-event sink
]);
BOLT_DB fails closed: if the private store is used while unconfigured, missing, or resolving to a path inside the web-served Bolt directory, it throws BoltDBConfigError rather than operate unsafely.
Exceptions
| Exception | Meaning |
|---|---|
BoltDBNotFound |
Bad or escaping path, or missing record |
BoltDBAccessDenied |
A rule rejected the read, or a present rule is broken |
BoltDBConfigError |
Private store misconfigured (unset or web-reachable) |
BoltDBException |
Base class; encode/write failure or malformed record |
At an HTTP boundary, return the same response for BoltDBNotFound and BoltDBAccessDenied so the endpoint cannot be used to enumerate which records exist.