Compare commits

...

1 Commits

Author SHA1 Message Date
Maxime Van Hees
55ebaa4d68 first impl of client lib for herodb 2025-11-26 15:20:46 +01:00
3 changed files with 213 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
#!/usr/bin/env -S v -n -w -enable-globals run
import incubaid.herolib.clients.herodb
// Initialize the client
mut client := herodb.new(herodb.Config{
url: 'http://localhost:3000'
})!
println('Connecting to HeroDB at ${client.server_url}...')
// List instances
instances := client.list_instances()!
println('Found ${instances.len} instances:')
for instance in instances {
println('----------------------------------------')
println('Index: ${instance.index}')
println('Name: ${instance.name}')
println('Created At: ${instance.created_at}')
// Parse backend info
backend := instance.get_backend_info() or {
println('Backend: Unknown/Error (${err})')
continue
}
println('Backend Type: ${backend.type_name}')
if backend.path != '' {
println('Backend Path: ${backend.path}')
}
}
println('----------------------------------------')

View File

@@ -0,0 +1,52 @@
# HeroDB Client
A V client library for interacting with the HeroDB JSON-RPC API.
## Features
- Connects to HeroDB's JSON-RPC server (default port 3000).
- Lists running database instances.
- Parses polymorphic backend types (InMemory, Redb, LanceDb).
## Usage
```v
import incubaid.herolib.clients.herodb
fn main() {
// Initialize the client
mut client := herodb.new(herodb.Config{
url: 'http://localhost:3000'
})!
// List instances
instances := client.list_instances()!
for instance in instances {
println('Index: ${instance.index}')
println('Name: ${instance.name}')
// Parse backend info
backend := instance.get_backend_info()!
println('Backend: ${backend.type_name}')
if backend.path != '' {
println('Path: ${backend.path}')
}
println('---')
}
}
```
## API Reference
### `fn new(cfg Config) !HeroDB`
Creates a new HeroDB client instance.
### `fn (mut self HeroDB) list_instances() ![]InstanceMetadata`
Retrieves a list of all currently loaded database instances.
### `fn (m InstanceMetadata) get_backend_info() !BackendInfo`
Helper method to parse the `backend_type` field from `InstanceMetadata` into a structured `BackendInfo` object.

127
lib/clients/herodb/herodb.v Normal file
View File

@@ -0,0 +1,127 @@
module herodb
import json
import incubaid.herolib.core.httpconnection
// JSON-RPC 2.0 Structures
struct JsonRpcRequest {
jsonrpc string = '2.0'
method string
params []string
id int
}
struct JsonRpcResponse[T] {
jsonrpc string
result T
error ?JsonRpcError
id int
}
struct JsonRpcError {
code int
message string
data string
}
// HeroDB Specific Structures
pub struct InstanceMetadata {
pub:
index int
name string
// backend_type can be a string ("InMemory") or an object ({"Redb": "path"}).
// We use the `raw` attribute to capture the raw JSON and parse it manually.
backend_type string @[raw]
created_at string
}
// Helper struct to represent the parsed backend info in a usable way
pub struct BackendInfo {
pub:
type_name string // "InMemory", "Redb", "LanceDb"
path string // Empty for InMemory
}
pub struct HeroDB {
pub:
server_url string
pub mut:
conn ?&httpconnection.HTTPConnection
}
pub struct Config {
pub:
url string = 'http://localhost:3000'
}
pub fn new(cfg Config) !HeroDB {
return HeroDB{
server_url: cfg.url
}
}
pub fn (mut self HeroDB) connection() !&httpconnection.HTTPConnection {
if mut conn := self.conn {
return conn
}
mut new_conn := httpconnection.new(
name: 'herodb'
url: self.server_url
retry: 3
)!
self.conn = new_conn
return new_conn
}
fn (mut self HeroDB) rpc_call[T](method string) !T {
mut conn := self.connection()!
req := JsonRpcRequest{
method: method
id: 1
params: []
}
response := conn.post_json_generic[JsonRpcResponse[T]](
method: .post
prefix: ''
data: json.encode(req)
dataformat: .json
)!
if err := response.error {
return error('RPC Error ${err.code}: ${err.message}')
}
return response.result
}
pub fn (mut self HeroDB) list_instances() ![]InstanceMetadata {
return self.rpc_call[[]InstanceMetadata]('db_listInstances')!
}
pub fn (m InstanceMetadata) get_backend_info() !BackendInfo {
if m.backend_type.len == 0 {
return error('empty backend_type')
}
if m.backend_type[0] == `"` {
// It's a string
val := json.decode(string, m.backend_type)!
return BackendInfo{
type_name: val
}
} else if m.backend_type[0] == `{` {
// It's an object
val := json.decode(map[string]string, m.backend_type)!
for k, v in val {
return BackendInfo{
type_name: k
path: v
}
}
}
return error('unknown backend_type format: ${m.backend_type}')
}