diff --git a/Directory.Packages.props b/Directory.Packages.props
index 546d307fd..899829472 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -19,6 +19,7 @@
+
diff --git a/storage/src/Storage/Internal/CheckSchemaVersionResult.cs b/storage/src/Storage/CheckSchemaVersionResult.cs
similarity index 52%
rename from storage/src/Storage/Internal/CheckSchemaVersionResult.cs
rename to storage/src/Storage/CheckSchemaVersionResult.cs
index d8963958c..254d8075d 100644
--- a/storage/src/Storage/Internal/CheckSchemaVersionResult.cs
+++ b/storage/src/Storage/CheckSchemaVersionResult.cs
@@ -1,8 +1,11 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.
-namespace Duende.Storage.Internal;
+namespace Duende.Storage;
+///
+/// Represents the result of a database schema version check.
+///
public sealed class CheckSchemaVersionResult
{
internal CheckSchemaVersionResult(uint currentVersion, uint requiredVersion)
@@ -11,9 +14,18 @@ public sealed class CheckSchemaVersionResult
RequiredVersion = requiredVersion;
}
+ ///
+ /// Gets the current schema version in the database.
+ ///
public uint CurrentVersion { get; }
+ ///
+ /// Gets the schema version required by the application.
+ ///
public uint RequiredVersion { get; }
+ ///
+ /// Gets a value indicating whether the current schema version is compatible with the required version.
+ ///
public bool IsCompatible => CurrentVersion == RequiredVersion;
}
diff --git a/storage/src/Storage/DatabaseSchemaVersion.cs b/storage/src/Storage/DatabaseSchemaVersion.cs
index 42776f7fc..863388bea 100644
--- a/storage/src/Storage/DatabaseSchemaVersion.cs
+++ b/storage/src/Storage/DatabaseSchemaVersion.cs
@@ -3,15 +3,28 @@
namespace Duende.Storage;
+///
+/// Represents a version number for the database schema.
+///
public sealed record DatabaseSchemaVersion
{
+ ///
+ /// Gets the numeric version value.
+ ///
public int Value { get; }
+ ///
+ /// Initializes a new instance of with the specified version number.
+ ///
+ /// The version number. Must be non-negative.
public DatabaseSchemaVersion(int value)
{
ArgumentOutOfRangeException.ThrowIfNegative(value);
Value = value;
}
+ ///
+ /// A schema version representing no schema (version zero).
+ ///
public static readonly DatabaseSchemaVersion Zero = new(0);
}
diff --git a/storage/src/Storage/EntityAttributeValue/AttributeDefinition.cs b/storage/src/Storage/EntityAttributeValue/AttributeDefinition.cs
index c1861346a..40a41e4a8 100644
--- a/storage/src/Storage/EntityAttributeValue/AttributeDefinition.cs
+++ b/storage/src/Storage/EntityAttributeValue/AttributeDefinition.cs
@@ -3,12 +3,35 @@
namespace Duende.Storage.EntityAttributeValue;
+///
+/// Defines an attribute's metadata including its code, type, description, uniqueness, tags, grouping, and ordering.
+///
public sealed record AttributeDefinition
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
public AttributeDefinition()
{
}
+ ///
+ /// Reconstitutes an from persisted data without re-running constructor validation.
+ ///
+ /// The attribute code.
+ /// The attribute type descriptor.
+ /// The optional description.
+ /// The optional display name.
+ /// Whether the attribute value must be unique.
+ /// Whether the attribute can be used in queries.
+ /// Whether the attribute is required.
+ /// Tags associated with the attribute.
+ /// The optional group code.
+ /// The sort order.
+ /// A new instance.
+ ///
+ /// This method is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+ ///
public static AttributeDefinition Load(
AttributeCode code,
AttributeType attributeType,
@@ -34,6 +57,9 @@ public sealed record AttributeDefinition
Order = order
};
+ ///
+ /// The programmatic identifier for this attribute.
+ ///
public required AttributeCode Code { get; init; }
///
@@ -51,7 +77,14 @@ public sealed record AttributeDefinition
: throw new InvalidOperationException(
$"Attribute '{Code}' has type '{AttributeType.GetType().Name}', not a scalar type. Use AttributeType instead.");
+ ///
+ /// An optional human-readable description of the attribute.
+ ///
public AttributeDescription? Description { get; init; }
+
+ ///
+ /// An optional human-readable display name for the attribute.
+ ///
public AttributeDisplayName? DisplayName { get; init; }
///
@@ -66,8 +99,19 @@ public sealed record AttributeDefinition
///
public bool IsQueryable { get; init; } = true;
+ ///
+ /// Indicates whether a value for this attribute is required.
+ ///
public bool IsRequired { get; init; }
+
+ ///
+ /// Indicates whether values of this attribute must be unique across entities.
+ ///
public bool IsUnique { get; init; }
+
+ ///
+ /// Tags associated with this attribute for categorization or filtering.
+ ///
public IReadOnlyCollection Tags { get; init; } = [];
///
@@ -81,6 +125,10 @@ public sealed record AttributeDefinition
///
public int Order { get; init; }
+ ///
+ /// Implicitly converts an to its .
+ ///
+ /// The attribute definition.
#pragma warning disable CA2225 // Operator overloads have named alternates
public static implicit operator AttributeCode(AttributeDefinition definition)
{
diff --git a/storage/src/Storage/EntityAttributeValue/AttributeGroup.cs b/storage/src/Storage/EntityAttributeValue/AttributeGroup.cs
index e9ced8260..88471c696 100644
--- a/storage/src/Storage/EntityAttributeValue/AttributeGroup.cs
+++ b/storage/src/Storage/EntityAttributeValue/AttributeGroup.cs
@@ -8,6 +8,13 @@ namespace Duende.Storage.EntityAttributeValue;
///
public sealed record AttributeGroup
{
+ ///
+ /// Creates a new attribute group with the specified metadata.
+ ///
+ /// The unique code identifying this group.
+ /// An optional human-readable display name.
+ /// An optional description of the group.
+ /// The sort order for display purposes.
public AttributeGroup(
AttributeGroupCode Code,
AttributeDisplayName? DisplayName,
@@ -20,8 +27,23 @@ public sealed record AttributeGroup
this.Order = Order;
}
+ ///
+ /// The unique code identifying this group.
+ ///
public AttributeGroupCode Code { get; init; }
+
+ ///
+ /// An optional human-readable display name for the group.
+ ///
public AttributeDisplayName? DisplayName { get; init; }
+
+ ///
+ /// An optional description of the group.
+ ///
public AttributeDescription? Description { get; init; }
+
+ ///
+ /// The sort order for display purposes.
+ ///
public int Order { get; init; }
}
diff --git a/storage/src/Storage/EntityAttributeValue/AttributeValue.cs b/storage/src/Storage/EntityAttributeValue/AttributeValue.cs
index 6559f8c95..646d44a44 100644
--- a/storage/src/Storage/EntityAttributeValue/AttributeValue.cs
+++ b/storage/src/Storage/EntityAttributeValue/AttributeValue.cs
@@ -6,15 +6,32 @@ using System.Diagnostics.CodeAnalysis;
namespace Duende.Storage.EntityAttributeValue;
+///
+/// Represents a read-only, validated attribute value paired with its attribute code.
+/// Instances can only be created via an (which validates against a schema)
+/// or by reconstituting from previously persisted data using .
+///
#pragma warning disable CA1711 // Identifiers should not have incorrect suffix
public abstract record AttributeValue
{
private protected AttributeValue(AttributeCode code) => Code = code;
+ ///
+ /// The attribute code identifying which attribute this value belongs to.
+ ///
public AttributeCode Code { get; }
+ ///
+ /// Gets the value as an untyped object.
+ ///
public abstract object UntypedValue { get; }
+ ///
+ /// Attempts to retrieve the value as the specified type.
+ ///
+ /// The expected value type.
+ /// The typed value if successful.
+ /// true if the value is of the specified type; otherwise, false.
public bool TryGetValue([MaybeNullWhen(false)] out T value)
{
if (this is AttributeValue typed)
@@ -27,11 +44,25 @@ public abstract record AttributeValue
return false;
}
+ ///
+ /// Returns the string representation of the value.
+ ///
public override string ToString() => UntypedValue.ToString()!;
+ ///
+ /// Reconstitutes a typed attribute value from persisted data.
+ ///
+ /// The type of the value.
+ /// The attribute code.
+ /// The attribute value.
+ /// A new typed instance.
public static AttributeValue Load(AttributeCode code, T value) => new(code, value);
}
+///
+/// Represents a strongly-typed attribute value paired with its attribute code.
+///
+/// The type of the attribute value.
public sealed record AttributeValue : AttributeValue
{
internal AttributeValue(AttributeCode code, T value) : base(code) =>
@@ -43,8 +74,14 @@ public sealed record AttributeValue : AttributeValue
_ => value
};
+ ///
+ /// Gets the strongly-typed value.
+ ///
public T TypedValue { get; }
+ ///
+ /// Gets the value as an untyped object.
+ ///
public override object UntypedValue => TypedValue!;
internal static AttributeValue Load(AttributeCode code, T value) => new(code, value);
diff --git a/storage/src/Storage/EntityAttributeValue/AttributeValueCollection.cs b/storage/src/Storage/EntityAttributeValue/AttributeValueCollection.cs
index d5d9af174..f4a22dec6 100644
--- a/storage/src/Storage/EntityAttributeValue/AttributeValueCollection.cs
+++ b/storage/src/Storage/EntityAttributeValue/AttributeValueCollection.cs
@@ -7,17 +7,29 @@ using Duende.Storage.EntityAttributeValue.Internal;
namespace Duende.Storage.EntityAttributeValue;
+///
+/// A mutable collection of attribute values validated against an attribute schema.
+///
public sealed class AttributeValueCollection : IEnumerable
{
private readonly Dictionary _dict = [];
private readonly IReadOnlyAttributeSchema? _schema;
+ ///
+ /// Creates an empty collection backed by the specified schema.
+ ///
+ /// The attribute schema used for validation.
public AttributeValueCollection(IReadOnlyAttributeSchema schema)
{
ArgumentNullException.ThrowIfNull(schema);
_schema = schema;
}
+ ///
+ /// Creates a collection pre-populated with the specified attributes, validated against the schema.
+ ///
+ /// The attribute schema used for validation.
+ /// The initial attribute values to populate.
public AttributeValueCollection(IReadOnlyAttributeSchema schema, IEnumerable attributes)
{
ArgumentNullException.ThrowIfNull(schema);
@@ -46,6 +58,10 @@ public sealed class AttributeValueCollection : IEnumerable
}
}
+ ///
+ /// Sets an attribute value in the collection, replacing any existing value for the same code.
+ ///
+ /// The attribute value to set.
public void Set(AttributeValue attribute)
{
ArgumentNullException.ThrowIfNull(attribute);
@@ -68,24 +84,45 @@ public sealed class AttributeValueCollection : IEnumerable
_dict[attribute.Code] = attribute;
}
+ /// Sets a boolean attribute value.
+ /// The attribute code.
+ /// The boolean value.
public void Set(AttributeCode code, bool value) =>
SetTyped(code, value, ScalarDataType.Boolean);
+ /// Sets an integer attribute value.
+ /// The attribute code.
+ /// The integer value.
public void Set(AttributeCode code, int value) =>
SetTyped(code, value, ScalarDataType.Integer);
+ /// Sets a decimal attribute value.
+ /// The attribute code.
+ /// The decimal value.
public void Set(AttributeCode code, decimal value) =>
SetTyped(code, value, ScalarDataType.Decimal);
+ /// Sets a string attribute value.
+ /// The attribute code.
+ /// The string value.
public void Set(AttributeCode code, string value) =>
SetTyped(code, value, ScalarDataType.String);
+ /// Sets a date attribute value.
+ /// The attribute code.
+ /// The date value.
public void Set(AttributeCode code, DateOnly value) =>
SetTyped(code, value, ScalarDataType.Date);
+ /// Sets a date-time attribute value.
+ /// The attribute code.
+ /// The date-time value.
public void Set(AttributeCode code, DateTimeOffset value) =>
SetTyped(code, value, ScalarDataType.DateTime);
+ /// Sets a complex (dictionary) attribute value.
+ /// The attribute code.
+ /// The complex value as a dictionary of properties.
public void Set(AttributeCode code, IReadOnlyDictionary value)
{
if (_schema != null)
@@ -96,6 +133,9 @@ public sealed class AttributeValueCollection : IEnumerable
_dict[code] = new AttributeValue>(code, value);
}
+ /// Sets a list attribute value.
+ /// The attribute code.
+ /// The list value.
public void Set(AttributeCode code, IReadOnlyList
public sealed record ComplexAttributeType : AttributeType
{
+ ///
+ /// Creates a complex attribute type with the specified named properties.
+ ///
+ /// The property definitions keyed by attribute code.
public ComplexAttributeType(IReadOnlyDictionary Properties)
{
ArgumentNullException.ThrowIfNull(Properties, nameof(Properties));
@@ -72,6 +76,11 @@ public sealed record ComplexAttributeType : AttributeType
return false;
}
+ ///
+ /// Determines whether this complex type is equal to another.
+ ///
+ /// The other complex attribute type.
+ /// true if the types are equal; otherwise, false.
public bool Equals(ComplexAttributeType? other)
{
if (other is null)
@@ -95,6 +104,10 @@ public sealed record ComplexAttributeType : AttributeType
return true;
}
+ ///
+ /// Returns the hash code for this complex attribute type.
+ ///
+ /// The hash code.
public override int GetHashCode()
{
var hash = new HashCode();
diff --git a/storage/src/Storage/EntityAttributeValue/IReadOnlyAttributeSchema.cs b/storage/src/Storage/EntityAttributeValue/IReadOnlyAttributeSchema.cs
index f5a6498a8..ae8c6f085 100644
--- a/storage/src/Storage/EntityAttributeValue/IReadOnlyAttributeSchema.cs
+++ b/storage/src/Storage/EntityAttributeValue/IReadOnlyAttributeSchema.cs
@@ -3,8 +3,14 @@
namespace Duende.Storage.EntityAttributeValue;
+///
+/// Provides a read-only view of attribute definitions and groups in a schema.
+///
public interface IReadOnlyAttributeSchema
{
+ ///
+ /// The attribute definitions in this schema, keyed by attribute code.
+ ///
IReadOnlyDictionary AttributeDefinitions { get; }
///
diff --git a/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeDefinitionDso.cs b/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeDefinitionDso.cs
index 28c1b3bcd..21663f5c6 100644
--- a/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeDefinitionDso.cs
+++ b/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeDefinitionDso.cs
@@ -3,8 +3,27 @@
namespace Duende.Storage.EntityAttributeValue.Internal.Storage;
-public static class AttributeDefinitionDso
+///
+/// Provides the persisted data storage object representation of an attribute definition.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
+internal static class AttributeDefinitionDso
{
+ ///
+ /// Version 1 of the attribute definition data storage object.
+ ///
+ /// The attribute code.
+ /// The attribute type descriptor.
+ /// The description, or null.
+ /// Whether the attribute value must be unique.
+ /// The tags associated with the attribute.
+ /// The group code, or null if ungrouped.
+ /// The sort order.
+ /// The display name, or null.
+ /// Whether the attribute can be used in queries.
+ /// Whether the attribute is required.
public sealed record V1(
string Code,
AttributeTypeDso Type,
diff --git a/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeGroupDso.cs b/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeGroupDso.cs
index fe9177a0a..5663d420f 100644
--- a/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeGroupDso.cs
+++ b/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeGroupDso.cs
@@ -3,7 +3,20 @@
namespace Duende.Storage.EntityAttributeValue.Internal.Storage;
-public static class AttributeGroupDso
+///
+/// Provides the persisted data storage object representation of an attribute group.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
+internal static class AttributeGroupDso
{
+ ///
+ /// Version 1 of the attribute group data storage object.
+ ///
+ /// The group code.
+ /// The display name, or null.
+ /// The description, or null.
+ /// The sort order.
public sealed record V1(string Code, string? DisplayName, string? Description, int Order);
}
diff --git a/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeSchemaDso.cs b/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeSchemaDso.cs
index 1e2de9d64..e856245af 100644
--- a/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeSchemaDso.cs
+++ b/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeSchemaDso.cs
@@ -5,12 +5,25 @@ using Duende.Storage.Internal;
namespace Duende.Storage.EntityAttributeValue.Internal.Storage;
-public static class AttributeSchemaDso
+///
+/// Provides the persisted data storage object representation of an attribute schema.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
+internal static class AttributeSchemaDso
{
+ /// Gets the entity type for the attribute schema DSO.
public static readonly EntityType EntityType = new(1501, "UserProfileSchemaDso");
+ ///
+ /// Version 1 of the attribute schema data storage object.
+ ///
+ /// The attribute definitions.
+ /// The attribute groups.
public sealed record V1(ICollection AttributeDefinitions, ICollection Groups) : IDataStorageObject
{
+ /// Gets the data storage object version descriptor.
public static DataStorageObjectVersion DsoVersion { get; } = new(EntityType, 1);
}
}
diff --git a/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeTypeDso.cs b/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeTypeDso.cs
index d2bc22986..33612bcc7 100644
--- a/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeTypeDso.cs
+++ b/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeTypeDso.cs
@@ -4,11 +4,14 @@
namespace Duende.Storage.EntityAttributeValue.Internal.Storage;
///
-/// Persisted representation of an .
-/// EnumValues and ConstrainedValues are reserved for future enum/constrained-string
-/// attribute types and are currently always null.
+/// Persisted representation of an .
+/// EnumValues and ConstrainedValues are reserved for future enum/constrained-string
+/// attribute types and are currently always null.
///
-public sealed record AttributeTypeDso(
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
+internal sealed record AttributeTypeDso(
string Kind,
string? ScalarDataType,
IReadOnlyList? EnumValues,
@@ -17,9 +20,12 @@ public sealed record AttributeTypeDso(
AttributeTypeDso? ElementType);
///
-/// Persisted representation of a sub-property within a complex attribute type.
+/// Persisted representation of a sub-property within a complex attribute type.
///
-public sealed record ComplexPropertyDso(
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
+internal sealed record ComplexPropertyDso(
AttributeTypeDso Type,
string? DisplayName,
string? Description);
diff --git a/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeTypeResolver.cs b/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeTypeResolver.cs
index 0daa39a38..62b5bf60e 100644
--- a/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeTypeResolver.cs
+++ b/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeTypeResolver.cs
@@ -8,12 +8,15 @@ using Duende.Storage.Internal.Querying.Fields;
namespace Duende.Storage.EntityAttributeValue.Internal.Storage;
///
-/// Resolves SCIM attribute paths to Faro Field types based on the dynamic user schema.
-/// Unlike other implementations which map fixed SCIM User schema attributes,
+/// Resolves query attribute paths to Faro Field types based on the dynamic user schema.
+/// Unlike other implementations which map fixed schema attributes,
/// this resolver dynamically maps user-defined schema attributes to their Faro field types.
/// Supports dotted paths (e.g., address.city, phones.type) for complex and list types.
///
-public sealed class AttributeTypeResolver : IScimAttributeTypeResolver
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
+internal sealed class AttributeTypeResolver : IQueryAttributeTypeResolver
{
private readonly IReadOnlyDictionary _attributeDefinitions;
diff --git a/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeValueDskV1.cs b/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeValueDskV1.cs
index 3a79156e0..ddc611f31 100644
--- a/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeValueDskV1.cs
+++ b/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeValueDskV1.cs
@@ -6,7 +6,13 @@ using Duende.Storage.Internal;
namespace Duende.Storage.EntityAttributeValue.Internal.Storage;
-public sealed record AttributeValueDskV1 : IDataStorageKey
+///
+/// Represents a version 1 data storage key for an attribute value, keyed by attribute code and value.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
+internal sealed record AttributeValueDskV1 : IDataStorageKey
{
private AttributeValueDskV1(string name, string value)
{
@@ -14,16 +20,30 @@ public sealed record AttributeValueDskV1 : IDataStorageKey
Value = value;
}
+ /// Gets the data storage key version descriptor.
public static DataStorageKeyVersion DskVersion { get; } =
new(new DataStorageKeyType(1u, "Attribute"), 1);
+ /// Gets the attribute name (code).
public string Name { get; }
+ /// Gets the attribute value as an invariant string.
public string Value { get; }
+ ///
+ /// Creates a key from an .
+ ///
+ /// The attribute value.
+ /// A new data storage key.
public static AttributeValueDskV1 Create(AttributeValue attribute) =>
new(attribute.Code.Value, ToInvariantString(attribute.UntypedValue));
+ ///
+ /// Creates a key from an attribute code and value.
+ ///
+ /// The attribute code.
+ /// The attribute value.
+ /// A new data storage key.
public static AttributeValueDskV1 Create(AttributeCode code, object value) =>
new(code.Value, ToInvariantString(value));
diff --git a/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeValueDso.cs b/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeValueDso.cs
index 9691d6582..b83022021 100644
--- a/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeValueDso.cs
+++ b/storage/src/Storage/EntityAttributeValue/Internal/Storage/AttributeValueDso.cs
@@ -3,7 +3,18 @@
namespace Duende.Storage.EntityAttributeValue.Internal.Storage;
-public static class AttributeValueDso
+///
+/// Provides the persisted data storage object representation of an attribute value.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
+internal static class AttributeValueDso
{
+ ///
+ /// Version 1 of the attribute value data storage object.
+ ///
+ /// The attribute name.
+ /// The attribute value, or null.
public sealed record V1(string Name, object? Value);
}
diff --git a/storage/src/Storage/EntityAttributeValue/Internal/Storage/EnumValueDso.cs b/storage/src/Storage/EntityAttributeValue/Internal/Storage/EnumValueDso.cs
index ec08404ee..994396b88 100644
--- a/storage/src/Storage/EntityAttributeValue/Internal/Storage/EnumValueDso.cs
+++ b/storage/src/Storage/EntityAttributeValue/Internal/Storage/EnumValueDso.cs
@@ -3,4 +3,12 @@
namespace Duende.Storage.EntityAttributeValue.Internal.Storage;
-public sealed record EnumValueDso(string Key, string DisplayName);
+///
+/// Persisted representation of an enumeration value within an attribute type.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
+/// The enum value key.
+/// The human-readable display name.
+internal sealed record EnumValueDso(string Key, string DisplayName);
diff --git a/storage/src/Storage/EntityAttributeValue/ListAttributeType.cs b/storage/src/Storage/EntityAttributeValue/ListAttributeType.cs
index 4497340c8..79be9cb3f 100644
--- a/storage/src/Storage/EntityAttributeValue/ListAttributeType.cs
+++ b/storage/src/Storage/EntityAttributeValue/ListAttributeType.cs
@@ -9,6 +9,10 @@ namespace Duende.Storage.EntityAttributeValue;
///
public sealed record ListAttributeType : AttributeType
{
+ ///
+ /// Creates a list attribute type with the specified element type.
+ ///
+ /// The type of elements in the list.
public ListAttributeType(AttributeType ElementType)
{
ArgumentNullException.ThrowIfNull(ElementType);
@@ -19,10 +23,22 @@ public sealed record ListAttributeType : AttributeType
ValidateNesting();
}
+ ///
+ /// Gets the type of elements in this list.
+ ///
public AttributeType ElementType { get; }
+ ///
+ /// Determines whether this list type is equal to another.
+ ///
+ /// The other list attribute type.
+ /// true if the types are equal; otherwise, false.
public bool Equals(ListAttributeType? other) =>
other is not null && ElementType.Equals(other.ElementType);
+ ///
+ /// Returns the hash code for this list attribute type.
+ ///
+ /// The hash code.
public override int GetHashCode() => HashCode.Combine(ElementType);
}
diff --git a/storage/src/Storage/EntityAttributeValue/ScalarAttributeType.cs b/storage/src/Storage/EntityAttributeValue/ScalarAttributeType.cs
index dc278c4bb..bb12a3a7c 100644
--- a/storage/src/Storage/EntityAttributeValue/ScalarAttributeType.cs
+++ b/storage/src/Storage/EntityAttributeValue/ScalarAttributeType.cs
@@ -8,6 +8,10 @@ namespace Duende.Storage.EntityAttributeValue;
///
public sealed record ScalarAttributeType : AttributeType
{
+ ///
+ /// Creates a scalar attribute type with the specified data type.
+ ///
+ /// The scalar data type.
public ScalarAttributeType(ScalarDataType DataType)
{
if (!Enum.IsDefined(DataType))
@@ -18,5 +22,8 @@ public sealed record ScalarAttributeType : AttributeType
this.DataType = DataType;
}
+ ///
+ /// Gets the scalar data type for this attribute.
+ ///
public ScalarDataType DataType { get; init; }
}
diff --git a/storage/src/Storage/EntityAttributeValue/ScalarDataType.cs b/storage/src/Storage/EntityAttributeValue/ScalarDataType.cs
index d13411e75..6c5f47058 100644
--- a/storage/src/Storage/EntityAttributeValue/ScalarDataType.cs
+++ b/storage/src/Storage/EntityAttributeValue/ScalarDataType.cs
@@ -9,10 +9,21 @@ namespace Duende.Storage.EntityAttributeValue;
#pragma warning disable CA1720 // Identifiers should not contain type names
public enum ScalarDataType
{
+ /// A boolean (true/false) value.
Boolean,
+
+ /// A date-only value without time component.
Date,
+
+ /// A date and time value with time zone offset.
DateTime,
+
+ /// A decimal numeric value.
Decimal,
+
+ /// A 32-bit integer value.
Integer,
+
+ /// A text string value.
String,
}
diff --git a/storage/src/Storage/EntityAttributeValue/ValidatedAttributeValueCollection.cs b/storage/src/Storage/EntityAttributeValue/ValidatedAttributeValueCollection.cs
index cc09dce16..6ef7b3975 100644
--- a/storage/src/Storage/EntityAttributeValue/ValidatedAttributeValueCollection.cs
+++ b/storage/src/Storage/EntityAttributeValue/ValidatedAttributeValueCollection.cs
@@ -7,10 +7,16 @@ using System.Diagnostics.CodeAnalysis;
namespace Duende.Storage.EntityAttributeValue;
+///
+/// An immutable, validated collection of attribute values that has passed schema validation.
+///
public sealed class ValidatedAttributeValueCollection : IEnumerable
{
private readonly FrozenDictionary _dict;
+ ///
+ /// Gets an empty validated collection with no attribute values.
+ ///
public static ValidatedAttributeValueCollection Empty { get; } =
new([], UuidV7.Load(Guid.Empty), 0);
@@ -24,17 +30,40 @@ public sealed class ValidatedAttributeValueCollection : IEnumerable
+ /// Gets the number of attribute values in the collection.
+ ///
public int Count => _dict.Count;
+ ///
+ /// Determines whether an attribute with the specified code exists in the collection.
+ ///
+ /// The attribute code to check.
+ /// true if the attribute exists; otherwise, false.
public bool Contains(AttributeCode code) => _dict.ContainsKey(code);
+ ///
+ /// Attempts to retrieve an attribute value by code.
+ ///
+ /// The attribute code to look up.
+ /// The attribute value if found.
+ /// true if the attribute was found; otherwise, false.
public bool TryGet(AttributeCode code, [MaybeNullWhen(false)] out AttributeValue attribute) =>
_dict.TryGetValue(code, out attribute);
+ ///
+ /// Gets the attribute value with the specified code.
+ ///
+ /// The attribute code.
+ /// The attribute value.
#pragma warning disable CA1043 // Use integral or string argument for indexers
public AttributeValue this[AttributeCode code] => _dict[code];
#pragma warning restore CA1043
+ ///
+ /// Returns an enumerator that iterates through the attribute values.
+ ///
+ /// An enumerator for the collection.
public IEnumerator GetEnumerator() => ((IEnumerable)_dict.Values).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
diff --git a/storage/src/Storage/IDatabaseSchema.cs b/storage/src/Storage/IDatabaseSchema.cs
index f623ce59f..ccbae0659 100644
--- a/storage/src/Storage/IDatabaseSchema.cs
+++ b/storage/src/Storage/IDatabaseSchema.cs
@@ -1,10 +1,11 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.
-using Duende.Storage.Internal;
-
namespace Duende.Storage;
+///
+/// Provides database schema management including migrations and verification.
+///
public interface IDatabaseSchema
{
///
diff --git a/storage/src/Storage/IStorageBuilder.cs b/storage/src/Storage/IStorageBuilder.cs
index 788cca39b..14fff4c49 100644
--- a/storage/src/Storage/IStorageBuilder.cs
+++ b/storage/src/Storage/IStorageBuilder.cs
@@ -5,7 +5,13 @@ using Microsoft.Extensions.DependencyInjection;
namespace Duende.Storage;
+///
+/// A builder interface for configuring storage services.
+///
public interface IStorageBuilder
{
+ ///
+ /// Gets the service collection used to register storage services.
+ ///
public IServiceCollection Services { get; }
}
diff --git a/storage/src/Storage/Internal/Builder/DsoRegistration.cs b/storage/src/Storage/Internal/Builder/DsoRegistration.cs
index 25c59811e..0977a2c87 100644
--- a/storage/src/Storage/Internal/Builder/DsoRegistration.cs
+++ b/storage/src/Storage/Internal/Builder/DsoRegistration.cs
@@ -3,8 +3,21 @@
namespace Duende.Storage.Internal.Builder;
-public sealed class DsoRegistration(Type dsoType, DataStorageObjectVersion dsoVersion)
+///
+/// Represents a DSO type registration binding a CLR type to its DSO version.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
+internal sealed class DsoRegistration(Type dsoType, DataStorageObjectVersion dsoVersion)
{
+ ///
+ /// Gets the CLR type of the DSO.
+ ///
public Type DsoType { get; } = dsoType;
+
+ ///
+ /// Gets the DSO version.
+ ///
public DataStorageObjectVersion DsoVersion { get; } = dsoVersion;
}
diff --git a/storage/src/Storage/Internal/Builder/DsoRegistrationServiceCollectionExtensions.cs b/storage/src/Storage/Internal/Builder/DsoRegistrationServiceCollectionExtensions.cs
index 94416a125..8d746ee06 100644
--- a/storage/src/Storage/Internal/Builder/DsoRegistrationServiceCollectionExtensions.cs
+++ b/storage/src/Storage/Internal/Builder/DsoRegistrationServiceCollectionExtensions.cs
@@ -7,10 +7,20 @@ using Microsoft.Extensions.DependencyInjection;
namespace Duende.Storage.Internal.Builder;
#pragma warning restore IDE0130
+///
+/// Extension methods for registering DSO types in the service collection.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public static class DsoRegistrationServiceCollectionExtensions
{
extension(IServiceCollection services)
{
+ ///
+ /// Registers a DSO type for deserialization support.
+ ///
+ /// The DSO type to register.
public void AddDsoRegistration() where TDso : IDataStorageObject
{
var dsoRegistration = new DsoRegistration(typeof(TDso), TDso.DsoVersion);
diff --git a/storage/src/Storage/Internal/Builder/GetRegisteredTypeForDso.cs b/storage/src/Storage/Internal/Builder/GetRegisteredTypeForDso.cs
index f788b9971..8c42a749e 100644
--- a/storage/src/Storage/Internal/Builder/GetRegisteredTypeForDso.cs
+++ b/storage/src/Storage/Internal/Builder/GetRegisteredTypeForDso.cs
@@ -3,4 +3,12 @@
namespace Duende.Storage.Internal.Builder;
+///
+/// Delegate that resolves the registered CLR type for a given DSO version.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
+/// The DSO version to look up.
+/// The registered CLR type.
public delegate Type GetRegisteredTypeForDso(DataStorageObjectVersion version);
diff --git a/storage/src/Storage/Internal/DataStorageKey.cs b/storage/src/Storage/Internal/DataStorageKey.cs
index a77812970..7cbd499f6 100644
--- a/storage/src/Storage/Internal/DataStorageKey.cs
+++ b/storage/src/Storage/Internal/DataStorageKey.cs
@@ -5,6 +5,12 @@ using System.Text.Json;
namespace Duende.Storage.Internal;
+///
+/// Represents a key used for alternate lookups in the data store.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed class DataStorageKey
{
private DataStorageKey(DataStorageKeyVersion version, Guid value, string? keyJsonValue)
@@ -14,12 +20,27 @@ public sealed class DataStorageKey
KeyJsonValue = keyJsonValue;
}
+ ///
+ /// Gets the version of the data storage key.
+ ///
public DataStorageKeyVersion DskVersion { get; private set; }
+ ///
+ /// Gets the GUID value of the key.
+ ///
public Guid Value { get; private set; }
+ ///
+ /// Gets the JSON representation of the key, or null for GUID-based keys.
+ ///
public string? KeyJsonValue { get; private set; }
+ ///
+ /// Creates a from the specified key instance.
+ ///
+ /// The type of the data storage key.
+ /// The data storage key instance.
+ /// A new .
public static DataStorageKey Create(T dsk) where T : IDataStorageKey
{
if (dsk is IGuidDataStorageKey guidDsk)
diff --git a/storage/src/Storage/Internal/DataStorageKeyType.cs b/storage/src/Storage/Internal/DataStorageKeyType.cs
index 9080c57b2..2cee2636a 100644
--- a/storage/src/Storage/Internal/DataStorageKeyType.cs
+++ b/storage/src/Storage/Internal/DataStorageKeyType.cs
@@ -6,19 +6,39 @@ using System.Globalization;
namespace Duende.Storage.Internal;
///
-/// The type of DSK that's being stored.
+/// The type of DSK that's being stored.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
/// A number representation for the DSK type.
-/// The name of the DSK type. This name is only used for display purposes and should never change.
+/// The name of the DSK type. This name is only used for display purposes and should never change.
public readonly record struct DataStorageKeyType(uint Id, string Name)
{
+ ///
+ /// Obsolete parameterless constructor. Do not use.
+ ///
[Obsolete("Don't use this constructor")]
public DataStorageKeyType() : this(0!, null!) => throw new InvalidOperationException("Cannot instantiate DSKType without parameters");
+ ///
+ /// Builds a from an enum value.
+ ///
+ /// The enum value to convert.
+ /// A with the numeric and string representation of the enum.
public static DataStorageKeyType BuildFrom(Enum @enum) =>
new((uint)Convert.ToInt32(@enum, CultureInfo.InvariantCulture), @enum.ToString());
+ ///
+ /// Creates a from an enum value.
+ ///
+ /// The enum value to convert.
+ /// A .
public static DataStorageKeyType FromEnum(Enum value) => BuildFrom(value);
+ ///
+ /// Implicitly converts an enum value to a .
+ ///
+ /// The enum value to convert.
public static implicit operator DataStorageKeyType(Enum value) => BuildFrom(value);
}
diff --git a/storage/src/Storage/Internal/DataStorageKeyVersion.cs b/storage/src/Storage/Internal/DataStorageKeyVersion.cs
index 4d992b4e7..b4a734ac4 100644
--- a/storage/src/Storage/Internal/DataStorageKeyVersion.cs
+++ b/storage/src/Storage/Internal/DataStorageKeyVersion.cs
@@ -3,8 +3,17 @@
namespace Duende.Storage.Internal;
+///
+/// Represents a versioned data storage key type, combining the key type with its schema version.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
+/// The type of the data storage key.
+/// The schema version of the key.
public sealed record DataStorageKeyVersion(DataStorageKeyType KeyType, uint SchemaVersion)
{
+ ///
public override string ToString() => $"{KeyType.Name}({KeyType.Id}) v{SchemaVersion}";
}
diff --git a/storage/src/Storage/Internal/DataStorageObjectVersion.cs b/storage/src/Storage/Internal/DataStorageObjectVersion.cs
index 2d60e7ce2..fbd94afd0 100644
--- a/storage/src/Storage/Internal/DataStorageObjectVersion.cs
+++ b/storage/src/Storage/Internal/DataStorageObjectVersion.cs
@@ -6,6 +6,9 @@ namespace Duende.Storage.Internal;
///
/// Represents a versioned DSO type.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record DataStorageObjectVersion(EntityType EntityType, uint SchemaVersion)
{
public override string ToString() => $"{EntityType.Name}({EntityType.Id}) v{SchemaVersion}";
diff --git a/storage/src/Storage/Internal/DeterministicGuidGenerator.cs b/storage/src/Storage/Internal/DeterministicGuidGenerator.cs
index 498d64262..2cb175b99 100644
--- a/storage/src/Storage/Internal/DeterministicGuidGenerator.cs
+++ b/storage/src/Storage/Internal/DeterministicGuidGenerator.cs
@@ -6,8 +6,20 @@ using System.Text;
namespace Duende.Storage.Internal;
+///
+/// Generates deterministic GUIDs from string inputs using MD5 hashing.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public static class DeterministicGuidGenerator
{
+ ///
+ /// Creates a deterministic GUID from the specified name.
+ ///
+ /// The name to generate a GUID from.
+ /// A deterministic GUID derived from the input name.
+ /// Thrown when is null or empty.
public static Guid Create(string name)
{
if (string.IsNullOrEmpty(name))
diff --git a/storage/src/Storage/Internal/EntityType.cs b/storage/src/Storage/Internal/EntityType.cs
index 4444639f0..4da4076c8 100644
--- a/storage/src/Storage/Internal/EntityType.cs
+++ b/storage/src/Storage/Internal/EntityType.cs
@@ -7,10 +7,16 @@ namespace Duende.Storage.Internal;
/// The type of document that's being stored.
/// Each library defines its own entity types as static fields on the containing DSO class.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
/// A number representation for the DSO type. Must be unique across all entity types.
/// The name of the DSO type. Used for analytics and display purposes. Should never change once entities exist.
public readonly record struct EntityType(uint Id, string Name)
{
+ ///
+ /// Obsolete parameterless constructor. Do not use.
+ ///
[Obsolete("Don't use this constructor")]
public EntityType() : this(0, null!) => throw new InvalidOperationException("Cannot instantiate EntityType without parameters");
}
diff --git a/storage/src/Storage/Internal/Expiration.cs b/storage/src/Storage/Internal/Expiration.cs
index 4ad941f8d..6f5b664e5 100644
--- a/storage/src/Storage/Internal/Expiration.cs
+++ b/storage/src/Storage/Internal/Expiration.cs
@@ -9,6 +9,9 @@ namespace Duende.Storage.Internal;
/// for a duration from now,
/// or to explicitly indicate no expiration.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public abstract record Expiration
{
// Prevent external subclassing.
diff --git a/storage/src/Storage/Internal/FieldType.cs b/storage/src/Storage/Internal/FieldType.cs
index 2b8b7e0dc..6321f0f87 100644
--- a/storage/src/Storage/Internal/FieldType.cs
+++ b/storage/src/Storage/Internal/FieldType.cs
@@ -7,6 +7,9 @@ namespace Duende.Storage.Internal;
/// Specifies the type of a field, corresponding to the typed columns in the search_values table.
/// This ensures QueryFields reads from the correct column instead of checking the first non-null value.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public enum FieldType
{
///
diff --git a/storage/src/Storage/Internal/Filtering/ComparisonOperator.cs b/storage/src/Storage/Internal/Filtering/ComparisonOperator.cs
index e71c11f20..b9198a080 100644
--- a/storage/src/Storage/Internal/Filtering/ComparisonOperator.cs
+++ b/storage/src/Storage/Internal/Filtering/ComparisonOperator.cs
@@ -3,16 +3,41 @@
namespace Duende.Storage.Internal.Filtering;
+///
+/// Defines the comparison operators supported by the filter expression parser.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public enum ComparisonOperator
{
+ /// Equality comparison.
Equal,
+
+ /// Inequality comparison.
NotEqual,
+
+ /// Substring containment.
Contains,
+
+ /// Prefix match.
StartsWith,
+
+ /// Suffix match.
EndsWith,
+
+ /// Greater than comparison.
GreaterThan,
+
+ /// Greater than or equal comparison.
GreaterThanOrEqual,
+
+ /// Less than comparison.
LessThan,
+
+ /// Less than or equal comparison.
LessThanOrEqual,
+
+ /// Presence check (field has a value).
Present
}
diff --git a/storage/src/Storage/Internal/Filtering/Expressions/AttributePathExpression.cs b/storage/src/Storage/Internal/Filtering/Expressions/AttributePathExpression.cs
index 1f2ef3d0a..4cac82e94 100644
--- a/storage/src/Storage/Internal/Filtering/Expressions/AttributePathExpression.cs
+++ b/storage/src/Storage/Internal/Filtering/Expressions/AttributePathExpression.cs
@@ -3,9 +3,18 @@
namespace Duende.Storage.Internal.Filtering.Expressions;
+///
+/// Represents an attribute path reference in a filter expression.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
+/// The attribute path string.
public sealed class AttributePathExpression(string path) : FilterExpression
{
+ /// Gets the attribute path.
public string Path { get; } = path ?? throw new ArgumentNullException(nameof(path));
+ ///
public override string ToString() => Path;
}
diff --git a/storage/src/Storage/Internal/Filtering/Expressions/ComparisonExpression.cs b/storage/src/Storage/Internal/Filtering/Expressions/ComparisonExpression.cs
index 391ce1fba..c403eff58 100644
--- a/storage/src/Storage/Internal/Filtering/Expressions/ComparisonExpression.cs
+++ b/storage/src/Storage/Internal/Filtering/Expressions/ComparisonExpression.cs
@@ -3,14 +3,27 @@
namespace Duende.Storage.Internal.Filtering.Expressions;
+///
+/// Represents a comparison filter expression (e.g., attribute eq value).
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
+/// The attribute path being compared.
+/// The comparison operator.
+/// The value to compare against.
public sealed class ComparisonExpression(AttributePathExpression attributePath, ComparisonOperator op, object? value)
: FilterExpression
{
+ /// Gets the attribute path being compared.
public AttributePathExpression AttributePath { get; } = attributePath ?? throw new ArgumentNullException(nameof(attributePath));
+ /// Gets the comparison operator.
public ComparisonOperator Operator { get; } = op;
+ /// Gets the comparison value.
public object? Value { get; } = value;
+ ///
public override string ToString() => $"{AttributePath} {Operator.ToFilterString()} {Value}";
}
diff --git a/storage/src/Storage/Internal/Filtering/Expressions/ComplexAttributeExpression.cs b/storage/src/Storage/Internal/Filtering/Expressions/ComplexAttributeExpression.cs
index a4ef696ff..ae1a04034 100644
--- a/storage/src/Storage/Internal/Filtering/Expressions/ComplexAttributeExpression.cs
+++ b/storage/src/Storage/Internal/Filtering/Expressions/ComplexAttributeExpression.cs
@@ -3,14 +3,25 @@
namespace Duende.Storage.Internal.Filtering.Expressions;
+///
+/// Represents a complex attribute filter expression with a nested filter (e.g., emails[type eq "work"]).
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
+/// The attribute path containing sub-attributes.
+/// The nested filter expression applied to the complex attribute.
public sealed class ComplexAttributeExpression(AttributePathExpression attributePath, FilterExpression filter)
: FilterExpression
{
+ /// Gets the attribute path.
public AttributePathExpression AttributePath { get; }
= attributePath ?? throw new ArgumentNullException(nameof(attributePath));
+ /// Gets the nested filter expression.
public FilterExpression Filter { get; }
= filter ?? throw new ArgumentNullException(nameof(filter));
+ ///
public override string ToString() => $"{AttributePath}[{Filter}]";
}
diff --git a/storage/src/Storage/Internal/Filtering/Expressions/FilterExpression.cs b/storage/src/Storage/Internal/Filtering/Expressions/FilterExpression.cs
index 5b484f37c..5f96a80d3 100644
--- a/storage/src/Storage/Internal/Filtering/Expressions/FilterExpression.cs
+++ b/storage/src/Storage/Internal/Filtering/Expressions/FilterExpression.cs
@@ -3,6 +3,12 @@
namespace Duende.Storage.Internal.Filtering.Expressions;
+///
+/// Base class for all filter expression tree nodes.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public abstract class FilterExpression
{
}
diff --git a/storage/src/Storage/Internal/Filtering/Expressions/LogicalExpression.cs b/storage/src/Storage/Internal/Filtering/Expressions/LogicalExpression.cs
index 0cf0ca36d..d70c56b07 100644
--- a/storage/src/Storage/Internal/Filtering/Expressions/LogicalExpression.cs
+++ b/storage/src/Storage/Internal/Filtering/Expressions/LogicalExpression.cs
@@ -3,14 +3,29 @@
namespace Duende.Storage.Internal.Filtering.Expressions;
+///
+/// Represents a logical filter expression combining sub-expressions with AND, OR, or NOT.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed class LogicalExpression : FilterExpression
{
+ /// Gets the logical operator.
public LogicalOperator Operator { get; }
+ /// Gets the left (or only, for NOT) operand.
public FilterExpression Left { get; }
+ /// Gets the right operand, or null for NOT expressions.
public FilterExpression? Right { get; }
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The logical operator.
+ /// The left operand.
+ /// The right operand (required for AND/OR, must be null for NOT).
public LogicalExpression(LogicalOperator op, FilterExpression left, FilterExpression? right = null)
{
Operator = op;
@@ -27,6 +42,7 @@ public sealed class LogicalExpression : FilterExpression
}
}
+ ///
public override string ToString() =>
Operator switch
{
diff --git a/storage/src/Storage/Internal/Filtering/FilterExpressionParser.cs b/storage/src/Storage/Internal/Filtering/FilterExpressionParser.cs
index 56f0a1d92..87a36e2bc 100644
--- a/storage/src/Storage/Internal/Filtering/FilterExpressionParser.cs
+++ b/storage/src/Storage/Internal/Filtering/FilterExpressionParser.cs
@@ -6,8 +6,21 @@ using Duende.Storage.Querying;
namespace Duende.Storage.Internal.Filtering;
+///
+/// Parses SCIM-style filter strings into a filter expression tree.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public static class FilterExpressionParser
{
+ ///
+ /// Parses a filter string into a .
+ ///
+ /// The SCIM filter string to parse.
+ /// The parsed filter expression tree.
+ /// Thrown when is null or whitespace.
+ /// Thrown when the filter string has invalid syntax.
public static FilterExpression Parse(string filter)
{
if (string.IsNullOrWhiteSpace(filter))
@@ -36,6 +49,12 @@ public static class FilterExpressionParser
}
}
+ ///
+ /// Attempts to parse a filter string, returning a value indicating success.
+ ///
+ /// The SCIM filter string to parse.
+ /// When successful, the parsed filter expression; otherwise, null.
+ /// true if parsing succeeded; otherwise, false.
public static bool TryParse(string filter, out FilterExpression? expression)
{
try
diff --git a/storage/src/Storage/Internal/Filtering/FilterTranslator.cs b/storage/src/Storage/Internal/Filtering/FilterTranslator.cs
index 10e3e17b5..f0e1d21da 100644
--- a/storage/src/Storage/Internal/Filtering/FilterTranslator.cs
+++ b/storage/src/Storage/Internal/Filtering/FilterTranslator.cs
@@ -8,13 +8,28 @@ using Duende.Storage.Internal.Querying.Fields;
namespace Duende.Storage.Internal.Filtering;
+///
+/// Translates parsed filter expression trees into query filter expressions.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed class FilterTranslator
{
- private readonly IScimAttributeTypeResolver _resolver;
+ private readonly IQueryAttributeTypeResolver _resolver;
- public FilterTranslator(IScimAttributeTypeResolver resolver) =>
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The attribute type resolver for mapping attribute paths to fields.
+ public FilterTranslator(IQueryAttributeTypeResolver resolver) =>
_resolver = resolver ?? throw new ArgumentNullException(nameof(resolver));
+ ///
+ /// Translates a filter string into a query filter expression.
+ ///
+ /// The filter string to translate, or null for no filter.
+ /// The query filter expression, or null if the filter is empty.
public IQueryFilterExpression? Translate(string? filter)
{
if (string.IsNullOrWhiteSpace(filter))
@@ -26,6 +41,11 @@ public sealed class FilterTranslator
return Translate(expression);
}
+ ///
+ /// Translates a parsed filter expression into a query filter expression.
+ ///
+ /// The filter expression to translate.
+ /// The query filter expression.
public IQueryFilterExpression Translate(FilterExpression expression)
{
ArgumentNullException.ThrowIfNull(expression);
@@ -148,7 +168,7 @@ public sealed class FilterTranslator
/// Inside an array filter, fields are per-element and the SQL builder
/// re-adds the array prefix when building the query.
///
- private sealed class ArrayElementResolver(IScimAttributeTypeResolver inner, string arrayPrefix) : IScimAttributeTypeResolver
+ private sealed class ArrayElementResolver(IQueryAttributeTypeResolver inner, string arrayPrefix) : IQueryAttributeTypeResolver
{
public Field ResolveField(string attributePath)
{
diff --git a/storage/src/Storage/Internal/Filtering/LogicalOperator.cs b/storage/src/Storage/Internal/Filtering/LogicalOperator.cs
index 53dd1ed8c..0f466370f 100644
--- a/storage/src/Storage/Internal/Filtering/LogicalOperator.cs
+++ b/storage/src/Storage/Internal/Filtering/LogicalOperator.cs
@@ -3,9 +3,20 @@
namespace Duende.Storage.Internal.Filtering;
+///
+/// Defines the logical operators supported by the filter expression parser.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public enum LogicalOperator
{
+ /// Logical AND.
And,
+
+ /// Logical OR.
Or,
+
+ /// Logical NOT (negation).
Not
}
diff --git a/storage/src/Storage/Internal/IDataStorageKey.cs b/storage/src/Storage/Internal/IDataStorageKey.cs
index 183dde991..cbad0c8c6 100644
--- a/storage/src/Storage/Internal/IDataStorageKey.cs
+++ b/storage/src/Storage/Internal/IDataStorageKey.cs
@@ -6,6 +6,9 @@ namespace Duende.Storage.Internal;
///
/// Interface for DSK (Data Store Key) types.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public interface IDataStorageKey
{
///
diff --git a/storage/src/Storage/Internal/IDataStorageObject.cs b/storage/src/Storage/Internal/IDataStorageObject.cs
index 6e2a949e2..445b3613c 100644
--- a/storage/src/Storage/Internal/IDataStorageObject.cs
+++ b/storage/src/Storage/Internal/IDataStorageObject.cs
@@ -6,7 +6,13 @@ namespace Duende.Storage.Internal;
///
/// Represents a Data Storage Object (DSO).
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public interface IDataStorageObject
{
+ ///
+ /// Gets the DSO version for this type.
+ ///
static abstract DataStorageObjectVersion DsoVersion { get; }
}
diff --git a/storage/src/Storage/Internal/IGuidDataStorageKey.cs b/storage/src/Storage/Internal/IGuidDataStorageKey.cs
index 7e312d1fd..b2fd81a88 100644
--- a/storage/src/Storage/Internal/IGuidDataStorageKey.cs
+++ b/storage/src/Storage/Internal/IGuidDataStorageKey.cs
@@ -5,9 +5,15 @@ namespace Duende.Storage.Internal;
///
/// Marks a dsk as only being a guid value. This means it won't be stored as serialized json
-/// but only uses the guid value. IE: UserSubjectId, which is already a Guid.
+/// but only uses the guid value. IE: UserSubjectId, which is already a Guid.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public interface IGuidDataStorageKey : IDataStorageKey
{
+ ///
+ /// Gets the GUID value of this key.
+ ///
Guid Value { get; }
}
diff --git a/storage/src/Storage/Internal/IPooledStore.cs b/storage/src/Storage/Internal/IPooledStore.cs
index 993fab58d..40b1262ca 100644
--- a/storage/src/Storage/Internal/IPooledStore.cs
+++ b/storage/src/Storage/Internal/IPooledStore.cs
@@ -3,7 +3,18 @@
namespace Duende.Storage.Internal;
+///
+/// Represents a pooled store that supports multiple isolated pools sharing a single database.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public interface IPooledStore : IDatabaseSchema
{
+ ///
+ /// Opens a store scoped to the specified pool.
+ ///
+ /// The pool identifier.
+ /// An scoped to the specified pool.
IStore OpenPool(PoolId poolId);
}
diff --git a/storage/src/Storage/Internal/IScimAttributeTypeResolver.cs b/storage/src/Storage/Internal/IQueryAttributeTypeResolver.cs
similarity index 55%
rename from storage/src/Storage/Internal/IScimAttributeTypeResolver.cs
rename to storage/src/Storage/Internal/IQueryAttributeTypeResolver.cs
index 947474d58..9380f2618 100644
--- a/storage/src/Storage/Internal/IScimAttributeTypeResolver.cs
+++ b/storage/src/Storage/Internal/IQueryAttributeTypeResolver.cs
@@ -6,15 +6,18 @@ using Duende.Storage.Internal.Querying.Fields;
namespace Duende.Storage.Internal;
///
-/// Resolves SCIM attribute paths to Faro Field types.
+/// Resolves query attribute paths to Faro Field types.
/// Implementations define the schema-specific mapping for a resource type (User, Group, etc.).
///
-public interface IScimAttributeTypeResolver
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
+public interface IQueryAttributeTypeResolver
{
///
- /// Resolves a SCIM attribute path to its corresponding Faro Field.
+ /// Resolves a query attribute path to its corresponding Faro Field.
///
- /// The SCIM attribute path (e.g., "userName", "name.familyName", "emails.value").
+ /// The attribute path (e.g., "userName", "name.familyName", "emails.value").
/// The corresponding Faro Field instance.
/// Thrown when the attribute path is not recognized.
Field ResolveField(string attributePath);
diff --git a/storage/src/Storage/Internal/IStore.cs b/storage/src/Storage/Internal/IStore.cs
index 419b3370e..3320659e2 100644
--- a/storage/src/Storage/Internal/IStore.cs
+++ b/storage/src/Storage/Internal/IStore.cs
@@ -13,6 +13,12 @@ using OutboxEventId = Duende.Storage.Internal.Outbox.OutboxEventId;
namespace Duende.Storage.Internal;
+///
+/// Defines the core data store operations for entities, links, and outbox events.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public interface IStore
{
internal void SetPoolId(PoolId poolId);
diff --git a/storage/src/Storage/Internal/InstrumentedStore.cs b/storage/src/Storage/Internal/InstrumentedStore.cs
index 7a72ab6a2..de0b14fdf 100644
--- a/storage/src/Storage/Internal/InstrumentedStore.cs
+++ b/storage/src/Storage/Internal/InstrumentedStore.cs
@@ -17,10 +17,17 @@ namespace Duende.Storage.Internal;
///
/// Decorates an with tracing and metrics instrumentation.
///
-public sealed class InstrumentedStore(IStore inner, StorageMetrics metrics, string dbSystem) : IStore
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
+internal sealed class InstrumentedStore(IStore inner, StorageMetrics metrics, string dbSystem) : IStore
{
+ ///
+ /// Gets the inner store being decorated.
+ ///
public IStore Inner => inner;
+ ///
public void SetPoolId(PoolId poolId) => inner.SetPoolId(poolId);
public async Task CreateAsync(
diff --git a/storage/src/Storage/Internal/LinkDefinition.cs b/storage/src/Storage/Internal/LinkDefinition.cs
index 92be12af3..d53d57fa4 100644
--- a/storage/src/Storage/Internal/LinkDefinition.cs
+++ b/storage/src/Storage/Internal/LinkDefinition.cs
@@ -7,6 +7,9 @@ namespace Duende.Storage.Internal;
/// Defines the schema for a link type — binding a with its left and right s.
/// Define link definitions once as static instances and reference them everywhere.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record LinkDefinition
{
/// The entity type on the left side of the link.
diff --git a/storage/src/Storage/Internal/LinkResult.cs b/storage/src/Storage/Internal/LinkResult.cs
index 98f8c22fe..60af945de 100644
--- a/storage/src/Storage/Internal/LinkResult.cs
+++ b/storage/src/Storage/Internal/LinkResult.cs
@@ -6,6 +6,9 @@ namespace Duende.Storage.Internal;
///
/// The result of a Link operation on .
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public enum LinkResult
{
/// The link was created successfully.
diff --git a/storage/src/Storage/Internal/LinkType.cs b/storage/src/Storage/Internal/LinkType.cs
index fcfdf3bb1..14a678f93 100644
--- a/storage/src/Storage/Internal/LinkType.cs
+++ b/storage/src/Storage/Internal/LinkType.cs
@@ -6,15 +6,30 @@ namespace Duende.Storage.Internal;
///
/// The type of link between two entities.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
/// A number representation for the link type.
/// The name of the link type. This name is only used for display purposes and should never change.
public readonly record struct LinkType(uint Id, string Name)
{
+ ///
+ /// Obsolete parameterless constructor. Do not use.
+ ///
[Obsolete("Don't use this constructor")]
public LinkType() : this(0!, null!) => throw new InvalidOperationException("Cannot instantiate LinkType without parameters");
+ ///
+ /// Converts a value to a .
+ ///
+ /// The registry value to convert.
+ /// A instance.
public static LinkType ToLinkType(LinkTypeRegistry registry) =>
new((uint)registry, registry.ToString());
+ ///
+ /// Implicitly converts a value to a .
+ ///
+ /// The registry value to convert.
public static implicit operator LinkType(LinkTypeRegistry value) => ToLinkType(value);
}
diff --git a/storage/src/Storage/Internal/LinkTypeRegistry.cs b/storage/src/Storage/Internal/LinkTypeRegistry.cs
index d5c237e0a..b6ab2d145 100644
--- a/storage/src/Storage/Internal/LinkTypeRegistry.cs
+++ b/storage/src/Storage/Internal/LinkTypeRegistry.cs
@@ -8,13 +8,26 @@ namespace Duende.Storage.Internal;
///
/// Once a link type is assigned an identifier, it must never be changed or reused for a different link type.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
#pragma warning disable CA1008 // Enums should have zero value. We explicitly want to avoid assigning a zero value to any link type, to catch uninitialized values.
public enum LinkTypeRegistry
#pragma warning restore CA1008
{
+ ///
+ /// Link type for group-to-role relationships.
+ ///
// user profile link types (1500-1599 range, aligned with entity types)
GroupRole = 1502,
+ ///
+ /// Link type for membership-to-role relationships.
+ ///
MembershipRole = 1503,
+
+ ///
+ /// Link type for membership-to-group relationships.
+ ///
MembershipGroup = 1504,
}
diff --git a/storage/src/Storage/Internal/Operations/BatchResult.cs b/storage/src/Storage/Internal/Operations/BatchResult.cs
index c6fe09db0..a0a677408 100644
--- a/storage/src/Storage/Internal/Operations/BatchResult.cs
+++ b/storage/src/Storage/Internal/Operations/BatchResult.cs
@@ -6,6 +6,9 @@ namespace Duende.Storage.Internal.Operations;
///
/// Represents the result of a batch operation.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
/// True if all operations succeeded; false if any failed (and all were rolled back).
/// The outcome of each operation, in order.
public sealed record BatchResult(bool Success, IReadOnlyList Results)
diff --git a/storage/src/Storage/Internal/Operations/CreateOperation.cs b/storage/src/Storage/Internal/Operations/CreateOperation.cs
index 634b67bb4..c4de994b4 100644
--- a/storage/src/Storage/Internal/Operations/CreateOperation.cs
+++ b/storage/src/Storage/Internal/Operations/CreateOperation.cs
@@ -8,6 +8,9 @@ namespace Duende.Storage.Internal.Operations;
///
/// Represents a create operation for batch processing.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed class CreateOperation : IStoreOperation
{
private CreateOperation(
diff --git a/storage/src/Storage/Internal/Operations/CreateResult.cs b/storage/src/Storage/Internal/Operations/CreateResult.cs
index 0781fabaf..0da6a0a52 100644
--- a/storage/src/Storage/Internal/Operations/CreateResult.cs
+++ b/storage/src/Storage/Internal/Operations/CreateResult.cs
@@ -3,10 +3,31 @@
namespace Duende.Storage.Internal.Operations;
+///
+/// Represents the possible outcomes of a create operation.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public enum CreateResult
{
+ ///
+ /// The entity was created successfully.
+ ///
Success,
+
+ ///
+ /// The entity already exists with the same identifier.
+ ///
AlreadyExists,
+
+ ///
+ /// A key conflict occurred with another entity.
+ ///
KeyConflict,
+
+ ///
+ /// A concurrency conflict occurred during creation.
+ ///
ConcurrencyConflict
}
diff --git a/storage/src/Storage/Internal/Operations/DeleteOperation.cs b/storage/src/Storage/Internal/Operations/DeleteOperation.cs
index 11c7d8ef2..3e23a0617 100644
--- a/storage/src/Storage/Internal/Operations/DeleteOperation.cs
+++ b/storage/src/Storage/Internal/Operations/DeleteOperation.cs
@@ -6,6 +6,9 @@ namespace Duende.Storage.Internal.Operations;
///
/// Represents a delete operation for batch processing.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed class DeleteOperation : IStoreOperation
{
private DeleteOperation(EntityType entityType, UuidV7? id, DataStorageKey? key)
diff --git a/storage/src/Storage/Internal/Operations/DeleteResult.cs b/storage/src/Storage/Internal/Operations/DeleteResult.cs
index 3fc0b25da..cd6daff64 100644
--- a/storage/src/Storage/Internal/Operations/DeleteResult.cs
+++ b/storage/src/Storage/Internal/Operations/DeleteResult.cs
@@ -3,7 +3,16 @@
namespace Duende.Storage.Internal.Operations;
+///
+/// Represents the possible outcomes of a delete operation.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public enum DeleteResult
{
+ ///
+ /// The entity was deleted successfully.
+ ///
Success
}
diff --git a/storage/src/Storage/Internal/Operations/IStoreOperation.cs b/storage/src/Storage/Internal/Operations/IStoreOperation.cs
index 3628c6cc8..3e87aaeec 100644
--- a/storage/src/Storage/Internal/Operations/IStoreOperation.cs
+++ b/storage/src/Storage/Internal/Operations/IStoreOperation.cs
@@ -6,6 +6,9 @@ namespace Duende.Storage.Internal.Operations;
///
/// Marker interface for batch operations.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public interface IStoreOperation
{
///
diff --git a/storage/src/Storage/Internal/Operations/LinkOperation.cs b/storage/src/Storage/Internal/Operations/LinkOperation.cs
index 1869daa30..21d19b96d 100644
--- a/storage/src/Storage/Internal/Operations/LinkOperation.cs
+++ b/storage/src/Storage/Internal/Operations/LinkOperation.cs
@@ -7,6 +7,9 @@ namespace Duende.Storage.Internal.Operations;
/// Represents a link operation for batch processing.
/// Creates a link between two entities as part of an atomic batch.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed class LinkOperation : IStoreOperation
{
private LinkOperation(LinkDefinition definition, UuidV7 leftEntityId, UuidV7 rightEntityId)
diff --git a/storage/src/Storage/Internal/Operations/OperationOutcome.cs b/storage/src/Storage/Internal/Operations/OperationOutcome.cs
index e91c0e461..82453a54e 100644
--- a/storage/src/Storage/Internal/Operations/OperationOutcome.cs
+++ b/storage/src/Storage/Internal/Operations/OperationOutcome.cs
@@ -6,6 +6,9 @@ namespace Duende.Storage.Internal.Operations;
///
/// Represents the outcome of an individual operation within a batch.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public enum OperationOutcome
{
///
diff --git a/storage/src/Storage/Internal/Operations/OperationResult.cs b/storage/src/Storage/Internal/Operations/OperationResult.cs
index b55a6aba6..f46ecbd04 100644
--- a/storage/src/Storage/Internal/Operations/OperationResult.cs
+++ b/storage/src/Storage/Internal/Operations/OperationResult.cs
@@ -6,6 +6,9 @@ namespace Duende.Storage.Internal.Operations;
///
/// Represents the result of an individual operation within a batch.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
/// The zero-based index of the operation in the batch.
/// The outcome of the operation.
public sealed record OperationResult(int Index, OperationOutcome Outcome);
diff --git a/storage/src/Storage/Internal/Operations/StoreGetResult.cs b/storage/src/Storage/Internal/Operations/StoreGetResult.cs
index d2d8be7c8..e9b73cba5 100644
--- a/storage/src/Storage/Internal/Operations/StoreGetResult.cs
+++ b/storage/src/Storage/Internal/Operations/StoreGetResult.cs
@@ -8,8 +8,19 @@ namespace Duende.Storage.Internal.Operations;
///
/// Wraps the result of a Get operation from the store.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record StoreGetResult
{
+ ///
+ /// Initializes a new instance of the class representing a found entity.
+ ///
+ /// The data storage object.
+ /// The unique identifier of the entity.
+ /// The version of the entity.
+ /// The creation timestamp.
+ /// The last update timestamp.
public StoreGetResult(IDataStorageObject dso, Guid id, int version, DateTimeOffset createdAt, DateTimeOffset lastUpdatedAt)
{
Found = true;
@@ -24,23 +35,54 @@ public sealed record StoreGetResult
{
}
+ ///
+ /// Creates a result indicating the entity was not found.
+ ///
+ /// A representing a not-found result.
public static StoreGetResult NotFound() => new();
+ ///
+ /// Gets a value indicating whether the entity was found.
+ ///
[MemberNotNullWhen(true, nameof(Dso))]
[MemberNotNullWhen(true, nameof(Id))]
[MemberNotNullWhen(true, nameof(Version))]
public bool Found { get; }
+ ///
+ /// Gets the data storage object, or null if not found.
+ ///
public IDataStorageObject? Dso { get; }
+ ///
+ /// Gets the unique identifier, or null if not found.
+ ///
public Guid? Id { get; }
+ ///
+ /// Gets the entity version, or null if not found.
+ ///
public int? Version { get; }
+ ///
+ /// Gets the creation timestamp.
+ ///
public DateTimeOffset CreatedAt { get; }
+ ///
+ /// Gets the last update timestamp.
+ ///
public DateTimeOffset LastUpdatedAt { get; }
+ ///
+ /// Creates a result indicating the entity was found.
+ ///
+ /// The data storage object.
+ /// The unique identifier.
+ /// The entity version.
+ /// The creation timestamp.
+ /// The last update timestamp.
+ /// A representing a found result.
public static StoreGetResult IsFound(IDataStorageObject item, Guid id, int version, DateTimeOffset createdAt, DateTimeOffset lastUpdatedAt) =>
new(item, id, version, createdAt, lastUpdatedAt);
}
diff --git a/storage/src/Storage/Internal/Operations/UnlinkOperation.cs b/storage/src/Storage/Internal/Operations/UnlinkOperation.cs
index 764b8ec5c..f456308a0 100644
--- a/storage/src/Storage/Internal/Operations/UnlinkOperation.cs
+++ b/storage/src/Storage/Internal/Operations/UnlinkOperation.cs
@@ -7,6 +7,9 @@ namespace Duende.Storage.Internal.Operations;
/// Represents an unlink operation for batch processing.
/// Removes a link between two entities as part of an atomic batch.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed class UnlinkOperation : IStoreOperation
{
private UnlinkOperation(LinkDefinition definition, UuidV7 leftEntityId, UuidV7 rightEntityId)
diff --git a/storage/src/Storage/Internal/Operations/UpdateOperation.cs b/storage/src/Storage/Internal/Operations/UpdateOperation.cs
index 90a12eff4..2d968df45 100644
--- a/storage/src/Storage/Internal/Operations/UpdateOperation.cs
+++ b/storage/src/Storage/Internal/Operations/UpdateOperation.cs
@@ -8,6 +8,9 @@ namespace Duende.Storage.Internal.Operations;
///
/// Represents an update operation for batch processing.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed class UpdateOperation : IStoreOperation
{
private UpdateOperation(
diff --git a/storage/src/Storage/Internal/Operations/UpdateResult.cs b/storage/src/Storage/Internal/Operations/UpdateResult.cs
index f7fd5514d..2aee06883 100644
--- a/storage/src/Storage/Internal/Operations/UpdateResult.cs
+++ b/storage/src/Storage/Internal/Operations/UpdateResult.cs
@@ -3,10 +3,31 @@
namespace Duende.Storage.Internal.Operations;
+///
+/// Represents the possible outcomes of an update operation.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public enum UpdateResult
{
+ ///
+ /// The entity was updated successfully.
+ ///
Success,
+
+ ///
+ /// The entity does not exist.
+ ///
DoesNotExist,
+
+ ///
+ /// The entity version did not match the expected version.
+ ///
UnexpectedVersion,
+
+ ///
+ /// A key conflict occurred with another entity.
+ ///
KeyConflict
}
diff --git a/storage/src/Storage/Internal/Outbox/HandleOutcomeResult.cs b/storage/src/Storage/Internal/Outbox/HandleOutcomeResult.cs
index 116319a0e..03f401951 100644
--- a/storage/src/Storage/Internal/Outbox/HandleOutcomeResult.cs
+++ b/storage/src/Storage/Internal/Outbox/HandleOutcomeResult.cs
@@ -7,6 +7,9 @@ namespace Duende.Storage.Internal.Outbox;
/// Represents the outcome of a handler processing an outbox event.
/// Handlers return one of: , , or .
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public abstract record HandleOutcomeResult
{
private HandleOutcomeResult() { }
diff --git a/storage/src/Storage/Internal/Outbox/IOutboxSubscriber.cs b/storage/src/Storage/Internal/Outbox/IOutboxSubscriber.cs
index 28afbc08d..da58e0f4a 100644
--- a/storage/src/Storage/Internal/Outbox/IOutboxSubscriber.cs
+++ b/storage/src/Storage/Internal/Outbox/IOutboxSubscriber.cs
@@ -7,6 +7,9 @@ namespace Duende.Storage.Internal.Outbox;
/// Declares a subscriber that wants to receive outbox events via the fanout mechanism.
/// Subscribers are registered in DI and matched against outbox events by event name and entity type.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public interface IOutboxSubscriber
{
/// The unique name identifying this subscriber.
diff --git a/storage/src/Storage/Internal/Outbox/IOutboxSubscriberHandler.cs b/storage/src/Storage/Internal/Outbox/IOutboxSubscriberHandler.cs
index 29c24bf4c..ce66283c6 100644
--- a/storage/src/Storage/Internal/Outbox/IOutboxSubscriberHandler.cs
+++ b/storage/src/Storage/Internal/Outbox/IOutboxSubscriberHandler.cs
@@ -7,6 +7,9 @@ namespace Duende.Storage.Internal.Outbox;
/// Defines the handler contract for processing outbox events for a specific subscriber.
/// Implementations are registered in DI using keyed services, keyed by subscriber name.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public interface IOutboxSubscriberHandler
{
/// Handles a single outbox event delivered to this subscriber.
diff --git a/storage/src/Storage/Internal/Outbox/OutboxEvent.cs b/storage/src/Storage/Internal/Outbox/OutboxEvent.cs
index 2d57ddd90..250d19841 100644
--- a/storage/src/Storage/Internal/Outbox/OutboxEvent.cs
+++ b/storage/src/Storage/Internal/Outbox/OutboxEvent.cs
@@ -8,6 +8,9 @@ namespace Duende.Storage.Internal.Outbox;
/// The store implementation stamps PoolId from the ambient pool context, and the
/// database automatically assigns the SequenceNumber on insert.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record OutboxEvent
{
/// The unique identifier for this outbox event.
diff --git a/storage/src/Storage/Internal/Outbox/OutboxEventId.cs b/storage/src/Storage/Internal/Outbox/OutboxEventId.cs
index b759e73dd..953eb251c 100644
--- a/storage/src/Storage/Internal/Outbox/OutboxEventId.cs
+++ b/storage/src/Storage/Internal/Outbox/OutboxEventId.cs
@@ -3,6 +3,12 @@
namespace Duende.Storage.Internal.Outbox;
+///
+/// Represents a unique identifier for an outbox event.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
[ValueOf]
public partial record OutboxEventId
{
diff --git a/storage/src/Storage/Internal/Outbox/OutboxEventName.cs b/storage/src/Storage/Internal/Outbox/OutboxEventName.cs
index b8a6d3768..4161d767b 100644
--- a/storage/src/Storage/Internal/Outbox/OutboxEventName.cs
+++ b/storage/src/Storage/Internal/Outbox/OutboxEventName.cs
@@ -8,6 +8,9 @@ namespace Duende.Storage.Internal.Outbox;
///
/// The name of a domain event written to the outbox. Only alphanumeric characters, underscores, and hyphens are allowed.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
[StringValue]
public partial record OutboxEventName
{
diff --git a/storage/src/Storage/Internal/Outbox/OutboxEventsPage.cs b/storage/src/Storage/Internal/Outbox/OutboxEventsPage.cs
index f54d98345..379638425 100644
--- a/storage/src/Storage/Internal/Outbox/OutboxEventsPage.cs
+++ b/storage/src/Storage/Internal/Outbox/OutboxEventsPage.cs
@@ -6,6 +6,9 @@ namespace Duende.Storage.Internal.Outbox;
///
/// A paged result of outbox events ordered by sequence number.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
/// The outbox events in this page.
/// Whether there are more events beyond this page.
public sealed record OutboxEventsPage(IReadOnlyList Events, bool HasMore);
diff --git a/storage/src/Storage/Internal/Outbox/PersistedOutboxEvent.cs b/storage/src/Storage/Internal/Outbox/PersistedOutboxEvent.cs
index 5bae409b6..1375f3781 100644
--- a/storage/src/Storage/Internal/Outbox/PersistedOutboxEvent.cs
+++ b/storage/src/Storage/Internal/Outbox/PersistedOutboxEvent.cs
@@ -7,6 +7,9 @@ namespace Duende.Storage.Internal.Outbox;
/// Represents an outbox event as read from the database, including the
/// database-assigned sequence number and the store-stamped space identifier.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record PersistedOutboxEvent
{
/// The store-generated unique identifier for this persisted message (one per subscriber fanout row).
diff --git a/storage/src/Storage/Internal/Outbox/SubscriberName.cs b/storage/src/Storage/Internal/Outbox/SubscriberName.cs
index 77de28734..18aa63b1f 100644
--- a/storage/src/Storage/Internal/Outbox/SubscriberName.cs
+++ b/storage/src/Storage/Internal/Outbox/SubscriberName.cs
@@ -8,6 +8,9 @@ namespace Duende.Storage.Internal.Outbox;
///
/// The unique name identifying an outbox subscriber. Only alphanumeric characters, underscores, and hyphens are allowed.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
[StringValue]
public partial record SubscriberName
{
diff --git a/storage/src/Storage/Internal/PoolId.cs b/storage/src/Storage/Internal/PoolId.cs
index 9d9f488fe..4997d9905 100644
--- a/storage/src/Storage/Internal/PoolId.cs
+++ b/storage/src/Storage/Internal/PoolId.cs
@@ -3,5 +3,11 @@
namespace Duende.Storage.Internal;
+///
+/// Represents a pool identifier.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
[ValueOf]
public partial record PoolId;
diff --git a/storage/src/Storage/Internal/Projection.cs b/storage/src/Storage/Internal/Projection.cs
index dc377ea19..fba35e963 100644
--- a/storage/src/Storage/Internal/Projection.cs
+++ b/storage/src/Storage/Internal/Projection.cs
@@ -8,6 +8,9 @@ namespace Duende.Storage.Internal;
/// When used, the query returns
/// instead of a fully-typed DTO.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record Projection
{
/// The attribute names to include.
diff --git a/storage/src/Storage/Internal/Querying/CursorToken.cs b/storage/src/Storage/Internal/Querying/CursorToken.cs
index 2f5c337f2..da9d98bd2 100644
--- a/storage/src/Storage/Internal/Querying/CursorToken.cs
+++ b/storage/src/Storage/Internal/Querying/CursorToken.cs
@@ -9,7 +9,10 @@ namespace Duende.Storage.Internal.Querying;
///
/// Represents a decoded cursor token containing the last-seen position for seek-based pagination.
///
-public sealed record CursorToken
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
+internal sealed record CursorToken
{
///
/// The ID of the last entity on the previous page.
diff --git a/storage/src/Storage/Internal/Querying/Expressions/AllExpression.cs b/storage/src/Storage/Internal/Querying/Expressions/AllExpression.cs
index 541b0dd25..55e490841 100644
--- a/storage/src/Storage/Internal/Querying/Expressions/AllExpression.cs
+++ b/storage/src/Storage/Internal/Querying/Expressions/AllExpression.cs
@@ -6,6 +6,9 @@ namespace Duende.Storage.Internal.Querying.Expressions;
///
/// Singleton expression representing 'match all records' - no filter applied.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record AllExpression : IQueryFilterExpression
{
///
diff --git a/storage/src/Storage/Internal/Querying/Expressions/AndExpression.cs b/storage/src/Storage/Internal/Querying/Expressions/AndExpression.cs
index f08130050..6cec2bd53 100644
--- a/storage/src/Storage/Internal/Querying/Expressions/AndExpression.cs
+++ b/storage/src/Storage/Internal/Querying/Expressions/AndExpression.cs
@@ -7,6 +7,9 @@ namespace Duende.Storage.Internal.Querying.Expressions;
/// Expression that combines multiple filter expressions with AND logic.
/// All conditions must be true for the expression to match.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record AndExpression : IQueryFilterExpression
{
///
@@ -14,6 +17,10 @@ public sealed record AndExpression : IQueryFilterExpression
///
public IReadOnlyList Parts { get; }
+ ///
+ /// Initializes a new instance of with the specified parts.
+ ///
+ /// The filter expressions to combine with AND logic.
public AndExpression(IReadOnlyList parts)
{
ArgumentNullException.ThrowIfNull(parts);
@@ -26,6 +33,10 @@ public sealed record AndExpression : IQueryFilterExpression
Parts = parts;
}
+ ///
+ /// Initializes a new instance of with the specified parts.
+ ///
+ /// The filter expressions to combine with AND logic.
public AndExpression(params IQueryFilterExpression[] parts)
: this((IReadOnlyList)parts)
{
diff --git a/storage/src/Storage/Internal/Querying/Expressions/ArrayContainsExpression.cs b/storage/src/Storage/Internal/Querying/Expressions/ArrayContainsExpression.cs
index 586767a43..07b41a526 100644
--- a/storage/src/Storage/Internal/Querying/Expressions/ArrayContainsExpression.cs
+++ b/storage/src/Storage/Internal/Querying/Expressions/ArrayContainsExpression.cs
@@ -9,4 +9,7 @@ namespace Duende.Storage.Internal.Querying.Expressions;
/// Expression that checks if a string array field contains an element equal to the specified value.
/// Generates an EXISTS subquery with item_index >= 0 to match any array element.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record ArrayContainsExpression(StringArrayField Field, string Value) : IQueryExpression, IQueryFilterExpression;
diff --git a/storage/src/Storage/Internal/Querying/Expressions/ArrayFilterExpression.cs b/storage/src/Storage/Internal/Querying/Expressions/ArrayFilterExpression.cs
index 554aad416..d34a4ddae 100644
--- a/storage/src/Storage/Internal/Querying/Expressions/ArrayFilterExpression.cs
+++ b/storage/src/Storage/Internal/Querying/Expressions/ArrayFilterExpression.cs
@@ -10,6 +10,9 @@ namespace Duende.Storage.Internal.Querying.Expressions;
/// Example: emails[type eq "work" and value co "@example.com"]
/// This ensures both conditions match the same email in the emails array.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record ArrayFilterExpression : IQueryFilterExpression
{
///
@@ -23,6 +26,11 @@ public sealed record ArrayFilterExpression : IQueryFilterExpression
///
public IQueryFilterExpression Filter { get; }
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The array field path.
+ /// The filter expression to apply within each array element.
public ArrayFilterExpression(string arrayFieldPath, IQueryFilterExpression filter)
{
if (string.IsNullOrWhiteSpace(arrayFieldPath))
diff --git a/storage/src/Storage/Internal/Querying/Expressions/BetweenExpression.cs b/storage/src/Storage/Internal/Querying/Expressions/BetweenExpression.cs
index b417b1d8e..dca145668 100644
--- a/storage/src/Storage/Internal/Querying/Expressions/BetweenExpression.cs
+++ b/storage/src/Storage/Internal/Querying/Expressions/BetweenExpression.cs
@@ -8,12 +8,26 @@ namespace Duende.Storage.Internal.Querying.Expressions;
///
/// Expression that checks if a field value is between two values (inclusive).
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record BetweenExpression : IQueryFilterExpression
{
+ /// Gets the field to compare.
public Field Field { get; }
+
+ /// Gets the minimum value (inclusive).
public object Min { get; }
+
+ /// Gets the maximum value (inclusive).
public object Max { get; }
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The field to compare.
+ /// The minimum value (inclusive).
+ /// The maximum value (inclusive).
public BetweenExpression(Field field, object min, object max)
{
Field = field ?? throw new ArgumentNullException(nameof(field));
diff --git a/storage/src/Storage/Internal/Querying/Expressions/ContainsExpression.cs b/storage/src/Storage/Internal/Querying/Expressions/ContainsExpression.cs
index 35a253674..2c748ed75 100644
--- a/storage/src/Storage/Internal/Querying/Expressions/ContainsExpression.cs
+++ b/storage/src/Storage/Internal/Querying/Expressions/ContainsExpression.cs
@@ -8,11 +8,22 @@ namespace Duende.Storage.Internal.Querying.Expressions;
///
/// Expression that checks if a string field contains a specified substring.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record ContainsExpression : IQueryFilterExpression
{
+ /// Gets the string field to search.
public StringField Field { get; }
+
+ /// Gets the substring to search for.
public string Value { get; }
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The string field to search.
+ /// The substring to search for.
public ContainsExpression(StringField field, string value)
{
Field = field ?? throw new ArgumentNullException(nameof(field));
diff --git a/storage/src/Storage/Internal/Querying/Expressions/EndsWithExpression.cs b/storage/src/Storage/Internal/Querying/Expressions/EndsWithExpression.cs
index 6711f413f..b0c66428c 100644
--- a/storage/src/Storage/Internal/Querying/Expressions/EndsWithExpression.cs
+++ b/storage/src/Storage/Internal/Querying/Expressions/EndsWithExpression.cs
@@ -9,11 +9,22 @@ namespace Duende.Storage.Internal.Querying.Expressions;
/// Expression that checks if a string field ends with a specified suffix.
/// Used for the SCIM 'ew' (ends with) operator.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record EndsWithExpression : IQueryFilterExpression
{
+ /// Gets the string field to check.
public StringField Field { get; }
+
+ /// Gets the suffix value.
public string Value { get; }
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The string field to check.
+ /// The suffix to match.
public EndsWithExpression(StringField field, string value)
{
Field = field ?? throw new ArgumentNullException(nameof(field));
diff --git a/storage/src/Storage/Internal/Querying/Expressions/EqualExpression.cs b/storage/src/Storage/Internal/Querying/Expressions/EqualExpression.cs
index 8158985b2..10094e34b 100644
--- a/storage/src/Storage/Internal/Querying/Expressions/EqualExpression.cs
+++ b/storage/src/Storage/Internal/Querying/Expressions/EqualExpression.cs
@@ -8,11 +8,22 @@ namespace Duende.Storage.Internal.Querying.Expressions;
///
/// Expression that checks if a field equals a specified value.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record EqualExpression : IQueryFilterExpression
{
+ /// Gets the field to compare.
public Field Field { get; }
+
+ /// Gets the value to compare against.
public object Value { get; }
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The field to compare.
+ /// The value to compare against.
public EqualExpression(Field field, object value)
{
Field = field ?? throw new ArgumentNullException(nameof(field));
diff --git a/storage/src/Storage/Internal/Querying/Expressions/GreaterOrEqualExpression.cs b/storage/src/Storage/Internal/Querying/Expressions/GreaterOrEqualExpression.cs
index 36aec43a3..eddd2bdef 100644
--- a/storage/src/Storage/Internal/Querying/Expressions/GreaterOrEqualExpression.cs
+++ b/storage/src/Storage/Internal/Querying/Expressions/GreaterOrEqualExpression.cs
@@ -8,11 +8,22 @@ namespace Duende.Storage.Internal.Querying.Expressions;
///
/// Expression that checks if a field is greater than or equal to a specified value.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record GreaterOrEqualExpression : IQueryFilterExpression
{
+ /// Gets the field to compare.
public Field Field { get; }
+
+ /// Gets the value to compare against.
public object Value { get; }
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The field to compare.
+ /// The value to compare against.
public GreaterOrEqualExpression(Field field, object value)
{
Field = field ?? throw new ArgumentNullException(nameof(field));
diff --git a/storage/src/Storage/Internal/Querying/Expressions/GreaterThanExpression.cs b/storage/src/Storage/Internal/Querying/Expressions/GreaterThanExpression.cs
index 2cdf2f3e2..875f55950 100644
--- a/storage/src/Storage/Internal/Querying/Expressions/GreaterThanExpression.cs
+++ b/storage/src/Storage/Internal/Querying/Expressions/GreaterThanExpression.cs
@@ -8,11 +8,22 @@ namespace Duende.Storage.Internal.Querying.Expressions;
///
/// Expression that checks if a field is greater than a specified value.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record GreaterThanExpression : IQueryFilterExpression
{
+ /// Gets the field to compare.
public Field Field { get; }
+
+ /// Gets the value to compare against.
public object Value { get; }
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The field to compare.
+ /// The value to compare against.
public GreaterThanExpression(Field field, object value)
{
Field = field ?? throw new ArgumentNullException(nameof(field));
diff --git a/storage/src/Storage/Internal/Querying/Expressions/InExpression.cs b/storage/src/Storage/Internal/Querying/Expressions/InExpression.cs
index db5a5e09d..b72a8a388 100644
--- a/storage/src/Storage/Internal/Querying/Expressions/InExpression.cs
+++ b/storage/src/Storage/Internal/Querying/Expressions/InExpression.cs
@@ -9,11 +9,22 @@ namespace Duende.Storage.Internal.Querying.Expressions;
///
/// Expression that checks if a field value is in a specified collection.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record InExpression : IQueryFilterExpression
{
+ /// Gets the field to check.
public Field Field { get; }
+
+ /// Gets the collection of values to match against.
public IEnumerable Values { get; }
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The field to check.
+ /// The collection of values.
public InExpression(Field field, IEnumerable values)
{
Field = field ?? throw new ArgumentNullException(nameof(field));
diff --git a/storage/src/Storage/Internal/Querying/Expressions/LessOrEqualExpression.cs b/storage/src/Storage/Internal/Querying/Expressions/LessOrEqualExpression.cs
index b540a70bf..68f7e4dfd 100644
--- a/storage/src/Storage/Internal/Querying/Expressions/LessOrEqualExpression.cs
+++ b/storage/src/Storage/Internal/Querying/Expressions/LessOrEqualExpression.cs
@@ -8,11 +8,22 @@ namespace Duende.Storage.Internal.Querying.Expressions;
///
/// Expression that checks if a field is less than or equal to a specified value.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record LessOrEqualExpression : IQueryFilterExpression
{
+ /// Gets the field to compare.
public Field Field { get; }
+
+ /// Gets the value to compare against.
public object Value { get; }
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The field to compare.
+ /// The value to compare against.
public LessOrEqualExpression(Field field, object value)
{
Field = field ?? throw new ArgumentNullException(nameof(field));
diff --git a/storage/src/Storage/Internal/Querying/Expressions/LessThanExpression.cs b/storage/src/Storage/Internal/Querying/Expressions/LessThanExpression.cs
index 28d266330..0b4a37d05 100644
--- a/storage/src/Storage/Internal/Querying/Expressions/LessThanExpression.cs
+++ b/storage/src/Storage/Internal/Querying/Expressions/LessThanExpression.cs
@@ -8,11 +8,22 @@ namespace Duende.Storage.Internal.Querying.Expressions;
///
/// Expression that checks if a field is less than a specified value.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record LessThanExpression : IQueryFilterExpression
{
+ /// Gets the field to compare.
public Field Field { get; }
+
+ /// Gets the value to compare against.
public object Value { get; }
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The field to compare.
+ /// The value to compare against.
public LessThanExpression(Field field, object value)
{
Field = field ?? throw new ArgumentNullException(nameof(field));
diff --git a/storage/src/Storage/Internal/Querying/Expressions/NotExpression.cs b/storage/src/Storage/Internal/Querying/Expressions/NotExpression.cs
index b750135fa..0947d7c7f 100644
--- a/storage/src/Storage/Internal/Querying/Expressions/NotExpression.cs
+++ b/storage/src/Storage/Internal/Querying/Expressions/NotExpression.cs
@@ -7,6 +7,9 @@ namespace Duende.Storage.Internal.Querying.Expressions;
/// Expression that negates an inner filter expression.
/// Used for SCIM 'not' operator and 'ne' (as Not(Equal(...))).
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record NotExpression : IQueryFilterExpression
{
///
@@ -14,6 +17,10 @@ public sealed record NotExpression : IQueryFilterExpression
///
public IQueryFilterExpression Inner { get; }
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The expression to negate.
public NotExpression(IQueryFilterExpression inner) =>
Inner = inner ?? throw new ArgumentNullException(nameof(inner));
}
diff --git a/storage/src/Storage/Internal/Querying/Expressions/OrExpression.cs b/storage/src/Storage/Internal/Querying/Expressions/OrExpression.cs
index 6136ae3f5..a55694973 100644
--- a/storage/src/Storage/Internal/Querying/Expressions/OrExpression.cs
+++ b/storage/src/Storage/Internal/Querying/Expressions/OrExpression.cs
@@ -7,6 +7,9 @@ namespace Duende.Storage.Internal.Querying.Expressions;
/// Expression that combines multiple filter expressions with OR logic.
/// At least one condition must be true for the expression to match.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record OrExpression : IQueryFilterExpression
{
///
@@ -14,6 +17,10 @@ public sealed record OrExpression : IQueryFilterExpression
///
public IReadOnlyList Parts { get; }
+ ///
+ /// Initializes a new instance of with the specified parts.
+ ///
+ /// The filter expressions to combine with OR logic.
public OrExpression(IReadOnlyList parts)
{
ArgumentNullException.ThrowIfNull(parts);
@@ -26,6 +33,10 @@ public sealed record OrExpression : IQueryFilterExpression
Parts = parts;
}
+ ///
+ /// Initializes a new instance of with the specified parts.
+ ///
+ /// The filter expressions to combine with OR logic.
public OrExpression(params IQueryFilterExpression[] parts)
: this((IReadOnlyList)parts)
{
diff --git a/storage/src/Storage/Internal/Querying/Expressions/PresentExpression.cs b/storage/src/Storage/Internal/Querying/Expressions/PresentExpression.cs
index 6f27c3cfa..99bebce1d 100644
--- a/storage/src/Storage/Internal/Querying/Expressions/PresentExpression.cs
+++ b/storage/src/Storage/Internal/Querying/Expressions/PresentExpression.cs
@@ -11,10 +11,18 @@ namespace Duende.Storage.Internal.Querying.Expressions;
/// For scalar fields, checks that a search_values row exists with a non-null typed column.
/// For array fields, checks that at least one row with item_index >= 0 exists.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record PresentExpression : IQueryFilterExpression
{
+ /// Gets the field to check for presence.
public Field Field { get; }
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The field to check for presence.
public PresentExpression(Field field) =>
Field = field ?? throw new ArgumentNullException(nameof(field));
}
diff --git a/storage/src/Storage/Internal/Querying/Expressions/StartsWithExpression.cs b/storage/src/Storage/Internal/Querying/Expressions/StartsWithExpression.cs
index e7626191f..e95d23c7a 100644
--- a/storage/src/Storage/Internal/Querying/Expressions/StartsWithExpression.cs
+++ b/storage/src/Storage/Internal/Querying/Expressions/StartsWithExpression.cs
@@ -8,11 +8,22 @@ namespace Duende.Storage.Internal.Querying.Expressions;
///
/// Expression that checks if a string field starts with a specified value.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record StartsWithExpression : IQueryFilterExpression
{
+ /// Gets the string field to check.
public StringField Field { get; }
+
+ /// Gets the prefix value.
public string Value { get; }
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The string field to check.
+ /// The prefix to match.
public StartsWithExpression(StringField field, string value)
{
Field = field ?? throw new ArgumentNullException(nameof(field));
diff --git a/storage/src/Storage/Internal/Querying/Fields/BooleanField.cs b/storage/src/Storage/Internal/Querying/Fields/BooleanField.cs
index d6671ffd6..ec49fa8c8 100644
--- a/storage/src/Storage/Internal/Querying/Fields/BooleanField.cs
+++ b/storage/src/Storage/Internal/Querying/Fields/BooleanField.cs
@@ -8,8 +8,16 @@ namespace Duende.Storage.Internal.Querying.Fields;
///
/// Represents a boolean-valued field with boolean-specific operations.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record BooleanField : Field
{
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The field path.
+ /// Whether the field is multi-valued.
public BooleanField(string path, bool isMultiValued = false) : base(path, FieldType.Boolean, isMultiValued)
{
}
diff --git a/storage/src/Storage/Internal/Querying/Fields/DateTimeField.cs b/storage/src/Storage/Internal/Querying/Fields/DateTimeField.cs
index 76490ce99..27de228f9 100644
--- a/storage/src/Storage/Internal/Querying/Fields/DateTimeField.cs
+++ b/storage/src/Storage/Internal/Querying/Fields/DateTimeField.cs
@@ -8,8 +8,16 @@ namespace Duende.Storage.Internal.Querying.Fields;
///
/// Represents a DateTimeOffset-valued field with temporal comparison operations.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record DateTimeField : Field
{
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The field path.
+ /// Whether the field is multi-valued.
public DateTimeField(string path, bool isMultiValued = false) : base(path, FieldType.DateTime, isMultiValued)
{
}
diff --git a/storage/src/Storage/Internal/Querying/Fields/ExactMatchField.cs b/storage/src/Storage/Internal/Querying/Fields/ExactMatchField.cs
index e0d43486e..728480f83 100644
--- a/storage/src/Storage/Internal/Querying/Fields/ExactMatchField.cs
+++ b/storage/src/Storage/Internal/Querying/Fields/ExactMatchField.cs
@@ -11,8 +11,15 @@ namespace Duende.Storage.Internal.Querying.Fields;
/// Queries use the guid_value column with hashed values for fast lookups.
/// No string_value is stored — only the deterministic hash in guid_value.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record ExactMatchField : Field
{
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The field path.
public ExactMatchField(string path) : base(path, FieldType.Guid)
{
}
diff --git a/storage/src/Storage/Internal/Querying/Fields/Field.cs b/storage/src/Storage/Internal/Querying/Fields/Field.cs
index edd624a43..1c6e85b67 100644
--- a/storage/src/Storage/Internal/Querying/Fields/Field.cs
+++ b/storage/src/Storage/Internal/Querying/Fields/Field.cs
@@ -8,6 +8,9 @@ namespace Duende.Storage.Internal.Querying.Fields;
///
/// Base class for all field types, representing a queryable field path.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public abstract record Field
{
///
@@ -28,6 +31,12 @@ public abstract record Field
///
public bool IsMultiValued { get; }
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The field path.
+ /// The field type.
+ /// Whether the field is multi-valued.
protected Field(string path, FieldType type, bool isMultiValued = false)
{
if (string.IsNullOrWhiteSpace(path))
diff --git a/storage/src/Storage/Internal/Querying/Fields/GuidField.cs b/storage/src/Storage/Internal/Querying/Fields/GuidField.cs
index 6932e0c34..301b17692 100644
--- a/storage/src/Storage/Internal/Querying/Fields/GuidField.cs
+++ b/storage/src/Storage/Internal/Querying/Fields/GuidField.cs
@@ -8,8 +8,16 @@ namespace Duende.Storage.Internal.Querying.Fields;
///
/// Represents a Guid-valued field stored in the guid_value column.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record GuidField : Field
{
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The field path.
+ /// Whether the field is multi-valued.
public GuidField(string path, bool isMultiValued = false) : base(path, FieldType.Guid, isMultiValued)
{
}
diff --git a/storage/src/Storage/Internal/Querying/Fields/NumberField.cs b/storage/src/Storage/Internal/Querying/Fields/NumberField.cs
index c1e5fe2fc..7e0f38258 100644
--- a/storage/src/Storage/Internal/Querying/Fields/NumberField.cs
+++ b/storage/src/Storage/Internal/Querying/Fields/NumberField.cs
@@ -8,8 +8,16 @@ namespace Duende.Storage.Internal.Querying.Fields;
///
/// Represents a number-valued field with numeric comparison operations.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record NumberField : Field
{
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The field path.
+ /// Whether the field is multi-valued.
public NumberField(string path, bool isMultiValued = false) : base(path, FieldType.Number, isMultiValued)
{
}
diff --git a/storage/src/Storage/Internal/Querying/Fields/StringArrayField.cs b/storage/src/Storage/Internal/Querying/Fields/StringArrayField.cs
index 1306f25da..1850572e8 100644
--- a/storage/src/Storage/Internal/Querying/Fields/StringArrayField.cs
+++ b/storage/src/Storage/Internal/Querying/Fields/StringArrayField.cs
@@ -9,8 +9,15 @@ namespace Duende.Storage.Internal.Querying.Fields;
/// Represents a string array field (multi-valued string).
/// This is a convenience subclass of with set to true.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record StringArrayField : Field
{
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The field path.
public StringArrayField(string path) : base(path, FieldType.String, isMultiValued: true)
{
}
diff --git a/storage/src/Storage/Internal/Querying/Fields/StringField.cs b/storage/src/Storage/Internal/Querying/Fields/StringField.cs
index b4e7d9669..e66e795f6 100644
--- a/storage/src/Storage/Internal/Querying/Fields/StringField.cs
+++ b/storage/src/Storage/Internal/Querying/Fields/StringField.cs
@@ -8,8 +8,16 @@ namespace Duende.Storage.Internal.Querying.Fields;
///
/// Represents a string-valued field with string-specific operations.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record StringField : Field
{
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The field path.
+ /// Whether the field is multi-valued.
public StringField(string path, bool isMultiValued = false) : base(path, FieldType.String, isMultiValued)
{
}
diff --git a/storage/src/Storage/Internal/Querying/IQueryExpression.cs b/storage/src/Storage/Internal/Querying/IQueryExpression.cs
index 0bf1b8cf2..36bfe0537 100644
--- a/storage/src/Storage/Internal/Querying/IQueryExpression.cs
+++ b/storage/src/Storage/Internal/Querying/IQueryExpression.cs
@@ -6,6 +6,9 @@ namespace Duende.Storage.Internal.Querying;
///
/// Base marker interface for all query expressions.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public interface IQueryExpression
{
}
diff --git a/storage/src/Storage/Internal/Querying/IQueryExpressionVisitor.cs b/storage/src/Storage/Internal/Querying/IQueryExpressionVisitor.cs
index 5393443b6..aab194665 100644
--- a/storage/src/Storage/Internal/Querying/IQueryExpressionVisitor.cs
+++ b/storage/src/Storage/Internal/Querying/IQueryExpressionVisitor.cs
@@ -9,6 +9,9 @@ namespace Duende.Storage.Internal.Querying;
/// Visitor interface for processing query expression trees.
/// Implementations can translate expressions to different formats (e.g., SQL, in-memory evaluation).
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
/// The type returned by visiting an expression.
public interface IQueryExpressionVisitor
{
diff --git a/storage/src/Storage/Internal/Querying/IQueryFilterExpression.cs b/storage/src/Storage/Internal/Querying/IQueryFilterExpression.cs
index 4bf278a13..a2b6056b3 100644
--- a/storage/src/Storage/Internal/Querying/IQueryFilterExpression.cs
+++ b/storage/src/Storage/Internal/Querying/IQueryFilterExpression.cs
@@ -6,6 +6,9 @@ namespace Duende.Storage.Internal.Querying;
///
/// Marker interface for filter expressions that can be used in WHERE clauses.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public interface IQueryFilterExpression : IQueryExpression
{
}
diff --git a/storage/src/Storage/Internal/Querying/ISqlDialect.cs b/storage/src/Storage/Internal/Querying/ISqlDialect.cs
index 7cee0e584..be21e9cb9 100644
--- a/storage/src/Storage/Internal/Querying/ISqlDialect.cs
+++ b/storage/src/Storage/Internal/Querying/ISqlDialect.cs
@@ -8,7 +8,10 @@ namespace Duende.Storage.Internal.Querying;
///
/// Defines SQL dialect-specific behavior for query building.
///
-public interface ISqlDialect
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
+internal interface ISqlDialect
{
///
/// The LIKE operator for case-insensitive matching.
diff --git a/storage/src/Storage/Internal/Querying/LinkQuery.cs b/storage/src/Storage/Internal/Querying/LinkQuery.cs
index 23c1d9089..b9f4adc51 100644
--- a/storage/src/Storage/Internal/Querying/LinkQuery.cs
+++ b/storage/src/Storage/Internal/Querying/LinkQuery.cs
@@ -6,6 +6,9 @@ namespace Duende.Storage.Internal.Querying;
///
/// Fluent entry point for building link traversal queries.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
///
///
/// var query = LinkQuery.From(userEntityType)
diff --git a/storage/src/Storage/Internal/Querying/LinkQueryBuilder.cs b/storage/src/Storage/Internal/Querying/LinkQueryBuilder.cs
index bcae2c712..1ef24646c 100644
--- a/storage/src/Storage/Internal/Querying/LinkQueryBuilder.cs
+++ b/storage/src/Storage/Internal/Querying/LinkQueryBuilder.cs
@@ -6,6 +6,9 @@ namespace Duende.Storage.Internal.Querying;
///
/// Builds a using a fluent API.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed class LinkQueryBuilder
{
private readonly EntityType _source;
diff --git a/storage/src/Storage/Internal/Querying/LinkQueryDescriptor.cs b/storage/src/Storage/Internal/Querying/LinkQueryDescriptor.cs
index 6df7748af..328e13724 100644
--- a/storage/src/Storage/Internal/Querying/LinkQueryDescriptor.cs
+++ b/storage/src/Storage/Internal/Querying/LinkQueryDescriptor.cs
@@ -7,6 +7,9 @@ namespace Duende.Storage.Internal.Querying;
/// Describes a link traversal query — built by LinkQueryBuilder and
/// consumed by IQueryStore.QueryLinks.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record LinkQueryDescriptor(
EntityType SourceEntityType,
IReadOnlyList Joins,
@@ -17,6 +20,9 @@ public sealed record LinkQueryDescriptor(
/// A single hop in a link query chain, pairing a
/// with the direction it is traversed.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record LinkQueryJoin(
LinkDefinition Definition,
LinkJoinDirection Direction);
@@ -24,6 +30,9 @@ public sealed record LinkQueryJoin(
///
/// Direction of a link traversal join.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public enum LinkJoinDirection
{
/// Traverse from the left entity to the right entity.
diff --git a/storage/src/Storage/Internal/Querying/MetadataEnvelope.cs b/storage/src/Storage/Internal/Querying/MetadataEnvelope.cs
index 36e8d4665..52b576d1e 100644
--- a/storage/src/Storage/Internal/Querying/MetadataEnvelope.cs
+++ b/storage/src/Storage/Internal/Querying/MetadataEnvelope.cs
@@ -6,6 +6,9 @@ namespace Duende.Storage.Internal.Querying;
///
/// Wraps a query result item with entity metadata: id, version, and timestamps.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
/// The type of the wrapped value.
public sealed record MetadataEnvelope(
TValue Value,
diff --git a/storage/src/Storage/Internal/Querying/ProjectedResult.cs b/storage/src/Storage/Internal/Querying/ProjectedResult.cs
index b9fa5dbaf..810fbe43f 100644
--- a/storage/src/Storage/Internal/Querying/ProjectedResult.cs
+++ b/storage/src/Storage/Internal/Querying/ProjectedResult.cs
@@ -6,6 +6,9 @@ namespace Duende.Storage.Internal.Querying;
///
/// Represents a query result with only selected field values rather than the full entity.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record ProjectedResult
{
///
diff --git a/storage/src/Storage/Internal/Querying/Query.cs b/storage/src/Storage/Internal/Querying/Query.cs
index 6730ee407..b3f8665fe 100644
--- a/storage/src/Storage/Internal/Querying/Query.cs
+++ b/storage/src/Storage/Internal/Querying/Query.cs
@@ -9,6 +9,9 @@ namespace Duende.Storage.Internal.Querying;
/// Static entry point for building query expressions.
/// Provides a fluent API for constructing query filter expressions.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public static class Query
{
///
diff --git a/storage/src/Storage/Internal/Querying/QueryFilterExpressionExtensions.cs b/storage/src/Storage/Internal/Querying/QueryFilterExpressionExtensions.cs
index 5bec1f5cb..8ccba5d2f 100644
--- a/storage/src/Storage/Internal/Querying/QueryFilterExpressionExtensions.cs
+++ b/storage/src/Storage/Internal/Querying/QueryFilterExpressionExtensions.cs
@@ -8,6 +8,9 @@ namespace Duende.Storage.Internal.Querying;
///
/// Extension methods for fluent composition of query filter expressions.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public static class QueryFilterExpressionExtensions
{
///
diff --git a/storage/src/Storage/Internal/Querying/SearchFields/SearchFieldCollection.cs b/storage/src/Storage/Internal/Querying/SearchFields/SearchFieldCollection.cs
index 9cdb28669..7add87a21 100644
--- a/storage/src/Storage/Internal/Querying/SearchFields/SearchFieldCollection.cs
+++ b/storage/src/Storage/Internal/Querying/SearchFields/SearchFieldCollection.cs
@@ -10,6 +10,9 @@ namespace Duende.Storage.Internal.Querying.SearchFields;
/// Immutable collection of search field values that can be passed to IStore.Create/Update methods.
/// Use to construct instances.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
[CollectionBuilder(typeof(SearchFieldCollection), nameof(Create))]
public sealed class SearchFieldCollection(IReadOnlyList values) : IReadOnlyCollection
{
@@ -30,5 +33,10 @@ public sealed class SearchFieldCollection(IReadOnlyList values
///
public static SearchFieldCollection Empty { get; } = new(Array.Empty());
+ ///
+ /// Creates a from a span of values.
+ ///
+ /// The values to include.
+ /// A new .
public static SearchFieldCollection Create(ReadOnlySpan values) => new(values.ToArray());
}
diff --git a/storage/src/Storage/Internal/Querying/SearchFields/SearchFieldValue.cs b/storage/src/Storage/Internal/Querying/SearchFields/SearchFieldValue.cs
index cbe17ca06..977c036f4 100644
--- a/storage/src/Storage/Internal/Querying/SearchFields/SearchFieldValue.cs
+++ b/storage/src/Storage/Internal/Querying/SearchFields/SearchFieldValue.cs
@@ -8,6 +8,8 @@ namespace Duende.Storage.Internal.Querying.SearchFields;
/// This is a pure data structure for Entity-Attribute-Value (EAV) storage pattern.
///
///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
/// For string fields, both StringValue and GuidValue are set — StringValue holds the raw (uppercased)
/// string for LIKE queries, and GuidValue holds a deterministic hash for fast exact-match queries.
/// For GuidField and ExactMatchField, only GuidValue is set.
diff --git a/storage/src/Storage/Internal/Querying/SearchFields/SearchFieldsBuilder.cs b/storage/src/Storage/Internal/Querying/SearchFields/SearchFieldsBuilder.cs
index 284d61d75..fc944408f 100644
--- a/storage/src/Storage/Internal/Querying/SearchFields/SearchFieldsBuilder.cs
+++ b/storage/src/Storage/Internal/Querying/SearchFields/SearchFieldsBuilder.cs
@@ -7,6 +7,9 @@ namespace Duende.Storage.Internal.Querying.SearchFields;
/// Builder for constructing collections.
/// Provides helper methods for adding scalar values, nested values, and array item values.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed class SearchFieldsBuilder
{
private readonly List _values = new();
diff --git a/storage/src/Storage/Internal/Querying/Sorting/SortParameter.cs b/storage/src/Storage/Internal/Querying/Sorting/SortParameter.cs
index d5c229314..6f5662d4b 100644
--- a/storage/src/Storage/Internal/Querying/Sorting/SortParameter.cs
+++ b/storage/src/Storage/Internal/Querying/Sorting/SortParameter.cs
@@ -10,6 +10,9 @@ namespace Duende.Storage.Internal.Querying.Sorting;
///
/// Specifies a field and direction for sorting query results.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record SortParameter
{
///
diff --git a/storage/src/Storage/Internal/Querying/SystemFields.cs b/storage/src/Storage/Internal/Querying/SystemFields.cs
index dfa9f3774..486686b38 100644
--- a/storage/src/Storage/Internal/Querying/SystemFields.cs
+++ b/storage/src/Storage/Internal/Querying/SystemFields.cs
@@ -9,6 +9,9 @@ namespace Duende.Storage.Internal.Querying;
/// Constants for system field paths that map to entity-level columns
/// rather than EAV search values.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public static class SystemFields
{
///
diff --git a/storage/src/Storage/Internal/StorageBuilderExtensions.cs b/storage/src/Storage/Internal/StorageBuilderExtensions.cs
index 15319f3cc..c1606bf4d 100644
--- a/storage/src/Storage/Internal/StorageBuilderExtensions.cs
+++ b/storage/src/Storage/Internal/StorageBuilderExtensions.cs
@@ -5,6 +5,12 @@ using Microsoft.Extensions.DependencyInjection;
namespace Duende.Storage.Internal;
+///
+/// Extension methods for configuring storage services.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public static class StorageBuilderExtensions
{
extension(IServiceCollection services)
diff --git a/storage/src/Storage/Internal/Telemetry/StorageMetrics.cs b/storage/src/Storage/Internal/Telemetry/StorageMetrics.cs
index 53a17b659..e208cc3f1 100644
--- a/storage/src/Storage/Internal/Telemetry/StorageMetrics.cs
+++ b/storage/src/Storage/Internal/Telemetry/StorageMetrics.cs
@@ -8,12 +8,18 @@ namespace Duende.Storage.Internal.Telemetry;
///
/// Records storage metrics using System.Diagnostics.Metrics.
///
-public sealed class StorageMetrics : IDisposable
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
+internal sealed class StorageMetrics : IDisposable
{
private readonly Meter _meter;
private readonly Counter _operationCounter;
private readonly Histogram _operationDuration;
+ ///
+ /// Initializes a new instance of .
+ ///
public StorageMetrics()
{
_meter = new Meter(StorageTelemetryConstants.MeterName, StorageTracing.ServiceVersion);
@@ -26,6 +32,12 @@ public sealed class StorageMetrics : IDisposable
description: "Duration of storage operations in seconds.");
}
+ ///
+ /// Records a successful operation.
+ ///
+ /// The operation name.
+ /// The database system.
+ /// The entity type name, or null.
public void RecordSuccess(string operation, string dbSystem, string? entityType)
{
if (entityType != null)
@@ -45,6 +57,13 @@ public sealed class StorageMetrics : IDisposable
}
}
+ ///
+ /// Records a failed operation.
+ ///
+ /// The operation name.
+ /// The database system.
+ /// The exception that occurred.
+ /// The entity type name, or null.
public void RecordError(string operation, string dbSystem, Exception ex, string? entityType)
{
if (entityType != null)
@@ -66,6 +85,14 @@ public sealed class StorageMetrics : IDisposable
}
}
+ ///
+ /// Records operation duration.
+ ///
+ /// The operation name.
+ /// The duration in seconds.
+ /// The database system.
+ /// The result tag value (success or error).
+ /// The entity type name, or null.
public void RecordDuration(string operation, double durationSeconds, string dbSystem, string result, string? entityType)
{
if (entityType != null)
@@ -85,5 +112,6 @@ public sealed class StorageMetrics : IDisposable
}
}
+ ///
public void Dispose() => _meter.Dispose();
}
diff --git a/storage/src/Storage/Internal/Telemetry/StorageTelemetryConstants.cs b/storage/src/Storage/Internal/Telemetry/StorageTelemetryConstants.cs
index f23b09480..1a6446a1d 100644
--- a/storage/src/Storage/Internal/Telemetry/StorageTelemetryConstants.cs
+++ b/storage/src/Storage/Internal/Telemetry/StorageTelemetryConstants.cs
@@ -6,58 +6,120 @@ namespace Duende.Storage.Internal.Telemetry;
///
/// Constants for storage telemetry tag keys, values, and instrument names.
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public static class StorageTelemetryConstants
{
+ /// The meter name for storage telemetry.
public const string MeterName = "Duende.Storage";
+ /// Instrument names for storage metrics.
public static class Instruments
{
+ /// Counter for storage operation count.
public const string OperationCount = "duende.storage.operation.count";
+
+ /// Histogram for storage operation duration.
public const string OperationDuration = "duende.storage.operation.duration";
}
+ /// Tag keys for storage telemetry.
public static class Tags
{
+ /// The operation being performed.
public const string Operation = "duende.storage.operation";
+
+ /// The database system.
public const string DbSystem = "db.system";
+
+ /// The entity type.
public const string EntityType = "duende.storage.entity_type";
+
+ /// The operation result.
public const string Result = "duende.storage.result";
+
+ /// The error type.
public const string ErrorType = "error.type";
}
+ /// Tag values for storage telemetry.
public static class TagValues
{
+ /// Successful operation.
public const string Success = "success";
+
+ /// Failed operation.
public const string Error = "error";
+
+ /// Unknown result.
public const string Unknown = "unknown";
}
+ /// Database provider identifiers.
public static class DatabaseProviders
{
+ /// Microsoft SQL Server.
public const string MsSql = "mssql";
+
+ /// PostgreSQL.
public const string PostgreSql = "postgresql";
+
+ /// SQLite.
public const string Sqlite = "sqlite";
+
+ /// In-memory store.
public const string InMemory = "in_memory";
}
+ /// Operation name constants.
#pragma warning disable CA1724 // Type name conflicts with namespace
public static class Operations
#pragma warning restore CA1724
{
+ /// Create operation.
public const string Create = "create";
+
+ /// Read operation.
public const string Read = "read";
+
+ /// Read many operation.
public const string ReadMany = "read_many";
+
+ /// Update operation.
public const string Update = "update";
+
+ /// Delete operation.
public const string Delete = "delete";
+
+ /// Link operation.
public const string Link = "link";
+
+ /// Unlink operation.
public const string Unlink = "unlink";
+
+ /// Purge expired entities operation.
public const string PurgeExpired = "purge_expired";
+
+ /// Batch operation.
public const string Batch = "batch";
+
+ /// Get outbox events operation.
public const string OutboxGet = "outbox_get";
+
+ /// Delete outbox events operation.
public const string OutboxDelete = "outbox_delete";
+
+ /// Query operation.
public const string Query = "query";
+
+ /// Query fields operation.
public const string QueryFields = "query_fields";
+
+ /// Query links operation.
public const string QueryLinks = "query_links";
+
+ /// Count operation.
public const string Count = "count";
}
}
diff --git a/storage/src/Storage/Internal/TypedDso.cs b/storage/src/Storage/Internal/TypedDso.cs
index 382659369..11fe75b59 100644
--- a/storage/src/Storage/Internal/TypedDso.cs
+++ b/storage/src/Storage/Internal/TypedDso.cs
@@ -3,8 +3,20 @@
namespace Duende.Storage.Internal;
+///
+/// Wraps a typed DSO value with its entity type and version metadata.
+///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public sealed record TypedDso
{
+ ///
+ /// Creates a from a typed DSO value.
+ ///
+ /// The DSO type.
+ /// The DSO value.
+ /// A wrapping the value with its metadata.
public static TypedDso For(TDso value) where TDso : IDataStorageObject => new TypedDso
{
Value = value,
@@ -12,7 +24,18 @@ public sealed record TypedDso
Version = TDso.DsoVersion
};
+ ///
+ /// Gets the DSO value.
+ ///
public required IDataStorageObject Value { get; init; }
+
+ ///
+ /// Gets the entity type.
+ ///
public required EntityType EntityType { get; init; }
+
+ ///
+ /// Gets the DSO version.
+ ///
public required DataStorageObjectVersion Version { get; init; }
}
diff --git a/storage/src/Storage/Internal/UnlinkResult.cs b/storage/src/Storage/Internal/UnlinkResult.cs
index c1d08943a..a08239a3c 100644
--- a/storage/src/Storage/Internal/UnlinkResult.cs
+++ b/storage/src/Storage/Internal/UnlinkResult.cs
@@ -6,6 +6,9 @@ namespace Duende.Storage.Internal;
///
/// The result of an Unlink operation on .
///
+///
+/// This type is for usage by Duende Software products, is not supported for end user consumption, and not subject to semantic versioning rules.
+///
public enum UnlinkResult
{
/// The link was removed, or did not exist (idempotent).
diff --git a/storage/src/Storage/Pagination/ContinuationToken.cs b/storage/src/Storage/Pagination/ContinuationToken.cs
index 6bf392bf4..2c9a2c883 100644
--- a/storage/src/Storage/Pagination/ContinuationToken.cs
+++ b/storage/src/Storage/Pagination/ContinuationToken.cs
@@ -12,5 +12,6 @@ namespace Duende.Storage.Pagination;
[StringValue]
public partial record ContinuationToken
{
+ /// The token value representing the beginning of the dataset.
public const string Beginning = "Beginning";
}
diff --git a/storage/src/Storage/Pagination/DataRange.cs b/storage/src/Storage/Pagination/DataRange.cs
index 3278364c2..9b02e748f 100644
--- a/storage/src/Storage/Pagination/DataRange.cs
+++ b/storage/src/Storage/Pagination/DataRange.cs
@@ -16,10 +16,13 @@ public sealed class DataRange
{
}
+ /// The continuation-token-based pagination value, if set.
public ContinuationTokenDataRange? TokenValue { get; private init; }
+ /// The offset-based pagination value, if set.
public OffsetDataRange? OffsetValue { get; private init; }
+ /// The page-number-based pagination value, if set.
public PagedDataRange? PageValue { get; private init; }
///
@@ -72,11 +75,20 @@ public sealed class DataRange
PageValue = page
};
+ ///
+ /// Creates a from offset-based pagination.
+ ///
+ /// The number of items to skip.
public static DataRange FromOffset(OffsetSkip? skip) => new()
{
OffsetValue = new OffsetDataRange(skip, null)
};
+ ///
+ /// Creates a from offset-based pagination.
+ ///
+ /// The number of items to skip.
+ /// The number of items to take.
public static DataRange FromOffset(OffsetSkip? skip, DataRangeSize? size) => new()
{
OffsetValue = new OffsetDataRange(skip, size)
diff --git a/storage/src/Storage/Pagination/OffsetSkip.cs b/storage/src/Storage/Pagination/OffsetSkip.cs
index 0c245b732..50aad1371 100644
--- a/storage/src/Storage/Pagination/OffsetSkip.cs
+++ b/storage/src/Storage/Pagination/OffsetSkip.cs
@@ -4,8 +4,7 @@
namespace Duende.Storage.Pagination;
///
-/// A starting position for pagination. Represents a 0-based row offset or a 1-based page number,
-/// depending on context.
+/// A validated 0-based row offset for offset-based pagination.
///
[ValueOf]
public partial record OffsetSkip
diff --git a/storage/src/Storage/Pagination/PageNumber.cs b/storage/src/Storage/Pagination/PageNumber.cs
index 328f9bbed..9dd897831 100644
--- a/storage/src/Storage/Pagination/PageNumber.cs
+++ b/storage/src/Storage/Pagination/PageNumber.cs
@@ -4,8 +4,7 @@
namespace Duende.Storage.Pagination;
///
-/// A starting position for pagination. Represents a 0-based row offset or a 1-based page number,
-/// depending on context.
+/// A validated 1-based page number for page-number-based pagination.
///
[ValueOf]
public partial record PageNumber
diff --git a/storage/src/Storage/Querying/FilterBy.cs b/storage/src/Storage/Querying/FilterBy.cs
index 43e2a996a..89587bb79 100644
--- a/storage/src/Storage/Querying/FilterBy.cs
+++ b/storage/src/Storage/Querying/FilterBy.cs
@@ -14,6 +14,7 @@ public sealed class FilterBy
{
}
+ /// The search expression value, if set.
public SearchExpression? SearchExpressionValue { get; init; }
///
@@ -49,6 +50,7 @@ public sealed class FilterBy
internal SearchExpression? SearchExpressionValue { get; init; }
+ /// The typed filter value, if set.
public T? FilterValue { get; init; }
///
diff --git a/storage/src/Storage/Querying/QueryRequest.cs b/storage/src/Storage/Querying/QueryRequest.cs
index 0139a4f53..5ffb8cf53 100644
--- a/storage/src/Storage/Querying/QueryRequest.cs
+++ b/storage/src/Storage/Querying/QueryRequest.cs
@@ -22,41 +22,61 @@ public sealed record QueryRequest
/// Optional pagination parameters.
public DataRange? Range { get; init; }
+ /// Creates an empty query request with no filter, sort, or range.
public static QueryRequest Create() => Empty;
+ /// Creates a query request with the specified filter.
+ /// The filter criteria.
public static QueryRequest Create(FilterBy filter) => new()
{
Filter = filter
};
+ /// Creates a query request with the specified sort.
+ /// The sort criteria.
public static QueryRequest Create(SortBy sort) => new()
{
Sort = sort
};
+ /// Creates a query request with the specified pagination range.
+ /// The pagination parameters.
public static QueryRequest Create(DataRange range) => new()
{
Range = range
};
+ /// Creates a query request with the specified filter and sort.
+ /// The filter criteria.
+ /// The sort criteria.
public static QueryRequest Create(FilterBy filter, SortBy sort) => new()
{
Filter = filter,
Sort = sort
};
+ /// Creates a query request with the specified filter and pagination range.
+ /// The filter criteria.
+ /// The pagination parameters.
public static QueryRequest Create(FilterBy filter, DataRange range) => new()
{
Filter = filter,
Range = range
};
+ /// Creates a query request with the specified sort and pagination range.
+ /// The sort criteria.
+ /// The pagination parameters.
public static QueryRequest Create(SortBy sort, DataRange range) => new()
{
Sort = sort,
Range = range
};
+ /// Creates a query request with the specified filter, sort, and pagination range.
+ /// The filter criteria.
+ /// The sort criteria.
+ /// The pagination parameters.
public static QueryRequest Create(FilterBy filter, SortBy sort, DataRange range) => new()
{
Filter = filter,
@@ -64,33 +84,57 @@ public sealed record QueryRequest
Range = range
};
+ /// Creates a typed query request with no filter, sort, or range.
+ /// The typed filter type.
+ /// The typed sort field enum.
public static QueryRequest Create()
where TSort : struct, Enum => new();
+ /// Creates a typed query request with the specified filter.
+ /// The typed filter type.
+ /// The typed sort field enum.
+ /// The typed filter value.
public static QueryRequest Create(TFilter? filter)
where TSort : struct, Enum => new()
{
Filter = CreateFilter(filter)
};
+ /// Creates a typed query request with the specified filter.
+ /// The typed filter type.
+ /// The typed sort field enum.
+ /// The filter criteria.
public static QueryRequest Create(FilterBy filter)
where TSort : struct, Enum => new()
{
Filter = filter
};
+ /// Creates a typed query request with the specified sort.
+ /// The typed filter type.
+ /// The typed sort field enum.
+ /// The sort criteria.
public static QueryRequest Create(SortBy.SortByField sort)
where TSort : struct, Enum => new()
{
Sort = sort
};
+ /// Creates a typed query request with the specified pagination range.
+ /// The typed filter type.
+ /// The typed sort field enum.
+ /// The pagination parameters.
public static QueryRequest Create(DataRange range)
where TSort : struct, Enum => new()
{
Range = range
};
+ /// Creates a typed query request with the specified filter and sort.
+ /// The typed filter type.
+ /// The typed sort field enum.
+ /// The typed filter value.
+ /// The sort criteria.
public static QueryRequest Create(TFilter? filter, SortBy.SortByField? sort)
where TSort : struct, Enum => new()
{
@@ -98,6 +142,11 @@ public sealed record QueryRequest
Sort = sort
};
+ /// Creates a typed query request with the specified filter and sort.
+ /// The typed filter type.
+ /// The typed sort field enum.
+ /// The filter criteria.
+ /// The sort criteria.
public static QueryRequest Create(FilterBy filter, SortBy.SortByField sort)
where TSort : struct, Enum => new()
{
@@ -105,6 +154,11 @@ public sealed record QueryRequest
Sort = sort
};
+ /// Creates a typed query request with the specified filter and pagination range.
+ /// The typed filter type.
+ /// The typed sort field enum.
+ /// The typed filter value.
+ /// The pagination parameters.
public static QueryRequest Create(TFilter? filter, DataRange? range)
where TSort : struct, Enum => new()
{
@@ -112,6 +166,11 @@ public sealed record QueryRequest
Range = range
};
+ /// Creates a typed query request with the specified filter and pagination range.
+ /// The typed filter type.
+ /// The typed sort field enum.
+ /// The filter criteria.
+ /// The pagination parameters.
public static QueryRequest Create(FilterBy filter, DataRange range)
where TSort : struct, Enum => new()
{
@@ -119,6 +178,11 @@ public sealed record QueryRequest
Range = range
};
+ /// Creates a typed query request with the specified sort and pagination range.
+ /// The typed filter type.
+ /// The typed sort field enum.
+ /// The sort criteria.
+ /// The pagination parameters.
public static QueryRequest Create(SortBy.SortByField? sort, DataRange? range)
where TSort : struct, Enum => new()
{
@@ -126,6 +190,12 @@ public sealed record QueryRequest
Range = range
};
+ /// Creates a typed query request with the specified filter, sort, and pagination range.
+ /// The typed filter type.
+ /// The typed sort field enum.
+ /// The typed filter value.
+ /// The sort criteria.
+ /// The pagination parameters.
public static QueryRequest Create(
TFilter? filter,
SortBy.SortByField? sort,
@@ -137,6 +207,12 @@ public sealed record QueryRequest
Range = range
};
+ /// Creates a typed query request with the specified filter, sort, and pagination range.
+ /// The typed filter type.
+ /// The typed sort field enum.
+ /// The filter criteria.
+ /// The sort criteria.
+ /// The pagination parameters.
public static QueryRequest Create(
FilterBy filter,
SortBy.SortByField sort,
diff --git a/storage/src/Storage/Querying/QueryResult.cs b/storage/src/Storage/Querying/QueryResult.cs
index 79af27fbf..c26110ae6 100644
--- a/storage/src/Storage/Querying/QueryResult.cs
+++ b/storage/src/Storage/Querying/QueryResult.cs
@@ -55,6 +55,12 @@ public sealed record QueryResult : IReadOnlyList
///
public TItem this[int index] => Items[index];
+ ///
+ /// Converts the items in this result to a different type using the specified conversion function.
+ ///
+ /// The target item type.
+ /// The function to convert each item.
+ /// A new with converted items and the same pagination metadata.
public QueryResult ConvertTo(Func convert) =>
new()
{
diff --git a/storage/src/Storage/SchemaVerificationError.cs b/storage/src/Storage/SchemaVerificationError.cs
index cf788bed4..2fd874167 100644
--- a/storage/src/Storage/SchemaVerificationError.cs
+++ b/storage/src/Storage/SchemaVerificationError.cs
@@ -3,17 +3,34 @@
namespace Duende.Storage;
+///
+/// Describes the kind of error found during schema verification.
+///
public enum SchemaVerificationErrorKind
{
+ /// A required table is missing from the database.
MissingTable,
+ /// A required column is missing from a table.
MissingColumn,
+ /// A column has an incorrect data type.
WrongType,
+ /// A required index is missing.
MissingIndex,
+ /// A required foreign key is missing.
MissingForeignKey,
+ /// A required user-defined type is missing.
MissingUserDefinedType,
+ /// An unclassified schema error.
Other
}
+///
+/// Represents an error found during schema verification.
+///
+/// The name of the table associated with the error.
+/// The name of the column associated with the error, or null if not column-specific.
+/// A description of the error.
+/// The kind of schema verification error.
public sealed record SchemaVerificationError(
string Table,
string? Column,
diff --git a/storage/src/Storage/SchemaVerificationResult.cs b/storage/src/Storage/SchemaVerificationResult.cs
index 171907edc..c863f9a64 100644
--- a/storage/src/Storage/SchemaVerificationResult.cs
+++ b/storage/src/Storage/SchemaVerificationResult.cs
@@ -3,7 +3,14 @@
namespace Duende.Storage;
+///
+/// Contains the results of a database schema verification.
+///
+/// The list of errors found during verification.
public sealed record SchemaVerificationResult(IReadOnlyList Errors)
{
+ ///
+ /// Gets a value indicating whether the schema is valid (no errors were found).
+ ///
public bool IsValid => Errors.Count == 0;
}
diff --git a/storage/src/Storage/Storage.csproj b/storage/src/Storage/Storage.csproj
index f624d3293..f63caf05d 100644
--- a/storage/src/Storage/Storage.csproj
+++ b/storage/src/Storage/Storage.csproj
@@ -6,6 +6,8 @@
Duende.StorageDuende.Storage$(NoWarn);CA1062
+ true
+ $(WarningsAsErrors);CS1591
@@ -24,6 +26,9 @@
+
+
+
diff --git a/storage/src/Storage/UuidV7.cs b/storage/src/Storage/UuidV7.cs
index 0880d2143..e42246ca6 100644
--- a/storage/src/Storage/UuidV7.cs
+++ b/storage/src/Storage/UuidV7.cs
@@ -3,6 +3,9 @@
namespace Duende.Storage;
+///
+/// A value object wrapping a version 7 UUID (time-ordered GUID).
+///
[ValueOf]
public partial record UuidV7
{
@@ -11,8 +14,19 @@ public partial record UuidV7
///
public static UuidV7 New() => new(Guid.CreateVersion7());
+ ///
+ /// Creates a from the specified value.
+ ///
+ /// The GUID value to wrap.
+ /// A new instance.
public static UuidV7 From(Guid value) => value;
+ ///
+ /// Validates that the specified GUID is a valid version 7 UUID.
+ ///
+ /// The GUID to validate.
+ /// When validation fails, contains the list of validation errors.
+ /// true if the input is a valid UUIDv7; otherwise, false.
public static bool TryValidate(Guid? input, out IReadOnlyList? errors)
{
errors = null;
diff --git a/storage/test/SharedIntegrationTests/FilterTranslatorIntegrationTests.cs b/storage/test/SharedIntegrationTests/FilterTranslatorIntegrationTests.cs
index 2453b4d81..35f97adac 100644
--- a/storage/test/SharedIntegrationTests/FilterTranslatorIntegrationTests.cs
+++ b/storage/test/SharedIntegrationTests/FilterTranslatorIntegrationTests.cs
@@ -22,7 +22,7 @@ public partial class FilterTranslatorIntegrationTests
private readonly Ct _ct = TestContext.Current.CancellationToken;
- private sealed class TestEntityAttributeResolver : IScimAttributeTypeResolver
+ private sealed class TestEntityAttributeResolver : IQueryAttributeTypeResolver
{
public Field ResolveField(string attributePath) => attributePath.ToLowerInvariant() switch
{