← Docs

Go

One import, one connection. Microsecond reads from L1 cache.

Install

go get github.com/goldlapel/goldlapel-go

// You also need a Postgres driver — either works:
go get github.com/jackc/pgx/v5   // pgx (recommended)
go get github.com/lib/pq          // or lib/pq

Gold Lapel is driver-agnostic — Go's database/sql abstraction works with both pgx (recommended) and lib/pq. Pick whichever you prefer and point it at gl.URL().

Quick Start

package main

import (
    "context"
    "database/sql"
    "log"

    _ "github.com/jackc/pgx/v5/stdlib"  // or _ "github.com/lib/pq"
    "github.com/goldlapel/goldlapel-go"
)

func main() {
    ctx := context.Background()

    // Spawn the proxy — returns a *GoldLapel instance
    gl, err := goldlapel.Start(ctx, "postgresql://user:pass@localhost:5432/mydb",
        goldlapel.WithProxyPort(7932),
        goldlapel.WithLogLevel("info"),
    )
    if err != nil {
        log.Fatal(err)
    }
    defer gl.Close()

    // Use gl.URL() with any database/sql driver for raw SQL
    db, err := sql.Open("pgx", gl.URL())
    if err != nil {
        log.Fatal(err)
    }
    rows, err := db.QueryContext(ctx, "SELECT * FROM users WHERE id = $1", 42)
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()

    // Or use Gold Lapel's wrapper methods directly — ctx first, no conn arg needed
    hits, _ := gl.Search(ctx, "articles", "body", "postgres tuning")
    _ = gl.DocInsert(ctx, "events", map[string]any{"type": "signup"})
    _ = hits
}

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 "" if not running / dashboard disabled)

Banner goes to stderr so it never pollutes stdout-piped application output. Pass goldlapel.WithSilent(true) to Start 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 *sql.Tx via gl.InTx(ctx, tx, fn) (scoped closure) or the WithTx option (per call):

// Scoped: every wrapper call inside the closure uses `tx`
tx, err := db.BeginTx(ctx, nil)
if err != nil {
    log.Fatal(err)
}

err = gl.InTx(ctx, tx, func(gl *goldlapel.GoldLapel) error {
    if err := gl.DocInsert(ctx, "events", map[string]any{"type": "order.created"}); err != nil {
        return err
    }
    return gl.Incr(ctx, "counters", "orders")
})
if err != nil {
    tx.Rollback()
    log.Fatal(err)
}
tx.Commit()

// Or per-call via WithTx
err = gl.DocInsert(ctx, "events", map[string]any{"type": "x"},
    goldlapel.WithTx(tx),
)

The closure form is the idiomatic Go shape — every wrapper call inside the closure binds to tx. For one-off overrides, pass goldlapel.WithTx(tx) as the final variadic option on any wrapper method.

API

goldlapel.Start(ctx context.Context, upstream string, opts ...Option) (*GoldLapel, error)

Factory that spawns the proxy and returns a *GoldLapel. Eagerly opens the wrapper's internal driver connection so wrapper methods are fast from the first call.

  • ctx — cancellation context for startup and internal connection
  • upstream — your Postgres connection string (e.g. postgresql://user:pass@localhost:5432/mydb)
  • opts — functional options: WithProxyPort, WithLogLevel, WithConfig, WithExtraArgs

gl.URL() string

Proxy connection string — pass to sql.Open("pgx", gl.URL()) (or "postgres" for lib/pq).

gl.DashboardURL() string

Dashboard URL (e.g. http://127.0.0.1:7933), or "" if not running or the dashboard is disabled.

gl.InTx(ctx, tx, func(gl *GoldLapel) error) error

Runs the closure with tx as the implicit transaction for every wrapper method invoked inside. Every wrapper method also accepts a trailing goldlapel.WithTx(tx) option for one-off overrides.

gl.Close() error

Stops the proxy and closes the internal connection. Idempotent. Wire it with defer gl.Close() for guaranteed cleanup.

gl.ProxyPort() int

Returns the port the proxy is listening on.

gl.Running() bool

Returns whether the proxy instance is currently running.

goldlapel.ConfigKeys() []string

Returns all valid config key names.

keys := goldlapel.ConfigKeys()
fmt.Println(keys)

Functional options

  • goldlapel.WithProxyPort(port int) — proxy port (default: 7932)
  • goldlapel.WithDashboardPort(port int) — dashboard port (default: proxy_port + 1; set to 0 to disable)
  • goldlapel.WithInvalidationPort(port int) — cache-invalidation port (default: proxy_port + 2)
  • goldlapel.WithLogLevel(level string)trace / debug / info / warn / error (translated to verbose flags internally)
  • goldlapel.WithMode(mode string) — operating mode (waiter, bellhop)
  • goldlapel.WithLicense(path string) — path to the signed license file
  • goldlapel.WithClient(name string) — client identifier for telemetry tagging (defaults to "go")
  • goldlapel.WithConfigFile(path string) — path to a TOML config file read by the Rust binary
  • goldlapel.WithConfig(config map[string]any) — structured tuning knobs only (snake_case keys → --pool-size style flags)
  • goldlapel.WithExtraArgs(args ...string) — raw CLI flags passed to the binary
  • goldlapel.WithSilent(silent bool) — suppress the stderr startup banner (default: off)
  • goldlapel.WithMesh(mesh bool) — opt into the mesh at startup (HQ enforces the license; denial is non-fatal)
  • goldlapel.WithMeshTag(tag string) — optional mesh tag; instances sharing a tag cluster together
  • goldlapel.WithTx(tx *sql.Tx) — per-call transaction override on any wrapper method

Promoted top-level options (WithProxyPort, WithDashboardPort, WithLogLevel, WithMode, etc.) must be passed as functional options — they are not valid keys inside WithConfig and will cause Start to return an error if present.

Multiple instances

Each Start call spawns its own proxy subprocess and returns a fresh *GoldLapel. Use different ports to run several side by side.

// Each Start call returns a fresh instance on its own port
gl1, _ := goldlapel.Start(ctx, "postgresql://user:pass@localhost:5432/app_db",
    goldlapel.WithProxyPort(7932),
)
gl2, _ := goldlapel.Start(ctx, "postgresql://user:pass@localhost:5432/analytics_db",
    goldlapel.WithProxyPort(7942),
)

// Each instance manages its own proxy and cache
defer gl1.Close()
defer gl2.Close()

Configuration

Pass a map[string]any via WithConfig. Keys use snake_case and map directly to CLI flags (pool_size--pool-size). The WithLogLevel option accepts string levels and translates to the binary's verbose flags internally.

gl, err := goldlapel.Start(ctx, "postgresql://user:pass@localhost/mydb",
    goldlapel.WithLogLevel("info"),     // top-level
    goldlapel.WithMode("waiter"),        // top-level (promoted out of config)
    goldlapel.WithMesh(true),            // top-level: opt into the mesh at startup
    goldlapel.WithMeshTag("prod-east"), // top-level: optional mesh tag
    goldlapel.WithConfig(map[string]any{ // structured tuning knobs only
        "pool_size":        50,
        "disable_matviews": true,
        "replica":          []string{"postgresql://user:pass@replica1/mydb"},
    }),
)

Unknown keys return an error immediately. The configuration reference has the complete list of valid keys.

Raw CLI flags

You can also pass raw CLI flags via WithExtraArgs:

gl, err := goldlapel.Start(ctx, "postgresql://user:pass@localhost:5432/mydb",
    goldlapel.WithExtraArgs("--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.

Driver-agnostic

Gold Lapel works with any Postgres driver that speaks database/sql. The two common choices:

  • pgx — recommended. import _ "github.com/jackc/pgx/v5/stdlib" then sql.Open("pgx", gl.URL()). Faster binary protocol; richer type support.
  • lib/pq — older, still widely used. import _ "github.com/lib/pq" then sql.Open("postgres", gl.URL()).

Concurrency is handled the idiomatic Go way — spin up goroutines, share the *GoldLapel. There's no separate async flavor.

Upgrading from v0.1

v0.2 is a breaking redesign. The two-step goldlapel.New(url) + gl.Start() pattern (which returned a *sql.DB) is replaced by a one-call goldlapel.Start(ctx, url, opts...) factory that returns a *GoldLapel. Bring your own driver and point it at gl.URL().

// v0.1 (old) — constructor + Start returned a *sql.DB
gl := goldlapel.New("postgresql://...")
db, err := gl.Start()
defer gl.Stop()
_, _ = db.Query("SELECT 1")

// v0.2 (new) — factory takes ctx + options, bring your own driver
import _ "github.com/jackc/pgx/v5/stdlib"

ctx := context.Background()
gl, err := goldlapel.Start(ctx, "postgresql://...",
    goldlapel.WithProxyPort(7932),
)
defer gl.Close()

db, _ := sql.Open("pgx", gl.URL())
_, _ = db.QueryContext(ctx, "SELECT 1")
  • goldlapel.Start(ctx, url, opts...) replaces the New + Start pair and now takes ctx as the first argument.
  • Wrapper methods take ctx context.Context as their first argument.
  • gl.Close() replaces gl.Stop() (pairs naturally with defer and io.Closer).
  • Bring your own *sql.DB against gl.URL() instead of using the one returned from Start.
  • New WithLogLevel option accepts string levels.
  • New WithTx option works on every wrapper method; gl.InTx provides a scoped closure form.
  • Multiple instances are first-class — each Start call 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 trailing goldlapel.WithTx(tx) option for transactional coordination.

Pub/Sub

// Pub/Sub — backed by PostgreSQL LISTEN/NOTIFY
_ = gl.Publish(ctx, "orders", "new order")
_ = gl.Subscribe(ctx, "orders", func(msg string) {
    fmt.Println(msg)
})

Queues

// Queues — backed by FOR UPDATE SKIP LOCKED
_ = gl.Enqueue(ctx, "jobs", map[string]any{"task": "send_email"})
job, _ := gl.Dequeue(ctx, "jobs")

Counters

// Counters — backed by INSERT ON CONFLICT
_ = gl.Incr(ctx, "page_views", "home")
count, _ := gl.GetCounter(ctx, "page_views", "home")

Sorted Sets

// Sorted Sets — backed by ORDER BY and window functions
_ = gl.Zadd(ctx, "leaderboard", "player1", 100)
top, _ := gl.Zrange(ctx, "leaderboard", 0, 9)

Streams

// Streams — append-only log with consumer groups
id, _ := gl.StreamAdd(ctx, "events", map[string]any{"type": "signup"})
_ = gl.StreamCreateGroup(ctx, "events", "workers")
msgs, _ := gl.StreamRead(ctx, "events", "workers", "worker-1")
_ = gl.StreamAck(ctx, "events", "workers", msgs[0].ID)
_ = id

See the Redis Replacement docs for the complete API — including geospatial, rate limiting, sessions, and scripting.

Document Store

DocFind, DocInsert, DocUpdate, DocDelete and friends operate on JSONB-backed collections. Tables are auto-created on first use.

Filter operators

DocFind 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
orders, err := gl.DocFind(ctx, "orders", map[string]any{
    "items": map[string]any{
        "$elemMatch": map[string]any{"sku": "ABC-123", "qty": map[string]any{"$gte": 2}},
    },
})
// $text — full-text search, document-wide or field-scoped
hits, err := gl.DocFind(ctx, "articles", map[string]any{
    "$text": map[string]any{"$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
results, err := gl.Facets(ctx, "articles", "category", nil)
filtered, err := gl.Facets(ctx, "articles", "category", &goldlapel.FacetsOpts{
    Query: "machine learning", QueryColumn: "body",
})

Aggregations

// Aggregations — count, sum, avg, min, max with optional grouping
byRegion, err := gl.Aggregate(ctx, "orders", "total", "avg", &goldlapel.AggregateOpts{
    GroupBy: "region",
})

Custom Search Config

// Custom search config
err := gl.CreateSearchConfig(ctx, "my_english", &goldlapel.SearchConfigOpts{
    CopyFrom: "english",
})

Percolator

Store queries, then match documents against them. Like Elasticsearch percolate.

// Percolator — store queries, match documents against them
err := gl.PercolateAdd(ctx, "alerts", "breaking-news",
    "breaking news earthquake", &goldlapel.PercolateAddOpts{
        Metadata: map[string]any{"notify": "slack"},
    })

matches, err := gl.Percolate(ctx, "alerts",
    "A 6.2 magnitude earthquake struck the coast.", nil)

deleted, err := gl.PercolateDelete(ctx, "alerts", "breaking-news")

Analyze

// Analyze — show tokenization pipeline
tokens, err := gl.Analyze(ctx, "The quick brown foxes", nil)

Explain Score

// Explain score — score breakdown for a specific document
result, err := gl.ExplainScore(ctx, "articles", "body",
    "machine learning", "id", 42, nil)

See the API reference for full parameter details on all search methods.