Advanced Operations
This guide covers advanced features available in ekoDB client libraries, including search operations, chat functionality, and real-time data handling.
For complete, runnable examples, visit the ekoDB Examples Repository. It contains 265+ examples across Rust, Go, Python, TypeScript, Kotlin, and JavaScript.
Options Structs
Cleaner method signatures using builder pattern for operation options (Rust, TypeScript, Kotlin).
Overview
Instead of long parameter lists, use options structs for cleaner, more maintainable code:
Before (v0.7.x):
await client.insert("users", record, "1h", true, "tx_123", false);
// What do these parameters mean? 🤔
After (v0.8.0):
await client.insert("users", record, {
ttl: "1h",
bypassRipple: true,
transactionId: "tx_123",
bypassCache: false
});
// Much clearer! ✨
Insert with Options
- 🦀 Rust
- 📘 TypeScript
- 📦 JavaScript
- 🟣 Kotlin
- 🐍 Python
- 🔷 Go
use ekodb_client::{Client, Record, InsertOptions};
let mut record = Record::new();
record.insert("name", "Alice");
record.insert("email", "alice@example.com");
// Use builder pattern for options
let options = InsertOptions::new()
.ttl("1h")
.bypass_ripple(true)
.transaction_id("tx_123")
.bypass_cache(false);
let result = client.insert("users", record, Some(options)).await?;
import { EkoDBClient, InsertOptions } from "@ekodb/ekodb-client";
const record = {
name: "Alice",
email: "alice@example.com"
};
// Use options object
const options: InsertOptions = {
ttl: "1h",
bypassRipple: true,
transactionId: "tx_123",
bypassCache: false
};
const result = await client.insert("users", record, options);
const { EkoDBClient } = require("@ekodb/ekodb-client");
const record = {
name: "Alice",
email: "alice@example.com"
};
// Use options object
const options = {
ttl: "1h",
bypassRipple: true,
transactionId: "tx_123",
bypassCache: false
};
const result = await client.insert("users", record, options);
import io.ekodb.client.EkoDBClient
import io.ekodb.client.types.Record
import io.ekodb.client.InsertOptions
val record = Record.new()
.insert("name", "Alice")
.insert("email", "alice@example.com")
// Use options object
val options = InsertOptions(
ttl = "1h",
bypassRipple = true,
transactionId = "tx_123",
bypassCache = false
)
val result = client.insert("users", record, options)
# Python uses optional keyword arguments (Pythonic approach)
record = {
"name": "Alice",
"email": "alice@example.com"
}
result = await client.insert(
"users",
record,
ttl="1h",
bypass_ripple=True,
transaction_id="tx_123",
bypass_cache=False
)
// Go uses variadic options (idiomatic approach)
record := map[string]interface{}{
"name": "Alice",
"email": "alice@example.com",
}
result, err := client.Insert("users", record, InsertOptions{
TTL: stringPtr("1h"),
BypassRipple: boolPtr(true),
TransactionID: stringPtr("tx_123"),
BypassCache: boolPtr(false),
})
Update with Options
- 🦀 Rust
- 📘 TypeScript
- 📦 JavaScript
- 🟣 Kotlin
- 🐍 Python
- 🔷 Go
use ekodb_client::UpdateOptions;
let options = UpdateOptions::new()
.bypass_ripple(true)
.transaction_id("tx_456");
let updated = client.update("users", "user-123", updates, Some(options)).await?;
const options: UpdateOptions = {
bypassRipple: true,
transactionId: "tx_456"
};
const updated = await client.update("users", "user-123", updates, options);
const options = {
bypassRipple: true,
transactionId: "tx_456"
};
const updated = await client.update("users", "user-123", updates, options);
val options = UpdateOptions(
bypassRipple = true,
transactionId = "tx_456"
)
val updated = client.update("users", "user-123", updates, options)
# Keyword arguments
updated = await client.update(
"users",
"user-123",
updates,
bypass_ripple=True,
transaction_id="tx_456"
)
updated, err := client.Update("users", "user-123", updates, UpdateOptions{
BypassRipple: boolPtr(true),
TransactionID: stringPtr("tx_456"),
})
Available Options Structs
| Struct | Available Fields | Languages |
|---|---|---|
| InsertOptions | ttl, bypass_ripple, transaction_id, bypass_cache | All |
| UpdateOptions | bypass_ripple, transaction_id, bypass_cache | All |
| UpsertOptions | bypass_ripple, transaction_id | All |
| DeleteOptions | bypass_ripple, transaction_id | All |
| FindOptions | bypass_cache, transaction_id | All |
- Rust/TypeScript/Kotlin: Dedicated options structs with builder pattern
- Python: Optional keyword arguments (Pythonic)
- Go: Variadic options with pointers (idiomatic)
All approaches provide the same functionality with language-appropriate ergonomics.
Search Operations
ekoDB provides powerful search capabilities including full-text search, fuzzy search, and vector search.
Full-Text Search
Search across all fields in your documents:
- 🦀 Rust
- 🐍 Python
- 📘 TypeScript
- 📦 JavaScript
- 🟣 Kotlin
- 🔷 Go
use ekodb_client::{Client, SearchQuery};
let search = SearchQuery::builder()
.query("database")
.min_score(0.1)
.limit(10)
.build();
let results = client.search("articles", search).await?;
for result in results.results {
println!("Score: {:.4} - {:?}", result.score, result.record);
}
from ekodb_client import Client, SearchQuery
search = SearchQuery(
query="database",
min_score=0.1,
limit=10
)
results = client.search("articles", search)
for result in results.results:
print(f"Score: {result.score:.4f} - {result.record}")
import { EkoDBClient, SearchQuery } from "@ekodb/ekodb-client";
const search: SearchQuery = {
query: "database",
minScore: 0.1,
limit: 10
};
const results = await client.search("articles", search);
for (const result of results.results) {
console.log(`Score: ${result.score.toFixed(4)} - ${JSON.stringify(result.record)}`);
}
const results = await client.search("articles", {
query: "database",
minScore: 0.1,
limit: 10
});
for (const result of results.results) {
console.log(`Score: ${result.score.toFixed(4)} - ${JSON.stringify(result.record)}`);
}
import io.ekodb.client.EkoDBClient
val results = client.search("articles") {
query = "database"
minScore = 0.1
limit = 10
}
results.results.forEach { result ->
println("Score: ${"%.4f".format(result.score)} - ${result.record}")
}
import ekodb "github.com/ekoDB/ekodb-client-go"
searchQuery := ekodb.SearchQuery{
Query: "database",
MinScore: 0.1,
Limit: 10,
}
results, err := client.Search("articles", searchQuery)
if err != nil {
log.Fatal(err)
}
for _, result := range results.Results {
fmt.Printf("Score: %.4f - %+v\n", result.Score, result.Record)
}
Field-Weighted Search
Search with custom field weights to prioritize certain fields:
- 🦀 Rust
- 🐍 Python
- 📘 TypeScript
- 📦 JavaScript
- 🟣 Kotlin
- 🔷 Go
use std::collections::HashMap;
let mut weights = HashMap::new();
weights.insert("title".to_string(), 2.0);
weights.insert("description".to_string(), 1.0);
let search = SearchQuery::builder()
.query("rust database")
.fields(vec!["title".to_string(), "description".to_string()])
.weights(weights)
.limit(5)
.build();
let results = client.search("articles", search).await?;
search = SearchQuery(
query="rust database",
fields=["title", "description"],
weights={"title": 2.0, "description": 1.0},
limit=5
)
results = client.search("articles", search)
const search: SearchQuery = {
query: "rust database",
fields: ["title", "description"],
weights: { title: 2.0, description: 1.0 },
limit: 5
};
const results = await client.search("articles", search);
const results = await client.search("articles", {
query: "rust database",
fields: ["title", "description"],
weights: { title: 2.0, description: 1.0 },
limit: 5
});
val results = client.search("articles") {
query = "rust database"
fields = listOf("title", "description")
weights = mapOf("title" to 2.0, "description" to 1.0)
limit = 5
}
searchQuery := ekodb.SearchQuery{
Query: "rust database",
Fields: []string{"title", "description"},
Weights: map[string]float64{"title": 2.0, "description": 1.0},
Limit: 5,
}
results, err := client.Search("articles", searchQuery)
Fuzzy Search
Enable typo tolerance with fuzzy matching:
- 🦀 Rust
- 🐍 Python
- 📘 TypeScript
- 📦 JavaScript
- 🟣 Kotlin
- 🔷 Go
let search = SearchQuery::builder()
.query("databse") // Typo: "databse" instead of "database"
.fuzzy(true)
.fuzziness(2) // Allow up to 2 character differences
.limit(10)
.build();
let results = client.search("articles", search).await?;
search = SearchQuery(
query="databse", # Typo
fuzzy=True,
fuzziness=2,
limit=10
)
results = client.search("articles", search)
const search: SearchQuery = {
query: "databse", // Typo
fuzzy: true,
fuzziness: 2,
limit: 10
};
const results = await client.search("articles", search);
const results = await client.search("articles", {
query: "databse", // Typo
fuzzy: true,
fuzziness: 2,
limit: 10
});
val results = client.search("articles") {
query = "databse" // Typo
fuzzy = true
fuzziness = 2
limit = 10
}
searchQuery := ekodb.SearchQuery{
Query: "databse", // Typo
Fuzzy: true,
Fuzziness: 2,
Limit: 10,
}
results, err := client.Search("articles", searchQuery)
Vector Search
Perform semantic similarity search using embeddings:
- 🦀 Rust
- 🐍 Python
- 📘 TypeScript
- 📦 JavaScript
- 🟣 Kotlin
- 🔷 Go
// First, create a collection with vector index
let schema = Schema::builder()
.add_field("content", FieldType::String)
.add_field("embedding", FieldType::Vector(384)) // 384-dimensional vector
.build();
client.create_collection("documents", schema).await?;
// Insert document with embedding
let mut doc = Record::new();
doc.insert("content", "ekoDB is a high-performance database");
doc.insert("embedding", vec![0.1, 0.2, 0.3, /* ... 384 dimensions */]);
client.insert("documents", doc).await?;
// Search by vector similarity
let query_vector = vec![0.1, 0.2, 0.3, /* ... */];
let search = SearchQuery::builder()
.vector(query_vector)
.limit(10)
.build();
let results = client.search("documents", search).await?;
# Create collection with vector index
schema = Schema(
fields={
"content": FieldType.String(),
"embedding": FieldType.Vector(384)
}
)
client.create_collection("documents", schema)
# Insert with embedding
doc = {
"content": "ekoDB is a high-performance database",
"embedding": [0.1, 0.2, 0.3, ...] # 384 dimensions
}
client.insert("documents", doc)
# Vector search
query_vector = [0.1, 0.2, 0.3, ...]
search = SearchQuery(vector=query_vector, limit=10)
results = client.search("documents", search)
// Create collection with vector index
const schema = {
fields: {
content: { type: "String" },
embedding: { type: "Vector", dimensions: 384 }
}
};
await client.createCollection("documents", schema);
// Insert with embedding
await client.insert("documents", {
content: "ekoDB is a high-performance database",
embedding: [0.1, 0.2, 0.3, /* ... 384 dimensions */]
});
// Vector search
const queryVector = [0.1, 0.2, 0.3, /* ... */];
const results = await client.search("documents", {
vector: queryVector,
limit: 10
});
// Insert with embedding
await client.insert("documents", {
content: "ekoDB is a high-performance database",
embedding: [0.1, 0.2, 0.3, /* ... 384 dimensions */]
});
// Vector search
const queryVector = [0.1, 0.2, 0.3, /* ... */];
const results = await client.search("documents", {
vector: queryVector,
limit: 10
});
// Insert with embedding
val doc = Record.new()
.insert("content", "ekoDB is a high-performance database")
.insert("embedding", listOf(0.1, 0.2, 0.3, /* ... 384 dimensions */))
client.insert("documents", doc)
// Vector search
val queryVector = listOf(0.1, 0.2, 0.3, /* ... */)
val results = client.search("documents") {
vector = queryVector
limit = 10
}
// Create collection with vector index
schema := ekodb.NewSchemaBuilder().
AddField("content", ekodb.NewFieldTypeSchemaBuilder("String").Build()).
AddField("embedding", ekodb.NewFieldTypeSchemaBuilder("Vector").
Dimensions(384).
Build()).
Build()
client.CreateCollection("documents", schema)
// Insert with embedding
doc := ekodb.Record{
"content": "ekoDB is a high-performance database",
"embedding": []float64{0.1, 0.2, 0.3, /* ... 384 dimensions */},
}
client.Insert("documents", doc)
// Vector search
queryVector := []float64{0.1, 0.2, 0.3, /* ... */}
searchQuery := ekodb.SearchQuery{
Vector: queryVector,
Limit: 10,
}
results, err := client.Search("documents", searchQuery)
Hybrid Search
Combine text and vector search for best results:
- 🦀 Rust
- 🐍 Python
- 📘 TypeScript
- 📦 JavaScript
- 🟣 Kotlin
- 🔷 Go
let search = SearchQuery::builder()
.query("database performance") // Text query
.vector(query_vector) // Vector query
.limit(10)
.build();
let results = client.search("documents", search).await?;
search = SearchQuery(
query="database performance", # Text query
vector=query_vector, # Vector query
limit=10
)
results = client.search("documents", search)
const search: SearchQuery = {
query: "database performance", // Text query
vector: queryVector, // Vector query
limit: 10
};
const results = await client.search("documents", search);
const results = await client.search("documents", {
query: "database performance", // Text query
vector: queryVector, // Vector query
limit: 10
});
val results = client.search("documents") {
query = "database performance" // Text query
vector = queryVector // Vector query
limit = 10
}
searchQuery := ekodb.SearchQuery{
Query: "database performance", // Text query
Vector: queryVector, // Vector query
Limit: 10,
}
results, err := client.Search("documents", searchQuery)
Chat Operations
Build AI-powered chat applications with built-in context management and session handling.
Basic Chat
- 🦀 Rust
- 🐍 Python
- 📘 TypeScript
- 📦 JavaScript
- 🟣 Kotlin
- 🔷 Go
use ekodb_client::{Client, CreateChatSessionRequest, ChatMessageRequest, CollectionConfig};
// Create a chat session
let session = client.create_chat_session(CreateChatSessionRequest {
collections: vec![CollectionConfig {
collection_name: "products".to_string(),
fields: vec![],
search_options: None,
}],
llm_provider: "openai".to_string(),
llm_model: Some("gpt-4.1".to_string()),
system_prompt: Some("You are a helpful assistant.".to_string()),
..Default::default()
}).await?;
// Send a message
let response = client.chat_message(
&session.chat_id,
ChatMessageRequest::new("What products do you have?")
).await?;
println!("AI: {:?}", response.responses);
# Create a chat session
session = await client.create_chat_session(
collections=[("products", [])],
llm_provider="openai",
llm_model="gpt-4.1",
system_prompt="You are a helpful assistant."
)
# Send a message
response = await client.chat_message(
session["chat_id"],
"What products do you have?"
)
print(f"AI: {response['responses']}")
// Create a chat session
const session = await client.createChatSession({
collections: [{ collection_name: "products", fields: [] }],
llm_provider: "openai",
llm_model: "gpt-4.1",
system_prompt: "You are a helpful assistant.",
});
// Send a message
const response = await client.chatMessage(session.chat_id, {
message: "What products do you have?",
});
console.log(`AI: ${response.responses}`);
// Create a chat session
const session = await client.createChatSession({
collections: [{ collection_name: "products", fields: [] }],
llm_provider: "openai",
llm_model: "gpt-4.1",
system_prompt: "You are a helpful assistant.",
});
// Send a message
const response = await client.chatMessage(session.chat_id, {
message: "What products do you have?",
});
console.log(`AI: ${response.responses}`);
// Create a chat session
val session = client.createChatSession(buildJsonObject {
putJsonArray("collections") {
add(buildJsonObject {
put("collection_name", "products")
putJsonArray("fields") {}
})
}
put("llm_provider", "openai")
put("llm_model", "gpt-4.1")
put("system_prompt", "You are a helpful assistant.")
})
// Send a message
val response = client.chatMessage(session["chat_id"].toString(), buildJsonObject {
put("message", "What products do you have?")
})
println("AI: ${response["responses"]}")
import ekodb "github.com/ekoDB/ekodb-client-go"
// Create a chat session
llmModel := "gpt-4.1"
systemPrompt := "You are a helpful assistant."
session, err := client.CreateChatSession(ekodb.CreateChatSessionRequest{
Collections: []ekodb.CollectionConfig{{
CollectionName: "products",
Fields: []interface{}{},
}},
LLMProvider: "openai",
LLMModel: &llmModel,
SystemPrompt: &systemPrompt,
})
// Send a message
response, err := client.ChatMessage(session.ChatID, ekodb.ChatMessageRequest{
Message: "What products do you have?",
})
fmt.Printf("AI: %v\n", response.Responses)
Chat Sessions
Manage conversation history with sessions:
- 🦀 Rust
- 🐍 Python
- 📘 TypeScript
- 📦 JavaScript
- 🟣 Kotlin
- 🔷 Go
// Send multiple messages in the same session
let response1 = client.chat_message(
&session.chat_id,
ChatMessageRequest::new("What's the price of ekoDB Pro?")
).await?;
let response2 = client.chat_message(
&session.chat_id,
ChatMessageRequest::new("What features does it include?")
).await?;
// Get session message history
let messages = client.get_chat_session_messages(
&session.chat_id, None
).await?;
println!("Total messages: {}", messages.messages.len());
# Send multiple messages in the same session
response1 = await client.chat_message(
session["chat_id"],
"What's the price of ekoDB Pro?"
)
response2 = await client.chat_message(
session["chat_id"],
"What features does it include?"
)
# Get session message history
messages = await client.get_chat_session_messages(session["chat_id"])
print(f"Total messages: {messages['total']}")
// Send multiple messages in the same session
const response1 = await client.chatMessage(session.chat_id, {
message: "What's the price of ekoDB Pro?",
});
const response2 = await client.chatMessage(session.chat_id, {
message: "What features does it include?",
});
// Get session message history
const messages = await client.getChatSessionMessages(session.chat_id, {
limit: 10,
sort: "asc",
});
console.log(`Total messages: ${messages.messages.length}`);
// Send multiple messages in the same session
const response1 = await client.chatMessage(session.chat_id, {
message: "What's the price of ekoDB Pro?",
});
const response2 = await client.chatMessage(session.chat_id, {
message: "What features does it include?",
});
// Get session message history
const messages = await client.getChatSessionMessages(session.chat_id, {
limit: 10,
sort: "asc",
});
console.log(`Total messages: ${messages.messages.length}`);
// Send multiple messages in the same session
val response1 = client.chatMessage(session["chat_id"].toString(), buildJsonObject {
put("message", "What's the price of ekoDB Pro?")
})
val response2 = client.chatMessage(session["chat_id"].toString(), buildJsonObject {
put("message", "What features does it include?")
})
// Get session message history
val messages = client.getChatSessionMessages(session["chat_id"].toString())
println("Total messages: ${messages["total"]}")
// Send multiple messages in the same session
response1, err := client.ChatMessage(session.ChatID, ekodb.ChatMessageRequest{
Message: "What's the price of ekoDB Pro?",
})
response2, err := client.ChatMessage(session.ChatID, ekodb.ChatMessageRequest{
Message: "What features does it include?",
})
// Get session message history
messages, err := client.GetChatSessionMessages(session.ChatID, nil)
fmt.Printf("Total messages: %d\n", messages.Total)
ekoDB's chat system features intelligent query understanding that works with your natural data structure:
- Field Name Matching: Queries like "What is the price?" automatically find records with a
pricefield, even if it contains numeric data (not text-searchable) - Multi-Turn Context: Follow-up questions use conversation history to enhance search relevance
- No Denormalization Required: Works with structured data as you'd naturally model it
Example:
// Your data structure
{
"product": "ekoDB",
"description": "High-performance database",
"price": 99 // Numeric field
}
When a user asks "What is the price?", ekoDB:
- Checks the collection schema for fields matching "price"
- Finds records with that field name
- Provides the full record to the LLM
- LLM responds: "The price is $99"
See complete Chat Session examples in all languages:
- Rust:
client_chat_sessions.rs - Python:
client_chat_sessions.py - TypeScript:
client_chat_sessions.ts - Go:
client_chat_sessions.go - Kotlin:
ClientChatSessions.kt
Real-Time Operations
WebSocket Queries
Subscribe to real-time data changes:
- 🦀 Rust
- 🐍 Python
- 📘 TypeScript
- 📦 JavaScript
- 🟣 Kotlin
- 🔷 Go
use ekodb_client::WebSocketClient;
// Connect to WebSocket
let ws_url = "wss://your-subdomain.production.google.ekodb.net";
let mut ws_client = client.websocket(ws_url).await?;
// Subscribe to collection changes
let results = ws_client.find_all("users").await?;
// Process real-time updates
for record in results {
println!("New/Updated record: {:?}", record);
}
ws_client.close().await?;
# Connect to WebSocket
ws_url = "wss://your-subdomain.production.google.ekodb.net"
ws_client = client.websocket(ws_url)
# Subscribe to collection changes
results = ws_client.find_all("users")
# Process real-time updates
for record in results:
print(f"New/Updated record: {record}")
ws_client.close()
// Connect to WebSocket
const wsUrl = "wss://your-subdomain.production.google.ekodb.net";
const wsClient = await client.websocket(wsUrl);
// Subscribe to collection changes
const results = await wsClient.findAll("users");
// Process real-time updates
for (const record of results) {
console.log("New/Updated record:", record);
}
await wsClient.close();
// Connect to WebSocket
const wsUrl = "wss://your-subdomain.production.google.ekodb.net";
const wsClient = await client.websocket(wsUrl);
// Subscribe to collection changes
const results = await wsClient.findAll("users");
// Process real-time updates
for (const record of results) {
console.log("New/Updated record:", record);
}
await wsClient.close();
// Connect to WebSocket
val wsUrl = "wss://your-subdomain.production.google.ekodb.net"
val wsClient = client.websocket(wsUrl)
// Subscribe to collection changes
val results = wsClient.findAll("users")
// Process real-time updates
results.forEach { record ->
println("New/Updated record: $record")
}
wsClient.close()
// Connect to WebSocket
wsURL := "wss://your-subdomain.production.google.ekodb.net"
wsClient, err := client.WebSocket(wsURL)
// Subscribe to collection changes
results, err := wsClient.FindAll("users")
// Process real-time updates
for _, record := range results {
fmt.Printf("New/Updated record: %+v\n", record)
}
wsClient.Close()
WebSocket CRUD Operations
All 14 server-supported CRUD operations are available over WebSocket. The persistent
WS connection eliminates HTTP overhead per request — zero TLS handshake, reuses the
authenticated connection. All methods support messageId for concurrent request
correlation.
- 🦀 Rust
- 🐍 Python
- 📘 TypeScript
- 🐹 Go
- 🟣 Kotlin
// Connect via convenience method (derives WS URL, attaches schema cache)
let ws = client.connect_ws().await?;
// Insert
let record = ws.insert("users", json!({"name": "Alice"}), None).await?;
// Query with filter
let results = ws.query("users",
Some(json!({"field": "status", "operator": "Eq", "value": "active"})),
None, Some(10), None,
).await?;
// Find by ID
let user = ws.find_by_id("users", "record-id").await?;
// Update
ws.update("users", "record-id", json!({"name": "Updated"}), None).await?;
// Delete
ws.delete("users", "record-id", None).await?;
// Batch operations
ws.batch_insert("logs", vec![json!({"msg": "a"}), json!({"msg": "b"})], None).await?;
// Search
let hits = ws.text_search("docs", "rust async", None, Some(10)).await?;
// Collection management
let collections = ws.list_collections().await?;
ws.create_collection("new_coll", None).await?;
// Atomic field actions
ws.update_with_action("counters", "views", "increment", "count", Some(json!(1))).await?;
ws = await client.websocket("wss://your-instance.ekodb.net")
result = await ws.ws_insert("users", {"name": "Alice"})
results = await ws.ws_query("users", filter={"field": "status", "operator": "Eq", "value": "active"})
user = await ws.ws_find_by_id("users", "record-id")
await ws.ws_update("users", "record-id", {"name": "Updated"})
await ws.ws_delete("users", "record-id")
await ws.ws_batch_insert("logs", [{"msg": "a"}, {"msg": "b"}])
hits = await ws.ws_text_search("docs", "python async", limit=10)
collections = await ws.ws_list_collections()
const ws = new WebSocketClient("wss://your-instance.ekodb.net", token);
const record = await ws.insert("users", { name: "Alice" });
const results = await ws.query("users", { filter: { field: "status", operator: "Eq", value: "active" } });
const user = await ws.findById("users", "record-id");
await ws.update("users", "record-id", { name: "Updated" });
await ws.delete("users", "record-id");
await ws.batchInsert("logs", [{ msg: "a" }, { msg: "b" }]);
const hits = await ws.textSearch("docs", "typescript async", undefined, 10);
const collections = await ws.listCollections();
ws, _ := client.ConnectWS()
result, _ := ws.Insert("users", map[string]interface{}{"name": "Alice"})
results, _ := ws.Query("users", ekodb.QueryOptions{
Filter: map[string]interface{}{"field": "status", "operator": "Eq", "value": "active"},
Limit: 10,
})
user, _ := ws.FindByID("users", "record-id")
ws.Update("users", "record-id", map[string]interface{}{"name": "Updated"})
ws.Delete("users", "record-id")
ws.BatchInsert("logs", []map[string]interface{}{{"msg": "a"}, {"msg": "b"}})
hits, _ := ws.TextSearch("docs", "go async", nil, 10)
collections, _ := ws.ListCollections()
val ws = client.websocket("wss://your-instance.ekodb.net")
ws.connect()
val record = ws.insert("users", buildJsonObject { put("name", "Alice") })
val results = ws.query("users", filter = buildJsonObject {
put("field", "status"); put("operator", "Eq"); put("value", "active")
})
val user = ws.findById("users", "record-id")
ws.update("users", "record-id", buildJsonObject { put("name", "Updated") })
ws.delete("users", "record-id")
ws.batchInsert("users", listOf(buildJsonObject { put("name", "Bob") }))
val collections = ws.listCollections()
Schema Cache
The schema cache stores each collection's primary_key_alias and version in memory.
This ensures extractRecordId() works correctly regardless of how users configure
their ID field names. The cache is LRU with configurable TTL, and auto-invalidates
via WebSocket SchemaChanged events.
- 🦀 Rust
- 🐍 Python
- 📘 TypeScript
- 🐹 Go
- 🟣 Kotlin
// Enable at client creation
let client = Client::builder()
.base_url("https://api.ekodb.net")
.api_key("key")
.schema_cache(true)
.schema_cache_ttl(300) // seconds
.schema_cache_max(100) // max collections
.build()?;
// Extract IDs correctly with any primary_key_alias
let id = client.extract_id("users", &record);
// Auto-invalidates when connected via WS
let ws = client.connect_ws().await?;
# Schema cache is enabled server-side via the Rust client internals.
# In Python, use extractRecordId for safe ID extraction:
from ekodb_client import extract_record_id
# Tries "id", then "_id", then custom candidates
record_id = extract_record_id(record)
# With custom alias
record_id = extract_record_id(record, extra_candidates=["user_id"])
import { SchemaCache, extractRecordId } from "@ekodb/ekodb-client";
// Create and attach cache to WS client
const cache = new SchemaCache({
enabled: true,
ttlSeconds: 300,
maxEntries: 100,
});
ws.setSchemaCache(cache);
// Extract IDs — cache-aware via WS client
const id = ws.extractId("users", record);
// Or standalone (tries "id", "_id")
const id2 = extractRecordId(record);
// Enable schema cache on client
client.EnableSchemaCache(5*time.Minute, 100)
// Extract IDs using cached primary_key_alias
id := client.ExtractRecordID("users", record)
// ConnectWS auto-attaches the cache
ws, _ := client.ConnectWS()
// Create and attach schema cache
val cache = SchemaCache(enabled = true, maxEntries = 100, ttlMs = 300_000)
ws.schemaCache = cache
// Extract IDs using cached alias
val id = ws.extractId("users", record)
// Or standalone
val id2 = extractRecordId(record, listOf("user_id"))
SSE Subscriptions
Subscribe to collection mutations via Server-Sent Events. Works behind reverse
proxies that block WebSocket upgrades. Also delivers schema_changed events
for automatic schema cache invalidation.
- 🦀 Rust
- 🐍 Python
- 📘 TypeScript
- 🐹 Go
- 🟣 Kotlin
let rx = client.subscribe_sse("orders", None, None).await?;
while let Some(event) = rx.recv().await {
println!("{}: {} on {}",
event.event, event.record_ids.join(", "), event.collection);
}
// With filter — only receive mutations where status = "active"
let rx = client.subscribe_sse(
"orders",
Some("status"),
Some("active"),
).await?;
# SSE subscriptions are available via the REST endpoint
import httpx
async with httpx.AsyncClient() as http:
async with http.stream(
"GET",
f"{base_url}/api/subscribe/orders",
headers={"Authorization": f"Bearer {token}"},
) as response:
async for line in response.aiter_lines():
if line.startswith("data: "):
data = json.loads(line[6:])
print(f"Mutation: {data}")
// Using the EventSource API or fetch with streaming
const response = await fetch(
`${baseUrl}/api/subscribe/orders?filter_field=status&filter_value=active`,
{ headers: { Authorization: `Bearer ${token}` } },
);
const reader = response.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
// Parse SSE events: "event: mutation\ndata: {...}\n\n"
for (const line of text.split("\n")) {
if (line.startsWith("data: ")) {
const event = JSON.parse(line.slice(6));
console.log("Mutation:", event);
}
}
}
// Using the streaming HTTP client
req, _ := http.NewRequest("GET",
baseURL+"/api/subscribe/orders?filter_field=status&filter_value=active", nil)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Accept", "text/event-stream")
resp, _ := client.StreamClient.Do(req)
defer resp.Body.Close()
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "data: ") {
data := line[6:]
fmt.Println("Mutation:", data)
}
}
// Using Ktor streaming
val client = HttpClient { install(ContentNegotiation) { json() } }
client.prepareGet("$baseUrl/api/subscribe/orders") {
header("Authorization", "Bearer $token")
header("Accept", "text/event-stream")
}.execute { response ->
val channel = response.bodyAsChannel()
while (!channel.isClosedForRead) {
val line = channel.readUTF8Line() ?: break
if (line.startsWith("data: ")) {
val data = line.substring(6)
println("Mutation: $data")
}
}
}
SSE also delivers
schema_changedevents, automatically invalidating the client's schema cache when a collection's configuration changes.
Joins
ekoDB supports cross-collection joins to combine data from multiple collections in a single query.
Single Collection Join
Join users with their department data:
- 🦀 Rust
- 📘 TypeScript
- 🐍 Python
- 📦 JavaScript
- 🟣 Kotlin
- 🔷 Go
use ekodb_client::{Client, QueryBuilder, JoinBuilder};
// Join users with departments
let join = JoinBuilder::single(
"departments", // Target collection
"department_id", // Local field (in users)
"id", // Foreign field (in departments)
"department" // Output field name
);
let query = QueryBuilder::new()
.join(join)
.limit(10)
.build();
let users = client.find("users", query).await?;
for user in users {
println!("User: {:?}, Department: {:?}", user["name"], user["department"]);
}
import { EkoDBClient, QueryBuilder, JoinBuilder } from "@ekodb/ekodb-client";
// Join users with departments
const join = JoinBuilder.single(
"departments", // Target collection
"department_id", // Local field (in users)
"id", // Foreign field (in departments)
"department" // Output field name
);
const query = new QueryBuilder()
.join(join)
.limit(10)
.build();
const users = await client.find("users", query);
users.forEach(user => {
console.log(`User: ${user.name}, Department: ${user.department[0]?.name}`);
});
from ekodb_client import Client, QueryBuilder, JoinBuilder
# Join users with departments
join = JoinBuilder.single(
"departments", # Target collection
"department_id", # Local field (in users)
"id", # Foreign field (in departments)
"department" # Output field name
)
query = QueryBuilder() \
.join(join) \
.limit(10) \
.build()
users = await client.find("users", query)
for user in users:
print(f"User: {user['name']}, Department: {user['department'][0]['name']}")
const { EkoDBClient, QueryBuilder, JoinBuilder } = require("@ekodb/ekodb-client");
// Join users with departments
const join = JoinBuilder.single(
"departments", // Target collection
"department_id", // Local field (in users)
"id", // Foreign field (in departments)
"department" // Output field name
);
const query = new QueryBuilder()
.join(join)
.limit(10)
.build();
const users = await client.find("users", query);
users.forEach(user => {
console.log(`User: ${user.name}, Department: ${user.department[0]?.name}`);
});
import io.ekodb.client.EkoDBClient
import io.ekodb.client.QueryBuilder
import io.ekodb.client.JoinBuilder
// Join users with departments
val join = JoinBuilder.single(
"departments", // Target collection
"department_id", // Local field (in users)
"id", // Foreign field (in departments)
"department" // Output field name
)
val query = QueryBuilder()
.join(join)
.limit(10)
.build()
val users = client.find("users", query)
users.forEach { user ->
println("User: ${user["name"]}, Department: ${user["department"]}")
}
import ekodb "github.com/ekoDB/ekodb-client-go"
// Join users with departments
join := ekodb.JoinBuilder.Single(
"departments", // Target collection
"department_id", // Local field (in users)
"id", // Foreign field (in departments)
"department", // Output field name
)
query := ekodb.NewQueryBuilder().
Join(join).
Limit(10).
Build()
users, _ := client.Find("users", query)
for _, user := range users {
fmt.Printf("User: %v, Department: %v\n", user["name"], user["department"])
}
Join examples - Single and multi-collection joins with filtering:
- Rust:
client_joins.rs - Python:
client_joins.py - TypeScript:
client_joins.ts - JavaScript:
client_joins.js - Go:
client_joins.go - Kotlin:
ClientJoins.kt
TTL (Time-To-Live)
Set automatic expiration for documents:
- 🦀 Rust
- 🐍 Python
- 📘 TypeScript
- 📦 JavaScript
- 🟣 Kotlin
- 🔷 Go
use ekodb_client::options::InsertOptions;
// Insert with 1 hour TTL
let mut session = Record::new();
session.insert("user_id", "user-123");
session.insert("token", "abc123");
let options = InsertOptions::new().ttl("1h"); // Expires in 1 hour
let result = client.insert("sessions", session, Some(options)).await?;
# Insert with 1 hour TTL
session = {
"user_id": "user-123",
"token": "abc123"
}
result = await client.insert(
"sessions",
session,
ttl="1h" # Expires in 1 hour
)
// Insert with 1 hour TTL
const session = {
userId: "user-123",
token: "abc123"
};
const result = await client.insert("sessions", session, {
ttl: "1h" // Expires in 1 hour
});
// Insert with 1 hour TTL
const session = {
userId: "user-123",
token: "abc123"
};
const result = await client.insert("sessions", session, {
ttl: "1h" // Expires in 1 hour
});
// Insert with 1 hour TTL
val session = mapOf(
"user_id" to "user-123",
"token" to "abc123"
)
val result = client.insert(
"sessions",
session,
InsertOptions(ttl = "1h") // Expires in 1 hour
)
// Insert with 1 hour TTL
session := ekodb.Record{
"user_id": "user-123",
"token": "abc123",
}
result, err := client.Insert("sessions", session, ekodb.InsertOptions{
TTL: "1h", // Expires in 1 hour
})
Document TTL examples - Insert with expiration, verify expiration works:
- Rust:
client_document_ttl.rs - Python:
client_document_ttl.py - TypeScript:
client_document_ttl.ts - JavaScript:
client_document_ttl.js - Go:
client_document_ttl.go - Kotlin:
ClientDocumentTtl.kt
WebSocket TTL examples - TTL with real-time connections:
- Rust:
client_websocket_ttl.rs - Python:
client_websocket_ttl.py - TypeScript:
client_websocket_ttl.ts - JavaScript:
client_websocket_ttl.js - Go:
client_websocket_ttl.go - Kotlin:
ClientWebsocketTtl.kt
Transactions
ekoDB supports ACID transactions with multiple isolation levels. Transactions ensure data consistency when performing multiple operations that must succeed or fail together.
Isolation Levels
| Level | Description |
|---|---|
READ_UNCOMMITTED | Can read uncommitted changes from other transactions |
READ_COMMITTED | Only reads committed data (default) |
REPEATABLE_READ | Same data on re-read within transaction |
SERIALIZABLE | Highest isolation, transactions appear sequential |
Basic Transaction
- 🔷 Go
- 🦀 Rust
- 🐍 Python
- 📘 TypeScript
// Start a transaction
txID, err := client.BeginTransaction("SERIALIZABLE")
if err != nil {
log.Fatal(err)
}
// Perform operations within transaction context
// (Pass txID in headers for transactional operations)
// Check transaction status
status, err := client.GetTransactionStatus(txID)
fmt.Printf("Status: %v\n", status)
// Commit when all operations succeed
err = client.CommitTransaction(txID)
if err != nil {
// Rollback on failure
client.RollbackTransaction(txID)
log.Fatal(err)
}
// Start a transaction
let tx_id = client.begin_transaction("SERIALIZABLE").await?;
// Perform operations within transaction context
// Commit when done
client.commit_transaction(&tx_id).await?;
// Or rollback on failure
// client.rollback_transaction(&tx_id).await?;
# Start a transaction
tx_id = client.begin_transaction("SERIALIZABLE")
# Perform operations within transaction context
try:
# ... your operations ...
client.commit_transaction(tx_id)
except Exception as e:
client.rollback_transaction(tx_id)
raise e
// Start a transaction
const txId = await client.beginTransaction("SERIALIZABLE");
try {
// Perform operations within transaction context
// Commit when done
await client.commitTransaction(txId);
} catch (error) {
// Rollback on failure
await client.rollbackTransaction(txId);
throw error;
}
- Use the lowest isolation level that meets your consistency requirements
- Keep transactions short to minimize lock contention
- Always handle rollback in error cases
- For detailed transaction patterns, see Transactions
RAG Helpers
The Go client includes convenience methods for RAG (Retrieval-Augmented Generation) workflows:
Generate Embeddings
Generate embedding vectors from text using ekoDB's native Functions:
// Generate embedding for text
embedding, err := client.Embed("Hello world", "text-embedding-3-small")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Generated %d dimensions\n", len(embedding))
Text Search
Perform full-text search with stemming and fuzzy matching:
// Search for documents by text
results, err := client.TextSearch("documents", "database performance", 10)
if err != nil {
log.Fatal(err)
}
for _, doc := range results {
fmt.Printf("Found: %v\n", doc["title"])
}
Hybrid Search
Combine semantic similarity (vector) with keyword matching (text):
// Generate embedding for query
embedding, _ := client.Embed("How to optimize queries?", "text-embedding-3-small")
// Perform hybrid search
results, err := client.HybridSearch("documents", "optimize queries", embedding, 5)
if err != nil {
log.Fatal(err)
}
Find All Records
Simple method to retrieve all records from a collection:
// Get all messages (up to limit)
allMessages, err := client.FindAll("messages", 1000)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Found %d messages\n", len(allMessages))
Functions
Functions are ekoDB's stored procedures system that runs on the server. They can be called from any client library or via REST API to execute complex business logic, queries, CRUD operations, AI workflows, and batch processing.
For comprehensive architecture details, operation types, and advanced patterns, see Functions Architecture.
Functions let you create, store, and execute complete business logic as composable operations. Define your data logic once in ekoDB, then call it like puzzle pieces from any client.
What You Can Do
- ✅ Complete business logic - Queries, CRUD, AI operations in one place
- ✅ Parameterize everything - Dynamic values via
{{param_name}} - ✅ Version control - Track function versions
- ✅ Compose like puzzles - Chain operations together
- ✅ Call from anywhere - REST API or any client library
Function Capabilities
- Query Operations: Find, filter, search, vector search, hybrid search
- CRUD Operations: Insert, update, delete (single and batch)
- Transformations: Group, project, count
- AI Operations: Chat completions, embeddings generation
- Conditional Logic: If/then/else, foreach loops
- External Integrations: HTTP requests to any REST API
Basic Example
Create a function to query active users:
POST /api/functions
Content-Type: application/json
{
"label": "get_active_users",
"name": "Get Active Users",
"description": "Returns active users with a limit",
"parameters": {
"limit": {
"default": 10,
"required": false
}
},
"functions": [
{
"type": "Query",
"collection": "users",
"filter": {
"type": "Condition",
"field": "status",
"operator": "Eq",
"value": "active"
},
"limit": "{{limit}}"
}
]
}
Call a Function
Execute via REST API:
POST /api/functions/get_active_users
Content-Type: application/json
{
"limit": 20
}
Managing Functions
REST API:
# List all functions
GET /api/functions
# Get a specific function by ID or label
GET /api/functions/get_active_users
# Update a function by ID or label
PUT /api/functions/{id_or_label}
# Delete a function by ID or label
DELETE /api/functions/get_active_users
Client Library Methods:
- 🦀 Rust
- 📘 TypeScript
- 📦 JavaScript
- 🐍 Python
- 🔷 Go
- 🟣 Kotlin
use ekodb_client::{Client, Function, ParameterDefinition, UserFunction};
// Create a user function using the builder pattern
let user_func = UserFunction::new("get_active_users", "Get Active Users")
.with_version("1.0.0")
.with_parameter(ParameterDefinition {
name: "collection".to_string(),
required: true,
description: Some("Collection to query".to_string()),
default: None,
})
.with_function(Function::FindAll {
collection: "{{collection}}".to_string(),
})
.with_tag("users")
.with_tag("query");
let func_id = client.save_user_function(user_func).await?;
// Get a user function by label
let func = client.get_user_function("get_active_users").await?;
// List all user functions (optionally filter by tags)
let all_funcs = client.list_user_functions(None).await?;
let tagged_funcs = client.list_user_functions(Some(vec!["users".to_string()])).await?;
// Update a user function
client.update_user_function("get_active_users", updated_func).await?;
// Delete a user function
client.delete_user_function("get_active_users").await?;
// Create a user function
const funcId = await client.saveUserFunction({
label: "get_active_users",
name: "Get Active Users",
version: "1.0.0",
parameters: {
collection: { required: true, description: "Collection to query" }
},
functions: [{ type: "FindAll", collection: "{{collection}}" }],
tags: ["users", "query"]
});
// Get a user function by label
const func = await client.getUserFunction("get_active_users");
// List all user functions (optionally filter by tags)
const allFuncs = await client.listUserFunctions();
const taggedFuncs = await client.listUserFunctions(["users"]);
// Update a user function
await client.updateUserFunction("get_active_users", updatedFunc);
// Delete a user function
await client.deleteUserFunction("get_active_users");
// Create a user function
const funcId = await client.saveUserFunction({
label: "get_active_users",
name: "Get Active Users",
version: "1.0.0",
parameters: {
collection: { required: true, description: "Collection to query" }
},
functions: [{ type: "FindAll", collection: "{{collection}}" }],
tags: ["users", "query"]
});
// Get a user function by label
const func = await client.getUserFunction("get_active_users");
// List all user functions (optionally filter by tags)
const allFuncs = await client.listUserFunctions();
const taggedFuncs = await client.listUserFunctions(["users"]);
// Update a user function
await client.updateUserFunction("get_active_users", updatedFunc);
// Delete a user function
await client.deleteUserFunction("get_active_users");
# Create a user function
func_id = await client.save_user_function({
"label": "get_active_users",
"name": "Get Active Users",
"version": "1.0.0",
"parameters": {
"collection": {"required": True, "description": "Collection to query"}
},
"functions": [{"type": "FindAll", "collection": "{{collection}}"}],
"tags": ["users", "query"]
})
# Get a user function by label
func = await client.get_user_function("get_active_users")
# List all user functions (optionally filter by tags)
all_funcs = await client.list_user_functions()
tagged_funcs = await client.list_user_functions(tags=["users"])
# Update a user function
await client.update_user_function("get_active_users", updated_func)
# Delete a user function
await client.delete_user_function("get_active_users")
// Create a user function
funcID, err := client.SaveUserFunction(ekodb.UserFunction{
Label: "get_active_users",
Name: "Get Active Users",
Version: &version,
Parameters: map[string]ekodb.ParameterDefinition{
"collection": {Required: true, Description: "Collection to query"},
},
Functions: []ekodb.FunctionStageConfig{
ekodb.StageFindAll("{{collection}}"),
},
Tags: []string{"users", "query"},
})
// Get a user function by label
fn, err := client.GetUserFunction("get_active_users")
// List all user functions (optionally filter by tags)
allFuncs, err := client.ListUserFunctions(nil)
taggedFuncs, err := client.ListUserFunctions([]string{"users"})
// Update a user function
err = client.UpdateUserFunction("get_active_users", updatedFunc)
// Delete a user function
err = client.DeleteUserFunction("get_active_users")
// Create a user function
val funcId = client.saveUserFunction(buildJsonObject {
put("label", "get_active_users")
put("name", "Get Active Users")
put("version", "1.0.0")
putJsonObject("parameters") {
putJsonObject("collection") {
put("required", true)
put("description", "Collection to query")
}
}
putJsonArray("functions") {
add(buildJsonObject {
put("type", "FindAll")
put("collection", "{{collection}}")
})
}
putJsonArray("tags") {
add(JsonPrimitive("users"))
add(JsonPrimitive("query"))
}
})
// Get a user function by label
val func = client.getUserFunction("get_active_users")
// List all user functions (optionally filter by tags)
val allFuncs = client.listUserFunctions()
val taggedFuncs = client.listUserFunctions(listOf("users"))
// Update a user function
client.updateUserFunction("get_active_users", updatedFunc)
// Delete a user function
client.deleteUserFunction("get_active_users")
Parameters
Make functions dynamic with parameters:
{
"parameters": {
"status": {
"default": "active",
"required": false,
"description": "Filter by status"
},
"min_amount": {
"required": true,
"description": "Minimum amount"
}
}
}
Reference parameters in your function definitions using {{param_name}}:
{
"type": "Query",
"collection": "orders",
"filter": {
"type": "Condition",
"field": "status",
"operator": "Eq",
"value": "{{status}}"
}
}
Available Operations
Functions support these operation types:
Query Operations
FindAll- Retrieve all recordsQuery- Advanced filtering, sorting, paginationVectorSearch- Semantic similarity searchHybridSearch- Combine text + vector searchTextSearch- Full-text searchFindById- Get specific record by IDFindOne- Find one by key/value
CRUD Operations
Insert- Insert single recordBatchInsert- Insert multiple recordsUpdate- Update with filterUpdateById- Update specific recordDelete- Delete with filterDeleteById- Delete specific recordBatchDelete- Delete multiple records
Transformations
Group- Group and aggregateProject- Select/exclude fieldsCount- Count records
AI Operations
Chat- AI chat completionsEmbed- Generate embeddings
Logic & Control
If- Conditional executionForEach- Loop over recordsCallFunction- Call another function
External Integrations
HttpRequest- Call external APIs (Stripe, SendGrid, etc.)
Example: AI-Powered Search
Combine embeddings with search:
{
"label": "smart_search",
"name": "Smart Product Search",
"parameters": {
"query": { "required": true }
},
"functions": [
{
"type": "Embed",
"input_field": "query",
"output_field": "query_embedding"
},
{
"type": "HybridSearch",
"collection": "products",
"query_text": "{{query}}",
"query_vector": "{{query_embedding}}",
"limit": 10
}
]
}
Example: Batch Processing
Process multiple records with AI:
{
"label": "enrich_articles",
"name": "AI Content Enrichment",
"functions": [
{
"type": "Query",
"collection": "articles",
"filter": {
"type": "Condition",
"field": "embedding",
"operator": "Eq",
"value": null
},
"limit": 100
},
{
"type": "ForEach",
"functions": [
{
"type": "Embed",
"input_field": "content",
"output_field": "embedding"
},
{
"type": "UpdateById",
"collection": "articles",
"record_id": "{{id}}",
"updates": {
"embedding": "{{embedding}}"
}
}
]
}
]
}
Best Practices
- Keep it simple - Start with single operations, build up
- Use parameters - Make functions reusable with dynamic values
- Filter early - Reduce data before expensive operations
- Add descriptions - Document what each function does
- Tag for organization - Use tags like
analytics,users,ai - Version your functions - Track changes with version field
- Test thoroughly - Validate with edge cases
Storage
Functions are stored in a dedicated collection: functions_{db_name} (configurable)
Complete Documentation
For complete details including:
- All operation types and parameters
- Advanced parameter resolution
- Conditional logic patterns
- External API integration examples
- Error handling
See complete Function examples in all languages:
- Rust:
client_function_composition.rs - Python:
client_function_composition.py - TypeScript:
client_function_composition.ts - JavaScript:
client_function_composition.js - Go:
client_functions.go - Kotlin:
ClientFunctionComposition.kt
See User Functions CRUD examples:
- Rust:
client_user_functions.rs - Python:
client_user_functions.py - TypeScript:
client_user_functions.ts - Go:
client_user_functions.go - Kotlin:
ClientUserFunctions.kt
WebSocket Chat Streaming
Stream real-time LLM responses via WebSocket. The server sends ChatStreamEvent messages as the model generates text, calls tools, or completes.
Event Types
| Event | Description |
|---|---|
chunk | A text token from the LLM (stream these as they arrive) |
end | Stream completed — includes messageId, executionTimeMs, tokenUsage, contextWindow |
toolCall | The LLM wants to execute a client-side tool |
error | An error occurred during streaming |
Streaming Example
- 🔷 Go
- 📘 TypeScript
- 🦀 Rust
- 🐍 Python
- 🟣 Kotlin
ws, _ := client.WebSocket("ws://localhost:8080")
defer ws.Close()
eventCh, _ := ws.ChatSend(chatID, "What is the capital of France?")
for event := range eventCh {
switch event.Type {
case "chunk":
fmt.Print(event.Content)
case "end":
fmt.Printf("\nDone in %dms\n", event.ExecutionTimeMs)
if event.ContextWindow > 0 {
fmt.Printf("Context window: %d tokens\n", event.ContextWindow)
}
case "toolCall":
fmt.Printf("[Tool] %s\n", event.ToolName)
ws.SendToolResult(chatID, event.CallID, true,
map[string]string{"result": "done"}, "")
case "error":
fmt.Printf("Error: %s\n", event.Error)
}
}
const ws = new WebSocketClient("ws://localhost:8080/api/ws", token);
const stream = await ws.chatSend(chatId, "What is the capital of France?");
stream.on("event", (event) => {
switch (event.type) {
case "chunk":
process.stdout.write(event.content);
break;
case "end":
console.log(`\nDone in ${event.executionTimeMs}ms`);
if (event.contextWindow) {
console.log(`Context window: ${event.contextWindow} tokens`);
}
break;
case "toolCall":
console.log(`[Tool] ${event.toolName}`);
ws.sendToolResult(chatId, event.callId, true, { result: "done" });
break;
case "error":
console.error(event.error);
break;
}
});
let ws = client.websocket("ws://localhost:8080").await?;
let mut stream = ws.chat_send_with_tools(
&chat_id, "What is the capital of France?",
None, None, None, None,
).await?;
while let Some(event) = stream.recv().await {
match event {
ChatStreamEvent::Chunk(text) => print!("{}", text),
ChatStreamEvent::End { execution_time_ms, context_window, .. } => {
println!("\nDone in {}ms", execution_time_ms);
if let Some(cw) = context_window {
println!("Context window: {} tokens", cw);
}
}
ChatStreamEvent::ToolCall { tool_name, call_id, arguments, .. } => {
println!("[Tool] {}", tool_name);
let result = serde_json::json!({"result": "done"});
ws.send_tool_result(&chat_id, &call_id, true, Some(result), None).await?;
}
ChatStreamEvent::Error(err) => eprintln!("Error: {}", err),
}
}
ws = await client.websocket("ws://localhost:8080")
stream = await ws.chat_send(chat_id, "What is the capital of France?")
async for event in stream:
if event.type == "chunk":
print(event.content, end="")
elif event.type == "end":
print(f"\nDone in {event.execution_time_ms}ms")
if event.context_window:
print(f"Context window: {event.context_window} tokens")
elif event.type == "tool_call":
print(f"[Tool] {event.tool_name}")
await ws.send_tool_result(chat_id, event.call_id, True, {"result": "done"})
elif event.type == "error":
print(f"Error: {event.error}")
val ws = client.webSocket("ws://localhost:8080")
val events = ws.chatSend(chatId, "What is the capital of France?")
events.collect { event ->
when (event) {
is ChatStreamEvent.Chunk -> print(event.content)
is ChatStreamEvent.End -> {
println("\nDone in ${event.executionTimeMs}ms")
event.contextWindow?.let {
println("Context window: $it tokens")
}
}
is ChatStreamEvent.ToolCall -> {
println("[Tool] ${event.toolName}")
val result = mapOf("result" to "done")
ws.sendToolResult(chatId, event.callId, true, result)
}
is ChatStreamEvent.Error -> println("Error: ${event.error}")
}
}
Goals, Tasks & Agents
Manage AI planning workflows with goals (multi-step plans), tasks (scheduled/triggered jobs), and agents (named AI profiles).
Goals
Goals are multi-step plans the AI can create, execute, and track. Each goal has steps that move through a lifecycle: active → pending_review → approved/rejected.
- 🔷 Go
- 📘 TypeScript
- 🦀 Rust
- 🐍 Python
- 🟣 Kotlin
// Create a goal
goal, _ := client.GoalCreate(map[string]interface{}{
"title": "Migrate user data",
"description": "Move users from legacy to new schema",
"status": "active",
})
// List and search goals
goals, _ := client.GoalList()
results, _ := client.GoalSearch("migrate")
// Complete (moves to pending_review)
client.GoalComplete(goalID, map[string]interface{}{"summary": "All records migrated"})
// Approve or reject
client.GoalApprove(goalID)
client.GoalReject(goalID, map[string]interface{}{"reason": "Missing validation"})
// Step lifecycle
client.GoalStepStart(goalID, 0)
client.GoalStepComplete(goalID, 0, map[string]interface{}{"result": "done"})
client.GoalStepFail(goalID, 1, map[string]interface{}{"error": "timeout"})
// Create a goal
const goal = await client.goalCreate({
title: "Migrate user data",
description: "Move users from legacy to new schema",
status: "active",
});
// List and search
const goals = await client.goalList();
const results = await client.goalSearch("migrate");
// Lifecycle
await client.goalComplete(goalId, { summary: "All records migrated" });
await client.goalApprove(goalId);
await client.goalReject(goalId, { reason: "Missing validation" });
// Steps
await client.goalStepStart(goalId, 0);
await client.goalStepComplete(goalId, 0, { result: "done" });
// Create a goal
let goal = client.goal_create(serde_json::json!({
"title": "Migrate user data",
"description": "Move users from legacy to new schema",
"status": "active",
})).await?;
// List and search
let goals = client.goal_list().await?;
let results = client.goal_search("migrate").await?;
// Lifecycle
client.goal_complete(&goal_id, serde_json::json!({"summary": "Done"})).await?;
client.goal_approve(&goal_id).await?;
// Steps
client.goal_step_start(&goal_id, 0).await?;
client.goal_step_complete(&goal_id, 0, serde_json::json!({"result": "done"})).await?;
# Create a goal
goal = await client.goal_create({
"title": "Migrate user data",
"description": "Move users from legacy to new schema",
"status": "active",
})
# List and search
goals = await client.goal_list()
results = await client.goal_search("migrate")
# Lifecycle
await client.goal_complete(goal_id, {"summary": "Done"})
await client.goal_approve(goal_id)
# Steps
await client.goal_step_start(goal_id, 0)
await client.goal_step_complete(goal_id, 0, {"result": "done"})
// Create a goal
val goal = client.goalCreate(buildJsonObject {
put("title", "Migrate user data")
put("description", "Move users from legacy to new schema")
put("status", "active")
})
// List and search
val goals = client.goalList()
val results = client.goalSearch("migrate")
// Lifecycle
client.goalComplete(goalId, buildJsonObject { put("summary", "Done") })
client.goalApprove(goalId)
// Steps
client.goalStepStart(goalId, 0)
client.goalStepComplete(goalId, 0, buildJsonObject { put("result", "done") })
Tasks
Tasks represent scheduled or triggered jobs with lifecycle management.
| Method | Description |
|---|---|
taskCreate | Create a new task |
taskList / taskGet | List or get task details |
taskDue(now) | Get tasks due at a given time |
taskStart / taskPause / taskResume | Lifecycle transitions |
taskSucceed / taskFail | Terminal states with result/error data |
taskDelete | Remove a task |
Agents
Agents are named AI profiles with specific models and configurations.
| Method | Description |
|---|---|
agentCreate | Create a new agent profile |
agentList / agentGet | List or get agent details |
agentGetByName | Look up by name |
agentUpdate / agentDelete | Modify or remove |
agentsByDeployment | List agents on a specific deployment |
Schedule Management
Manage cron-based scheduled execution of functions and tasks.
- 🔷 Go
- 📘 TypeScript
- 🦀 Rust
- 🐍 Python
- 🟣 Kotlin
// Create a schedule
sched, _ := client.CreateSchedule(map[string]interface{}{
"name": "nightly-backup",
"cron": "0 2 * * *",
"task_type": "backup",
})
schedID := sched["id"].(string)
// List, get, update
schedules, _ := client.ListSchedules()
client.UpdateSchedule(schedID, map[string]interface{}{"cron": "0 3 * * *"})
// Pause and resume
client.PauseSchedule(schedID)
client.ResumeSchedule(schedID)
// Delete
client.DeleteSchedule(schedID)
const sched = await client.createSchedule({
name: "nightly-backup",
cron: "0 2 * * *",
taskType: "backup",
});
const schedId = sched.id;
const schedules = await client.listSchedules();
await client.pauseSchedule(schedId);
await client.resumeSchedule(schedId);
await client.deleteSchedule(schedId);
let sched = client.create_schedule(serde_json::json!({
"name": "nightly-backup",
"cron": "0 2 * * *",
"task_type": "backup",
})).await?;
let sched_id = sched["id"].as_str().unwrap();
let schedules = client.list_schedules().await?;
client.pause_schedule(&sched_id).await?;
client.resume_schedule(&sched_id).await?;
client.delete_schedule(&sched_id).await?;
sched = await client.create_schedule({
"name": "nightly-backup",
"cron": "0 2 * * *",
"task_type": "backup",
})
sched_id = sched["id"]
schedules = await client.list_schedules()
await client.pause_schedule(sched_id)
await client.resume_schedule(sched_id)
await client.delete_schedule(sched_id)
val sched = client.createSchedule(buildJsonObject {
put("name", "nightly-backup")
put("cron", "0 2 * * *")
put("taskType", "backup")
})
val schedId = sched.getString("id")
val schedules = client.listSchedules()
client.pauseSchedule(schedId)
client.resumeSchedule(schedId)
client.deleteSchedule(schedId)
Next Steps
- GitHub Examples - 129 (client library & direct HTTP examples)
- API Reference - Direct HTTP API documentation
Need Help?
- 💬 Support: app.ekodb.io/support
- 📧 Email: support@ekodb.io