dynamobind

package
v0.3.4 Latest Latest
Warning

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

Go to latest
Published: Aug 3, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

Documentation

Overview

Package dynamobind provides typed, reflection-free DynamoDB item binding on top of github.com/shibukawa/tinygodriver/nosql/dynamodb.

A struct declares its attributes once with dynamo tags, tinybind-gen emits the codec, and the call site never handles a map[string]dynamodb.AttributeValue:

type Reading struct {
	Sensor  string  `dynamo:"sensor,partitionkey"`
	At      int64   `dynamo:"at,sortkey"`
	Celsius float64 `dynamo:"celsius"`
}

ctx = dynamobind.WithClient(ctx, client, dynamobind.WithTablePrefix(""))
got, err := dynamobind.Load[Reading](ctx, "readings", want.ItemKey())

The client is not a parameter. It and the deployment table prefix are facts of one process, installed once with WithClient, so no call site and no generated signature carries them; see context.go.

Dispatch is by type constraint rather than by a registry, so a type without generated code fails to compile instead of failing at run time on a missing registration. Nothing here reflects on application fields.

What this package does not do

It adds no retry loop: the driver already retries with backoff, and a second loop would multiply the delivery count silently. It hides no page boundary: Query and Scan iterate, but QueryPage stays public and returns LastEvaluatedKey. It swallows no error: every driver sentinel survives errors.Is and *dynamodb.Error survives errors.As through every helper here.

Index

Constants

View Source
const (
	MaxBatchWrite = 25
	MaxBatchGet   = 100
)

DynamoDB's per-request batch limits. They are fixed by the service, so the chunking that respects them is mechanical and belongs here rather than in generated code. The size limits, 16 MB per request and 400 KB per item, are properties of the data rather than of the type, so nothing at generation time could bound them; an oversized request surfaces as dynamodb.ErrRequestTooLarge.

Variables

View Source
var ErrNoClient = errors.New("dynamobind: no DynamoDB client in context")

ErrNoClient reports that a Context does not carry a DynamoDB client. It is returned rather than panicking, so every entry stays an ordinary error-returning function.

Functions

func ClientFromContext added in v0.2.10

func ClientFromContext(ctx context.Context) (*dynamodb.Client, error)

ClientFromContext returns the client installed by WithClient.

It is the escape hatch for reaching the driver directly, for an operation this package does not wrap. Everything this package does wrap resolves through TableFromContext instead, so the name mapping is applied.

func Load

func Load[T any, PT interface {
	*T
	ItemDecoder
}](ctx context.Context, table string, key dynamodb.Key, opts ...dynamodb.GetOption) (T, error)

Load reads one item by key and decodes it into T.

A key that matches nothing keeps the driver's dynamodb.ErrItemNotFound rather than returning a zero value, so a miss cannot be mistaken for an empty item.

func LoadAll

func LoadAll[T any, PT interface {
	*T
	ItemDecoder
}](ctx context.Context, table string, keys []dynamodb.Key, opts ...dynamodb.BatchOption) ([]T, []dynamodb.Key, error)

LoadAll reads every key, splitting the input into requests of at most MaxBatchGet keys.

DynamoDB does not promise to return batch items in request order, and a key that matches nothing is simply absent rather than an error, so len(items) can be smaller than len(keys) with no error and no unprocessed key. Keys DynamoDB declined to read come back in unprocessed, unretried.

func Query

func Query[T any, PT interface {
	*T
	ItemDecoder
}](ctx context.Context, table, keyCond string, opts ...dynamodb.QueryOption) iter.Seq2[T, error]

Query iterates every item of a query, requesting pages as the range advances.

One range can issue many requests. The iterator reports no page boundary, no Count, no ScannedCount, and no final LastEvaluatedKey, so a query that scans far more than it returns looks the same as one that does not, and an interrupted run cannot be resumed. Use QueryPage when any of that matters.

Iteration stops at the first error, which is yielded once with the zero value of T. A break stops it without issuing a further request.

func Remove

func Remove[T Keyer](ctx context.Context, table string, v T, opts ...dynamodb.WriteOption) error

Remove deletes the item identified by v's key. Only the key of v is read.

func RemoveReturning

func RemoveReturning[T Keyer, PT interface {
	*T
	ItemDecoder
}](ctx context.Context, table string, v T, opts ...dynamodb.WriteOption) (T, bool, error)

RemoveReturning is Remove, and also decodes the item it deleted.

The bool is false when the key held no item, which is not an error.

func Scan

func Scan[T any, PT interface {
	*T
	ItemDecoder
}](ctx context.Context, table string, opts ...dynamodb.ScanOption) iter.Seq2[T, error]

Scan iterates every item of a table or index scan.

An unfiltered scan walks the whole table, one page per request. Everything said about Query's hidden request count applies here and costs more.

func Store

func Store[T ItemEncoder](ctx context.Context, table string, v T, opts ...dynamodb.WriteOption) error

Store writes v as a whole item, replacing any item with the same key.

It is PutItem, not a partial update: every attribute of the stored item comes from v. Use Update for a partial change.

func StoreAll

func StoreAll[T ItemEncoder](ctx context.Context, table string, vs []T) ([]T, error)

StoreAll writes every value, splitting the input into requests of at most MaxBatchWrite items.

What DynamoDB declined comes back in unprocessed, unretried: the driver already retries the transport, and whether a partial success is worth a second attempt is the caller's decision, not this package's. Passing unprocessed back to StoreAll is the retry, and it belongs in caller code where a backoff can live.

func StoreReturning

func StoreReturning[T ItemEncoder, PT interface {
	*T
	ItemDecoder
}](ctx context.Context, table string, v T, opts ...dynamodb.WriteOption) (T, bool, error)

StoreReturning is Store, and also decodes the item it replaced.

The bool is false when the key held no item, which is not an error. It asks the driver for ALL_OLD, so it costs a write capacity unit more than Store on a table that charges for it.

func TableFromContext added in v0.2.10

func TableFromContext(ctx context.Context, table string) (*dynamodb.Client, string, error)

TableFromContext resolves a declared table name into the client and the name to send. Every entry of this package calls it.

func TypeError

func TypeError(attribute, expected string, got dynamodb.AttributeValue) error

TypeError reports an attribute whose stored kind is not the one the field needs. Generated decoders call it.

func Update

func Update[T Keyer](ctx context.Context, table string, v T, update string, opts ...dynamodb.WriteOption) error

Update applies a DynamoDB update expression to the item identified by v's key.

The expression is passed to the driver verbatim; nothing here generates or validates it. Only the key is typed, which is the part a struct tag can actually supply. Attribute values in the expression come from dynamodb.WithExpressionValues as usual:

err := dynamobind.Update(ctx, "readings", key, "SET celsius = :c",
	dynamodb.WithExpressionValues(map[string]dynamodb.AttributeValue{":c": dynamodb.N(21.5)}))

func ValueError

func ValueError(attribute, message string, cause error) error

ValueError reports an attribute whose kind is right but whose value cannot be represented by the field, such as a number too large for the Go type. Generated decoders call it.

func WithClient added in v0.2.10

func WithClient(ctx context.Context, c *dynamodb.Client, options ...ClientOption) context.Context

WithClient returns a child Context carrying a DynamoDB client and, with WithTableNames, how its tables are named. Framework middleware installs it once, and every entry of this package resolves it.

Types

type ClientOption added in v0.2.10

type ClientOption func(*clientEntry)

ClientOption configures what a stored client is used with.

func WithTableNames added in v0.2.10

func WithTableNames(resolve TableResolver) ClientOption

WithTableNames records how declared table names map onto this deployment's. Without it the declared name is sent unchanged, which is what a deployment whose tables are named as declared wants. A nil resolver is ignored, so a mistaken nil behaves as no resolver rather than panicking on the first call.

type Error

type Error struct {
	// Attribute is the attribute that failed, or "" for a whole-item failure.
	Attribute string
	// Expected and Got name attribute kinds, such as "S" or "N".
	Expected string
	Got      string
	Message  string
	// contains filtered or unexported fields
}

Error describes an item mapping failure. Attribute names the attribute that failed, which is the DynamoDB name rather than the Go field name, because that is the name the stored data uses.

func AsError

func AsError(err error) (*Error, bool)

AsError finds a dynamobind Error in a chain without errors.As, which needs reflection. Whether reflect is linked at all is the driver's business, not this package's.

func (*Error) Error

func (e *Error) Error() string

func (*Error) Unwrap

func (e *Error) Unwrap() error

type ItemDecoder

type ItemDecoder interface {
	DecodeItem(item dynamodb.Item) error
}

ItemDecoder fills a value from a DynamoDB item. Generated code implements it on the pointer receiver.

type ItemEncoder

type ItemEncoder interface {
	EncodeItem() dynamodb.Item
}

ItemEncoder converts a value into a DynamoDB item. Generated code implements it on the value receiver.

type Keyer

type Keyer interface {
	ItemKey() dynamodb.Key
}

Keyer reports the primary key of a value. Generated code implements it when the type carries a partitionkey tag.

type Page

type Page[T any] struct {
	Items            []T
	LastEvaluatedKey dynamodb.Key
	Count            int
	ScannedCount     int
}

Page is one page of decoded items.

LastEvaluatedKey is the continuation and is the authority on whether more pages follow: a non-nil key means more, whatever Count says, because a filter can empty a page that still has a successor.

func QueryPage

func QueryPage[T any, PT interface {
	*T
	ItemDecoder
}](ctx context.Context, table, keyCond string, opts ...dynamodb.QueryOption) (Page[T], error)

QueryPage runs one Query and decodes its page.

This is the form that keeps the request count visible: one call is one request. Query iterates instead, at the cost of hiding how many requests that takes.

func ScanPage

func ScanPage[T any, PT interface {
	*T
	ItemDecoder
}](ctx context.Context, table string, opts ...dynamodb.ScanOption) (Page[T], error)

ScanPage runs one Scan and decodes its page.

func (Page[T]) HasMore

func (p Page[T]) HasMore() bool

HasMore reports whether another page follows this one.

type TableResolver added in v0.2.10

type TableResolver func(ctx context.Context, declared string) string

TableResolver maps the name a declaration or a call site writes onto the name this deployment uses.

It takes the Context so the mapping can depend on the request as well as on the process: a per-tenant table, or a name read from configuration bound to the Context, is the same one function. Nothing here composes the name, so a prefix, a suffix, a lookup table and a wholly unrelated name are equally expressible:

dynamobind.WithTableNames(func(ctx context.Context, declared string) string {
	return "staging-" + declared
})

dynamobind.WithTableNames(func(ctx context.Context, declared string) string {
	return config.Tables[declared] // whatever the IaC named it
})

Jump to

Keyboard shortcuts

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