<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>nurago</title><link>https://nurago.org/</link><description>Recent content on nurago</description><generator>Hugo</generator><language>en-gb</language><lastBuildDate>Sun, 20 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://nurago.org/index.xml" rel="self" type="application/rss+xml"/><item><title>Packages</title><link>https://nurago.org/packages/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/</guid><description>&lt;p&gt;Every package is imported on its own and pulls only the dependencies it reaches, so adopting one does not commit you to the rest. 40 of the 70 reach no external module at all: see &lt;a href="https://nurago.org/docs/dependency-footprint/"&gt;/docs/dependency-footprint/&lt;/a&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;go get github.com/tecnickcom/nurago&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Each page below is the package documentation with a runnable example and the guidance on when the package is the right choice. The generated &lt;abbr title="Application Programming Interface"&gt;API&lt;/abbr&gt; reference for every package is on &lt;a href="https://pkg.go.dev/github.com/tecnickcom/nurago"&gt;pkg.go.dev&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Documentation</title><link>https://nurago.org/docs/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/docs/</guid><description>&lt;p&gt;One guide per topic, each linking to the package pages it covers.&lt;/p&gt;&#10;&lt;h2 id="where-to-start"&gt;Where to start&lt;/h2&gt;&#10;&lt;p&gt;Three entry points, depending on what you are doing.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Adopting one package into an existing service.&lt;/strong&gt; Go straight to its page under &lt;a href="https://nurago.org/packages/"&gt;/packages/&lt;/a&gt;. Every package is importable on its own and pulls only the dependencies it reaches, so nothing else here has to be adopted with it.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Building a new service.&lt;/strong&gt; &lt;a href="https://nurago.org/docs/getting-started/"&gt;/docs/getting-started/&lt;/a&gt; wires the lifecycle end to end, and &lt;a href="https://nurago.org/docs/service-scaffolding/"&gt;/docs/service-scaffolding/&lt;/a&gt; generates a complete working project if you would rather start from one.&lt;/p&gt;</description></item><item><title>Search</title><link>https://nurago.org/search/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://nurago.org/search/</guid><description/></item><item><title>Articles</title><link>https://nurago.org/articles/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/articles/</guid><description>&lt;p&gt;One package at a time, at the depth the package page does not have room for: the failure that prompted the design, the trade-off taken, and the boundary of what the code promises.&lt;/p&gt;&#10;&lt;p&gt;These were first published on &lt;a href="https://tecnick.com/"&gt;tecnick.com&lt;/a&gt; and moved here in September 2026, so that the writing sits alongside the packages it describes. The old URLs redirect.&lt;/p&gt;&#10;&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://nurago.org/articles/nurago/"&gt;Stop Rewriting Go Service Boilerplate&lt;/a&gt; : why the library exists and what a service lifecycle built on it looks like.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="reliability-and-networking"&gt;Reliability and Networking&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://nurago.org/articles/nurago-backoff/"&gt;Exponential Backoff with Jitter in Go, Without the Overflow Footgun&lt;/a&gt; : the AWS jitter strategies, and the clamps that keep exponential growth from wrapping into a negative duration. Package: &lt;a href="https://nurago.org/packages/backoff/"&gt;backoff&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://nurago.org/articles/nurago-httpretrier/"&gt;Between Two Attempts: What an HTTP Retry Loop Must Decide&lt;/a&gt; : judging the outcome, closing the body, replaying the request, honouring &lt;code&gt;Retry-After&lt;/code&gt;, and knowing when to stop. Package: &lt;a href="https://nurago.org/packages/httpretrier/"&gt;httpretrier&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://nurago.org/articles/nurago-httpclient/"&gt;Observing Outbound HTTP in Go Without Disturbing It&lt;/a&gt; : a cloned request, a timeout timer tied to the response body, three-source trace IDs, redacted logs, and bounded debug dumps. Package: &lt;a href="https://nurago.org/packages/httpclient/"&gt;httpclient&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://nurago.org/articles/nurago-httpserver/"&gt;Before the First Request and After the Last: a Go HTTP Server&amp;rsquo;s Edges&lt;/a&gt; : startup validation, a listener bound before serving, request contexts that survive shutdown, and the timeout lattice. Package: &lt;a href="https://nurago.org/packages/httpserver/"&gt;httpserver&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://nurago.org/articles/nurago-httpreverseproxy/"&gt;A Reverse Proxy Is Defined by What It Refuses to Do&lt;/a&gt; : no followed redirects, no whole-request timeout, no base-path escapes, no 502 for a client that went away. Package: &lt;a href="https://nurago.org/packages/httpreverseproxy/"&gt;httpreverseproxy&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://nurago.org/articles/nurago-dnscache/"&gt;A DNS-Caching Dialer for http.Transport in Go&lt;/a&gt; : &lt;abbr title="American Standard Code for Information Interchange"&gt;ASCII&lt;/abbr&gt;-only host folding, single-flight caching, canonical dedup, family interleaving, rotation, and the attempt loop. Package: &lt;a href="https://nurago.org/packages/dnscache/"&gt;dnscache&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://nurago.org/articles/nurago-sfcache/"&gt;From singleflight to a Production Cache: a Gap Analysis&lt;/a&gt; : &lt;abbr title="Time To Live"&gt;TTL&lt;/abbr&gt; caching, context provenance, failure-anchored stale windows, and bounded eviction. Package: &lt;a href="https://nurago.org/packages/sfcache/"&gt;sfcache&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://nurago.org/articles/nurago-mysqllock/"&gt;A Distributed Lock in Go and MySQL, and Every Way the Session Can Betray You&lt;/a&gt; : a pinned connection, a bounded keep-alive, deadlock-free release, and what the lock cannot promise. Package: &lt;a href="https://nurago.org/packages/mysqllock/"&gt;mysqllock&lt;/a&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="security"&gt;Security&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://nurago.org/articles/nurago-passwordhash/"&gt;The Life of a Password: Argon2id in Production with nurago&lt;/a&gt; : one password followed through minting, storage, verification, and parameter migration. Package: &lt;a href="https://nurago.org/packages/passwordhash/"&gt;passwordhash&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://nurago.org/articles/nurago-passwordpwned/"&gt;Checking Passwords Against Have I Been Pwned Without Leaking Them&lt;/a&gt; : what actually leaves the machine, what the padding hides, and why an unverifiable response must be an error. Package: &lt;a href="https://nurago.org/packages/passwordpwned/"&gt;passwordpwned&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://nurago.org/articles/nurago-jwt/"&gt;The Classic JWT Attacks, Addressed by Construction in Go&lt;/a&gt; : &lt;code&gt;alg=none&lt;/code&gt;, &lt;abbr title="Rivest-Shamir-Adleman"&gt;RSA&lt;/abbr&gt;-to-HMAC confusion, unverified &lt;abbr title="JavaScript Object Notation"&gt;JSON&lt;/abbr&gt;, weak keys, oversized tokens, immortal sessions, and the structural property that answers each. Package: &lt;a href="https://nurago.org/packages/jwt/"&gt;jwt&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://nurago.org/articles/nurago-redact/"&gt;Redacting Secrets from Go Logs on a Performance Budget&lt;/a&gt; : a 256-entry byte-class table for speed, convergent and boundary-exact matching for safety. Package: &lt;a href="https://nurago.org/packages/redact/"&gt;redact&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://nurago.org/articles/nurago-filter/"&gt;Filtering Untrusted Client Queries in Go: Threat Model First&lt;/a&gt; : bounded input, RE2 linear-time matching, exact int64 comparison past 2^53, and boxing-free in-place evaluation. Package: &lt;a href="https://nurago.org/packages/filter/"&gt;filter&lt;/a&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="logging-and-observability"&gt;Logging and Observability&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://nurago.org/articles/nurago-logsrv/"&gt;A Production slog.Handler on Top of zerolog&lt;/a&gt; : extended syslog severities, open groups versus baked attributes, root trace IDs, pooled events, and the hazards of process-global state. Package: &lt;a href="https://nurago.org/packages/logsrv/"&gt;logsrv&lt;/a&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="data-structures-and-encoding"&gt;Data Structures and Encoding&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://nurago.org/articles/nurago-numtrie/"&gt;A Longest-Prefix Trie for Numeric Keys, in Six Design Questions&lt;/a&gt; : the fixed 10-slot child array, the packed match status, vanity letter folding, value ownership, and what O(k) covers. Package: &lt;a href="https://nurago.org/packages/numtrie/"&gt;numtrie&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://nurago.org/articles/nurago-countrycode/"&gt;Packing an Entire Country Record into a Single uint64&lt;/a&gt; : a full &lt;abbr title="International Organization for Standardization"&gt;ISO&lt;/abbr&gt; 3166 record as a Reversible Numeric Composite Key, decoded with bit shifts. Package: &lt;a href="https://nurago.org/packages/countrycode/"&gt;countrycode&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://nurago.org/articles/nurago-random/"&gt;Random IDs in Go, Trap by Trap: UUIDv7 and Unbiased Random Strings&lt;/a&gt; : entropy budgeting in UUIDv7, rejection sampling, reader contracts, and allocation-free formatting. Package: &lt;a href="https://nurago.org/packages/random/"&gt;random&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://nurago.org/articles/nurago-uhex/"&gt;Deriving a Fast Fixed-Width Hex Encoder in Go&lt;/a&gt; : from what &lt;code&gt;encoding/hex&lt;/code&gt; does per byte to a branchless encoder on a 256-entry table. Package: &lt;a href="https://nurago.org/packages/uhex/"&gt;uhex&lt;/a&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="longer-form-elsewhere"&gt;Longer form, elsewhere&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://tecnick.com/guides/go-rest-api/"&gt;Building a Production REST API in Go&lt;/a&gt; : twenty parts building one service end to end, from &lt;code&gt;net/http&lt;/code&gt; and the startup path through configuration, shutdown, middleware, the request lifecycle, connection pools, one complete feature, testing and deployment. It stays on tecnick.com, because it is a Go guide that happens to use nurago rather than a nurago guide.&lt;/li&gt;&#10;&lt;/ul&gt;</description></item><item><title>Features</title><link>https://nurago.org/features/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/features/</guid><description>&lt;p&gt;What the library provides, grouped by area, with a link to the package that covers each item. For how this compares with the alternatives, see &lt;a href="https://nurago.org/comparison/"&gt;/comparison/&lt;/a&gt;. For the packages themselves, grouped and described, see &lt;a href="https://nurago.org/packages/"&gt;/packages/&lt;/a&gt;.&lt;/p&gt;&#10;&#10;&lt;div class="toc"&gt;&#10;&#9;&lt;h2&gt;Table of contents&lt;/h2&gt;&#10;&#9;&lt;nav id="TableOfContents"&gt;&#10; &lt;ul&gt;&#10; &lt;li&gt;&lt;a href="#service-lifecycle"&gt;Service Lifecycle&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#http-server"&gt;HTTP Server&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#outbound-http"&gt;Outbound HTTP&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#resilience"&gt;Resilience&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#observability"&gt;Observability&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#secret-redaction"&gt;Secret Redaction&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#security"&gt;Security&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#sql"&gt;SQL&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#messaging-and-storage"&gt;Messaging and Storage&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#enumerations-and-reference-data"&gt;Enumerations and Reference Data&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#data-handling"&gt;Data Handling&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#collections-and-generics"&gt;Collections and Generics&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#errors-types-and-validation"&gt;Errors, Types and Validation&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#service-clients"&gt;Service Clients&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#testing"&gt;Testing&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#project-properties"&gt;Project Properties&lt;/a&gt;&lt;/li&gt;&#10; &lt;/ul&gt;&#10;&lt;/nav&gt;&#10;&lt;/div&gt;&#10;&#10;&#10;&lt;h2 id="service-lifecycle"&gt;Service Lifecycle&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Context, structured logger, metrics client, &lt;abbr title="Operating System"&gt;OS&lt;/abbr&gt; signal handling, and bounded graceful shutdown in a single call. See &lt;a href="https://nurago.org/packages/bootstrap/"&gt;/packages/bootstrap/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;A shared shutdown channel and wait group that every component registers against, so one signal drains the whole service and the process waits for the slowest, up to a budget.&lt;/li&gt;&#10;&lt;li&gt;Shutdown that exceeds its budget returns an error wrapping &lt;code&gt;ErrShutdownTimeout&lt;/code&gt; rather than hanging.&lt;/li&gt;&#10;&lt;li&gt;Configuration built from defaults, a searched local file, environment overrides, and an optional remote source, validated before the service starts. See &lt;a href="https://nurago.org/packages/config/"&gt;/packages/config/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;A file-less configuration path for containers: base64 &lt;abbr title="JavaScript Object Notation"&gt;JSON&lt;/abbr&gt; supplied through an environment variable.&lt;/li&gt;&#10;&lt;li&gt;Project generation: a complete service, with packaging, containers, OpenAPI, and integration tests, from one configuration file. See &lt;a href="https://nurago.org/docs/service-scaffolding/"&gt;/docs/service-scaffolding/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;One worked feature in the generated service: a domain package, a database-backed repository, and public create, read, list, and delete endpoints registered only when a database is configured.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="http-server"&gt;HTTP Server&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Option-driven server assembly with pluggable route binding, over httprouter. See &lt;a href="https://nurago.org/packages/httpserver/"&gt;/packages/httpserver/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Selectable built-in operational routes: &lt;code&gt;/ping&lt;/code&gt;, &lt;code&gt;/status&lt;/code&gt;, &lt;code&gt;/metrics&lt;/code&gt;, &lt;code&gt;/ip&lt;/code&gt;, &lt;code&gt;/pprof/*option&lt;/code&gt;, and a generated route index.&lt;/li&gt;&#10;&lt;li&gt;Startup validation: nil handlers or middleware, duplicate or malformed routes, and unknown default route identifiers are reported by the constructor as wrapped sentinel errors, not panics.&lt;/li&gt;&#10;&lt;li&gt;Non-blocking start, ephemeral port discovery, context-aware shutdown with a configurable timeout, and abnormal-termination reporting.&lt;/li&gt;&#10;&lt;li&gt;Global and per-route middleware, with per-route timeout override or opt-out.&lt;/li&gt;&#10;&lt;li&gt;Default not-found, method-not-allowed, and panic handlers with structured logging, and &lt;code&gt;net/http&lt;/code&gt; internal diagnostics routed to the structured logger.&lt;/li&gt;&#10;&lt;li&gt;Plain &lt;abbr title="Transmission Control Protocol"&gt;TCP&lt;/abbr&gt; or &lt;abbr title="Transport Layer Security"&gt;TLS&lt;/abbr&gt; (HTTP/1.1 and HTTP/2 via &lt;abbr title="Application-Layer Protocol Negotiation"&gt;ALPN&lt;/abbr&gt;) from certificate material or a supplied &lt;code&gt;tls.Config&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;Request and response primitives: header decorators, path, query, and header extraction with defaults, no-cache and nosniff responses, full-buffer encoding with a 500 fallback on marshal failure, and a response-writer wrapper that captures status and size. See &lt;a href="https://nurago.org/packages/httputil/"&gt;/packages/httputil/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;abbr title="Request for Comments"&gt;RFC&lt;/abbr&gt; 9457 problem details for error responses, with not-found, method-not-allowed, and panic router fallbacks in the same format, a send helper for any other JSON-based media type, and reason phrases that fall back to the status class name for non-standard codes. See &lt;a href="https://nurago.org/packages/httputil/"&gt;/packages/httputil/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;An extended JSend response envelope carrying program name, version, release, &lt;abbr title="Request for Comments"&gt;RFC&lt;/abbr&gt; 3339 datetime, and Unix-nanosecond timestamp, with default handlers for the router fallbacks. See &lt;a href="https://nurago.org/packages/jsendx/"&gt;/packages/jsendx/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Concurrent dependency probes aggregated into one health endpoint: 200 when all pass, 503 when any fails, per-check results always in the payload, panics recovered and reported as failures. See &lt;a href="https://nurago.org/packages/healthcheck/"&gt;/packages/healthcheck/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;pprof served through a single httprouter wildcard route, without touching &lt;code&gt;http.DefaultServeMux&lt;/code&gt;. See &lt;a href="https://nurago.org/packages/profiling/"&gt;/packages/profiling/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Reverse proxying with automatic &lt;code&gt;X-Forwarded-*&lt;/code&gt; headers, no followed redirects, a base-path boundary that rejects traversal before contacting the upstream, and 499 rather than 502 when the client disconnected first. See &lt;a href="https://nurago.org/packages/httpreverseproxy/"&gt;/packages/httpreverseproxy/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="outbound-http"&gt;Outbound HTTP&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;A configurable client with automatic trace-ID propagation, generating a UUIDv7-based ID when the context carries none. See &lt;a href="https://nurago.org/packages/httpclient/"&gt;/packages/httpclient/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Structured request and response logging: summary metadata at normal levels, redacted dumps at debug level, bounded by a configurable maximum dump size.&lt;/li&gt;&#10;&lt;li&gt;Query strings redacted before logging at every level, including the &lt;abbr title="Uniform Resource Locator"&gt;URL&lt;/abbr&gt; embedded in a failed request&amp;rsquo;s error.&lt;/li&gt;&#10;&lt;li&gt;A private transport and connection pool per client, with &lt;code&gt;MaxIdleConnsPerHost&lt;/code&gt; raised above the &lt;code&gt;net/http&lt;/code&gt; default, plus hooks for a custom TLS config or a fully tuned transport.&lt;/li&gt;&#10;&lt;li&gt;Per-request retry orchestration wrapping an already configured client. See &lt;a href="https://nurago.org/packages/httpretrier/"&gt;/packages/httpretrier/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Retry policies for idempotent reads and for writes, selectable by &lt;abbr title="HyperText Transfer Protocol"&gt;HTTP&lt;/abbr&gt; method, with the decision function receiving both the response and the error.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;Retry-After&lt;/code&gt; honoured when enabled, jittered through the same helper as every other delay, and still bounded by your cap.&lt;/li&gt;&#10;&lt;li&gt;Request body replay through &lt;code&gt;Request.GetBody&lt;/code&gt;, with a clear error when the body cannot be recreated.&lt;/li&gt;&#10;&lt;li&gt;A caching &lt;abbr title="Domain Name System"&gt;DNS&lt;/abbr&gt; dialer for &lt;code&gt;http.Transport&lt;/code&gt;, with single-flight collapsing, a bounded cache, and case-insensitive host matching. See &lt;a href="https://nurago.org/packages/dnscache/"&gt;/packages/dnscache/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="resilience"&gt;Resilience&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Exponential backoff as a pure per-call calculator: no timers, no goroutines, no I/O. See &lt;a href="https://nurago.org/packages/backoff/"&gt;/packages/backoff/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Three jitter strategies from the AWS taxonomy: additive, full, and equal.&lt;/li&gt;&#10;&lt;li&gt;Both the exponential state and the jitter addition clamped, so no delay can overflow into a negative duration at any attempt count or configured maximum.&lt;/li&gt;&#10;&lt;li&gt;A retry engine with a retry predicate, a per-attempt timeout separate from the overall deadline, and parent-context cancellation always respected. See &lt;a href="https://nurago.org/packages/retrier/"&gt;/packages/retrier/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Fixed-interval scheduling with jitter, a per-invocation deadline, an eager first run, and optional initial jitter to spread a fleet&amp;rsquo;s first call. See &lt;a href="https://nurago.org/packages/periodic/"&gt;/packages/periodic/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Bounded single-flight caching for expensive lookups, with &lt;abbr title="Time To Live"&gt;TTL&lt;/abbr&gt; and failure-anchored stale windows. See &lt;a href="https://nurago.org/packages/sfcache/"&gt;/packages/sfcache/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="observability"&gt;Observability&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;log/slog&lt;/code&gt; handlers built from typed configuration, with JSON, text console, and discard output. See &lt;a href="https://nurago.org/packages/logutil/"&gt;/packages/logutil/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Syslog-range severities (emergency through debug) extended with a trace level.&lt;/li&gt;&#10;&lt;li&gt;A hook that intercepts every emitted record, used to count log lines by level as a metric.&lt;/li&gt;&#10;&lt;li&gt;Bridges in both directions between the standard &lt;code&gt;log&lt;/code&gt; package and &lt;code&gt;slog&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;A zerolog backend behind the same &lt;code&gt;log/slog&lt;/code&gt; &lt;abbr title="Application Programming Interface"&gt;API&lt;/abbr&gt;, with the divergences from the standard-library backend documented value shape by value shape. See &lt;a href="https://nurago.org/packages/logsrv/"&gt;/packages/logsrv/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;A backend-agnostic metrics contract covering &lt;abbr title="Structured Query Language"&gt;SQL&lt;/abbr&gt;, inbound and outbound HTTP, the metrics endpoint, and application counters, with a no-op default implementation. See &lt;a href="https://nurago.org/packages/metrics/"&gt;/packages/metrics/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;OpenTelemetry backend with &lt;code&gt;otelhttp&lt;/code&gt; and &lt;code&gt;otelsql&lt;/code&gt; instrumentation, environment-driven &lt;abbr title="OpenTelemetry Protocol"&gt;OTLP&lt;/abbr&gt; exporter selection, and &lt;abbr title="World Wide Web Consortium"&gt;W3C&lt;/abbr&gt; context propagation. See &lt;a href="https://nurago.org/packages/opentel/"&gt;/packages/opentel/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Prometheus backend with Go runtime and process collectors, HTTP server and client metrics, SQL pool stats, and error counters. See &lt;a href="https://nurago.org/packages/prometheus/"&gt;/packages/prometheus/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;StatsD backend pushing counters, gauges, and timers over &lt;abbr title="User Datagram Protocol"&gt;UDP&lt;/abbr&gt; or TCP. See &lt;a href="https://nurago.org/packages/statsd/"&gt;/packages/statsd/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Request-scoped trace IDs read from an inbound header, validated against injection, carried in the context, and written back onto outbound requests. See &lt;a href="https://nurago.org/packages/traceid/"&gt;/packages/traceid/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="secret-redaction"&gt;Secret Redaction&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Every rule matched in a single pass, so cost does not grow with the number of rules. See &lt;a href="https://nurago.org/packages/redact/"&gt;/packages/redact/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Sensitive HTTP headers, JSON keys (replacing a whole nested object or array), URL-encoded pairs, &lt;abbr title="Extensible Markup Language"&gt;XML&lt;/abbr&gt; elements, and URL userinfo passwords in bare DSNs.&lt;/li&gt;&#10;&lt;li&gt;&lt;abbr title="JSON Web Token"&gt;JWT&lt;/abbr&gt; and &lt;abbr title="JSON Web Encryption"&gt;JWE&lt;/abbr&gt; compact tokens in free text, query strings, and JSON values.&lt;/li&gt;&#10;&lt;li&gt;Vendor credential literals by prefix: GitHub, Slack, Stripe, OpenAI and Anthropic, Hugging Face, SendGrid, AWS, Google, GitLab, DigitalOcean, Docker Hub, and Shopify.&lt;/li&gt;&#10;&lt;li&gt;&lt;abbr title="Privacy-Enhanced Mail"&gt;PEM&lt;/abbr&gt; private key blocks, with public blocks such as certificates left visible.&lt;/li&gt;&#10;&lt;li&gt;Credit-card numbers by network prefix and length, contiguous or space- and dash-grouped, with an optional Luhn gate for fewer false positives.&lt;/li&gt;&#10;&lt;li&gt;Token-exact key matching after camelCase, snake_case, kebab-case, and acronym normalisation, so &lt;code&gt;monkey&lt;/code&gt; never matches &lt;code&gt;key&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;Convergent output: re-redacting never reveals more and reaches a fixed point, so layered redaction does not keep mangling logs.&lt;/li&gt;&#10;&lt;li&gt;Applied by default in the HTTP client, server, and reverse proxy, and overridable per component; disabling it requires naming &lt;code&gt;InsecureNoRedaction&lt;/code&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="security"&gt;Security&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Argon2id password hashing (RFC 9106) with &lt;abbr title="Open Worldwide Application Security Project"&gt;OWASP&lt;/abbr&gt;-recommended defaults. See &lt;a href="https://nurago.org/packages/passwordhash/"&gt;/packages/passwordhash/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Self-describing stored hashes that carry their own parameters, so cost can be raised without invalidating existing hashes, with rehash detection after a successful verification.&lt;/li&gt;&#10;&lt;li&gt;An optional &lt;abbr title="Advanced Encryption Standard"&gt;AES&lt;/abbr&gt;-GCM pepper layer for a secret held outside the database.&lt;/li&gt;&#10;&lt;li&gt;Have I Been Pwned checks under k-anonymity: only a 5-character hash prefix leaves the process, with response padding, a size cap against decompression bombs, and malformed responses rejected rather than read as &amp;ldquo;not pwned&amp;rdquo;. See &lt;a href="https://nurago.org/packages/passwordpwned/"&gt;/packages/passwordpwned/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;abbr title="Hash-based Message Authentication Code"&gt;HMAC&lt;/abbr&gt;-only JWT (HS256, HS384, HS512) on the standard library, with the signature verified before the payload is decoded, making &lt;code&gt;alg=none&lt;/code&gt; and asymmetric-to-HMAC confusion structurally impossible. See &lt;a href="https://nurago.org/packages/jwt/"&gt;/packages/jwt/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Login, renew, and authorization handlers, middleware that injects verified claims into the request context, and token issue and verify for non-HTTP transports.&lt;/li&gt;&#10;&lt;li&gt;AES-GCM authenticated encryption with a random nonce, raw and serialized (gob or JSON) helpers, and the key-rotation bound stated explicitly. See &lt;a href="https://nurago.org/packages/encrypt/"&gt;/packages/encrypt/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="sql"&gt;SQL&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Connection lifecycle for long-running services: pool limits, connectivity verification, health check, and shutdown on a signal, a cancelled lifetime context, or a direct call. See &lt;a href="https://nurago.org/packages/sqlconn/"&gt;/packages/sqlconn/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;A &lt;code&gt;&amp;lt;DRIVER&amp;gt;://&amp;lt;DSN&amp;gt;&lt;/code&gt; connection string form, or explicit driver and DSN.&lt;/li&gt;&#10;&lt;li&gt;Transactions with begin, commit, and guarded rollback on error or panic, with rollback failures joined onto the original error. See &lt;a href="https://nurago.org/packages/sqltransaction/"&gt;/packages/sqltransaction/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;The same helper for sqlx, when struct scanning inside the transaction is wanted. See &lt;a href="https://nurago.org/packages/sqlxtransaction/"&gt;/packages/sqlxtransaction/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Identifier and string-literal quoting for dynamically generated fragments, MySQL-like by default and replaceable per dialect. See &lt;a href="https://nurago.org/packages/sqlutil/"&gt;/packages/sqlutil/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Process-distributed mutual exclusion on MySQL &lt;code&gt;GET_LOCK&lt;/code&gt; and &lt;code&gt;RELEASE_LOCK&lt;/code&gt;, with a keep-alive and a deadlock-free release. See &lt;a href="https://nurago.org/packages/mysqllock/"&gt;/packages/mysqllock/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="messaging-and-storage"&gt;Messaging and Storage&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Redis: key/value with expiration, typed values through pluggable codecs, Pub/Sub in raw and typed form, and a PING health check. See &lt;a href="https://nurago.org/packages/redis/"&gt;/packages/redis/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Valkey: the same surface over valkey-go. See &lt;a href="https://nurago.org/packages/valkey/"&gt;/packages/valkey/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Kafka: pure Go, no CGO and no system librdkafka, with at-most-once and at-least-once consumption as an explicit choice and full in-sync-replica acknowledgment by default. See &lt;a href="https://nurago.org/packages/kafka/"&gt;/packages/kafka/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;abbr title="Simple Queue Service"&gt;SQS&lt;/abbr&gt;: send, receive, decode, acknowledge, and health-check, with &lt;abbr title="First In, First Out"&gt;FIFO&lt;/abbr&gt; rules enforced at construction and the region derived from the queue URL. See &lt;a href="https://nurago.org/packages/sqs/"&gt;/packages/sqs/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;S3: upload from a reader, download as a stream, list keys or objects with metadata by prefix, delete, and a health check that verifies access as well as reachability. See &lt;a href="https://nurago.org/packages/s3/"&gt;/packages/s3/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Shared AWS &lt;abbr title="Software Development Kit"&gt;SDK&lt;/abbr&gt; v2 configuration composed once and handed to every AWS client. See &lt;a href="https://nurago.org/packages/awsopt/"&gt;/packages/awsopt/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;A bounded, single-flight cache for AWS Secrets Manager lookups. See &lt;a href="https://nurago.org/packages/awssecretcache/"&gt;/packages/awssecretcache/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Serialization for values crossing system boundaries, backing the default codecs of the clients above. See &lt;a href="https://nurago.org/packages/encode/"&gt;/packages/encode/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Incoming-webhook messaging to Slack. See &lt;a href="https://nurago.org/packages/slack/"&gt;/packages/slack/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="enumerations-and-reference-data"&gt;Enumerations and Reference Data&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Thread-safe enumeration name and ID caches, and loading enumeration sets from database tables. See &lt;a href="https://nurago.org/packages/enumcache/"&gt;/packages/enumcache/&lt;/a&gt; and &lt;a href="https://nurago.org/packages/enumdb/"&gt;/packages/enumdb/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Encoding a set of enumeration values as an integer bitmap, and decoding it back. See &lt;a href="https://nurago.org/packages/enumbitmap/"&gt;/packages/enumbitmap/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;ISO-3166 country metadata: alpha-2, alpha-3 and numeric codes, English and French names, region, sub-region and intermediate region, assignment status, and &lt;abbr title="Top Level Domain"&gt;TLD&lt;/abbr&gt;, with selection by geographic hierarchy, status, or domain. See &lt;a href="https://nurago.org/packages/countrycode/"&gt;/packages/countrycode/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;abbr title="International Telecommunication Union"&gt;ITU&lt;/abbr&gt; country calling code resolution by longest-prefix lookup over a trie, with an embedded default dataset and support for custom plans. See &lt;a href="https://nurago.org/packages/countryphone/"&gt;/packages/countryphone/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Alphabetic to numeric conversion on the ITU E.161 / T.9 telephone keypad, for vanity numbers. See &lt;a href="https://nurago.org/packages/phonekeypad/"&gt;/packages/phonekeypad/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="data-handling"&gt;Data Handling&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Fixed-point decimals: values with up to six decimal places carried as scaled integers, so comparison, serialization, and transport stay deterministic. See &lt;a href="https://nurago.org/packages/decint/"&gt;/packages/decint/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;JSON-friendly time types: a &lt;code&gt;DateTime&lt;/code&gt; whose layout is a compile-time-checked type parameter (covering every standard library layout) and a &lt;code&gt;Duration&lt;/code&gt; that marshals as &amp;ldquo;1h30m&amp;rdquo; rather than a nanosecond integer. See &lt;a href="https://nurago.org/packages/timeutil/"&gt;/packages/timeutil/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Fixed-width lowercase hexadecimal encoders for unsigned integers and byte arrays, branchless and allocation-free. See &lt;a href="https://nurago.org/packages/uhex/"&gt;/packages/uhex/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Stable compact keys derived from several text fields, normalised (whitespace, case, Unicode &lt;abbr title="Normalization Form C"&gt;NFC&lt;/abbr&gt;) and hashed, available as uint64, base-36, or fixed 16-character hex. See &lt;a href="https://nurago.org/packages/stringkey/"&gt;/packages/stringkey/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Chunking text to a byte limit without breaking runes, preferring newline, whitespace, and punctuation boundaries. See &lt;a href="https://nurago.org/packages/strsplit/"&gt;/packages/strsplit/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Rune-based Damerau-Levenshtein edit distance for fuzzy matching. See &lt;a href="https://nurago.org/packages/stringmetric/"&gt;/packages/stringmetric/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Random bytes, numeric identifiers, UUIDv7, hex and base-36 identifiers, and configurable random strings, from &lt;code&gt;crypto/rand&lt;/code&gt; by default, with unbiased sampling. See &lt;a href="https://nurago.org/packages/random/"&gt;/packages/random/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="collections-and-generics"&gt;Collections and Generics&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Generic &lt;code&gt;Filter&lt;/code&gt;, &lt;code&gt;Map&lt;/code&gt;, and &lt;code&gt;Reduce&lt;/code&gt; over slices, with index-aware callbacks. See &lt;a href="https://nurago.org/packages/sliceutil/"&gt;/packages/sliceutil/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Descriptive statistics for any numeric slice: count, sum, min and max with indexes, range, mode, mean, median, entropy, variance, standard deviation, skewness, and excess kurtosis.&lt;/li&gt;&#10;&lt;li&gt;Generic &lt;code&gt;Filter&lt;/code&gt;, &lt;code&gt;Map&lt;/code&gt;, &lt;code&gt;Reduce&lt;/code&gt;, and &lt;code&gt;Invert&lt;/code&gt; over maps, with the iteration-order caveats stated. See &lt;a href="https://nurago.org/packages/maputil/"&gt;/packages/maputil/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;A generic digit-indexed trie with O(k) longest-prefix lookup, partial matching, and vanity phone-number keys. See &lt;a href="https://nurago.org/packages/numtrie/"&gt;/packages/numtrie/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Pagination metadata and SQL &lt;code&gt;OFFSET&lt;/code&gt;/&lt;code&gt;LIMIT&lt;/code&gt; values from page number, page size, and total count, with out-of-range inputs clamped. See &lt;a href="https://nurago.org/packages/paging/"&gt;/packages/paging/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Declarative rule-based filtering of in-memory slices, with AND-of-OR rule groups, in-place filtering, and rules parseable from JSON or a URL query. See &lt;a href="https://nurago.org/packages/filter/"&gt;/packages/filter/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Lock interfaces for building goroutine-safe structures without hard-coding a lock type, plus map and slice helpers that take a caller-supplied lock at every access. See &lt;a href="https://nurago.org/packages/threadsafe/"&gt;/packages/threadsafe/&lt;/a&gt;, &lt;a href="https://nurago.org/packages/tsmap/"&gt;/packages/tsmap/&lt;/a&gt;, and &lt;a href="https://nurago.org/packages/tsslice/"&gt;/packages/tsslice/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="errors-types-and-validation"&gt;Errors, Types and Validation&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Errors annotated with caller location, cleanup failures joined onto an existing error, and enumeration of the errors inside an &lt;code&gt;errors.Join&lt;/code&gt;. See &lt;a href="https://nurago.org/packages/errutil/"&gt;/packages/errutil/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Nil detection through interfaces (including a nil pointer in a non-nil interface), generic zero values, safe pointer dereference, and branchless bool-to-int. See &lt;a href="https://nurago.org/packages/typeutil/"&gt;/packages/typeutil/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Struct validation over go-playground/validator with custom rules, tag aliases, a template-based message translation engine, and typed errors carrying tag, parameter, namespace, field, and message. See &lt;a href="https://nurago.org/packages/validator/"&gt;/packages/validator/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="service-clients"&gt;Service Clients&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Slack incoming webhooks, Jira Server &lt;abbr title="Representational State Transfer"&gt;REST&lt;/abbr&gt;, DevLake webhooks, Sleuth.io delivery metrics, and public &lt;abbr title="Internet Protocol"&gt;IP&lt;/abbr&gt; resolution through ipify. See &lt;a href="https://nurago.org/packages/slack/"&gt;/packages/slack/&lt;/a&gt;, &lt;a href="https://nurago.org/packages/jirasrv/"&gt;/packages/jirasrv/&lt;/a&gt;, &lt;a href="https://nurago.org/packages/devlake/"&gt;/packages/devlake/&lt;/a&gt;, &lt;a href="https://nurago.org/packages/sleuth/"&gt;/packages/sleuth/&lt;/a&gt;, and &lt;a href="https://nurago.org/packages/ipify/"&gt;/packages/ipify/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="testing"&gt;Testing&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Readers and closers that fail on demand, output capture, an httprouter-backed handler bootstrap, and timestamp normalisation for golden-value assertions. See &lt;a href="https://nurago.org/packages/testutil/"&gt;/packages/testutil/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;An injectable client on every package that talks to a network dependency, so tests dial nothing.&lt;/li&gt;&#10;&lt;li&gt;A no-op metrics implementation that still returns a working SQL connection.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="project-properties"&gt;Project Properties&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;70 packages in one module, each imported on its own; 40 reach no external module at all. See &lt;a href="https://nurago.org/docs/dependency-footprint/"&gt;/docs/dependency-footprint/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;One configuration idiom throughout: a &lt;code&gt;New&lt;/code&gt; constructor with variadic &lt;code&gt;WithXxx&lt;/code&gt; options.&lt;/li&gt;&#10;&lt;li&gt;Errors exported as sentinels, matchable with &lt;code&gt;errors.Is&lt;/code&gt;, rather than compared as strings.&lt;/li&gt;&#10;&lt;li&gt;Semantic versioning with a stable exported API within v1.&lt;/li&gt;&#10;&lt;li&gt;No CGO anywhere, including the Kafka client.&lt;/li&gt;&#10;&lt;li&gt;Package documentation generated from the source, with the package READMEs and &lt;code&gt;llms.txt&lt;/code&gt; verified in &lt;abbr title="Continuous Integration"&gt;CI&lt;/abbr&gt;, so neither can drift from the code. The package pages on this site are generated from those READMEs.&lt;/li&gt;&#10;&lt;/ul&gt;</description></item><item><title>About</title><link>https://nurago.org/about/</link><pubDate>Sun, 20 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/about/</guid><description>&lt;p&gt;nurago is maintained by Nicola Asuni, trading as &lt;a href="https://tecnick.com"&gt;Tecnick.com LTD&lt;/a&gt;. It is &lt;a href="https://github.com/tecnickcom/nurago/blob/main/LICENSE"&gt;MIT&lt;/a&gt; licensed and developed in the open at &lt;a href="https://github.com/tecnickcom/nurago"&gt;github.com/tecnickcom/nurago&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h2 id="where-the-library-came-from"&gt;Where the library came from&lt;/h2&gt;&#10;&lt;p&gt;The code is older than the name. Four names, in fact, and the &lt;a href="https://github.com/tecnickcom/nurago/blob/main/LICENSE"&gt;LICENSE file&lt;/a&gt; records the whole chain:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;From&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;What happened&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2014&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;gogen&lt;/code&gt; starts at Tecnick.com as a Go service library and project generator&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2020&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Vonage refactors and extends gogen into &lt;a href="https://github.com/Vonage/gosrvlib"&gt;gosrvlib&lt;/a&gt;, under the same authorship&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2025-11-01&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;gosrvlib is merged back into gogen at Tecnick.com&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2026-07-11&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;gogen is renamed nurago; the old module path is &lt;a href="https://nurago.org/docs/migration-from-gogen/"&gt;deprecated&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;The packages have been through multiple production API platforms, not only a personal repository.&lt;/p&gt;</description></item><item><title>Comparison</title><link>https://nurago.org/comparison/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/comparison/</guid><description>&lt;p&gt;Four ways to get the infrastructure a backend service needs, each suited to a different situation. This page covers what nurago gives up along with what it provides. For the capability index, see &lt;a href="https://nurago.org/features/"&gt;/features/&lt;/a&gt;.&lt;/p&gt;&#10;&#10;&lt;div class="toc"&gt;&#10;&#9;&lt;h2&gt;Table of contents&lt;/h2&gt;&#10;&#9;&lt;nav id="TableOfContents"&gt;&#10; &lt;ul&gt;&#10; &lt;li&gt;&lt;a href="#the-four-options"&gt;The Four Options&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#against-the-standard-library-alone"&gt;Against the Standard Library Alone&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#against-a-framework"&gt;Against a Framework&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#against-an-internal-shared-library"&gt;Against an Internal Shared Library&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#against-single-purpose-packages"&gt;Against Single-Purpose Packages&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#what-nurago-does-not-provide"&gt;What nurago Does Not Provide&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#choosing"&gt;Choosing&lt;/a&gt;&lt;/li&gt;&#10; &lt;/ul&gt;&#10;&lt;/nav&gt;&#10;&lt;/div&gt;&#10;&#10;&#10;&lt;h2 id="the-four-options"&gt;The Four Options&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Approach&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;What you adopt&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;What it costs&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Standard library only&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Nothing beyond Go&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Every service re-derives retries, shutdown, redaction, and health checks&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;A framework&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;An application structure and its runtime&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;The structure reaches into your code and is expensive to leave&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;An internal shared library&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Your own module&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;You maintain, test, document, and version it&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;nurago&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Individual packages&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;An external dependency per package you import&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="against-the-standard-library-alone"&gt;Against the Standard Library Alone&lt;/h2&gt;&#10;&lt;p&gt;Go&amp;rsquo;s standard library covers a great deal of what a service does. &lt;code&gt;net/http&lt;/code&gt; serves and calls, &lt;code&gt;log/slog&lt;/code&gt; logs, &lt;code&gt;database/sql&lt;/code&gt; handles pools and transactions, and &lt;code&gt;context&lt;/code&gt; handles cancellation. Plenty of services need nothing more.&lt;/p&gt;</description></item><item><title>Sponsor</title><link>https://nurago.org/sponsor/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/sponsor/</guid><description>&lt;p&gt;&lt;strong&gt;nurago&lt;/strong&gt; is built and maintained by Nicola Asuni (&lt;a href="https://tecnick.com"&gt;Tecnick.com LTD&lt;/a&gt;). It is free, open-source infrastructure that backend services depend on in production.&lt;/p&gt;&#10;&lt;p&gt;Sponsorship pays for the unglamorous half of that: security fixes, dependency and Go version updates, vulnerability response, keeping the &lt;abbr title="Application Programming Interface"&gt;API&lt;/abbr&gt; stable across releases, and answering issues. If your company runs services built on these packages, it is closer to an insurance premium than to a donation.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://github.com/sponsors/tecnickcom"&gt;&lt;strong&gt;Become a sponsor →&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Legal</title><link>https://nurago.org/legal/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://nurago.org/legal/</guid><description>&lt;h2&gt;Trademarks&lt;/h2&gt;&#10;&#10;&lt;p&gt;Any trademarks, service marks, collective marks, design rights, personality rights or similar rights that are mentioned, used or cited on this website are the property of their respective owners. Unless otherwise stated, this website and its owners are neither endorsed by nor affiliated with any of the holders of such rights, and cannot grant any rights to use otherwise protected materials. Your use of any such property is at your own risk.&lt;/p&gt;</description></item><item><title>Getting Started</title><link>https://nurago.org/docs/getting-started/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/docs/getting-started/</guid><description>&lt;p&gt;nurago can be adopted one package at a time or used to wire a whole service. Both paths start from the same module.&lt;/p&gt;&#10;&lt;h2 id="requirements"&gt;Requirements&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Go 1.26.0 or later (the minimum declared in &lt;code&gt;go.mod&lt;/code&gt;; any newer release works).&lt;/li&gt;&#10;&lt;li&gt;No CGO and no system libraries.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="install"&gt;Install&lt;/h2&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;go get github.com/tecnickcom/nurago&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Import the packages you need, individually. There is no root package to import and nothing to register at startup:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;(&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/backoff&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/redact&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;go mod tidy&lt;/code&gt; then keeps only the modules your imports actually reach. Importing one package does not pull the dependencies of the others: see &lt;a href="https://nurago.org/docs/dependency-footprint/"&gt;/docs/dependency-footprint/&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Service Scaffolding</title><link>https://nurago.org/docs/service-scaffolding/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/docs/service-scaffolding/</guid><description>&lt;p&gt;Besides the packages, the repository carries a working web service under &lt;code&gt;examples/service&lt;/code&gt; and a generator that turns it into a new project with your own name, owner, and repository path. What you get is not a snippet to copy: it is a service with the lifecycle, configuration, routing, health endpoint, OpenAPI specifications, Docker build, packaging, and integration tests already wired and passing.&lt;/p&gt;&#10;&lt;h2 id="generate-a-project"&gt;Generate a Project&lt;/h2&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git clone https://github.com/tecnickcom/nurago.git&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd nurago&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cp project.cfg myproject.cfg&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Edit &lt;code&gt;myproject.cfg&lt;/code&gt;. The keys are the placeholders substituted throughout the generated tree:&lt;/p&gt;</description></item><item><title>Dependency Footprint</title><link>https://nurago.org/docs/dependency-footprint/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/docs/dependency-footprint/</guid><description>&lt;p&gt;nurago is one Go module containing 70 packages, several of which wrap large third-party SDKs. The obvious worry is that importing one package drags all of that into the binary. It does not.&lt;/p&gt;&#10;&lt;h2 id="how-it-works"&gt;How It Works&lt;/h2&gt;&#10;&lt;p&gt;Go resolves dependencies per package. The &lt;code&gt;require&lt;/code&gt; list in a module&amp;rsquo;s &lt;code&gt;go.mod&lt;/code&gt; is the set of modules that &lt;em&gt;some&lt;/em&gt; package in it may need. What ends up in your build is what your own imports reach, transitively, and &lt;code&gt;go mod tidy&lt;/code&gt; then prunes your &lt;code&gt;go.sum&lt;/code&gt; and module graph to that set.&lt;/p&gt;</description></item><item><title>Observability</title><link>https://nurago.org/docs/observability/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/docs/observability/</guid><description>&lt;p&gt;These concerns stay in separate packages so each can be adopted alone: what the service writes (logging), what it counts (metrics), what correlates a request&amp;rsquo;s records (trace IDs), and what must never reach the output (redaction).&lt;/p&gt;&#10;&lt;h2 id="logging"&gt;Logging&lt;/h2&gt;&#10;&lt;p&gt;Application code depends on the standard &lt;code&gt;log/slog&lt;/code&gt; and nothing else. The handler behind it comes from either &lt;code&gt;logutil&lt;/code&gt; or &lt;code&gt;logsrv&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://nurago.org/packages/logutil/"&gt;&lt;code&gt;logutil&lt;/code&gt;&lt;/a&gt; is the configuration-driven path, on the standard library alone. &lt;code&gt;Config&lt;/code&gt; and its options build handlers and loggers from typed settings; &lt;code&gt;ParseLevel&lt;/code&gt;, &lt;code&gt;ParseFormat&lt;/code&gt;, &lt;code&gt;ValidLevel&lt;/code&gt;, and &lt;code&gt;ValidFormat&lt;/code&gt; turn runtime configuration strings into those settings and reject the invalid ones. Output is &lt;abbr title="JavaScript Object Notation"&gt;JSON&lt;/abbr&gt;, text console, or discard. The severity model extends the syslog levels (emergency through debug) with a trace level, so a service can log at severities the standard &lt;code&gt;slog&lt;/code&gt; levels do not name.&lt;/p&gt;</description></item><item><title>Resilience</title><link>https://nurago.org/docs/resilience/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/docs/resilience/</guid><description>&lt;p&gt;Every dependency a service calls will fail sometimes. These packages decide when to try again and how long to wait. They share one delay calculator, so a single retry policy applies to &lt;abbr title="HyperText Transfer Protocol"&gt;HTTP&lt;/abbr&gt; and non-HTTP work alike.&lt;/p&gt;&#10;&lt;h2 id="the-delay-schedule"&gt;The Delay Schedule&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://nurago.org/packages/backoff/"&gt;&lt;code&gt;backoff&lt;/code&gt;&lt;/a&gt; is a pure calculator: no timing, no sleeping, no goroutines, no I/O. Given a base delay, a growth factor, a jitter ceiling, and a maximum, each call to &lt;code&gt;Schedule.Next&lt;/code&gt; returns the next delay and advances the progression. You own the loop and the timer.&lt;/p&gt;</description></item><item><title>Security</title><link>https://nurago.org/docs/security/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/docs/security/</guid><description>&lt;p&gt;The security-relevant packages, and the operational defaults worth checking before a service faces the internet.&lt;/p&gt;&#10;&lt;h2 id="password-storage"&gt;Password Storage&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://nurago.org/packages/passwordhash/"&gt;&lt;code&gt;passwordhash&lt;/code&gt;&lt;/a&gt; implements Argon2id (&lt;abbr title="Request for Comments"&gt;RFC&lt;/abbr&gt; 9106) following the &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html" rel="nofollow"&gt;OWASP Password Storage Cheat Sheet&lt;/a&gt;, with &lt;code&gt;New()&lt;/code&gt; returning the RFC 9106 §4 defaults.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;p&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;passwordhash.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;()&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hash,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;p.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;PasswordHash&lt;/span&gt;(plaintext)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// store this&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ok,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;p.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;PasswordVerify&lt;/span&gt;(plaintext,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;hash)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// on login&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;upgrade,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;p.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;PasswordNeedsRehash&lt;/span&gt;(hash)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// after a successful verify&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;By default the stored value is a base64-encoded &lt;abbr title="JavaScript Object Notation"&gt;JSON&lt;/abbr&gt; object of about 200 bytes that describes itself: algorithm name, Argon2id version, key and salt lengths, time and memory cost, parallelism, the random salt, and the derived key.&lt;/p&gt;</description></item><item><title>Data and Messaging</title><link>https://nurago.org/docs/data-and-messaging/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/docs/data-and-messaging/</guid><description>&lt;p&gt;The stateful dependencies a backend service talks to. Every client here exposes a &lt;code&gt;HealthCheck(ctx) error&lt;/code&gt; method that satisfies the &lt;a href="https://nurago.org/packages/healthcheck/"&gt;&lt;code&gt;healthcheck&lt;/code&gt;&lt;/a&gt; interface directly, so registering a dependency in the readiness endpoint needs no adapter.&lt;/p&gt;&#10;&lt;h2 id="sql-connections"&gt;SQL Connections&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://nurago.org/packages/sqlconn/"&gt;&lt;code&gt;sqlconn&lt;/code&gt;&lt;/a&gt; manages a &lt;code&gt;database/sql&lt;/code&gt; pool through the lifetime of a long-running service: applying pool limits, verifying connectivity, exposing a health check, and closing on shutdown. It reaches no external module (you supply the driver).&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;c,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;sqlconn.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Connect&lt;/span&gt;(&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;ctx,&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;mysql://user:pass@tcp(localhost:3306)/appdb&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;sqlconn.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;WithDefaultDriver&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;mysql&amp;#34;&lt;/span&gt;),&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;sqlconn.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;WithShutdownSignalChan&lt;/span&gt;(shutdownCh),&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;sqlconn.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;WithShutdownWaitGroup&lt;/span&gt;(&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;shutdownWG),&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;Connect&lt;/code&gt; accepts a &lt;code&gt;&amp;lt;DRIVER&amp;gt;://&amp;lt;DSN&amp;gt;&lt;/code&gt; string; &lt;code&gt;New&lt;/code&gt; takes the driver and DSN separately.&lt;/p&gt;</description></item><item><title>Testing</title><link>https://nurago.org/docs/testing/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/docs/testing/</guid><description>&lt;p&gt;How to test code that uses nurago, and how nurago tests itself.&lt;/p&gt;&#10;&lt;h2 id="test-helpers"&gt;Test Helpers&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://nurago.org/packages/testutil/"&gt;&lt;code&gt;testutil&lt;/code&gt;&lt;/a&gt; collects the helpers that make otherwise awkward branches testable. It is intended for use in tests only.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;reader&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;testutil.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;NewErrorReader&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;read failed&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// Read always fails&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;closer&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;testutil.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;NewErrorCloser&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;close failed&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// Close always fails&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;output&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;testutil.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;CaptureOutput&lt;/span&gt;(t,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;func&lt;/span&gt;()&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;fmt.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Println&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;hello&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;h&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;testutil.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;RouterWithHandler&lt;/span&gt;(http.MethodGet,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;/health&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;handler)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;normalized&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;testutil.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;ReplaceDateTime&lt;/span&gt;(responseBody,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;&amp;lt;DATETIME&amp;gt;&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;normalized&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;testutil.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;ReplaceUnixTimestamp&lt;/span&gt;(normalized,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;&amp;lt;UNIX_TS&amp;gt;&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The error reader and closer exercise I/O failure paths that are hard to trigger otherwise. &lt;code&gt;CaptureOutput&lt;/code&gt; redirects stdout, stderr, and the default logger for the duration of a call. &lt;code&gt;RouterWithHandler&lt;/code&gt; builds a real httprouter-backed handler, which is what a handler reading path parameters needs. &lt;code&gt;ReplaceDateTime&lt;/code&gt; and &lt;code&gt;ReplaceUnixTimestamp&lt;/code&gt; normalise generated timestamps so a &lt;abbr title="JavaScript Object Notation"&gt;JSON&lt;/abbr&gt; response can be compared against a fixed golden value.&lt;/p&gt;</description></item><item><title>Migration from gogen</title><link>https://nurago.org/docs/migration-from-gogen/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/docs/migration-from-gogen/</guid><description>&lt;p&gt;This project was previously named &lt;a href="https://github.com/tecnickcom/gogen"&gt;gogen&lt;/a&gt;. Same library, same packages, new name.&lt;/p&gt;&#10;&lt;p&gt;The &lt;code&gt;github.com/tecnickcom/gogen&lt;/code&gt; module path is &lt;strong&gt;deprecated&lt;/strong&gt;. A build pinned to &lt;code&gt;v1.145.0&lt;/code&gt; or earlier keeps working, because those versions still declare &lt;code&gt;module github.com/tecnickcom/gogen&lt;/code&gt; in their &lt;code&gt;go.mod&lt;/code&gt;. From &lt;code&gt;v1.146.0&lt;/code&gt; onwards the tags declare the new module path, so the old import path no longer resolves at all:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;go: github.com/tecnickcom/gogen@latest (v1.154.3) requires&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;github.com/tecnickcom/gogen@v1.154.3: parsing go.mod:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;module declares its path as: github.com/tecnickcom/nurago&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9; but was required as: github.com/tecnickcom/gogen&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;An old pin therefore keeps building until you try to update it, at which point the migration below is the only way forward.&lt;/p&gt;</description></item><item><title>Development</title><link>https://nurago.org/docs/development/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/docs/development/</guid><description>&lt;p&gt;Working on nurago itself, or on a project generated from it.&lt;/p&gt;&#10;&lt;h2 id="clone-and-validate"&gt;Clone and Validate&lt;/h2&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git clone https://github.com/tecnickcom/nurago.git&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd nurago&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;make x&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;make x&lt;/code&gt; runs the whole flow from scratch: update the library version in the examples, format, clean, download and get dependencies, generate code, regenerate the documentation, run the &lt;abbr title="Quality Assurance"&gt;QA&lt;/abbr&gt; pipeline, then build and test the service example. All artifacts and reports land in &lt;code&gt;target/&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Requirements: Go 1.26.0 or later. No CGO.&lt;/p&gt;</description></item><item><title>AI Coding Assistants</title><link>https://nurago.org/docs/ai-assistants/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/docs/ai-assistants/</guid><description>&lt;p&gt;Some of the readers of this documentation are language models writing code on someone&amp;rsquo;s behalf. This page collects what nurago publishes for them, and what to tell an assistant so its output compiles.&lt;/p&gt;&#10;&lt;h2 id="machine-readable-indexes"&gt;Machine-Readable Indexes&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;&lt;a href="https://nurago.org/llms.txt"&gt;/llms.txt&lt;/a&gt;&lt;/strong&gt;: the condensed index of this site and of all 70 packages, in the &lt;a href="https://llmstxt.org/" rel="nofollow"&gt;llms.txt&lt;/a&gt; format. One entry per package with its one-line description and its page &lt;abbr title="Uniform Resource Locator"&gt;URL&lt;/abbr&gt;, so an assistant can find the right package without crawling.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/tecnickcom/nurago/blob/main/llms.txt"&gt;llms.txt in the repository&lt;/a&gt;&lt;/strong&gt;: the same index pointing at the reference documentation on pkg.go.dev. It is generated from the package source by &lt;code&gt;make gendoc&lt;/code&gt; and checked in &lt;abbr title="Continuous Integration"&gt;CI&lt;/abbr&gt;, so it cannot drift from the code.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&lt;a href="https://pkg.go.dev/github.com/tecnickcom/nurago"&gt;pkg.go.dev&lt;/a&gt;&lt;/strong&gt;: the full generated &lt;abbr title="Application Programming Interface"&gt;API&lt;/abbr&gt; reference, including every runnable example.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Per-package pages&lt;/strong&gt; on this site, each carrying the complete package documentation, its runnable example, and its exact dependency footprint: &lt;a href="https://nurago.org/packages/"&gt;/packages/&lt;/a&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Every one of those is plain text or plain &lt;abbr title="HyperText Markup Language"&gt;HTML&lt;/abbr&gt;, reachable without JavaScript. &lt;a href="https://nurago.org/robots.txt"&gt;robots.txt&lt;/a&gt; allows every crawler, &lt;abbr title="Artificial Intelligence"&gt;AI&lt;/abbr&gt; and assistant retrieval agents included.&lt;/p&gt;</description></item><item><title>bootstrap</title><link>https://nurago.org/packages/bootstrap/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/bootstrap/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/bootstrap&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;One call that builds the context, logger and metrics client, hands them to your wiring function, then blocks until a shutdown signal and waits for registered workers to finish.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;bootstrap.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Bootstrap&lt;/span&gt;(bind,&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;bootstrap.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;WithLogConfig&lt;/span&gt;(logutil.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;DefaultConfig&lt;/span&gt;()),&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;bootstrap.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;WithShutdownTimeout&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;30&lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;time.Second),&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;bootstrap.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;WithShutdownWaitGroup&lt;/span&gt;(shutdownWG),&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;bootstrap.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;WithShutdownSignalChan&lt;/span&gt;(shutdownCh),&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-sequence"&gt;The sequence&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;A cancellable context is created and threaded through everything.&lt;/li&gt;&#10;&lt;li&gt;A &lt;a href="https://nurago.org/packages/metrics/"&gt;metrics.Client&lt;/a&gt; is created.&lt;/li&gt;&#10;&lt;li&gt;A &lt;code&gt;*slog.Logger&lt;/code&gt; is created.&lt;/li&gt;&#10;&lt;li&gt;Your &lt;code&gt;BindFunc&lt;/code&gt; is called with all three. This is where servers, database connections and workers are registered.&lt;/li&gt;&#10;&lt;li&gt;Bootstrap blocks until SIGINT, SIGTERM, or external context cancellation.&lt;/li&gt;&#10;&lt;li&gt;The shutdown signal is broadcast on the shared channel and the context is cancelled.&lt;/li&gt;&#10;&lt;li&gt;It waits on the shared &lt;code&gt;sync.WaitGroup&lt;/code&gt;, bounded by a timeout.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Your code appears once, at step 4, and receives the three things every component needs rather than constructing them itself or reaching for globals.&lt;/p&gt;</description></item><item><title>config</title><link>https://nurago.org/packages/config/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/config/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/config&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Loads configuration from defaults, a file, the environment and optionally a remote source, in a fixed order, then validates the result. Built on &lt;a href="https://github.com/spf13/viper"&gt;Viper&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;An application implements two methods:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;SetDefaults&lt;/span&gt;(v&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Viper)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// register application defaults&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Validate&lt;/span&gt;()&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;error&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// enforce final constraints&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-load-order"&gt;The load order&lt;/h2&gt;&#10;&lt;p&gt;Later steps override earlier ones:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Built-in defaults from this package (log and shutdown settings), plus your &lt;code&gt;SetDefaults&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;A local config file, &lt;code&gt;config.json&lt;/code&gt; by default, searched in an explicit &lt;code&gt;configDir&lt;/code&gt; first, then &lt;code&gt;./&lt;/code&gt;, &lt;code&gt;$HOME/.&amp;lt;cmdName&amp;gt;/&lt;/code&gt;, &lt;code&gt;/etc/&amp;lt;cmdName&amp;gt;/&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;Environment overrides.&lt;/li&gt;&#10;&lt;li&gt;An optional remote source through &lt;code&gt;WithRemoteLoader&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;Validate&lt;/code&gt;.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Fixing the order is the point. The common alternative, each service resolving precedence in its own &lt;code&gt;main&lt;/code&gt;, produces subtly different behaviour per service, and the difference is only discovered when an environment variable that works in one service is ignored in another.&lt;/p&gt;</description></item><item><title>httpserver</title><link>https://nurago.org/packages/httpserver/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/httpserver/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/httpserver&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Builds and runs the HTTP server: your routes from a &lt;code&gt;Binder&lt;/code&gt;, the operational routes from a built-in set, middleware, and a shutdown path that waits for in-flight work.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;srv,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;httpserver.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;binder{},&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;httpserver.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;WithServerAddr&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;:8080&amp;#34;&lt;/span&gt;),&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;httpserver.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;WithEnableDefaultRoutes&lt;/span&gt;(httpserver.PingRoute,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;httpserver.StatusRoute),&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;httpserver.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;WithRequestTimeout&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;30&lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;time.Second),&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;httpserver.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;WithShutdownTimeout&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;10&lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;time.Second),&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;srv.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;StartServer&lt;/span&gt;()&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="misconfiguration-is-an-error-from-new-not-a-panic"&gt;Misconfiguration is an error from New, not a panic&lt;/h2&gt;&#10;&lt;p&gt;Nil handlers, nil middleware, duplicate routes, malformed routes and unknown default-route identifiers are all reported by &lt;code&gt;New&lt;/code&gt; as wrapped sentinel errors.&lt;/p&gt;</description></item><item><title>httputil</title><link>https://nurago.org/packages/httputil/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/httputil/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/httputil&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Request and response primitives for &lt;code&gt;net/http&lt;/code&gt; handlers: header decorators, query and path extraction with defaults, buffered JSON/XML/text responses, RFC 9457 problem documents, and a &lt;code&gt;ResponseWriter&lt;/code&gt; wrapper that captures status and size.&lt;/p&gt;&#10;&lt;p&gt;No framework, no router of its own, no interface your handlers must implement.&lt;/p&gt;&#10;&lt;h2 id="reading-a-request"&gt;Reading a request&lt;/h2&gt;&#10;&lt;p&gt;Header decorators for outbound requests:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;httputil.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;AddJSONHeaders&lt;/span&gt;(r)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;httputil.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;AddBearerToken&lt;/span&gt;(token,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;r)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;httputil.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;AddBasicAuth&lt;/span&gt;(apiKey,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;apiSecret,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;r)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;httputil.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;AddAuthorizationHeader&lt;/span&gt;(auth,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;r)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Extraction with defaults for inbound ones:&lt;/p&gt;</description></item><item><title>jsendx</title><link>https://nurago.org/packages/jsendx/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/jsendx/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/httputil/jsendx&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;One response shape for every endpoint in a service, whether it succeeded, failed, or panicked.&lt;/p&gt;&#10;&lt;h2 id="what-the-envelope-adds-to-jsend"&gt;What the envelope adds to JSend&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://github.com/omniti-labs/jsend"&gt;JSend&lt;/a&gt; is a minimal convention: a &lt;code&gt;status&lt;/code&gt; of &lt;code&gt;success&lt;/code&gt;, &lt;code&gt;fail&lt;/code&gt; or &lt;code&gt;error&lt;/code&gt;, and a &lt;code&gt;data&lt;/code&gt; payload. It says nothing about anything else.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;Response&lt;/code&gt; extends it with fields that answer operational questions from the response body alone:&lt;/p&gt;</description></item><item><title>healthcheck</title><link>https://nurago.org/packages/healthcheck/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/healthcheck/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/healthcheck&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Turns a list of dependency probes into one HTTP endpoint that reports whether the service can do its job.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;h&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;healthcheck.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;NewHandler&lt;/span&gt;([]healthcheck.HealthCheck{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;healthcheck.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;database&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;db),&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;healthcheck.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;redis&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;redisClient),&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;healthcheck.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;s3&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;s3Client),&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="one-method-and-the-clients-already-have-it"&gt;One method, and the clients already have it&lt;/h2&gt;&#10;&lt;p&gt;The contract is &lt;code&gt;HealthCheck(context.Context) error&lt;/code&gt;. Every client in this module implements it directly, so registration needs no adapter: &lt;a href="https://nurago.org/packages/redis/"&gt;redis&lt;/a&gt;, &lt;a href="https://nurago.org/packages/valkey/"&gt;valkey&lt;/a&gt;, &lt;a href="https://nurago.org/packages/sqs/"&gt;sqs&lt;/a&gt;, &lt;a href="https://nurago.org/packages/s3/"&gt;s3&lt;/a&gt;, &lt;a href="https://nurago.org/packages/sqlconn/"&gt;sqlconn&lt;/a&gt;, &lt;a href="https://nurago.org/packages/ipify/"&gt;ipify&lt;/a&gt; and &lt;a href="https://nurago.org/packages/slack/"&gt;slack&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>profiling</title><link>https://nurago.org/packages/profiling/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/profiling/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/profiling&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Exposes Go&amp;rsquo;s &lt;code&gt;net/http/pprof&lt;/code&gt; profiles through &lt;a href="https://github.com/julienschmidt/httprouter"&gt;httprouter&lt;/a&gt;, as one handler on one wildcard route.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;router.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;HandlerFunc&lt;/span&gt;(http.MethodGet,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;/pprof/*option&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;profiling.PProfHandler)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-problem-with-importing-nethttppprof"&gt;The problem with importing net/http/pprof&lt;/h2&gt;&#10;&lt;p&gt;The standard package registers its handlers on &lt;code&gt;http.DefaultServeMux&lt;/code&gt; as an import side effect. That is convenient for a program serving from the default mux and a nuisance otherwise: it mutates global state on import, binds the paths to &lt;code&gt;/debug/pprof/&lt;/code&gt;, and does not help at all with a router that is not the default mux.&lt;/p&gt;</description></item><item><title>logutil</title><link>https://nurago.org/packages/logutil/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/logutil/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/logutil&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Builds &lt;code&gt;log/slog&lt;/code&gt; loggers from typed configuration: format, level, common attributes, trace-ID injection and an optional hook, with parsing and validation for values that arrive as strings from a config file.&lt;/p&gt;&#10;&lt;h2 id="two-slog-encoding-faults-it-repairs"&gt;Two slog encoding faults it repairs&lt;/h2&gt;&#10;&lt;p&gt;The handlers from &lt;code&gt;Config.SlogHandler&lt;/code&gt; write through the standard library&amp;rsquo;s JSON or text handler, but filter the record first. Two of the differences are corrections to output the standard library gets wrong.&lt;/p&gt;</description></item><item><title>logsrv</title><link>https://nurago.org/packages/logsrv/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/logsrv/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/logsrv&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A &lt;code&gt;slog.Handler&lt;/code&gt; implemented on zerolog. Application code calls &lt;code&gt;log/slog&lt;/code&gt;; zerolog does the encoding.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;logger&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;logsrv.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;NewLogger&lt;/span&gt;(cfg)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It reuses the configuration model from &lt;a href="https://nurago.org/packages/logutil/"&gt;logutil&lt;/a&gt;, so format selection, common attributes, trace-ID injection, hooks and the full syslog level names work identically across both backends.&lt;/p&gt;&#10;&lt;h2 id="native-handler-not-an-adapter"&gt;Native handler, not an adapter&lt;/h2&gt;&#10;&lt;p&gt;The distinction matters for what it costs. A naive bridge formats the slog record into a string or a map and hands that to zerolog, paying for an intermediate representation on every line.&lt;/p&gt;</description></item><item><title>metrics</title><link>https://nurago.org/packages/metrics/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/metrics/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/metrics&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Defines the &lt;code&gt;Client&lt;/code&gt; interface that service code instruments against, so the choice of metrics backend stays in &lt;code&gt;main&lt;/code&gt;. Ships one no-op implementation; the real backends are &lt;a href="https://nurago.org/packages/opentel/"&gt;opentel&lt;/a&gt;, &lt;a href="https://nurago.org/packages/prometheus/"&gt;prometheus&lt;/a&gt; and &lt;a href="https://nurago.org/packages/statsd/"&gt;statsd&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h2 id="the-interface"&gt;The interface&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;Client&lt;/code&gt; covers the instrumentation points a service actually has, rather than exposing a generic counter/gauge/histogram vocabulary:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;SqlOpen&lt;/span&gt;(driverName,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;dsn&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;(&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;sql.DB,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;error&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;InstrumentDB&lt;/span&gt;(name&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;db&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;sql.DB)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;error&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;InstrumentHandler&lt;/span&gt;(path&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;handler&lt;span style="color:#6e7681"&gt; &lt;/span&gt;http.HandlerFunc)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;http.Handler&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;InstrumentRoundTripper&lt;/span&gt;(next&lt;span style="color:#6e7681"&gt; &lt;/span&gt;http.RoundTripper)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;http.RoundTripper&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;MetricsHandlerFunc&lt;/span&gt;()&lt;span style="color:#6e7681"&gt; &lt;/span&gt;http.HandlerFunc&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;IncLogLevelCounter&lt;/span&gt;(level&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;IncErrorCounter&lt;/span&gt;(task,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;operation,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;code&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Close&lt;/span&gt;()&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;error&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Naming the integration points rather than the primitives is what makes the abstraction hold. A generic &lt;code&gt;Counter(name string)&lt;/code&gt; interface leaks immediately, because Prometheus wants label dimensions declared up front, StatsD wants a dotted name, and OpenTelemetry wants attributes on an instrument. Wrapping an &lt;code&gt;http.Handler&lt;/code&gt; is the same operation in all three.&lt;/p&gt;</description></item><item><title>opentel</title><link>https://nurago.org/packages/opentel/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/opentel/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/metrics/opentel&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A &lt;a href="https://nurago.org/packages/metrics/"&gt;metrics.Client&lt;/a&gt; backed by OpenTelemetry, covering metrics and distributed tracing through one client.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;m,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;opentel.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;myservice&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;1.4.2&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;defer&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;m.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Close&lt;/span&gt;()&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="both-signals-one-construction"&gt;Both signals, one construction&lt;/h2&gt;&#10;&lt;p&gt;This is what separates it from the other two backends. &lt;a href="https://nurago.org/packages/prometheus/"&gt;prometheus&lt;/a&gt; and &lt;a href="https://nurago.org/packages/statsd/"&gt;statsd&lt;/a&gt; do metrics. This one also produces spans, and the instrumentation points are shared: wrapping an HTTP handler records a duration histogram &lt;em&gt;and&lt;/em&gt; starts a server span; wrapping a round tripper does the same on the client side and injects trace context into the outbound request.&lt;/p&gt;</description></item><item><title>prometheus</title><link>https://nurago.org/packages/prometheus/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/prometheus/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/metrics/prometheus&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A &lt;a href="https://nurago.org/packages/metrics/"&gt;metrics.Client&lt;/a&gt; backed by &lt;code&gt;prometheus/client_golang&lt;/code&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;m,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;prometheus.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;()&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="what-you-get-without-defining-a-metric"&gt;What you get without defining a metric&lt;/h2&gt;&#10;&lt;p&gt;The default registration is the reason to use this rather than wiring &lt;code&gt;client_golang&lt;/code&gt; yourself. Out of &lt;code&gt;New&lt;/code&gt; you have:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Go runtime and process collectors: goroutine count, heap, GC pause, open file descriptors, CPU.&lt;/li&gt;&#10;&lt;li&gt;Inbound HTTP: request count, in-flight gauge, duration histogram, request size histogram, response size histogram.&lt;/li&gt;&#10;&lt;li&gt;Outbound HTTP: request count, in-flight gauge, duration histogram.&lt;/li&gt;&#10;&lt;li&gt;SQL connection pool stats, through &lt;code&gt;dlmiddlecote/sqlstats&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;Error counters by log level and by task, operation and code.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;That set answers most of what a service dashboard asks, and defining it by hand is a few hundred lines that every service repeats.&lt;/p&gt;</description></item><item><title>statsd</title><link>https://nurago.org/packages/statsd/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/statsd/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/metrics/statsd&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A &lt;a href="https://nurago.org/packages/metrics/"&gt;metrics.Client&lt;/a&gt; that pushes to a StatsD daemon, which aggregates and forwards to whatever sits behind it. Built on &lt;code&gt;github.com/tecnickcom/statsd&lt;/code&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;m,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;statsd.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;statsd.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;WithPrefix&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;myservice&amp;#34;&lt;/span&gt;),&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;statsd.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;WithNetwork&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;udp&amp;#34;&lt;/span&gt;),&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;statsd.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;WithAddress&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;127.0.0.1:8125&amp;#34;&lt;/span&gt;),&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;statsd.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;WithFlushPeriod&lt;/span&gt;(time.Second),&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="push-and-what-udp-buys"&gt;Push, and what UDP buys&lt;/h2&gt;&#10;&lt;p&gt;The process sends metrics out rather than waiting to be scraped, normally to an agent on the same host or in the same pod.&lt;/p&gt;</description></item><item><title>traceid</title><link>https://nurago.org/packages/traceid/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/traceid/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/traceid&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Read an ID off an inbound request, carry it in the context, write it onto outbound requests. That is the whole loop, and it works without adopting OpenTelemetry or any other tracing framework.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// inbound, in middleware&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;id&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;traceid.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;FromHTTPRequestHeader&lt;/span&gt;(r,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;traceid.DefaultHeader,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;traceid.DefaultValue)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ctx&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;traceid.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;NewContext&lt;/span&gt;(r.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Context&lt;/span&gt;(),&lt;span style="color:#6e7681"&gt; &lt;/span&gt;id)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// outbound, before calling a downstream service&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;traceid.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;SetHTTPRequestHeaderFromContext&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;req,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;traceid.DefaultHeader,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;traceid.DefaultValue)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-header-is-untrusted-input-and-this-package-treats-it-that-way"&gt;The header is untrusted input, and this package treats it that way&lt;/h2&gt;&#10;&lt;p&gt;Validation happens at ingest.&lt;/p&gt;</description></item><item><title>redact</title><link>https://nurago.org/packages/redact/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/redact/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/redact&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Replaces secrets in text with a marker, before it reaches a log.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;safe&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;redact.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Default&lt;/span&gt;().&lt;span style="color:#d2a8ff;font-weight:bold"&gt;String&lt;/span&gt;(rawPayload)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="one-pass-whatever-the-pattern-count"&gt;One pass, whatever the pattern count&lt;/h2&gt;&#10;&lt;p&gt;Every pattern is matched in a &lt;strong&gt;single pass&lt;/strong&gt; over the input. Enabling more patterns does not make redaction slower.&lt;/p&gt;&#10;&lt;p&gt;That property is what makes it usable on the logging path at all. The obvious implementation, a slice of regular expressions applied in sequence, costs one full scan per pattern, so a realistic set of twenty patterns scans the input twenty times. On a request dump of a few kilobytes, in a service logging every request, that becomes measurable and people start disabling redaction for performance.&lt;/p&gt;</description></item><item><title>httpclient</title><link>https://nurago.org/packages/httpclient/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/httpclient/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/httpclient&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;An outbound HTTP client that carries the trace ID onward and logs each call in a structured, redacted form.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;c&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;httpclient.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(httpclient.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;WithTimeout&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;10&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;time.Second))&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;resp,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Do&lt;/span&gt;(req)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Defaults: a 1-minute timeout, the standard transport, and the &lt;a href="https://nurago.org/packages/traceid/"&gt;traceid&lt;/a&gt; default header.&lt;/p&gt;&#10;&lt;h2 id="trace-propagation-including-when-there-is-nothing-to-propagate"&gt;Trace propagation, including when there is nothing to propagate&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;Do&lt;/code&gt; reads the trace ID from the context and sets it on the outbound request. When the context has none, it &lt;strong&gt;generates a UUIDv7&lt;/strong&gt; and attaches it to both the context and the request headers.&lt;/p&gt;</description></item><item><title>httpretrier</title><link>https://nurago.org/packages/httpretrier/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/httpretrier/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/httpretrier&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Adds retries to an HTTP client you already have.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;r,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;httpretrier.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(client,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;httpretrier.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;WithAttempts&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;3&lt;/span&gt;))&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;resp,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;r.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Do&lt;/span&gt;(req)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="it-wraps-rather-than-replaces"&gt;It wraps, rather than replaces&lt;/h2&gt;&#10;&lt;p&gt;Retries are applied per request, around an existing &lt;code&gt;HTTPClient&lt;/code&gt;, so a client that is already instrumented, authenticated, or both keeps everything it had.&lt;/p&gt;&#10;&lt;p&gt;The alternative shape, a retrying client that you construct instead of yours, forces a choice between retries and whatever the other client was doing. Here &lt;a href="https://nurago.org/packages/httpclient/"&gt;httpclient&lt;/a&gt; provides tracing, logging and redaction, and this wraps it.&lt;/p&gt;</description></item><item><title>httpreverseproxy</title><link>https://nurago.org/packages/httpreverseproxy/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/httpreverseproxy/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/httpreverseproxy&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Forwards an inbound request to a configured upstream, on top of &lt;code&gt;net/http/httputil.ReverseProxy&lt;/code&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;c,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;httpreverseproxy.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(upstreamURL)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;router.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Handle&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;GET&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;/api/*path&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.ForwardRequest)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="redirects-are-forwarded-never-followed"&gt;Redirects are forwarded, never followed&lt;/h2&gt;&#10;&lt;p&gt;The default upstream client does not follow redirects. A 3xx is passed back to the client verbatim.&lt;/p&gt;&#10;&lt;p&gt;This closes an SSRF vector. A proxy that follows redirects will fetch whatever the upstream points it at, and the upstream can point it at &lt;code&gt;169.254.169.254&lt;/code&gt;, at an internal service, or at a host the proxy can reach and the original client cannot. The proxy&amp;rsquo;s network position is the privilege being borrowed.&lt;/p&gt;</description></item><item><title>retrier</title><link>https://nurago.org/packages/retrier/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/retrier/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/retrier&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Runs a function until it succeeds, gives up, or the context is cancelled, waiting a growing jittered delay between attempts.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;r,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;retrier.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(retrier.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;WithAttempts&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;5&lt;/span&gt;))&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;r.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Run&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;func&lt;/span&gt;(ctx&lt;span style="color:#6e7681"&gt; &lt;/span&gt;context.Context)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;error&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;return&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;doSomething&lt;/span&gt;(ctx)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-loop"&gt;The loop&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Run the task with a per-attempt timeout context.&lt;/li&gt;&#10;&lt;li&gt;Evaluate the result with the retry predicate.&lt;/li&gt;&#10;&lt;li&gt;Stop when attempts are exhausted or the predicate says no.&lt;/li&gt;&#10;&lt;li&gt;Otherwise wait the delay plus jitter.&lt;/li&gt;&#10;&lt;li&gt;Multiply the delay by the factor and go again.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Parent context cancellation is honoured throughout, including while waiting between attempts. A cancelled request does not sit out a five-second backoff before noticing.&lt;/p&gt;</description></item><item><title>backoff</title><link>https://nurago.org/packages/backoff/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/backoff/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/backoff&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Computes the delay before the next retry attempt. &lt;code&gt;Schedule.Next&lt;/code&gt; returns a duration and advances the progression.&lt;/p&gt;&#10;&lt;p&gt;It performs no timing, no scheduling, no goroutines and no I/O. You own the loop and the timer; this answers how long to wait.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;s&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;backoff.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(backoff.Config{&lt;span style="color:#ff7b72;font-weight:bold"&gt;...&lt;/span&gt;})&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;for&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;if&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;do&lt;/span&gt;();&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;==&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#79c0ff"&gt;nil&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;break&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;time.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Sleep&lt;/span&gt;(s.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Next&lt;/span&gt;())&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="three-jitter-strategies"&gt;Three jitter strategies&lt;/h2&gt;&#10;&lt;p&gt;Without jitter, a fleet of clients that all failed at the same instant retries at the same instant, and again at the same instant, hammering a recovering service in synchronised waves.&lt;/p&gt;</description></item><item><title>periodic</title><link>https://nurago.org/packages/periodic/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/periodic/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/periodic&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Runs a function repeatedly on a schedule, in its own goroutine, with a deadline on each call.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;p,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;periodic.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;30&lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;time.Second,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// interval&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;5&lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;time.Second,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// jitter ceiling&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;10&lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;time.Second,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// per-call timeout&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;myTask,&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;p.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Start&lt;/span&gt;(ctx)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;defer&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;p.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Stop&lt;/span&gt;()&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="what-it-adds-to-timeticker"&gt;What it adds to time.Ticker&lt;/h2&gt;&#10;&lt;p&gt;A &lt;code&gt;time.Ticker&lt;/code&gt; and a &lt;code&gt;for&lt;/code&gt; loop is four lines. The four properties below are what the four lines leave out, and each one is a production incident in its own right.&lt;/p&gt;</description></item><item><title>dnscache</title><link>https://nurago.org/packages/dnscache/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/dnscache/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/dnscache&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Caches host resolution and dials through it. &lt;code&gt;Cache.DialContext&lt;/code&gt; drops straight into an &lt;code&gt;http.Transport&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;c&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;dnscache.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(&lt;span style="color:#79c0ff"&gt;nil&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;1024&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;30&lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;time.Second)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;transport&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;http.Transport{DialContext:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.DialContext}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="why-go-programs-need-this"&gt;Why Go programs need this&lt;/h2&gt;&#10;&lt;p&gt;Go&amp;rsquo;s resolver does not cache. Every &lt;code&gt;LookupHost&lt;/code&gt; goes to the configured DNS servers, and in a container that usually means a cluster DNS service a network hop away, sometimes with a search-domain list that turns one lookup into four or five queries.&lt;/p&gt;</description></item><item><title>passwordhash</title><link>https://nurago.org/packages/passwordhash/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/passwordhash/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/passwordhash&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Hashes and verifies passwords with Argon2id, following &lt;a href="https://www.rfc-editor.org/rfc/rfc9106.html"&gt;RFC 9106&lt;/a&gt; and the &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html"&gt;OWASP Password Storage Cheat Sheet&lt;/a&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;p&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;passwordhash.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;()&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// RFC 9106 §4 defaults&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hash,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;p.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;PasswordHash&lt;/span&gt;(plaintext)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ok,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;p.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;PasswordVerify&lt;/span&gt;(plaintext,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;hash)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;upgrade,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;p.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;PasswordNeedsRehash&lt;/span&gt;(hash)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="argon2id-and-what-the-defaults-cost"&gt;Argon2id, and what the defaults cost&lt;/h2&gt;&#10;&lt;p&gt;Argon2id is memory-hard by design: it forces an attacker to commit RAM per guess. That removes the advantage of the GPU and ASIC hardware that bcrypt and PBKDF2 no longer hold back well.&lt;/p&gt;</description></item><item><title>passwordpwned</title><link>https://nurago.org/packages/passwordpwned/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/passwordpwned/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/passwordpwned&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Checks whether a password appears in a known breach, through the &lt;a href="https://haveibeenpwned.com/API/v3#PwnedPasswords"&gt;Have I Been Pwned Pwned Passwords API v3&lt;/a&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;c,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;passwordpwned.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;()&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pwned,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;IsPwnedPassword&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;password)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;if&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;pwned&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;return&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;errors.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;password has been compromised in a data breach, please choose another&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="what-actually-leaves-the-process"&gt;What actually leaves the process&lt;/h2&gt;&#10;&lt;p&gt;This is the question anyone sensible asks before sending passwords to a third party, and the answer is: &lt;strong&gt;five hexadecimal characters&lt;/strong&gt;.&lt;/p&gt;</description></item><item><title>jwt</title><link>https://nurago.org/packages/jwt/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/jwt/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/jwt&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Login, token issue, token verification and route authorisation, against &lt;code&gt;net/http&lt;/code&gt;.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Method&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Purpose&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;LoginHandler&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;validates credentials, issues a token&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;RenewHandler&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;renews a valid token near expiry&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;IsAuthorized&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;validates a bearer token for a protected handler&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;Middleware&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;wraps a handler, injects verified claims into the context&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;Authenticate&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;validates a bearer token, returns claims, for custom middleware&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;IssueToken&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;mints a token outside the HTTP login flow&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;VerifyToken&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;validates a raw token from any transport&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;&lt;code&gt;IssueToken&lt;/code&gt; and &lt;code&gt;VerifyToken&lt;/code&gt; are the ones for non-HTTP paths: a WebSocket message, a queue payload, gRPC metadata.&lt;/p&gt;</description></item><item><title>encrypt</title><link>https://nurago.org/packages/encrypt/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/encrypt/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/encrypt&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;AES-GCM encryption for payloads moving between systems: a database column, a queue message, a cache entry, a cookie.&lt;/p&gt;&#10;&lt;p&gt;Raw byte APIs, plus helpers that serialise a Go value first with gob (&lt;code&gt;ByteEncryptAny&lt;/code&gt;) or JSON (&lt;code&gt;*SerializeAny&lt;/code&gt;).&lt;/p&gt;&#10;&lt;h2 id="authenticated-which-is-the-part-that-matters"&gt;Authenticated, which is the part that matters&lt;/h2&gt;&#10;&lt;p&gt;AES-GCM provides confidentiality &lt;strong&gt;and&lt;/strong&gt; authenticity. Tampering with a ciphertext makes decryption fail rather than producing plausible-looking garbage.&lt;/p&gt;</description></item><item><title>sqlconn</title><link>https://nurago.org/packages/sqlconn/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/sqlconn/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/sqlconn&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Opens a &lt;code&gt;database/sql&lt;/code&gt; pool, applies limits, verifies it works, exposes a health check, and closes it on shutdown.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;c,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;sqlconn.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Connect&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;mysql://user:pass@tcp(host:3306)/db&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;defer&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Shutdown&lt;/span&gt;()&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;New&lt;/code&gt; takes driver and DSN separately; &lt;code&gt;Connect&lt;/code&gt; takes a &lt;code&gt;&amp;lt;DRIVER&amp;gt;://&amp;lt;DSN&amp;gt;&lt;/code&gt; string, with &lt;code&gt;WithDefaultDriver&lt;/code&gt; for a bare DSN.&lt;/p&gt;&#10;&lt;h2 id="the-context-bounds-connection-not-the-pool"&gt;The context bounds connection, not the pool&lt;/h2&gt;&#10;&lt;p&gt;This is the distinction the package documents most carefully, and the one that causes the most confusion.&lt;/p&gt;</description></item><item><title>sqltransaction</title><link>https://nurago.org/packages/sqltransaction/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/sqltransaction/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/sqltransaction&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Runs your function inside a transaction. Return nil and it commits; return an error and it rolls back.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;sqltransaction.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Exec&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;db,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;func&lt;/span&gt;(ctx&lt;span style="color:#6e7681"&gt; &lt;/span&gt;context.Context,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;tx&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;sql.Tx)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;error&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;if&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;_,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;tx.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;ExecContext&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;q1);&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;!=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#79c0ff"&gt;nil&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;return&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;_,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;tx.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;ExecContext&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;q2)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;return&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-control-flow-it-removes"&gt;The control flow it removes&lt;/h2&gt;&#10;&lt;p&gt;Written by hand, a transaction is this:&lt;/p&gt;</description></item><item><title>sqlxtransaction</title><link>https://nurago.org/packages/sqlxtransaction/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/sqlxtransaction/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/sqlxtransaction&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;a href="https://nurago.org/packages/sqltransaction/"&gt;sqltransaction&lt;/a&gt; pattern against &lt;a href="https://github.com/jmoiron/sqlx"&gt;&lt;code&gt;jmoiron/sqlx&lt;/code&gt;&lt;/a&gt;, so the callback receives a &lt;code&gt;*sqlx.Tx&lt;/code&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;sqlxtransaction.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Exec&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;db,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;func&lt;/span&gt;(ctx&lt;span style="color:#6e7681"&gt; &lt;/span&gt;context.Context,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;tx&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;sqlx.Tx)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;error&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;return&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;tx.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;SelectContext&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;users,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;query,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;arg)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="why-a-separate-package"&gt;Why a separate package&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;sqlx.Tx&lt;/code&gt; embeds &lt;code&gt;sql.Tx&lt;/code&gt; but is a distinct type, and its extra methods are the reason to be using sqlx at all. A callback typed &lt;code&gt;func(context.Context, *sql.Tx) error&lt;/code&gt; cannot call &lt;code&gt;SelectContext&lt;/code&gt;, &lt;code&gt;GetContext&lt;/code&gt;, &lt;code&gt;NamedExecContext&lt;/code&gt; or &lt;code&gt;StructScan&lt;/code&gt;, which is most of what sqlx is for.&lt;/p&gt;</description></item><item><title>sqlutil</title><link>https://nurago.org/packages/sqlutil/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/sqlutil/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/sqlutil&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Quotes identifiers and literals when a query has to be assembled rather than parameterised, and builds &lt;code&gt;IN&lt;/code&gt; and &lt;code&gt;NOT IN&lt;/code&gt; clauses from typed slices.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;s&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;sqlutil.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;()&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;s.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;QuoteID&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;myschema.users.name&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// `myschema`.`users`.`name`&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;s.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;BuildInClauseInt64&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;[]&lt;span style="color:#ff7b72"&gt;int64&lt;/span&gt;{&lt;span style="color:#a5d6ff"&gt;1&lt;/span&gt;,&lt;span style="color:#a5d6ff"&gt;2&lt;/span&gt;,&lt;span style="color:#a5d6ff"&gt;3&lt;/span&gt;})&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// id IN (1,2,3)&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="placeholders-cannot-quote-an-identifier"&gt;Placeholders cannot quote an identifier&lt;/h2&gt;&#10;&lt;p&gt;A prepared statement binds values. It cannot bind a table name, a column name, or an &lt;code&gt;ORDER BY&lt;/code&gt; direction, because those are parsed before the plan exists.&lt;/p&gt;</description></item><item><title>mysqllock</title><link>https://nurago.org/packages/mysqllock/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/mysqllock/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/mysqllock&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A named lock shared across processes, built on MySQL&amp;rsquo;s &lt;code&gt;GET_LOCK&lt;/code&gt; and &lt;code&gt;RELEASE_LOCK&lt;/code&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;locker&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;mysqllock.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(db)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;release,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;locker.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Acquire&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;daily-reconciliation&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;10&lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;time.Second)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;if&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;!=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#79c0ff"&gt;nil&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;if&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;errors.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Is&lt;/span&gt;(err,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;mysqllock.ErrTimeout)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;return&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// another instance holds it&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;return&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;defer&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;release&lt;/span&gt;()&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="when-a-database-you-already-have-is-the-right-lock"&gt;When a database you already have is the right lock&lt;/h2&gt;&#10;&lt;p&gt;Running three replicas means a scheduled job runs three times. A cron-style task, a reconciliation, a report, a cleanup sweep: each needs exactly one instance to do the work.&lt;/p&gt;</description></item><item><title>redis</title><link>https://nurago.org/packages/redis/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/redis/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/redis&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Key/value operations, typed payloads and Pub/Sub over &lt;a href="https://github.com/redis/go-redis"&gt;go-redis&lt;/a&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;c,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;redis.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;redis.SrvOptions{Addr:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;localhost:6379&amp;#34;&lt;/span&gt;})&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;defer&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Close&lt;/span&gt;()&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;SetData&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;value,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;time.Hour)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;GetData&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;value)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="typed-payloads"&gt;Typed payloads&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;SetData&lt;/code&gt; and &lt;code&gt;GetData&lt;/code&gt; serialise and deserialise around the raw string operations, so a struct goes in and comes back out without the call site handling encoding.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;DefaultMessageEncodeFunc&lt;/code&gt; and &lt;code&gt;DefaultMessageDecodeFunc&lt;/code&gt; are replaceable, which matters when the data has to be readable by something that is not this service. A shared cache populated by a Go service and read by a Python one needs an encoding both understand, and that choice belongs at construction rather than at each call.&lt;/p&gt;</description></item><item><title>valkey</title><link>https://nurago.org/packages/valkey/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/valkey/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/valkey&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Key/value storage, typed payloads and Pub/Sub over &lt;a href="https://github.com/valkey-io/valkey-go"&gt;valkey-go&lt;/a&gt;, the client for &lt;a href="https://valkey.io"&gt;Valkey&lt;/a&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;c,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;valkey.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;valkey.SrvOptions{InitAddress:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;[]&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;{&lt;span style="color:#a5d6ff"&gt;&amp;#34;localhost:6379&amp;#34;&lt;/span&gt;}})&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;defer&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Close&lt;/span&gt;()&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="why-a-separate-package-from-redis"&gt;Why a separate package from redis&lt;/h2&gt;&#10;&lt;p&gt;Valkey is the Linux Foundation fork of Redis, created after Redis moved off its BSD licence in 2024. It remains protocol-compatible, so most operations behave identically.&lt;/p&gt;&#10;&lt;p&gt;The clients are not the same library, though. &lt;code&gt;valkey-go&lt;/code&gt; is a different implementation with a different option type and a different command-building model, built around client-side caching and pipelining. Wrapping both behind one type would mean either exposing the lowest common denominator or leaking whichever client was underneath.&lt;/p&gt;</description></item><item><title>kafka</title><link>https://nurago.org/packages/kafka/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/kafka/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/kafka&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Producer and consumer for Apache Kafka, built on &lt;a href="https://github.com/segmentio/kafka-go"&gt;segmentio/kafka-go&lt;/a&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;p,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;kafka.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;NewProducer&lt;/span&gt;(urls,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;topic)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;p.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;SendData&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;key,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;payload)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;c,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;kafka.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;NewConsumer&lt;/span&gt;(urls,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;topic,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;groupID)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;msg,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Receive&lt;/span&gt;(ctx)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="pure-go-no-cgo"&gt;Pure Go, no CGO&lt;/h2&gt;&#10;&lt;p&gt;No &lt;code&gt;librdkafka&lt;/code&gt; to install, no CGO, so &lt;code&gt;CGO_ENABLED=0&lt;/code&gt; still builds.&lt;/p&gt;&#10;&lt;p&gt;The practical consequences are all about deployment. A statically linked binary runs in a &lt;code&gt;scratch&lt;/code&gt; or &lt;code&gt;distroless&lt;/code&gt; container with nothing else in it. Cross-compiling for another platform is &lt;code&gt;GOOS=linux GOARCH=arm64 go build&lt;/code&gt; rather than a toolchain and a C library for the target. And &lt;code&gt;go test&lt;/code&gt; needs no system package installed on the machine or in CI.&lt;/p&gt;</description></item><item><title>sqs</title><link>https://nurago.org/packages/sqs/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/sqs/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/sqs&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Send, receive, decode and delete against an SQS queue, on &lt;code&gt;aws-sdk-go-v2&lt;/code&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;c,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;sqs.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;queueURL,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;SendData&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;payload)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;msg,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Receive&lt;/span&gt;(ctx)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Delete&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;msg.ReceiptHandle)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="fifo-constraints-are-checked-at-construction"&gt;FIFO constraints are checked at construction&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;New&lt;/code&gt; takes a queue URL and a message-group ID, and validates the combination &lt;strong&gt;before any AWS configuration is loaded&lt;/strong&gt;:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;The queue URL must be a valid absolute URL.&lt;/li&gt;&#10;&lt;li&gt;A FIFO queue, whose URL ends in &lt;code&gt;.fifo&lt;/code&gt;, &lt;strong&gt;requires&lt;/strong&gt; a message-group ID.&lt;/li&gt;&#10;&lt;li&gt;A standard queue &lt;strong&gt;requires&lt;/strong&gt; the group ID to be empty; a non-empty value is rejected with &lt;code&gt;ErrUnexpectedMessageGroupID&lt;/code&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Validating first means a misconfiguration fails immediately with a specific error, rather than surfacing later as an AWS API rejection whose message does not name the actual problem.&lt;/p&gt;</description></item><item><title>s3</title><link>https://nurago.org/packages/s3/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/s3/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/s3&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Object operations against one bucket.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;c,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;s3.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;my-bucket&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Put&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;path/to/key&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;reader)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;obj,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Get&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;path/to/key&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;keys,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;ListKeys&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;prefix/&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Delete&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;path/to/key&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="bucket-scoped-and-streaming"&gt;Bucket-scoped, and streaming&lt;/h2&gt;&#10;&lt;p&gt;The &lt;code&gt;Client&lt;/code&gt; is bound to a bucket at construction, so the bucket name is not repeated at every call site and cannot be got wrong on one of them. A service using two buckets constructs two clients, which also keeps their configurations independently visible.&lt;/p&gt;</description></item><item><title>awsopt</title><link>https://nurago.org/packages/awsopt/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/awsopt/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/awsopt&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Accumulates &lt;code&gt;aws-sdk-go-v2&lt;/code&gt; load options into a reusable slice, then materialises them into an &lt;code&gt;aws.Config&lt;/code&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;var&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;opts&lt;span style="color:#6e7681"&gt; &lt;/span&gt;awsopt.Options&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;opts.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;WithRegion&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;eu-west-1&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cfg,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;opts.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;LoadDefaultConfig&lt;/span&gt;(ctx)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The zero value is ready to use.&lt;/p&gt;&#10;&lt;h2 id="one-configuration-several-clients"&gt;One configuration, several clients&lt;/h2&gt;&#10;&lt;p&gt;A service using S3, SQS and Secrets Manager constructs three SDK clients. Each needs region, credentials, endpoint resolution and retry behaviour, and the SDK&amp;rsquo;s own pattern is to pass load options at each construction site.&lt;/p&gt;</description></item><item><title>awssecretcache</title><link>https://nurago.org/packages/awssecretcache/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/awssecretcache/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/awssecretcache&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Wraps an &lt;code&gt;aws-sdk-go-v2&lt;/code&gt; Secrets Manager client in an &lt;a href="https://nurago.org/packages/sfcache/"&gt;sfcache&lt;/a&gt;, so repeated lookups of the same secret hit memory rather than the API.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cache,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;awssecretcache.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;ctx,&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;128&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// maximum cached secrets&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;5&lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;time.Minute,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// TTL per entry&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="three-reasons-the-cache-is-not-optional"&gt;Three reasons the cache is not optional&lt;/h2&gt;&#10;&lt;p&gt;Secrets Manager &lt;code&gt;GetSecretValue&lt;/code&gt; is metered, rate limited, and on the network.&lt;/p&gt;</description></item><item><title>sfcache</title><link>https://nurago.org/packages/sfcache/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/sfcache/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/sfcache&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Caches the result of an expensive lookup, collapses concurrent requests for the same key into one call, and can keep serving the last good value while the upstream is down.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cache&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;sfcache.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(&lt;span style="color:#ff7b72"&gt;func&lt;/span&gt;(ctx&lt;span style="color:#6e7681"&gt; &lt;/span&gt;context.Context,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;key&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;(&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;Customer,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;error&lt;/span&gt;)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;return&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;fetchCustomer&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;key)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;},&lt;span style="color:#6e7681"&gt; &lt;/span&gt;sfcache.Config{Size:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;256&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;TTL:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;5&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;time.Minute})&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;customer,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;cache.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Lookup&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;customer:123&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The value type is inferred from the lookup function, so &lt;code&gt;Lookup&lt;/code&gt; returns &lt;code&gt;*Customer&lt;/code&gt; with no type assertion.&lt;/p&gt;</description></item><item><title>enumcache</title><link>https://nurago.org/packages/enumcache/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/enumcache/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/enumcache&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A two-way map between small integer IDs and names, held in memory and safe for concurrent reads.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ec&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;enumcache.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;()&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ec.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;SetAllNameByID&lt;/span&gt;(enumcache.NameByID{&lt;span style="color:#a5d6ff"&gt;1&lt;/span&gt;:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;ACTIVE&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;2&lt;/span&gt;:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;SUSPENDED&amp;#34;&lt;/span&gt;})&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;id,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;ec.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;ID&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;ACTIVE&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// 1&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;name,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;ec.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Name&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;2&lt;/span&gt;)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// &amp;#34;SUSPENDED&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="what-it-replaces"&gt;What it replaces&lt;/h2&gt;&#10;&lt;p&gt;Reference data is the least interesting part of a service and one of the most repeated. A &lt;code&gt;status&lt;/code&gt; table with twenty rows, a &lt;code&gt;country&lt;/code&gt; table, a &lt;code&gt;channel&lt;/code&gt; table: the database owns the values, the code needs them in both directions, and the lookup happens on every request.&lt;/p&gt;</description></item><item><title>enumdb</title><link>https://nurago.org/packages/enumdb/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/enumdb/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/enumdb&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Reads reference tables into memory at startup, producing populated &lt;a href="https://nurago.org/packages/enumcache/"&gt;enumcache&lt;/a&gt; instances.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;db,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;enumdb.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;sqlDB,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;queries)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;One call, at startup, after which every lookup is a map read.&lt;/p&gt;&#10;&lt;h2 id="the-table-shape-it-expects"&gt;The table shape it expects&lt;/h2&gt;&#10;&lt;p&gt;Rows scannable into &lt;code&gt;(id int, name string)&lt;/code&gt;. In practice a numeric primary key called &lt;code&gt;id&lt;/code&gt; and a unique string column called &lt;code&gt;name&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;CREATE&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;TABLE&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;IF&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;NOT&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;EXISTS&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;`&lt;/span&gt;example&lt;span style="color:#ff7b72;font-weight:bold"&gt;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;(&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;`&lt;/span&gt;id&lt;span style="color:#ff7b72;font-weight:bold"&gt;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;SMALLINT&lt;span style="color:#6e7681"&gt; &lt;/span&gt;UNSIGNED&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;NOT&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;NULL&lt;/span&gt;,&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;`&lt;/span&gt;name&lt;span style="color:#ff7b72;font-weight:bold"&gt;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;VARCHAR(&lt;span style="color:#a5d6ff"&gt;50&lt;/span&gt;)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;NOT&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;NULL&lt;/span&gt;,&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;`&lt;/span&gt;disabled&lt;span style="color:#ff7b72;font-weight:bold"&gt;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;TINYINT&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;NOT&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;NULL&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;DEFAULT&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;0&lt;/span&gt;,&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;PRIMARY&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;KEY&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;(&lt;span style="color:#ff7b72;font-weight:bold"&gt;`&lt;/span&gt;id&lt;span style="color:#ff7b72;font-weight:bold"&gt;`&lt;/span&gt;),&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;UNIQUE&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;INDEX&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;`&lt;/span&gt;id_UNIQUE&lt;span style="color:#ff7b72;font-weight:bold"&gt;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;(&lt;span style="color:#ff7b72;font-weight:bold"&gt;`&lt;/span&gt;id&lt;span style="color:#ff7b72;font-weight:bold"&gt;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;ASC&lt;/span&gt;),&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;UNIQUE&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;INDEX&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;`&lt;/span&gt;name_UNIQUE&lt;span style="color:#ff7b72;font-weight:bold"&gt;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;(&lt;span style="color:#ff7b72;font-weight:bold"&gt;`&lt;/span&gt;name&lt;span style="color:#ff7b72;font-weight:bold"&gt;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;ASC&lt;/span&gt;))&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ENGINE&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;InnoDB;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Both uniqueness constraints matter, because the cache is bidirectional. A duplicate name makes the name-to-ID direction ambiguous, and the database is the only place that can enforce it.&lt;/p&gt;</description></item><item><title>enumbitmap</title><link>https://nurago.org/packages/enumbitmap/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/enumbitmap/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/enumbitmap&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Two functions. &lt;code&gt;StringsToBitMap&lt;/code&gt; packs a set of names into one integer; &lt;code&gt;BitMapToStrings&lt;/code&gt; unpacks it.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;00000001 = 1 = FIRST&#10;00000010 = 2 = SECOND&#10;00000100 = 4 = THIRD&#10;00001000 = 8 = FOURTH&#10;&#10;00001001 = 9 = FIRST + FOURTH&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="when-one-integer-beats-a-join-table"&gt;When one integer beats a join table&lt;/h2&gt;&#10;&lt;p&gt;A set of flags on a record has two standard storage shapes. A separate table with a row per flag per record, which is correct and normalised and costs a join on every read. Or a single integer column, which is denormalised and costs nothing.&lt;/p&gt;</description></item><item><title>countrycode</title><link>https://nurago.org/packages/countrycode/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/countrycode/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/countrycode&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;ISO-3166 country records, compiled in, queryable by code or by geography.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;d,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;countrycode.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(&lt;span style="color:#79c0ff"&gt;nil&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;c,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;d.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;CountryByAlpha2Code&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;IT&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="what-a-record-holds"&gt;What a record holds&lt;/h2&gt;&#10;&lt;p&gt;Each &lt;code&gt;CountryData&lt;/code&gt; carries all three ISO-3166 code forms (alpha-2, alpha-3, numeric), English and French names, the full UN M49 geographic hierarchy (region, sub-region, intermediate region, each with both name and code), an assignment status, and the country-code top-level domain.&lt;/p&gt;</description></item><item><title>countryphone</title><link>https://nurago.org/packages/countryphone/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/countryphone/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/countryphone&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Give it a number or a prefix, get back the country and the numbering-plan metadata that prefix belongs to. It follows the ITU-T &lt;a href="https://www.itu.int/rec/T-REC-E.123/en"&gt;E.123&lt;/a&gt; and &lt;a href="https://www.itu.int/rec/T-REC-E.164/en"&gt;E.164&lt;/a&gt; recommendations.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;d&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;countryphone.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(&lt;span style="color:#79c0ff"&gt;nil&lt;/span&gt;)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// embedded dataset&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;info,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;d.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;NumberInfo&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;442071234567&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="longest-prefix-because-prefixes-overlap"&gt;Longest-prefix, because prefixes overlap&lt;/h2&gt;&#10;&lt;p&gt;A hash map keyed on the calling code does not work for this, and the reason is that E.164 prefixes are not a flat namespace.&lt;/p&gt;</description></item><item><title>phonekeypad</title><link>https://nurago.org/packages/phonekeypad/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/phonekeypad/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/phonekeypad&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Turns &lt;code&gt;1-800-FLOWERS&lt;/code&gt; into &lt;code&gt;18003569377&lt;/code&gt;, following the &lt;a href="https://www.itu.int/rec/T-REC-E.161/en"&gt;ITU E.161&lt;/a&gt; keypad layout.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;+-----+-----+-----+&#10;| 1 | 2 | 3 |&#10;| | ABC | DEF |&#10;+-----+-----+-----+&#10;| 4 | 5 | 6 |&#10;| GHI | JKL | MNO |&#10;+-----+-----+-----+&#10;| 7 | 8 | 9 |&#10;| PQRS| TUV | WXYZ|&#10;+-----+-----+-----+&#10;| | 0 | |&#10;+-----+-----+-----+&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="why-this-is-a-package-and-not-four-lines"&gt;Why this is a package and not four lines&lt;/h2&gt;&#10;&lt;p&gt;The mapping is trivial. The edge cases are where inline implementations diverge from each other.&lt;/p&gt;</description></item><item><title>encode</title><link>https://nurago.org/packages/encode/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/encode/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/encode&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Turns a Go value into a string safe to put in a database column, a queue message, or a cache key, and turns it back.&lt;/p&gt;&#10;&lt;p&gt;Two families do that here. &lt;code&gt;Encode&lt;/code&gt;/&lt;code&gt;Decode&lt;/code&gt; use &lt;code&gt;encoding/gob&lt;/code&gt; wrapped in Base64. &lt;code&gt;Serialize&lt;/code&gt;/&lt;code&gt;Deserialize&lt;/code&gt; use &lt;code&gt;encoding/json&lt;/code&gt; wrapped in Base64. There are also the bare &lt;code&gt;Base64EncodeString&lt;/code&gt;, &lt;code&gt;Base64DecodeString&lt;/code&gt;, and streaming &lt;code&gt;Base64Encoder&lt;/code&gt;/&lt;code&gt;Base64Decoder&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h2 id="measure-before-assuming-gob-is-faster"&gt;Measure before assuming gob is faster&lt;/h2&gt;&#10;&lt;p&gt;Gob has a reputation as the fast binary option and JSON as the slow interoperable one. On this package&amp;rsquo;s own benchmarks, over the same value, that is backwards:&lt;/p&gt;</description></item><item><title>decint</title><link>https://nurago.org/packages/decint/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/decint/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/decint&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Stores a decimal with up to six fractional digits as an &lt;code&gt;int64&lt;/code&gt; scaled by 1e6, and converts to and from &lt;code&gt;float64&lt;/code&gt; and &lt;code&gt;string&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h2 id="the-problem-it-refuses-to-have"&gt;The problem it refuses to have&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;0.1 + 0.2 != 0.3&lt;/code&gt; in binary floating point, and every language with IEEE 754 doubles inherits it. For a rendering coordinate nobody cares. For money, a quantity that must reconcile, or anything that gets compared for equality after a round trip through a database and a JSON payload, the accumulated error is a defect.&lt;/p&gt;</description></item><item><title>timeutil</title><link>https://nurago.org/packages/timeutil/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/timeutil/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/timeutil&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Two types that serialise to human-readable strings where the standard library does not: &lt;code&gt;DateTime[T]&lt;/code&gt; for timestamps in a layout chosen at compile time, and &lt;code&gt;Duration&lt;/code&gt; for &lt;code&gt;&amp;quot;1h30m&amp;quot;&lt;/code&gt; instead of a nanosecond integer.&lt;/p&gt;&#10;&lt;h2 id="the-two-mismatches"&gt;The two mismatches&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;time.Duration&lt;/code&gt; marshals to JSON as a bare nanosecond integer. A thirty-minute timeout leaves your service as &lt;code&gt;1800000000000&lt;/code&gt;, which no human reads correctly and no other language parses back into a duration without knowing the unit. &lt;code&gt;timeutil.Duration&lt;/code&gt; marshals as &lt;code&gt;&amp;quot;30m0s&amp;quot;&lt;/code&gt;, the same string &lt;code&gt;time.Duration.String()&lt;/code&gt; produces and &lt;code&gt;time.ParseDuration&lt;/code&gt; accepts.&lt;/p&gt;</description></item><item><title>uhex</title><link>https://nurago.org/packages/uhex/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/uhex/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/uhex&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Encodes unsigned integers and fixed-size byte arrays as zero-padded lowercase hexadecimal, at widths known at compile time.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;id&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;uhex.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Hex64&lt;/span&gt;(traceID)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// 16 bytes&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sum&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;uhex.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Hex32&lt;/span&gt;(checksum)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// 8 bytes&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;var&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;dst&lt;span style="color:#6e7681"&gt; &lt;/span&gt;[&lt;span style="color:#a5d6ff"&gt;16&lt;/span&gt;]&lt;span style="color:#ff7b72"&gt;byte&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;uhex.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Hex64UB&lt;/span&gt;(traceID,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;dst)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// no allocation&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-naming-pattern"&gt;The naming pattern&lt;/h2&gt;&#10;&lt;p&gt;Four suffixes, orthogonal along two axes:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Suffix&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Input&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Output&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;none&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;unsigned integer&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;returns &lt;code&gt;[]byte&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;UB&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;unsigned integer&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;writes into a caller buffer&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;B&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;fixed-size byte array&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;returns &lt;code&gt;[]byte&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;BB&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;fixed-size byte array&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;writes into a caller buffer&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Integer helpers always zero-pad to the full width of the type: &lt;code&gt;Hex64&lt;/code&gt; gives 16 characters, &lt;code&gt;Hex32&lt;/code&gt; 8, &lt;code&gt;Hex16&lt;/code&gt; 4, &lt;code&gt;Hex8&lt;/code&gt; 2. Byte-array helpers encode each input byte in order, two characters per byte.&lt;/p&gt;</description></item><item><title>stringkey</title><link>https://nurago.org/packages/stringkey/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/stringkey/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/stringkey&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Takes several text fields and produces one &lt;code&gt;uint64&lt;/code&gt; that is the same every time for inputs that differ only in ways you do not care about.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sk&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;stringkey.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34; John Doe &amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;LONDON&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sk.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Key&lt;/span&gt;()&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// uint64&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sk.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Hex&lt;/span&gt;()&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// fixed 16-character hex&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sk.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;String&lt;/span&gt;()&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// base-36, shorter and variable length&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-pipeline-is-the-product"&gt;The pipeline is the product&lt;/h2&gt;&#10;&lt;p&gt;The hash is the boring part. What makes this a package rather than a call to a hash function is the six-step normalisation in front of it, applied identically every time:&lt;/p&gt;</description></item><item><title>strsplit</title><link>https://nurago.org/packages/strsplit/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/strsplit/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/strsplit&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;ChunkLine&lt;/code&gt; splits one line into pieces of at most &lt;code&gt;size&lt;/code&gt; bytes. &lt;code&gt;Chunk&lt;/code&gt; splits a block of text, treating newlines as the first-choice boundary and handing long lines to &lt;code&gt;ChunkLine&lt;/code&gt;. Both take an optional chunk limit.&lt;/p&gt;&#10;&lt;p&gt;The use case is anything with a hard payload ceiling: an SMS segment, a log field, a message body, a column width, an embedding window.&lt;/p&gt;</description></item><item><title>stringmetric</title><link>https://nurago.org/packages/stringmetric/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/stringmetric/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/stringmetric&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;One function. &lt;code&gt;DLDistance&lt;/code&gt; returns the Damerau-Levenshtein edit distance between two strings: the fewest insertions, deletions, substitutions and adjacent transpositions needed to turn one into the other.&lt;/p&gt;&#10;&lt;h2 id="why-damerau-and-not-plain-levenshtein"&gt;Why Damerau and not plain Levenshtein&lt;/h2&gt;&#10;&lt;p&gt;Transposition is the fourth operation. Plain Levenshtein scores &lt;code&gt;teh&lt;/code&gt; against &lt;code&gt;the&lt;/code&gt; as distance 2, reading it as a deletion plus an insertion. Damerau-Levenshtein scores it 1, because swapping two adjacent characters counts as one edit.&lt;/p&gt;</description></item><item><title>random</title><link>https://nurago.org/packages/random/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/random/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/random&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Random bytes, unsigned integers, UUIDv7, 64- and 128-bit UIDs, hexadecimal and base-36 IDs, and configurable random strings.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;r&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;random.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(&lt;span style="color:#79c0ff"&gt;nil&lt;/span&gt;)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// crypto/rand.Reader&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;id&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;r.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;UUIDv7&lt;/span&gt;()&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;s,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;r.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;RandString&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;16&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-fallback-stated-plainly"&gt;The fallback, stated plainly&lt;/h2&gt;&#10;&lt;p&gt;This is the part of the package that deserves the most attention, and the doc comment does not soften it.&lt;/p&gt;&#10;&lt;p&gt;Some helpers have error-free signatures: &lt;code&gt;RandUint32&lt;/code&gt;, &lt;code&gt;RandUint64&lt;/code&gt;, &lt;code&gt;UUIDv7&lt;/code&gt;, and the &lt;code&gt;RandHex64&lt;/code&gt;, &lt;code&gt;RandString64&lt;/code&gt;, &lt;code&gt;UID64&lt;/code&gt; and &lt;code&gt;UID128&lt;/code&gt; built on them. They cannot return an error, so when the reader fails they &lt;strong&gt;fall back to &lt;code&gt;math/rand/v2&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;</description></item><item><title>maputil</title><link>https://nurago.org/packages/maputil/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/maputil/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/maputil&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Four functions over Go maps: &lt;code&gt;Filter&lt;/code&gt;, &lt;code&gt;Map&lt;/code&gt;, &lt;code&gt;Reduce&lt;/code&gt;, &lt;code&gt;Invert&lt;/code&gt;. None of them mutate the input.&lt;/p&gt;&#10;&lt;h2 id="the-gap-this-fills"&gt;The gap this fills&lt;/h2&gt;&#10;&lt;p&gt;Go&amp;rsquo;s standard &lt;a href="https://pkg.go.dev/maps"&gt;&lt;code&gt;maps&lt;/code&gt;&lt;/a&gt; package covers copying and comparison (&lt;code&gt;Clone&lt;/code&gt;, &lt;code&gt;Copy&lt;/code&gt;, &lt;code&gt;DeleteFunc&lt;/code&gt;, &lt;code&gt;Equal&lt;/code&gt;, &lt;code&gt;EqualFunc&lt;/code&gt;) and, since the iterator work landed, traversal (&lt;code&gt;All&lt;/code&gt;, &lt;code&gt;Keys&lt;/code&gt;, &lt;code&gt;Values&lt;/code&gt;, &lt;code&gt;Insert&lt;/code&gt;, &lt;code&gt;Collect&lt;/code&gt;). It stops short of the transforms. There is no &lt;code&gt;maps.Filter&lt;/code&gt;, no &lt;code&gt;maps.Map&lt;/code&gt;, no &lt;code&gt;maps.Reduce&lt;/code&gt;, and no inverse.&lt;/p&gt;</description></item><item><title>sliceutil</title><link>https://nurago.org/packages/sliceutil/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/sliceutil/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/sliceutil&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Filter, map and reduce over slices, plus &lt;code&gt;Stats&lt;/code&gt;, which summarises a numeric slice with seventeen descriptive statistics in one call.&lt;/p&gt;&#10;&lt;h2 id="stats"&gt;Stats&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;Stats&lt;/code&gt; takes a numeric slice and returns seventeen descriptive statistics:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;data&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;[]&lt;span style="color:#ff7b72"&gt;int&lt;/span&gt;{&lt;span style="color:#a5d6ff"&gt;53&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;83&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;13&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;79&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;13&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;37&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;83&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;29&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;37&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;13&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;83&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;83&lt;/span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ds,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;sliceutil.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Stats&lt;/span&gt;(data)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// ds.Count, ds.Sum, ds.Min, ds.MinID, ds.Max, ds.MaxID, ds.Range,&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// ds.Mean, ds.Median, ds.Mode, ds.ModeFreq, ds.MeanDev, ds.Variance,&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// ds.StdDev, ds.Skewness, ds.ExKurtosis, ds.Entropy&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Most of that list is routine. Four entries are less common in a Go statistics helper:&lt;/p&gt;</description></item><item><title>numtrie</title><link>https://nurago.org/packages/numtrie/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/numtrie/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/numtrie&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A prefix tree indexed by digits, holding a value of any type, answering longest-prefix queries.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;root&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;numtrie.New[Route]()&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;root.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Add&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;defaultUSRoute)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;root.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Add&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;1212&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;newYorkRoute)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;root.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Add&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;44&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;ukRoute)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;root.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Add&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;44207&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;londonRoute)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;val,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;status&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;root.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Get&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;+1-212-555-0100&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// val == &amp;amp;newYorkRoute, the longest matching prefix &amp;#34;1212&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="longest-prefix-which-a-map-cannot-do"&gt;Longest-prefix, which a map cannot do&lt;/h2&gt;&#10;&lt;p&gt;A &lt;code&gt;map[string]Route&lt;/code&gt; answers exact-match questions. Numbering plans are not exact-match problems.&lt;/p&gt;</description></item><item><title>paging</title><link>https://nurago.org/packages/paging/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/paging/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/paging&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Computes pagination metadata from a current page, a page size and a total item count: total pages, previous and next page, and the &lt;code&gt;OFFSET&lt;/code&gt;/&lt;code&gt;LIMIT&lt;/code&gt; pair for a SQL query.&lt;/p&gt;&#10;&lt;p&gt;Three numbers in, a filled-in struct out:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;p&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;paging.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;3&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;5&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;17&lt;/span&gt;)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// page 3, 5 per page, 17 items total&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// p.TotalPages == 4&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// p.PreviousPage == 2&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// p.NextPage == 4&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// p.Offset == 10&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// p.HasPreviousPage == true&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// p.HasNextPage == true&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The arithmetic is three lines. The edge cases are where the implementations differ.&lt;/p&gt;</description></item><item><title>filter</title><link>https://nurago.org/packages/filter/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/filter/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/filter&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Filters a slice against structured rules supplied by the caller, or parsed from JSON or a query parameter.&lt;/p&gt;&#10;&lt;p&gt;Rules are &lt;code&gt;[][]Rule&lt;/code&gt;, with the outer slice AND and the inner slice OR:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;[[A], [B, C], [D]] evaluates as A AND (B OR C) AND D&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="built-for-untrusted-rules"&gt;Built for untrusted rules&lt;/h2&gt;&#10;&lt;p&gt;A filter expression that arrives from a client is code someone else wrote, running in your process. The package treats it that way, and the bounds are the substance of the design.&lt;/p&gt;</description></item><item><title>threadsafe</title><link>https://nurago.org/packages/threadsafe/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/threadsafe/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/threadsafe&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Two interfaces and no implementation. &lt;code&gt;Locker&lt;/code&gt; is &lt;code&gt;Lock&lt;/code&gt;/&lt;code&gt;Unlock&lt;/code&gt;; &lt;code&gt;RLocker&lt;/code&gt; is &lt;code&gt;RLock&lt;/code&gt;/&lt;code&gt;RUnlock&lt;/code&gt;. That is the entire package.&lt;/p&gt;&#10;&lt;h2 id="why-an-interface-and-not-a-mutex"&gt;Why an interface and not a mutex&lt;/h2&gt;&#10;&lt;p&gt;A concurrent container normally embeds its lock:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;type&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Cache&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;struct&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;mu&lt;span style="color:#6e7681"&gt; &lt;/span&gt;sync.RWMutex&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;m&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;map&lt;/span&gt;[&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;]entry&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The lock type is then fixed for every caller. None of them can supply an existing lock that already guards a wider critical section, substitute an instrumented lock that records contention, or drop to a plain &lt;code&gt;sync.Mutex&lt;/code&gt; where the read/write split costs more than it saves.&lt;/p&gt;</description></item><item><title>tsmap</title><link>https://nurago.org/packages/tsmap/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/tsmap/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/threadsafe/tsmap&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Concurrent access to a shared map: read, write, delete and transform, each taking a caller-supplied lock so the access is serialised.&lt;/p&gt;&#10;&lt;p&gt;Every function takes the lock as its first argument:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;var&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;(&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;mu&lt;span style="color:#6e7681"&gt; &lt;/span&gt;sync.RWMutex&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;m&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;map&lt;/span&gt;[&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;]&lt;span style="color:#ff7b72"&gt;int&lt;/span&gt;{&lt;span style="color:#a5d6ff"&gt;&amp;#34;a&amp;#34;&lt;/span&gt;:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;1&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;b&amp;#34;&lt;/span&gt;:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;2&lt;/span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;tsmap.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Set&lt;/span&gt;(&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;mu,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;m,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;c&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;3&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;v,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;ok&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;tsmap.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;GetOK&lt;/span&gt;(&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;mu,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;m,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;a&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Writes (&lt;code&gt;Set&lt;/code&gt;, &lt;code&gt;Delete&lt;/code&gt;, &lt;code&gt;Do&lt;/code&gt;) want a &lt;a href="https://nurago.org/packages/threadsafe/"&gt;&lt;code&gt;threadsafe.Locker&lt;/code&gt;&lt;/a&gt;; reads and pure transforms (&lt;code&gt;Get&lt;/code&gt;, &lt;code&gt;GetOK&lt;/code&gt;, &lt;code&gt;Len&lt;/code&gt;, &lt;code&gt;Snapshot&lt;/code&gt;, &lt;code&gt;Filter&lt;/code&gt;, &lt;code&gt;Map&lt;/code&gt;, &lt;code&gt;Reduce&lt;/code&gt;, &lt;code&gt;Invert&lt;/code&gt;, &lt;code&gt;RDo&lt;/code&gt;) want an &lt;code&gt;RLocker&lt;/code&gt; and take the read lock.&lt;/p&gt;</description></item><item><title>tsslice</title><link>https://nurago.org/packages/tsslice/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/tsslice/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/threadsafe/tsslice&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Concurrent access to a shared slice: read, write, append, delete and transform, each taking a lock so the access is serialised.&lt;/p&gt;&#10;&lt;p&gt;This package exposes two APIs where its sibling &lt;a href="https://nurago.org/packages/tsmap/"&gt;tsmap&lt;/a&gt; exposes one.&lt;/p&gt;&#10;&lt;h2 id="two-apis-one-of-them-safer"&gt;Two APIs, one of them safer&lt;/h2&gt;&#10;&lt;p&gt;The function API mirrors tsmap: pass a &lt;a href="https://nurago.org/packages/threadsafe/"&gt;&lt;code&gt;threadsafe.Locker&lt;/code&gt;&lt;/a&gt; or &lt;code&gt;RLocker&lt;/code&gt; at each call.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;var&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;(&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;mu&lt;span style="color:#6e7681"&gt; &lt;/span&gt;sync.RWMutex&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;s&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;[]&lt;span style="color:#ff7b72"&gt;int&lt;/span&gt;{&lt;span style="color:#a5d6ff"&gt;1&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;2&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;3&lt;/span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;tsslice.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Append&lt;/span&gt;(&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;mu,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;s,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;4&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;v,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;ok&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;tsslice.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;GetOK&lt;/span&gt;(&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;mu,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;s,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;0&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;Guarded[E]&lt;/code&gt; bundles the slice with its own &lt;code&gt;sync.RWMutex&lt;/code&gt; instead:&lt;/p&gt;</description></item><item><title>errutil</title><link>https://nurago.org/packages/errutil/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/errutil/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/errutil&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Three functions, each answering a question the standard &lt;code&gt;errors&lt;/code&gt; package leaves open: where did this error come from, how do I report a cleanup failure without losing the real one, and what is actually inside this joined error.&lt;/p&gt;&#10;&lt;h2 id="trace-and-what-caller-capture-costs"&gt;Trace, and what caller capture costs&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;Trace&lt;/code&gt; wraps an error with the file, line and function of its call site, using &lt;code&gt;%w&lt;/code&gt; so &lt;code&gt;errors.Is&lt;/code&gt; and &lt;code&gt;errors.As&lt;/code&gt; still see through it.&lt;/p&gt;</description></item><item><title>typeutil</title><link>https://nurago.org/packages/typeutil/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/typeutil/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/typeutil&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nil detection through an interface, zero values for a type parameter, safe pointer dereference, and branch-free bool-to-integer conversion, plus a set of numeric type constraints.&lt;/p&gt;&#10;&lt;p&gt;Four of the five helpers are conveniences. &lt;code&gt;IsNil&lt;/code&gt; exists because of a Go language trap.&lt;/p&gt;&#10;&lt;h2 id="the-typed-nil"&gt;The typed nil&lt;/h2&gt;&#10;&lt;p&gt;The trap:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;var&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;p&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;MyStruct&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// nil pointer&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;var&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;i&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;any&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;p&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// non-nil interface holding a nil pointer&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;i&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;==&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#79c0ff"&gt;nil&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// false&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;typeutil.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;IsNil&lt;/span&gt;(i)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// true&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;An interface value carries two words, a type and a pointer. Assigning a nil &lt;code&gt;*MyStruct&lt;/code&gt; to an &lt;code&gt;any&lt;/code&gt; fills in the type word, so the interface is no longer nil even though what it points at is. The comparison &lt;code&gt;i == nil&lt;/code&gt; asks about the interface, not about the pointer inside it.&lt;/p&gt;</description></item><item><title>validator</title><link>https://nurago.org/packages/validator/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/validator/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/validator&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Struct-tag validation on &lt;a href="https://github.com/go-playground/validator"&gt;go-playground/validator&lt;/a&gt;, with the error handling and message rendering that library leaves to the caller.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;v,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;validator.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;()&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;v.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;ValidateStruct&lt;/span&gt;(req)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="errors-come-back-joined-and-typed"&gt;Errors come back joined and typed&lt;/h2&gt;&#10;&lt;p&gt;Upstream returns &lt;code&gt;vt.ValidationErrors&lt;/code&gt;, a slice you type-assert and loop over, formatting each entry yourself. Every service that uses it writes that loop, and they all format differently.&lt;/p&gt;</description></item><item><title>slack</title><link>https://nurago.org/packages/slack/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/slack/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/slack&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Posts text to a Slack &lt;a href="https://api.slack.com/messaging/webhooks"&gt;Incoming Webhook&lt;/a&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;c,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;slack.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(webhookURL,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;my-bot&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;:rocket:&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;#alerts&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Send&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;deploy finished&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-webhook-url-is-a-credential-and-it-appears-in-error-messages"&gt;The webhook URL is a credential, and it appears in error messages&lt;/h2&gt;&#10;&lt;p&gt;This is the part that separates the package from an inline &lt;code&gt;http.Post&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;A Slack Incoming Webhook URL contains the secret. Anyone holding &lt;code&gt;https://hooks.slack.com/services/T00.../B00.../XXXX&lt;/code&gt; can post to that channel; there is no separate token.&lt;/p&gt;</description></item><item><title>jirasrv</title><link>https://nurago.org/packages/jirasrv/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/jirasrv/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/jirasrv&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A client foundation for the Jira Server REST API, with typed models for the common payloads: issues, transitions, comments, users, visibility, properties and metadata.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;c,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;jirasrv.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(addr,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;token)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;SendRequest&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;method,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;path,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;body,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;result)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="a-foundation-not-a-wrapper-for-every-endpoint"&gt;A foundation, not a wrapper for every endpoint&lt;/h2&gt;&#10;&lt;p&gt;The Jira REST API is large, and versions differ between deployments. Rather than modelling every endpoint, the package handles the plumbing and exposes &lt;code&gt;SendRequest&lt;/code&gt; as a generic typed entry point.&lt;/p&gt;</description></item><item><title>devlake</title><link>https://nurago.org/packages/devlake/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/devlake/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/devlake&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A client for the &lt;a href="https://devlake.apache.org/docs/Plugins/webhook/"&gt;Apache DevLake&lt;/a&gt; Webhook API, covering the deployment and incident events that feed DORA metrics.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;c,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;devlake.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(addr,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;apiKey)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;SendDeployment&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;devlake.DeploymentRequest{&lt;span style="color:#ff7b72;font-weight:bold"&gt;...&lt;/span&gt;})&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;SendIncident&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;devlake.IncidentRequest{&lt;span style="color:#ff7b72;font-weight:bold"&gt;...&lt;/span&gt;})&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;SendIncidentClose&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;devlake.IncidentRequestClose{&lt;span style="color:#ff7b72;font-weight:bold"&gt;...&lt;/span&gt;})&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="what-devlake-wants-and-why-the-events-are-split"&gt;What DevLake wants and why the events are split&lt;/h2&gt;&#10;&lt;p&gt;DevLake computes the four DORA metrics: deployment frequency, lead time for changes, change failure rate, and time to restore service. Two of the four are durations, which is why incidents are reported twice.&lt;/p&gt;</description></item><item><title>sleuth</title><link>https://nurago.org/packages/sleuth/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/sleuth/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/sleuth&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Write-side client for the &lt;a href="https://help.sleuth.io/sleuth-api"&gt;Sleuth.io API&lt;/a&gt;, covering four registration workflows.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;c,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;sleuth.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;(addr,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;org,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;apiKey)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;SendDeployRegistration&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;sleuth.DeployRegistrationRequest{&lt;span style="color:#ff7b72;font-weight:bold"&gt;...&lt;/span&gt;})&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;SendManualChange&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;sleuth.ManualChangeRequest{&lt;span style="color:#ff7b72;font-weight:bold"&gt;...&lt;/span&gt;})&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;SendCustomIncidentImpactRegistration&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;sleuth.CustomIncidentImpactRegistrationRequest{&lt;span style="color:#ff7b72;font-weight:bold"&gt;...&lt;/span&gt;})&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;SendCustomMetricImpactRegistration&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;sleuth.CustomMetricImpactRegistrationRequest{&lt;span style="color:#ff7b72;font-weight:bold"&gt;...&lt;/span&gt;})&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="four-workflows-and-what-separates-them"&gt;Four workflows, and what separates them&lt;/h2&gt;&#10;&lt;p&gt;&lt;strong&gt;Deploy registration&lt;/strong&gt; reports a code deployment: the ordinary case, where a pipeline ships a build.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Manual change&lt;/strong&gt; reports something that changed production without going through a deployment. A feature flag flipped, a configuration value edited, a database migration run by hand. These cause incidents at a rate comparable to deployments and are invisible to a tool that only watches your pipeline, which makes change failure rate look better than it is.&lt;/p&gt;</description></item><item><title>ipify</title><link>https://nurago.org/packages/ipify/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/ipify/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/ipify&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Asks &lt;a href="https://www.ipify.org/"&gt;ipify.org&lt;/a&gt; what address the outside world sees this process as having.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;c,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;ipify.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;New&lt;/span&gt;()&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ip,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;GetPublicIP&lt;/span&gt;(ctx)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Defaults: endpoint &lt;code&gt;https://api.ipify.org&lt;/code&gt;, timeout 4 seconds.&lt;/p&gt;&#10;&lt;h2 id="why-a-process-cannot-answer-this-itself"&gt;Why a process cannot answer this itself&lt;/h2&gt;&#10;&lt;p&gt;A service behind NAT, in a container, on a cloud instance, or behind a load balancer has local interface addresses that bear no relation to the address its outbound traffic appears to come from. Reading &lt;code&gt;net.Interfaces()&lt;/code&gt; gives you &lt;code&gt;10.0.x.x&lt;/code&gt; or &lt;code&gt;172.17.x.x&lt;/code&gt;, which is not the answer to &amp;ldquo;what should I tell a partner to allowlist&amp;rdquo;.&lt;/p&gt;</description></item><item><title>testutil</title><link>https://nurago.org/packages/testutil/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/packages/testutil/</guid><description>&lt;p&gt;Part of &lt;a href="https://nurago.org/"&gt;nurago&lt;/a&gt;, a collection of independent Go packages for backend services.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;github.com/tecnickcom/nurago/pkg/testutil&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Test-only helpers: readers and closers that always fail, capture of stdout, stderr and the default logger, a pre-routed HTTP handler, and substitution of time-variant values in expected output.&lt;/p&gt;&#10;&lt;h2 id="failing-io-on-demand"&gt;Failing I/O on demand&lt;/h2&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;reader&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;testutil.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;NewErrorReader&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;read failed&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;closer&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;testutil.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;NewErrorCloser&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;close failed&amp;#34;&lt;/span&gt;)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;ErrorReader.Read&lt;/code&gt; always returns the error. &lt;code&gt;ErrorCloser.Close&lt;/code&gt; always returns the error, on an &lt;code&gt;io.ReadCloser&lt;/code&gt; whose read side works normally.&lt;/p&gt;</description></item><item><title>Stop Rewriting Go Service Boilerplate: Ship Production Go APIs Faster with nurago</title><link>https://nurago.org/articles/nurago/</link><pubDate>Sun, 29 Mar 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/articles/nurago/</guid><description>&lt;hr&gt;&#10;&lt;img src="https://nurago.org/img/nurago_pipeline_640x300.png" alt="tecnickcom/nurago pipeline" width="640" height="300" /&gt;&#10;&lt;p&gt;Every backend team says the same thing when starting a new service:&lt;/p&gt;&#10;&lt;p&gt;&amp;ldquo;This one will be lean.&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;Then the infrastructure checklist arrives.&lt;/p&gt;&#10;&lt;p&gt;Configuration loading. Structured logging. Metrics. Retries. Health checks. Graceful shutdown. Validation. Cache layers. Cloud clients. Test utilities.&lt;/p&gt;&#10;&lt;p&gt;None of it is core business logic. All of it is essential in production.&lt;/p&gt;&#10;&lt;p&gt;After watching that pattern repeat across teams and projects, I consolidated the building blocks I kept rewriting into one open-source project: &lt;a href="https://github.com/tecnickcom/nurago"&gt;nurago&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Exponential Backoff with Jitter in Go, Without the Overflow Footgun</title><link>https://nurago.org/articles/nurago-backoff/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/articles/nurago-backoff/</guid><description>&lt;hr&gt;&#10;&lt;p&gt;Exponential backoff is the usual first response when a downstream call starts failing: wait a bit, then wait longer, then longer still. It looks trivial. It hides two real bugs.&lt;/p&gt;&#10;&lt;p&gt;The first is the &lt;em&gt;thundering herd&lt;/em&gt;. Without jitter, a fleet of clients that all failed at the same instant will all retry at the same instant, hammering the recovering service in synchronised waves.&lt;/p&gt;&#10;&lt;p&gt;The second is that the delay arithmetic can overflow. Signed integer arithmetic in Go wraps around, so a &lt;code&gt;time.Duration&lt;/code&gt; that grows past &lt;code&gt;math.MaxInt64&lt;/code&gt; nanoseconds does not saturate. It comes back &lt;em&gt;negative&lt;/em&gt;. Delays only get that large after many consecutive failures, so the wrap fires in the middle of your worst outage.&lt;/p&gt;</description></item><item><title>Between Two Attempts: What an HTTP Retry Loop Must Decide</title><link>https://nurago.org/articles/nurago-httpretrier/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/articles/nurago-httpretrier/</guid><description>&lt;hr&gt;&#10;&lt;p&gt;A retry loop looks like the simplest code you will write all week: try, and if it failed, wait and try again. The hard questions live in the gap between one attempt and the next. Was that outcome worth retrying at all? What has to be released before another attempt is safe? Can the request even be sent a second time? How long is the wait, and who has the final say on it?&lt;/p&gt;</description></item><item><title>Observing Outbound HTTP in Go Without Disturbing It</title><link>https://nurago.org/articles/nurago-httpclient/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/articles/nurago-httpclient/</guid><description>&lt;hr&gt;&#10;&lt;p&gt;Instrumentation has an observer problem. The moment you wrap an &lt;abbr title="HyperText Transfer Protocol"&gt;HTTP&lt;/abbr&gt; client to log requests, propagate trace IDs, and dump payloads for debugging, you have added code that can mutate the caller&amp;rsquo;s request, leak timers, buffer streams that were meant to flow, and write secrets to disk. The wrapper meant to explain production behaviour becomes part of it.&lt;/p&gt;&#10;&lt;p&gt;The &lt;a href="https://github.com/tecnickcom/nurago/tree/main/pkg/httpclient"&gt;&lt;code&gt;httpclient&lt;/code&gt;&lt;/a&gt; package in &lt;a href="https://github.com/tecnickcom/nurago"&gt;nurago&lt;/a&gt; wraps &lt;code&gt;net/http&lt;/code&gt; with trace ID propagation and structured request/response logging, and most of its design is about exactly this tension. Each section below is one place where observing a request could disturb it, and what the code does there instead.&lt;/p&gt;</description></item><item><title>Before the First Request and After the Last: a Go HTTP Server's Edges</title><link>https://nurago.org/articles/nurago-httpserver/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/articles/nurago-httpserver/</guid><description>&lt;hr&gt;&#10;&lt;p&gt;Serving &lt;abbr title="HyperText Transfer Protocol"&gt;HTTP&lt;/abbr&gt; is the easy middle of a server&amp;rsquo;s life. &lt;code&gt;net/http&lt;/code&gt; handles it well, and it is rarely where the trouble starts. The places where a server can embarrass itself are the edges: the startup that half-works until the first request finds the misconfiguration, and the shutdown that drops in-flight requests, leaks a goroutine, or hangs the deploy. The &lt;a href="https://github.com/tecnickcom/nurago/tree/main/pkg/httpserver"&gt;&lt;code&gt;httpserver&lt;/code&gt;&lt;/a&gt; package in &lt;a href="https://github.com/tecnickcom/nurago"&gt;nurago&lt;/a&gt; is a bootstrap around &lt;code&gt;net/http&lt;/code&gt; (routing, middleware, operational endpoints, &lt;abbr title="Transport Layer Security"&gt;TLS&lt;/abbr&gt;, graceful shutdown), and its most interesting engineering lives at those edges, so that is where this post stays.&lt;/p&gt;</description></item><item><title>A Reverse Proxy Is Defined by What It Refuses to Do</title><link>https://nurago.org/articles/nurago-httpreverseproxy/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/articles/nurago-httpreverseproxy/</guid><description>&lt;hr&gt;&#10;&lt;p&gt;Every Go reverse proxy runs on &lt;code&gt;net/http/httputil.ReverseProxy&lt;/code&gt;, and it is a good engine. What distinguishes one proxy deployment from another is almost entirely policy: what gets forwarded, what gets rewritten, what gets timed out, what gets logged.&lt;/p&gt;&#10;&lt;p&gt;In a proxy the characteristic failure mode is helpfulness. Following a redirect is helpful, and it is also a request-forgery vector. A whole-request timeout looks like a safety net until it truncates a long download. Logging everything drowns the one entry that mattered.&lt;/p&gt;</description></item><item><title>A DNS-Caching Dialer for http.Transport in Go</title><link>https://nurago.org/articles/nurago-dnscache/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/articles/nurago-dnscache/</guid><description>&lt;hr&gt;&#10;&lt;p&gt;Go&amp;rsquo;s standard &lt;code&gt;http.Transport&lt;/code&gt; resolves the destination host on every new connection and does not cache the result. For a client that talks to the same handful of hosts thousands of times a minute, that is a steady drip of Domain Name System (&lt;abbr title="Domain Name System"&gt;DNS&lt;/abbr&gt;) queries, each adding latency to connection setup and load on the resolver. The &lt;a href="https://github.com/tecnickcom/nurago/tree/main/pkg/dnscache"&gt;&lt;code&gt;dnscache&lt;/code&gt;&lt;/a&gt; package in &lt;a href="https://github.com/tecnickcom/nurago"&gt;nurago&lt;/a&gt; sits in that gap: a bounded, concurrency-safe DNS cache with a &lt;code&gt;DialContext&lt;/code&gt; you can drop straight into a transport.&lt;/p&gt;</description></item><item><title>From singleflight to a Production Cache: a Gap Analysis</title><link>https://nurago.org/articles/nurago-sfcache/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/articles/nurago-sfcache/</guid><description>&lt;hr&gt;&#10;&lt;p&gt;Some lookups are too expensive to run on every request: a Domain Name System (&lt;abbr title="Domain Name System"&gt;DNS&lt;/abbr&gt;) resolution, a secret from a vault, slow remote metadata. Caching them is the natural move, and it brings a well-known hazard: when a hot value expires, a crowd of goroutines can rush the upstream at once, the cache stampede. In Go, the usual remedy is &lt;code&gt;golang.org/x/sync/singleflight&lt;/code&gt;, which lets one goroutine do the work while the other forty-nine wait for its result.&lt;/p&gt;</description></item><item><title>A Distributed Lock in Go and MySQL, and Every Way the Session Can Betray You</title><link>https://nurago.org/articles/nurago-mysqllock/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/articles/nurago-mysqllock/</guid><description>&lt;hr&gt;&#10;&lt;p&gt;Some work must happen at most once across an entire fleet: a nightly reconciliation, an idempotent migration, a cache rebuild. When several instances of a service could each try to run it, you need a lock that spans processes, and an in-memory mutex does not.&lt;/p&gt;&#10;&lt;p&gt;If MySQL is already in the stack, its built-in named locks are the tempting answer. &lt;code&gt;GET_LOCK('key', timeout)&lt;/code&gt; grants a server-wide lock, &lt;code&gt;RELEASE_LOCK('key')&lt;/code&gt; frees it, and you have avoided standing up a separate coordination service. The &lt;a href="https://github.com/tecnickcom/nurago/tree/main/pkg/mysqllock"&gt;&lt;code&gt;mysqllock&lt;/code&gt;&lt;/a&gt; package in &lt;a href="https://github.com/tecnickcom/nurago"&gt;nurago&lt;/a&gt; is under six hundred lines built on that primitive.&lt;/p&gt;</description></item><item><title>The Life of a Password: Argon2id in Production with nurago</title><link>https://nurago.org/articles/nurago-passwordhash/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/articles/nurago-passwordhash/</guid><description>&lt;hr&gt;&#10;&lt;p&gt;Password storage is one of the more consequential things a service does, and it has a long record of being botched. General-purpose hashes like &lt;abbr title="Message Digest 5"&gt;MD5&lt;/abbr&gt; and &lt;abbr title="Secure Hash Algorithm 1"&gt;SHA-1&lt;/abbr&gt; are the classic mistake: fast, and trivially parallelised on a &lt;abbr title="Graphics Processing Unit"&gt;GPU&lt;/abbr&gt;, which is precisely backwards.&lt;/p&gt;&#10;&lt;p&gt;What the job calls for is a hash that is deliberately slow and &lt;em&gt;memory-hard&lt;/em&gt;, needing a large tunable amount of &lt;abbr title="Random Access Memory"&gt;RAM&lt;/abbr&gt; per guess. That is what neutralises the massively parallel GPU and Application-Specific Integrated Circuit (&lt;abbr title="Application-Specific Integrated Circuit"&gt;ASIC&lt;/abbr&gt;) rigs used to crack leaked password databases. Bcrypt is slow but uses a fixed, tiny amount of memory, so those rigs still scale against it. Password-Based Key Derivation Function 2 (&lt;abbr title="Password-Based Key Derivation Function 2"&gt;PBKDF2&lt;/abbr&gt;), the usual choice when Federal Information Processing Standards (&lt;abbr title="Federal Information Processing Standards"&gt;FIPS&lt;/abbr&gt;) compliance is required, iterates a cheap hash and is not memory-hard at all. Scrypt is memory-hard, but ties memory and &lt;abbr title="Central Processing Unit"&gt;CPU&lt;/abbr&gt; cost to a single knob. Argon2, the Password Hashing Competition (&lt;abbr title="Password Hashing Competition"&gt;PHC&lt;/abbr&gt;) winner standardised as &lt;abbr title="Request for Comments"&gt;RFC&lt;/abbr&gt; 9106, lets you dial time, memory, and parallelism independently, and its &lt;code&gt;id&lt;/code&gt; variant adds resistance to side-channel attacks. Hence its place at the top of the Open Worldwide Application Security Project (&lt;abbr title="Open Worldwide Application Security Project"&gt;OWASP&lt;/abbr&gt;) Password Storage Cheat Sheet.&lt;/p&gt;</description></item><item><title>Checking Passwords Against Have I Been Pwned Without Leaking Them</title><link>https://nurago.org/articles/nurago-passwordpwned/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/articles/nurago-passwordpwned/</guid><description>&lt;hr&gt;&#10;&lt;p&gt;Rejecting passwords that have appeared in known data breaches is a high-value check for a registration flow, and the best-known source is Troy Hunt&amp;rsquo;s Have I Been Pwned (&lt;abbr title="Have I Been Pwned"&gt;HIBP&lt;/abbr&gt;) Pwned Passwords database. The obvious objection: sending a user&amp;rsquo;s password, or even its full hash, to a third-party service to ask &amp;ldquo;have you seen this?&amp;rdquo; would itself be a security incident.&lt;/p&gt;&#10;&lt;p&gt;The &lt;a href="https://github.com/tecnickcom/nurago/tree/main/pkg/passwordpwned"&gt;&lt;code&gt;passwordpwned&lt;/code&gt;&lt;/a&gt; package in &lt;a href="https://github.com/tecnickcom/nurago"&gt;nurago&lt;/a&gt; implements the check in a way that answers that objection. Following the exchange as a network observer would is the quickest way to see how: what leaves the machine, what comes back, and what happens when the answer cannot be trusted.&lt;/p&gt;</description></item><item><title>The Classic JWT Attacks, Addressed by Construction in Go</title><link>https://nurago.org/articles/nurago-jwt/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/articles/nurago-jwt/</guid><description>&lt;hr&gt;&#10;&lt;p&gt;&lt;abbr title="JavaScript Object Notation"&gt;JSON&lt;/abbr&gt; Web Tokens (JWTs) have a reputation for being dangerous, and the reputation is earned. The danger is almost never in the cryptography; it is in the flexibility of the format. A token header announces how it was signed, and a naive verifier believes the announcement. Many published &lt;abbr title="JSON Web Token"&gt;JWT&lt;/abbr&gt; exploits are variations on that single act of misplaced trust.&lt;/p&gt;&#10;&lt;p&gt;The &lt;a href="https://github.com/tecnickcom/nurago/tree/main/pkg/jwt"&gt;&lt;code&gt;jwt&lt;/code&gt;&lt;/a&gt; package in &lt;a href="https://github.com/tecnickcom/nurago"&gt;nurago&lt;/a&gt; takes a stricter stance. Each of the classic attacks is closed by how the package is built, rather than by a runtime check someone could forget to call or misconfigure away. Here is the list, one attack at a time.&lt;/p&gt;</description></item><item><title>Redacting Secrets from Go Logs on a Performance Budget</title><link>https://nurago.org/articles/nurago-redact/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/articles/nurago-redact/</guid><description>&lt;hr&gt;&#10;&lt;p&gt;Services leak secrets into their own logs all the time. Someone dumps an inbound &lt;abbr title="HyperText Transfer Protocol"&gt;HTTP&lt;/abbr&gt; request to debug a flaky integration and the &lt;code&gt;Authorization&lt;/code&gt; header goes straight to disk. A &lt;abbr title="JavaScript Object Notation"&gt;JSON&lt;/abbr&gt; body with a &lt;code&gt;password&lt;/code&gt; field lands in an error log. A connection string with the database password ends up in a stack trace. Nobody did anything reckless. They logged what they needed to see when something broke, and the credentials came along for the ride.&lt;/p&gt;</description></item><item><title>Filtering Untrusted Client Queries in Go: Threat Model First</title><link>https://nurago.org/articles/nurago-filter/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/articles/nurago-filter/</guid><description>&lt;hr&gt;&#10;&lt;p&gt;A &lt;code&gt;filter=&lt;/code&gt; query parameter is a common way to let clients narrow down a list endpoint. The client asks for &amp;ldquo;users named Doe, at most 42 years old, in England or France&amp;rdquo;, and the server turns that into a predicate over an in-memory slice. It looks like a small convenience. It is also a little expression language exposed to the open internet, which makes the hostile client the right place to start designing from. The &lt;a href="https://github.com/tecnickcom/nurago/tree/main/pkg/filter"&gt;&lt;code&gt;filter&lt;/code&gt;&lt;/a&gt; package in &lt;a href="https://github.com/tecnickcom/nurago"&gt;nurago&lt;/a&gt; is built that way, so the threat model comes first here and the machinery second.&lt;/p&gt;</description></item><item><title>A Production slog.Handler on Top of zerolog</title><link>https://nurago.org/articles/nurago-logsrv/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/articles/nurago-logsrv/</guid><description>&lt;hr&gt;&#10;&lt;p&gt;Go&amp;rsquo;s &lt;code&gt;log/slog&lt;/code&gt; gave the ecosystem a standard structured-logging interface to code against: your libraries speak &lt;code&gt;slog&lt;/code&gt; and stay portable. Teams that care about logging overhead often reach for &lt;a href="https://github.com/rs/zerolog" rel="nofollow"&gt;zerolog&lt;/a&gt;, whose reputation is allocation-free structured output.&lt;/p&gt;&#10;&lt;p&gt;The &lt;a href="https://github.com/tecnickcom/nurago/tree/main/pkg/logsrv"&gt;&lt;code&gt;logsrv&lt;/code&gt;&lt;/a&gt; package in &lt;a href="https://github.com/tecnickcom/nurago"&gt;nurago&lt;/a&gt; takes both. It implements a native &lt;code&gt;slog.Handler&lt;/code&gt; that writes each record&amp;rsquo;s attributes directly onto zerolog events. The work is all in the disagreements between the two APIs: what a level is, where attributes may nest, who owns rendering decisions, and who reports errors. Every heading below names one of those mismatches and answers it in three parts: what the &lt;code&gt;slog&lt;/code&gt; contract requires, how a naive bridge gets it wrong, and what this handler does instead.&lt;/p&gt;</description></item><item><title>A Longest-Prefix Trie for Numeric Keys, in Six Design Questions</title><link>https://nurago.org/articles/nurago-numtrie/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/articles/nurago-numtrie/</guid><description>&lt;hr&gt;&#10;&lt;p&gt;Some lookups are not &amp;ldquo;does this key exist&amp;rdquo; but &amp;ldquo;what is the most specific rule that applies to this key&amp;rdquo;. Telephone routing is the canonical example: a dialled number matches the longest stored prefix, so &lt;code&gt;+1 212 555 0100&lt;/code&gt; should resolve to a New York rule (&lt;code&gt;1212&lt;/code&gt;) if one exists, and fall back to a broader North American rule (&lt;code&gt;1&lt;/code&gt;) if it does not. A hash map answers only exact-key questions, so longest-prefix matching would need a separate probe for every shrinking prefix; a linear scan over a sorted list degrades as the table grows. The &lt;a href="https://github.com/tecnickcom/nurago/tree/main/pkg/numtrie"&gt;&lt;code&gt;numtrie&lt;/code&gt;&lt;/a&gt; package in &lt;a href="https://github.com/tecnickcom/nurago"&gt;nurago&lt;/a&gt; is a small generic trie built for exactly this question:&lt;/p&gt;</description></item><item><title>Packing an Entire Country Record into a Single uint64</title><link>https://nurago.org/articles/nurago-countrycode/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/articles/nurago-countrycode/</guid><description>&lt;hr&gt;&#10;&lt;p&gt;Country metadata is one of those unglamorous needs that turns up in most backends. Translate &lt;code&gt;US&lt;/code&gt; to &lt;code&gt;USA&lt;/code&gt; to &lt;code&gt;840&lt;/code&gt;. Find which region a country belongs to. Validate a country top-level domain (&lt;abbr title="Top Level Domain"&gt;TLD&lt;/abbr&gt;). The usual answer is a pile of maps, one per lookup direction, each holding strings, and it works well enough that nobody looks twice at it.&lt;/p&gt;&#10;&lt;p&gt;The &lt;a href="https://github.com/tecnickcom/nurago/tree/main/pkg/countrycode"&gt;&lt;code&gt;countrycode&lt;/code&gt;&lt;/a&gt; package in &lt;a href="https://github.com/tecnickcom/nurago"&gt;nurago&lt;/a&gt; does something else. Internally, an entire International Organization for Standardization (&lt;abbr title="International Organization for Standardization"&gt;ISO&lt;/abbr&gt;) 3166 country record is encoded into a single 64-bit integer, and every lookup is a handful of shifts away from that one number.&lt;/p&gt;</description></item><item><title>Random IDs in Go, Trap by Trap: UUIDv7 and Unbiased Random Strings</title><link>https://nurago.org/articles/nurago-random/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/articles/nurago-random/</guid><description>&lt;hr&gt;&#10;&lt;p&gt;Generating random identifiers looks trivial and hides several traps. Reach for &lt;code&gt;math/rand&lt;/code&gt; out of habit and your &amp;ldquo;secure&amp;rdquo; token is predictable. Map random bytes onto an alphabet with a naive &lt;code&gt;% len&lt;/code&gt; and you skew the distribution. Accept whatever a custom entropy reader hands you and you can end up with truncated randomness, or a call that never returns.&lt;/p&gt;&#10;&lt;p&gt;The &lt;a href="https://github.com/tecnickcom/nurago/tree/main/pkg/random"&gt;&lt;code&gt;random&lt;/code&gt;&lt;/a&gt; package in &lt;a href="https://github.com/tecnickcom/nurago"&gt;nurago&lt;/a&gt; centralises these patterns behind one small &lt;abbr title="Application Programming Interface"&gt;API&lt;/abbr&gt;. &lt;code&gt;New(nil)&lt;/code&gt; draws from &lt;code&gt;crypto/rand.Reader&lt;/code&gt;, the right default for anything security-sensitive, and a custom &lt;code&gt;io.Reader&lt;/code&gt; can be supplied for testing or specialised entropy sources:&lt;/p&gt;</description></item><item><title>Deriving a Fast Fixed-Width Hex Encoder in Go</title><link>https://nurago.org/articles/nurago-uhex/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><guid>https://nurago.org/articles/nurago-uhex/</guid><description>&lt;hr&gt;&#10;&lt;p&gt;Hexadecimal encoding is a solved problem. The standard library has &lt;code&gt;encoding/hex&lt;/code&gt;, and if you just want a string there is always &lt;code&gt;fmt.Sprintf(&amp;quot;%x&amp;quot;, v)&lt;/code&gt;. So why does &lt;a href="https://github.com/tecnickcom/nurago"&gt;nurago&lt;/a&gt; ship a dedicated &lt;a href="https://github.com/tecnickcom/nurago/tree/main/pkg/uhex"&gt;&lt;code&gt;uhex&lt;/code&gt;&lt;/a&gt; package?&lt;/p&gt;&#10;&lt;p&gt;Because the places where hex encoding runs hottest, trace IDs, hashes, log fields, protocol framing, share a property the general tools cannot exploit: the width is fixed and known at compile time. Derive &lt;code&gt;uhex&lt;/code&gt; from the standard library by removing one cost at a time, and you can see exactly what that constraint buys.&lt;/p&gt;</description></item></channel></rss>