.NET
One import, one connection. Microsecond reads from L1 cache.
Install
dotnet add package GoldLapel
# You also need a Postgres driver — Npgsql is the standard:
dotnet add package Npgsql Gold Lapel is driver-agnostic, but the .NET ecosystem is effectively single-driver — install Npgsql and point it at gl.Url.
Quick Start
using GoldLapel;
using Npgsql;
// Spawn the proxy — returns a GoldLapel instance (IAsyncDisposable)
await using var gl = await GoldLapel.StartAsync(
"postgresql://user:pass@localhost:5432/mydb",
opts => {
opts.ProxyPort = 7932;
opts.LogLevel = "info";
});
// Use gl.Url with Npgsql for raw SQL — gl.Url is already a connection string
await using var conn = new NpgsqlConnection(gl.Url);
await conn.OpenAsync();
await using var cmd = new NpgsqlCommand("SELECT * FROM users WHERE id = @id", conn);
cmd.Parameters.AddWithValue("id", 42);
await using var reader = await cmd.ExecuteReaderAsync();
// Or use Gold Lapel's wrapper methods directly — no connection arg needed
var hits = await gl.SearchAsync("articles", "body", "postgres tuning");
await gl.DocInsertAsync("events", new { type = "signup" });
// gl disposes automatically via `await using` — stops the proxy and closes the internal connection Repeated reads serve in microseconds from the built-in L1 cache.
After startup, Gold Lapel prints a one-line summary to stderr and serves a dashboard:
gl.DashboardUrl
// => "http://127.0.0.1:7933" (or null if not running / dashboard disabled) Banner goes to stderr so it never pollutes stdout-piped application output. Set opts.Silent = true on the options callback to suppress it entirely — useful for daemons and structured-log pipelines that inspect both streams.
Transactional coordination
When you want wrapper methods to run inside your own transaction, pass your NpgsqlConnection via gl.UsingAsync(conn, callback) (scoped, backed by AsyncLocal) or the connection: named parameter (per call):
await using var conn = new NpgsqlConnection(gl.Url);
await conn.OpenAsync();
await using var tx = await conn.BeginTransactionAsync();
// Scoped: every wrapper call inside the callback uses `conn`
await gl.UsingAsync(conn, async gl => {
await gl.DocInsertAsync("events", new { type = "order.created" });
await gl.IncrAsync("counters", "orders");
});
await tx.CommitAsync();
// Or per-call — every wrapper method accepts an optional `connection` named parameter
await gl.DocInsertAsync("events", new { type = "x" }, connection: conn); The scoped override survives across await boundaries (backed by AsyncLocal), so nested async helpers pick up the same connection. Every wrapper method also accepts an optional connection: named parameter for one-off overrides.
API
await GoldLapel.StartAsync(upstream, Action<GoldLapelOptions>)
Async factory that spawns the proxy and returns a GoldLapel instance. Implements IAsyncDisposable — use with await using for automatic cleanup. Eagerly opens the wrapper's internal Npgsql connection so wrapper methods are fast from the first call.
upstream— your Postgres connection string (e.g.postgresql://user:pass@localhost:5432/mydb)- options callback — configure port, log level, raw config, extra args
gl.Url
Proxy URL string, already formatted as a connection string. Pass to new NpgsqlConnection(gl.Url).
gl.DashboardUrl
Dashboard URL (e.g. http://127.0.0.1:7933), or null if not running or the dashboard is disabled.
gl.UsingAsync(connection, Func<GoldLapel, Task>)
Runs the callback with connection as the implicit connection for any wrapper method invoked inside. Backed by AsyncLocal — survives across await boundaries. Every wrapper method also accepts an optional connection: named parameter for one-off overrides.
await gl.StopAsync() / gl.DisposeAsync()
Stops the proxy and closes the internal connection. Idempotent. Called automatically by await using.
GoldLapel.ConfigKeys()
Returns an IReadOnlyCollection<string> of all valid config key names.
using GoldLapel;
foreach (var key in GoldLapel.ConfigKeys())
Console.WriteLine(key); GoldLapelOptions
Configuration class populated via the options callback on StartAsync.
ProxyPort— proxy port (default: 7932)DashboardPort— dashboard port (default:ProxyPort + 1; set to0to disable)InvalidationPort— cache-invalidation port (default:ProxyPort + 2)LogLevel—trace/debug/info/warn/error(string-based; translated to the binary's verbose flags internally)Mode— operating mode (waiter,bellhop)License— path to the signed license fileClient— client identifier for telemetry tagging (defaults to"dotnet")ConfigFile— path to a TOML config file read by the Rust binaryConfig—Dictionary<string, object>of structured tuning knobs (see Configuration)ExtraArgs— raw CLI flags passed to the binarySilent— suppress thestderrstartup banner (default:false)Mesh— opt into the mesh at startup (HQ enforces the license; denial is non-fatal)MeshTag— optional mesh tag; instances sharing a tag cluster together
Promoted top-level options (ProxyPort, DashboardPort, LogLevel, Mode, etc.) are not valid keys inside Config — passing them there raises ArgumentException at construction.
Multiple instances
Each StartAsync call spawns its own proxy subprocess and returns a fresh instance. Use different ports to run several side by side.
// Each StartAsync call returns a fresh instance on its own port
await using var glPrimary = await GoldLapel.StartAsync(
"postgresql://primary/mydb",
opts => opts.ProxyPort = 7932);
await using var glReplica = await GoldLapel.StartAsync(
"postgresql://replica/mydb",
opts => opts.ProxyPort = 7942);
await glPrimary.SearchAsync("articles", "body", "query"); // hits primary
await glReplica.SearchAsync("articles", "body", "query"); // hits replica
// Each instance manages its own proxy and cache — disposed automatically Configuration
Populate opts.Config with a dictionary on the options callback. Keys use camelCase and map to CLI flags (poolSize → --pool-size). The LogLevel option accepts string levels and translates to the binary's verbose flags internally.
await using var gl = await GoldLapel.StartAsync(
"postgresql://user:pass@localhost/mydb",
opts => {
opts.LogLevel = "info"; // top-level: trace | debug | info | warn | error
opts.Mode = "waiter"; // top-level (promoted out of config)
opts.Mesh = true; // top-level: opt into the mesh at startup
opts.MeshTag = "prod-east"; // top-level: optional mesh tag
opts.Config = new Dictionary<string, object> // structured tuning knobs only
{
["poolSize"] = 50,
["disableMatviews"] = true,
["replica"] = new List<string> { "postgresql://user:pass@replica1/mydb" },
};
}); Unknown keys throw ArgumentException immediately. A dedicated configuration reference covers every available setting.
Raw CLI flags
You can also pass raw CLI flags via ExtraArgs:
await using var gl = await GoldLapel.StartAsync(
"postgresql://user:pass@localhost:5432/mydb",
opts => {
opts.ExtraArgs = new[] { "--threshold-duration-ms", "200", "--refresh-interval-secs", "30" };
}); Environment variables
The binary also reads GOLDLAPEL_PROXY_PORT, GOLDLAPEL_UPSTREAM, and all other GOLDLAPEL_* env vars automatically. Set GOLDLAPEL_BINARY to override the binary location.
Supported platforms
The package targets netstandard2.0, so it works with .NET Framework 4.6.1+, .NET Core 2.0+, .NET 5+, Xamarin, Unity, and more. Bundled native binaries:
- Linux x64
- Linux ARM64
- macOS ARM64
- Windows x64
Upgrading from v0.1
v0.2 is a breaking redesign. The synchronous new GoldLapel(url).Start() pattern (which returned an Npgsql connection) is replaced by an async GoldLapel.StartAsync(url, opts) factory that returns a GoldLapel instance. Bring your own NpgsqlConnection and point it at gl.Url.
// v0.1 (old) — constructor + synchronous Start
using var gl = new GoldLapel("postgresql://...");
var conn = gl.Start();
using var cmd = new NpgsqlCommand("SELECT 1", conn);
// v0.2 (new) — async factory + bring-your-own Npgsql connection
await using var gl = await GoldLapel.StartAsync(
"postgresql://...",
opts => opts.ProxyPort = 7932);
await using var conn = new NpgsqlConnection(gl.Url);
await conn.OpenAsync();
await using var cmd = new NpgsqlCommand("SELECT 1", conn);
await cmd.ExecuteScalarAsync(); await GoldLapel.StartAsync(url, opts)replacesnew GoldLapel(url)+gl.Start().- The instance is
IAsyncDisposable— useawait usingfor automatic cleanup (IDisposablepreviously). - All wrapper methods are async now (
SearchAsync,DocInsertAsync, etc.) — add theAsyncsuffix andawaitthem. - Bring your own
NpgsqlConnectionagainstgl.Urlinstead of using the one returned fromStart. - New
LogLeveloption accepts string levels. - All wrapper methods accept an optional
connection:named parameter;gl.UsingAsync(conn, fn)provides anAsyncLocal-scoped override. - Multiple instances are first-class — each
StartAsynccall spawns its own proxy.
Redis Replacement
Gold Lapel includes a Redis Replacement API — common Redis patterns backed by PostgreSQL. Every method hangs directly off gl and accepts an optional connection: named parameter for transactional coordination.
Pub/Sub
// Pub/Sub — backed by PostgreSQL LISTEN/NOTIFY
await gl.PublishAsync("orders", "new order");
await gl.SubscribeAsync("orders", msg => {
Console.WriteLine(msg);
}); Queues
// Queues — backed by FOR UPDATE SKIP LOCKED
await gl.EnqueueAsync("jobs", new { task = "send_email" });
var job = await gl.DequeueAsync("jobs"); Counters
// Counters — backed by INSERT ON CONFLICT
await gl.IncrAsync("page_views", "home");
var count = await gl.GetCounterAsync("page_views", "home"); Sorted Sets
// Sorted Sets — backed by ORDER BY and window functions
await gl.ZaddAsync("leaderboard", "player1", 100);
var top = await gl.ZrangeAsync("leaderboard", 0, 9); Streams
// Streams — append-only log with consumer groups
var id = await gl.StreamAddAsync("events", new { type = "signup" });
await gl.StreamCreateGroupAsync("events", "workers");
var msgs = await gl.StreamReadAsync("events", "workers", "worker-1");
await gl.StreamAckAsync("events", "workers", msgs[0].Id); See the Redis Replacement docs for the complete API — including geospatial, rate limiting, sessions, and scripting.
Document Store
DocFindAsync, DocInsertAsync, DocUpdateAsync, DocDeleteAsync and friends operate on JSONB-backed collections. Tables are auto-created on first use.
Filter operators
DocFindAsync supports the MongoDB filter operators you'd reach for — $elemMatch, $text, $gt, $in, and more.
// $elemMatch — scope multi-condition filters to a single array element
var orders = await gl.DocFindAsync("orders", new Dictionary<string, object>
{
["items"] = new Dictionary<string, object>
{
["$elemMatch"] = new Dictionary<string, object>
{
["sku"] = "ABC-123",
["qty"] = new Dictionary<string, object> { ["$gte"] = 2 },
},
},
}); // $text — full-text search, document-wide or field-scoped
var hits = await gl.DocFindAsync("articles", new Dictionary<string, object>
{
["$text"] = new Dictionary<string, object> { ["$search"] = "postgres tuning" },
}); See Appendix D: Filter Operator Reference for the full list, Postgres translations, and index notes.
Search
Full-text search utilities backed by PostgreSQL tsvector/tsquery. No extensions required.
Facets
// Facets — value counts, optionally filtered by a search query
var results = await gl.FacetsAsync("articles", "category");
var filtered = await gl.FacetsAsync("articles", "category",
query: "machine learning", queryColumn: "body"); Aggregations
// Aggregations — count, sum, avg, min, max with optional grouping
var byRegion = await gl.AggregateAsync("orders", "total", "avg",
groupBy: "region"); Custom Search Config
// Custom search config
await gl.CreateSearchConfigAsync("my_english", copyFrom: "english"); Percolator
Store queries, then match documents against them. Like Elasticsearch percolate.
// Percolator — store queries, match documents against them
await gl.PercolateAddAsync("alerts", "breaking-news",
"breaking news earthquake", metadata: new { notify = "slack" });
var matches = await gl.PercolateAsync("alerts",
"A 6.2 magnitude earthquake struck the coast.");
await gl.PercolateDeleteAsync("alerts", "breaking-news"); Analyze
// Analyze — show tokenization pipeline
var tokens = await gl.AnalyzeAsync("The quick brown foxes"); Explain Score
// Explain score — score breakdown for a specific document
var result = await gl.ExplainScoreAsync("articles", "body",
"machine learning", "id", 42); See the API reference for full parameter details on all search methods.