From 15939b5fa64aefc4e4a496f825347bb3ddc70f97 Mon Sep 17 00:00:00 2001 From: Duende Bot Date: Fri, 22 May 2026 23:49:50 +0000 Subject: [PATCH] Publish - 2026-05-22 23:49:24 UTC --- Directory.Packages.props | 1 + .../CheckSchemaVersionResult.cs | 14 ++- storage/src/Storage/DatabaseSchemaVersion.cs | 13 ++ .../AttributeDefinition.cs | 48 +++++++ .../EntityAttributeValue/AttributeGroup.cs | 22 ++++ .../EntityAttributeValue/AttributeValue.cs | 37 ++++++ .../AttributeValueCollection.cs | 118 ++++++++++++++++++ .../ComplexAttributeType.cs | 13 ++ .../IReadOnlyAttributeSchema.cs | 6 + .../Storage/AttributeDefinitionDso.cs | 21 +++- .../Internal/Storage/AttributeGroupDso.cs | 15 ++- .../Internal/Storage/AttributeSchemaDso.cs | 15 ++- .../Internal/Storage/AttributeTypeDso.cs | 18 ++- .../Internal/Storage/AttributeTypeResolver.cs | 9 +- .../Internal/Storage/AttributeValueDskV1.cs | 22 +++- .../Internal/Storage/AttributeValueDso.cs | 13 +- .../Internal/Storage/EnumValueDso.cs | 10 +- .../EntityAttributeValue/ListAttributeType.cs | 16 +++ .../ScalarAttributeType.cs | 7 ++ .../EntityAttributeValue/ScalarDataType.cs | 11 ++ .../ValidatedAttributeValueCollection.cs | 29 +++++ storage/src/Storage/IDatabaseSchema.cs | 5 +- storage/src/Storage/IStorageBuilder.cs | 6 + .../Internal/Builder/DsoRegistration.cs | 15 ++- ...RegistrationServiceCollectionExtensions.cs | 10 ++ .../Builder/GetRegisteredTypeForDso.cs | 8 ++ .../src/Storage/Internal/DataStorageKey.cs | 21 ++++ .../Storage/Internal/DataStorageKeyType.cs | 24 +++- .../Storage/Internal/DataStorageKeyVersion.cs | 9 ++ .../Internal/DataStorageObjectVersion.cs | 3 + .../Internal/DeterministicGuidGenerator.cs | 12 ++ storage/src/Storage/Internal/EntityType.cs | 6 + storage/src/Storage/Internal/Expiration.cs | 3 + storage/src/Storage/Internal/FieldType.cs | 3 + .../Internal/Filtering/ComparisonOperator.cs | 25 ++++ .../Expressions/AttributePathExpression.cs | 9 ++ .../Expressions/ComparisonExpression.cs | 13 ++ .../Expressions/ComplexAttributeExpression.cs | 11 ++ .../Filtering/Expressions/FilterExpression.cs | 6 + .../Expressions/LogicalExpression.cs | 16 +++ .../Filtering/FilterExpressionParser.cs | 19 +++ .../Internal/Filtering/FilterTranslator.cs | 26 +++- .../Internal/Filtering/LogicalOperator.cs | 11 ++ .../src/Storage/Internal/IDataStorageKey.cs | 3 + .../Storage/Internal/IDataStorageObject.cs | 6 + .../Storage/Internal/IGuidDataStorageKey.cs | 8 +- storage/src/Storage/Internal/IPooledStore.cs | 11 ++ ...lver.cs => IQueryAttributeTypeResolver.cs} | 11 +- storage/src/Storage/Internal/IStore.cs | 6 + .../src/Storage/Internal/InstrumentedStore.cs | 9 +- .../src/Storage/Internal/LinkDefinition.cs | 3 + storage/src/Storage/Internal/LinkResult.cs | 3 + storage/src/Storage/Internal/LinkType.cs | 15 +++ .../src/Storage/Internal/LinkTypeRegistry.cs | 13 ++ .../Internal/Operations/BatchResult.cs | 3 + .../Internal/Operations/CreateOperation.cs | 3 + .../Internal/Operations/CreateResult.cs | 21 ++++ .../Internal/Operations/DeleteOperation.cs | 3 + .../Internal/Operations/DeleteResult.cs | 9 ++ .../Internal/Operations/IStoreOperation.cs | 3 + .../Internal/Operations/LinkOperation.cs | 3 + .../Internal/Operations/OperationOutcome.cs | 3 + .../Internal/Operations/OperationResult.cs | 3 + .../Internal/Operations/StoreGetResult.cs | 42 +++++++ .../Internal/Operations/UnlinkOperation.cs | 3 + .../Internal/Operations/UpdateOperation.cs | 3 + .../Internal/Operations/UpdateResult.cs | 21 ++++ .../Internal/Outbox/HandleOutcomeResult.cs | 3 + .../Internal/Outbox/IOutboxSubscriber.cs | 3 + .../Outbox/IOutboxSubscriberHandler.cs | 3 + .../Storage/Internal/Outbox/OutboxEvent.cs | 3 + .../Storage/Internal/Outbox/OutboxEventId.cs | 6 + .../Internal/Outbox/OutboxEventName.cs | 3 + .../Internal/Outbox/OutboxEventsPage.cs | 3 + .../Internal/Outbox/PersistedOutboxEvent.cs | 3 + .../Storage/Internal/Outbox/SubscriberName.cs | 3 + storage/src/Storage/Internal/PoolId.cs | 6 + storage/src/Storage/Internal/Projection.cs | 3 + .../Storage/Internal/Querying/CursorToken.cs | 5 +- .../Querying/Expressions/AllExpression.cs | 3 + .../Querying/Expressions/AndExpression.cs | 11 ++ .../Expressions/ArrayContainsExpression.cs | 3 + .../Expressions/ArrayFilterExpression.cs | 8 ++ .../Querying/Expressions/BetweenExpression.cs | 14 +++ .../Expressions/ContainsExpression.cs | 11 ++ .../Expressions/EndsWithExpression.cs | 11 ++ .../Querying/Expressions/EqualExpression.cs | 11 ++ .../Expressions/GreaterOrEqualExpression.cs | 11 ++ .../Expressions/GreaterThanExpression.cs | 11 ++ .../Querying/Expressions/InExpression.cs | 11 ++ .../Expressions/LessOrEqualExpression.cs | 11 ++ .../Expressions/LessThanExpression.cs | 11 ++ .../Querying/Expressions/NotExpression.cs | 7 ++ .../Querying/Expressions/OrExpression.cs | 11 ++ .../Querying/Expressions/PresentExpression.cs | 8 ++ .../Expressions/StartsWithExpression.cs | 11 ++ .../Internal/Querying/Fields/BooleanField.cs | 8 ++ .../Internal/Querying/Fields/DateTimeField.cs | 8 ++ .../Querying/Fields/ExactMatchField.cs | 7 ++ .../Storage/Internal/Querying/Fields/Field.cs | 9 ++ .../Internal/Querying/Fields/GuidField.cs | 8 ++ .../Internal/Querying/Fields/NumberField.cs | 8 ++ .../Querying/Fields/StringArrayField.cs | 7 ++ .../Internal/Querying/Fields/StringField.cs | 8 ++ .../Internal/Querying/IQueryExpression.cs | 3 + .../Querying/IQueryExpressionVisitor.cs | 3 + .../Querying/IQueryFilterExpression.cs | 3 + .../Storage/Internal/Querying/ISqlDialect.cs | 5 +- .../Storage/Internal/Querying/LinkQuery.cs | 3 + .../Internal/Querying/LinkQueryBuilder.cs | 3 + .../Internal/Querying/LinkQueryDescriptor.cs | 9 ++ .../Internal/Querying/MetadataEnvelope.cs | 3 + .../Internal/Querying/ProjectedResult.cs | 3 + .../src/Storage/Internal/Querying/Query.cs | 3 + .../QueryFilterExpressionExtensions.cs | 3 + .../SearchFields/SearchFieldCollection.cs | 8 ++ .../Querying/SearchFields/SearchFieldValue.cs | 2 + .../SearchFields/SearchFieldsBuilder.cs | 3 + .../Querying/Sorting/SortParameter.cs | 3 + .../Storage/Internal/Querying/SystemFields.cs | 3 + .../Internal/StorageBuilderExtensions.cs | 6 + .../Internal/Telemetry/StorageMetrics.cs | 30 ++++- .../Telemetry/StorageTelemetryConstants.cs | 62 +++++++++ storage/src/Storage/Internal/TypedDso.cs | 23 ++++ storage/src/Storage/Internal/UnlinkResult.cs | 3 + .../Storage/Pagination/ContinuationToken.cs | 1 + storage/src/Storage/Pagination/DataRange.cs | 12 ++ storage/src/Storage/Pagination/OffsetSkip.cs | 3 +- storage/src/Storage/Pagination/PageNumber.cs | 3 +- storage/src/Storage/Querying/FilterBy.cs | 2 + storage/src/Storage/Querying/QueryRequest.cs | 76 +++++++++++ storage/src/Storage/Querying/QueryResult.cs | 6 + .../src/Storage/SchemaVerificationError.cs | 17 +++ .../src/Storage/SchemaVerificationResult.cs | 7 ++ storage/src/Storage/Storage.csproj | 5 + storage/src/Storage/UuidV7.cs | 14 +++ .../FilterTranslatorIntegrationTests.cs | 2 +- 137 files changed, 1492 insertions(+), 38 deletions(-) rename storage/src/Storage/{Internal => }/CheckSchemaVersionResult.cs (52%) rename storage/src/Storage/Internal/{IScimAttributeTypeResolver.cs => IQueryAttributeTypeResolver.cs} (55%) 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 value) { if (_schema != null) @@ -106,24 +146,59 @@ public sealed class AttributeValueCollection : IEnumerable _dict[code] = new AttributeValue>(code, value); } + /// Attempts to set a boolean value, returning errors on failure. + /// The attribute code. + /// The boolean value. + /// Validation errors if the operation fails. + /// true if the value was set successfully; otherwise, false. public bool TrySet(AttributeCode code, bool value, [NotNullWhen(false)] out IReadOnlyList? errors) => TrySetTyped(code, value, ScalarDataType.Boolean, out errors); + /// Attempts to set an integer value, returning errors on failure. + /// The attribute code. + /// The integer value. + /// Validation errors if the operation fails. + /// true if the value was set successfully; otherwise, false. public bool TrySet(AttributeCode code, int value, [NotNullWhen(false)] out IReadOnlyList? errors) => TrySetTyped(code, value, ScalarDataType.Integer, out errors); + /// Attempts to set a decimal value, returning errors on failure. + /// The attribute code. + /// The decimal value. + /// Validation errors if the operation fails. + /// true if the value was set successfully; otherwise, false. public bool TrySet(AttributeCode code, decimal value, [NotNullWhen(false)] out IReadOnlyList? errors) => TrySetTyped(code, value, ScalarDataType.Decimal, out errors); + /// Attempts to set a string value, returning errors on failure. + /// The attribute code. + /// The string value. + /// Validation errors if the operation fails. + /// true if the value was set successfully; otherwise, false. public bool TrySet(AttributeCode code, string value, [NotNullWhen(false)] out IReadOnlyList? errors) => TrySetTyped(code, value, ScalarDataType.String, out errors); + /// Attempts to set a date value, returning errors on failure. + /// The attribute code. + /// The date value. + /// Validation errors if the operation fails. + /// true if the value was set successfully; otherwise, false. public bool TrySet(AttributeCode code, DateOnly value, [NotNullWhen(false)] out IReadOnlyList? errors) => TrySetTyped(code, value, ScalarDataType.Date, out errors); + /// Attempts to set a date-time value, returning errors on failure. + /// The attribute code. + /// The date-time value. + /// Validation errors if the operation fails. + /// true if the value was set successfully; otherwise, false. public bool TrySet(AttributeCode code, DateTimeOffset value, [NotNullWhen(false)] out IReadOnlyList? errors) => TrySetTyped(code, value, ScalarDataType.DateTime, out errors); + /// Attempts to set a complex (dictionary) value, returning errors on failure. + /// The attribute code. + /// The complex value. + /// Validation errors if the operation fails. + /// true if the value was set successfully; otherwise, false. public bool TrySet(AttributeCode code, IReadOnlyDictionary value, [NotNullWhen(false)] out IReadOnlyList? errors) { if (_schema != null) @@ -142,6 +217,11 @@ public sealed class AttributeValueCollection : IEnumerable return true; } + /// Attempts to set a list value, returning errors on failure. + /// The attribute code. + /// The list value. + /// Validation errors if the operation fails. + /// true if the value was set successfully; otherwise, false. public bool TrySet(AttributeCode code, IReadOnlyList value, [NotNullWhen(false)] out IReadOnlyList? errors) { if (_schema != null) @@ -160,6 +240,10 @@ public sealed class AttributeValueCollection : IEnumerable return true; } + /// + /// Validates the collection against the schema and returns an immutable validated collection. + /// + /// A validated, immutable attribute value collection. public ValidatedAttributeValueCollection Validate() { if (_schema == null) @@ -181,6 +265,12 @@ public sealed class AttributeValueCollection : IEnumerable return new ValidatedAttributeValueCollection(_dict.Values, schema.SchemaId, schema.Version); } + /// + /// Attempts to validate the collection, returning the validated collection or errors. + /// + /// The validated collection if successful. + /// Validation errors if the operation fails. + /// true if validation succeeds; otherwise, false. public bool TryValidate([NotNullWhen(true)] out ValidatedAttributeValueCollection? validated, [NotNullWhen(false)] out IReadOnlyList? errors) { if (_schema == null) @@ -208,8 +298,16 @@ public sealed class AttributeValueCollection : IEnumerable return true; } + /// + /// Gets the number of attribute values in the collection. + /// public int Count => _dict.Count; + /// + /// Removes the attribute value with the specified code. + /// + /// The attribute code to remove. + /// true if the attribute was removed; otherwise, false. public bool Remove(AttributeCode code) { if (_schema != null && @@ -222,15 +320,35 @@ public sealed class AttributeValueCollection : IEnumerable return _dict.Remove(code); } + /// + /// 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() => _dict.Values.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); diff --git a/storage/src/Storage/EntityAttributeValue/ComplexAttributeType.cs b/storage/src/Storage/EntityAttributeValue/ComplexAttributeType.cs index e30b7f0ac..6d1ab4b14 100644 --- a/storage/src/Storage/EntityAttributeValue/ComplexAttributeType.cs +++ b/storage/src/Storage/EntityAttributeValue/ComplexAttributeType.cs @@ -10,6 +10,10 @@ namespace Duende.Storage.EntityAttributeValue; /// 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.Storage Duende.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 {