Documentation
¶
Overview ¶
Package storage defines the universal adapter contract for pluggable storage backends used by Nexss Flow and any other consumer of nexssp/kernel.
The contract is intentionally tiny: a Backend advertises a Name and a Kind, and produces an Instance from an options map. Every Instance satisfies one or more concrete Kernel interfaces (CacheLayer[V], Mutex, FencedMutex, IdempotencyStore, Journal, TxRunner, RateLimiter, or the streaming BlobStore defined in blob.go).
Typical use:
reg := storage.NewRegistry()
reg.MustRegister(memory.NewKV())
reg.MustRegister(memory.NewBlob())
decl := storage.Declaration{
Name: "kv:memory",
Backend: "memory",
Kind: storage.KindKV,
Options: map[string]string{"ttl": "5m"},
}
inst, err := decl.Open(reg)
// inst implements action.CacheLayer[any]
A Flow extension wires this Registry into a Bundle; see ../flow-storage-extension/library.go.
Index ¶
- func AssertFencedMutex(inst Instance) (action.FencedMutex, error)
- func AssertIdempotencyStore(inst Instance) (action.IdempotencyStore, error)
- func AssertJournal(inst Instance) (action.Journal, error)
- func AssertKV(inst Instance) (action.CacheLayer[any], error)
- func AssertMutex(inst Instance) (action.Mutex, error)
- func AssertRateLimiter(inst Instance) (action.RateLimiter, error)
- func AssertTxRunner(inst Instance) (action.TxRunner, error)
- func BadRequest(msg string) error
- func BadRequestf(format string, args ...any) error
- func Conflict(msg string) error
- func Conflictf(format string, args ...any) error
- func Internal(msg string) error
- func Internalf(format string, args ...any) error
- func NotFound(msg string) error
- func NotFoundf(format string, args ...any) error
- func ParseDuration(s string, def time.Duration) (time.Duration, error)
- func TooManyRequests(msg string) error
- func Unavailable(msg string, cause error) error
- func Unavailablef(cause error, format string, args ...any) error
- type Backend
- type BlobStore
- type Declaration
- type Instance
- type Kind
- type Position
- type Registry
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AssertFencedMutex ¶
func AssertFencedMutex(inst Instance) (action.FencedMutex, error)
AssertFencedMutex asserts that inst satisfies action.FencedMutex.
func AssertIdempotencyStore ¶
func AssertIdempotencyStore(inst Instance) (action.IdempotencyStore, error)
AssertIdempotencyStore asserts that inst satisfies action.IdempotencyStore. Callers may additionally probe for IdempotencyCoordinator and IdempotencyLeaseRenewer via type assertion; see action.IdempotencyCoordinator.
func AssertJournal ¶
AssertJournal asserts that inst satisfies action.Journal.
func AssertKV ¶
func AssertKV(inst Instance) (action.CacheLayer[any], error)
AssertKV asserts that inst satisfies action.CacheLayer[any] and returns the typed handle. The Kind check is defensive: a well-formed Registry never hands back an Instance of the wrong Kind, but a consumer may have an Instance from another source.
func AssertMutex ¶
AssertMutex asserts that inst satisfies action.Mutex.
func AssertRateLimiter ¶
func AssertRateLimiter(inst Instance) (action.RateLimiter, error)
AssertRateLimiter asserts that inst satisfies action.RateLimiter.
func AssertTxRunner ¶
AssertTxRunner asserts that inst satisfies action.TxRunner.
func BadRequest ¶
BadRequest wraps xerr.BadRequest. Use for invalid user-supplied options (unknown key, malformed value, missing required field). msg is treated as a final message; pre-format with fmt.Sprintf if interpolation is needed.
func BadRequestf ¶
BadRequestf is the printf-style variant.
func Conflict ¶
Conflict wraps xerr.Conflict. Use for duplicate registrations, fence-token mismatches, idempotency-key payload conflicts.
func Internal ¶
Internal wraps xerr.Internal. Use for programmer bugs that are not the user's fault: a Backend that does not implement its declared Kind's interface, a corrupted in-memory cache entry.
func NotFound ¶
NotFound wraps xerr.NotFound. Use for missing declarations, missing backends, missing keys (when "missing" is not the expected hit path).
func ParseDuration ¶
ParseDuration is a tiny helper that mirrors time.ParseDuration but returns a classified BadRequest on parse failure. Backends may use it to validate duration options consistently.
func TooManyRequests ¶
TooManyRequests wraps xerr.TooManyRequests. Use from RateLimiter backends when the underlying store returns its own rate-limit signal distinct from the token-bucket decision.
func Unavailable ¶
Unavailable wraps xerr.Unavailable. Use for infrastructure failures: connection refused, timeout, network partition, disk full. Always wrap the underlying cause.
Types ¶
type Backend ¶
type Backend interface {
// Name is the adapter identifier, e.g. "memory", "redis", "s3".
// Two backends with the same Name cannot coexist in one Registry.
Name() string
// Kind is the primary storage contract this Backend provides.
// A Backend declares exactly one Kind; adapters that can serve
// multiple Kinds (e.g., Redis can do both KV and Lock) register
// separate Backend instances for each Kind.
Kind() Kind
// Open returns a fresh Instance bound to opts. opts keys are
// backend-specific; common keys include "addr", "ttl",
// "namespace", "bucket", "region". Unrecognized keys MUST be
// rejected with xerr.BadRequest.
Open(opts map[string]string) (Instance, error)
}
Backend is the contract every adapter implements. Open returns an Instance whose concrete type satisfies the Kernel interface for the declared Kind.
Open MUST be safe to call more than once with the same opts map. Callers may open fresh Instances per declaration, or share one Instance across declarations; the Registry caches by Declaration name regardless.
Open MUST validate opts and return a classified error (xerr.BadRequest for invalid user input, xerr.Unavailable for infrastructure failures) rather than panicking.
type BlobStore ¶
type BlobStore interface {
// Get looks up key and returns a small reference. A missing or inaccessible
// key returns (zero, false, nil). The reference carries expected metadata
// when the backend has it; it never contains the payload.
Get(ctx context.Context, key string) (ref xfile.Ref, hit bool, err error)
// Put streams src under key, replacing any existing object. It returns a
// typed reference with integrity expectations for the bytes consumed.
Put(ctx context.Context, key string, src io.Reader) (xfile.Ref, error)
// Open resolves ref and returns a reader owned by the caller plus confirmed
// metadata for the opened object. Expected metadata in ref is verified.
Open(ctx context.Context, ref xfile.Ref) (io.ReadCloser, xfile.Info, error)
// Delete removes key for the authenticated caller and tenant. A missing or
// inaccessible key is not an error.
Delete(ctx context.Context, key string) error
}
BlobStore stores opaque payloads without TTL semantics. Unlike a KV cache, BlobStore is intended for files, documents, snapshots, and other large data.
Put consumes src and returns an opaque, provider-scoped reference. Get is a metadata-only lookup: it returns a compact xfile.Ref, never the object bytes. Open streams an object identified by a reference and returns confirmed metadata separately from the caller-supplied expectations in xfile.Ref. Implementations scope operations to the authenticated caller and tenant in ctx; references are untrusted selectors, not authorization credentials.
func AssertBlob ¶
AssertBlob asserts that inst satisfies BlobStore.
type Declaration ¶
type Declaration struct {
// Name is the unique identifier callers use to reference this
// declaration. Convention: "<kind>:<backend>" (e.g. "kv:redis"),
// but any unique non-empty string is allowed. The Flow @storage
// directive uses "as <alias>" to override.
Name string
// Backend names the adapter to open. Must match a Backend in the
// target Registry. Example: "memory", "redis", "s3".
Backend string
// Kind is the storage contract. Must match Backend.Kind() or
// Declaration.Open returns a classified mismatch error.
Kind Kind
// Options are backend-specific connection details. Validated by
// the Backend at Open time.
Options map[string]string
// Position carries source location for error attribution. Pure-Go
// consumers leave it zero. Flow fills it from the directive.
Position Position
}
Declaration is the parsed form of an @storage block. It binds a human-readable Name to a Backend and its connection options.
Flow parses declarations from source; pure-Go consumers construct them directly. Either way, Declaration.Open is the only way to obtain a live Instance from a Registry.
type Instance ¶
type Instance interface {
// Kind echoes the Kind the Instance was opened under. Must match
// the Backend that produced it.
Kind() Kind
// Close releases the Instance's resources. It is called exactly
// once by the Registry when the host is shutting down. After
// Close returns, the Instance must not be used.
Close() error
}
Instance is a type-erased handle to an opened store. Callers assert to the specific Kernel interface matching the declared Kind at use time. This is the only reflection-like step in the system, and it happens once per declaration, never per call.
An Instance MAY satisfy multiple Kernel interfaces (e.g., a relational database backend might implement both TxRunner and Journal). The Kind only declares the primary contract; consumers may probe for additional capabilities with the standard Go type-assertion idiom:
if rl, ok := inst.(action.RateLimiter); ok { ... }
type Kind ¶
type Kind string
Kind enumerates the storage contracts an adapter can implement. Each Kind maps 1:1 to a Kernel interface, so the type system guarantees the right method set is available after Instance assertion.
Add a new Kind only when a new Kernel storage interface appears. Do not add Kinds for non-storage contracts (logging, metrics, etc.).
const ( // KindKV maps to action.CacheLayer[V]. A KV store serves typed // values keyed by string with per-entry TTL. Use this for caches, // session stores, and small structured records. KindKV Kind = "kv" // KindBlob maps to BlobStore (defined in blob.go). A Blob store // serves streamed objects addressed by typed xfile.Ref handles, // without TTL semantics. Use it for files, documents, snapshots, // and large payloads. KindBlob Kind = "blob" // KindLock maps to action.Mutex. A basic distributed lock with // TryLock/Unlock semantics. Use this for short critical sections // where stale writers are tolerable. KindLock Kind = "lock" // KindFencedLock maps to action.FencedMutex. A fenced lock returns // a monotonic LockLease that downstream systems can use to reject // stale writers. Use this for any storage backend that supports // fencing tokens (Redis ACL, etcd revision, Postgres advisory // locks with version columns). KindFencedLock Kind = "fenced_lock" // KindIdem maps to action.IdempotencyStore. The advanced // IdempotencyCoordinator and IdempotencyLeaseRenewer contracts are // optional capabilities an Instance may additionally satisfy; the // Flow extension probes for them at use time. KindIdem Kind = "idem" // KindJournal maps to action.Journal. Used by action.Durable for // step-level memoization so resumed executions skip already-run // steps. Use this for workflow checkpoint storage. KindJournal Kind = "journal" // KindTx maps to action.TxRunner. A TxRunner wraps a function in a // transaction boundary. Use this for relational databases and // document stores that expose atomic multi-op transactions. KindTx Kind = "tx" // KindRateLimit maps to action.RateLimiter. A distributed // token-bucket admission control. Use this when rate-limit state // must be shared across processes (Redis, etcd). KindRateLimit Kind = "rate_limit" )
func AllKinds ¶
func AllKinds() []Kind
AllKinds returns every defined Kind, sorted. Useful for diagnostics and CLI help output.
func (Kind) KernelInterface ¶
KernelInterface returns the Kernel interface name an Instance of this Kind is expected to satisfy. Used in error messages and diagnostics; not used for runtime assertion.
type Position ¶
Position is a minimal source-location record. It mirrors core.Position from flow/compiler but lives here so the storage package does not depend on flow.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry holds the available backends and tracks opened Instances for lifecycle management. It is a struct, not a package global, so two flows in the same process can have independent storage configurations. This addresses the anti-pattern flagged in flow/AGENTS.md §7 (currently exhibited by extensions/decide).
Backends are keyed by (Name, Kind) — a single adapter like Redis can serve both KV and Lock contracts by registering two Backend instances with Name="redis" but different Kind values. This keeps the user's mental model simple ("the redis backend" is one thing) while letting the type system distinguish which contract is in use.
func NewRegistry ¶
func NewRegistry() *Registry
NewRegistry returns an empty Registry. Callers Register backends before any Declaration.Open call.
func (*Registry) Close ¶
Close releases every opened Instance. Errors are joined but never abort the loop; one failing Close does not leak the rest.
Close honors ctx for backends whose Close blocks on network I/O. Callers should pass a context with a deadline so a hung backend cannot stall host shutdown indefinitely.
func (*Registry) Lookup ¶
Lookup returns the Backend with the given (name, kind) and a found flag. Use LookupKind to enumerate Backends regardless of Kind.
func (*Registry) LookupKind ¶
LookupKind returns every Backend registered under name, regardless of Kind. Useful for diagnostics: "what kinds does 'redis' serve?".
func (*Registry) MustRegister ¶
MustRegister panics on registration error. Intended for built-in backends whose absence is a programmer bug, not a user error.
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
postgres
command
Command postgres demonstrates the PostgreSQL KV adapter.
|
Command postgres demonstrates the PostgreSQL KV adapter. |
|
internal
|
|
|
blobref
Package blobref contains shared helpers for storage BlobStore adapters.
|
Package blobref contains shared helpers for storage BlobStore adapters. |
|
sqladapter
Package sqladapter contains the shared SQL implementation used by the SQLite and PostgreSQL adapters.
|
Package sqladapter contains the shared SQL implementation used by the SQLite and PostgreSQL adapters. |
|
Package memory provides reference in-process implementations of every storage.Kind.
|
Package memory provides reference in-process implementations of every storage.Kind. |
|
Package postgres provides PostgreSQL implementations of storage KV, Blob, and Tx backends.
|
Package postgres provides PostgreSQL implementations of storage KV, Blob, and Tx backends. |
|
Package redis provides Redis-backed KV, Blob, and distributed lock implementations.
|
Package redis provides Redis-backed KV, Blob, and distributed lock implementations. |
|
Package sqlite provides SQLite implementations of storage KV, Blob, and Tx backends.
|
Package sqlite provides SQLite implementations of storage KV, Blob, and Tx backends. |