feat(clients): professional clients with tests, CI/CD, and examples

- Python: pyproject.toml, pytest suite (unit + integration), README, examples
- JavaScript: package.json, TypeScript definitions, node:test suite, README, examples
- Nim: integration tests, examples, README, improved nimble tasks
- Rust: fixed nodelay/localhost IPv6 bug, integration tests, examples, README
- Added GitHub Actions CI for all 4 clients against Docker server
- Added docker-compose.test.yml for local integration testing
- Added .gitignore for each client
This commit is contained in:
2026-05-08 00:18:34 +03:00
parent e9d3c7e9de
commit ae2e07e626
31 changed files with 2651 additions and 12 deletions
+8
View File
@@ -0,0 +1,8 @@
nimcache/
*.exe
*.dll
*.so
*.dylib
tests/test_client
tests/test_integration
examples/basic
+146
View File
@@ -0,0 +1,146 @@
# BaraDB Nim Client
Official Nim client for **BaraDB** — a multimodal database engine.
## Features
- **Binary wire protocol** — fast TCP communication via `asyncnet`
- **Async/await API** — non-blocking by default
- **Sync wrapper** — `SyncClient` for blocking code
- **Query builder** — fluent SQL construction
- **Zero server dependencies** — self-contained, uses only Nim stdlib
## Installation
Add to your `.nimble` file:
```nim
requires "baradb >= 1.0.0"
```
Or clone locally:
```bash
git clone https://github.com/barabadb/baradadb.git
cd clients/nim
nimble develop
```
## Quick Start
### Async API
```nim
import asyncdispatch
import baradb/client
proc main() {.async.} =
let client = newClient()
await client.connect()
let result = await client.query("SELECT name, age FROM users WHERE age > 18")
echo result
client.close()
waitFor main()
```
### Sync API
```nim
import baradb/client
let client = newSyncClient()
client.connect()
let result = client.query("SELECT name, age FROM users WHERE age > 18")
echo result
client.close()
```
### Parameterized Queries
```nim
import baradb/client
proc main() {.async.} =
let client = newClient()
await client.connect()
let result = await client.query(
"SELECT * FROM users WHERE age > $1",
@[WireValue(kind: fkInt64, int64Val: 18)]
)
echo result
client.close()
waitFor main()
```
### Query Builder
```nim
import baradb/client
proc main() {.async.} =
let client = newClient()
await client.connect()
let result = await newQueryBuilder(client)
.select("name", "email")
.from("users")
.where("active = true")
.orderBy("name", "ASC")
.limit(10)
.exec()
echo result
client.close()
waitFor main()
```
## Running Tests
Unit tests (no server):
```bash
nimble test
```
Integration tests (requires server on `localhost:9472`):
```bash
# Start server
docker run -d -p 9472:9472 baradb:latest
# Run integration tests
nim c -r tests/test_integration.nim
```
## API Reference
### `ClientConfig`
| Field | Default | Description |
|--------------|-------------|------------------------|
| `host` | `127.0.0.1` | Server hostname |
| `port` | `9472` | TCP port |
| `database` | `default` | Default database |
| `username` | `admin` | Username |
| `password` | `""` | Password |
| `timeoutMs` | `30000` | Timeout in ms |
| `maxRetries` | `3` | Max reconnect retries |
### Methods (Async)
- `connect()` — open TCP connection
- `close()` — close connection
- `query(sql)` — execute SELECT-like query
- `query(sql, params)` — parameterized query
- `exec(sql)` — execute DDL/DML, returns affected rows
- `auth(token)` — JWT authentication
- `ping()` — health check
### Methods (Sync)
Same as async but blocking. Available on `SyncClient`.
## License
Apache-2.0
+14 -3
View File
@@ -1,8 +1,8 @@
# Package
version = "0.1.0"
version = "1.0.0"
author = "BaraDB Team"
description = "BaraDB client library for Nim — async binary protocol client"
description = "Official Nim client for BaraDB — async binary protocol client"
license = "Apache-2.0"
srcDir = "src"
@@ -12,5 +12,16 @@ requires "nim >= 2.2.0"
# Export the client module
bin = @[]
task test, "Run client tests":
task test, "Run all client tests (unit + integration if server available)":
exec "nim c -r tests/test_client.nim"
# Integration tests are compiled separately; they auto-skip if no server.
try:
exec "nim c -r tests/test_integration.nim"
except:
echo "Integration tests skipped (no server on localhost:9472)"
task test_unit, "Run unit tests only":
exec "nim c -r tests/test_client.nim"
task example, "Run basic example":
exec "nim c -r examples/basic.nim"
+96
View File
@@ -0,0 +1,96 @@
## BaraDB Nim Client — Basic Examples
## Make sure BaraDB is running on localhost:9472
import std/asyncdispatch
import std/strutils
import baradb/client
proc exampleConnection() {.async.} =
echo "=== Connection ==="
let client = newClient()
await client.connect()
echo "Connected: ", client.isConnected
echo "Ping: ", await client.ping()
client.close()
echo "Connected after close: ", client.isConnected
echo ""
proc exampleSimpleQuery() {.async.} =
echo "=== Simple Query ==="
let client = newClient()
await client.connect()
let result = await client.query("SELECT 42 as answer, 'BaraDB' as db")
echo "Columns: ", result.columns
echo "Row count: ", result.rowCount
for row in result.rows:
echo " ", row.join(", ")
client.close()
echo ""
proc exampleParameterizedQuery() {.async.} =
echo "=== Parameterized Query ==="
let client = newClient()
await client.connect()
let result = await client.query(
"SELECT $1 as num, $2 as txt",
@[
WireValue(kind: fkInt64, int64Val: 123),
WireValue(kind: fkString, strVal: "hello world"),
]
)
for row in result.rows:
echo " ", row.join(", ")
client.close()
echo ""
proc exampleQueryBuilder() {.async.} =
echo "=== Query Builder ==="
let client = newClient()
await client.connect()
let sql = newQueryBuilder(client)
.select("id", "name")
.from("users")
.where("active = true")
.orderBy("name", "ASC")
.limit(5)
.build()
echo "Generated SQL: ", sql
client.close()
echo ""
proc exampleDdlDml() {.async.} =
echo "=== DDL & DML ==="
let client = newClient()
await client.connect()
try:
discard await client.exec("DROP TABLE IF EXISTS demo_products")
except:
discard
discard await client.exec("CREATE TABLE demo_products (id INT PRIMARY KEY, name STRING, price FLOAT)")
let affected = await client.exec("INSERT INTO demo_products (id, name, price) VALUES (1, 'Widget', 9.99)")
echo "Insert affected rows: ", affected
let result = await client.query("SELECT * FROM demo_products")
echo "Select returned ", result.rowCount, " row(s)"
for row in result.rows:
echo " ", row.join(", ")
discard await client.exec("DROP TABLE demo_products")
echo "Table dropped"
client.close()
echo ""
proc main() {.async.} =
echo "BaraDB Nim Client Examples"
echo "Make sure BaraDB is running on localhost:9472"
echo ""
await exampleConnection()
await exampleSimpleQuery()
await exampleParameterizedQuery()
await exampleQueryBuilder()
await exampleDdlDml()
waitFor main()
+119
View File
@@ -0,0 +1,119 @@
## BaraDB Nim Client — Integration Tests
## Requires a running BaraDB server on localhost:9472.
import std/unittest
import std/asyncdispatch
import std/asyncnet
import std/strutils
import baradb/client
const
TestHost = "127.0.0.1"
TestPort = 9472
proc serverAvailable(): bool =
try:
var socket = newAsyncSocket()
waitFor socket.connect(TestHost, Port(TestPort))
socket.close()
return true
except:
return false
let hasServer = serverAvailable()
suite "Integration: Connection":
test "Connect and close":
if not hasServer:
skip()
var client = newClient(ClientConfig(host: TestHost, port: TestPort))
check not client.isConnected
waitFor client.connect()
check client.isConnected
client.close()
check not client.isConnected
test "Ping":
if not hasServer:
skip()
var client = newClient(ClientConfig(host: TestHost, port: TestPort))
waitFor client.connect()
check (waitFor client.ping()) == true
client.close()
suite "Integration: Query":
test "Simple SELECT":
if not hasServer:
skip()
var client = newClient(ClientConfig(host: TestHost, port: TestPort))
waitFor client.connect()
let result = waitFor client.query("SELECT 1 as one")
check result.rowCount >= 0
client.close()
test "Parameterized query":
if not hasServer:
skip()
var client = newClient(ClientConfig(host: TestHost, port: TestPort))
waitFor client.connect()
let result = waitFor client.query(
"SELECT $1 as num, $2 as txt",
@[WireValue(kind: fkInt64, int64Val: 42), WireValue(kind: fkString, strVal: "hello")]
)
check result.rowCount >= 0
client.close()
suite "Integration: DDL & DML":
test "Create table, insert, select, drop":
if not hasServer:
skip()
var client = newClient(ClientConfig(host: TestHost, port: TestPort))
waitFor client.connect()
try:
discard waitFor client.exec("DROP TABLE IF EXISTS nim_test_users")
except:
discard
discard waitFor client.exec("CREATE TABLE nim_test_users (id INT PRIMARY KEY, name STRING, age INT)")
let affected = waitFor client.exec("INSERT INTO nim_test_users (id, name, age) VALUES (1, 'Alice', 30)")
check affected >= 0
let result = waitFor client.query("SELECT name, age FROM nim_test_users WHERE id = 1")
check result.rowCount == 1
client.close()
suite "Integration: QueryBuilder":
test "Builder exec":
if not hasServer:
skip()
var client = newClient(ClientConfig(host: TestHost, port: TestPort))
waitFor client.connect()
try:
discard waitFor client.exec("DROP TABLE IF EXISTS nim_test_products")
except:
discard
discard waitFor client.exec("CREATE TABLE nim_test_products (id INT PRIMARY KEY, name STRING, price FLOAT)")
discard waitFor client.exec("INSERT INTO nim_test_products (id, name, price) VALUES (1, 'Widget', 9.99)")
let result = waitFor newQueryBuilder(client)
.select("name", "price")
.from("nim_test_products")
.where("id = 1")
.exec()
check result.rowCount == 1
discard waitFor client.exec("DROP TABLE nim_test_products")
client.close()
suite "Integration: SyncClient":
test "Sync query":
if not hasServer:
skip()
var client = newSyncClient(ClientConfig(host: TestHost, port: TestPort))
client.connect()
let result = client.query("SELECT 1 as one")
check result.rowCount >= 0
client.close()