diff --git a/CHANGELOG.md b/CHANGELOG.md index e8de85e..9753d4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes are recorded here. This project follows Semantic Versioning. ## [Unreleased] +## [0.1.2] - 2026-09-17 + +### Added + +- Add `LexKey.AsMemory()` for allocation-free integration with memory-oriented storage APIs. + ## [0.1.1] - 2026-09-17 ### Fixed diff --git a/Directory.Build.props b/Directory.Build.props index 043d738..345872f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -13,7 +13,7 @@ true true 1.0.0.0 - 0.1.1 + 0.1.2 $(PackageVersion) $(PackageVersion) true diff --git a/README.md b/README.md index 73a8ffe..14cd91c 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ UUIDs, time values, composites, and storage ranges. The package is published to Cntryl's GitHub Packages feed: ```xml - + ``` The package namespace is `Cntryl.Keys` so the primary type remains the unambiguous @@ -45,6 +45,8 @@ The composite convenience API uses typed, stack-friendly descriptors and allocat the immutable result for strings, numbers, UUIDs, existing keys, and custom encoders. The reusable encoder can write without managed allocations after its buffer reaches capacity; materializing a `LexKey` or array intentionally creates an owned copy. +Use `AsMemory()` to pass an encoded key to memory-oriented storage APIs such as Fitz without +copying; use `ToArray()` only when the caller needs mutable ownership. ## Range helpers diff --git a/smoke/LexKey.PackageConsumer/LexKey.PackageConsumer.csproj b/smoke/LexKey.PackageConsumer/LexKey.PackageConsumer.csproj index 87ddefc..88687ed 100644 --- a/smoke/LexKey.PackageConsumer/LexKey.PackageConsumer.csproj +++ b/smoke/LexKey.PackageConsumer/LexKey.PackageConsumer.csproj @@ -5,7 +5,7 @@ enable enable true - 0.1.1 + 0.1.2 $(MSBuildThisFileDirectory)../../artifacts/consumer-package-cache false true diff --git a/smoke/LexKey.PackageConsumer/Program.cs b/smoke/LexKey.PackageConsumer/Program.cs index 5bac1d3..2517084 100644 --- a/smoke/LexKey.PackageConsumer/Program.cs +++ b/smoke/LexKey.PackageConsumer/Program.cs @@ -3,6 +3,8 @@ var key = LexKey.EncodeComposite("tenant", 42L, true); if (key.ToHexString() != "74656e616e7400800000000000002a0001") throw new InvalidOperationException("Packed composite encoding did not match the contract."); +if (!key.AsMemory().Span.SequenceEqual(key.AsSpan())) + throw new InvalidOperationException("The allocation-free memory view did not match the encoded key."); var uuid = LexKey.EncodeGuid(Guid.Parse("550e8400-e29b-41d4-a716-446655440000")); if (uuid.ToHexString() != "550e8400e29b41d4a716446655440000") diff --git a/src/LexKey/LexKey.cs b/src/LexKey/LexKey.cs index a0a057a..69ed10f 100644 --- a/src/LexKey/LexKey.cs +++ b/src/LexKey/LexKey.cs @@ -33,6 +33,9 @@ public sealed class LexKey : IComparable, IEquatable, ILexKeyEnc /// Returns a read-only view of the encoded bytes. public ReadOnlySpan AsSpan() => _bytes; + /// Returns an allocation-free read-only memory view of the encoded bytes. + public ReadOnlyMemory AsMemory() => _bytes; + /// Returns an owned copy of the encoded bytes. public byte[] ToArray() => _bytes.ToArray(); diff --git a/src/LexKey/PublicAPI.Unshipped.txt b/src/LexKey/PublicAPI.Unshipped.txt index 2946079..99a4305 100644 --- a/src/LexKey/PublicAPI.Unshipped.txt +++ b/src/LexKey/PublicAPI.Unshipped.txt @@ -1,4 +1,5 @@ #nullable enable +Cntryl.Keys.LexKey.AsMemory() -> System.ReadOnlyMemory Cntryl.Keys.Encoder Cntryl.Keys.Encoder.AsSpan() -> System.ReadOnlySpan Cntryl.Keys.Encoder.Clear() -> void diff --git a/test/LexKey.Tests/RangeAndValueTests.cs b/test/LexKey.Tests/RangeAndValueTests.cs index 6a23977..cdcb177 100644 --- a/test/LexKey.Tests/RangeAndValueTests.cs +++ b/test/LexKey.Tests/RangeAndValueTests.cs @@ -75,6 +75,21 @@ public void ShouldCloneInputsAndOutputsGivenImmutableKey() Assert.Equal(new byte[] { 1, 2, 3 }, key.ToArray()); } + [Fact] + public void ShouldExposeReadOnlyMemoryWithoutCopyGivenImmutableKey() + { + // Arrange + var key = LexKey.FromBytes([1, 2, 3]); + + // Act + var first = key.AsMemory(); + var second = key.AsMemory(); + + // Assert + Assert.True(first.Equals(second)); + Assert.Equal(new byte[] { 1, 2, 3 }, first.ToArray()); + } + [Fact] public void ShouldCloneCallerOwnedBytesGivenCompositePart() {