idempotency

package
v0.0.0-...-ea9222f Latest Latest
Warning

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

Go to latest
Published: Feb 15, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Index

Constants

View Source
const (
	KeyVersion = "v1"

	MaxTenantLen = 64
	MaxScopeLen  = 32
	MaxKeyLen    = 256

	MaxParts = 32
	MaxBytes = 32 * 1024 // 32 KiB input cap for hashing
)

Variables

View Source
var (
	ErrInvalidKey   = errors.New("idempotency: invalid key")
	ErrInputTooBig  = errors.New("idempotency: input too big")
	ErrInvalidScope = errors.New("idempotency: invalid scope")
)
View Source
var (
	ErrInvalid  = errors.New("idempotency: invalid")
	ErrConflict = errors.New("idempotency: conflict")
	ErrNotOwner = errors.New("idempotency: not owner")
	ErrExpired  = errors.New("idempotency: expired")
	ErrTooLarge = errors.New("idempotency: too large")
)

Functions

func BuildKey

func BuildKey(tenant, scope string, parts ...any) (string, error)

BuildKey computes a deterministic key for a tenant+scope from ordered parts. Parts are encoded deterministically and hashed with SHA-256.

func BuildKeyFromMap

func BuildKeyFromMap(tenant, scope string, m map[string]any) (string, error)

BuildKeyFromMap computes a deterministic key from a map by sorting keys. Useful when you have named inputs rather than ordered parts.

func ValidateKey

func ValidateKey(key string) error

ValidateKey checks format and returns nil if valid.

Types

type BeginResult

type BeginResult struct {
	// Record is the current record after TryBegin (either existing or newly created).
	Record Record `json:"record"`
	// Fresh indicates whether the caller acquired a new lease (should do work).
	Fresh bool `json:"fresh"`
}

type Clock

type Clock interface {
	Now()
}

type KeyParts

type KeyParts struct {
	Version string `json:"version"`
	Tenant  string `json:"tenant"`
	Scope   string `json:"scope"`
	Hash    string `json:"hash"` // lowercase hex sha256
}

KeyParts is the parsed representation.

func ParseKey

func ParseKey(key string) (KeyParts, error)

ParseKey parses "v1:<tenant>:<scope>:<sha256hex>".

type MemoryStore

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

MemoryStore is a reference in-memory implementation (suitable for local/dev). It is not durable.

func NewMemoryStore

func NewMemoryStore(opts Options) *MemoryStore

NewMemoryStore constructs an in-memory store with defaults applied.

func (*MemoryStore) Complete

func (s *MemoryStore) Complete(ctx context.Context, key string, ownerToken string, result []byte) (Record, error)

func (*MemoryStore) Delete

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

func (*MemoryStore) Fail

func (s *MemoryStore) Fail(ctx context.Context, key string, ownerToken string, code string, msg string) (Record, error)

func (*MemoryStore) Get

func (s *MemoryStore) Get(ctx context.Context, key string) (Record, bool, error)

func (*MemoryStore) Sweep

func (s *MemoryStore) Sweep(ctx context.Context) (int, error)

func (*MemoryStore) Touch

func (s *MemoryStore) Touch(ctx context.Context, key string, ownerToken string, ttl time.Duration) (Record, error)

func (*MemoryStore) TryBegin

func (s *MemoryStore) TryBegin(ctx context.Context, key string, ttl time.Duration) (BeginResult, error)

type Options

type Options struct {
}

type Record

type Record struct {
	Key string `json:"key"`

	State State `json:"state"`

	// OwnerToken is set when in_progress and must match to Complete/Fail/Touch.
	OwnerToken string `json:"owner_token,omitempty"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
	ExpiresAt time.Time `json:"expires_at"`

	// Result (for complete)
	ResultHash  string `json:"result_hash,omitempty"` // sha256 hex of ResultBytes
	ResultBytes []byte `json:"result_bytes,omitempty"`

	// Failure (for failed)
	ErrorCode string `json:"error_code,omitempty"`
	ErrorMsg  string `json:"error_msg,omitempty"`
}

Record is the canonical idempotency entry. ResultBytes is optional; some services may store a pointer (URI) elsewhere and only store hashes here later.

func (Record) IsExpired

func (r Record) IsExpired(now time.Time) bool

type Store

type Store interface {
	// TryBegin attempts to acquire a lease for key. If key is complete, returns Fresh=false with the record.
	// If key is in_progress and not expired, returns Fresh=false with ErrConflict.
	// If key is expired, it is replaced and Fresh=true.
	TryBegin(ctx context.Context, key string, ttl time.Duration) (BeginResult, error)

	// Touch extends the lease if caller holds ownership (in_progress only).
	Touch(ctx context.Context, key string, ownerToken string, ttl time.Duration) (Record, error)

	// Complete marks the key complete (requires owner token).
	Complete(ctx context.Context, key string, ownerToken string, result []byte) (Record, error)

	// Fail marks the key failed (requires owner token).
	Fail(ctx context.Context, key string, ownerToken string, code string, msg string) (Record, error)

	// Get fetches record.
	Get(ctx context.Context, key string) (Record, bool, error)

	// Delete removes record (admin/debug).
	Delete(ctx context.Context, key string)

	// Sweep removes expired entries. Deterministic, no background goroutine.
	Sweep(ctx context.Context) (removed int, err error)
}

Store defines the idempotency persistence contract.

Jump to

Keyboard shortcuts

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