idemkit

package
v11.3.24 Latest Latest
Warning

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

Go to latest
Published: Jul 19, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package idemkit provides tenant-scoped HTTP idempotency middleware.

The middleware suppresses duplicate HTTP responses only after the configured Store confirms persistence. It cannot make a handler's business side effects exactly once by itself. Stronger guarantees require service-owned transactional storage or an outbox that atomically couples the business mutation to the idempotency record.

Index

Constants

View Source
const (
	DefaultHeader           = "Idempotency-Key"
	ReplayHeader            = "Idempotency-Replayed"
	DefaultMemoryTTL        = 24 * time.Hour
	DefaultMemoryCapacity   = 10_000
	DefaultMaxRequestBytes  = int64(1 << 20)
	DefaultMaxResponseBytes = int64(1 << 20)
)

Variables

View Source
var (
	// ErrLeaseLost means a token-aware operation no longer owns the claim.
	ErrLeaseLost = stderrors.New("idemkit: lease ownership lost")
	// ErrCapacity means the store cannot admit another live claim.
	ErrCapacity = stderrors.New("idemkit: store capacity reached")
)

Functions

func Fingerprint

func Fingerprint(method, target string, body []byte) string

Fingerprint returns a stable hash over method, target, and body bytes.

func Middleware

func Middleware(store Store, opts ...Option) func(http.Handler) http.Handler

Middleware returns HTTP middleware that provides idempotency replay for mutating requests carrying an idempotency key. A successful keyed handler response is buffered and becomes visible only after Store persistence.

Types

type BeginResult

type BeginResult int

BeginResult describes the state returned by Store.Begin.

const (
	Started BeginResult = iota
	Replay
	InFlight
	Mismatch
)

type Claim

type Claim struct {
	Result   BeginResult
	Response *StoredResponse
}

Claim is returned by Store.Begin.

type LeaseClaim

type LeaseClaim struct {
	Claim
	Token LeaseToken
}

LeaseClaim augments a Claim with ownership for a newly started request.

type LeaseStore

type LeaseStore interface {
	Store
	BeginLease(context.Context, string, string, string) (LeaseClaim, error)
	CompleteLease(context.Context, string, string, string, LeaseToken, StoredResponse) error
	ReleaseLease(context.Context, string, string, LeaseToken) error
}

LeaseStore is an optional token-aware extension to Store. It lets middleware release or complete only the claim it owns, preventing stale-owner ABA mutations after expiry and replacement.

type LeaseToken

type LeaseToken string

LeaseToken is an opaque claim-ownership token.

type MemoryStore

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

MemoryStore is an in-memory tenant-aware LeaseStore implementation. Its capacity is global across tenant/key pairs and it never evicts live records.

func NewMemoryStore

func NewMemoryStore(ttl time.Duration) *MemoryStore

NewMemoryStore creates a bounded tenant-scoped in-memory idempotency store.

func NewMemoryStoreWithCapacity

func NewMemoryStoreWithCapacity(ttl time.Duration, capacity int) *MemoryStore

NewMemoryStoreWithCapacity creates a tenant-scoped store with a live-record bound. Non-positive capacity restores DefaultMemoryCapacity.

func (*MemoryStore) Begin

func (s *MemoryStore) Begin(ctx context.Context, tenantID, key, fingerprint string) (Claim, error)

Begin implements the legacy Store contract. Middleware uses BeginLease when the dynamic store implements LeaseStore.

func (*MemoryStore) BeginLease

func (s *MemoryStore) BeginLease(ctx context.Context, tenantID, key, fingerprint string) (LeaseClaim, error)

BeginLease starts or observes a token-owned claim.

func (*MemoryStore) Complete

func (s *MemoryStore) Complete(ctx context.Context, tenantID, key, fingerprint string, response StoredResponse) error

Complete implements the legacy Store contract. It preserves key-only compatibility; token-aware callers should use CompleteLease.

func (*MemoryStore) CompleteLease

func (s *MemoryStore) CompleteLease(ctx context.Context, tenantID, key, fingerprint string, token LeaseToken, response StoredResponse) error

CompleteLease persists only if token still owns the live in-flight claim.

func (*MemoryStore) Release

func (s *MemoryStore) Release(ctx context.Context, tenantID, key string) error

Release implements the legacy key-only Store contract.

func (*MemoryStore) ReleaseLease

func (s *MemoryStore) ReleaseLease(ctx context.Context, tenantID, key string, token LeaseToken) error

ReleaseLease removes only the live claim owned by token.

type Option

type Option func(*options)

Option configures middleware.

func WithHeader

func WithHeader(name string) Option

WithHeader changes the request header used for the idempotency key.

func WithRequestBodyLimit

func WithRequestBodyLimit(maxBytes int64) Option

WithRequestBodyLimit bounds keyed request bodies retained for fingerprinting. Non-positive values restore DefaultMaxRequestBytes.

func WithResponseBodyLimit

func WithResponseBodyLimit(maxBytes int64) Option

WithResponseBodyLimit bounds keyed handler responses buffered before Store persistence. Keyed endpoints are therefore non-streaming. Non-positive values restore DefaultMaxResponseBytes.

func WithTenantResolver

func WithTenantResolver(resolver TenantResolver) Option

WithTenantResolver configures tenant extraction. Nil restores single-tenant mode.

type Store

type Store interface {
	Begin(ctx context.Context, tenantID, key, fingerprint string) (Claim, error)
	Complete(ctx context.Context, tenantID, key, fingerprint string, response StoredResponse) error
	Release(ctx context.Context, tenantID, key string) error
}

Store is the source-compatible, tenant-aware idempotency storage contract.

If Complete returns an error, the result is ambiguous. Implementations that require duplicate suppression must retain the claim until store-owned expiry or operator reconciliation; middleware deliberately does not call Release.

type StoredResponse

type StoredResponse struct {
	StatusCode  int
	Header      http.Header
	Body        []byte
	Fingerprint string
	CreatedAt   time.Time
}

StoredResponse is replayed for matching completed idempotency claims.

type TenantResolver

type TenantResolver func(*http.Request) string

TenantResolver maps a request to its tenant namespace. The default resolver returns an empty string for single-tenant services.

Jump to

Keyboard shortcuts

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