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: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -438,3 +438,5 @@ Thumbs.db
**/*.DotSettings.user
/.claude/do_not_commit/
/nupkg/

**/.env
6 changes: 3 additions & 3 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionPrefix>1.1.0</VersionPrefix>
<!-- SPDX license identifier for MIT -->
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<!-- Other useful metadata -->
Expand Down Expand Up @@ -28,8 +28,8 @@
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\icon.png" Pack="true" PackagePath="" Visible="False" />
<None Include="..\..\README.md" Pack="true" PackagePath="" Visible="False" />
<None Include="..\..\icon.png" Pack="true" PackagePath="" Visible="False"/>
<None Include="..\..\README.md" Pack="true" PackagePath="" Visible="False"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub">
Expand Down
12 changes: 9 additions & 3 deletions docs/api-reference/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Marks a static partial class as a mapper and sets defaults.
Convention = DynamoNamingConvention.CamelCase,
DefaultRequiredness = Requiredness.InferFromNullability,
IncludeBaseClassProperties = false,
OmitNullStrings = true,
OmitNullValues = true,
OmitEmptyStrings = false,
DateTimeFormat = "O",
EnumFormat = "G")]
Expand All @@ -27,7 +27,9 @@ Properties:
- `Convention` - key naming convention
- `DefaultRequiredness` - default requiredness
- `IncludeBaseClassProperties` - include properties declared on base classes (opt-in)
- `OmitNullStrings` - omit null string attributes
- `OmitNullValues` - omit null values, including nested object and nested collection properties
- `OmitNullStrings` - deprecated legacy option kept for compatibility with helper-backed null
omission
- `OmitEmptyStrings` - omit empty string attributes
- `DateTimeFormat` - `DateTime`/`DateTimeOffset` format
- `TimeSpanFormat` - `TimeSpan` format
Expand All @@ -40,6 +42,8 @@ Notes:
nested inline objects.
- If a derived type declares a property with the same name as an inherited property, the derived
property wins.
- Prefer `OmitNullValues` for mapper-level null omission. `OmitNullStrings` remains available only
as a legacy compatibility option.

## DynamoFieldAttribute

Expand All @@ -57,11 +61,12 @@ public static partial class OrderMapper
```

Properties:

- `MemberName` (ctor) - target member name
- `AttributeName` - DynamoDB attribute name override
- `Required` - requiredness override
- `Kind` - DynamoDB `DynamoKind` override
- `OmitIfNull` - omit when null
- `OmitIfNull` - omit when null, including nested object and nested collection properties
- `OmitIfEmptyString` - omit when empty string
- `ToMethod` / `FromMethod` - static conversion methods on the mapper class

Expand All @@ -80,6 +85,7 @@ public static partial class OrderMapper
```

Properties:

- `MemberName` (ctor) - target member name
- `Ignore` - `IgnoreMapping.All`, `IgnoreMapping.FromModel`, or `IgnoreMapping.ToModel`

Expand Down
88 changes: 49 additions & 39 deletions docs/core-concepts/how-it-works.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ DynamoMapper is an incremental source generator that produces high-performance,
DynamoMapper is built on three fundamental principles:

1. **Domain Stays Clean** - Your domain models remain free of persistence attributes
2. **Compile-Time Safety** - All mapping code is generated and validated at compile time
3. **DynamoDB-Focused** - Single-purpose library for DynamoDB attribute mapping
1. **Compile-Time Safety** - All mapping code is generated and validated at compile time
1. **DynamoDB-Focused** - Single-purpose library for DynamoDB attribute mapping

## Mapping Scope

Expand Down Expand Up @@ -50,24 +50,28 @@ DynamoMapper uses .NET's `IIncrementalGenerator` API to analyze your code at com
### The Generation Pipeline

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

2. **Analysis Phase**
- Resolve target entity types
- Analyze properties (public, readable, writable)
- Apply naming conventions
- Validate converters and hooks
- Locate classes marked with `[DynamoMapper]`
- Find partial mapping methods (`ToItem`, `FromItem`)
- Collect configuration attributes

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

4. **Compilation Phase**
- Generated code is compiled with your project
- No runtime dependencies beyond AWS SDK types
- Resolve target entity types
- Analyze properties (public, readable, writable)
- Apply naming conventions
- Validate converters and hooks

1. **Code Generation Phase**

- Generate `ToItem` implementation
- Generate `FromItem` implementation
- Emit diagnostics for configuration errors

1. **Compilation Phase**

- Generated code is compiled with your project
- No runtime dependencies beyond AWS SDK types

## Mapper Anatomy

Expand Down Expand Up @@ -189,10 +193,13 @@ DynamoMapper uses a layered configuration model:
```csharp
[DynamoMapper(
Convention = DynamoNamingConvention.CamelCase,
OmitNullStrings = true,
OmitNullValues = true,
DateTimeFormat = "O")]
```

`OmitNullStrings` is still supported as a deprecated legacy option for helper-backed null omission,
but `OmitNullValues` is the preferred mapper-level setting.

### 2. Property-Level Overrides

```csharp
Expand Down Expand Up @@ -241,16 +248,16 @@ static partial void AfterToItem(Product source, Dictionary<string, AttributeValu

DynamoMapper natively supports:

| .NET Type | DynamoDB Type | Notes |
|-----------|---------------|-------|
| `string` | S (String) | |
| `int`, `long`, `decimal`, `double` | N (Number) | Culture-invariant |
| `bool` | BOOL | |
| `Guid` | S | ToString/Parse |
| `DateTime`, `DateTimeOffset`, `TimeSpan` | S | ISO-8601 / constant format |
| `enum` | S | String name |
| Nullable variants | S/N/BOOL | Null checks generated |
| Collections | L/M/SS/NS/BS | Lists, maps, and sets of supported element types |
| .NET Type | DynamoDB Type | Notes |
|------------------------------------------|---------------|--------------------------------------------------|
| `string` | S (String) | |
| `int`, `long`, `decimal`, `double` | N (Number) | Culture-invariant |
| `bool` | BOOL | |
| `Guid` | S | ToString/Parse |
| `DateTime`, `DateTimeOffset`, `TimeSpan` | S | ISO-8601 / constant format |
| `enum` | S | String name |
| Nullable variants | S/N/BOOL | Null checks generated |
| Collections | L/M/SS/NS/BS | Lists, maps, and sets of supported element types |

### Custom Types

Expand Down Expand Up @@ -321,6 +328,7 @@ static partial void AfterFromItem(Dictionary<string, AttributeValue> item, ref P
```

**Common use cases:**

- PK/SK composition for single-table design
- Record type discrimination
- TTL attributes
Expand All @@ -332,9 +340,9 @@ static partial void AfterFromItem(Dictionary<string, AttributeValue> item, ref P
DynamoMapper uses hooks instead of a general mapping pipeline because:

1. **Focused scope** - Only two mapping directions, not arbitrary transformations
2. **Zero overhead** - Unimplemented hooks compile away completely
3. **Type safety** - Statically bound, no reflection
4. **DynamoDB patterns** - Designed specifically for single-table design
1. **Zero overhead** - Unimplemented hooks compile away completely
1. **Type safety** - Statically bound, no reflection
1. **DynamoDB patterns** - Designed specifically for single-table design

## Diagnostics

Expand Down Expand Up @@ -370,11 +378,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 |
|-------------------------|---------|----------------|
| ToItem (5 properties) | ~50ns | 1 (dictionary) |
| FromItem (5 properties) | ~100ns | 1 (entity) |
| With hooks | +5-10ns | 0 additional |

## Design Constraints

Expand All @@ -388,6 +396,7 @@ mapper.Configure(x => x.Property("Name").Ignore());
```

**Why:** Compile-time configuration enables:

- Zero reflection
- Faster runtime performance
- Compile-time validation
Expand Down Expand Up @@ -437,16 +446,17 @@ public static partial class ProductMapper
```

**Key points:**

- DSL is **optional** - attributes remain fully supported
- DSL is **compile-time only** - no runtime evaluation
- DSL and attributes can coexist - DSL takes precedence

## Best Practices

1. **Use default conventions** - Override only when necessary
2. **Keep domain clean** - No DynamoDB concerns in domain models
3. **Use hooks for DynamoDB patterns** - PK/SK, TTL, record types
4. **Use static methods for conversions** - Simple, co-located, explicit
1. **Keep domain clean** - No DynamoDB concerns in domain models
1. **Use hooks for DynamoDB patterns** - PK/SK, TTL, record types
1. **Use static methods for conversions** - Simple, co-located, explicit

## See Also

Expand Down
23 changes: 13 additions & 10 deletions docs/usage/field-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@ public static partial class OrderMapper
```

Notes:

- Dot-notation overrides force inline mapping for the nested path.
- Invalid paths emit `DM0008`.
- `OmitIfNull` works on nested object and nested collection properties too, so paths like
`"Customer.Profile"` or `"Customer.Addresses"` can omit null containers during `ToItem`.

### Collection Element Members

Expand Down Expand Up @@ -75,13 +78,13 @@ Notes:

## Supported Options

| Option | Description |
| --- | --- |
| `AttributeName` | Overrides the DynamoDB attribute name. |
| `Required` | Controls requiredness during `FromItem`. |
| `Kind` | Forces a specific `DynamoKind`. |
| `OmitIfNull` | Omits null values during `ToItem`. |
| `OmitIfEmptyString` | Omits empty strings during `ToItem`. |
| `ToMethod` | Uses a custom method to serialize a value. |
| `FromMethod` | Uses a custom method to deserialize a value. |
| `Format` | Overrides default format for date/time/enum conversions. |
| Option | Description |
|---------------------|----------------------------------------------------------------------------------------------|
| `AttributeName` | Overrides the DynamoDB attribute name. |
| `Required` | Controls requiredness during `FromItem`. |
| `Kind` | Forces a specific `DynamoKind`. |
| `OmitIfNull` | Omits null values during `ToItem`, including nested object and nested collection properties. |
| `OmitIfEmptyString` | Omits empty strings during `ToItem`. |
| `ToMethod` | Uses a custom method to serialize a value. |
| `FromMethod` | Uses a custom method to deserialize a value. |
| `Format` | Overrides default format for date/time/enum conversions. |
14 changes: 10 additions & 4 deletions skills/dynamo-mapper/references/core-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ public static partial class OrderMapper
- names default to camelCase
- requiredness defaults to nullability inference
- base-class properties are excluded by default
- null strings are omitted by default
- `OmitNullValues` defaults to `false`
- deprecated `OmitNullStrings` still affects helper-backed nullable scalars and collections
- empty strings are kept by default
- format defaults are `DateTime = O`, `TimeSpan = c`, `Enum = G`, `Guid = D`

Expand All @@ -57,12 +58,14 @@ Use `[DynamoIgnore(memberName)]` to skip one or both directions.
Dot notation works for nested members like `"ShippingAddress.Line1"` and for collection element
members like `"Contacts.VerifiedAt"` (where `Contacts` is `List<CustomerContact>`).

`OmitIfNull` applies at any depth, including nested object and nested collection properties.

## Constructors

- Put `[DynamoMapperConstructor]` on the model constructor, not the mapper.
- If exactly one constructor is marked, it wins.
- Otherwise DynamoMapper prefers a usable parameterless/property-init path and falls back to the
constructor with the most parameters.
constructor with the most parameters.
- Constructor parameters match .NET property names, not DynamoDB attribute names.

## Nested mapping
Expand All @@ -76,8 +79,11 @@ Supported nested shapes include:
Selection order for a nested member:

1. dot-notation override
2. nested mapper
3. inline helper generation
1. nested mapper
1. inline helper generation

Use `OmitNullValues` for mapper-level null omission that should also affect nested object and
nested collection containers. Treat `OmitNullStrings` as legacy compatibility.

## Custom conversion

Expand Down
2 changes: 2 additions & 0 deletions skills/dynamo-mapper/references/gotchas.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
- nested objects are not supported inside sets
- constructor parameter matching uses .NET property names, not attribute names
- empty sets are omitted because DynamoDB does not allow them
- `OmitNullStrings` is legacy and misnamed; prefer `OmitNullValues` for mapper-level null omission,
especially for nested object and nested collection containers

## Stale-doc corrections

Expand Down
Loading
Loading