Reference

Features, end to end.

Capabilities below are in the codebase at HEAD. Anything not listed here is either on the roadmap or a deliberate non-goal — see the Shipped / Roadmap / Not planned columns on the landing page.

// 01

What's actually built.

11 capabilities, each linked to its docs page. No marketing labels — the names match what the engine logs at startup.

  1. 01

    GraphQL from your Postgres catalog

    Husaria introspects the live Postgres schema and exposes a typed GraphQL API for it. No metadata files to sync.

  2. 02

    Row- and column-level permissions

    Permissions live in a husaria_data schema. Per-role, per-table, per-operation policies with column allow-lists and JSON filters.

  3. 03

    Admin secret or JWT — your choice

    Two auth modes. Admin-secret header for service callers, HS256/RS256 JWTs for user sessions. Auth0, Zitadel, Keycloak — all wire up cleanly.

  4. 04

    Live queries & streaming subs

    graphql-transport-ws subscriptions over WebSocket. Cohort multiplexing means N subscribers on the same query share one execution per tick.

  5. 05

    Webhook actions

    Register custom action_<name> fields that delegate resolution to an external HTTP webhook. Sync or async, RBAC-gated, with case-insensitive header forwarding.

  6. 06

    REST APIs as GraphQL fields

    Point Husaria at any OpenAPI 3 spec and tick the endpoints you want — each becomes a delegated_<source>_<op> field with per-source auth, RBAC, and response caching.

  7. 07

    Embedded admin console

    Eighteen tabs: GraphQL, Data Browser, SQL Runner, Schema, API Explorer, Permissions, Actions, Delegations, Remote Schemas, Exposed Functions, REST Endpoints, Allow-list, Sources, Events, Migrations, Performance, Metadata, Settings.

  8. 08

    Live runtime settings

    Toggle log level, introspection, maintenance mode, and more from the console — most settings apply without restart.

  9. 09

    Read-only replica mode

    HUSARIA_READ_ONLY=true points a second instance at a Postgres read replica. Queries + subscriptions pass through; mutations + console writes return 503; the load balancer routes around it.

  10. 10

    One Go binary

    Statically linked, CGO_ENABLED=0. Drop it next to your Postgres and run. No JVM, no Node runtime, no sidecar. Distroless multi-arch container available.

  11. 11

    Production-shaped surface

    Prometheus metrics, separate /live + /ready probes, traceparent passthrough, configurable timeouts, request size limits, CORS allow-lists — ready for ops day one.

// 02

Auth modes.

One env var picks the chain. Open mode is for local dev and internal-only deployments — the kind of network where every caller is already trusted. Secured mode is the prod posture: admin secret or validated JWT.

A wrong admin secret in secured mode doesn't fall through to JWT. That's a wrong-secret-is-an-attack-signal decision; clients should never be retrying with the wrong value.
AspectOpenSecured
TriggerHUSARIA_ADMIN_SECRET unsetHUSARIA_ADMIN_SECRET set
Caller defaultEveryone is admin401 without secret or JWT
X-Husaria-RoleImpersonates that role; RLS still enforcedMust appear in JWT claims.allowed_roles
JWT honouredYes — for role resolutionRequired (or admin secret)
Wrong secret401, no JWT fallthrough (attack signal)

// 03

Environment variables.

.env.example in the repo is the source of truth; the inventory here is the most-used subset, grouped by area. 80 keys total.

Server

25 keys
VariableDefaultDescription
HUSARIA_HOST0.0.0.0Bind address.
HUSARIA_PORT8080HTTP port.
HUSARIA_READ_TIMEOUT30sHTTP read timeout.
HUSARIA_WRITE_TIMEOUT30sHTTP write timeout.
HUSARIA_IDLE_TIMEOUT120sConnection idle timeout.
HUSARIA_SHUTDOWN_TIMEOUT30sGraceful shutdown deadline.
HUSARIA_MAX_HEADER_BYTES1048576Max HTTP header bytes.
HUSARIA_MAX_REQUEST_BYTES1048576Max body bytes.
HUSARIA_ENABLE_CONSOLEfalseMount the embedded /console UI.
HUSARIA_ENABLE_INTROSPECTIONtrueAllow GraphQL introspection. Single non-bypassable gate — the caller-controlled Referer/header backdoor was removed.
HUSARIA_ALLOW_OPEN_MODEfalseExplicit opt-in to OPEN MODE. When HUSARIA_ADMIN_SECRET is unset, the server refuses to start unless this is =true — open mode (every caller is admin, full RLS bypass) is never the silent default. Intended for local dev only.
HUSARIA_ALLOWLIST_MODEdisabledQuery allow-list / persisted-query enforcement at boot. disabled (default) resolves and auto-stores APQ hashes without gating; log-only records violations without blocking; enforce rejects any operation whose hash is not on the allow-list. Also settable live via the allowlist_mode runtime setting.
HUSARIA_CORS_ALLOWED_ORIGINSunsetComma-separated CORS allow-list (required in prod).
HUSARIA_CORS_ALLOWED_HEADERSContent-Type,AuthorizationAllowed inbound request headers.
HUSARIA_TRUSTED_PROXIESunsetCIDRs trusted as proxies. The rate limiter checks the TCP RemoteAddr against this list (plus loopback) BEFORE consulting X-Forwarded-For — trusted transports bypass the per-IP cap, untrusted ones can't spoof identity via XFF.
HUSARIA_MEMORY_LIMITunsetSoft memory ceiling (Go's GOMEMLIMIT). Accepts decimal (500M, 2G) and binary (256Mi, 1Gi) suffixes. Falls back to 80% of the cgroup memory.max when readable (v1 + v2 supported); off when neither is set.
HUSARIA_GOGCunsetHeap growth percent between GCs. Defaults to 50 when a memory limit is in play, 75 otherwise. Lower = heap stays close to the live set, more CPU on GC.
HUSARIA_FREE_OS_MEM_INTERVALunsetPeriodic debug.FreeOSMemory cadence (Go duration). Default 60s when a memory limit is set, 0 (off) otherwise. Returns idle spans to the OS so RSS doesn't drift above the live set on a quiet pod.
HUSARIA_READ_ONLYfalseRun as a read-only replica. Mutations + console writes return 503; bootstrap of husaria_data is skipped.
HUSARIA_GIN_DEBUGfalseForce gin into DebugMode (verbose per-route logging). Otherwise ReleaseMode unless HUSARIA_ENV=development.
HUSARIA_ENVunsetThree effects. (1) HUSARIA_ENV=development flips gin to DebugMode. (2) HUSARIA_ENV in {production, prod, live} enables error-response redaction. (3) Same values REFUSE startup when HUSARIA_ADMIN_SECRET is empty — prevents booting OPEN MODE in prod.
ENVIRONMENTunsetLogging-mode gate. 'production' selects ProductionConfig (Level=info, sampling on). Anything else (including unset) selects DevelopmentConfig (Level=debug). Separate from HUSARIA_ENV.
HUSARIA_LOG_LEVELunsetInitial log level override. Replaces whichever level ENVIRONMENT selected. Valid values: debug, info, warn, error, fatal.
LOG_LEVELunsetBare log-level override read by the logger at configure time. Same valid values as HUSARIA_LOG_LEVEL.
HUSARIA_IMPORT_COMPATfalseOpt-in to reading relationship names from an imported hdb_catalog.hdb_metadata document. Only enable on instances migrated from a prior deployment that stored that document.

Database

12 keys
VariableDefaultDescription
DATABASE_URLunsetFull DSN — takes priority over discrete vars. When supplied without sslmode, Husaria injects one from HUSARIA_DB_SSL_MODE (default require).
HUSARIA_DB_HOSTlocalhostPostgres host.
HUSARIA_DB_PORT5432Postgres port.
HUSARIA_DB_NAMEhusariaDatabase name.
HUSARIA_DB_USERpostgresDB user.
HUSARIA_DB_PASSWORDunsetDB password.
HUSARIA_DB_SSL_MODErequirelibpq sslmode. verify-ca and verify-full both honour chain verification now (the older bug that turned verify-ca into require has been fixed). Also the value injected into DATABASE_URL when that DSN omits it.
HUSARIA_DB_MAX_OPEN_CONNS25Pool: max open connections.
HUSARIA_DB_MAX_IDLE_CONNS25Pool: max idle connections.
HUSARIA_DB_CONN_MAX_LIFETIME1hPool: max connection lifetime.
HUSARIA_DB_CONN_MAX_IDLE_TIME10mPool: max idle time before a connection is closed.
HUSARIA_DB_QUERY_TIMEOUT30sPer-query execution timeout.

Authentication

23 keys
VariableDefaultDescription
HUSARIA_ADMIN_SECRETunsetRequired to enter SECURED mode. Unset = OPEN MODE. Refused at startup when empty + HUSARIA_ENV ∈ {production, prod, live}. Header comparison is constant-time; both X-Husaria-Admin-Secret and X-Admin-Secret accepted.
HUSARIA_ADMIN_ENABLEtrueMount the admin API surface.
HUSARIA_ADMIN_PATH/adminAdmin API path prefix.
HUSARIA_ADMIN_ALLOWED_IPS127.0.0.1,::1IP allow-list for admin endpoints.
HUSARIA_JWT_SECRETunsetHS256 signing key. Minimum 32 bytes (RFC 7518 §3.2; enforced at JWTManager construction).
HUSARIA_JWT_PUBLIC_KEY_PATHunsetRS256 verify key (PEM). Misconfig is fatal.
HUSARIA_JWT_PRIVATE_KEY_PATHunsetRS256 sign key (PEM).
HUSARIA_JWT_JWKS_URLunsetRS256 — JWKS endpoint (RFC 7517). Wins over HUSARIA_JWT_PUBLIC_KEY_PATH if both are set. Lazy fetch, cached, refreshed on kid miss. Singleflight + 5s debounce + stale-but-present fallback.
HUSARIA_JWT_JWKS_CACHE_TTL1hJWKS cache TTL. Go duration (15m, 1h, 24h). Invalid value is fatal at startup.
HUSARIA_JWT_ISSUERunsetRequired JWT 'iss' claim.
HUSARIA_JWT_AUDIENCEunsetComma-separated allowed audiences.
HUSARIA_JWT_ALGORITHMHS256Local-issuer algorithm.
HUSARIA_JWT_EXPIRY24hLocal-issuer access token expiry.
HUSARIA_JWT_REFRESH_EXPIRY168hLocal-issuer refresh token expiry.
HUSARIA_UNAUTHORIZED_ROLEanonymousRole used for unauthenticated requests.
HUSARIA_DEFAULT_ROLEuserDefault role applied when claims lack one.
HUSARIA_AUTH_ALLOW_PRIVATE_HOSTSfalseSSRF opt-out for the auth subsystem. Without it, JWKS fetches and auth/login webhooks refuse to connect to loopback / RFC1918 / link-local / CGNAT / IMDS hosts. Set =true only if your identity provider legitimately lives on a private network.
HUSARIA_AUTH_WEBHOOK_URLunsetAuth webhook endpoint. When set, inbound requests are authenticated by POSTing to this URL.
HUSARIA_AUTH_WEBHOOK_TIMEOUT10sPer-call timeout for the auth webhook.
HUSARIA_AUTH_WEBHOOK_RETRIES3Retry attempts for a failed auth webhook call.
HUSARIA_SESSION_COOKIE_NAMEhusaria-sessionName of the session cookie.
HUSARIA_SESSION_COOKIE_SECUREtrueSet the Secure flag on the session cookie (HTTPS-only).
HUSARIA_SESSION_COOKIE_DOMAINunsetDomain scope for the session cookie. The config validator suggests setting it for better security.

Subscriptions

5 keys
VariableDefaultDescription
HUSARIA_WEBSOCKET_ENABLEtrueEnable WebSocket subscriptions.
HUSARIA_WEBSOCKET_PORT8081WebSocket port if distinct from HTTP (dedicated-port branch is a stub today; mainline mount is on the shared HTTP port).
HUSARIA_WEBSOCKET_PATH/v1/graphql/wsSubscription endpoint path.
HUSARIA_LIVE_QUERIES_REFETCH_INTERVAL_MS1000Live-query poll interval.
HUSARIA_GRAPHQL_CONNECTION_INIT_WAIT_TIMEOUT3sgraphql-transport-ws init timeout.

Cache

11 keys
VariableDefaultDescription
REDIS_HOSTlocalhostRedis host. Falls back to in-memory if unreachable.
REDIS_PORT6379Redis port.
REDIS_PASSWORDunsetRedis password.
DISABLE_CACHEfalseDisable the response-cache middleware entirely.
HUSARIA_REDIS_POOL_SIZE10Redis connection pool size.
HUSARIA_REDIS_MIN_IDLE_CONNS2Minimum idle connections kept in the Redis pool.
HUSARIA_REDIS_MAX_IDLE_CONNS5Maximum idle connections kept in the Redis pool.
HUSARIA_REDIS_DIAL_TIMEOUT5sTimeout for establishing a Redis connection.
HUSARIA_REDIS_READ_TIMEOUT3sRedis socket read timeout.
HUSARIA_REDIS_WRITE_TIMEOUT3sRedis socket write timeout.
HUSARIA_REDIS_POOL_TIMEOUT4sTime to wait for a free connection from the Redis pool.

Migrations

2 keys
VariableDefaultDescription
HUSARIA_AUTO_MIGRATEfalseApply file-based migrations at startup. No-op on read-only replicas. Cannot reach migrations/samples/.
HUSARIA_MIGRATIONS_PATH./migrationsPath to migration files. Loader does not recurse.

Metrics

2 keys
VariableDefaultDescription
HUSARIA_METRICS_ENABLEtrueExpose Prometheus metrics. Endpoint is on adminGroup — Prometheus needs X-Husaria-Admin-Secret in its scrape config.
HUSARIA_METRICS_PATH/metricsPrometheus exposition path.
subscribe.ts
import { createClient } from 'graphql-ws'

const client = createClient({
  url: 'ws://localhost:8080/v1/graphql/ws',
  connectionParams: {
    headers: {
      'X-Husaria-Admin-Secret': process.env.HUSARIA_ADMIN_SECRET!
    }
  }
})

// One subscriber. Behind the scenes Husaria
// joins this into a cohort with anyone else
// running the exact same query — one Postgres
// poll per tick covers them all.
const unsubscribe = client.subscribe(
  { query: 'subscription { notes_stream(cursor: { initial_value: { id: 0 }, ordering: ASC }, batch_size: 50) { id body } }' },
  { next: (msg) => console.log(msg), error: console.error, complete: () => {} }
)

// 04

Subscriptions.

graphql-transport-ws over WebSocket. The runtime polls the database on a configurable interval (default 1000 ms), hashes the canonical-JSON result, and emits next only when the hash changes.

Identical queries from N subscribers share one execution per tick. The cohort key is SHA-256(query + vars + role + admin-bypass) — adding a sixth subscriber costs nothing extra.

Poll interval
1000 ms (default)
Cohort key
SHA-256, versioned
Append-only feeds
_stream variant
Metrics
husaria_subs_*