Turbine

Realtime (LISTEN / NOTIFY)

$listen and $notify send events between processes through PostgreSQL LISTEN / NOTIFY, with no broker to run. Use them for cache invalidation, live dashboards and worker fan-out.

Subscribe and publish#

const sub = await db.$listen('order_created', (payload) => {
  const order = JSON.parse(payload);
  console.log('new order:', order.id);
});
 
await db.$notify('order_created', JSON.stringify({ id: 1 }));
await db.$notify('cache_invalidated'); // payload defaults to ''
 
await sub.unsubscribe();

$listen resolves once the subscription is live. It holds one pooled connection until you call unsubscribe(). In a long-lived worker, db.disconnect() releases every subscription on shutdown:

await db.$listen('cache_invalidated', (key) => cache.delete(key));
 
process.on('SIGTERM', async () => {
  await db.disconnect(); // unsubscribes and cancels any pending reconnect
});

$notify is one round trip: SELECT pg_notify($1, $2) with both values bound. The payload is a string, so serialize structured data.

Lost connections#

A restart, failover or compute suspend eventually closes the subscription's connection. $listen then reconnects with exponential backoff and runs LISTEN again:

const sub = await db.$listen('order_created', onOrder, {
  onReconnect: () => refreshOrders(), // resync: anything sent while down is gone
  onError: (err) => log.warn(err),    // each loss, as a ConnectionError (E004)
});
OptionDefaultWhat it does
reconnecttruefalse ends the subscription at the first loss. { initialDelayMs, maxDelayMs } tunes the backoff (default 100 ms doubling to 30 s)
onErrora line through the client's loggerCalled on each loss and each failed reconnect
onReconnectnoneCalled once the subscription is live again

NOTIFY is not durable: a notification sent while the subscription is down is never delivered. Treat onReconnect as the signal to re-read the state the channel tracks.

Limits#

  • Channel names must be plain identifiers (letters, digits, underscores, not starting with a digit, at most 63 characters). Anything else, 'order created' or 'app.events' for instance, throws ValidationError (E003) before a connection is used.
  • $listen needs a persistent connection. On an HTTP driver (Neon HTTP, Vercel Postgres) it throws ConnectionError instead of hanging. Run it in a long-lived process; $notify works everywhere.
  • PostgreSQL only. Other engines throw UnsupportedFeatureError (E017).

See also#