Your language

fenecdb speaks HTTP and JSON, so every language reaches it with the HTTP client it already has. No driver to install.

Every example on this page is a program that runs against a real server on each change to fenecdb (make languages-test). Python, JavaScript, Go and .NET have official clients, each its standard library alone; in the other languages these few lines are the whole client. Swift, Kotlin and Dart embed the database in the app, a file on the device (Mobile and native apps), and reach a server with Fenec.connect; their tabs run the same flow in their own tests. Each tab ends with a full example: one app, Notes, in every language, a folder of a few files to clone and run (examples), and beside them a whole shop in Next.js (shop), a double-entry payments ledger on Node (ledger) and courier dispatch across tenants (dispatch).

Start a server

fenec-server --file app.fenec --http 127.0.0.1:8080 --http-token secret

Then pick your language. Each example makes a collection, writes two documents with a vector, asks for the nearest and handles a refusal. Each sends one FenecQL statement a request to POST /query, its values as $1, $2, ... in params, and the token as Authorization: Bearer.

Connect and search

install
pip install fenecdb   # the standard library alone
search.py
from fenecdb import Client, FenecError

db = Client("http://127.0.0.1:8080", token="secret")

db.query("create collection if not exists docs "
         "(title text, embed vector<3> @hnsw(cosine))")
db.query("put docs {title: $1, embed: $2}", ["Night at the oasis", [0.1, 0.2, 0.3]])
db.query("put docs {title: $1, embed: $2}", ["Dunes", [0.9, 0.1, 0.0]])

rows = db.query("get docs select title near embed $1 limit $2", [[0.1, 0.2, 0.3], 5])
# [{"title": "Night at the oasis", "_score": 0.99...}, {"title": "Dunes", ...}]

try:
    db.query("get nowhere")
except FenecError as e:
    print(e.status, e)   # 404 collection `nowhere`

Or let the query builder write the statement: the same text, every value a parameter, every name checked.

builder
docs = db.collection("docs")
docs.insert({"title": "Night at the oasis", "embed": [0.1, 0.2, 0.3]})

rows = docs.select("title").near("embed", [0.1, 0.2, 0.3]).limit(5).rows()
n = docs.where("title", "~", "Dunes").count()

batch([(text, params), ...]) runs statements as one block, all or none -- a FenecError that stops it says which (e.at, from 0); idempotency_key= on query and batch, or with_idempotency_key(key) for every write of a copy, makes a write once however often it is sent (db.replayed), and db.seq is the change the last write left the database at. AsyncClient does the same in an event loop, the endpoints of its collection(...) awaited; LangChain and LlamaIndex have stores of their own (Integrations).

Full example: Notes, a CLI over the server with the builder, match, fuse and the change stream.

search.ts
async function query(q: string, params: unknown[] = []) {
  const res = await fetch('http://127.0.0.1:8080/query', {
    method: 'POST',
    headers: { 'content-type': 'application/json', authorization: 'Bearer secret' },
    body: JSON.stringify({ query: q, params }),
  });
  const body = await res.json();
  if (!res.ok) throw Object.assign(new Error(body.error), { status: res.status });
  return body;
}

await query('create collection if not exists docs (title text, embed vector<3> @hnsw(cosine))');
await query('put docs {title: $1, embed: $2}', ['Night at the oasis', [0.1, 0.2, 0.3]]);
await query('put docs {title: $1, embed: $2}', ['Dunes', [0.9, 0.1, 0.0]]);

const rows = await query('get docs select title near embed $1 limit $2', [[0.1, 0.2, 0.3], 5]);

Or npm install @fenecdb/web: its connect sends the query builder's statements to a server, the same code that runs in the page.

client.ts
import { connect } from '@fenecdb/web';
import type { FenecSchema } from './fenec-schema.js';   // fenec types app.fenec

const db = connect<FenecSchema>('http://127.0.0.1:8080', { token: 'secret' });
await db.from('docs').insert({ title: 'Night at the oasis', embed: [0.1, 0.2, 0.3] });
const rows = await db.from('docs').select('title').near('embed', [0.1, 0.2, 0.3]).limit(5).rows();
//    rows: { title: string | null; _score: number }[]

The same code runs as plain JavaScript, without the types. In the browser there is no server at all: the same database runs in the page (JavaScript and TypeScript).

Full example: Notes from Node through @fenecdb/web/client, its live queries run on the server; in the page, web-local and react.

install
go get github.com/fenecdb/fenec/integrations/go   # the standard library alone
main.go
import fenecdb "github.com/fenecdb/fenec/integrations/go"

db := fenecdb.New("http://127.0.0.1:8080", fenecdb.WithToken("secret"))

db.Exec(ctx, "create collection if not exists docs (title text, embed vector<3> @hnsw(cosine))")
db.Exec(ctx, "put docs {title: $1, embed: $2}", "Night at the oasis", []float32{0.1, 0.2, 0.3})
db.Exec(ctx, "put docs {title: $1, embed: $2}", "Dunes", []float32{0.9, 0.1, 0.0})

type Hit struct {
	Title string  `json:"title"`
	Score float32 `json:"_score"`
}
hits, err := fenecdb.QueryAs[Hit](ctx, db, "get docs select title near embed $1 limit $2", []float32{0.1, 0.2, 0.3}, 5)

var e *fenecdb.Error
if _, err := db.Query(ctx, "get nowhere"); errors.As(err, &e) {
	fmt.Println(e.Status, e.Code, e.Message) // 404 not_found collection `nowhere`
}

Or let the query builder write the statement, immutable, each step a copy:

builder
docs := db.From("docs")
docs.Insert(ctx, fenecdb.D("title", "Night at the oasis", "embed", []float32{0.1, 0.2, 0.3}))

query := docs.Select("title").Near("embed", []float32{0.1, 0.2, 0.3}).Limit(5)
hits, err := fenecdb.RowsAs[Hit](ctx, query)
n, err := docs.Where("title", "~", "Dunes").Count(ctx)

Query gives rows as []map[string]any; Batch, IdempotencyKey, After for a replica, Subscribe, Changes and WithTenant cover the rest of the HTTP endpoint (integrations/go); a refusal is an *fenecdb.Error, and a batch's says which statement stopped it (At, from 0). A []float32 goes out as the decimals that read back as each f32, so a vector round-trips to the bit.

Without the SDK, net/http and encoding/json:

main.go
// query runs one FenecQL statement and decodes its answer into out.
func query(out any, q string, params ...any) error {
	body, _ := json.Marshal(map[string]any{"query": q, "params": params})
	req, _ := http.NewRequest("POST", "http://127.0.0.1:8080/query", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer secret")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()
	if res.StatusCode >= 300 {
		e := &FenecError{Status: res.StatusCode}
		json.NewDecoder(res.Body).Decode(e)
		return e
	}
	if out == nil {
		return nil
	}
	return json.NewDecoder(res.Body).Decode(out)
}

query(nil, "create collection if not exists docs (title text, embed vector<3> @hnsw(cosine))")
query(nil, "put docs {title: $1, embed: $2}", "Night at the oasis", []float32{0.1, 0.2, 0.3})
query(nil, "put docs {title: $1, embed: $2}", "Dunes", []float32{0.9, 0.1, 0.0})

var rows []struct {
	Title string  `json:"title"`
	Score float32 `json:"_score"`
}
err := query(&rows, "get docs select title near embed $1 limit $2", []float32{0.1, 0.2, 0.3}, 5)

FenecError is a struct of the status and the body's error.

Full example: Notes, a CLI over the server with the builder, typed rows and a subscription.

install
cargo add ureq --features json
cargo add serde_json
main.rs
use serde_json::{json, Value};

fn query(q: &str, params: Value) -> Result<Value, ureq::Error> {
    Ok(ureq::post("http://127.0.0.1:8080/query")
        .set("Authorization", "Bearer secret")
        .send_json(json!({"query": q, "params": params}))?
        .into_json()?)
}

query("create collection if not exists docs (title text, embed vector<3> @hnsw(cosine))", json!([]))?;
query("put docs {title: $1, embed: $2}", json!(["Night at the oasis", [0.1, 0.2, 0.3]]))?;
query("put docs {title: $1, embed: $2}", json!(["Dunes", [0.9, 0.1, 0.0]]))?;

let rows = query("get docs select title near embed $1 limit $2", json!([[0.1, 0.2, 0.3], 5]))?;

match query("get nowhere", json!([])) {
    Err(ureq::Error::Status(404, res)) => { /* res.into_json()? holds {"error": ...} */ }
    other => { /* ... */ }
}

Or embed the engine in your process, with no server (Embedded Rust).

Full example: Notes with fenec-core embedded, a file and no server.

install
dotnet add package FenecDb   # HttpClient and System.Text.Json alone
Program.cs
using FenecDb;

using var db = new FenecClient("http://127.0.0.1:8080", new() { Token = "secret" });

await db.ExecAsync("create collection if not exists docs (title text, embed vector<3> @hnsw(cosine))");
await db.ExecAsync("put docs {title: $1, embed: $2}", ["Night at the oasis", new[] { 0.1f, 0.2f, 0.3f }]);
await db.ExecAsync("put docs {title: $1, embed: $2}", ["Dunes", new[] { 0.9f, 0.1f, 0.0f }]);

var hits = await db.QueryAsync<Hit>("get docs select title near embed $1 limit $2", [new[] { 0.1f, 0.2f, 0.3f }, 5]);

try { await db.QueryAsync("get nowhere"); }
catch (FenecException e) { Console.WriteLine($"{e.Status} {e.Code} {e.Message}"); } // 404 not_found collection `nowhere`

record Hit(string Title, [property: JsonPropertyName("_score")] float Score);

Or let the query builder write the statement, immutable, each call a new query:

builder
var docs = db.From("docs");
await docs.InsertAsync(new { title = "Night at the oasis", embed = new[] { 0.1f, 0.2f, 0.3f } });

var query = docs.Select("title").Near("embed", new[] { 0.1f, 0.2f, 0.3f }).Limit(5);
var hits = await query.RowsAsync<Hit>();
var n = await docs.Where("title", "~", "Dunes").CountAsync();

.NET 8 and newer, async throughout with a CancellationToken on every call. QueryAsync<T> maps rows to records or classes by property name, any case; BatchAsync, WithIdempotencyKey, After for a replica, SubscribeAsync as an IAsyncEnumerable, ChangesAsync and a Tenant option cover the rest of the HTTP endpoint (integrations/dotnet); a refusal is a FenecException, and a batch's says which statement stopped it (At, from 0). A float[] or ReadOnlyMemory<float> goes out as the decimals that read back as each float.

Without the SDK, HttpClient and System.Text.Json:

Program.cs
var http = new HttpClient { BaseAddress = new Uri("http://127.0.0.1:8080") };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "secret");

async Task<JsonElement> Query(string query, params object[] args)
{
    var json = JsonSerializer.Serialize(new { query, @params = args });
    var res = await http.PostAsync("/query", new StringContent(json, Encoding.UTF8, "application/json"));
    var body = await res.Content.ReadFromJsonAsync<JsonElement>();
    if (!res.IsSuccessStatusCode)
        throw new HttpRequestException(body.GetProperty("error").GetString(), null, res.StatusCode);
    return body;
}

await Query("create collection if not exists docs (title text, embed vector<3> @hnsw(cosine))");
await Query("put docs {title: $1, embed: $2}", "Night at the oasis", new[] { 0.1f, 0.2f, 0.3f });
await Query("put docs {title: $1, embed: $2}", "Dunes", new[] { 0.9f, 0.1f, 0.0f });

var rows = await Query("get docs select title near embed $1 limit $2", new[] { 0.1f, 0.2f, 0.3f }, 5);
var titles = rows.EnumerateArray().Select(r => r.GetProperty("title").GetString());

The body is serialised whole: PostAsJsonAsync streams it chunked, and the server wants a Content-Length.

Full example: Notes, a console app over the server with the builder, records and SubscribeAsync.

Search.java
static final HttpClient HTTP = HttpClient.newHttpClient();

static HttpResponse<String> query(String json) throws Exception {
    var req = HttpRequest.newBuilder(URI.create("http://127.0.0.1:8080/query"))
        .header("Content-Type", "application/json")
        .header("Authorization", "Bearer secret")
        .POST(HttpRequest.BodyPublishers.ofString(json))
        .build();
    return HTTP.send(req, HttpResponse.BodyHandlers.ofString());
}

query("""
    {"query": "create collection if not exists docs (title text, embed vector<3> @hnsw(cosine))"}""");
query("""
    {"query": "put docs {title: $1, embed: $2}", "params": ["Night at the oasis", [0.1, 0.2, 0.3]]}""");
query("""
    {"query": "put docs {title: $1, embed: $2}", "params": ["Dunes", [0.9, 0.1, 0.0]]}""");

String rows = query("""
    {"query": "get docs select title near embed $1 limit $2", "params": [[0.1, 0.2, 0.3], 5]}""").body();

java.net.http alone, Java 11 and newer (text blocks need 15), run as java Search.java. The standard library has no JSON: the test reads the titles with a pattern, and a project reads the rows with Jackson or Gson.

search.php
function query(string $q, array $params = []) {
    $ch = curl_init('http://127.0.0.1:8080/query');
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Authorization: Bearer secret'],
        CURLOPT_POSTFIELDS => json_encode(['query' => $q, 'params' => $params]),
    ]);
    $body = json_decode(curl_exec($ch), true);
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    if ($status >= 300) {
        throw new RuntimeException($body['error'], $status);
    }
    return $body;
}

query('create collection if not exists docs (title text, embed vector<3> @hnsw(cosine))');
query('put docs {title: $1, embed: $2}', ['Night at the oasis', [0.1, 0.2, 0.3]]);
query('put docs {title: $1, embed: $2}', ['Dunes', [0.9, 0.1, 0.0]]);

$rows = query('get docs select title near embed $1 limit $2', [[0.1, 0.2, 0.3], 5]);

The curl extension and json_encode; Guzzle sends the same request.

search.rb
require "json"
require "net/http"

URL = URI("http://127.0.0.1:8080")

def query(q, params = [])
  req = Net::HTTP::Post.new(URI.join(URL, "/query"), "Content-Type" => "application/json")
  req["Authorization"] = "Bearer secret"
  req.body = JSON.generate(query: q, params: params)
  res = Net::HTTP.start(URL.host, URL.port) { |http| http.request(req) }
  body = JSON.parse(res.body)
  raise "#{res.code}: #{body["error"]}" unless res.is_a?(Net::HTTPSuccess)
  body
end

query("create collection if not exists docs (title text, embed vector<3> @hnsw(cosine))")
query("put docs {title: $1, embed: $2}", ["Night at the oasis", [0.1, 0.2, 0.3]])
query("put docs {title: $1, embed: $2}", ["Dunes", [0.9, 0.1, 0.0]])

rows = query("get docs select title near embed $1 limit $2", [[0.1, 0.2, 0.3], 5])
Package.swift
.package(url: "https://github.com/fenecdb/fenec", from: "0.1.7")   // product "FenecDB"
Search.swift
import FenecDB

// Embedded: the database is a file the app holds, no server.
let db = try await Fenec.open(path: path)

try await db.execute("create collection if not exists docs (title text, embed vector<3> @hnsw(cosine))")
try await db.execute("put docs {title: $1, embed: $2}", "Night at the oasis", [Float(0.1), 0.2, 0.3])
try await db.execute("put docs {title: $1, embed: $2}", "Dunes", [Float(0.9), 0.1, 0.0])

let rows = try await db.query("get docs select title near embed $1 limit $2", [Float(0.1), 0.2, 0.3], 5)
// rows[0]["title"] == "Night at the oasis", rows[0]["_score"] its score

do {
    _ = try await db.query("get nowhere")
} catch let e as FenecError {
    print(e.code, e)   // notFound: collection `nowhere` ...
}
builder
let docs = try db.from("docs")
try await docs.insert(["title": "Night at the oasis", "embed": .floats([0.1, 0.2, 0.3])] as Value)
let near = try await docs.select("title").near("embed", [Float(0.1), 0.2, 0.3]).limit(5).rows()
let n = try await docs.where("title", "~", "Dunes").count()
over HTTP
// No file: every query goes to the server, through the same builder.
let remote = Fenec.connect(url: "http://127.0.0.1:8080", token: "secret")
try await remote.execute("put docs {title: $1, embed: $2}", "Dunes", [Float(0.9), 0.1, 0.0])
let hits = try await remote.from("docs").select("title").near("embed", [Float(0.1), 0.2, 0.3]).limit(5).rows()

A [Float] goes to the library as its bytes; rows decode as any Decodable. A live query for SwiftUI, where the file goes and a replica a server keeps in step are on Mobile and native apps. A device reaches a server through TLS: URLSession makes the requests; over it, remote.batch([...], idempotencyKey:) lands statements whole and withIdempotencyKey makes a write once (Without a file).

Full example: Notes, a SwiftUI app over a file, its list a LiveQuery, and a CLI.

build.gradle.kts
implementation("com.fenecdb:fenecdb-android:0.1.7")   // the AAR; the JVM: com.fenecdb:fenecdb
Search.kt
import com.fenecdb.*

// Embedded: the database is a file the app holds, no server.
val db = Fenec.openAsync(path)

db.execute("create collection if not exists docs (title text, embed vector<3> @hnsw(cosine))")
db.execute("put docs {title: \$1, embed: \$2}", "Night at the oasis", floatArrayOf(0.1f, 0.2f, 0.3f))
db.execute("put docs {title: \$1, embed: \$2}", "Dunes", floatArrayOf(0.9f, 0.1f, 0f))

val rows = db.query("get docs select title near embed \$1 limit \$2", floatArrayOf(0.1f, 0.2f, 0.3f), 5)
// rows[0].string("title") == "Night at the oasis", rows[0].double("_score") its score

try {
    db.query("get nowhere")
} catch (e: FenecException) {
    println("${e.code} ${e.message}")   // NOT_FOUND collection `nowhere` ...
}
builder
val docs = db.from("docs")
docs.insert(mapOf("title" to "Night at the oasis", "embed" to floatArrayOf(0.1f, 0.2f, 0.3f)))
val near = docs.select("title").near("embed", floatArrayOf(0.1f, 0.2f, 0.3f)).limit(5).rows()
val n = docs.where("title", "~", "Dunes").count()
over HTTP
// No file: every query goes to the server, through the same builder.
val remote = Fenec.connect("http://127.0.0.1:8080", token = "secret")
remote.execute("put docs {title: \$1, embed: \$2}", "Dunes", floatArrayOf(0.9f, 0.1f, 0f))
val hits = remote.from("docs").select("title").near("embed", floatArrayOf(0.1f, 0.2f, 0.3f)).limit(5).rows()

The calls suspend, on Dispatchers.IO; Java calls the ...Blocking ones. Live queries are Flows, and a replica a server keeps in step is Fenec.sync (Mobile and native apps). A device reaches a server through TLS: HttpURLConnection makes the requests; over it, remote.batch(listOf(...), idempotencyKey = id) lands statements whole and withIdempotencyKey makes a write once (Without a file).

Full example: Notes, a Compose app over a file, its list a live Flow, and a JVM CLI.

install
flutter pub add fenecdb_flutter   # Dart alone: dart pub add fenecdb
search.dart
import 'package:fenecdb/fenecdb.dart';

// Embedded: the database is a file the app holds, no server.
final db = await Fenec.open(path);

await db.execute('create collection if not exists docs (title text, embed vector<3> @hnsw(cosine))');
await db.execute(r'put docs {title: $1, embed: $2}', ['Night at the oasis', Float32List.fromList([0.1, 0.2, 0.3])]);
await db.execute(r'put docs {title: $1, embed: $2}', ['Dunes', Float32List.fromList([0.9, 0.1, 0.0])]);

final rows = await db.query(r'get docs select title near embed $1 limit $2', [Float32List.fromList([0.1, 0.2, 0.3]), 5]);
// rows[0]['title'] == 'Night at the oasis', rows[0]['_score'] its score

try {
  await db.query('get nowhere');
} on FenecException catch (e) {
  print('${e.code.name} ${e.message}');   // notFound collection `nowhere` ...
}
builder
final docs = db.from('docs');
await docs.insert({'title': 'Night at the oasis', 'embed': Float32List.fromList([0.1, 0.2, 0.3])});
final near = await docs.select(['title']).near('embed', Float32List.fromList([0.1, 0.2, 0.3])).limit(5).rows();
final n = await docs.where('title', '~', 'Dunes').count();
over HTTP
// No file: every query goes to the server, through the same builder.
final remote = Fenec.connect('http://127.0.0.1:8080', token: 'secret');
await remote.execute(r'put docs {title: $1, embed: $2}', ['Dunes', Float32List.fromList([0.9, 0.1, 0.0])]);
final hits = await remote.from('docs').select(['title']).near('embed', Float32List.fromList([0.1, 0.2, 0.3])).limit(5).rows();

Every call runs on an isolate the database keeps, so none blocks the UI. Live queries are Streams, and a replica a server keeps in step is Fenec.openSynced (Mobile and native apps). A device reaches a server through TLS: dart:io's HttpClient makes the requests; over it, remote.batch([...], idempotencyKey: id) lands statements whole and withIdempotencyKey makes a write once (Without a file).

Full example: Notes, a Flutter app over a file, its list a StreamBuilder over a live query.

The builder

Python, JavaScript, Go, .NET, Swift, Kotlin and Dart each have a query builder: a chain of calls that writes the FenecQL statement, every value a parameter and every name checked against FenecQL's own rule, which is where an injection would have to get through. All seven build the same text from the same chain, to the byte, and refuse the same mistakes with the same message. Python's builder starts with collection, since from is a keyword there.

PythonJavaScriptGo.NET
db.collection("docs")db.from('docs')db.From("docs")db.From("docs")
.where("year", ">=", 2024).where('year', '>=', 2024).Where("year", ">=", 2024).Where("year", ">=", 2024)
.where(or_({"lang": "tr"}, {"tags": {"has": "rust"}})).where(or({ lang: 'tr' }, { tags: { has: 'rust' } })).WhereCond(fenecdb.Or(fenecdb.Fields("lang", "tr"), fenecdb.Fields("tags", fenecdb.Ops("has", "rust")))).Where(Cond.Or(Cond.Cmp("lang", "=", "tr"), Cond.Cmp("tags", "has", "rust")))
.near("embed", v, ef=64).near('embed', v, { ef: 64 }).Near("embed", v, fenecdb.Ef(64)).Near("embed", v, ef: 64)
.lookup("reviews", on="product_id", limit=3).lookup('reviews', { on: 'product_id', limit: 3 }).Lookup("reviews", fenecdb.On("product_id"), fenecdb.Limit(3)).Lookup("reviews", on: "product_id", limit: 3)
.order("title", "desc", collate="tr").order('title', 'desc', { collate: 'tr' }).Order("title", "desc", fenecdb.Collate("tr")).Order("title", "desc", collate: "tr")
.rows(), .first(), .count().rows(), .first(), .count().Rows(ctx), .First(ctx), .Count(ctx), RowsAs[T].RowsAsync(), .FirstAsync(), .CountAsync(), .RowsAsync<T>()
.insert(doc), .update(patch), .delete().insert(doc), .update(patch), .delete().Insert(ctx, doc), .Update(ctx, patch), .Delete(ctx).InsertAsync(doc), .UpdateAsync(patch), .DeleteAsync()
.update({"n": inc(1)}), .insert(doc, if_absent=True).update({ n: inc(1) }), .insert(doc, { ifAbsent: true }).Update(ctx, fenecdb.D("n", fenecdb.Inc(1))), .Insert(ctx, doc, fenecdb.IfAbsent()).UpdateAsync(new { n = Computed.Inc(1) }), .InsertAsync(doc, ifAbsent: true)
SwiftKotlinDart
try db.from("docs")db.from("docs")db.from('docs')
.where("year", ">=", 2024).where("year", ">=", 2024).where('year', '>=', 2024)
.where(.or(["lang": "tr"], ["tags": ["has": "rust"]])).where(Cond.or(mapOf("lang" to "tr"), mapOf("tags" to mapOf("has" to "rust")))).where(Cond.or([{'lang': 'tr'}, {'tags': {'has': 'rust'}}]))
.near("embed", v, ef: 64).near("embed", v, ef = 64).near('embed', v, ef: 64)
.lookup("reviews", on: "product_id", limit: 3).lookup("reviews", on = "product_id", limit = 3).lookup('reviews', on: 'product_id', limit: 3)
.order("title", "desc", collate: "tr").order("title", "desc", collate = "tr").order('title', 'desc', 'tr')
.rows(), .first(), .count(), .rows(as: T.self).rows(), .first(), .count(), .rowsBlocking().rows(), .first(), .count()
.insert(doc), .update(patch), .delete().insert(doc), .update(patch), .delete().insert(doc), .update(patch), .delete()
.update(["n": .inc(1)]), .insert(doc, ifAbsent: true).update(mapOf("n" to Computed.inc(1))), .insert(doc, ifAbsent = true).update({'n': Computed.inc(1)}), .insert(doc, ifAbsent: true)

Each has select with aggregates and group, where with every operator, in, null, paths into a json field and or, and, not and raw; near, match, fuse and rerank; highlight, snippet and facet, the facets beside the rows; lookup and its chains; order with a collation; limit and offset; and the writes, an update or delete with no filter refused unless it says all, a value worked out over the row with inc and expr (Writes that read the row), and an insert if absent. A query is immutable, so a base query can be kept and branched from.

Details: one golden file

integrations/builder-golden.json is the contract: each case a chain of steps ({op, args}, the JavaScript builder's names) and the statement it must make, its text and parameters, or the refusal and its message. The JavaScript builder is the reference: make builder-golden runs every chain through it and writes what it made, and its own tests run the file again, so the two cannot drift. The Python, Go, .NET, Swift, Kotlin and Dart tests each read every case, call their own builder, and compare the text and parameters exactly. The cases cover every method and option, each operator and kind of value, parameter numbering across where, near, match and the levels of a lookup chain, and the names and combinations each builder refuses. Each SDK's tests also run builder queries against a real server and hold their answers to the same statements sent as text.

What a language has no word for is written its own way: Go's object conditions are Fields and Ops, names and values in turn, since a Go map has no order and the order is the text's; a Go document keeps its order as a fenecdb.D(...), and a map's fields go in sorted. Options are keyword arguments in Python, named arguments in C# and functional options in Go, where a struct could not tell limit 0 from no limit. A refusal is a FenecError with status 0 in Python, a *fenecdb.QueryError in Go and a FenecQueryException in .NET. Swift's dictionaries have no order either, so a document or an object condition is a Value or Row literal, which keeps the order written, or a Codable type, its fields in the order it encodes them; Kotlin's and Dart's maps keep their order. A refusal there is a FenecError, a FenecException and a FenecException, each with the code builder, and every builder method throws as it is called, so a Swift chain takes one try.

From the shell

The same request from curl:

curl -X POST localhost:8080/query -H 'authorization: Bearer secret' \
  -d '{"query":"get docs select title near embed $1 limit $2","params":[[0.1,0.2,0.3],5]}'
# [{"title":"Night at the oasis","_score":0.9999999},{"title":"Dunes","_score":0.3246547}]

The REST routes, batches, subscriptions and the change stream are on HTTP endpoint.

Details: what the examples are held to

The programs under integrations/languages are the examples above with their asserts: make languages-test starts a fenec-server over a new file for each language and runs Python, Java, PHP and Ruby from their official images in Docker, and JavaScript, Go, C# and Rust with the toolchains on the machine — Go and C# both through their SDK and without it, and each SDK's program through its query builder too. The SDKs' own tests (make go-test, make dotnet-test) start a primary, a replica of it and a node of tenants and cover the rest: every type of parameter, the errors, a batch put back whole, an idempotency key, reading a write on the replica, a subscription, the change stream, a tenant and a scoped token. CI runs it on every change, so a change to the HTTP endpoint that would break one of them is caught before it ships.

The TypeScript tab's programs run there as the JavaScript they are without their types, and the tab's code itself is type-checked under tsc --strict against the client's declarations, as every TypeScript example in these docs is (make docs-types).

Every answer is JSON: a read is an array of rows, a vector search's rows carry their _score, a write is {"affected": n}, and a refusal is a status of 400 and up with {"error": "..."} — 401 for a missing or wrong token, 404 for a collection that is not there, 409 for an id insert finds taken. A vector goes in as a JSON array of numbers and comes out as the shortest text that reads back as each f32. Values always go in params, never into the statement's text.

The server keeps a connection open between requests (HTTP/1.1 keep-alive) for a client that reuses it, as Go's, .NET's, Java's and ureq's do. A body is read by its Content-Length; a chunked one is refused.