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
- Variables
- func ClientFromContext(ctx context.Context) (*dynamodb.Client, error)
- func Load[T any, PT interface{ ... }](ctx context.Context, table string, key dynamodb.Key, ...) (T, error)
- func LoadAll[T any, PT interface{ ... }](ctx context.Context, table string, keys []dynamodb.Key, ...) ([]T, []dynamodb.Key, error)
- func Query[T any, PT interface{ ... }](ctx context.Context, table, keyCond string, opts ...dynamodb.QueryOption) iter.Seq2[T, error]
- func Remove[T Keyer](ctx context.Context, table string, v T, opts ...dynamodb.WriteOption) error
- func RemoveReturning[T Keyer, PT interface{ ... }](ctx context.Context, table string, v T, opts ...dynamodb.WriteOption) (T, bool, error)
- func Scan[T any, PT interface{ ... }](ctx context.Context, table string, opts ...dynamodb.ScanOption) iter.Seq2[T, error]
- func Store[T ItemEncoder](ctx context.Context, table string, v T, opts ...dynamodb.WriteOption) error
- func StoreAll[T ItemEncoder](ctx context.Context, table string, vs []T) ([]T, error)
- func StoreReturning[T ItemEncoder, PT interface{ ... }](ctx context.Context, table string, v T, opts ...dynamodb.WriteOption) (T, bool, error)
- func TableFromContext(ctx context.Context, table string) (*dynamodb.Client, string, error)
- func TypeError(attribute, expected string, got dynamodb.AttributeValue) error
- func Update[T Keyer](ctx context.Context, table string, v T, update string, ...) error
- func ValueError(attribute, message string, cause error) error
- func WithClient(ctx context.Context, c *dynamodb.Client, options ...ClientOption) context.Context
- func WithHandle(ctx context.Context, h Handle) context.Context
- type ClientOption
- type Error
- type Handle
- func (h Handle) Client() *dynamodb.Client
- func (h Handle) Load[T any, PT interface{ ... }](ctx context.Context, table string, key dynamodb.Key, ...) (T, error)
- func (h Handle) LoadAll[T any, PT interface{ ... }](ctx context.Context, table string, keys []dynamodb.Key, ...) ([]T, []dynamodb.Key, error)
- func (h Handle) Query[T any, PT interface{ ... }](ctx context.Context, table, keyCond string, opts ...dynamodb.QueryOption) iter.Seq2[T, error]
- func (h Handle) QueryPage[T any, PT interface{ ... }](ctx context.Context, table, keyCond string, opts ...dynamodb.QueryOption) (Page[T], error)
- func (h Handle) Remove[T Keyer](ctx context.Context, table string, v T, opts ...dynamodb.WriteOption) error
- func (h Handle) RemoveReturning[T Keyer, PT interface{ ... }](ctx context.Context, table string, v T, opts ...dynamodb.WriteOption) (T, bool, error)
- func (h Handle) Scan[T any, PT interface{ ... }](ctx context.Context, table string, opts ...dynamodb.ScanOption) iter.Seq2[T, error]
- func (h Handle) ScanPage[T any, PT interface{ ... }](ctx context.Context, table string, opts ...dynamodb.ScanOption) (Page[T], error)
- func (h Handle) Store[T ItemEncoder](ctx context.Context, table string, v T, opts ...dynamodb.WriteOption) error
- func (h Handle) StoreAll[T ItemEncoder](ctx context.Context, table string, vs []T) ([]T, error)
- func (h Handle) StoreReturning[T ItemEncoder, PT interface{ ... }](ctx context.Context, table string, v T, opts ...dynamodb.WriteOption) (T, bool, error)
- func (h Handle) Table(ctx context.Context, table string) (*dynamodb.Client, string, error)
- func (h Handle) Update[T Keyer](ctx context.Context, table string, v T, update string, ...) error
- type ItemDecoder
- type ItemEncoder
- type Keyer
- type Page
- type TableResolver
Constants ¶
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 ¶
var ErrNoClient = errors.New("dynamobind: no DynamoDB client in context")
ErrNoClient reports that a Context does not carry a DynamoDB client, or that a zero Handle was passed to an entry taking one. It is returned rather than panicking, so every entry stays an ordinary error-returning function.
Functions ¶
func ClientFromContext ¶ added in v0.2.10
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 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
TableFromContext resolves a declared table name into the client and the name to send. Every Context-resolving 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 ¶
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
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.
func WithHandle ¶ added in v0.3.7
WithHandle returns a child Context carrying an already-built Handle.
It is WithClient for a caller that holds a Handle: a framework resolving one out of its own Context value, or a setup path that builds the Handle once and installs it in several Contexts.
Types ¶
type ClientOption ¶ added in v0.2.10
type ClientOption func(*Handle)
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.
type Handle ¶ added in v0.3.7
type Handle struct {
// contains filtered or unexported fields
}
Handle is a DynamoDB client together with the table naming of one deployment.
It is what WithClient stores in a Context, and it is what the entries suffixed On take directly. The two forms are the same value reached two ways: a Context keeps it off every call site, and a parameter keeps it out of every lookup. Which one a program uses is a call-site preference, not a behaviour difference.
The zero Handle carries no client, so an entry given one returns ErrNoClient exactly as a Context carrying none does.
Fields are unexported so a field added later is not a breaking change; build one with NewHandle.
func HandleFromContext ¶ added in v0.3.7
HandleFromContext returns the Handle installed by WithClient or WithHandle.
It is the one lookup a caller needs: a framework reading it once in middleware has the client and the table naming in hand, and can then call the Handle's methods with no further Context lookup on the operation path.
func NewHandle ¶ added in v0.3.7
func NewHandle(c *dynamodb.Client, options ...ClientOption) Handle
NewHandle binds a client to the table naming of one deployment, for the methods on it.
It takes the same ClientOption list as WithClient, so a program moving between the two forms rewrites the call and not the configuration:
h := dynamobind.NewHandle(client, dynamobind.WithTableNames(names)) r, err := h.Load[Reading](ctx, "readings", key)
func (Handle) Client ¶ added in v0.3.7
Client returns the driver client this Handle carries, or nil for the zero Handle. It is the escape hatch for reaching the driver directly, and it applies no table naming; Table is what applies that.
func (Handle) Load ¶ added in v0.5.28
func (h Handle) Load[T any, PT interface { *T ItemDecoder }](ctx context.Context, table string, key dynamodb.Key, opts ...dynamodb.GetOption) (T, error)
Load is Load on a Handle the caller already holds.
func (Handle) LoadAll ¶ added in v0.5.28
func (h Handle) LoadAll[T any, PT interface { *T ItemDecoder }](ctx context.Context, table string, keys []dynamodb.Key, opts ...dynamodb.BatchOption) ([]T, []dynamodb.Key, error)
LoadAll is LoadAll on a Handle the caller already holds.
func (Handle) Query ¶ added in v0.5.28
func (h Handle) Query[T any, PT interface { *T ItemDecoder }](ctx context.Context, table, keyCond string, opts ...dynamodb.QueryOption) iter.Seq2[T, error]
Query is Query on a Handle the caller already holds. The Handle is resolved once for the whole range rather than once per page.
func (Handle) QueryPage ¶ added in v0.5.28
func (h Handle) QueryPage[T any, PT interface { *T ItemDecoder }](ctx context.Context, table, keyCond string, opts ...dynamodb.QueryOption) (Page[T], error)
QueryPage is QueryPage on a Handle the caller already holds.
func (Handle) Remove ¶ added in v0.5.28
func (h Handle) Remove[T Keyer](ctx context.Context, table string, v T, opts ...dynamodb.WriteOption) error
Remove is Remove on a Handle the caller already holds.
func (Handle) RemoveReturning ¶ added in v0.5.28
func (h Handle) RemoveReturning[T Keyer, PT interface { *T ItemDecoder }](ctx context.Context, table string, v T, opts ...dynamodb.WriteOption) (T, bool, error)
RemoveReturning is RemoveReturning on a Handle the caller already holds.
func (Handle) Scan ¶ added in v0.5.28
func (h Handle) Scan[T any, PT interface { *T ItemDecoder }](ctx context.Context, table string, opts ...dynamodb.ScanOption) iter.Seq2[T, error]
Scan is Scan on a Handle the caller already holds. The Handle is resolved once for the whole range rather than once per page.
func (Handle) ScanPage ¶ added in v0.5.28
func (h Handle) ScanPage[T any, PT interface { *T ItemDecoder }](ctx context.Context, table string, opts ...dynamodb.ScanOption) (Page[T], error)
ScanPage is ScanPage on a Handle the caller already holds.
func (Handle) Store ¶ added in v0.5.28
func (h Handle) Store[T ItemEncoder](ctx context.Context, table string, v T, opts ...dynamodb.WriteOption) error
Store is Store on a Handle the caller already holds.
func (Handle) StoreAll ¶ added in v0.5.28
StoreAll is StoreAll on a Handle the caller already holds.
func (Handle) StoreReturning ¶ added in v0.5.28
func (h Handle) StoreReturning[T ItemEncoder, PT interface { *T ItemDecoder }](ctx context.Context, table string, v T, opts ...dynamodb.WriteOption) (T, bool, error)
StoreReturning is StoreReturning on a Handle the caller already holds.
type ItemDecoder ¶
ItemDecoder fills a value from a DynamoDB item. Generated code implements it on the pointer receiver.
type ItemEncoder ¶
ItemEncoder converts a value into a DynamoDB item. Generated code implements it on the value receiver.
type Keyer ¶
Keyer reports the primary key of a value. Generated code implements it when the type carries a partitionkey tag.
type Page ¶
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.
type TableResolver ¶ added in v0.2.10
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
})