Pub/Sub
Honker’s pub/sub is a direct analog of Postgres’s pg_notify / LISTEN: fire-and-forget signals between processes that share a SQLite file. 1-2 ms cross-process wake latency on M-series laptops. No replay, no delivery guarantees. For that, use Streams.
The wire format is the same across bindings: a Python publisher and a Go listener talk to the same channel through the same .db file.
Fire a signal
Section titled “Fire a signal”import honker
db = honker.open("app.db")
with db.transaction() as tx: tx.notify("orders", {"id": 42, "event": "placed"})The notification commits with the surrounding transaction. If the tx rolls back, no one receives it.
const { open } = require('@russellthehippo/honker-node');const db = open('app.db');
// Atomic with a business writeconst tx = db.transaction();tx.execute("INSERT INTO orders (id) VALUES (?)", [42]);tx.notify('orders', { id: 42, event: 'placed' });tx.commit();
// Or fire-and-forget outside a txdb.notify('orders', { id: 42, event: 'placed' });use honker::Database;use serde_json::json;
let db = Database::open("app.db")?;db.notify("orders", &json!({"id": 42, "event": "placed"}))?;import honker "github.com/russellromney/honker-go"
db, _ := honker.Open("app.db", "./libhonker_ext.dylib")defer db.Close()
db.Notify("orders", map[string]any{"id": 42, "event": "placed"})require "honker"
db = Honker::Database.new("app.db", extension_path: "./libhonker_ext.dylib")db.notify("orders", {id: 42, event: "placed"})import { open } from "@russellthehippo/honker-bun";
const db = open("app.db", "./libhonker_ext.dylib");db.notify("orders", { id: 42, event: "placed" });{:ok, db} = Honker.open("app.db", extension_path: "./libhonker_ext.dylib")Honker.notify(db, "orders", %{id: 42, event: "placed"})#include "honker.hpp"
int main() { honker::Database db{"app.db", "./libhonker_ext.dylib"};
honker::Transaction tx{db.raw()}; sqlite3_exec(tx.raw(), "INSERT INTO orders (id) VALUES (42)", nullptr, nullptr, nullptr); sqlite3_exec(tx.raw(), "SELECT notify('orders', '{\"id\":42,\"event\":\"placed\"}')", nullptr, nullptr, nullptr); tx.commit();
db.notify("orders", R"({"id":42,"event":"placed"})");}.load ./libhonker_extSELECT honker_bootstrap();
-- Matches pg_notify's signature for drop-in familiarity.SELECT notify('orders', '{"id":42,"event":"placed"}');Listen
Section titled “Listen”The listener starts from MAX(id) at attach time. Historical notifications are not replayed. Honker runs a shared commit-poll thread at 1 ms cadence (one PRAGMA data_version read per database, counter increments on every commit from any connection in any journal mode), so every listener in the process wakes on any commit and then filters by channel at the SELECT level.
async for notif in db.listen("orders"): print(notif.channel, notif.payload)for await (const notif of db.listen('orders')) { console.log(notif.channel, notif.payload);}// Blocking iterator; wakes on any commit to the db.for notif in db.listen("orders")? { let notif = notif?; println!("{} {}", notif.channel, notif.payload);}sub, _ := db.Listen("orders")defer sub.Close()
for notif := range sub.Channel() { fmt.Println(notif.Channel, string(notif.Payload))}db.listen("orders") do |notif| puts "#{notif.channel} #{notif.payload}"endfor await (const notif of db.listen("orders")) { console.log(notif.channel, notif.payload);}{:ok, sub} = Honker.listen(db, "orders")
receive_loop = fn loop -> receive do {:honker_notification, notif} -> IO.inspect({notif.channel, notif.payload}) loop.(loop) endendreceive_loop.(receive_loop)#include "honker.hpp"
#include <chrono>#include <iostream>
int main() { honker::Database db{"app.db", "./libhonker_ext.dylib"}; auto sub = db.listen("orders");
for (;;) { if (auto notif = sub.recv(std::chrono::milliseconds(5000))) { std::cout << notif->channel() << " " << notif->payload() << "\n"; } }}Cross-process
Section titled “Cross-process”The commit watcher fires on commits from any process. A FastAPI app, a CLI worker, and a cron job can all notify() the same channel, and a separate listener receives every signal within a few milliseconds. No coordination, no broker, no extra network hop.
Pruning
Section titled “Pruning”Notifications accumulate in _honker_notifications until you prune them. There’s no auto-prune because “how much history do you want” is a product decision. Honker exposes two prune modes, available from any binding.
# Pythondb.prune_notifications(older_than_s=3600) # delete rows older than 1 hourdb.prune_notifications(max_keep=10_000) # keep only the most recent 10k// Nodedb.pruneNotifications(3600); // delete rows older than 1 hourdb.pruneNotifications(null, 10000); // keep only the most recent 10kThere are no honker_prune_* SQL functions — pruning is a binding-level helper. From raw SQL, delete against the table directly:
DELETE FROM _honker_notifications WHERE created_at < unixepoch() - 3600;