Skip to content
Closed
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
18 changes: 9 additions & 9 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ dotnet run --project examples/DynamoMapper.SimpleExample/DynamoMapper.SimpleExam
2. **Analysis**: `MapperSyntaxProvider.Transformer()` extracts mapper and model metadata
3. **Type Resolution**: Analyzes properties to determine AttributeValue mappings
4. **Code Generation**: `MapperEmitter.Generate()` renders Scriban template
5. **Output**: Generates `{MapperClassName}.g.cs` with `ToItem` and `FromItem` methods
5. **Output**: Generates `{MapperClassName}.g.cs` with `FromModel` and `ToModel` methods

### Key Components

Expand All @@ -94,7 +94,7 @@ dotnet run --project examples/DynamoMapper.SimpleExample/DynamoMapper.SimpleExam

**WellKnownTypes**: Caches and resolves commonly-used types from Roslyn compilation

**Mapper.scriban**: Scriban template that generates both `ToItem` and `FromItem` method implementations
**Mapper.scriban**: Scriban template that generates both `FromModel` and `ToModel` method implementations

## Supported Types (Phase 1)

Expand Down Expand Up @@ -128,22 +128,22 @@ Nullable: All nullable versions of the above
## Naming Conventions

**Method Prefixes**:
- "To" prefix: Domain model β†’ DynamoDB (e.g., `ToItem`, `ToProduct`)
- "From" prefix: DynamoDB β†’ Domain model (e.g., `FromItem`, `FromProduct`)
- "To" prefix: Domain model β†’ DynamoDB (e.g., `FromModel`, `ToProduct`)
- "From" prefix: DynamoDB β†’ Domain model (e.g., `ToModel`, `FromProduct`)

**Generator Tracking Names** (in `TrackingName.cs`): Used for Roslyn incremental generation debugging

## Customization Hooks (Phase 1)

Mappers support four lifecycle hooks as `static partial void` methods:

- `BeforeToItem(T source, Dictionary<string, AttributeValue> item)`: Before property mapping
- `AfterToItem(T source, Dictionary<string, AttributeValue> item)`: After property mapping (add pk/sk here)
- `BeforeFromItem(Dictionary<string, AttributeValue> item)`: Before deserialization
- `AfterFromItem(Dictionary<string, AttributeValue> item, ref T entity)`: After object construction
- `BeforeFromModel(T source, Dictionary<string, AttributeValue> item)`: Before property mapping
- `AfterFromModel(T source, Dictionary<string, AttributeValue> item)`: After property mapping (add pk/sk here)
- `BeforeToModel(Dictionary<string, AttributeValue> item)`: Before deserialization
- `AfterToModel(Dictionary<string, AttributeValue> item, ref T entity)`: After object construction

**Common Use Cases**:
- Adding single-table keys (pk/sk) in `AfterToItem`
- Adding single-table keys (pk/sk) in `AfterFromModel`
- Adding discriminator fields (`recordType`, `entityType`)
- Adding TTL attributes
- Capturing unmapped attributes in an attribute bag
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ namespace MyApp.Data;
public static partial class ProductMapper
{
[DynamoField(nameof(Product.Description), OmitIfNullOrWhiteSpace = true)]
public static partial Dictionary<string, AttributeValue> ToItem(Product source);
public static partial Dictionary<string, AttributeValue> FromModel(Product source);

public static partial Product FromItem(Dictionary<string, AttributeValue> item);
public static partial Product ToModel(Dictionary<string, AttributeValue> item);
}
```

Expand All @@ -87,7 +87,7 @@ var product = new Product
};

// Convert to DynamoDB item
var item = ProductMapper.ToItem(product);
var item = ProductMapper.FromModel(product);
await dynamoDbClient.PutItemAsync(new PutItemRequest
{
TableName = "MyTable",
Expand All @@ -104,7 +104,7 @@ var getResponse = await dynamoDbClient.GetItemAsync(new GetItemRequest
["sk"] = new AttributeValue { S = $"PRODUCT#{product.ProductId}" }
}
});
var retrievedProduct = ProductMapper.FromItem(getResponse.Item);
var retrievedProduct = ProductMapper.ToModel(getResponse.Item);
```

For more examples including single-table patterns and custom converters, see the [Quick Start Guide](https://layeredcraft.github.io/dynamo-mapper/getting-started/quick-start/).
Expand Down
56 changes: 28 additions & 28 deletions docs/core-concepts/how-it-works.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ DynamoMapper is built on three fundamental principles:
DynamoMapper supports **exactly two mapping directions**:

```csharp
// ToItem: Domain model β†’ DynamoDB item
Dictionary<string, AttributeValue> ToItem(T source);
// FromModel: Domain model β†’ DynamoDB item
Dictionary<string, AttributeValue> FromModel(T source);

// FromItem: DynamoDB item β†’ Domain model
T FromItem(Dictionary<string, AttributeValue> item);
// ToModel: DynamoDB item β†’ Domain model
T ToModel(Dictionary<string, AttributeValue> item);
```

### What DynamoMapper Does NOT Do
Expand All @@ -51,7 +51,7 @@ DynamoMapper uses .NET's `IIncrementalGenerator` API to analyze your code at com

1. **Discovery Phase**
- Locate classes marked with `[DynamoMapper]`
- Find partial mapping methods (`ToItem`, `FromItem`)
- Find partial mapping methods (`FromModel`, `ToModel`)
- Collect configuration attributes

2. **Analysis Phase**
Expand All @@ -61,8 +61,8 @@ DynamoMapper uses .NET's `IIncrementalGenerator` API to analyze your code at com
- Validate converters and hooks

3. **Code Generation Phase**
- Generate `ToItem` implementation
- Generate `FromItem` implementation
- Generate `FromModel` implementation
- Generate `ToModel` implementation
- Emit diagnostics for configuration errors

4. **Compilation Phase**
Expand All @@ -80,12 +80,12 @@ using Amazon.DynamoDBv2.Model;
public static partial class ProductMapper
{
// Partial method declarations (you provide)
public static partial Dictionary<string, AttributeValue> ToItem(Product source);
public static partial Product FromItem(Dictionary<string, AttributeValue> item);
public static partial Dictionary<string, AttributeValue> FromModel(Product source);
public static partial Product ToModel(Dictionary<string, AttributeValue> item);

// Generated implementations (DynamoMapper provides)
// - ToItem implementation
// - FromItem implementation
// - FromModel implementation
// - ToModel implementation
}
```

Expand All @@ -110,8 +110,8 @@ public class Product
[DynamoMapper(Convention = DynamoNamingConvention.CamelCase)]
public static partial class ProductMapper
{
public static partial Dictionary<string, AttributeValue> ToItem(Product source);
public static partial Product FromItem(Dictionary<string, AttributeValue> item);
public static partial Dictionary<string, AttributeValue> FromModel(Product source);
public static partial Product ToModel(Dictionary<string, AttributeValue> item);
}
```

Expand All @@ -120,7 +120,7 @@ DynamoMapper generates:
```csharp
public static partial class ProductMapper
{
public static partial Dictionary<string, AttributeValue> ToItem(Product source)
public static partial Dictionary<string, AttributeValue> FromModel(Product source)
{
var item = new Dictionary<string, AttributeValue>(capacity: 3);

Expand All @@ -131,7 +131,7 @@ public static partial class ProductMapper
return item;
}

public static partial Product FromItem(Dictionary<string, AttributeValue> item)
public static partial Product ToModel(Dictionary<string, AttributeValue> item)
{
var entity = new Product
{
Expand Down Expand Up @@ -170,7 +170,7 @@ DynamoMapper uses a layered configuration model:
```csharp
[DynamoField(nameof(Product.Name), Required = true)]
[DynamoField(nameof(Product.Description), OmitIfNullOrWhiteSpace = true)]
public static partial Dictionary<string, AttributeValue> ToItem(Product source);
public static partial Dictionary<string, AttributeValue> FromModel(Product source);
```

### 3. Converters
Expand All @@ -190,7 +190,7 @@ Two approaches, both first-class:
### 4. Customization Hooks

```csharp
static partial void AfterToItem(Product source, Dictionary<string, AttributeValue> item)
static partial void AfterFromModel(Product source, Dictionary<string, AttributeValue> item)
{
item["pk"] = new AttributeValue { S = $"PRODUCT#{source.ProductId}" };
item["sk"] = new AttributeValue { S = "METADATA" };
Expand Down Expand Up @@ -267,16 +267,16 @@ Hooks enable DynamoDB-specific patterns without compromising the focused mapping

```csharp
// Before property mapping
static partial void BeforeToItem(Product source, Dictionary<string, AttributeValue> item);
static partial void BeforeFromModel(Product source, Dictionary<string, AttributeValue> item);

// After property mapping - most common
static partial void AfterToItem(Product source, Dictionary<string, AttributeValue> item);
static partial void AfterFromModel(Product source, Dictionary<string, AttributeValue> item);

// Before deserialization
static partial void BeforeFromItem(Dictionary<string, AttributeValue> item);
static partial void BeforeToModel(Dictionary<string, AttributeValue> item);

// After object construction
static partial void AfterFromItem(Dictionary<string, AttributeValue> item, ref Product entity);
static partial void AfterToModel(Dictionary<string, AttributeValue> item, ref Product entity);
```

**Common use cases:**
Expand Down Expand Up @@ -331,11 +331,11 @@ error DM0201: Static conversion method 'ToStatus' not found on mapper 'OrderMapp

### Benchmarks (Typical)

| Operation | Time | Allocations |
|-----------|------|-------------|
| ToItem (5 properties) | ~50ns | 1 (dictionary) |
| FromItem (5 properties) | ~100ns | 1 (entity) |
| With hooks | +5-10ns | 0 additional |
| Operation | Time | Allocations |
|--------------------------|---------|----------------|
| FromModel (5 properties) | ~50ns | 1 (dictionary) |
| ToModel (5 properties) | ~100ns | 1 (entity) |
| With hooks | +5-10ns | 0 additional |

## Design Constraints

Expand Down Expand Up @@ -382,8 +382,8 @@ Phase 2 adds an optional fluent DSL while maintaining all Phase 1 capabilities:
[DynamoMapper]
public static partial class ProductMapper
{
public static partial Dictionary<string, AttributeValue> ToItem(Product source);
public static partial Product FromItem(Dictionary<string, AttributeValue> item);
public static partial Dictionary<string, AttributeValue> FromModel(Product source);
public static partial Product ToModel(Dictionary<string, AttributeValue> item);

static partial void Configure(DynamoMapBuilder<Product> map)
{
Expand Down
Loading