Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/publish_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
- name: Generate Changelog
id: changelog
run: |
PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -v "^${{ github.ref_name }}$" | head -n1)
PREVIOUS_TAG=$(git tag --sort=-creatordate | grep -E 'v[0-9]+\.[0-9]+\.[0-9]+$' | grep -v "^${{ github.ref_name }}$" | head -n1
if [ -z "$PREVIOUS_TAG" ]; then
PREVIOUS_TAG=$(git rev-list --max-parents=0 HEAD)
fi
Expand Down
2 changes: 0 additions & 2 deletions templates/v2/core/mixin_filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@ const KeyConditionMixinSugarTemplate = `

// WithEQ adds equality key condition.
// Required for partition key, optional for sort key.
// Example: .WithEQ("user_id", "123")
func (kcm *KeyConditionMixin) WithEQ(field string, value any) {
kcm.With(field, EQ, value)
}

// WithBetween adds range key condition for sort keys.
// Example: .WithBetween("created_at", start_time, end_time)
func (kcm *KeyConditionMixin) WithBetween(field string, start, end any) {
kcm.With(field, BETWEEN, start, end)
}
Expand Down
3 changes: 0 additions & 3 deletions templates/v2/core/mixins.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,12 @@ func NewPaginationMixin() PaginationMixin {
}

// Limit sets the maximum number of items to return in one request.
// Example: .Limit(25)
func (pm *PaginationMixin) Limit(limit int) {
pm.LimitValue = &limit
}

// StartFrom sets the exclusive start key for pagination.
// Use LastEvaluatedKey from previous response for next page.
// Example: .StartFrom(previousResponse.LastEvaluatedKey)
func (pm *PaginationMixin) StartFrom(lastEvaluatedKey map[string]types.AttributeValue) {
pm.ExclusiveStartKey = lastEvaluatedKey
}
Expand All @@ -87,7 +85,6 @@ func (kcm *KeyConditionMixin) With(field string, op OperatorType, values ...any)
if !ValidateValues(op, values) {
return
}

fieldInfo, exists := TableSchema.FieldsMap[field]
if !exists {
return
Expand Down
6 changes: 0 additions & 6 deletions templates/v2/core/mixins_with.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ const FilterMixinSugarTemplate = `
// CONVENIENCE METHODS - Only available in ALL mode

// FilterEQ adds equality filter condition.
// Example: .FilterEQ("status", "active")
func (fm *FilterMixin) FilterEQ(field string, value any) {
fm.Filter(field, EQ, value)
}

// FilterContains adds contains filter for strings or sets.
// Example: .FilterContains("tags", "important")
func (fm *FilterMixin) FilterContains(field string, value any) {
fm.Filter(field, CONTAINS, value)
}
Expand All @@ -22,13 +20,11 @@ func (fm *FilterMixin) FilterNotContains(field string, value any) {
}

// FilterBeginsWith adds begins_with filter for strings.
// Example: .FilterBeginsWith("email", "admin@")
func (fm *FilterMixin) FilterBeginsWith(field string, value any) {
fm.Filter(field, BEGINS_WITH, value)
}

// FilterBetween adds range filter for comparable values.
// Example: .FilterBetween("price", 10, 100)
func (fm *FilterMixin) FilterBetween(field string, start, end any) {
fm.Filter(field, BETWEEN, start, end)
}
Expand All @@ -54,7 +50,6 @@ func (fm *FilterMixin) FilterLTE(field string, value any) {
}

// FilterExists checks if attribute exists.
// Example: .FilterExists("optional_field")
func (fm *FilterMixin) FilterExists(field string) {
fm.Filter(field, EXISTS)
}
Expand All @@ -71,7 +66,6 @@ func (fm *FilterMixin) FilterNE(field string, value any) {

// FilterIn adds IN filter for scalar values.
// For DynamoDB Sets (SS/NS), use FilterContains instead.
// Example: .FilterIn("category", "books", "electronics")
func (fm *FilterMixin) FilterIn(field string, values ...any) {
if len(values) == 0 {
return
Expand Down
2 changes: 0 additions & 2 deletions templates/v2/core/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package core
// SchemaTemplate with pre-computed allowed operators
const SchemaTemplate = `
// FieldInfo contains metadata about a schema field with operator validation.
// Provides O(1) lookup for supported DynamoDB operations per field type.
type FieldInfo struct {
DynamoType string
IsKey bool
Expand All @@ -14,7 +13,6 @@ type FieldInfo struct {

// SupportsOperator checks if this field supports the given operator.
// Returns false for invalid operator/type combinations.
// Example: stringField.SupportsOperator(BEGINS_WITH) -> true
func (fi FieldInfo) SupportsOperator(op OperatorType) bool {
return fi.AllowedOperators[op]
}
Expand Down
36 changes: 14 additions & 22 deletions templates/v2/generic/operators.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,29 @@ const (
LTE OperatorType = "<=" // Less than or equal

// Range operator for between comparisons
BETWEEN OperatorType = "BETWEEN" // Between two values (inclusive)
BETWEEN OperatorType = "BETWEEN"

// String operators - work with String types and Sets
CONTAINS OperatorType = "contains" // Contains substring or set member
NOT_CONTAINS OperatorType = "not_contains" // Does not contain substring or member
BEGINS_WITH OperatorType = "begins_with" // String starts with prefix
CONTAINS OperatorType = "contains"
NOT_CONTAINS OperatorType = "not_contains"
BEGINS_WITH OperatorType = "begins_with"

// Set operators for scalar values only (not DynamoDB Sets SS/NS)
IN OperatorType = "IN" // Value is in list of values
NOT_IN OperatorType = "NOT_IN" // Value is not in list of values
IN OperatorType = "IN"
NOT_IN OperatorType = "NOT_IN"

// Existence operators - work with all types
EXISTS OperatorType = "attribute_exists" // Attribute exists
NOT_EXISTS OperatorType = "attribute_not_exists" // Attribute does not exist
EXISTS OperatorType = "attribute_exists"
NOT_EXISTS OperatorType = "attribute_not_exists"
)

// ConditionType defines whether this is a key condition or filter condition.
// Key conditions are used in Query operations, filters in both Query and Scan.
type ConditionType string

const (
KeyCondition ConditionType = "KEY" // For partition/sort key conditions
FilterCondition ConditionType = "FILTER" // For non-key attribute filtering
KeyCondition ConditionType = "KEY"
FilterCondition ConditionType = "FILTER"
)

// Condition represents a single query or filter condition with validation metadata.
Expand All @@ -54,7 +54,6 @@ type Condition struct {
type KeyOperatorHandler func(expression.KeyBuilder, []any) expression.KeyConditionBuilder
type ConditionOperatorHandler func(expression.NameBuilder, []any) expression.ConditionBuilder

// keyOperatorHandlers provides O(1) lookup for key condition operations.
// Only includes operators valid for key conditions (partition/sort keys).
var keyOperatorHandlers = map[OperatorType]KeyOperatorHandler{
EQ: func(field expression.KeyBuilder, values []any) expression.KeyConditionBuilder {
Expand Down Expand Up @@ -88,10 +87,8 @@ var allowedKeyConditionOperators = map[OperatorType]bool{
BETWEEN: true,
}

// conditionOperatorHandlers provides O(1) lookup for filter operations.
// Includes all operators supported in filter expressions.
var conditionOperatorHandlers = map[OperatorType]ConditionOperatorHandler{
// Basic comparison operators
EQ: func(field expression.NameBuilder, values []any) expression.ConditionBuilder {
return field.Equal(expression.Value(values[0]))
},
Expand All @@ -114,7 +111,6 @@ var conditionOperatorHandlers = map[OperatorType]ConditionOperatorHandler{
return field.Between(expression.Value(values[0]), expression.Value(values[1]))
},

// String and set operations
CONTAINS: func(field expression.NameBuilder, values []any) expression.ConditionBuilder {
return field.Contains(fmt.Sprintf("%v", values[0]))
},
Expand All @@ -125,7 +121,6 @@ var conditionOperatorHandlers = map[OperatorType]ConditionOperatorHandler{
return field.BeginsWith(fmt.Sprintf("%v", values[0]))
},

// Scalar value list operations (not for DynamoDB Sets)
IN: func(field expression.NameBuilder, values []any) expression.ConditionBuilder {
if len(values) == 0 {
return expression.AttributeNotExists(field)
Expand Down Expand Up @@ -153,7 +148,6 @@ var conditionOperatorHandlers = map[OperatorType]ConditionOperatorHandler{
return expression.Not(field.In(operands[0], operands[1:]...))
},

// Existence checks
EXISTS: func(field expression.NameBuilder, values []any) expression.ConditionBuilder {
return expression.AttributeExists(field)
},
Expand All @@ -167,13 +161,13 @@ var conditionOperatorHandlers = map[OperatorType]ConditionOperatorHandler{
func ValidateValues(op OperatorType, values []any) bool {
switch op {
case EQ, NE, GT, LT, GTE, LTE, CONTAINS, NOT_CONTAINS, BEGINS_WITH:
return len(values) == 1 // Single value operators
return len(values) == 1
case BETWEEN:
return len(values) == 2 // Start and end values
return len(values) == 2
case IN, NOT_IN:
return len(values) >= 1 // At least one value required
return len(values) >= 1
case EXISTS, NOT_EXISTS:
return len(values) == 0 // No values needed
return len(values) == 0
default:
return false
}
Expand All @@ -196,7 +190,6 @@ func ValidateOperator(fieldName string, op OperatorType) bool {

// BuildConditionExpression converts operator to DynamoDB filter expression.
// Creates type-safe filter conditions with full validation.
// Example: BuildConditionExpression("name", EQ, []any{"John"})
func BuildConditionExpression(field string, op OperatorType, values []any) (expression.ConditionBuilder, error) {
fieldInfo, exists := TableSchema.FieldsMap[field]
if !exists {
Expand All @@ -217,7 +210,6 @@ func BuildConditionExpression(field string, op OperatorType, values []any) (expr

// BuildKeyConditionExpression converts operator to DynamoDB key condition.
// Creates type-safe key conditions for Query operations only.
// Example: BuildKeyConditionExpression("user_id", EQ, []any{"123"})
func BuildKeyConditionExpression(field string, op OperatorType, values []any) (expression.KeyConditionBuilder, error) {
fieldInfo, exists := TableSchema.FieldsMap[field]
if !exists {
Expand Down
3 changes: 0 additions & 3 deletions templates/v2/helpers/atomic.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const AtomicHelpersTemplate = `
// IncrementAttribute atomically increments a numeric attribute by a specified value.
// Uses DynamoDB's ADD operation to ensure thread-safe increments without race conditions.
// Creates the attribute with the increment value if it doesn't exist.
// Example: IncrementAttribute("user123", nil, "view_count", 1)
func IncrementAttribute(hashKeyValue any, rangeKeyValue any, attributeName string, incrementValue int) (*dynamodb.UpdateItemInput, error) {
if err := validateKeyInputs(hashKeyValue, rangeKeyValue); err != nil {
return nil, err
Expand Down Expand Up @@ -38,7 +37,6 @@ func IncrementAttribute(hashKeyValue any, rangeKeyValue any, attributeName strin
// Uses DynamoDB's ADD operation for sets - duplicate values are automatically ignored.
// Creates the set with provided values if the attribute doesn't exist.
// Supports string sets ([]string) and numeric sets ([]int, []float64, etc.).
// Example: AddToSet("user123", nil, "tags", []string{"premium", "verified"})
func AddToSet(hashKeyValue any, rangeKeyValue any, attributeName string, values any) (*dynamodb.UpdateItemInput, error) {
if err := validateKeyInputs(hashKeyValue, rangeKeyValue); err != nil {
return nil, err
Expand Down Expand Up @@ -91,7 +89,6 @@ func AddToSet(hashKeyValue any, rangeKeyValue any, attributeName string, values
// Uses DynamoDB's DELETE operation for sets - non-existent values are ignored.
// If all values are removed, the attribute is deleted from the item.
// Supports string sets ([]string) and numeric sets ([]int, []float64, etc.).
// Example: RemoveFromSet("user123", nil, "tags", []string{"temporary"})
func RemoveFromSet(hashKeyValue any, rangeKeyValue any, attributeName string, values any) (*dynamodb.UpdateItemInput, error) {
if err := validateKeyInputs(hashKeyValue, rangeKeyValue); err != nil {
return nil, err
Expand Down
3 changes: 0 additions & 3 deletions templates/v2/helpers/marshaling.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ type Float interface {

// toIntStrings converts any signed or unsigned integer slice to string slice.
// DynamoDB requires numeric sets as string arrays for the wire protocol.
// Example: toIntStrings([]int{1, 2, 3}) -> ["1", "2", "3"]
func toIntStrings[T Signed | Unsigned](nums []T) []string {
out := make([]string, len(nums))
for i, n := range nums {
Expand All @@ -29,7 +28,6 @@ func toIntStrings[T Signed | Unsigned](nums []T) []string {

// toFloatStrings converts any float slice to string slice.
// Uses 'g' format for optimal precision and readability.
// Example: toFloatStrings([]float64{1.5, 2.7}) -> ["1.5", "2.7"]
func toFloatStrings[F Float](nums []F) []string {
out := make([]string, len(nums))
for i, f := range nums {
Expand Down Expand Up @@ -131,7 +129,6 @@ func marshalUpdatesWithSchema(updates map[string]any) (map[string]types.Attribut
// marshalValueByType marshals value according to specific DynamoDB type.
// Handles special cases like String Sets (SS) and Number Sets (NS) that require
// custom marshaling logic not provided by the default AWS SDK marshaler.
// Example: marshalValueByType([]int{1,2,3}, "NS") -> AttributeValueMemberNS
func marshalValueByType(value any, dynamoType string) (types.AttributeValue, error) {
switch dynamoType {
case "SS":
Expand Down
27 changes: 0 additions & 27 deletions templates/v2/helpers/stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const StreamHelpersTemplate = `
// ExtractFromDynamoDBStreamEvent extracts SchemaItem from DynamoDB stream event.
// Converts Lambda stream AttributeValues to DynamoDB SDK types for safe unmarshaling.
// Used for INSERT and MODIFY events to get the new item state.
// Example: item, err := ExtractFromDynamoDBStreamEvent(record)
func ExtractFromDynamoDBStreamEvent(dbEvent events.DynamoDBEventRecord) (*SchemaItem, error) {
if dbEvent.Change.NewImage == nil {
return nil, fmt.Errorf("new image is nil in the event")
Expand All @@ -22,7 +21,6 @@ func ExtractFromDynamoDBStreamEvent(dbEvent events.DynamoDBEventRecord) (*Schema
// ExtractOldFromDynamoDBStreamEvent extracts old SchemaItem from DynamoDB stream event.
// Converts Lambda stream AttributeValues to DynamoDB SDK types for safe unmarshaling.
// Used for MODIFY and REMOVE events to get the previous item state.
// Example: oldItem, err := ExtractOldFromDynamoDBStreamEvent(record)
func ExtractOldFromDynamoDBStreamEvent(dbEvent events.DynamoDBEventRecord) (*SchemaItem, error) {
if dbEvent.Change.OldImage == nil {
return nil, fmt.Errorf("old image is nil in the event")
Expand All @@ -37,7 +35,6 @@ func ExtractOldFromDynamoDBStreamEvent(dbEvent events.DynamoDBEventRecord) (*Sch
}

// toDynamoMap converts Lambda events.DynamoDBAttributeValue to SDK types.AttributeValue.
// Required because Lambda and DynamoDB SDK use different attribute value types.
func toDynamoMap(streamAttrs map[string]events.DynamoDBAttributeValue) map[string]types.AttributeValue {
dynamoAttrs := make(map[string]types.AttributeValue, len(streamAttrs))
for key, streamAttr := range streamAttrs {
Expand All @@ -47,7 +44,6 @@ func toDynamoMap(streamAttrs map[string]events.DynamoDBAttributeValue) map[strin
}

// toDynamoAttr converts single Lambda AttributeValue to SDK AttributeValue.
// Handles all DynamoDB data types including nested Lists and Maps.
func toDynamoAttr(streamAttr events.DynamoDBAttributeValue) types.AttributeValue {
switch streamAttr.DataType() {
case events.DataTypeString:
Expand Down Expand Up @@ -86,7 +82,6 @@ func toDynamoAttr(streamAttr events.DynamoDBAttributeValue) types.AttributeValue
// IsFieldModified checks if a specific field was modified in a MODIFY event.
// Compares old and new values to detect actual changes, not just updates.
// Returns false for INSERT/REMOVE events or if images are missing.
// Example: if IsFieldModified(record, "status") { ... }
func IsFieldModified(dbEvent events.DynamoDBEventRecord, fieldName string) bool {
if dbEvent.EventName != "MODIFY" {
return false
Expand Down Expand Up @@ -160,28 +155,6 @@ func streamAttributeValuesEqual(a, b events.DynamoDBAttributeValue) bool {
}
}

// GetBoolFieldChanged checks if a boolean field changed from false to true.
// Useful for detecting state transitions like activation flags.
// Example: if GetBoolFieldChanged(record, "is_verified") { sendWelcomeEmail() }
func GetBoolFieldChanged(dbEvent events.DynamoDBEventRecord, fieldName string) bool {
if dbEvent.EventName != "MODIFY" {
return false
}
if dbEvent.Change.OldImage == nil || dbEvent.Change.NewImage == nil {
return false
}

oldValue := false
if oldVal, ok := dbEvent.Change.OldImage[fieldName]; ok {
oldValue = oldVal.Boolean()
}
newValue := false
if newVal, ok := dbEvent.Change.NewImage[fieldName]; ok {
newValue = newVal.Boolean()
}
return !oldValue && newValue
}

// ExtractBothFromDynamoDBStreamEvent extracts both old and new items from stream event.
// Returns nil for missing images (e.g., oldItem is nil for INSERT events).
// Useful for MODIFY events where you need to compare before/after states.
Expand Down
Loading