pwruntime

package
v0.5.8 Latest Latest
Warning

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

Go to latest
Published: Sep 11, 2026 License: Apache-2.0 Imports: 41 Imported by: 0

Documentation

Overview

Package pwruntime contains the narrow runtime contract used by generated Popcorn Web code. Handwritten applications should normally import pw.

Index

Constants

View Source
const (
	APICatalogPath      = "/.well-known/api-catalog"
	APICatalogProfile   = "https://www.rfc-editor.org/info/rfc9727"
	APICatalogMediaType = "application/linkset+json"
	// APICatalogContentType carries the profile parameter RFC 9727 section 4.2
	// recommends, which is what tells a reader that this particular Linkset is
	// an API catalog rather than any other set of links.
	APICatalogContentType = APICatalogMediaType + `; profile="` + APICatalogProfile + `"`
	// APICatalogLinkHeader is the Link header a HEAD answer carries, per
	// section 2. It names this same location because the document is served
	// here rather than resolved to somewhere else, which section 3 permits.
	APICatalogLinkHeader = "<" + APICatalogPath + `>; rel="api-catalog"`
)

The api-catalog well-known endpoint of RFC 9727.

The path is the standard's rather than the deployment's, which is why it is a constant here and a boolean in the configuration: every other operational endpoint carries its path so an operator reading a settings file sees every address the deployment answers on, and this one is stated by its own name.

View Source
const (
	CORSDeclinedOrigin = "origin"
	CORSDeclinedMethod = "method"
	CORSDeclinedHeader = "header"
)

The reasons a request was not marked. They name which half did not match, so the record says what to fix; the response never says, because the browser was going to answer the question anyway and a precise refusal only helps a caller enumerate the policy.

View Source
const (
	LiveCloseDone  = "done"
	LiveCloseRetry = "retry"
)

The close reasons. A clean transport close cannot say whether the sources finished or a bound ended a healthy response, and the two deserve opposite client behaviour.

View Source
const (
	ResponseModeHeader = "Pw-Response-Mode"
	LiveResponseMode   = "live"
)

ResponseModeHeader is how a client asks for something other than a document on a route's own URL, and LiveResponseMode is the one value that does.

They are here rather than on either runtime because they are the wire between the browser runtime this framework ships and whichever half is serving. Two transports reading two different headers is a client that works against one build of an application and not the other, which is what happened.

View Source
const (
	FieldTimestamp   = "timestamp"
	FieldSeverity    = "severity"
	FieldMessage     = "message"
	FieldServiceName = "service_name"
	FieldTraceID     = "trace_id"
	FieldSpanID      = "span_id"
	FieldTraceFlags  = "trace_flags"
)

Reserved record fields. A user attribute may not take one of these names, because a consumer reading a stream has to be able to trust them.

View Source
const (
	CacheResultHit       = "hit"
	CacheResultMiss      = "miss"
	CacheResultStaleHit  = "stale_hit"
	CacheResultCoalesced = "coalesced"
)

Cache result attribute values, a closed set so that a hit rate has a denominator and a reader can enumerate the outcomes.

View Source
const (
	// RateLimitBackendMemory counts inside this process. It is correct on one replica
	// and enforces N times the configured limit on N of them.
	RateLimitBackendMemory = "memory"
	// RateLimitBackendRedis counts in a shared server, which is what a deployment
	// running more than one replica needs.
	RateLimitBackendRedis = "redis"
	// RateLimitBackendCloudflareKV counts in a Workers KV namespace, as an
	// estimate, per requirement:cloudflare-kv-backends.
	RateLimitBackendCloudflareKV = "cloudflarekv"
)

Rate limit backend names a deployment selects with ratelimit.backend.

View Source
const (
	// StorageBackendLocal keeps objects in a directory under the project,
	// which is what api:cli-dev and a test run on.
	StorageBackendLocal = "local"
	// StorageBackendS3 reaches an S3-compatible endpoint, including R2's S3
	// API from a process host.
	StorageBackendS3 = "s3"
	// StorageBackendR2 reaches an R2 bucket through a Worker binding, and
	// exists only inside a Cloudflare Worker.
	StorageBackendR2 = "r2"
)

Storage backends a bucket may name, per requirement:object-storage.

View Source
const (
	// UpdateHeaderPrefix yields Pw-Render, Pw-Manifest, and Pw-Build.
	UpdateHeaderPrefix = "Pw"
	// UpdateAttributePrefix names the boundary attributes generation writes and
	// the placeholder element the render option spells, so one document holds
	// one spelling rather than two.
	//
	// It is the module's default rather than this framework's brand because
	// routetree compiles a page tree's templates without the prefix option, so
	// branding it here would split a document's naming in exactly the way the
	// option exists to prevent. internal/pwgen names the same value.
	UpdateAttributePrefix = "tb"
	// UpdateGlobalName is the browser namespace the client update API installs.
	UpdateGlobalName = "popcornweb"
	// UpdatePathPrefix is where the framework's own endpoints answer.
	UpdatePathPrefix = "/_pw"
)

These are fixed rather than configured. They are contracts between this framework and the runtime it ships — the header namespace, the attribute prefix, the installed name, and the endpoint prefix all reach the browser as one configuration object, and a deployment changing one would be describing a framework it is not running.

They are here rather than on either runtime because the document a browser receives is the same document whichever transport wrote it, and the settings reduction that describes it is published by whichever half parsed the configuration.

View Source
const CSRFCookieName = "pw_csrf"

CSRFCookieName is the companion cookie the browser runtime reads a token from.

It is a constant rather than configuration because it is a contract between the framework and the shipped script, not a deployment choice. The runtime is a module script, so it cannot read a name off its own tag the way a classic script can; a renamed cookie would leave it looking for one that is not there, and nothing would say so.

View Source
const CSRFHeaderName = "X-CSRF-Token"

CSRFHeaderName is where the runtime puts the token. It is outside the framework's own header namespace because it is a name middleware already looks for.

View Source
const CSRFSecretBytes = 32

CSRFSecretBytes is the raw entropy of a session's CSRF secret. It matches the session token, because both are the same kind of unguessable value.

View Source
const DefaultConnectionGroup = "default"

DefaultConnectionGroup is the group name of a configuration that declares a single database without naming any group.

View Source
const DefaultContentSecurityPolicy = "script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'"

DefaultContentSecurityPolicy is the policy a project gets without naming one.

It restricts the four directives a web application can almost always accept, and leaves alone the ones it cannot: images, fonts, styles, and connections are unrestricted, so an ordinary page keeps working without anyone editing configuration.

  • script-src 'self' is the load-bearing one. It refuses inline event handlers, inline <script>, and javascript: URLs, which together are how an HTML-injection sink becomes running code. It also matters more here than in a framework without a browser runtime: the CSRF companion cookie is readable by script on purpose, so script that runs on this origin can mint a valid token. The framework's own runtime is a same-origin module tag and needs nothing else.
  • object-src 'none' closes <object> and <embed>, which route around script-src on some engines.
  • base-uri 'self' stops an injected <base href> from re-pointing every relative URL on the page, including the runtime's own.
  • frame-ancestors 'none' says what X-Frame-Options: DENY already says, in the header that is not deprecated.

A project that loads third-party script names its own policy. That is the conversation this default is for: a CSP that shipped empty was one nobody had.

View Source
const DefaultLiveManifestEntries = 64

DefaultLiveManifestEntries bounds an unbounded configuration's manifest parse. It is not a limit on the response: past it the extra claims are dropped and their boundaries are delivered rather than suppressed.

View Source
const DefaultRateLimitKeyPrefix = "pw:ratelimit:"

DefaultRateLimitKeyPrefix namespaces the keys this limiter owns.

View Source
const LiveMediaType = "application/x-ndjson"

LiveMediaType frames the record stream.

View Source
const LiveRetryHint = 2 * time.Second

LiveRetryHint is what a retry close suggests waiting.

View Source
const LocaleCookieMaxAge = 365 * 24 * 60 * 60

LocaleCookieMaxAge is how long a recorded choice lives. A year, because the choice is a preference rather than a session fact and re-asking a returning reader every month is the behaviour the cookie exists to remove.

View Source
const LocaleCookieName = "pw_lang"

LocaleCookieName is the cookie a reader's explicit choice is stored in.

It is separate from the preference cookie on purpose: requirement:user-preference-rendering forbids that cookie deciding text content, and language is content.

View Source
const MetricScope = "github.com/shibukawa/popcornweb"

MetricScope names the instrumentation scope of framework instruments, matching the scope its spans already carry.

View Source
const ReservedSignalPrefix = "pw."

ReservedSignalPrefix is this framework's signal namespace.

Every layer that produces signals reserves a prefix: the module holds tb., this framework holds pw., and an application uses what is left. The module cannot hold this one, because a signal constructor is called at a yield site inside a source and is not render-scoped, so it can reach no configured value.

What it protects is trust. The lifecycle names this framework's client runtime dispatches — a boundary settled, a live response opened, a delivery applied — arrive under this prefix, and a handler believes them precisely because application data has no route into the namespace. A source able to emit pw.delivery_applied could make a screen believe a render landed that never did.

It lives here rather than beside either live loop because both of them enforce it, and a prefix one backend reserved and the other did not would be a namespace an application could reach through the second one.

Variables

View Source
var ErrCrossGroupTransaction = errors.New("popcornweb: transaction cannot span two connection groups")

ErrCrossGroupTransaction reports a nested transaction naming a different connection group than the one already open.

View Source
var ErrSavepointUnsupported = errors.New("popcornweb: driver does not support savepoints")

ErrSavepointUnsupported reports that the configured driver cannot nest a transaction because it has no known savepoint support.

View Source
var ErrSignal = htmlbind.ErrSignal

ErrSignal matches any signal under errors.Is, for code that wants the classification without the value.

View Source
var ErrTransactionFailed = errors.New("popcornweb: transaction is no longer usable")

ErrTransactionFailed reports that a savepoint operation left the transaction in an unknown state, so no further work may be committed on it.

View Source
var ErrUnknownConnectionGroup = errors.New("popcornweb: unknown database connection group")

ErrUnknownConnectionGroup reports a group name that no configured connection carries. A group name is data, so the failure surfaces at the statement that depended on it rather than where the name was written.

View Source
var OpenAPIDocumentOrigin = ResponseHeader{Name: "Access-Control-Allow-Origin", Value: corsWildcard}

OpenAPIDocumentOrigin is the marking the generated OpenAPI document carries, whatever security.cors says and whether or not it is enabled at all.

This one endpoint is answered by what it is rather than by a policy a deployment writes. The document describes a contract already chosen for publication, holds nothing that varies per visitor, and is read by tools whose origins nobody can enumerate in advance: a documentation UI hosted somewhere else, a client generator, a linter in someone's CI.

It is safe on a protected document too. A wildcard forbids credentials, so a cross-origin page reading one that sits behind the authenticated-path check receives the unauthenticated answer and learns nothing it could not have learned by asking for the path itself.

No Vary goes with it: the answer is the same for every caller, which is what keeps the document shared-cacheable.

View Source
var UpdateBuildID = sync.OnceValue(func() string {
	if info, ok := debug.ReadBuildInfo(); ok {
		for _, setting := range info.Settings {
			if setting.Key == "vcs.revision" {
				return setting.Value
			}
		}
	}
	return ""
})

UpdateBuildID identifies the binary that rendered a page.

It answers the same question the live delivery stream's version does: was the page asking rendered by this build? A page from another one holds client state this binary cannot vouch for — a template it does not have, a runtime that renders differently — and none of that is visible in a validator.

The two differ on an unstamped binary, and the difference is deliberate. Live delivery reports nothing there, which disables its check rather than inventing a value that would differ per process and reload every client on every restart. An update falls back to the module's per-process identity instead, which costs a complete document after a restart and never a wrong delta. A frozen screen is worse than a re-transferred page.

Functions

func AcceptsHTML

func AcceptsHTML(accept string) bool

AcceptsHTML reports whether a client would rather have a page than a document, given its Accept header.

An absent, empty, or unreadable header is not a preference, so it takes the API representation — which is also what a client that sent no opinion at all is most likely to be.

It is here rather than in either runtime because it reads a header and decides a representation, and two builds of one application answering the same request differently is exactly what a shared rule prevents.

func AdmitLive

func AdmitLive(key string, maximum int) (func(), bool)

AdmitLive takes one slot for a client and returns the release. A maximum of zero or less admits everything.

The key is the caller's to choose, and it is the authenticated subject where there is one and the remote address otherwise: an anonymous screen is still one browser, and grouping every anonymous client together would refuse the second visitor of the day.

func AppendProblemJSON

func AppendProblemJSON(dst []byte, problem Problem) []byte

AppendProblemJSON appends the RFC problem document to dst.

It is here because it was written by hand in each runtime, and the two copies were one response body described twice — which is the shape of thing that stays identical until the day it does not, with nothing to say which client saw which. It is built by hand rather than marshalled because the document is flat and known, and this path must not fail: it is what answers when everything else already has.

The caller sanitizes and sets the headers; this writes the body alone.

func ApplyProblemHeaders

func ApplyProblemHeaders(header http.Header, problem Problem) error

ApplyProblemHeaders writes response metadata before a problem response is committed. Invalid optional metadata is returned to the caller and omitted.

It writes straight onto the caller's header rather than through the detached map ProblemHeaders builds: this runs on every error response, and the common problem carries zero or one header.

func CSRFHTMLRequest added in v0.5.2

func CSRFHTMLRequest(secFetchDest, accept string) bool

CSRFHTMLRequest reports whether a safe request is expected to render HTML, given its Sec-Fetch-Dest and Accept header values. Browsers send either an HTML Accept value or a document navigation target; a generic */* request does not justify allocating session state merely in case the handler might render a form. Both transports call this one function so the answer cannot drift.

func CSRFSafeMethod added in v0.5.2

func CSRFSafeMethod(method string) bool

CSRFSafeMethod reports whether a method is one the cross-site check lets through — GET, HEAD, and OPTIONS, the set HTTP defines as not changing state. TRACE is deliberately absent: nothing in this framework routes it, and a state-changing handler bound to it would otherwise pass unchecked. Both transports call this one function so the set cannot drift.

func CSRFSecret

func CSRFSecret(ctx context.Context) (string, bool)

CSRFSecret returns the request's CSRF secret, if the request carries a session that has one.

func CSRFToken

func CSRFToken(secret string, random io.Reader) (string, error)

CSRFToken derives the value a browser receives from the session's secret.

The pad is fresh for every call, and the emitted bytes therefore differ on every response. That is the whole point: a stable token reflected into a compressed body beside attacker-influenced input is what a compression oracle extracts one byte at a time, which is why Rails and Django both mask.

The pad travels in the clear because it has to: verification recomputes the expected value from it. Knowing the pad reveals nothing, since producing the rest of the token still requires the secret.

func ClientAddress

func ClientAddress(ctx context.Context, r *http.Request) string

ClientAddress returns the resolved caller address for the request.

It falls back to the request's peer for a chain that never resolved one, which is what a handler served outside the framework middleware stack sees.

func Compose

func Compose[Handler any](handler Handler, frames []Frame[Handler]) Handler

Compose wraps handler in frames, outermost first by slot.

The sort is stable and equal slots keep their append order, so two frames registered at one number run in registration order rather than in whatever order the sort happened to produce. Both runtimes call this rather than sorting for themselves, because the ordering rule is the thing that has to be identical and a second implementation of it is a second chance to differ.

func Config

func Config[T any](ctx context.Context) (T, bool)

func ConnectionExecutor

func ConnectionExecutor(ctx context.Context) (sqlbind.SQLExecutor, bool)

ConnectionExecutor returns the pool-level statement surface of the effective group, undecorated and outside any transaction. It exists for framework storage that holds one executor for the process lifetime, such as the rdb session backend; request statements resolve through SQLExecutor instead.

func DB

func DB(ctx context.Context) (*sql.DB, bool)

DB returns the pool of the effective group, which is the group pinned by SelectDB, otherwise the group of an active transaction, otherwise the default group.

A connection whose engine bypasses database/sql has no *sql.DB, so DB reports false for it rather than fabricating a handle; ConnectionExecutor is the surface that exists on every connection.

func DBDriver

func DBDriver(ctx context.Context) (string, bool)

DBDriver reports the driver scheme of the effective connection, which dialect-specific storage needs before it issues SQL.

func DatabasesReady

func DatabasesReady(parent context.Context, resources Resources) bool

DatabasesReady reports whether every configured connection answers.

It is here rather than in either runtime because readiness is a fact about the process rather than about the request that asked: the same probe on either transport must give the same answer, and a second implementation could disagree about the timeout or about which connections count.

The bound is one second. A readiness probe that hangs is worse than one that answers unavailable, because an orchestrator waiting on it cannot tell a slow database from a wedged process.

func DeclaredLocales

func DeclaredLocales() []string

DeclaredLocales returns the declared tags in declaration order. It reports nil when no generated message package is linked, which is every project that has not adopted requirement:application-i18n.

func DigestKeyMaterial added in v0.5.2

func DigestKeyMaterial(configured string) int

DigestKeyMaterial measures the key material a configured validator key carries: a base64 value counts as its decoded length, anything else as its raw bytes. It is the one measure both the startup floor and LiveDigestKey use, so the two cannot drift on what counts as long enough.

func ErrorType

func ErrorType(err error) string

ErrorType is the error.type attribute value for an error.

It reports the type name and never the message. A message carries values — a path, an identifier, a bind value policy:query-log-safety keeps off a span — and an attribute built from one is an unbounded series wearing a closed set's name.

func ExpectedCSRFToken

func ExpectedCSRFToken(secret, presented string) string

ExpectedCSRFToken rebuilds the token a correct client would have sent, using the pad carried by the one that arrived.

The comparison stays an equality against a value this framework computed, which is what lets the masked form work with a verifier that only compares. A malformed token yields an empty string, so it cannot match and cannot panic.

func FirstValue

func FirstValue(values []any) any

FirstValue is the variadic-to-optional shim the constructors share.

func FormatInt

func FormatInt(locale Locale, value int) string

FormatInt renders an integer argument for a message, grouped for the locale.

Plural selection reads the value and never this string, so a locale whose grouping is unknown still selects the right form.

func LangSegment

func LangSegment(ctx context.Context) string

LangSegment is the provider behind the path-segment binding: the tag under LocaleModePath and the empty string otherwise.

An empty value collapses the separator before it where it is written into a URL attribute, so one template serves every mode: "/{lang}/about" is /ja/about where the locale is in the path and /about where it is not.

func LangTag

func LangTag(ctx context.Context) string

LangTag is the provider behind the ordinary string binding: the resolved tag, never empty, in every mode. It is what a document language attribute and a localized asset path are written with.

func LiveDigest

func LiveDigest(key, html []byte) string

LiveDigest is the validator of one delivery: what a screen holds, and what it returns on its next connection so the server can leave it alone.

Keyed, because it travels in a request header on every reconnect, and a request header is the most widely logged thing between a browser and a handler. An unkeyed digest there is a stable fingerprint of the region's content, and a live region with few possible renderings — a status badge, a queue depth, a seat count — is enumerable from a proxy log by anyone who can render the same page. Keying costs one HMAC per delivery and takes the fingerprint away.

Truncated to twelve bytes. The digest decides only whether to skip a transfer the client would have discarded, so the wrong answer costs one region one stale rendering, and ninety-six bits is far past where that becomes the system's most likely failure.

func LiveDigestKey

func LiveDigestKey(configured string) []byte

LiveDigestKey keys the delivery validators, or reports nil where suppression has to be off. A configured validator key is used where there is one, and a per-process key otherwise, so suppression works in a single-instance deployment that configured nothing.

A configured key carrying fewer than 32 bytes of material is treated as if it were absent and the random per-process key is used instead. The digest keeps a proxy or log observer from enumerating a low-entropy live region by its digest, and a guessable key is no key at all — so a short one must not key it, even on the Live-without-Update path where the startup floor does not run.

func LiveLifetime

func LiveLifetime(maximum time.Duration, jitterPercent int) time.Duration

LiveLifetime spreads a configured maximum around its value.

A fixed lifetime resynchronizes every client on every cycle, so one restart produces a herd that then repeats forever; a client cannot fix that with backoff, because it never chose the moment it was closed.

func LocaleAlternateLinks(r *http.Request, origin string) string

LocaleAlternateLinks renders the hreflang alternates of the current page.

It reads the same data the switcher does, so the two cannot drift. A mode with no per-locale URL produces nothing, because there is no URL to point at.

func LocaleMiddleware

func LocaleMiddleware(next http.Handler) http.Handler

LocaleMiddleware resolves the request's locale before the handler runs and records the Vary the route's mode implies.

Resolution happens here rather than lazily at the first message because headers are final before the body under flow:initial-streaming-render, and a Vary decided mid-render is decided too late.

A project declaring no locale is passed through untouched.

func LocalePath

func LocalePath(locale Locale, mode LocaleMode, path string) string

LocalePath builds the URL of a path in a locale.

It is the Go-side counterpart of the template binding: a redirect Location, a mail body, or a push deep link is composed here rather than in markup. The empty-prefix case is handled the same way, so a caller never branches on the mode.

func LogUpdateRefusal

func LogUpdateRefusal(ctx context.Context, failure UpdateFailure)

LogUpdateRefusal records a refused update request.

Both runtimes install it as their failure hook, so one refusal reads the same whichever transport served it. Version skew is the ordinary case — a page loaded before a deploy asks for a component whose markup has changed, gets a 404, and reloads — so it is recorded rather than treated as a fault.

func NewCSRFSecret

func NewCSRFSecret(random io.Reader) (string, error)

NewCSRFSecret returns a session's CSRF secret, encoded the way every other opaque value in this framework is.

func NewRedirect

func NewRedirect(location string, status int) error

NewRedirect returns the value. The status comes from a named constructor rather than from a caller, so there is nothing here to validate: a redirect answering a status no browser follows would render the page it meant to leave, and no constructor can spell one.

func NormalizeHTTPMethod added in v0.5.2

func NormalizeHTTPMethod(method string) string

NormalizeHTTPMethod maps a request method onto the OpenTelemetry HTTP semantic-convention verb set, collapsing anything outside it to "_OTHER".

A metric label must not carry an unbounded value. Both net/http and fasthttp accept any RFC 7230 token as a method, and the in-process aggregator never evicts a series, so a client sending a stream of distinct arbitrary method tokens would otherwise add a permanent time series per token until the process runs out of memory. The original method still belongs on a span as http.request.method_original; it never belongs on a metric label.

func NormalizeScheme added in v0.5.2

func NormalizeScheme(scheme string) string

NormalizeScheme bounds a request scheme to the two an HTTP server metric can carry. An absolute-form target or a crafted URI could otherwise put an arbitrary scheme on the url.scheme label; anything that is not https is reported as http so the label stays two-valued.

func ParseLiveManifest

func ParseLiveManifest(value string, key []byte, maximum int) map[string]string

ParseLiveManifest reads what the client claims each boundary is showing.

The bound is on entries rather than on bytes, and exceeding it drops the extra claims rather than refusing the request: the cost of a dropped claim is one region re-transferred, and refusing would cost the whole stream.

func ProblemHeaders

func ProblemHeaders(problem Problem) (http.Header, error)

ProblemHeaders returns a detached set of response metadata for either HTTP transport runtime.

func PublishBotSettings

func PublishBotSettings(settings BotSettings)

PublishBotSettings records the resolved configuration for whichever runtime reads it.

func PublishChainSettings

func PublishChainSettings(settings ChainSettings)

PublishChainSettings records the resolved configuration for whichever runtime builds a chain from it.

func PublishConfigLookup

func PublishConfigLookup(lookup func(reflect.Type) (any, bool)) func(reflect.Type) (any, bool)

PublishConfigLookup records how to read the resolved configuration. Whichever runtime owns configuration parsing calls it once.

It returns whatever was published before, so a caller installing one temporarily can put that back rather than leaving nothing behind. Publishing nil is how a caller takes its own lookup away, and a process left with none answers every binding with its zero value.

func PublishUpdateSettings

func PublishUpdateSettings(settings UpdateSettings)

PublishUpdateSettings records the resolved configuration for whichever runtime reads it. The transport that resolved the configuration and the transport that serves the request need not be the same one.

func ReadClientAddress

func ReadClientAddress(ctx context.Context) string

ReadClientAddress returns the address the chain resolved, or the empty string where no frame resolved one.

It is the portable half of ClientAddress: the fallback that half takes needs a net/http request, and on the second transport the request value answers Value from the same store StoreClientAddress writes to, so there is nothing left to fall back to.

func RegisterCacheMetrics

func RegisterCacheMetrics(meter *metric.Meter)

RegisterCacheMetrics registers the data result cache observables.

The counters already exist on every store, incremented on the lookup path, and this reads them at collection instead of recording a second time. It is one registration for every store rather than one per store, because the set is rebuilt whenever its configuration fingerprint changes and a per-store registration would leave an instrument behind pointing at a store nobody uses.

The result is an attribute of a closed set rather than four instruments, so a reader dividing hits by the total finds both terms under one name. No ratio is computed here: a quotient without its denominator cannot tell a cache that is working from one nothing is eligible for.

func RegisterDatabaseMetrics

func RegisterDatabaseMetrics(meter *metric.Meter, pools func() []NamedPool)

RegisterDatabaseMetrics registers the connection pool observables.

They matter because an exhausted pool makes every request slow with no statement being slow, which is the one failure the per-statement duration histogram cannot show.

pools is read at collection rather than captured, so a process that opens its databases after this registration still reports them.

func RegisterHTMLDocument

func RegisterHTMLDocument(wrapper htmlbind.Wrapper)

RegisterHTMLDocument installs the document shell every rendered page is wrapped in. It is called once, from generated code, and a second call is a programming error rather than a replacement.

func RegisterHTMLErrorPage

func RegisterHTMLErrorPage(resolve HTMLErrorPage)

RegisterHTMLErrorPage installs the application's error page resolver.

func RegisterLocaleRouting

func RegisterLocaleRouting(routes []LocaleRoute, labels map[string]string, prefixDefault bool)

RegisterLocaleRouting records the per-prefix modes, the display labels, and whether the default locale carries a path prefix.

It is called by the generated message package beside RegisterLocales, because all of it is build configuration and none of it is knowable at run time.

func RegisterLocales

func RegisterLocales(tags []string, defaultTag string)

RegisterLocales records the declared locale list. The generated message package calls it from an init function, because that package is the one holding the tables this list indexes.

tags is in declaration order and defaultTag must be a member. Registering a second, different set panics: the tables were generated against one list, and continuing with another would read them at subscripts they were never built for. Registering the identical set again is a no-op, so a test binary linking the generated package twice is not a failure.

func RegisterPageActions

func RegisterPageActions(pattern string, actions ...PageAction)

RegisterPageActions publishes the actions reachable from one route.

The key is the route's own registered pattern, because that is what a request reports having matched, so nothing here re-matches a path the router already resolved.

The ordinary caller is a generated init beside the registry it reads, so a repeated pattern replaces rather than failing: regenerating is what produces the second call, and the two carry the same set.

func RegisterReloadable

func RegisterReloadable(components ...UpdateReloadable) error

RegisterReloadable publishes generated components as redraw endpoints.

A repeated kind is an error rather than a silent overwrite: the kind covers a component's name, parameters, and markup but not its package, so two identical templates in different packages produce the same one and the wrong component could answer.

The failure is also kept, because the ordinary caller is a generated init beside the component it registers, and an init has nowhere to return to.

func RegisteredConfig

func RegisteredConfig[T any]() (T, bool)

RegisteredConfig returns the resolved value for T, and whether one was registered and parsed.

func RegisteredHTMLDocument

func RegisteredHTMLDocument() []htmlbind.Wrapper

RegisteredHTMLDocument returns the shell chain, or nil where none was registered. The slice is freshly allocated per call, so a caller appending its own wrappers cannot reach the chain another request is rendering.

func RegisteredHTMLDocumentWith added in v0.5.2

func RegisteredHTMLDocumentWith(wrappers []htmlbind.Wrapper) []htmlbind.Wrapper

RegisteredHTMLDocumentWith returns the shell chain with wrappers appended after it, built in one allocation. It exists for the page render path: the plain accessor's copy carries no spare capacity, so appending a route's own wrappers to it re-allocated and re-copied on every rendered page. The result is freshly allocated for the same isolation reason as above.

func ReloadableRegistrationFailure

func ReloadableRegistrationFailure() error

ReloadableRegistrationFailure reports a registration that failed before main ran, whatever the configuration says: a collision is a defect in what generation produced rather than a deployment choice.

func RenderMessage

func RenderMessage(row []Segment, args ...string) string

RenderMessage assembles one row into a string.

The result is unescaped: a message is an ordinary string value and the template escapes it once, together with its arguments, for the position it lands in. Escaping here would double-escape every one. See .knowledge decision:message-code-shape escaping_stays_where_it_is.

A row of exactly one literal segment is returned directly, so a message carrying no argument costs a table read and no allocation. Generated code for a message with no parameters skips this function entirely and indexes a plain string table, which is the same saving one call earlier.

func RenderRichMessage

func RenderRichMessage(row []RichSegment, args ...string) []htmlbind.MessageSegment

RenderRichMessage assembles one row into the segment list system:tinybind interleaves with the markup a template bound to each hole.

Adjacent literal runs are merged so a hole's text arrives as one segment, which is what the interleaver writes between a bound element's tags.

This returns an upstream type on purpose. The generated function is the argument of htmlbind.Builder.Message, so the shape is fixed by that call rather than chosen here; policy:message-rich-text records why the framework produces the segments and upstream drives the interleaving.

func ReservedSignalName

func ReservedSignalName(name string) bool

ReservedSignalName reports a name this framework refuses to put on the wire.

The module refuses its own prefix inside its constructors, where a bad name becomes a value that faults when the runtime reads it. This one is enforced where a signal is written instead, for two reasons: a constructor cannot carry a message of its own, since the fault field is the module's and unexported; and a constructor is not a chokepoint at all, because an application calling the module's constructor directly bypasses any wrapper this framework offers. A live loop is the only path a signal reaches a client through.

func ResetPageActions

func ResetPageActions()

ResetPageActions clears the registry, for a test that publishes its own.

func ResolveConfig

func ResolveConfig[T any](ctx context.Context) T

ResolveConfig answers the way both runtimes' Config accessors do: the value carried on the request if a middleware put one there, and otherwise the process-wide resolved value.

The per-request value comes first because that is what lets a test or a development mode serve one request with configuration that is not the process's. Falling back rather than requiring it is what lets a caller outside a request — startup validation, or a runtime with no middleware of its own — read the same settings.

func RestoreReloadableForTest

func RestoreReloadableForTest(registry *UpdateRegistry, count int, failure error)

RestoreReloadableForTest puts back what ResetReloadableForTest returned.

func SQLExecutor

func SQLExecutor(ctx context.Context) (sqlbind.SQLExecutor, error)

SQLExecutor is used by generated .pw.sql context wrappers. It is the one seam every generated statement passes through, so query diagnostics attach here instead of in generated code or in a wrapping driver.

func SelectDB

func SelectDB(ctx context.Context, group string) context.Context

SelectDB pins group onto ctx so generated SQL and Transaction use it instead of the default group.

An unknown name is not rejected here. The returned context fails at its first executor resolution, DB call, or Transaction with ErrUnknownConnectionGroup.

func SetLocale

func SetLocale(w http.ResponseWriter, locale Locale)

SetLocale records a reader's explicit choice.

The value is validated against the declared set before it is written, because a cookie is client-writable under the plain mode of policy:cookie-value-protection and a decoded value is request input.

func SetSocketOriginPolicy

func SetSocketOriginPolicy(check func(origin, host string) bool)

SetSocketOriginPolicy records the check an application installed with the socket defaults. Both runtimes call it, so a policy installed once covers both, and passing nil restores this framework's own resolution.

func SocketOriginCheck

func SocketOriginCheck(handshake SocketHandshake, development bool) func(origin, host string) bool

SocketOriginCheck returns the check one handshake is judged by, or nil when the module's own default should stand.

The resolution runs per handshake rather than per message, which is once for the life of a connection, so it compiles the declared proxies and the trusted origin set there rather than caching a value a republished configuration would leave stale.

func StoreAuthentication

func StoreAuthentication(store ValueStore, authentication Authentication)

StoreAuthentication is WithAuthentication for a transport that cannot derive.

It is the write half of the pair described on ValueStore: the reader above is already portable, because a request value that answers Value from its own store reaches this capsule the same way a derived context does. Only the installation differs, and this is it.

func StoreCSRFSecret

func StoreCSRFSecret(store ValueStore, secret string)

StoreCSRFSecret records the request's CSRF secret on a request value that carries its own state, which is WithCSRFSecret for a transport that cannot derive a context.

func StoreClientAddress

func StoreClientAddress(store ValueStore, address string)

StoreClientAddress records the resolved caller on a request value that carries its own state, which is WithClientAddress for a transport that cannot derive a context.

An empty address is not stored, the same as the deriving form: the absence of a resolved address and a resolution to the empty string would otherwise be indistinguishable to every reader.

func StoreLogAttributes

func StoreLogAttributes(store ValueStore, attributes ...Attribute)

StoreLogAttributes is WithLogAttributes for a value store.

func StoreResources

func StoreResources(store ValueStore, resources Resources)

StoreResources writes the request resources into a value store, which is WithResources for a transport that cannot derive.

func SupportsExplain

func SupportsExplain(driver string) bool

SupportsExplain reports whether driver has a known plan-only EXPLAIN form. An unsupported driver keeps the query log and loses only the plan.

func SupportsSavepoint

func SupportsSavepoint(driver string) bool

SupportsSavepoint reports whether driver may nest transactions.

func SwapHTMLDocument

func SwapHTMLDocument(chain []htmlbind.Wrapper) []htmlbind.Wrapper

SwapHTMLDocument installs a shell chain and returns what was there.

It exists for tests, which install a document and must put back what they found; the compare-and-swap above admits one registration and no undo, which is right for an init and unusable for a test. Passing nil clears it.

func Transaction

func Transaction(ctx context.Context, fn func(context.Context) error) error

Transaction executes fn with the active transaction stored in its context. The outermost call begins a real transaction; a nested call opens a savepoint, so an inner failure rolls back only the inner work and leaves the outer transaction usable.

The transaction runs on the effective group of ctx, so SelectDB names a connection group for a whole transaction exactly as it does for a single statement, and nothing here can move a transaction to another group.

func ValidateAPICatalogOrigin added in v0.5.4

func ValidateAPICatalogOrigin(value string) (string, error)

ValidateAPICatalogOrigin accepts an absolute scheme-and-host origin and returns it without a trailing slash. The empty value is legal and means the links are written relative, per the note on buildAPICatalog.

func VerifyCSRFToken

func VerifyCSRFToken(secret, presented string) bool

VerifyCSRFToken reports whether presented unmasks to secret.

It exists so a caller that does not route through the update options still compares in constant time rather than with ==.

func WithAuthentication

func WithAuthentication(ctx context.Context, authentication Authentication) context.Context

WithAuthentication installs the verified authentication result while preserving every other runtime resource already present on ctx. Only framework authentication middleware calls it.

func WithCSRFSecret

func WithCSRFSecret(ctx context.Context, secret string) context.Context

WithCSRFSecret carries the session's CSRF secret for the framework code that emits and verifies tokens. The session middleware sets it; nothing else does.

func WithClientAddress

func WithClientAddress(ctx context.Context, address string) context.Context

WithClientAddress records the address of the caller rather than of the relay in front of it, resolved once per request against this deployment's declared proxies.

It is recorded rather than recomputed because more than one consumer needs it — a rate limit bucket, a live subscription bound — and two resolutions of one question are one answer that drifts.

func WithLocale

func WithLocale(ctx context.Context, locale Locale) context.Context

WithLocale pins a resolved locale onto ctx. Locale resolution runs once per request, before the first byte, because policy:locale-vary-correctness needs the answer while headers are still open and flow:initial-streaming-render closes them before the body renders.

func WithLocaleMode

func WithLocaleMode(ctx context.Context, mode LocaleMode) context.Context

WithLocaleMode records the mode the matched route declared.

func WithLogAttributes

func WithLogAttributes(ctx context.Context, attributes ...Attribute) context.Context

WithLogAttributes adds stable request attributes to every record taken from ctx afterwards, preserving the other runtime resources already installed.

func WithLogBackend

func WithLogBackend(ctx context.Context, backend *LogBackend) context.Context

WithLogBackend replaces only the emission policy, which is what a test needs to capture records without rebuilding every other resource.

func WithResources

func WithResources(ctx context.Context, resources Resources) context.Context

func WriteLiveClose

func WriteLiveClose(w io.Writer, scratch []byte, reason string, retryAfter time.Duration) ([]byte, error)

WriteLiveClose is always the last record.

func WriteLiveDelivery

func WriteLiveDelivery(w io.Writer, scratch []byte, content htmlbind.Content, digest string) ([]byte, error)

WriteLiveDelivery writes one delivery, carrying the validator the client stores and returns on its next connection.

func WriteLiveHead

func WriteLiveHead(w io.Writer, scratch []byte, build string, head []string) ([]byte, error)

WriteLiveHead opens the stream, on the record every other update response opens with. It carries the head a delivery may need, because a delivery whose content reaches a component the document never carried would otherwise install nothing and paint unstyled.

func WriteLiveRecord

func WriteLiveRecord(w io.Writer, record []byte) ([]byte, error)

WriteLiveRecord writes one newline-terminated record and flushes it. A delivery the client cannot see until a buffer fills is a delivery that did not happen.

func WriteLiveSignal

func WriteLiveSignal(w io.Writer, scratch []byte, signal htmlbind.Signal) ([]byte, error)

WriteLiveSignal writes one signal: a name the client looks up in the table it registered while the page loaded, and the payload the source encoded.

It carries no boundary id, no validator and no revision, because a signal addresses no region. It is dispatched rather than applied, so the suppression and manifest bookkeeping every delivery goes through has nothing to say about it, and a client that skips a malformed one desynchronizes nothing.

The name is escaped rather than written through: it reaches a client as a lookup key, and the payload is appended exactly as the generated encoder produced it, which htmlbind already escaped for a script context as well as a JSON one.

Types

type APICatalogSettings added in v0.5.4

type APICatalogSettings struct {
	Enabled bool
	// Origin is the absolute scheme-and-host the links are built from. Empty
	// writes them relative rather than guessing one; see buildAPICatalog.
	Origin string
	// OpenAPI, APIDoc and APIDocPath are the document and the UI over it;
	// Health is the liveness probe. Each empty setting removes its link.
	OpenAPI    string
	APIDoc     string
	APIDocPath string
	Health     string
}

APICatalogSettings are the endpoints the catalog links, which is every endpoint the deployment already configured.

Nothing here is authored for the catalog. RFC 9727 describes collating API metadata as the expensive step of publishing one, and that step is absent because the three targets below are the three the settings file already names.

type Attribute

type Attribute = otel.Attribute

Attribute is one scalar key-value pair on a record.

It is the same type contrib/otel puts on a span, so a value that annotates a span annotates a record without conversion, and neither surface can carry a structure whose encoding could fail while a request is being served.

func Bool

func Bool(key string, value bool) Attribute

func Duration

func Duration(key string, value time.Duration) Attribute

Duration records a duration in milliseconds, which is the unit both the OpenTelemetry log model and a human reading a terminal expect.

func Err

func Err(err error) Attribute

Err renders an error as a record attribute. A nil error yields an empty value instead of panicking, because a log site is the last place that should introduce a crash.

func Float64

func Float64(key string, value float64) Attribute

func Int

func Int(key string, value int) Attribute

Int is the convenience form of Int64 for the counts and sizes framework code mostly logs.

func Int64

func Int64(key string, value int64) Attribute

func String

func String(key, value string) Attribute

Attribute constructors, re-exported so application code needs no contrib import to write a record.

type Authentication

type Authentication struct {
	Authenticated bool
	// Subject is the stable local identity identifier.
	Subject string
	// Method is session, oidc, passkey, bearer, or an application-defined name.
	Method string
	// Principal is an optional application-defined typed value. Middleware must
	// freeze or copy mutable claims before installing it.
	Principal any
	// Scope carries optional tenant, role, or permission values.
	Scope           []string
	AuthenticatedAt time.Time
	ExpiresAt       time.Time
}

Authentication is the immutable, protocol-neutral authentication result that middleware records for downstream handlers and authorization checks. The zero value is an explicitly unauthenticated request.

It never carries passwords, token bodies, cookie values, or provider secrets.

func RequestAuthentication

func RequestAuthentication(ctx context.Context) Authentication

Authentication reports the verified authentication state of the request. A request without authentication middleware reports the unauthenticated zero value.

type BotSettings

type BotSettings struct {
	// Enabled is html.bot_detection. It is off by default, which is why an
	// unpublished value is a usable answer rather than a missing one: a runtime
	// that finds nothing behaves as a deployment that never turned it on.
	Enabled bool
	// UserAgents are the additional tokens from html.bot_user_agents, already
	// lowercased, so classification stays a plain scan.
	UserAgents []string
}

BotSettings is the resolved bot-detection configuration in a form that names no transport, so one resolution serves both runtimes.

It travels the way the update settings do, and for the same reason: a configuration file is not a transport concern, and the runtime that read it and the runtime that serves the request need not be the same one.

func ResolvedBotSettings

func ResolvedBotSettings() BotSettings

ResolvedBotSettings returns the published configuration, or the zero value where nothing published one.

It reports no second value, unlike the update settings. Those have no safe default and a runtime that finds none must decline; this one does, because detection off is both the configured default and the conservative branch — every client gets the streamed render, which is correct for a browser and merely slower for a crawler.

type CORSConfig

type CORSConfig struct {
	Enabled bool `default:"false"`
	// Include and Exclude scope the policy, in the segment grammar the
	// authenticated-path check and the CSRF check already share.
	//
	// Scope earns its keep with credentials rather than without them: an
	// unscoped credentialed grant covers every authenticated page, every
	// side-effect-free GET the update runtime issues, and the live stream —
	// not the API the deployment meant to open. That is why AllowCredentials
	// refuses the default include rather than quietly inheriting it.
	Include []string `default:"[\"/**\"]" dependon:".enabled"`
	Exclude []string `env:"-" dependon:".enabled"`
	// AllowedOrigins are exact scheme://host[:port] values, or the single
	// literal "*".
	//
	// There is no pattern language. A subdomain wildcard is an expression in
	// framework configuration, which this framework refuses for the reason the
	// bearer admission policy gives: an expression is a second thing to get
	// right, evaluated on every request, whose mistakes are silent.
	AllowedOrigins []string `env:"-" dependon:".enabled"`
	// AllowCredentials lets a listed origin read a response the browser sent
	// cookies with.
	//
	// It grants a read and never a write. The CSRF token travels in a cookie
	// only same-origin script can read, so a cross-origin page cannot attach
	// the header the check wants and every unsafe request is refused whatever
	// this says.
	AllowCredentials bool `default:"false" dependon:".enabled"`
	// AllowedMethods are the methods a preflight admits. The default stops at
	// the three a browser can already send without one, so admitting a write is
	// something a deployment states rather than inherits.
	AllowedMethods []string `default:"[\"GET\",\"HEAD\",\"POST\"]" dependon:".enabled"`
	// AllowedHeaders are the request headers a preflight admits. The configured
	// CSRF header is added to this set on its own while credentials are on,
	// because a header that has to be remembered is the one that is forgotten.
	AllowedHeaders []string `default:"[\"Content-Type\",\"Authorization\"]" dependon:".enabled"`
	// ExposedHeaders are the response headers script may read.
	//
	// The default is the set framework frames write and no browser exposes on
	// its own: the correlation identifier, and the four fields a 429 carries to
	// say when to come back. Without them a cross-origin client cannot be told
	// to back off, and keeps retrying at the rate that limited it.
	ExposedHeaders []string `` /* 134-byte string literal not displayed */
	// MaxAge bounds how long a browser may cache one preflight. Browsers cap it
	// themselves — Safari at ten minutes, Chrome at two hours — so a larger
	// value is reduced rather than honoured, and the default is the smallest
	// cap so every engine keeps the same answer.
	MaxAge time.Duration `default:"10m" dependon:".enabled" help:"how long a browser may cache one preflight"`
}

CORSConfig is the cross-origin admission policy.

It lives in the shared leaf beside SecurityHeadersConfig for the reason that one does: this is arithmetic over configuration with no request in it, both chains read it, and one declaration means one set of binder tags with nothing that can drift between two readers of the same policy.

Enabled is false by default, and a deployment that turns it on names the origins in the same breath. An admission policy with an empty list is a frame that marks nothing, which reads as cross-origin access that is not there.

func DefaultCORS

func DefaultCORS() CORSConfig

DefaultCORS returns the shipped defaults: off, and admitting the reads a bearer API needs once turned on.

func (CORSConfig) Validate

func (c CORSConfig) Validate() error

Validate rejects a policy that cannot mean what it says.

Every refusal here is a configuration a browser would answer by dropping the response, or a grant wider than the deployment can have meant. Both fail at startup rather than per request, because the failure mode of a wrong CORS policy is a network error in somebody else's browser.

type CORSDecision

type CORSDecision struct {
	// Preflight is true when the frame answers 204 and stops. The rest of the
	// chain never sees the request: a preflight carries no cookie, no
	// Authorization and no token, so every frame below would read it as an
	// anonymous caller asking for something it may not have.
	Preflight bool
	// Headers are set on the response before anything downstream commits one,
	// so a refusal written by any frame below carries them and its status
	// reaches the caller instead of an opaque network error.
	Headers []ResponseHeader
	// Vary is appended to the response's Vary header.
	Vary []string
	// Declined names which half did not match, empty when nothing did.
	Declined string
	// Origin is the caller's Origin, carried for the record.
	Origin string
}

CORSDecision is what one request produced.

It is a value rather than a set of calls into a response, because the two transports write headers differently and decide identically. Everything below is what to write; nothing here writes it.

type CSRFConfig

type CSRFConfig struct {
	Enabled   bool     `default:"false"`
	Include   []string `default:"[\"/**\"]" dependon:".enabled"`
	Exclude   []string `env:"-" dependon:".enabled"`
	FormField string   `default:"_csrf" dependon:".enabled"`
	Header    string   `default:"X-CSRF-Token" dependon:".enabled"`
	// CookieName is the companion cookie carrying the masked token the browser
	// runtime reads. It is never HttpOnly, because the runtime has to read it.
	//
	// It belongs here rather than to the session cookie policy: the secret is a
	// registered session slot like any other, and writing this cookie is the
	// check's own job rather than something the session does on its behalf.
	CookieName     string   `default:"pw_csrf" dependon:".enabled" help:"companion cookie carrying the token the browser runtime reads"`
	TrustedOrigins []string `env:"-" dependon:".enabled"`
	// TTL bounds the companion cookie the runtime reads. The secret itself is
	// bounded by the session slot that holds it.
	TTL time.Duration `default:"12h" dependon:".enabled" help:"lifetime of the companion token cookie"`
}

CSRFConfig is the cross-site check's configuration.

It lives in the shared leaf for the reason the security headers do: both chains read it, none of it names a transport, and one declaration means one set of binder tags and nothing that can drift between two readers of the same policy.

Enabled is false by default. A project turns it on together with the include patterns that say what it covers, because a middleware installed over nothing reads as protection that is not there.

func DefaultCSRF

func DefaultCSRF() CSRFConfig

DefaultCSRF returns the shipped defaults: off, and covering everything once turned on.

type CacheConfig

type CacheConfig struct {
	Enabled bool `default:"false" help:"reuse what a fetch returned for equal keys"`
	// Stores is the array-of-tables form. An element takes no CLI option and no
	// environment variable, because its identity is its position in the file.
	Stores []CacheStoreConfig `dependon:".enabled" help:"cache store set, one element per store"`
}

CacheConfig is the named store set, configured as [[cache.stores]].

A store is configured rather than constructed because it outlives every request, carries an operational size, and one day may address a process outside this one. That is the same set of properties a database pool has, so it is configured the way one is.

type CacheKey

type CacheKey = cachekeybind.CacheKey

CacheKey is what a Memo key type implements: the type's identity followed by the framed encoding of every field marked with the cache tag.

It is system:tinybind's interface rather than one of ours, so a key method emitted by that generator satisfies this without an adapter. A key may also be written by hand against the same package.

It is a method rather than a shape this framework walks because walking would mean reflection, and because the set of fields an entry depends on is exactly what has to be visible for the cache to be correct.

type CacheStats

type CacheStats struct {
	Hits      int64
	Misses    int64
	Coalesced int64
	StaleHits int64
	Entries   int
}

CacheStats is what one store has answered, for a diagnostic or a test.

type CacheStore

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

CacheStore is a handle to one configured store.

It holds no request state, so one handle serves every request and may be resolved once at setup or per call. The scope and the trace come from the context passed to each operation instead.

The typed operations are package functions rather than methods because a Go method may not declare its own type parameters. When that changes they become Get, Has, and Set on this type, and no call site that already holds a handle has to move. cache_go127.go carries them written already, behind the build tag for the release expected to allow them.

func MemoStore

func MemoStore(ctx context.Context, name string) (*CacheStore, error)

MemoStore resolves a configured store by name.

A disabled section returns no store and no error, so removing caching from a deployment edits no call site: every operation on a nil store falls through to its fetch. A name the set does not hold is an error naming what is configured, because the alternative is a project that believes it caches.

The set is immutable once built, so an application whose store name is static may resolve once during setup and hold the handle, which turns an unknown name into a startup failure rather than a first-request one.

func (*CacheStore) Get added in v0.5.1

func (s *CacheStore) Get[K CacheKey, T any](ctx context.Context, key K, fetch func(context.Context) (T, error)) (T, error)

Get returns what fetch produced for this key, reusing a stored result while it is fresh and coalescing concurrent misses onto one fetch.

A nil store, or a private store reached by an anonymous request, calls fetch and returns. No call site branches on whether caching is on: a nil pointer is a legal receiver, and the check is here.

The fetch receives a context detached from every waiter, which is what lets one request's cancellation leave the shared work alone. Do not capture the request context inside the closure instead: that would pin the fetch to whichever caller happened to miss first.

func (*CacheStore) Has added in v0.5.1

func (s *CacheStore) Has[K CacheKey](ctx context.Context, key K) bool

Has reports whether this key currently has a fresh entry. A stale one answers false, because the useful question here is whether the held value is current rather than whether a read would block.

It is racy by nature — the entry may expire between the answer and the next read — so it answers a diagnostic or a decision to skip expensive work, never control flow that assumes the following read hits.

func (*CacheStore) Invalidate added in v0.5.1

func (s *CacheStore) Invalidate[K CacheKey](ctx context.Context, key K)

Invalidate drops one entry, taking the key the read took.

func (*CacheStore) InvalidateScope added in v0.5.1

func (s *CacheStore) InvalidateScope(scope string)

InvalidateScope drops everything one reader holds, which the prepended scope makes a prefix rather than a scan.

func (*CacheStore) InvalidateTag added in v0.5.1

func (s *CacheStore) InvalidateTag(tag string)

InvalidateTag drops everything a tag names.

It is the axis a prefix cannot serve: the scope comes first, so every entry of one key type across all readers is not a range. A tag is the reverse index that answers it.

func (*CacheStore) Name

func (s *CacheStore) Name() string

Name reports which configured store this handle addresses.

func (*CacheStore) Set added in v0.5.1

func (s *CacheStore) Set[K CacheKey, T any](ctx context.Context, key K, value T) error

Set writes an entry without consulting one, which is how a writer refreshes what it just made wrong.

It bypasses the fetch and with it the coalescing, so not storing an error is the caller's to keep here. The lifetime comes from the store, so a call cannot mint a longer-lived entry than the configuration allows.

func (*CacheStore) Stats

func (s *CacheStore) Stats() CacheStats

Stats reports this store's counters. A hit count alone cannot distinguish a working cache from one nothing is eligible for, so every counter is reported together.

type CacheStoreConfig

type CacheStoreConfig struct {
	// Name is what a call site addresses this store by.
	Name string `help:"name this store is addressed by"`
	// Backend names where entries live. Only memory is implemented; any other
	// value is refused rather than ignored, because a store that silently fell
	// back to memory would be a shared cache that is not shared.
	Backend string `default:"memory" help:"where entries live; memory is the only implemented backend"`
	// TTL is how long an entry is fresh.
	TTL time.Duration `default:"1m" help:"how long an entry is fresh"`
	// Stale is how long past fresh a held entry may still answer while one
	// revalidation runs. Zero disables the window.
	Stale time.Duration `default:"0s" help:"how long a stale entry may answer while it revalidates"`
	// Scope is private or public. Private prefixes every key with the reader's
	// identity, which is the default for the same reason it is the render
	// cache's: a shared entry holding one reader's data is the failure that
	// does not degrade.
	Scope string `default:"private" help:"private keys entries per reader; public shares them"`
	// MaxEntries bounds the in-process store. Zero or less is unbounded.
	MaxEntries int `default:"1024" help:"maximum entries this store holds"`
	// FetchTimeout bounds a coalesced fetch. It exists because the shared fetch
	// runs on a context detached from every waiter, so nothing else would ever
	// stop it and a hung upstream would leak one goroutine per cold key.
	FetchTimeout time.Duration `default:"30s" help:"bound on a fetch running detached from its waiters"`
}

CacheStoreConfig is one store of the set.

type CacheTagger

type CacheTagger interface {
	CacheTags() []string
}

CacheTagger is the optional half of a key type: the tags whose invalidation drops this entry. It is ours rather than the generator's, because a tag is an invalidation policy and the key method is an identity.

A key type implementing nothing is invalidated by key or by scope alone.

type CaptureSink

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

CaptureSink collects records in memory instead of writing them anywhere.

It exists so a test can assert on what was logged without parsing formatted output, which is the difference between checking a fact and checking an encoder. It is safe for concurrent use.

func NewCaptureSink

func NewCaptureSink() *CaptureSink

NewCaptureSink returns an empty sink.

func (*CaptureSink) Emit

func (sink *CaptureSink) Emit(_ context.Context, record Record)

func (*CaptureSink) Records

func (sink *CaptureSink) Records() []Record

Records returns a copy of what has been captured so far.

func (*CaptureSink) Reset

func (sink *CaptureSink) Reset()

Reset discards everything captured so far.

type ChainSettings

type ChainSettings struct {
	// The three frames a deployment turns on and off.
	RequestID bool
	AccessLog bool
	Recovery  bool
	// RequestTimeout and MaxRequestBody install their frames when positive.
	RequestTimeout time.Duration
	MaxRequestBody int64
	// SecurityHeaders is installed when Enabled says so, which is the switch
	// the configuration keeps beside the values rather than a separate one.
	SecurityHeaders SecurityHeadersConfig
	// CORS is answered by the frame SecurityHeaders installs rather than by one
	// of its own, so either half being enabled is what puts that frame in the
	// chain. They are separate settings and one position.
	CORS CORSConfig
	// TrustedProxies are the networks whose forwarding headers this deployment
	// reads, as configured. They are strings rather than parsed networks
	// because that is what the configuration carries and parsing them is the
	// caller's, which already has to report a bad one against a config key.
	TrustedProxies []string
	// Health and Readiness are the paths the two probes answer on, empty when
	// a deployment turned one off.
	Health    string
	Readiness string
	// OpenAPI, APIDoc and APIDocPath are the document path, the UI kind, and
	// the path that UI is read at. All three empty means the chain answers no
	// documentation at all, which is the common case.
	OpenAPI    string
	APIDoc     string
	APIDocPath string
	// APICatalog and APICatalogOrigin are the RFC 9727 endpoint's switch and
	// the origin its links are built from; unset writes them relative. The path
	// is the standard's, so it is pwruntime.APICatalogPath rather than a
	// setting.
	APICatalog       bool
	APICatalogOrigin string
	// CSRF is the cross-site check's configuration, carried whole because the
	// check reads most of it: the scope patterns, the token names, the cookie
	// name and the lifetime.
	CSRF CSRFConfig
	// Public is the static asset configuration. The tree itself is not here
	// because an embed is a fact of the binary rather than of a settings file.
	Public PublicAssetSettings
	// RateLimit is the limiter's configuration, carried whole because a chain
	// builder reads most of it.
	//
	// Two frames come out of it, at two slots: the ceiling out where a refusal
	// costs least, and the identity bucket below whatever establishes
	// authentication. Neither can be the other's position.
	RateLimit RateLimitConfig
}

ChainSettings is what building the request chain needs from configuration, in a form that names no transport.

It is the fourth thing to travel this way, after the update settings, the bot settings and the configuration lookup, and for the same reason each time: a configuration file is not a transport concern, and the runtime that read it and the runtime that serves the request need not be the same one.

It is a flat value rather than the configuration structs themselves because those carry the binder's tags, the scaffold help text and the defaults, none of which a chain builder reads. What a chain builder reads is this.

func ResolvedChainSettings

func ResolvedChainSettings() (ChainSettings, bool)

ResolvedChainSettings returns the published configuration, and whether anything published one.

A runtime that finds none has no configuration to build a chain from and says so, rather than composing a chain out of zero values. Zero here would mean no recovery frame, no request ID, no security headers — a chain that serves requests and looks like a chain, with the frames a deployment configured silently missing.

type Connection

type Connection struct {
	DB *sql.DB
	// Native is the handle of an engine that bypasses database/sql. A caller
	// that needs a *sql.DB finds none on such a connection; statements run
	// through Executor and transactions through the scope.
	Native database.NativeDB
	// Driver is the resolved driver scheme, which decides savepoint support.
	Driver string
	// Group is the name this connection is addressed by.
	Group string
	// Label identifies this connection inside its group, as group#ordinal.
	Label string
	// ReadOnly marks a replica. It selects a read-only transaction and, once
	// the SQL runtime can classify statements, a read-only executor.
	ReadOnly bool
}

Connection is one configured pool of the connection set. Exactly one of DB and Native is set: DB for an engine served through database/sql, Native for one whose request-time path bypasses it.

func (*Connection) Close

func (connection *Connection) Close() error

Close releases the pool, whichever kind it is.

func (*Connection) Executor

func (connection *Connection) Executor() sqlbind.SQLExecutor

Executor is the pool-level statement surface of this connection, which is what the executor seam stores when no transaction is active.

func (*Connection) Ping

func (connection *Connection) Ping(ctx context.Context) error

Ping verifies the pool can reach its database.

func (*Connection) TransactionScope

func (connection *Connection) TransactionScope() *TransactionScope

TransactionScope prepares an inactive transaction scope over this connection's pool, carrying its group and read-only marking. Begin activates it. It exists for the test bridge, whose shared test transaction must run on whichever kind of pool the connection holds.

type ConnectionSet

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

ConnectionSet is the immutable set of connection groups owned by the framework. Selection inside a group is round robin.

func NewConnectionSet

func NewConnectionSet(defaultGroup string, connections []Connection) (*ConnectionSet, error)

NewConnectionSet groups connections by name in configuration order.

defaultGroup may be empty only when exactly one group is present.

func (*ConnectionSet) Close

func (set *ConnectionSet) Close() error

Close closes every pool and joins the failures.

func (*ConnectionSet) Collapsed

func (set *ConnectionSet) Collapsed() bool

Collapsed reports whether every group name resolves to one database.

A single-database configuration and a test both collapse, so application code that selects a replica group runs unchanged against one development sqlite file and against a reader-writer cluster.

func (*ConnectionSet) Connections

func (set *ConnectionSet) Connections() []*Connection

Connections lists every connection in configuration order, which is what the startup summary and shutdown need.

func (*ConnectionSet) Count

func (set *ConnectionSet) Count() int

Count reports how many connections are configured. It exists for callers that need only the number, which Connections would answer with a fresh slice per call.

func (*ConnectionSet) DefaultGroup

func (set *ConnectionSet) DefaultGroup() string

DefaultGroup is the group serving statements that pin no group of their own.

func (*ConnectionSet) Groups

func (set *ConnectionSet) Groups() []string

Groups lists the configured group names in configuration order.

func (*ConnectionSet) Has

func (set *ConnectionSet) Has(group string) bool

Has reports whether group is configured. A collapsed set answers for every name, because it has only one database to answer with.

type FieldError

type FieldError = tinybind.FieldError

FieldError describes a single field-level validation failure. It is the module's own type, already shared by both of its runtimes.

func Field

func Field(field, location, message string) FieldError

Field builds a field-level validation error for Validation.

type Frame

type Frame[Handler any] struct {
	Slot       Slot
	Name       string
	Middleware func(Handler) Handler
}

Frame is one positioned step of the chain.

It is generic over the handler because that is the one thing the two runtimes genuinely disagree about: net/http wraps an interface and fasthttp wraps a function. Everything else about a frame — where it sits, what it is called — is the same on both.

type HSTSConfig

type HSTSConfig struct {
	Enabled           bool          `default:"false"`
	MaxAge            time.Duration `default:"0s" dependon:".enabled"`
	IncludeSubdomains bool          `default:"false" dependon:".enabled"`
	Preload           bool          `default:"false" dependon:".enabled"`
}

HSTSConfig controls Strict-Transport-Security on verified HTTPS requests. The dependon on the HSTS field itself carries the headers switch down over this whole block, so nothing here names an absolute key.

type HTMLErrorPage

type HTMLErrorPage func(Problem) HTMLFragment

HTMLErrorPage renders an application error page from a problem. It names no transport, which is what lets one registration serve both runtimes.

func RegisteredHTMLErrorPage

func RegisteredHTMLErrorPage() HTMLErrorPage

RegisteredHTMLErrorPage returns the resolver, or nil where none was registered and a problem therefore takes its document form.

type HTMLFragment

type HTMLFragment = htmlbind.Fragment

HTMLFragment is a bound component ready to render. It is named here as well as on each runtime because an application file that registers an error page belongs to both builds, and a file naming a runtime does not.

type Level

type Level int8

Level is the severity of a log record.

The numbers are slog's, so a Level converts to a slog.Level without a table and a handler written against slog filters these records correctly. Trace is the one severity slog does not name; it sits one step below debug.

const (
	LevelTrace Level = Level(slog.LevelDebug) - 4
	LevelDebug Level = Level(slog.LevelDebug)
	LevelInfo  Level = Level(slog.LevelInfo)
	LevelWarn  Level = Level(slog.LevelWarn)
	LevelError Level = Level(slog.LevelError)
	// LevelOff is above every severity, so nothing passes the filter.
	LevelOff Level = 127
)

func (Level) String

func (level Level) String() string

String renders the severity as the lowercase token used by configuration.

type LiveDigester added in v0.5.2

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

LiveDigester is LiveDigest with the HMAC state built once and reset between records. A stream digests every boundary it sends, and each hmac.New allocates two SHA-256 states plus the key pads, so the loop that owns a response builds one of these instead. It is single-goroutine, like that loop.

func NewLiveDigester added in v0.5.2

func NewLiveDigester(key []byte) *LiveDigester

NewLiveDigester returns a digester over key, or nil for a nil key — the same "suppression off" answer LiveDigest gives, which Digest keeps by answering the empty string on a nil receiver.

func (*LiveDigester) Digest added in v0.5.2

func (d *LiveDigester) Digest(html []byte) string

Digest is the validator of one delivery, from the reused state.

type LiveWatchdog

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

LiveWatchdog closes a response that has run long enough or delivered nothing for long enough. Both bounds cancel the render context, which breaks every pull loop and lets each source observe the cancellation through its context.

func StartLiveWatchdog

func StartLiveWatchdog(cancel context.CancelFunc, lifetime, idle time.Duration) *LiveWatchdog

StartLiveWatchdog begins watching. A zero lifetime and a zero idle bound leave it inert, which is the configuration that asks for no bound at all.

func (*LiveWatchdog) Delivered

func (d *LiveWatchdog) Delivered()

Delivered reports activity, which restarts the idle bound.

func (*LiveWatchdog) Stop

func (d *LiveWatchdog) Stop()

Stop ends the watch.

type Locale

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

Locale identifies one locale the project declared. It is opaque because the value that matters is the dense index the generated message tables are addressed by; the tag is carried so a document, a URL, and a Content-Language header can name it.

A zero Locale is the explicit "no locale resolved" value. It is never a silent stand-in for the default, per policy:absent-rather-than-stubbed.

See .knowledge api:locale-accessors.

func DefaultLocale

func DefaultLocale() Locale

DefaultLocale returns the declared default. It reports the zero Locale when no message package is linked.

func LocaleContext

func LocaleContext(ctx context.Context) Locale

LocaleContext returns the locale resolved for this request.

Unlike ParseLocale it always answers: a request that reached a route with no declared mode, or a context with no resolution at all, reports the declared default. A caller rendering text has no useful branch on absence, and the alternative is every message call site handling a case the router already decided.

func MessageLocale

func MessageLocale(ctx context.Context) Locale

MessageLocale is the provider behind the implicit binding supplying every generated message symbol's leading argument.

It returns Locale rather than a tag so the generated catalog takes the type it declares. A typed binding cannot be written into markup, which is what keeps this value out of the positions LangTag and LangSegment serve.

See .knowledge data:locale-bindings.

func MustParseLocale

func MustParseLocale(tag string) Locale

MustParseLocale is ParseLocale for a tag the caller knows is declared, such as a constant in generated code. It panics on an undeclared tag.

func NewLocale

func NewLocale(tag string, index int) Locale

NewLocale builds a Locale for a declared tag at a known position.

It exists for generated code, which holds the tables this index addresses and therefore already knows every position. Handwritten code has ParseLocale, which validates against the registered set; this one does not, because a generated constant is initialized before init functions run and so before any registration could have happened.

index is the zero-based position in the declared list.

func ParseLocale

func ParseLocale(tag string) (Locale, bool)

ParseLocale resolves a tag against the declared set by RFC 4647 lookup: an exact match wins, then progressively shorter prefixes, so ja-JP finds ja.

It reports absence rather than substituting the default, per policy:absent-rather-than-stubbed. A caller that wants the default asks for it.

This is the entry point for a locale that did not come from a request — a value read from a user record before sending mail, or from a job payload.

func StripLocalePrefix

func StripLocalePrefix(path string) (string, Locale, bool)

StripLocalePrefix removes a leading locale segment from a path and reports the locale it named.

The prefix position is always read as a locale, so an undeclared tag there is not reinterpreted as an ordinary path segment: a route literally named /de/ would otherwise break on the day German is added.

func (Locale) Index

func (l Locale) Index() int

Index returns the position of this locale in the declared list. It is the subscript of every generated message table.

The zero Locale reports -1 rather than 0, so indexing with an unresolved locale panics at the read instead of silently serving the first declared language.

func (Locale) String

func (l Locale) String() string

String makes a Locale printable in a log or a test failure without reaching for Tag, and names the zero value rather than rendering as empty.

func (Locale) Tag

func (l Locale) Tag() string

Tag returns the BCP 47 tag, empty for the zero value.

func (Locale) Valid

func (l Locale) Valid() bool

Valid reports whether this value names a declared locale.

type LocaleChoice

type LocaleChoice struct {
	Locale Locale
	// Label is the display name written in that locale itself, declared in the
	// project's i18n block. It falls back to the tag, which is visibly wrong in
	// a switcher rather than silently wrong.
	Label string
	// URL is this same page in that locale, empty in a mode with no per-locale
	// URL.
	URL     string
	Current bool
}

LocaleChoice is one entry of a language switcher, and one alternate link.

The switcher, the hreflang alternates, and the canonical all read this, so a page cannot have its switcher and its alternates disagree. See .knowledge requirement:locale-switching-surface.

func LocaleChoices

func LocaleChoices(r *http.Request) []LocaleChoice

LocaleChoices reports the locales this page is available in.

A path-mode page carries a URL per locale, which is the same data the alternate links are built from. A cookie-mode page carries none, because switching there is a server action writing the cookie rather than a link.

func LocaleChoicesFor

func LocaleChoicesFor(path, rawQuery string, current Locale, mode LocaleMode) []LocaleChoice

LocaleChoicesFor is LocaleChoices over the values a request carries rather than over a request.

The switcher is identical on both transports, so the computation lives here and each transport's entry supplies its own path, query, and resolved locale. Duplicating it per transport is how the two would drift.

type LocaleMode

type LocaleMode uint8

LocaleMode is how a route decides its locale, declared per path prefix in the project's i18n block. It is carried on the request because it decides two things a handler cannot re-derive: whether a link carries a locale segment, and what the response varies on.

See .knowledge decision:locale-url-modes.

const (
	// LocaleModePath reads the locale from a URL path prefix. Two languages are
	// two URLs, so the response varies on nothing.
	LocaleModePath LocaleMode = iota
	// LocaleModeCookie reads a stored reader choice, then Accept-Language.
	LocaleModeCookie
	// LocaleModeHeader reads Accept-Language only, which is the mode an API
	// answering a native client uses.
	LocaleModeHeader
)

func LocaleModeContext

func LocaleModeContext(ctx context.Context) LocaleMode

LocaleModeContext reports the mode of the matched route.

A request that reached no declared route reports LocaleModeHeader, which is the mode that puts nothing in a URL. That is the safe default: emitting a locale segment for a route that has no prefixed form produces links that 404, while omitting one produces links that work and merely do not carry the language.

type LocaleRoute

type LocaleRoute struct {
	Prefix string
	Mode   LocaleMode
}

LocaleRoute binds one path prefix to the way its locale is decided. The list is registered by the generated message package, because the modes are declared in the project's build configuration.

type LogBackend

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

LogBackend is the process-wide emission policy: one severity floor and the set of destinations a record reaches.

More than one sink is how a development run keeps its terminal stream while also feeding a collector. A production run configured for OTLP normally holds one sink, because duplicate delivery is a cost nobody asked for.

func NewLogBackend

func NewLogBackend(minimum Level, sinks ...Sink) *LogBackend

NewLogBackend returns a backend that drops records below minimum and hands the rest to every sink in order. A backend with no sink is valid and silent.

func (*LogBackend) Enabled

func (backend *LogBackend) Enabled(level Level) bool

Enabled reports whether a record at level would reach any destination.

func (*LogBackend) Minimum

func (backend *LogBackend) Minimum() Level

Minimum reports the configured severity floor.

func (*LogBackend) Sinks

func (backend *LogBackend) Sinks() int

Sinks reports how many destinations a record reaches, which is what distinguishes exclusive routing from the development fan-out.

type Logger

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

Logger is the context-bound logger of the framework logging API.

It is a value: With returns a new one and never mutates the receiver, so a logger captured by a handler cannot be changed underneath it. The zero value is usable and discards everything, which is what keeps every accessor free to return a logger rather than nil.

func NewLogger

func NewLogger(ctx context.Context, backend *LogBackend, attributes ...Attribute) Logger

NewLogger binds backend and the trace correlation of ctx into a logger.

func ReadLogger

func ReadLogger(ctx context.Context) Logger

ReadLogger returns a logger bound to the emission policy of ctx, its stable request attributes, and the span active on it. It never returns a logger that cannot be called: a context with nothing installed still yields a usable one.

Acquire again inside a child span to correlate with that span, because the correlation is captured here rather than at each call.

func (Logger) Debug

func (logger Logger) Debug(message string, attributes ...Attribute)

func (Logger) Enabled

func (logger Logger) Enabled(level Level) bool

Enabled reports whether a record at level would reach any destination. Call it before assembling attributes that cost something to produce.

func (Logger) Error

func (logger Logger) Error(message string, attributes ...Attribute)

func (Logger) Info

func (logger Logger) Info(message string, attributes ...Attribute)

func (Logger) Log

func (logger Logger) Log(ctx context.Context, level Level, message string, attributes ...Attribute)

Log emits at an explicit level with the caller's context, which a sink may use for cancellation. There is deliberately no Fatal or Panic: logging must not decide whether the process lives.

func (Logger) SpanID

func (logger Logger) SpanID() string

SpanID reports the span this logger is correlated with, if any.

func (Logger) Trace

func (logger Logger) Trace(message string, attributes ...Attribute)

func (Logger) TraceID

func (logger Logger) TraceID() string

TraceID reports the trace this logger is correlated with, if any.

func (Logger) Warn

func (logger Logger) Warn(message string, attributes ...Attribute)

func (Logger) With

func (logger Logger) With(attributes ...Attribute) Logger

With returns a logger carrying attributes in addition to its own. A later duplicate key replaces an earlier one when the record is built.

type MetricGroups

type MetricGroups struct {
	HTTP   bool
	DB     bool
	Render bool
	Cache  bool
}

MetricGroups selects which instruments exist. A false group leaves its fields nil, which is what makes declining a group free rather than cheap.

type Metrics

type Metrics struct {
	// RequestDuration is http.server.request.duration. Its count answers how
	// many requests were served and its buckets answer the percentile, so no
	// counter sits beside it.
	RequestDuration *metric.Histogram
	// ActiveRequests is http.server.active_requests, the concurrency no
	// per-request record shows.
	ActiveRequests *metric.UpDownCounter
	// RequestBodySize and ResponseBodySize are the http.server body size
	// histograms. A request with no declared length records nothing rather than
	// zero.
	RequestBodySize  *metric.Histogram
	ResponseBodySize *metric.Histogram

	// QueryDuration is db.client.operation.duration, recorded on the same
	// resolver seam the statement span uses.
	QueryDuration *metric.Histogram

	// RenderDuration and RenderBytes describe one response per render mode,
	// which is the branch nothing outside this process can attribute a response
	// time to.
	RenderDuration *metric.Histogram
	RenderBytes    *metric.Histogram
	// BoundarySettle is how long a fallback held the screen. It carries no
	// boundary id: positional is safe on a span and unbounded across pages.
	BoundarySettle *metric.Histogram
	// LiveDelivery is the interval between consecutive deliveries of one
	// boundary, and LiveActive is how many subscriptions exist right now.
	LiveDelivery *metric.Histogram
	LiveActive   *metric.UpDownCounter
	LiveClosed   *metric.Counter

	// RenderCache counts component output cache operations, with a result
	// attribute rather than one instrument per outcome, because a reader
	// dividing them needs both under one name.
	RenderCache *metric.Counter
	// DataCache counts data result cache operations the same way.
	DataCache *metric.Counter
}

Metrics is the resolved framework instrument set installed by pw.

A nil value records nothing, and so does a nil field inside it: a group the configuration declined leaves its instruments nil, and every recording site is one nil comparison. It mirrors Tracing in being a resolved policy rather than the configuration struct, and it differs from Tracing in one way that matters — a recording here never consults whether a span is being sampled, because a count of the sampled fraction is not a count.

func MetricPolicy

func MetricPolicy(ctx context.Context) *Metrics

MetricPolicy returns the framework instrument set of ctx, or nil when nothing is recorded. It is one context lookup, the same one every other resource accessor makes.

func NewMetrics

func NewMetrics(meter *metric.Meter, groups MetricGroups) *Metrics

NewMetrics creates the instrument set for one meter.

Every name, unit, and attribute set of the http and db groups is the semantic convention's; the pw-prefixed ones are this framework's, one per question no external agent can answer.

type NamedPool

type NamedPool struct {
	Name string
	DB   *sql.DB
}

NamedPool is one connection pool and the label a metric groups it by.

type NumberFormat

type NumberFormat struct {
	// Group separates thousands, empty for a locale that does not group.
	Group string
	// Decimal separates the fractional part.
	Decimal string
}

NumberFormat is how one locale groups digits.

Only grouping and the two separators are modelled. Digit shaping, currency placement, and ordinal words each need their own tables, and a message argument is a count or a quantity rather than a formatted money value — the framework offering half of currency formatting would be worse than offering none.

func FormatNumber

func FormatNumber(locale Locale) NumberFormat

FormatNumber reports the grouping of a locale, falling back to the comma-and-period convention for one this build does not tabulate.

The fallback is a convention rather than a guess at the locale: a number that groups the wrong way is legible, and one that carries no separators at all is legible too, so neither failure is worth an error that stops a page.

type OtelSink

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

OtelSink emits records through an OpenTelemetry log provider.

func (*OtelSink) Emit

func (sink *OtelSink) Emit(ctx context.Context, record Record)

type PageAction

type PageAction struct {
	// Name is the exported Go function, which is what a script names.
	Name string
	// Path is its direct endpoint, which holds no path parameter and is
	// therefore a constant rather than something to build per request.
	Path string
}

PageAction is one server function reachable from a page's scripts.

func PageActionsFor

func PageActionsFor(pattern string) []PageAction

PageActionsFor returns what the route that matched publishes, or nothing.

Nothing is the ordinary answer: a project with no page tree registers none, a route whose package exports no handler has none, and both should cost a render exactly one map lookup.

type PreparedResources added in v0.5.4

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

PreparedResources is one injector frame's capsule, prepared at chain construction rather than per request.

The injectors hand every request the same Resources value, and prepareResources copied it to the heap once per request to do so. Most deployments' capsule is request-independent — the per-request fields prepare would add are a connection set's round-robin memo and the statement-wrapper cache, and a capsule without either is frozen after preparation, with every later change travelling through a derived copy. Such a capsule is prepared here once and shared by every request; one that does need per-request state keeps being prepared per request, unchanged.

func PrepareResources added in v0.5.4

func PrepareResources(resources Resources) PreparedResources

PrepareResources readies resources for an injector frame.

func (PreparedResources) Attach added in v0.5.4

Attach returns ctx carrying the capsule, the injector's WithResources.

func (PreparedResources) StoreOn added in v0.5.4

func (p PreparedResources) StoreOn(store ValueStore)

StoreOn records the capsule on a request value, the injector's StoreResources.

type Problem

type Problem struct {
	Status    int
	Title     string
	Code      string
	Message   string
	Fields    []FieldError
	Cause     error
	RateLimit *RateLimit
}

Problem is the application-facing RFC problem value.

It lives here rather than in pw because both transport runtimes have to build, inspect, and unwrap the same type. Two structs that agree today are two chances to disagree later, and the failure is silent: an errors.As that stops matching, a problem that no longer unwraps. The module reached the same conclusion for its own error types and put them in a leaf both of its runtimes alias.

This is not the module's Problem, which carries a code and a message and is the body its error constructors take. This one is the whole answer: the status a response gets, the title it shows, the fields that failed, and the cause that stays server-side.

func BadRequest

func BadRequest(values ...any) Problem

func Conflict

func Conflict(values ...any) Problem

func Forbidden

func Forbidden(values ...any) Problem

func InternalServerError

func InternalServerError(values ...any) Problem

func MapProblem

func MapProblem(err error) Problem

MapProblem turns any error into the problem a response describes.

Three cases, in order: an error that is already a Problem keeps everything it declared and gains the defaults it left out; an error the binding layer recognizes carries its own status, title and field failures across; anything else is an internal error, which is the only honest answer for a cause this package cannot classify.

It is here rather than in either runtime because both answer the same failures, and a status that differed between two builds of one application would be a difference nothing in either response explained.

func NewProblem

func NewProblem(status int, title string, value any) Problem

NewProblem builds one from a status, a title, and whatever the caller passed: nothing, another problem to carry through, an error to wrap, or a message.

func NotFound

func NotFound(values ...any) Problem

func PayloadTooLarge

func PayloadTooLarge(values ...any) Problem

func RateLimited

func RateLimited(rate RateLimit, values ...any) Problem

RateLimited builds a 429 problem carrying retry and quota metadata.

func SanitizeProblem

func SanitizeProblem(problem Problem) Problem

SanitizeProblem drops what a 5xx must never carry out of the process.

Applying it twice changes nothing, which is what makes it safe to put at each writer rather than at one entry point: a boundary that failed with no recover clause reaches a writer directly, and every path that can answer with a server error has to lose the cause on the way out.

func ServiceUnavailable

func ServiceUnavailable(values ...any) Problem

func TooManyRequests

func TooManyRequests(values ...any) Problem

func Unauthorized

func Unauthorized(values ...any) Problem

func Validation

func Validation(fields ...FieldError) Problem

Validation reports a 400 response carrying every detected field failure.

func (Problem) Error

func (p Problem) Error() string

func (Problem) Unwrap

func (p Problem) Unwrap() error

type PublicAssetSettings

type PublicAssetSettings struct {
	Enabled   bool   `default:"true"`
	Mount     string `default:"/public" dependon:".enabled"`
	ReadLocal bool   `default:"false" dependon:".enabled"`
	// SVGSandbox adds a sandbox Content-Security-Policy to an image/svg+xml
	// response. An SVG is the one served image that executes, and it executes
	// in the application origin on direct navigation, so this is on by default
	// and is off only for a project deliberately serving an interactive SVG
	// through object, embed, or a link.
	SVGSandbox bool `default:"true" dependon:".enabled"`
}

PublicAssetConfig controls the framework-owned static asset endpoint.

type QueryDiagnostics

type QueryDiagnostics struct {
	// Level is the severity of an ordinary statement record.
	Level Level
	// SlowLevel is the severity once a statement exceeds SlowThreshold.
	SlowLevel Level
	// SlowThreshold is the duration above which a statement is slow. Zero
	// disables slow detection, and with it EXPLAIN and reproduction.
	SlowThreshold time.Duration
	// BindValues allows argument values into the record. It is the only path by
	// which application row data reaches a framework SQL log.
	BindValues bool
	// Explain captures a plan-only EXPLAIN for a slow statement.
	Explain bool
	// Reproduction renders a paste-able rerun snippet for a slow statement.
	Reproduction bool
	// MaxSQLLength bounds the logged statement text.
	MaxSQLLength int
	// MaxValueLength bounds each logged argument value.
	MaxValueLength int
}

QueryDiagnostics is the resolved query diagnostics setting installed by pw.

A nil value disables the feature entirely, so SQLExecutor returns the bare database handle and no statement is timed.

type RateLimit

type RateLimit struct {
	Limit      uint64
	Remaining  uint64
	Reset      time.Time
	RetryAfter time.Duration
}

RateLimit is the retry metadata attached to a 429 problem. X-RateLimit-* fields are compatibility conventions; Retry-After is the standard signal.

func (RateLimit) Validate

func (r RateLimit) Validate() error

Validate rejects metadata that would contradict itself on the wire.

type RateLimitCloudflareKVConfig added in v0.5.5

type RateLimitCloudflareKVConfig struct {
	Binding   string `help:"KV namespace binding the Worker env carries"`
	KeyPrefix string `default:"pw:ratelimit:" help:"key space this limiter owns"`
}

RateLimitCloudflareKVConfig addresses the KV namespace a Worker counts in.

type RateLimitConfig

type RateLimitConfig struct {
	Enabled bool   `default:"false"`
	Backend string `default:"memory" enum:"memory,redis,cloudflarekv" dependon:".enabled" help:"counter storage: memory, redis, or cloudflarekv"`
	// Window is the period every count below is measured over. It is also the
	// burst granularity, because the algorithm is a fixed window, and it is
	// what X-RateLimit-Reset reports.
	Window time.Duration `default:"1m" dependon:".enabled" help:"period every count is measured over"`
	// PerSubject bounds one authenticated caller. Zero disables this bucket,
	// because such a caller is accountable and revocable by other means.
	PerSubject int `default:"600" dependon:".enabled" help:"requests one authenticated subject may make in a window; zero disables"`
	// PerAddress bounds one caller with no session, and has no off position.
	// It is the only bucket an unauthenticated flood meets, so an unlimited
	// value here is an absent control rather than a permissive one.
	PerAddress int `default:"300" dependon:".enabled" help:"requests one caller with no session may make in a window"`
	// Process is the total arrival ceiling, unkeyed. It is the only layer that
	// sees a distributed flood, since such a flood keeps every source under
	// PerAddress by construction.
	//
	// It defaults to zero rather than to a guess: the right value follows from
	// what a deployment can serve, and one set below real capacity refuses
	// legitimate traffic globally.
	Process int `default:"0" dependon:".enabled" help:"total arrivals allowed in a window, unkeyed; zero leaves only the identity buckets"`
	// Redis names the backend it belongs to, so a memory-counted deployment
	// reports no counter server. Backend already answers to Enabled, so the
	// switch is not repeated here.
	Redis RateLimitRedisConfig `dependon:".backend=redis"`
	// CloudflareKV names the KV namespace binding a Worker counts in, per
	// requirement:cloudflare-kv-backends. The count is an estimate there: KV
	// has no atomic increment and propagates in tens of seconds, which is the
	// precision a rate limit tolerates and a session does not.
	CloudflareKV RateLimitCloudflareKVConfig `key:"cloudflarekv" dependon:".backend=cloudflarekv"`
}

Config bounds how often one caller, and the process as a whole, may arrive within a window.

It deliberately declares no per-route rules. A per-operation quota belongs to an API gateway, and a pattern grammar with a precedence order would be most of the cost of this middleware for a capability the boundary in front of a normal deployment already sells.

func DefaultRateLimit

func DefaultRateLimit() RateLimitConfig

DefaultConfig returns the shipped defaults: off, and bounded once on.

func (RateLimitConfig) Validate

func (c RateLimitConfig) Validate() error

Validate rejects a configuration whose limits cannot bind.

type RateLimitRedisConfig

type RateLimitRedisConfig struct {
	DSN            string        `secret:"mask" env:"RATELIMIT_REDIS_DSN" help:"redis:// or rediss:// counter server"`
	KeyPrefix      string        `default:"pw:ratelimit:" help:"key space this limiter owns"`
	ConnectTimeout time.Duration `default:"5s" help:"bounds the startup ping and per-command deadlines"`
}

RedisConfig addresses the shared counter server.

type Record

type Record struct {
	Time       time.Time
	Level      Level
	Message    string
	Attributes []Attribute
	TraceID    string
	SpanID     string
	TraceFlags byte
}

Record is one finished log record handed to a Sink. Correlation is resolved before the record is built, so a Sink never inspects the context for it.

func (Record) Lookup

func (record Record) Lookup(key string) (Attribute, bool)

Lookup returns the attribute stored under key.

func (Record) Text

func (record Record) Text(key string) string

Text returns the string value stored under key, or the empty string when the key is absent or holds another kind.

type RedirectError

type RedirectError struct {
	// Location is the target, checked for navigability where it is written
	// rather than here, so one check covers both the returned and the written
	// form.
	Location string
	// Status is one of the five redirect codes.
	Status int
}

RedirectError carries a location and the status that sends the browser to it.

It is exported so a caller can inspect one with errors.As, and constructed through the framework's own constructors so the status cannot be a value no browser treats as a redirect.

func (RedirectError) Error

func (e RedirectError) Error() string

type ResolvedAPICatalog added in v0.5.4

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

ResolvedAPICatalog is a validated catalog and the document it answers with.

The two transports share it for the reason ResolveCORS is shared: a misconfiguration is an error before a port is bound, and neither transport computes a document of its own. The document is assembled once here, because it depends on the settings and on nothing about a request.

func ResolveAPICatalog added in v0.5.4

func ResolveAPICatalog(settings APICatalogSettings) (ResolvedAPICatalog, error)

ResolveAPICatalog validates the configuration and reduces it to what the frame writes.

A catalog with nothing to link is refused rather than served: the document would carry a status link and a self-referential item link and no link to any API description, which satisfies the section 4.1 requirement on no reading.

func (ResolvedAPICatalog) Document added in v0.5.4

func (r ResolvedAPICatalog) Document() []byte

Document returns the catalog. It is the same bytes for every caller.

func (ResolvedAPICatalog) Enabled added in v0.5.4

func (r ResolvedAPICatalog) Enabled() bool

Enabled reports whether the endpoint answers.

type ResolvedCORS

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

ResolvedCORS is a validated policy reduced to what a request needs.

Everything a response can carry is precomputed: the header values are joined once here rather than per request, and the two lookups a request performs are map reads.

func ResolveCORS

func ResolveCORS(config CORSConfig, csrfHeader string) (ResolvedCORS, error)

ResolveCORS validates a policy and reduces it to the values a response carries.

csrfHeader is the configured CSRF header name. It is admitted with the configured headers while credentials are on, because a credentialed origin that may write has to send it, and a name that must be remembered in a second place is the one that is forgotten. Admitting the name grants nothing on its own: the token check still runs, and still refuses a request that cannot present a value derived from the session's own secret.

func (ResolvedCORS) Decide

func (c ResolvedCORS) Decide(path, rawPath, method, origin, requestMethod, requestHeaders string) CORSDecision

Decide answers one request.

path and rawPath are the decoded and raw request paths; a path that cannot be matched unambiguously is left unmarked rather than refused, since deciding about a target whose identity depends on who resolves it is the mistake the canonical form exists to prevent.

func (ResolvedCORS) Enabled

func (c ResolvedCORS) Enabled() bool

Enabled reports whether the policy installs anything.

func (ResolvedCORS) RecordCORSDecline

func (c ResolvedCORS) RecordCORSDecline(ctx context.Context, decision CORSDecision, path string)

RecordCORSDecline writes the one account of a refusal that exists.

A declined request is served like any other: the status is 200 or 204, the access log records a request that worked, and the browser reports the failure to its own console and to nobody else. Unlike a CSP violation this is not a Reporting API type, so no report is ever delivered anywhere — the frame that declined is the only thing that knows, and this is it saying so.

The level is info rather than debug because the shipped severity floor is info, and a record nobody sees is not an improvement on no record. The bound above is what makes that affordable.

type ResolvedSecurityHeaders

type ResolvedSecurityHeaders struct {
	// Always is set on every response. None of these values depends on the
	// request, so they are computed once rather than per request.
	Always []ResponseHeader
	// HSTS is sent only on a request that arrived over HTTPS, and is empty when
	// the feature is off. The condition is about the connection rather than the
	// value, which is why this one is separate rather than in Always.
	HSTS string
}

ResolvedSecurityHeaders is a validated configuration reduced to what a response actually carries.

func ResolveSecurityHeaders

func ResolveSecurityHeaders(config SecurityHeadersConfig) (ResolvedSecurityHeaders, error)

ResolveSecurityHeaders validates a configuration and reduces it to the header set a response carries.

Both middleware chains call it at construction, so a misconfiguration is an error before the port is bound rather than a wrong header per request, and the two transports send the same headers because they are computed here rather than in either of them.

type Resources

type Resources struct {
	Configs map[reflect.Type]any
	// Log is the process emission policy. A nil backend falls back to a plain
	// stderr text handler so that a request served outside pw still logs.
	Log *LogBackend
	// LogAttributes are the stable request attributes every record carries,
	// such as the request correlation ID.
	LogAttributes []Attribute
	// DB is the pool of the default group. It stays the whole database for a
	// configuration that declares no connection set.
	DB *sql.DB
	// DBDriver is the driver scheme of DSN, used to decide savepoint support.
	DBDriver string
	// Connections is the configured connection set. A nil value means the
	// single DB above is the only database.
	Connections *ConnectionSet
	// Group is the group pinned by SelectDB. Empty selects the default.
	Group string

	// TxScope is the active transaction scope, installed by the framework only.
	TxScope *TransactionScope
	// Query enables development query diagnostics. A nil value leaves the
	// resolved executor undecorated.
	Query *QueryDiagnostics
	// Trace enables the framework's own spans. A nil value creates none.
	Trace *Tracing
	// Metrics is the framework instrument set. A nil value records nothing, and
	// it is independent of Trace: a deployment sampling one trace in ten still
	// counts every request.
	Metrics *Metrics
	// Authentication is the verified request authentication result, finalized
	// by authentication middleware before handler dispatch.
	Authentication Authentication
	// contains filtered or unexported fields
}

Resources is the immutable process/request state installed by pw.

func DeriveResources

func DeriveResources(ctx context.Context) Resources

DeriveResources copies the capsule a request carries, so a caller can change one field and store it back.

func (*Resources) Parent

func (r *Resources) Parent() *Resources

Parent returns the capsule this one was derived from, or nil for the request root capsule.

type ResponseHeader

type ResponseHeader struct{ Name, Value string }

ResponseHeader is one name and value to set on a response.

type RichSegment

type RichSegment struct {
	// Hole names the hole this segment fills, empty for an ordinary run.
	Hole string
	Lit  string
	Arg  uint8
}

RichSegment is a Segment that may also open a hole. A hole marker carries the translated text that belongs inside the template's markup.

type SecurityHeadersConfig

type SecurityHeadersConfig struct {
	Enabled            bool   `default:"true"`
	ContentTypeOptions bool   `default:"true" dependon:".enabled"`
	FrameOptions       string `default:"deny" dependon:".enabled"`
	ReferrerPolicy     string `default:"strict-origin-when-cross-origin" dependon:".enabled"`
	// ContentSecurityPolicy ships with DefaultContentSecurityPolicy rather than
	// empty. Setting it replaces that value entirely; "off" sends no policy.
	ContentSecurityPolicy           string     `` /* 168-byte string literal not displayed */
	ContentSecurityPolicyReportOnly string     `env:"-" dependon:".enabled"`
	PermissionsPolicy               string     `env:"-" dependon:".enabled"`
	HSTS                            HSTSConfig `dependon:".enabled"`
}

SecurityHeadersConfig contains browser security response headers.

Enabled records whether a runtime selects this middleware at all; the middleware itself always applies the configured headers.

It lives in the shared leaf because the headers a browser is sent should not depend on which transport served the response, and because everything about resolving them — the validation, the policy defaults, the HSTS string — is arithmetic over configuration with no request in it.

func DefaultSecurityHeaders

func DefaultSecurityHeaders() SecurityHeadersConfig

DefaultSecurityHeaders returns the classic mode defaults.

func (SecurityHeadersConfig) Validate

func (c SecurityHeadersConfig) Validate() error

Validate rejects response splitting and unsupported fixed-value policies.

type Segment

type Segment struct {
	// Lit is the literal run, used when Arg is zero.
	Lit string
	// Arg is the one-based argument position this segment renders, zero for a
	// literal. It is one-based so the zero value is a literal rather than the
	// first argument.
	Arg uint8
}

Segment is one piece of a generated message row: either a literal run of the translation, or the position an argument fills.

The table is data and the renderer below is code, which is what makes adding a locale cost no generated code at all — only another row. See .knowledge decision:message-code-shape.

type Signal

type Signal = htmlbind.Signal

Signal is one named instruction and its encoded payload.

func AsSignal

func AsSignal(err error) (Signal, bool)

AsSignal reports whether err is a signal, and returns it. It is what a live loop classifies with, and it is exported because an application wrapping a source of its own needs the same test.

func NamedSignal

func NamedSignal(name string) Signal

NamedSignal builds a signal with no payload.

func NewRawSignal

func NewRawSignal(name string, payload []byte) Signal

NewRawSignal builds a signal from a payload that is already encoded JSON.

Nothing validates those bytes. A caller passing something that is not one JSON value produces a record a client cannot parse, which is why NewSignal is the ordinary path.

func NewSignal

func NewSignal[T SignalPayload](name string, payload T) Signal

NewSignal builds a signal carrying an encoded payload.

The payload is encoded here, at the call site, so the value is immutable once yielded and the runtime holds bytes rather than something it would have to reflect on to write. A nil payload is legal and means an instruction with no arguments.

type SignalPayload

type SignalPayload = htmlbind.SignalPayload

SignalPayload is what a signal carries. Generated encoders satisfy it, which is what keeps a payload encoded by the same codec as every other typed value this framework sends.

type Sink

type Sink interface {
	Emit(ctx context.Context, record Record)
}

Sink receives records that passed the severity filter. Emitting must not panic and must not block the request goroutine for longer than a bounded enqueue: a logger that can stop a handler is worse than a missing record.

func NewOtelSink

func NewOtelSink(logger *otellog.Logger) Sink

NewOtelSink returns a sink emitting through logger. A nil logger yields a nil Sink for the same reason NewSlogSink does.

func NewSlogSink

func NewSlogSink(handler slog.Handler) Sink

NewSlogSink returns a sink writing to handler. A nil handler yields a nil Sink, which NewLogBackend drops.

The return type is the interface rather than *SlogSink on purpose: a typed nil pointer stored in an interface is not nil, so returning the concrete type would make "no destination configured" silently look like a destination.

type SlogSink

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

SlogSink writes records through a slog.Handler.

slog owns the encoding rather than a private formatter because it already ships the JSON and text forms this framework needs, an application can substitute any handler it already uses, and the result stays readable on TinyGo, where slog works apart from source locations.

func (*SlogSink) Emit

func (sink *SlogSink) Emit(ctx context.Context, record Record)

type Slot

type Slot int

Slot orders every frame of the request chain. A smaller slot runs earlier, which is to say outermost, so a guard always observes the session and authentication state established before it.

Framework frames sit at multiples of ten, BASIC style, so a middleware can be inserted between any two by picking a number in the gap: SlotAccessLog - 5 runs after the request ID is minted and before the access log times the request.

The numbers live here rather than in either runtime because the order is the part that must not differ. A chain whose frames run in a different order on the second transport is a different application: a guard that ran after the session on one and before it on the other would authorize differently, and nothing about the response would say so.

const (
	// SlotMetrics records the http.server instruments of one request.
	//
	// It is outside the tracing frame rather than inside it, so that a duration
	// counted here covers everything a client waited for including the span the
	// next frame opens, and so that the two are installed independently: a
	// deployment sampling almost no traces still counts every request.
	SlotMetrics Slot = 5
	// SlotTracing opens the request root span. The frame is installed only
	// when tracing has somewhere to export.
	SlotTracing Slot = 10
	// SlotResources injects the logger, configuration, and database clients.
	SlotResources Slot = 20
	// SlotClientAddress records the caller every downstream bound counts.
	SlotClientAddress Slot = 25
	// SlotRequestID mints or accepts the correlation identifier.
	SlotRequestID Slot = 30
	// SlotAccessLog times the request.
	SlotAccessLog Slot = 40
	// SlotRecover converts a panic into a response.
	SlotRecover Slot = 50
	// SlotSecurityHeaders sets the browser policy headers, answers a CORS
	// preflight, and marks a cross-origin response.
	//
	// It sits above every frame that can refuse because of the marking rather
	// than because of the headers. A response a browser will not hand to script
	// is a status nobody can read, so the frame that marks it has to have run
	// before the 429, the 413, the 401, the 403 and the 500 that frames below
	// write — and w.Header() is one map for the whole chain, so setting the
	// headers here puts them on every one of those.
	//
	// It was at 60 while it only set headers. The move gained the refusals
	// written between the two numbers, which is the process rate limit's 429
	// and the 503 beside it.
	SlotSecurityHeaders Slot = 52
	// SlotRateLimitProcess bounds work this process is already doing.
	SlotRateLimitProcess Slot = 55
	// SlotRequestTimeout bounds how long a request may take.
	SlotRequestTimeout Slot = 70
	// SlotMaxRequestBody refuses an oversized body.
	SlotMaxRequestBody Slot = 80
	// SlotPublicAssets answers static files before anything authenticates.
	SlotPublicAssets Slot = 90
	// SlotSignedStorage serves the URLs a self-serving storage backend
	// presigns, per requirement:object-storage, ahead of anything that
	// authenticates: the signature is the authorization.
	SlotSignedStorage Slot = 95
	// SlotOperational answers the framework assets and the two probes.
	SlotOperational Slot = 100
	// SlotStorage opens request-scoped storage.
	SlotStorage Slot = 110
	// SlotSession resolves the session.
	SlotSession Slot = 120
	// SlotAuthentication verifies the caller.
	SlotAuthentication Slot = 130
	// SlotRateLimit bounds a caller's requests.
	SlotRateLimit Slot = 135
	// SlotCSRF checks the cross-site request forgery defence.
	SlotCSRF Slot = 140
	// SlotGuard authorizes.
	SlotGuard Slot = 150
	// SlotAPIDoc answers the OpenAPI document and its UI beneath the guard.
	SlotAPIDoc Slot = 160
)

type SocketHandshake

type SocketHandshake struct {
	// TLS reports a direct TLS connection, which outranks any header.
	TLS bool
	// Host is the host the request was addressed to.
	Host string
	// RemoteAddress is the peer, which decides whether the forwarded header
	// below is evidence or noise.
	RemoteAddress string
	// ForwardedProto is X-Forwarded-Proto, read only from a declared proxy.
	ForwardedProto string
}

SocketHandshake is what an upgrade request tells the origin check.

type StorageBucketConfig added in v0.5.5

type StorageBucketConfig struct {
	// Name is what a call site addresses this bucket by.
	Name string `json:"name" help:"name this bucket is addressed by"`
	// Backend names where objects live.
	Backend string `json:"backend" default:"local" enum:"local,s3,r2" help:"where objects live: local, s3, or r2"`
	// Directory is the local backend's root, relative to the working directory.
	Directory string `json:"directory" help:"local: directory objects are kept in"`
	// Endpoint, Region and Bucket address an S3-compatible store.
	Endpoint string `json:"endpoint" help:"s3: endpoint URL"`
	Region   string `json:"region" help:"s3: signing region"`
	Bucket   string `json:"bucket" help:"s3: bucket name at the endpoint"`
	// AccessKeyID and SecretAccessKey sign S3 requests. A file writes them as
	// ${NAME} references, never inline.
	AccessKeyID     string `json:"access_key_id" secret:"mask" help:"s3: access key id"`
	SecretAccessKey string `json:"secret_access_key" secret:"mask" help:"s3: secret access key"`
	// PathStyle addresses the bucket in the path rather than the host, which
	// MinIO and some proxies need.
	PathStyle bool `json:"path_style" default:"false" help:"s3: put the bucket in the path rather than the host"`
	// Binding is the R2 bucket binding's name in the Worker env.
	Binding string `json:"binding" help:"r2: bucket binding the Worker env carries"`
}

StorageBucketConfig is one bucket of the set. The backend decides which of the remaining fields are read; the rest are ignored rather than refused, so one file can describe a bucket for two hosts.

type StorageConfig added in v0.5.5

type StorageConfig struct {
	Enabled bool `default:"false" help:"open the configured object storage buckets"`
	// Buckets is the array-of-tables form. An element takes no CLI option and
	// no environment variable of its own; the array travels as JSON in one
	// variable, per pwconfig.BucketsEnv.
	Buckets []StorageBucketConfig `dependon:".enabled" help:"bucket set, one element per bucket"`
}

StorageConfig names the object storage buckets an application addresses.

func (StorageConfig) Validate added in v0.5.5

func (c StorageConfig) Validate() error

Validate rejects a set whose buckets cannot be addressed or opened.

type Tracing

type Tracing struct {
	// Render opens a span for one HTML response, with the initial build as its
	// first child.
	Render bool
	// Boundary opens a span per settled await boundary and per live delivery.
	// It depends on Render, because a boundary span with no render span to sit
	// under would attach each fragment straight to the request root.
	Boundary bool
	// Database opens a client span per executed statement.
	Database bool
	// Statement puts the statement text on that span. Bind values never reach
	// it: policy:query-log-safety keeps row data in the log record, which the
	// span id correlates.
	Statement bool
	// MaxSQLLength bounds the statement text on a span. It is the bound query
	// diagnostics already declares, so one setting bounds both surfaces.
	MaxSQLLength int
}

Tracing is the resolved framework span policy installed by pw.

A nil value creates no framework span at all, which is what an application exporting nothing pays for the feature: one nil comparison per render and per statement, and no span object anywhere. It is a resolved policy rather than the configuration struct for the same reason QueryDiagnostics is — the auto/on/off vocabulary and the environment are read once at startup, not once per request.

func TracePolicy

func TracePolicy(ctx context.Context) *Tracing

TracePolicy returns the framework span policy of ctx, or nil when no framework span should be created. It is one context lookup, the same one every other resource accessor makes.

type TransactionScope

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

TransactionScope owns one open transaction and the savepoint stack nested inside it. The transaction is a *sql.Tx on a database/sql connection and a native transaction on one that bypasses it; the savepoint stack runs the same statements on either. The framework creates a scope; applications only observe it indirectly through Transaction.

func NewTransactionScope

func NewTransactionScope(db *sql.DB, driver string) *TransactionScope

NewTransactionScope prepares an inactive scope over db. Begin activates it.

func (*TransactionScope) Active

func (scope *TransactionScope) Active() bool

Active reports whether a depth 0 transaction is open.

func (*TransactionScope) ActiveExecutor

func (scope *TransactionScope) ActiveExecutor() sqlbind.SQLExecutor

ActiveExecutor returns the statement surface of the open transaction — a *sql.Tx or the native transaction — or nil when the scope is inactive.

It exists beside Tx for test tooling that must run statements inside the same transaction as the requests under test, whichever kind of pool backs the connection. Application code uses Transaction instead.

func (*TransactionScope) Begin

func (scope *TransactionScope) Begin(ctx context.Context, options *sql.TxOptions) error

Begin opens the depth 0 transaction of an inactive scope.

A nil options on a read-only connection begins a read-only transaction, which is the one enforcement the database itself can apply today.

func (*TransactionScope) Commit

func (scope *TransactionScope) Commit() error

Commit commits the depth 0 transaction. A scope marked failed by a savepoint operation is rolled back instead.

The native commit is sent on the wire without a caller context, because the owner that could carry one may already hold a cancelled context, and a commit must not be abandoned by the cancellation that ended the request.

func (*TransactionScope) Group

func (scope *TransactionScope) Group() string

Group is the connection group the scope is bound to.

func (*TransactionScope) ReadOnly

func (scope *TransactionScope) ReadOnly() bool

ReadOnly reports whether the scope runs on a read-only connection.

func (*TransactionScope) Rollback

func (scope *TransactionScope) Rollback() error

Rollback rolls back the depth 0 transaction. Rolling back an inactive scope is not an error, so owners can defer it unconditionally.

func (*TransactionScope) Tx

func (scope *TransactionScope) Tx() *sql.Tx

Tx returns the depth 0 transaction, or nil when the scope is inactive or runs on a native connection, whose transaction is no *sql.Tx.

It exists for test tooling that must run statements inside the same transaction as the requests under test, such as dataset seeding and database assertions. Application code uses Transaction instead.

type UpdateFailure

type UpdateFailure = htmlupdate.Failure

The partial-update types are the module's shared ones. htmlupdate and its fasthttp sibling both alias a single declaration of each, so naming them through either package names one type — and one registry therefore serves both runtimes, which is what this file exists to make true.

They are reached through htmlupdate rather than the fasthttp package because this leaf already imports net/http for status constants, where importing the other would put the fasthttp fork into every project that never opens a socket with it.

type UpdateRegion

type UpdateRegion = htmlupdate.Update

The partial-update types are the module's shared ones. htmlupdate and its fasthttp sibling both alias a single declaration of each, so naming them through either package names one type — and one registry therefore serves both runtimes, which is what this file exists to make true.

They are reached through htmlupdate rather than the fasthttp package because this leaf already imports net/http for status constants, where importing the other would put the fasthttp fork into every project that never opens a socket with it.

type UpdateRegistry

type UpdateRegistry = htmlupdate.Registry

The partial-update types are the module's shared ones. htmlupdate and its fasthttp sibling both alias a single declaration of each, so naming them through either package names one type — and one registry therefore serves both runtimes, which is what this file exists to make true.

They are reached through htmlupdate rather than the fasthttp package because this leaf already imports net/http for status constants, where importing the other would put the fasthttp fork into every project that never opens a socket with it.

func ReloadableRegistry

func ReloadableRegistry() *UpdateRegistry

ReloadableRegistry returns the published set, or nil where nothing published one, which is what tells a caller there is no redraw endpoint to serve.

func ResetReloadableForTest

func ResetReloadableForTest() (*UpdateRegistry, int, error)

ResetReloadableForTest restores the registry to a known state and returns what it replaced, so a test that registers can put back what it found.

type UpdateReloadable

type UpdateReloadable = htmlupdate.Reloadable

The partial-update types are the module's shared ones. htmlupdate and its fasthttp sibling both alias a single declaration of each, so naming them through either package names one type — and one registry therefore serves both runtimes, which is what this file exists to make true.

They are reached through htmlupdate rather than the fasthttp package because this leaf already imports net/http for status constants, where importing the other would put the fasthttp fork into every project that never opens a socket with it.

type UpdateSettings

type UpdateSettings struct {
	// Enabled is the update surface: navigation deltas, redraws, and action
	// responses. It gates those and nothing else.
	Enabled bool
	// Live is the subscription that keeps a page updating after its document is
	// complete. It is a separate switch because it answers a separate request,
	// and gating it on the one above would turn a project that asked only for
	// live rendering into one that got none — which is how the second transport
	// answered a subscription with a whole document.
	Live                bool
	ValidatorKey        string
	HeaderPrefix        string
	DataAttributePrefix string
	GlobalName          string
	PathPrefix          string
	BuildID             string
	MaxManifestBytes    int
	CSRFHeaderName      string
	CallerOwnsRuntime   bool
	// AsyncTimeout bounds one await boundary and AsyncConcurrency bounds the
	// boundary work running at once. They travel with the update settings
	// because a streamed answer renders the same chain a document does, and a
	// runtime that could not read them would settle boundaries on terms the
	// deployment did not choose.
	AsyncTimeout     time.Duration
	AsyncConcurrency int
	// The live bounds, which the delivery loop reads on either transport. They
	// travel here for the reason the async bounds do: the loop is shared, so
	// the values it consults have to be.
	LiveMaxResponses  int
	LiveMaxBoundaries int
	// LiveMaxSignalBytes bounds the signal payloads one response may write. It
	// travels here because a signal is written by both loops, and a budget one
	// backend enforced and the other did not would be a bound an application
	// escapes by changing transports.
	LiveMaxSignalBytes int
	LiveMaxDuration    time.Duration
	LiveDurationJitter int
	LiveIdleTimeout    time.Duration
}

UpdateSettings is the resolved partial-update configuration in a form that names no transport, so one resolution serves both runtimes.

It is values rather than a composed options struct because the two runtimes each declare their own, with the same fields and different method receivers. Copying the fields twice is cheaper than a conversion whose correctness would depend on two struct declarations staying identical.

func ResolvedUpdateSettings

func ResolvedUpdateSettings() (UpdateSettings, bool)

ResolvedUpdateSettings returns the published configuration, and whether anything published one. A runtime that finds none has nothing to answer an update request with and says so rather than guessing at defaults.

func (UpdateSettings) RenderOptions

func (s UpdateSettings) RenderOptions(ctx context.Context) []htmlbind.Option

RenderOptions is the option set a streamed answer renders with, built from the published settings so both runtimes bound a boundary the same way.

It is the shared subset rather than everything pw assembles: the cache store and its scope are resolved from the request context by whichever runtime owns that resolution, and a caller adds them after these.

type ValueStore

type ValueStore interface {
	context.Context
	SetUserValue(key, value any)
}

A ValueStore is a request value that carries its own state instead of being replaced by a derived copy for each frame of the chain.

net/http middleware derives a context and hands it to the next handler, so a frame changes what the rest of the chain sees by returning something new. fasthttp has one request value, which is itself the context, and a frame changes what the chain sees by writing into it. Only the write side differs: that value answers Value from the same store, so every reader in this package works on both transports unchanged.

It is declared structurally rather than by naming the type, so this leaf stays free of the fasthttp fork the way it stays free of anything else a net/http project should not have to build.

Jump to

Keyboard shortcuts

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