Queries you can read.
PDO, bound values, immutable queries and deliberately explicit transactions.
A scoped connection
use Icom\Database\Database;
$app->get('/notes', function (Database $db) {
return Response::json(
$db->table('notes')
->where('title', 'First note')
->orderBy('created_at', 'DESC')
->limit(20)
->get(['id', 'title'])
);
});Writes and transactions
$db->transaction(function (Database $db) {
$db->table('notes')->insert([
'id' => bin2hex(random_bytes(16)),
'title' => 'First note',
'created_at' => gmdate('c'),
]);
});
$db->table('notes')->where('id', $id)->update(['title' => 'Revised']);
$db->table('notes')->where('id', $id)->delete();Safety and limits
Builder values are bound parameters. Table/column names, operators and sort directions are allowlisted; use trusted schema identifiers. update/delete require a WHERE condition. Raw query() is for developer-written SQL and still requires parameters for user data. Nested transactions are rejected. The builder has no ORM, relationship loader, joins or bulk upsert API. SQLite is exercised by the test suite; MySQL connection and quoting paths are included but need integration tests against your database.
Migrations
php bin/icom migrate
// migrations/002_example.php
return static function (Database $db): void {
$db->query('CREATE TABLE IF NOT EXISTS tags (name VARCHAR(80) PRIMARY KEY)');
};Migration operations
Migrations run in lexical filename order, are recorded in icom_migrations and are protected by a single-node lock. SQLite DDL runs in a transaction; MySQL DDL implicitly commits, so use idempotent migrations and a verified backup. Run migrations through one deployment coordinator in a cluster. There is no automatic down/rollback migration command. Readiness checks the configured database connection and storage access.