Go SDK Reference (v1)
This page documents the v1 API. Breaking changes will be released under v2 with a new import path.
Import
import ara "github.com/ara-mesh/ara-sdk-go"
CGO_ENABLED=1 is required at build time. No other C dependencies are needed — the engine
is bundled as a pre-built static library.
ara.Open
func Open(ctx context.Context, cfg Config) (*Node, error)
Open (or create) an Ara node backed by a SQLite database at cfg.Path. Runs all pending
migrations, initialises the CRDT engine, starts the background sync loop, and returns a
ready node. There is no separate Run call.
ara.Config
type Config struct {
Path string // SQLite file path; use ":memory:" for tests
Migrations []Migration // ordered schema migrations
NetworkID string // logical mesh identifier; only matching nodes sync (default: "" = all)
Encryption bool // enable X25519 keypairs + AES-256-GCM on all messages (default: false)
SyncInterval time.Duration // periodic handshake interval; 0 = default 30s; increase for LoRa nodes
OTLPAddr string // optional OTLP gRPC endpoint, e.g. "localhost:4317"
OTLPServiceName string // optional OTel service name; defaults to "ara-sdk-go"
LicenseKey string // Ed25519-signed key from Ara; empty = 10-node evaluation limit
}
SyncInterval tuning guide:
- WiFi / LAN / MQTT: use default (30s) or lower (10s) for faster convergence
- LoRa-only nodes: use 60–120s to stay within the 1% duty cycle budget (~110 bytes/min sustained at SF10 915 MHz; single-packet latency is ~20s)
ara.Migration
type Migration struct {
Version int
Description string
SQL string // DDL to execute (CREATE TABLE, ALTER TABLE, etc.)
Sync []string // tables to register as CRDTs after SQL runs
AlterSync string // override table for ALTER; inferred automatically from SQL
}
Migrations are applied in Version order. They are additive-only — never drop or rename
columns or tables that are registered for sync.
When adding a column to an existing synced table, the runner infers the target table from
the ALTER TABLE statement automatically and rebuilds the CRDT triggers — no extra fields needed:
{
Version: 2,
Description: "add edited_at to messages",
SQL: `ALTER TABLE messages ADD COLUMN edited_at INTEGER NOT NULL DEFAULT 0;`,
// AlterSync is inferred as "messages" from the ALTER TABLE statement
}
Set AlterSync explicitly only when inference would be ambiguous (e.g. multi-statement SQL
where the first statement is not the ALTER TABLE).
ara.Node
Close
err := node.Close()
Stop the sync loop and close the database.
Exec
err := node.Exec(ctx, "INSERT INTO messages (id, content) VALUES (?, ?)", "id-1", "hello")
Execute a write statement. Args are positional ? bind parameters.
Query
rows, err := node.Query(ctx, "SELECT id, content FROM messages WHERE id = ?", "id-1")
for _, row := range rows {
fmt.Println(row["id"], row["content"])
}
Returns all rows as []map[string]any. JSON numbers come back as float64; use
Row.Get for typed access to individual values.
QueryRow
row := node.QueryRow(ctx, "SELECT COUNT(*) AS n FROM messages")
var count int
err := row.Get("n", &count)
Returns a *Row. If no rows matched, row.Err() returns ErrNoRows.
Sync
err := node.Sync(ctx)
Trigger an immediate changeset broadcast to all connected peers. The background loop also syncs automatically; this is for on-demand flush.
NodeID
id := node.NodeID() // UUID string, e.g. "6ff234d2-..."
SchemaVersion
v := node.SchemaVersion() // highest applied migration version
Peers
peers, err := node.Peers(ctx) // []PeerInfo
AddTransportUDP
err := node.AddTransportUDP(port int, seeds ...string)
Add a UDP LAN transport. seeds is an optional list of "host:port" peer addresses.
Seeds are required on macOS when running multiple nodes on the same machine because
broadcast does not loop back on the loopback interface.
// Single node, no seeds needed (LAN broadcast discovers peers)
node.AddTransportUDP(7946)
// Multiple nodes on localhost
node.AddTransportUDP(7946, "127.0.0.1:7947", "127.0.0.1:7948")
AddTransportMeshtastic
err := node.AddTransportMeshtastic(portPath string, channel int)
Add a Meshtastic LoRa transport via USB serial.
portPath— serial device path (e.g."/dev/ttyUSB0"or"/dev/ttyACM0")channel— Meshtastic channel index (0-7, default 0)
node.AddTransportMeshtastic("/dev/ttyUSB0", 0)
AddTransportMQTT
err := node.AddTransportMQTT(ara.MQTTConfig{
BrokerURL: "tcp://192.168.1.1:1883",
NetworkID: "my-mesh",
})
Add an MQTT transport. All nodes sharing the same NetworkID form a mesh over the broker.
SetBlobStore
err := node.SetBlobStore(dir string, policy BlobPolicy)
Configure a local content-addressed blob store at dir with an automatic sync policy.
Call before the node begins ingesting or receiving blobs.
node.SetBlobStore("/data/blobs", ara.BlobPolicy{
Mode: ara.BlobSyncFull, // BlobSyncNone | BlobSyncThumbOnly | BlobSyncFull
MaxBytes: 500 << 20, // 500 MB total cap; 0 = unlimited
MaxBlobSize: 10 << 20, // skip blobs > 10 MB; 0 = unlimited
})
IngestBlob
id, err := node.IngestBlob(ctx, path, mimeType string) (string, error)
Copy a local file into the blob store and return its SHA-256 content id. Store the id as a foreign key in an app table to associate the blob with a record.
id, err := node.IngestBlob(ctx, "/tmp/photo.jpg", "image/jpeg")
if err != nil { ... }
node.Exec(ctx, "INSERT INTO clues (id, photo_id) VALUES (?, ?)", clueID, id)
node.Sync(ctx)
Peers with BlobSyncFull policy will automatically pull the bytes after receiving the blob
metadata through normal changeset sync.
BlobPath
path := node.BlobPath(id) // filesystem path, or "" if not yet present locally
ara.Row
QueryRow returns *Row. Read columns by name with Get, or retrieve the full map.
Row.Get
func (r *Row) Get(col string, dest any) error
Read a named column into dest. Supported destination types: *string, *int, *int64,
*float64, *bool, *[]byte, *any.
var count int
if err := row.Get("n", &count); err != nil { ... }
Row.Map
m, err := row.Map() // map[string]any
Row.Err
err := row.Err() // non-nil if no rows matched (ErrNoRows) or a query error occurred
ara.BlobPolicy
type BlobPolicy struct {
Mode BlobSyncMode
MaxBytes int64 // total storage cap in bytes; 0 = unlimited
MaxBlobSize int64 // skip individual blobs larger than this; 0 = unlimited
}
const (
BlobSyncNone BlobSyncMode = 0 // metadata only; never pull bytes (default)
BlobSyncThumbOnly BlobSyncMode = 1 // pull thumbnails only (≤ 2 KB)
BlobSyncFull BlobSyncMode = 2 // pull full blobs when transport allows
)
ara.MQTTConfig
type MQTTConfig struct {
BrokerURL string // e.g. "tcp://192.168.1.1:1883"
NetworkID string // shared identifier for topic isolation
}
ara.PeerInfo
type PeerInfo struct {
ID string
SchemaVersion int
Health string // "HEALTHY" | "DEGRADED" | "ISOLATED" | "UNKNOWN"
Transports []string
}
ErrNoRows
var ErrNoRows = errors.New("ara: no rows in result")
Returned by Row.Err() when a QueryRow matched no rows.