Postgres — vyuh_server_plugin_postgres
The reference relational DbPlugin. PostgresDbPlugin wraps the official postgres Dart driver behind the framework's DbAdapter boundary and publishes it as vyuh.db.
What It Provides
PostgresDbPlugin— claims theDbPluginsingleton role.PostgresDbAdapter— implementsopen,close,execute,runTx,applyDdl,listen,rpc, andfrom(...).PostgresDbSession— the transactional session passed torunTx.PostgresConnectionConfig— typed env config consumed by the default plugin constructor.
Install
dependencies:
vyuh_server_plugin_postgres:
hosted: https://pub.vyuh.tech
version: ^0.2.0Env-Driven Wiring
The default constructor is lazy and env-driven. Register PostgresConnectionConfig on EnvPlugin, then add PostgresDbPlugin().
import 'package:vyuh_server/vyuh_server.dart';
import 'package:vyuh_server_plugin_postgres/vyuh_server_plugin_postgres.dart';
final runtime = await VyuhServer.bootstrap(
name: 'inventory-api',
plugins: [
EnvPlugin(
configs: [
TypedEnvConfig.factory(PostgresConnectionConfig.new),
],
),
PostgresDbPlugin(),
],
);PostgresConnectionConfig reads POSTGRES_URL from the framework env cascade. When it is absent, local development falls back to:
postgresql://postgres:postgres@127.0.0.1:5432/postgres?sslmode=disableProduction should set POSTGRES_URL, usually to the pooled Supabase connection URL. The plugin opens the adapter during init() and closes it during graceful shutdown.
Caller-Owned Connection
For tests or hosts that own the connection lifecycle, use PostgresDbPlugin.from:
import 'package:postgres/postgres.dart';
final conn = await Connection.open(endpoint);
final runtime = await VyuhServer.bootstrap(
name: 'inventory-test',
plugins: [
PostgresDbPlugin.from(connection: conn),
],
);The plugin will not close a caller-owned connection.
Connection Settings
Pass settings: when you need to override the URL-derived ConnectionSettings:
PostgresDbPlugin(
settings: ConnectionSettings(
sslMode: SslMode.require,
connectTimeout: Duration(seconds: 10),
),
);Using vyuh.db
Ad-Hoc Reads
final rows = await vyuh.db.execute(
'SELECT * FROM products WHERE tenant_id = @tenant LIMIT @limit',
parameters: {
'tenant': tenantId,
'limit': 50,
},
);
return jsonResponse(body: {'success': true, 'data': rows.asMaps});Parameters are named and parameterized. Do not interpolate user input into SQL strings.
Transactions
await vyuh.db.runTx((session) async {
await session.execute(
'INSERT INTO orders (id, tenant_id, total_cents) '
'VALUES (@id, @tenant, @total)',
parameters: {
'id': orderId,
'tenant': tenantId,
'total': totalCents,
},
);
for (final item in items) {
await session.execute(
'INSERT INTO order_items (order_id, sku, qty) '
'VALUES (@order, @sku, @qty)',
parameters: {
'order': orderId,
'sku': item.sku,
'qty': item.qty,
},
);
}
});Throwing inside runTx rolls back. Returning normally commits.
DDL
await vyuh.db.applyDdl([
'''
CREATE TABLE IF NOT EXISTS products (
id UUID PRIMARY KEY,
tenant_id TEXT NOT NULL,
name TEXT NOT NULL,
price_cents INTEGER NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
)
''',
'CREATE INDEX products_tenant_id ON products(tenant_id)',
'CREATE INDEX products_name ON products(name)',
]);Query Builder
Prefer the adapter-neutral builder for feature/plugin code that should survive backend swaps:
final result = await vyuh.db
.from('products', schema: 'public')
.select()
.eq('tenant_id', tenantId)
.order('name')
.limit(50)
.read();LISTEN / NOTIFY
await for (final payload in vyuh.db.listen('order_events')) {
final event = jsonDecode(payload) as Map<String, Object?>;
await dispatchOrderEvent(event);
}LISTEN <channel> starts when the first subscriber attaches and UNLISTEN runs when the last subscriber detaches.
Where to Go Next
- Entity CRUD — descriptor-driven CRUD over
vyuh.db - Query — compile
cdx_queryJSON into Postgres SQL vyuh.db— runtime accessor reference