parsec

package module
v0.3.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 16, 2026 License: MIT Imports: 22 Imported by: 0

README

Parsec

Realtime messaging engine for Go. Public + private channels, token-gated ACLs, out-of-band sink fallback, optional Redis cluster, optional WebTransport, and a self-describing wire — all behind one library import.

Parsec wraps the Centrifuge OSS library with a small, opinionated set of primitives: a channel naming grammar, a TTL-driven lifecycle manager, HMAC + OIDC mgmt tokens with zero-downtime key rotation, sink retry + Redis DLQ, sliding-window rate limits, and a single descriptor envelope every surface honors.

It ships as a Go library (the deliverable), a CLI binary, and a Twirp JSON RPC service — three surfaces over the same engine.

# Docker (fastest eval — multi-arch, ~7 MB image)
docker run -p 8000:8000 ghcr.io/frankbardon/parsec:latest

# Go install (build from source)
go install github.com/frankbardon/parsec/cmd/parsec@latest

For the full local stack (parsec + redis + reference YAML config), docker compose up --build from the repo root.


When to reach for Parsec

  • You want a websocket / WebTransport pub-sub broker you embed in a Go service rather than run as a separate platform.
  • You need an opinionated channel namespace (public: vs private:, per-resource segments, TTL caps) instead of a free-for-all topic tree.
  • You want token-based subscribe authorization with pattern-grant scopes and deny-wins ACLs, signed with HMAC and rotatable without dropping live connections.
  • You need a "no-one's listening — page somebody" path (email / slack / webhook) with retry + dead-letter, not just a fire-and-forget publish.
  • You operate multi-node and want one Redis to back the registry, presence, keyring, DLQ, and rate limiter without bolting on five separate stores.

It is not a durable work queue, not Centrifugo Pro, and not an opinion about your domain — pick verbs that fit your app.


Highlights

Capability Mechanism
Channel grammar <visibility>:<app>.<domain>[.<id>][.<topic>] validated by channels.ParseName
Lifecycle TTL sweep, close (drain) vs. delete (kick), event bus to broker
Auth HMAC-SHA256 JWT, kid-aware KeyRing, zero-downtime rotation
OIDC bridge Optional — composite verifier accepts IdP ID tokens as mgmt bearers
Pattern scopes * + ** glob grants per verb (subscribe / publish / manage), with deny-wins precedence
Sinks Email / Slack / Webhook out of the box, Sink interface for your own
Retry + DLQ Per-sink retry policy + Redis Streams DLQ (parsec dlq {list,count,discard,replay})
Rate limits Sliding-window per subject/IP, Redis or in-memory, per-token override
Persistence In-memory by default; Redis broker + presence + registry when Options.RedisClient is set
Delta compression Per-channel fossil-delta opt-in (channels.SetDelta)
Observability Prometheus collectors + OTLP traces + structured access logs
Transports WebSocket (default), HTTP/3 WebTransport (opt-in)
Surfaces Go library, CLI, Twirp JSON RPC
Browser client centrifuge-js drop-in (parsec speaks the Centrifugo wire protocol)
Testing parsec/parsectesthttptest-style helpers, miniredis cluster variant
Container Multi-arch (amd64/arm64) distroless image at ghcr.io/frankbardon/parsec
Config YAML file + env interpolation, with CLI / env / file / default precedence
Multi-region parsec keys export/import, parsec-keys-sync pub/sub bridge, manifest peer list
Admin UI Embedded vanilla-JS SPA (/admin), opt-in via Options.AdminUI

60-second quickstart (Docker)

docker run --rm -p 8000:8000 \
  -v parsec-state:/var/lib/parsec \
  ghcr.io/frankbardon/parsec:latest

# In another shell:
docker logs <container> 2>&1 | grep bootstrap   # capture mgmt token
curl http://localhost:8000/manifest             # public — no bearer

The browser end-to-end demo at examples/browser/ is go run ./examples/browser — one Go file, one HTML file, full broker → websocket → browser path on http://localhost:8000.

60-second quickstart (CLI)

Boot the broker. First run mints an HMAC secret and prints a bootstrap mgmt token to stderr — capture it:

parsec serve --addr :8000 --state-dir /var/lib/parsec
# parsec serve: bootstrap mgmt token (expires ...):
# eyJhbGciOiJIUzI1NiIs...

Authenticate subsequent CLI calls:

export PARSEC_TOKEN="eyJhbGciOiJIUzI1NiIs..."
export PARSEC_SERVER="http://localhost:8000"

Open a public channel, publish, and tail it:

parsec channels open public:webapp.system.status
echo '{"msg":"hello"}' | parsec publish public:webapp.system.status
parsec subscribe public:webapp.system.status   # SSE probe, NDJSON to stdout

Mint a private channel with a one-shot access + refresh token pair:

parsec channels create private:webapp.user.42.downloads \
  --subject user-42 --ttl 30m

Exchange a refresh for a fresh access token (this RPC is public):

parsec tokens refresh "<refresh-token>"

Browser clients connect with centrifuge-js at ws://localhost:8000/connection/websocket and present the access token.


Testing against parsec

github.com/frankbardon/parsec/parsectest ships test helpers shaped like net/http/httptest. One call returns a running instance bound to test lifetime:

import "github.com/frankbardon/parsec/parsectest"

func TestMyPublisher(t *testing.T) {
    p := parsectest.New(t)                       // ephemeral keyring, in-memory everything
    ch, _ := p.OpenPublic("public:webapp.system.status", time.Minute)
    p.Publish(context.Background(), ch.Name.String(), []byte(`{"k":"v"}`))
}

func TestMyClient(t *testing.T) {
    inst := parsectest.NewServer(t)              // adds an *httptest.Server
    bearer := inst.MintMgmt(t, "ops", time.Hour)
    // point a Twirp client at inst.BaseURL with bearer ...
}

func TestClusterScenario(t *testing.T) {
    inst := parsectest.NewWithRedis(t)           // miniredis-backed multi-node code paths
}

Full reference: docs/src/library/testing.md.

Library quickstart (Go)

package main

import (
    "context"
    "log"
    "time"

    "github.com/frankbardon/parsec"
)

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    p, err := parsec.New(parsec.Options{
        StateDir: "/var/lib/parsec",   // persistent keyring
    })
    if err != nil {
        log.Fatal(err)
    }
    go func() { _ = p.Run(ctx) }()

    ch, _ := p.OpenPublic("public:webapp.system.status", time.Hour)
    _, _ = p.Publish(ctx, ch.Name.String(), []byte(`{"msg":"hello"}`))

    // PublishOrSink: deliver in-band when subscribers exist; sink otherwise.
    _, _ = p.PublishOrSink(ctx,
        "private:webapp.user.42.downloads",
        []byte(`{"status":"ready"}`),
        "email",
        /* sinks.Recipient */ nil,
        parsec.Message{Subject: "Your download is ready", Body: "..."},
    )
}

parsec.New assembles the keyring, the signer/verifier/issuer triad, the channel manager, and the broker. Run(ctx) launches the sweep loop, the manager→broker event bridge, the keyring watcher, and blocks on the Centrifuge node until the context cancels.

Runnable scenarios — scoreboards, downloads, agent progress, nightly reports, heartbeat probes, feedback flagging — live under examples/scenarios/. Each is one go run ./examples/scenarios/<name> away from booting.


Channel naming

<visibility>:<app>.<domain>[.<id>][.<topic>]
  • public:webapp.system.status — broadcast feed, anyone subscribes.
  • private:agent.analysis.{job}.progress — token-gated, per-job.
  • private:webapp.user.{uid}.downloads — per-user notifications.

Rules:

Rule
Visibility public or private
Private channels MUST have an id segment
TTL cap Private channels capped at 1h
Components lowercase ASCII + digits + - + _
Reserved : is the visibility separator only

channels.ParseName is the single source of truth and every surface calls it. See docs/src/channels/naming.md.


Production config (YAML)

parsec serve --config /etc/parsec/parsec.yaml. Precedence is CLI flag > env var > file > built-in default. Every section is optional; ${ENV_VAR} interpolation lets secrets come from the environment.

server:
  addr: ":8000"

auth:
  state_dir: /var/lib/parsec
  mgmt_ttl: 24h
  keyring_poll: 5s

redis:
  addr: redis://localhost:6379
  key_prefix: parsec

manager:
  sweep_interval: 30s

sink_retry:
  max_attempts: 5
  base_backoff: 1s
  max_backoff: 30s

rate_limits:
  publish:     { rate: 100, per: 1s, burst: 30 }
  subscribe:   { rate: 20,  per: 1s, burst: 10 }
  token_issue: { rate: 5,   per: 1s }

observability:
  metrics_bearer_token: "${PARSEC_METRICS_TOKEN}"
  trusted_proxies:
    - 10.0.0.0/8

sinks:
  alerts-email:
    kind: email
    smtp_addr: smtp.example.com:587
    from: ops@example.com
    auth_user: ${SMTP_USER}
    auth_pass: ${SMTP_PASS}
  ops-slack:
    kind: slack
    webhook_url: ${SLACK_WEBHOOK}

Full reference: examples/config/parsec.yaml and docs/src/ops/config.md.


Operating modes

Mode What you get How
Single-node In-memory channel registry, in-memory keyring (or file with --state-dir), in-memory DLQ, in-memory rate limiter Default. No Redis required.
Clustered Redis-backed broker (centrifuge.RedisBroker) + presence, Redis HASH channel registry with pub/sub event bus, Redis-watched keyring, Redis Streams DLQ, Redis sliding-window rate limiter Set Options.RedisClient (or --redis-addr). Every subsystem switches automatically.
Multi-region Cross-region keyring sync via pub/sub bridge, manifest peer list, region label on metrics/logs Run parsec-keys-sync between regions; set Options.Region and Options.Peers.

Surfaces

Surface Use case Scope
Go library Embedded in your service Full
CLI Operator control + scripts Full (client-scoped)
Twirp JSON RPC External clients Full (client-scoped)
WebSocket Browser + service clients Subscribe / publish
WebTransport (HTTP/3) Low-latency browser clients Subscribe / publish
Admin UI (/admin) Operator dashboard Read-only, opt-in
Prometheus (/metrics) Scrape Read-only, bearer-gated

Auth model

Three HMAC-SHA256 JWT token types, all kid-tagged:

Type Default TTL Used for
Access 5 min Websocket connect + subscribe to listed channels
Refresh min(channel TTL, 1h) Exchange at tokens refresh for a new access
Mgmt 24h Authorization: Bearer on the management RPC

Tokens carry an exact-match channel list (chs) and optional pattern-based scopes:

{
  "sub": "ops-bot",
  "chs": ["public:webapp.system.status"],
  "scopes": [
    {"pattern": "public:webapp.*.metrics", "verbs": ["subscribe"]},
    {"pattern": "private:webapp.user.**", "verbs": ["publish"], "deny": true}
  ]
}

Verbs: subscribe, publish, manage. Patterns use the channel grammar plus * (single segment) and trailing ** (multi-segment). Evaluation is deny → chs → allow; any matching deny wins.

When Options.OIDCConfig is non-nil, an OIDC composite verifier accepts IdP-issued ID tokens as mgmt bearers — group claims map to scope grants, so role-based access lives in the IdP. parsec login oidc runs the device-code flow and persists the token at ~/.parsec/credentials.

Manifest and RefreshToken skip the bearer gate; every other RPC requires a valid mgmt token signed by a non-retired key.


Zero-downtime key rotation

The keyring file lives at <state-dir>/keyring.json. Procedure:

parsec keys generate                       # new kid joins as verify-only
parsec keys promote <new-kid>              # new tokens use new-kid; old still verify
NEW_BEARER=$(parsec tokens mgmt | jq -r .payload.mgmt_token)
export PARSEC_TOKEN="$NEW_BEARER"          # switch operator bearer
# wait the longest in-flight token TTL (default 24h for mgmt)
parsec keys retire <old-kid>               # old key stops verifying

Reload triggers: SIGHUP, parsec keys reload, or the mtime-poll watcher (5s default). Full runbook including break-glass: docs/src/ops/key-rotation.md.


Sink reliability

Every sink in Options.Sinks is wrapped in sinks.Retrier at construction time (idempotent). Defaults: 5 attempts, exponential backoff 1s → 30s, 20% jitter — override with Options.SinkRetry or Options.PerSinkRetry["<name>"]. Sinks classify errors by wrapping with sinks.Transient(err) / sinks.Terminal(err).

When retries exhaust, the Retrier pushes a DLQItem and returns nil — the caller only sees PARSEC_SINK_DLQ_OVERFLOW if the DLQ push itself fails.

CLI What
parsec dlq list One row per parked item
parsec dlq count Per-sink counters
parsec dlq discard <id> Drop an item
parsec dlq replay <id> Re-enqueue through the retrier

Backends: sinks.MemoryDLQ (single-node) or sinks.RedisDLQ (one stream per sink, parsec:dlq:<sink>).


Observability

Stream Where Notes
Prometheus metrics /metrics Bearer-gated when MetricsBearerToken set. Cardinality budget: visibility + result enums + bounded sink/method names. No channel names or subjects in labels.
OpenTelemetry traces OTLP HTTP Off by default; set Options.OTLPEndpoint. No-op tracer is zero-alloc.
Structured access logs slog INFO One line per HTTP request with method/path/status/duration_ms/request_id/remote_addr/bearer_subject/trace_id. XFF-aware via Options.TrustedProxies. Token contents are never logged.

A starter Grafana dashboard lives at examples/grafana/. Metric reference: docs/src/ops/observability.md.


Documentation map

The full mdBook is under docs/. Top of mind:

Section Pages
Getting started installation, quickstart, Docker, browser client
Channels naming, public, private, TTL, ACL scopes
CLI serve, channels, publish, subscribe, tokens, keys, dlq, login
Library overview, options, custom sinks, testing with parsectest
Ops deployment, config file, observability, DLQ, rate limiting, key rotation, OIDC, admin UI, multi-region, troubleshooting
Internals architecture, broker, channel manager
RPC Twirp service

Build locally with make docs (mdBook required).


Repo layout

parsec.go                 # library facade — embed this
parsectest/               # test helpers (`parsectest.New(t)`, miniredis variant)
auth/                     # HMAC + OIDC verifiers, KeyRing, scopes, claims
broker/                   # centrifuge.Node wrapper (Redis broker swap, presence)
channels/                 # naming grammar, Manager, EventBus, Store impls
sinks/                    # Sink interface + email/slack/webhook + Retrier + DLQ
ratelimit/                # Limiter interface + memory + Redis sliding-window
service/                  # surface-agnostic business logic
rpc/                      # Twirp JSON wire (generated from service.proto)
descriptor/               # manifest envelope (CLI --json, /manifest)
errors/                   # coded errors (PARSEC_*)
internal/admin/           # embedded SPA at /admin
internal/cli/             # CLI subcommands (urfave/cli/v3)
internal/config/          # YAML loader + Resolved → Options
internal/metrics/         # Prometheus collectors
internal/rpcclient/       # CLI adapter onto the Twirp JSON client
internal/server/          # HTTP mux: twirp + ws + wt + sse + admin + healthz
internal/tracing/         # OTel tracer (no-op default)
cmd/parsec/               # main.go — thin entry point
cmd/parsec-keys-sync/     # multi-region keyring pub/sub bridge daemon
Dockerfile                # multi-stage; ships gcr.io/distroless/static
docker-compose.yml        # parsec + redis local stack
docs/                     # mdBook source
examples/                 # runnable scenarios + reference YAML + Grafana + browser

Build, test, lint

make build      # bin/parsec
make test       # go test -race ./...
make lint       # go vet + staticcheck
make cover      # coverage profile
make proto      # regenerate rpc/ from service.proto (requires protoc-gen-twirp)
make docs       # mdBook build

CGO is disabled. Pinned: Go 1.26.1, centrifuge v0.36.0, urfave/cli/v3 v3.9.0, afero v1.15.0.


License

MIT — see LICENSE.

Documentation

Overview

Package parsec is a scalable realtime messaging engine built on the github.com/centrifugal/centrifuge OSS Go library.

Parsec ships as a CLI binary and as an embeddable Go library. The library is the primary deliverable; the CLI in cmd/parsec is a thin adapter over it, and the Twirp surface is a sibling to the CLI.

Concepts:

  • Channel — a named pub/sub topic. Names follow the public/private namespace grammar in package channels.
  • Broker — the centrifuge Node wrapper that holds connections and ships publications.
  • Manager — channel lifecycle (open, create-private, touch, sweep, delete). TTL enforcement.
  • Sink — out-of-band delivery (email, slack, webhook) when a session is no longer listening.
  • KeyRing — N HMAC keys with one active signer; rotated without downtime.
  • Issuer — mints access / refresh / mgmt tokens using the active key.

Index

Constants

View Source
const MetricsPath = "/metrics"

MetricsPath is the canonical mount point for the Prometheus endpoint. Surfaced in the manifest so clients can discover it.

Variables

This section is empty.

Functions

func ClaimsFromContext

func ClaimsFromContext(ctx context.Context) *auth.Claims

ClaimsFromContext returns the claims planted by WithClaims, or nil.

func RemoteIPFromContext

func RemoteIPFromContext(ctx context.Context) string

RemoteIPFromContext returns the IP planted by WithRemoteIP, or "".

func SubjectFromContext

func SubjectFromContext(ctx context.Context) string

SubjectFromContext returns the bearer subject planted by WithSubject, or "" when no bearer was presented (e.g. RefreshToken's body authentication, or tests with bearer enforcement disabled).

func WithClaims

func WithClaims(ctx context.Context, c *auth.Claims) context.Context

WithClaims returns a new context that carries the validated claims. Surface code calls this so downstream rate-limit gates can read the optional rl override.

func WithRemoteIP

func WithRemoteIP(ctx context.Context, ip string) context.Context

WithRemoteIP returns a new context that carries the request's remote IP (host portion of r.RemoteAddr). Used by gates that key off IP instead of subject — typically RefreshToken which has no mgmt bearer.

func WithSubject

func WithSubject(ctx context.Context, sub string) context.Context

WithSubject returns a new context that carries the authenticated bearer subject (the sub claim). Surface code calls this from the bearer middleware after validation succeeds.

Types

type AdminUIOptions

type AdminUIOptions struct {
	// Enabled mounts /admin/* with the SPA when true. Operators opt in
	// per deployment; production deployments that don't want the UI
	// leave it off so the asset bundle never enters the response graph.
	Enabled bool
}

AdminUIOptions controls the embedded operator admin SPA. Off by default — when disabled the entire /admin/* tree returns 404 and no bytes from the embedded asset bundle reach the response.

type Channel

type Channel = channels.Channel

Re-exports for embedders.

type ChannelName

type ChannelName = channels.Name

Re-exports for embedders.

type Credentials

type Credentials struct {
	Name           channels.Name
	AccessToken    string
	RefreshToken   string
	AccessExpires  time.Time
	RefreshExpires time.Time
}

Credentials returned from CreatePrivate.

type Message

type Message = sinks.Message

Re-exports for embedders.

type Options

type Options struct {
	// FS is the filesystem used for any persistence. Defaults to OsFs.
	FS afero.Fs

	// Sinks is the registry of out-of-band delivery sinks.
	Sinks *sinks.Registry

	// KeyRing holds the HMAC signing keys. If nil, parsec.New chooses
	// between StateDir-backed bootstrap and an ephemeral in-memory ring
	// (logged loudly so the operator knows tokens won't survive restart).
	KeyRing *auth.KeyRing

	// StateDir, when set, makes parsec.New load (or bootstrap) the
	// keyring from <StateDir>/keyring.json. Ignored if KeyRing is
	// non-nil. The directory is created with 0700; the file with 0600.
	StateDir string

	// KeyringPollInterval enables the mtime-poll watcher when > 0 and a
	// StateDir is set. Default 5s.
	KeyringPollInterval time.Duration

	// RedisClient enables multi-node mode. When set, channel registry,
	// keyring, DLQ, and rate limiter all share this client by default.
	// The zero value (nil) means single-node in-memory.
	RedisClient redis.UniversalClient

	// RedisAddr is a convenience: when set and RedisClient is nil,
	// parsec.New constructs a default go-redis client. Address syntax
	// accepts the same forms as centrifuge.RedisShardConfig.Address
	// (host:port, redis://..., redis+sentinel://..., etc.).
	RedisAddr string

	// RedisShards configures the centrifuge Redis broker. When set, the
	// broker switches from in-memory to Redis-backed. If empty and
	// RedisAddr is set, parsec.New builds a single shard from RedisAddr.
	RedisShards []*centrifuge.RedisShard

	// RedisKeyPrefix namespaces every Parsec key in Redis. Default
	// "parsec". Override when multiple Parsec deployments share an
	// instance.
	RedisKeyPrefix string

	// NodeID identifies this Parsec process across the cluster. Used to
	// dedupe cross-node pub/sub events. Empty means auto-generate.
	NodeID string

	// AccessTokenTTL overrides the default access-token lifetime (5m).
	AccessTokenTTL time.Duration

	// MaxRefreshTokenTTL overrides the default refresh-token cap (1h).
	MaxRefreshTokenTTL time.Duration

	// RefreshStore tracks redeemed refresh JTIs and revoked rotation
	// families. When nil and a RedisClient is configured parsec.New
	// builds a RedisRefreshStore; otherwise a MemoryRefreshStore. The
	// store gates RefreshAccess so a leaked refresh token cannot be
	// redeemed twice without revoking the entire family.
	RefreshStore auth.RefreshStore

	// MemoryRefreshPruneInterval is the cleanup interval used when
	// parsec.New constructs the default MemoryRefreshStore. Default
	// 5 minutes. Ignored when RefreshStore is explicit.
	MemoryRefreshPruneInterval time.Duration

	// BrokerOptions are forwarded to broker.New. The SubscribeAuthorizer
	// is overridden by parsec.New to use the Verifier.
	BrokerOptions broker.Options

	// SweepInterval is how often the channel manager runs Sweep. Default 30s.
	SweepInterval time.Duration

	// SinkRetry configures the sink-side retry loop used by PublishOrSink.
	// Defaults are filled by sinks.RetryConfig.Normalize: 5 attempts,
	// 1s..30s exponential backoff, 0.2 jitter. Set MaxAttempts=1 to
	// disable retries.
	SinkRetry sinks.RetryConfig

	// PerSinkRetry holds per-sink-name overrides of SinkRetry. Useful
	// when email needs longer backoffs than slack, for instance.
	PerSinkRetry map[string]sinks.RetryConfig

	// DLQ overrides the default DLQ. When nil, parsec.New constructs a
	// sinks.RedisDLQ if RedisClient is set, otherwise a MemoryDLQ.
	DLQ sinks.DLQ

	// Logger receives boot warnings and reload events. Optional.
	Logger *slog.Logger

	// MetricsRegistry, when non-nil, is used as the Prometheus registry
	// the /metrics endpoint scrapes. When nil, parsec.New constructs a
	// dedicated registry (Go + process collectors included) — set this
	// when the embedder already runs prometheus.DefaultRegisterer and
	// wants Parsec metrics to share that universe.
	MetricsRegistry *prometheus.Registry

	// MetricsBearerToken gates the /metrics endpoint. When set, requests
	// must present Authorization: Bearer <token> matching this value
	// exactly. When empty, /metrics is unguarded — appropriate only when
	// the route is blocked at the network layer.
	MetricsBearerToken string

	// OTLPEndpoint enables OpenTelemetry tracing when non-empty. The
	// value is passed to otlptracehttp.WithEndpoint (host:port form, no
	// scheme). Empty means "tracing disabled" and the hot path performs
	// zero allocations.
	OTLPEndpoint string

	// AccessLogger receives the per-request INFO line from the HTTP
	// access-log middleware. Defaults to Logger when nil.
	AccessLogger *slog.Logger

	// TrustedProxies is the CIDR list of upstream proxies whose
	// X-Forwarded-For headers Parsec will honor. Anything else is
	// ignored to prevent client-IP spoofing.
	TrustedProxies []net.IPNet

	// WebTransport, when populated with a UDP listen address + TLS cert
	// pair, enables the HTTP/3 WebTransport handler. Default: disabled.
	WebTransport WebTransportOptions

	// AdminUI controls the embedded operator SPA. Default: disabled —
	// the bytes are kept out of the response graph entirely (every
	// /admin/* path returns 404 when off).
	AdminUI AdminUIOptions

	// ConfigSource is the path the TOML config was loaded from. The CLI
	// stamps this from `parsec serve --config <path>`; the library
	// facade carries it through to the manifest for operator visibility.
	// Embedders typically leave it empty.
	ConfigSource string

	// Limiter, when non-nil, is the rate-limit backend used by every
	// gated surface (publish / subscribe / token-issue). When nil and
	// RateLimits has any non-zero bucket, parsec.New builds one:
	// RedisLimiter when RedisClient is set, MemoryLimiter otherwise.
	Limiter ratelimit.Limiter

	// RateLimits configures the default per-subject budgets for each
	// gated bucket. The zero value (every Limit.Rate == 0) means
	// "unlimited" — no gating, no Redis traffic.
	RateLimits ratelimit.RateLimits

	// PerChannelPublishLimits, when non-empty, overrides RateLimits.Publish
	// for channels whose name matches a configured pattern. Keys are
	// channel-name patterns (see channels.ParsePattern grammar); values
	// are the Limit applied to publishes to channels matching that
	// pattern. parsec.New compiles each pattern at boot — an invalid
	// pattern aborts startup so misconfigurations fail loud. Per-rule
	// keys are namespaced separately from the default bucket, so a
	// hot channel cannot drain the global publish budget.
	PerChannelPublishLimits map[string]ratelimit.Limit

	// PerChannelSubscribeLimits mirrors PerChannelPublishLimits for the
	// subscribe bucket. The subscribe authorizer resolves the most-
	// specific rule on every subscribe attempt; the per-rule key is
	// namespaced (`subscribe-channel:<rule-raw>:<subject>`) so a hot
	// channel cannot drain the global subscribe budget. An invalid
	// pattern aborts startup with PARSEC_INVALID_ARGUMENT.
	PerChannelSubscribeLimits map[string]ratelimit.Limit

	// OIDCConfig, when non-nil, enables the OIDC bridge: management
	// RPC requests carrying an IdP-issued ID token bearer are
	// translated into a synthetic mgmt Claims and accepted alongside
	// HMAC-signed parsec mgmt tokens. nil = OIDC disabled (default).
	//
	// See auth.OIDCConfig for the full shape. parsec.New discovers
	// the issuer at boot — discovery failures abort startup so
	// misconfigured deployments fail loud.
	OIDCConfig *auth.OIDCConfig

	// Region labels metrics and access logs with the operator's region
	// name (e.g. "us-east"). When empty no label is attached, so
	// single-region deployments stay label-free. The value is also
	// surfaced in the manifest so peers can discover topology.
	Region string

	// Peers is the informational list of peer region URLs. Surfaced in
	// the manifest so operators (and tooling like parsec-keys-sync) can
	// discover the multi-region topology. Parsec itself does no peer
	// validation or active replication against this list — it is purely
	// declarative.
	Peers []string

	// SchemaHandler, when non-nil, is mounted at /parsec/schemas. The
	// HTTP surface delegates to the supplied handler so the embedder
	// can plug a schema.MemoryRegistry (or any other Registry impl)
	// without parsec.go importing the schema package itself.
	SchemaHandler http.Handler

	// JWKSHandler, when non-nil, is mounted at /parsec/jwks.json. The
	// handler is expected to serve the active KeyRing's asymmetric
	// public keys as an RFC 7517 JWKS document. Embedders that only
	// use HMAC signing should leave this nil — there is nothing to
	// publish, and the 404 prevents probing.
	JWKSHandler http.Handler

	// TokenBrokerHandler, when non-nil, is mounted under /parsec
	// (/parsec/token, /parsec/token/delegate, /parsec/revoke). The
	// embedder constructs a tokenbroker.Broker, configures its
	// Authenticator / Authorizer, and passes Broker.Handler() here.
	TokenBrokerHandler http.Handler

	// RevocationStore, when non-nil, is consulted on every private
	// channel subscribe attempt. The subscribe authorizer checks the
	// access token's JTI and the user's blanket revoke timestamp; a
	// match denies the subscribe with PARSEC_AUTH_DENIED. Pair this
	// with a tokenbroker.Broker so revoke RPCs from operators land
	// somewhere both the broker and the subscribe path can read.
	RevocationStore tokenbroker.RevocationStore

	// TelemetryHandler, when non-nil, is mounted at /parsec/metrics
	// and serves the aggregated JSON snapshot. The native /metrics
	// Prometheus endpoint is unaffected.
	TelemetryHandler http.Handler

	// Cache, when non-nil, is the request-hash cache exposed to
	// embedder code via Parsec.Cache(). Parsec itself does NOT
	// consult this cache on the hot path — the field exists so
	// embedders share one cache instance across the library, the
	// telemetry source, and any sinks/handlers they wire. Leave
	// nil to disable; pass cache.NewMemoryCache or cache.NewRedisCache
	// to enable.
	//
	// When nil and RedisClient is set, parsec.New auto-builds a
	// cache.RedisCache against the same client (sharing the
	// connection pool). Pass an explicit cache.NoopCache instance
	// to opt out of the auto-build.
	Cache cache.Cache
}

Options configures a Parsec instance. All fields are optional.

func (Options) OIDCEnabled

func (o Options) OIDCEnabled() bool

OIDCEnabled reports whether the OIDC bridge is configured. Convenience shorthand for the manifest layer.

type Parsec

type Parsec struct {
	// contains filtered or unexported fields
}

Parsec is the library facade. Construct with New, drive with Run, publish with Publish.

func New

func New(opts Options) (*Parsec, error)

New constructs a Parsec instance. The broker is created but not started; call Run on the returned instance to boot the centrifuge Node and the sweeper goroutine.

func (*Parsec) AccessLogger

func (p *Parsec) AccessLogger() *slog.Logger

AccessLogger returns the slog logger the HTTP surface should use for access logs. Falls back to Logger when nothing explicit was set.

func (*Parsec) AdminUIOptions

func (p *Parsec) AdminUIOptions() AdminUIOptions

AdminUIOptions returns the embedded admin SPA configuration. Used by the HTTP surface to decide whether to mount the /admin/* routes.

func (*Parsec) Broker

func (p *Parsec) Broker() *broker.Broker

Broker returns the underlying broker handle.

func (*Parsec) Cache added in v0.3.0

func (p *Parsec) Cache() cache.Cache

Cache returns the configured request-hash cache, or nil when no cache was wired (the default). Embedders share this instance so the library, the telemetry source, and any sinks/handlers consult one view of cached state. See docs/src/ops/cache.md.

func (*Parsec) CacheBackend added in v0.3.0

func (p *Parsec) CacheBackend() string

CacheBackend reports the cache implementation in use: "" when no cache is wired, "memory" / "redis" / "noop" for the bundled impls, and "custom" for an embedder-supplied Cache. Surfaced in the manifest.

func (*Parsec) CheckPublishLimit added in v0.3.0

func (p *Parsec) CheckPublishLimit(ctx context.Context, subject string, channel channels.Name, override *ratelimit.Limit) (ratelimit.Decision, error)

CheckPublishLimit is CheckRateLimit specialised for the publish bucket with per-channel rule resolution: the channel's name is matched against the operator's PerChannelPublish rules and the most-specific match wins. When no rule matches, the global publish default applies. Per-rule and per-token-override are honored in that order — override beats the rule.

The bucket key namespaces channels: rule-matched calls use `publish-channel:<rule-raw>:<subject>`, default calls use `publish:<subject>` (unchanged). That keeps a hot channel from eating the global budget.

func (*Parsec) CheckRateLimit

func (p *Parsec) CheckRateLimit(ctx context.Context, bucket, subject string, override *ratelimit.Limit) (ratelimit.Decision, error)

CheckRateLimit charges one event against bucket:subject and reports the decision. When no limiter is configured the call returns AllowDecisionUnlimited without touching state. When override is non-nil and tighter than the configured default, the override applies — surfacing per-token rate-limit claims.

func (*Parsec) CheckSubscribeLimit added in v0.3.0

func (p *Parsec) CheckSubscribeLimit(ctx context.Context, subject string, channel channels.Name, override *ratelimit.Limit) (ratelimit.Decision, error)

CheckSubscribeLimit is CheckRateLimit specialised for the subscribe bucket with per-channel rule resolution. The channel's name is matched against the operator's PerChannelSubscribe rules and the most-specific match wins; when no rule matches, the global subscribe default applies. override is honored when tighter (or when the default + matched rule are both unlimited).

Bucket keys mirror CheckPublishLimit: rule-matched calls use `subscribe-channel:<rule-raw>:<subject>`, default calls use `subscribe:<subject>`. Per-rule namespacing prevents a hot channel's subscribers from eating the global subscribe budget.

func (*Parsec) CompositeVerifier

func (p *Parsec) CompositeVerifier() *auth.CompositeVerifier

CompositeVerifier returns the composed HMAC+OIDC verifier, or nil when OIDC is disabled. Surface code that wraps mgmt bearer validation should check for non-nil first and fall back to Verifier() when nil.

func (*Parsec) ConfigSource

func (p *Parsec) ConfigSource() string

ConfigSource returns the path the TOML config was loaded from, or "" when no file was used (flag/env or embedded configuration).

func (*Parsec) CreatePrivate

func (p *Parsec) CreatePrivate(subjectID, name string, ttl time.Duration, scopes []auth.Scope) (Credentials, error)

CreatePrivate mints a private channel and returns the access + refresh token pair. Pattern grants in scopes are stamped into both tokens; pass nil for "exact-match channel only".

func (*Parsec) DLQ

func (p *Parsec) DLQ() sinks.DLQ

DLQ returns the dead-letter queue. Never nil — when no DLQ is configured (and no Redis client is present) parsec.New constructs an in-memory MemoryDLQ.

func (*Parsec) DLQBackend

func (p *Parsec) DLQBackend() string

DLQBackend reports "memory", "redis", or "custom" depending on which backend is wired. Exposed in the manifest.

func (*Parsec) GenerateKey

func (p *Parsec) GenerateKey() (auth.Key, error)

GenerateKey mints a new HS256 key (joins the ring as verify-only) and persists if StateDir is configured.

func (*Parsec) GenerateKeyAlg added in v0.3.0

func (p *Parsec) GenerateKeyAlg(alg auth.Alg) (auth.Key, error)

GenerateKeyAlg mints a new key of the requested algorithm. After generation: if the freshly-added key is asymmetric and the embedder did not wire an explicit JWKSHandler, parsec auto-installs one so the next /parsec/jwks.json hit picks the new public material up.

func (*Parsec) Issuer

func (p *Parsec) Issuer() *auth.Issuer

Issuer returns the token issuer.

func (*Parsec) JWKSHandler added in v0.3.0

func (p *Parsec) JWKSHandler() http.Handler

JWKSHandler returns the optional JWKS handler mounted at /parsec/jwks.json. Convenience: when Options.JWKSHandler is nil and the live ring carries at least one asymmetric key, parsec.New auto-wires auth.JWKSHandler against the ring — operators who add an Ed25519 key get a JWKS endpoint for free.

func (*Parsec) KeyRing

func (p *Parsec) KeyRing() *auth.KeyRing

KeyRing returns the live ring. Mutations go through the dedicated methods (GenerateKey / PromoteKey / RetireKey) so persistence stays in sync — surface code should not call ring.Promote directly.

func (*Parsec) KeyringPath

func (p *Parsec) KeyringPath() string

KeyringPath returns the on-disk path of the keyring file, or "" if the instance runs with an ephemeral / programmatic ring.

func (*Parsec) KeyringStore

func (p *Parsec) KeyringStore() auth.KeyRingStore

KeyringStore returns the configured KeyRingStore, or nil when the instance runs with an explicit/ephemeral ring. Surface code (the CLI `keys import` adapter) calls this to push an externally produced snapshot through the store so the watch event fires on every node.

func (*Parsec) Limiter

func (p *Parsec) Limiter() ratelimit.Limiter

Limiter returns the resolved rate-limit backend, or nil when no limiting is configured. Surface code calls this at request time.

func (*Parsec) Manager

func (p *Parsec) Manager() *channels.Manager

Manager returns the channel lifecycle manager.

func (*Parsec) Metrics

func (p *Parsec) Metrics() *metrics.Metrics

Metrics returns the Prometheus collector bundle. Surface code (HTTP middleware, sink wrappers, broker hooks) reaches for this rather than constructing its own registry.

func (*Parsec) MetricsBearerToken

func (p *Parsec) MetricsBearerToken() string

MetricsBearerToken returns the configured bearer for the /metrics route. Empty means /metrics is unguarded.

func (*Parsec) OIDCEnabled

func (p *Parsec) OIDCEnabled() bool

OIDCEnabled reports whether an OIDC issuer is wired. Used by the manifest layer.

func (*Parsec) OIDCIssuer

func (p *Parsec) OIDCIssuer() string

OIDCIssuer returns the configured OIDC issuer URL, or "" when OIDC is disabled. Used by the manifest layer.

func (*Parsec) OIDCVerifier

func (p *Parsec) OIDCVerifier() *auth.OIDCVerifier

OIDCVerifier returns the OIDC verifier, or nil when OIDC is disabled. Exposed primarily for tests + the manifest.

func (*Parsec) OTLPEndpoint

func (p *Parsec) OTLPEndpoint() string

OTLPEndpoint returns the configured OTLP endpoint or "" if tracing is off.

func (*Parsec) OpenPublic

func (p *Parsec) OpenPublic(name string, ttl time.Duration) (*channels.Channel, error)

OpenPublic creates or re-opens a public channel by parsed name. TTL of 0 uses the manager default. Fossil-delta encoding is off by default; enable it after the open call with Manager().SetDelta(name, true).

func (*Parsec) Peers

func (p *Parsec) Peers() []string

Peers returns a defensive copy of the configured peer region URLs. Always non-nil-safe: returns nil when no peers were configured. The list is purely informational — parsec.New does no validation.

func (*Parsec) Persistence

func (p *Parsec) Persistence() string

Persistence returns "redis" when the channel registry is Redis-backed, or "in-memory" otherwise. Exposed to manifests.

func (*Parsec) PromoteKey

func (p *Parsec) PromoteKey(id string) error

PromoteKey makes id the active signing key.

func (*Parsec) Publish

func (p *Parsec) Publish(ctx context.Context, name string, data []byte) (broker.PublishResult, error)

Publish ships data to a managed channel.

func (*Parsec) PublishOrSink

func (p *Parsec) PublishOrSink(ctx context.Context, name string, data []byte, sinkName string, to sinks.Recipient, msg sinks.Message) (bool, error)

PublishOrSink publishes to ch if there is at least one live subscriber, otherwise falls back to the named sink.

func (*Parsec) RateLimits

func (p *Parsec) RateLimits() ratelimit.RateLimits

RateLimits returns the normalized default budgets. Exposed to manifest + service code so /manifest can advertise the configured ceilings.

func (*Parsec) RefreshAccess

func (p *Parsec) RefreshAccess(refreshToken string) (RefreshResult, error)

RefreshAccess verifies refreshToken, enforces rotation policy via the RefreshStore, and mints a fresh access (and, when the token carries a JTI, a fresh refresh in the same family).

Rotation rules:

  1. Tokens without JTI are legacy: mint a new access only. The store is untouched.
  2. Tokens with JTI consult the store. A revoked family or an already-redeemed JTI returns PARSEC_AUTH_DENIED. A reused JTI additionally revokes the family so subsequent siblings fail even before the JTI check.
  3. On success, both tokens are reissued: the new refresh shares the old FID, a fresh JTI, and refresh TTL bounded by the channel TTL and MaxRefreshTTL.

func (*Parsec) RefreshAccessCtx added in v0.3.0

func (p *Parsec) RefreshAccessCtx(ctx context.Context, refreshToken string) (RefreshResult, error)

RefreshAccessCtx is RefreshAccess with an explicit context. The context is forwarded to the RefreshStore so Redis-backed stores pick up the caller's cancellation + deadline. Most callers should use RefreshAccess; this overload exists for the RPC adapter.

func (*Parsec) Region

func (p *Parsec) Region() string

Region returns the configured region label, or "" when single-region (no label) mode is active. Surfaced in the manifest and used as a constant metric label by parsec.New.

func (*Parsec) ReloadKeys

func (p *Parsec) ReloadKeys() error

ReloadKeys re-reads the keyring from its backing store. Returns an error if no store is configured (i.e. explicit ring or ephemeral).

func (*Parsec) RetireKey

func (p *Parsec) RetireKey(id string) error

RetireKey removes id from verification.

func (*Parsec) RevocationStore added in v0.3.0

func (p *Parsec) RevocationStore() tokenbroker.RevocationStore

RevocationStore returns the optional revocation store the subscribe authorizer consults on every private channel subscribe. Used by the manifest layer to surface the on/off state to operators.

func (*Parsec) Run

func (p *Parsec) Run(ctx context.Context) error

Run starts the broker, the sweeper, the manager↔broker event bridge, and (when StateDir is set) the keyring file watcher. Blocks until ctx is done.

func (*Parsec) SchemaHandler

func (p *Parsec) SchemaHandler() http.Handler

SchemaHandler returns the optional handler mounted at /parsec/schemas, or nil when no schema registry was wired.

func (*Parsec) SinkRetryConfig

func (p *Parsec) SinkRetryConfig() sinks.RetryConfig

SinkRetryConfig returns the normalized retry config used by PublishOrSink. Per-sink overrides are not reflected here — call Options().PerSinkRetry directly for that.

func (*Parsec) Sinks

func (p *Parsec) Sinks() *sinks.Registry

Sinks returns the registry.

func (*Parsec) TelemetryHandler

func (p *Parsec) TelemetryHandler() http.Handler

TelemetryHandler returns the optional aggregated /parsec/metrics handler, or nil when telemetry aggregation is not wired.

func (*Parsec) TokenBrokerHandler

func (p *Parsec) TokenBrokerHandler() http.Handler

TokenBrokerHandler returns the optional handler mounted under /parsec (token, token/delegate, revoke), or nil when no broker was wired.

func (*Parsec) Tracer

func (p *Parsec) Tracer() trace.Tracer

Tracer returns the active tracer. The default-off path returns a no-op tracer that never allocates.

func (*Parsec) TracerShutdown

func (p *Parsec) TracerShutdown(ctx context.Context) error

TracerShutdown flushes any buffered spans and stops the exporter. Safe to call when tracing is off (returns nil).

func (*Parsec) TrustedProxies

func (p *Parsec) TrustedProxies() []net.IPNet

TrustedProxies returns the proxy CIDR list as configured. Empty means X-Forwarded-For is ignored.

func (*Parsec) Verifier

func (p *Parsec) Verifier() *auth.Verifier

Verifier returns the HMAC token verifier. Surface code that needs to accept either HMAC or OIDC tokens should prefer CompositeVerifier when it is non-nil.

func (*Parsec) WebTransportOptions

func (p *Parsec) WebTransportOptions() WebTransportOptions

WebTransportOptions returns the WT listener configuration. Used by the HTTP surface to decide whether to mount the h3 endpoint.

type RefreshResult

type RefreshResult struct {
	AccessToken    string
	AccessExpires  time.Time
	RefreshToken   string
	RefreshExpires time.Time
	Rotated        bool
}

RefreshResult is what RefreshAccess returns. The RefreshToken / RefreshExpires fields are populated on rotation — a token carrying a JTI yields a fresh refresh in the same family. Legacy tokens (no JTI) leave them zero-valued so older clients keep working.

type Sink

type Sink = sinks.Sink

Re-exports for embedders.

type WebTransportOptions

type WebTransportOptions struct {
	// Addr is the UDP listen address for the QUIC/HTTP3 listener.
	Addr string
	// TLSCertFile + TLSKeyFile are the TLS cert pair. Required.
	TLSCertFile string
	TLSKeyFile  string
	// AllowedOrigins gates incoming WT requests by Origin header.
	// Empty slice = allow all (dev mode).
	AllowedOrigins []string
}

WebTransportOptions is re-exported from internal/server so embedders don't need to import the internal package for the WT config shape.

func (WebTransportOptions) Enabled

func (o WebTransportOptions) Enabled() bool

Enabled reports whether the WT options describe an active listener.

Directories

Path Synopsis
Package auth issues and verifies the HMAC-SHA256 JWTs that gate access to Parsec.
Package auth issues and verifies the HMAC-SHA256 JWTs that gate access to Parsec.
Package broker wraps the github.com/centrifugal/centrifuge OSS library in the small surface Parsec actually needs: boot a Node, publish to a named channel, authorize subscribes against the Parsec channel grammar, and expose presence + history for the management surface.
Package broker wraps the github.com/centrifugal/centrifuge OSS library in the small surface Parsec actually needs: boot a Node, publish to a named channel, authorize subscribes against the Parsec channel grammar, and expose presence + history for the management surface.
Package cache provides request-hash caching primitives for Parsec.
Package cache provides request-hash caching primitives for Parsec.
Package channels owns the channel naming contract and the channel manager (public / private namespaces, TTL bookkeeping, expiry).
Package channels owns the channel naming contract and the channel manager (public / private namespaces, TTL bookkeeping, expiry).
Package client is the envelope-aware Go client for Parsec.
Package client is the envelope-aware Go client for Parsec.
cmd
parsec command
Package main is the entry point for the parsec CLI binary.
Package main is the entry point for the parsec CLI binary.
parsec-gen command
Package main is the parsec-gen command: it reads a Parsec schema registry snapshot (either from a running registry over HTTP or from a JSON file on disk) and emits typed Go and TypeScript bindings for every registered channel pattern.
Package main is the parsec-gen command: it reads a Parsec schema registry snapshot (either from a running registry over HTTP or from a JSON file on disk) and emits typed Go and TypeScript bindings for every registered channel pattern.
parsec-keys-sync command
Package main is the parsec-keys-sync daemon: a tiny pub/sub bridge that mirrors Parsec keyring rotation events from one Redis (the source region) to N target Redis instances (the peer regions).
Package main is the parsec-keys-sync daemon: a tiny pub/sub bridge that mirrors Parsec keyring rotation events from one Redis (the source region) to N target Redis instances (the peer regions).
Package descriptor provides the self-describing manifest envelope used by every surface (CLI --json, Twirp Manifest RPC).
Package descriptor provides the self-describing manifest envelope used by every surface (CLI --json, Twirp Manifest RPC).
Package envelope is the wire-format contract every Parsec subscriber and publisher uses.
Package envelope is the wire-format contract every Parsec subscriber and publisher uses.
Package errors holds the typed Code system used across Parsec.
Package errors holds the typed Code system used across Parsec.
examples
browser command
Browser end-to-end demo.
Browser end-to-end demo.
embedded command
Embedded shows how a Go service uses parsec as a library.
Embedded shows how a Go service uses parsec as a library.
notify-or-email command
notify-or-email demonstrates PublishOrSink: if the user's browser session is still subscribed, ship to the channel; otherwise fall back to email.
notify-or-email demonstrates PublishOrSink: if the user's browser session is still subscribed, ship to the channel; otherwise fall back to email.
playground command
Command playground boots an in-memory parsec instance and serves an interactive browser UI that exercises publish, subscribe, token issuance, sinks, DLQ, rate limits, and manifest inspection.
Command playground boots an in-memory parsec instance and serves an interactive browser UI that exercises publish, subscribe, token issuance, sinks, DLQ, rate limits, and manifest inspection.
scenarios/agent-analysis command
Scenario 2 from the parsec brief:
Scenario 2 from the parsec brief:
scenarios/download-notify command
Scenario 1 from the parsec brief:
Scenario 1 from the parsec brief:
scenarios/feedback-flag command
Scenario 3 from the parsec brief:
Scenario 3 from the parsec brief:
scenarios/heartbeat command
Scenario 4 from the parsec brief:
Scenario 4 from the parsec brief:
scenarios/nightly-report command
Scenario 5 from the parsec brief:
Scenario 5 from the parsec brief:
scenarios/scoreboard command
Scenario 7 from the parsec brief:
Scenario 7 from the parsec brief:
internal
admin
Package admin embeds the parsec administrative single-page application.
Package admin embeds the parsec administrative single-page application.
cli
Package cli holds the urfave/cli/v3 command definitions for the parsec binary.
Package cli holds the urfave/cli/v3 command definitions for the parsec binary.
codegen
Package codegen reads a schema-registry snapshot and emits typed Go and TypeScript bindings for each registered ChannelPattern.
Package codegen reads a schema-registry snapshot and emits typed Go and TypeScript bindings for each registered ChannelPattern.
config
Package config defines the on-disk configuration schema for `parsec serve`.
Package config defines the on-disk configuration schema for `parsec serve`.
metrics
Package metrics centralizes the Prometheus collectors Parsec exposes.
Package metrics centralizes the Prometheus collectors Parsec exposes.
rpcclient
Package rpcclient is the CLI's adapter onto the generated Twirp JSON client.
Package rpcclient is the CLI's adapter onto the generated Twirp JSON client.
server
Package server mounts the parsec HTTP surface: the centrifuge websocket transport, the rpc Twirp-JSON handler, an SSE fallback for the CLI `subscribe` probe, and /healthz.
Package server mounts the parsec HTTP surface: the centrifuge websocket transport, the rpc Twirp-JSON handler, an SSE fallback for the CLI `subscribe` probe, and /healthz.
testutil
Package testutil contains shared scaffolding for Parsec test packages: boot a real Parsec instance with an ephemeral key, wrap it in a Service, and (optionally) expose the composed HTTP surface via httptest.Server.
Package testutil contains shared scaffolding for Parsec test packages: boot a real Parsec instance with an ephemeral key, wrap it in a Service, and (optionally) expose the composed HTTP surface via httptest.Server.
tracing
Package tracing wraps the OpenTelemetry SDK into a small surface Parsec actually uses: a tracer + a shutdown hook.
Package tracing wraps the OpenTelemetry SDK into a small surface Parsec actually uses: a tracer + a shutdown hook.
Package parsectest provides test helpers for code that embeds or integrates against the parsec library.
Package parsectest provides test helpers for code that embeds or integrates against the parsec library.
Package ratelimit gates Parsec's publish/subscribe/refresh-token paths behind configurable per-key budgets.
Package ratelimit gates Parsec's publish/subscribe/refresh-token paths behind configurable per-key budgets.
Package rpc is the parsec management RPC surface.
Package rpc is the parsec management RPC surface.
Package schema is the Parsec schema registry.
Package schema is the Parsec schema registry.
Package service implements the surface-agnostic business logic that the CLI and Twirp layers all call into.
Package service implements the surface-agnostic business logic that the CLI and Twirp layers all call into.
Classification helpers for the sink retry loop.
Classification helpers for the sink retry loop.
email
Package email is a thin SMTP sink.
Package email is a thin SMTP sink.
slack
Package slack is a Slack incoming-webhook sink.
Package slack is a Slack incoming-webhook sink.
webhook
Package webhook posts an arbitrary JSON envelope to a configured URL.
Package webhook posts an arbitrary JSON envelope to a configured URL.
Package telemetry exposes the aggregated /parsec/metrics view that the upgrade spec calls for.
Package telemetry exposes the aggregated /parsec/metrics view that the upgrade spec calls for.
Package tokenbroker is the policy point for issuing Parsec / Centrifugo connection tokens.
Package tokenbroker is the policy point for issuing Parsec / Centrifugo connection tokens.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL