The client SDK mirrors the core Database API with async methods. It supports HTTP and WebSocket transports, and the WebSocket transport reconnects automatically and restores active subscriptions after a drop. This page builds one file, client.ts, step by step. You open a connection, read and write rows, subscribe to changes, then switch the transport to run an atomic batch over HTTP.
Connect and query
A client connects to a running server and picks a transport. Start client.ts with a WebSocket client that reconnects on its own, take a handle to the banking database, and read the accounts back.
import { SirannonClient } from '@delali/sirannon-db/client'
const client = new SirannonClient('http://localhost:9876', {
transport: 'websocket',
autoReconnect: true,
reconnectInterval: 1000,
})
const db = client.database('banking')
const accounts = await db.query<{ id: number; holder: string; balance: number }>(
'SELECT id, holder, balance FROM accounts ORDER BY id'
)query returns every matching row as an array. When you expect a single row, read the first element and check for undefined. It sends the SQL to the server and runs it against the named database, so parameterise every value you interpolate rather than building strings by hand. The banking database and its accounts table live on the server, so create and seed them there first; the server guide covers standing one up.
Execute a write
execute runs an insert, update, or delete and returns the change count and the last inserted row id. Add a new account to client.ts and read back what the server assigned.
import { SirannonClient } from '@delali/sirannon-db/client'
const client = new SirannonClient('http://localhost:9876', {
transport: 'websocket',
autoReconnect: true,
reconnectInterval: 1000,
})
const db = client.database('banking')
const accounts = await db.query<{ id: number; holder: string; balance: number }>(
'SELECT id, holder, balance FROM accounts ORDER BY id'
)
const result = await db.execute(
'INSERT INTO accounts (holder, balance) VALUES (?, ?)',
['Emma Wright', 500]
)The returned result carries changes and lastInsertRowId, so result.lastInsertRowId gives you the id of Emma's new account without a follow-up read.
Subscribe to changes
on(table).subscribe(callback) opens a live feed for a table and returns a handle you call unsubscribe on when you are done. It delivers the same row-level events as local change data capture, dispatched by the server as changes commit. Watch the accounts table so every balance change reaches this client.
import { SirannonClient } from '@delali/sirannon-db/client'
const client = new SirannonClient('http://localhost:9876', {
transport: 'websocket',
autoReconnect: true,
reconnectInterval: 1000,
})
const db = client.database('banking')
const accounts = await db.query<{ id: number; holder: string; balance: number }>(
'SELECT id, holder, balance FROM accounts ORDER BY id'
)
const result = await db.execute(
'INSERT INTO accounts (holder, balance) VALUES (?, ?)',
['Emma Wright', 500]
)
const sub = await db.on('accounts').subscribe((event) => {
console.log('Account changed:', event.type, event.row)
})The WebSocket transport tracks this subscription, so if the connection drops and autoReconnect brings it back, the server resumes the feed without you re-registering it. Call sub.unsubscribe() to close the feed, and client.close() to shut the connection when the client is finished.
Move a transfer to an atomic HTTP batch
The single execute above is fine for one write, but a transfer touches two accounts and both changes must commit together or not at all. Transactions run over the HTTP transport, which sends the whole statement batch in one atomic request. Switch the transport from WebSocket to HTTP, drop the live subscription (HTTP does not support it), and replace the single insert with a two-statement transfer.
import { SirannonClient } from '@delali/sirannon-db/client'
const client = new SirannonClient('http://localhost:9876', {
transport: 'websocket',
autoReconnect: true,
reconnectInterval: 1000,
})
const client = new SirannonClient('http://localhost:9876', {
transport: 'http',
})
const db = client.database('banking')
const accounts = await db.query<{ id: number; holder: string; balance: number }>(
'SELECT id, holder, balance FROM accounts ORDER BY id'
)
const result = await db.execute(
'INSERT INTO accounts (holder, balance) VALUES (?, ?)',
['Emma Wright', 500]
)
const sub = await db.on('accounts').subscribe((event) => {
console.log('Account changed:', event.type, event.row)
})
const results = await db.transaction([
{ sql: 'UPDATE accounts SET balance = balance - 50 WHERE id = ?', params: [1] },
{ sql: 'UPDATE accounts SET balance = balance + 50 WHERE id = ?', params: [2] },
])
client.close()The server runs both updates inside one transaction: if the second statement fails, the first rolls back, so account 1 never loses its 50 without account 2 gaining it. The results array holds one entry per statement in the order you sent them.
Client options
| Option | Default | Description |
|---|---|---|
transport | 'websocket' | Choose 'websocket' or 'http'. |
headers | none | Send custom HTTP headers, such as an Authorization bearer token. Browser WebSocket handshakes cannot use them. |
webSocketProtocols | none | Send WebSocket subprotocols during the upgrade, the browser-compatible way to pass credentials. |
autoReconnect | true | Reconnect the WebSocket transport automatically after a disconnect. |
reconnectInterval | 1000 | Set the reconnect delay in milliseconds. |
Authentication patterns for both transports, including the browser WebSocket credential flow, are in the security guide.