Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,14 +1,58 @@
using System.Text.Json.Nodes;
using CrestApps.Core.Models;

namespace CrestApps.Core.Services;

/// <summary>
/// A catalog manager that supports finding entries by their unique name,
/// extending <see cref="ICatalogManager{T}"/> with name-based lookup for models
/// extending <see cref="IReadCatalogManager{T}"/> with name-based lookup for models
/// that implement <see cref="INameAwareModel"/>.
/// </summary>
/// <typeparam name="T">The type of catalog entry, which must have a <see cref="INameAwareModel.Name"/> property.</typeparam>
public interface INamedCatalogManager<T> : ICatalogManager<T>
public interface INamedCatalogManager<T> : IReadCatalogManager<T>
where T : INameAwareModel
{
/// <summary>
/// Asynchronously deletes the specified model from the catalog.
/// </summary>
/// <param name="model">The model to delete.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns><see langword="true"/> if the model was successfully deleted; otherwise, <see langword="false"/>.</returns>
ValueTask<bool> DeleteAsync(T model, CancellationToken cancellationToken = default);

/// <summary>
/// Asynchronously creates a new model instance pre-assigned to the specified name,
/// optionally populating it from JSON data.
/// </summary>
/// <param name="name">The name to assign to the new model.</param>
/// <param name="data">Optional JSON data to seed the new model.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A newly created and initialized model instance assigned to the specified name.</returns>
ValueTask<T> NewAsync(string name, JsonNode data = null, CancellationToken cancellationToken = default);

/// <summary>
/// Asynchronously creates the specified model in the catalog.
/// </summary>
/// <param name="model">The model to create.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
ValueTask CreateAsync(T model, CancellationToken cancellationToken = default);

/// <summary>
/// Asynchronously updates the specified model in the catalog, optionally merging changes from JSON data.
/// </summary>
/// <param name="model">The model to update.</param>
/// <param name="data">Optional JSON data containing fields to merge into the model.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
ValueTask UpdateAsync(T model, JsonNode data = null, CancellationToken cancellationToken = default);

/// <summary>
/// Asynchronously validates the specified model and returns the validation result.
/// </summary>
/// <param name="model">The model to validate.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The validation result details indicating success or failure with error messages.</returns>
ValueTask<ValidationResultDetails> ValidateAsync(T model, CancellationToken cancellationToken = default);

/// <summary>
/// Asynchronously finds a catalog entry by its unique name.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,83 @@
using System.Text.Json.Nodes;
using CrestApps.Core.Models;

namespace CrestApps.Core.Services;

/// <summary>
/// A catalog manager that supports composite lookup by both name and source,
/// extending <see cref="INamedCatalogManager{T}"/> and <see cref="ISourceCatalogManager{T}"/>
/// extending <see cref="IReadCatalogManager{T}"/>
/// for models that implement both <see cref="INameAwareModel"/> and <see cref="ISourceAwareModel"/>.
/// </summary>
/// <typeparam name="T">The type of catalog entry.</typeparam>
public interface INamedSourceCatalogManager<T> : INamedCatalogManager<T>, ISourceCatalogManager<T>
public interface INamedSourceCatalogManager<T> : IReadCatalogManager<T>
where T : INameAwareModel, ISourceAwareModel
{
/// <summary>
/// Asynchronously deletes the specified model from the catalog.
/// </summary>
/// <param name="model">The model to delete.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns><see langword="true"/> if the model was successfully deleted; otherwise, <see langword="false"/>.</returns>
ValueTask<bool> DeleteAsync(T model, CancellationToken cancellationToken = default);

/// <summary>
/// Asynchronously creates a new model instance pre-assigned to the specified name and source,
/// optionally populating it from JSON data.
/// </summary>
/// <param name="name">The name to assign to the new model.</param>
/// <param name="source">The source to assign to the new model.</param>
/// <param name="data">Optional JSON data to seed the new model.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A newly created and initialized model instance assigned to the specified name and source.</returns>
ValueTask<T> NewAsync(string name, string source, JsonNode data = null, CancellationToken cancellationToken = default);

/// <summary>
/// Asynchronously creates the specified model in the catalog.
/// </summary>
/// <param name="model">The model to create.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
ValueTask CreateAsync(T model, CancellationToken cancellationToken = default);

/// <summary>
/// Asynchronously updates the specified model in the catalog, optionally merging changes from JSON data.
/// </summary>
/// <param name="model">The model to update.</param>
/// <param name="data">Optional JSON data containing fields to merge into the model.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
ValueTask UpdateAsync(T model, JsonNode data = null, CancellationToken cancellationToken = default);

/// <summary>
/// Asynchronously validates the specified model and returns the validation result.
/// </summary>
/// <param name="model">The model to validate.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The validation result details indicating success or failure with error messages.</returns>
ValueTask<ValidationResultDetails> ValidateAsync(T model, CancellationToken cancellationToken = default);

/// <summary>
/// Asynchronously finds a catalog entry by its unique name.
/// </summary>
/// <param name="name">The unique name of the entry to find.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The matching entry, or <see langword="null"/> if no entry with the specified name exists.</returns>
ValueTask<T> FindByNameAsync(string name, CancellationToken cancellationToken = default);

/// <summary>
/// Asynchronously retrieves all catalog entries belonging to the specified source.
/// </summary>
/// <param name="source">The source or provider name to filter by.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>An enumerable of entries matching the specified source.</returns>
ValueTask<IEnumerable<T>> GetAsync(string source, CancellationToken cancellationToken = default);

/// <summary>
/// Asynchronously finds all catalog entries that belong to the specified source.
/// </summary>
/// <param name="source">The source or provider name to search for.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>An enumerable of entries matching the specified source.</returns>
ValueTask<IEnumerable<T>> FindBySourceAsync(string source, CancellationToken cancellationToken = default);

/// <summary>
/// Asynchronously retrieves a catalog entry by its unique name and source combination.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json.Nodes;
using CrestApps.Core.Models;

namespace CrestApps.Core.Services;

Expand All @@ -7,9 +8,40 @@ namespace CrestApps.Core.Services;
/// extending <see cref="ICatalogManager{T}"/> for models that implement <see cref="ISourceAwareModel"/>.
/// </summary>
/// <typeparam name="T">The type of catalog entry, which must have a <see cref="ISourceAwareModel.Source"/> property.</typeparam>
public interface ISourceCatalogManager<T> : ICatalogManager<T>
public interface ISourceCatalogManager<T> : IReadCatalogManager<T>
where T : ISourceAwareModel
{
/// <summary>
/// Asynchronously deletes the specified model from the catalog.
/// </summary>
/// <param name="model">The model to delete.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns><see langword="true"/> if the model was successfully deleted; otherwise, <see langword="false"/>.</returns>
ValueTask<bool> DeleteAsync(T model, CancellationToken cancellationToken = default);

/// <summary>
/// Asynchronously creates the specified model in the catalog.
/// </summary>
/// <param name="model">The model to create.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
ValueTask CreateAsync(T model, CancellationToken cancellationToken = default);

/// <summary>
/// Asynchronously updates the specified model in the catalog, optionally merging changes from JSON data.
/// </summary>
/// <param name="model">The model to update.</param>
/// <param name="data">Optional JSON data containing fields to merge into the model.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
ValueTask UpdateAsync(T model, JsonNode data = null, CancellationToken cancellationToken = default);

/// <summary>
/// Asynchronously validates the specified model and returns the validation result.
/// </summary>
/// <param name="model">The model to validate.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The validation result details indicating success or failure with error messages.</returns>
ValueTask<ValidationResultDetails> ValidateAsync(T model, CancellationToken cancellationToken = default);

/// <summary>
/// Asynchronously creates a new model instance pre-assigned to the specified source,
/// optionally populating it from JSON data.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,26 @@ namespace CrestApps.Core.Infrastructure.Indexing;

/// <summary>
/// Manages the full lifecycle of <see cref="SearchIndexProfile"/> entries, including
/// creation, update, deletion, field retrieval, synchronization, and reset operations.
/// Extends <see cref="ICatalogManager{T}"/> with indexing-specific methods.
/// creation, update, deletion, name-scoped initialization, field retrieval,
/// synchronization, and reset operations. Extends <see cref="INamedCatalogManager{T}"/>
/// with indexing-specific methods.
/// </summary>
public interface ISearchIndexProfileManager : ICatalogManager<SearchIndexProfile>
public interface ISearchIndexProfileManager : INamedCatalogManager<SearchIndexProfile>
{
/// <summary>
/// Asynchronously finds an index profile by its unique name.
/// </summary>
/// <param name="name">The unique name of the profile to find.</param>
/// <returns>The matching profile, or <see langword="null"/> if not found.</returns>
ValueTask<SearchIndexProfile> FindByNameAsync(string name);

/// <summary>
/// Asynchronously retrieves all index profiles of the specified type.
/// </summary>
/// <param name="type">The profile type to filter by (e.g., <see cref="IndexProfileTypes.AIDocuments"/>).</param>
/// <returns>A read-only collection of profiles matching the specified type.</returns>
Task<IReadOnlyCollection<SearchIndexProfile>> GetByTypeAsync(string type);
Task<IReadOnlyCollection<SearchIndexProfile>> GetByTypeAsync(string type, CancellationToken cancellationToken = default);

/// <summary>
/// Asynchronously retrieves the provider-specific field definitions for the specified profile.
/// </summary>
/// <param name="profile">The index profile whose fields should be retrieved.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A read-only collection of field definitions for the profile.</returns>
ValueTask<IReadOnlyCollection<SearchIndexField>> GetFieldsAsync(
SearchIndexProfile profile,
CancellationToken cancellationToken = default);
ValueTask<IReadOnlyCollection<SearchIndexField>> GetFieldsAsync(SearchIndexProfile profile, CancellationToken cancellationToken = default);

/// <summary>
/// Asynchronously synchronizes the specified profile to its remote search index provider.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ public interface ISearchIndexProfileStore : ICatalog<SearchIndexProfile>, INamed
/// </summary>
/// <param name="type">The index profile type to filter by.</param>
/// <returns>A read-only collection of matching index profiles.</returns>
Task<IReadOnlyCollection<SearchIndexProfile>> GetByTypeAsync(string type);
Task<IReadOnlyCollection<SearchIndexProfile>> GetByTypeAsync(string type, CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Text.Json;
using CrestApps.Core.AI.DataSources;
using CrestApps.Core.AI.Models;
using CrestApps.Core.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

Expand Down Expand Up @@ -45,7 +45,7 @@ public async Task UpdatingAsync(ChatInteraction interaction, JsonElement setting
return;
}

var dataSourceCatalog = _serviceProvider.GetService<ICatalog<AIDataSource>>();
var dataSourceCatalog = _serviceProvider.GetService<IAIDataSourceStore>();
if (dataSourceCatalog == null)
{
_logger.LogDebug("Skipping chat interaction data source settings because no AI data source catalog is registered.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public async ValueTask ValidateAsync(AIDataSource dataSource, ValidationResultDe
return;
}

var sourceProfile = await _indexProfileManager.FindByNameAsync(dataSource.SourceIndexProfileName);
var sourceProfile = await _indexProfileManager.FindByNameAsync(dataSource.SourceIndexProfileName, cancellationToken);
if (sourceProfile == null)
{
result.Fail(new ValidationResult("The selected source index profile could not be found.", [nameof(AIDataSource.SourceIndexProfileName)]));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CrestApps.Core.AI.Clients;
using CrestApps.Core.AI.DataSources;
using CrestApps.Core.AI.Deployments;
using CrestApps.Core.AI.Models;
using CrestApps.Core.AI.Orchestration;
Expand All @@ -7,7 +8,6 @@
using CrestApps.Core.Infrastructure.Indexing;
using CrestApps.Core.Infrastructure.Indexing.DataSources;
using CrestApps.Core.Infrastructure.Indexing.Models;
using CrestApps.Core.Services;
using CrestApps.Core.Templates.Services;
using Cysharp.Text;
using Microsoft.Extensions.AI;
Expand Down Expand Up @@ -68,7 +68,7 @@ public ValueTask<bool> CanHandleAsync(OrchestrationContextBuiltContext context)
}

return ValueTask.FromResult(
_serviceProvider.GetService<ICatalog<AIDataSource>>() != null &&
_serviceProvider.GetService<IAIDataSourceStore>() != null &&
_serviceProvider.GetService<ISearchIndexProfileStore>() != null);
}

Expand All @@ -93,7 +93,7 @@ public async Task HandleAsync(PreemptiveRagContext context)

private async Task InjectPreemptiveRagContextAsync(PreemptiveRagContext context, AIDataSourceRagMetadata ragMetadata)
{
var dataSourceCatalog = _serviceProvider.GetService<ICatalog<AIDataSource>>();
var dataSourceCatalog = _serviceProvider.GetService<IAIDataSourceStore>();
var indexProfileStore = _serviceProvider.GetService<ISearchIndexProfileStore>();

if (dataSourceCatalog == null || indexProfileStore == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public ValueTask<SearchIndexProfile> FindByNameAsync(string name, CancellationTo
/// Gets by type.
/// </summary>
/// <param name="type">The type.</param>
public Task<IReadOnlyCollection<SearchIndexProfile>> GetByTypeAsync(string type)
public Task<IReadOnlyCollection<SearchIndexProfile>> GetByTypeAsync(string type, CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrEmpty(type);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace CrestApps.Core.AI.Indexing;
/// <summary>
/// Represents the search Index Profile Manager.
/// </summary>
public sealed class SearchIndexProfileManager : CatalogManager<SearchIndexProfile>, ISearchIndexProfileManager
public sealed class SearchIndexProfileManager : NamedCatalogManager<SearchIndexProfile>, ISearchIndexProfileManager
{
private readonly ISearchIndexProfileStore _store;
private readonly IEnumerable<IIndexProfileHandler> _handlers;
Expand Down Expand Up @@ -37,22 +37,19 @@ public SearchIndexProfileManager(
/// Finds by name.
/// </summary>
/// <param name="name">The name.</param>
public ValueTask<SearchIndexProfile> FindByNameAsync(string name)
{
ArgumentException.ThrowIfNullOrEmpty(name);

return _store.FindByNameAsync(name);
}
/// <param name="cancellationToken">The cancellation token.</param>
public new ValueTask<SearchIndexProfile> FindByNameAsync(string name, CancellationToken cancellationToken = default)
=> base.FindByNameAsync(name, cancellationToken);

/// <summary>
/// Gets by type.
/// </summary>
/// <param name="type">The type.</param>
public Task<IReadOnlyCollection<SearchIndexProfile>> GetByTypeAsync(string type)
public Task<IReadOnlyCollection<SearchIndexProfile>> GetByTypeAsync(string type, CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrEmpty(type);

return _store.GetByTypeAsync(type);
return _store.GetByTypeAsync(type, cancellationToken);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public static IServiceCollection AddCoreIndexingServices(this IServiceCollection
services.TryAddScoped<ICatalog<SearchIndexProfile>>(sp => sp.GetRequiredService<ISearchIndexProfileStore>());
services.TryAddScoped<INamedCatalog<SearchIndexProfile>>(sp => sp.GetRequiredService<ISearchIndexProfileStore>());
services.TryAddScoped<ISearchIndexProfileManager, SearchIndexProfileManager>();
services.TryAddScoped<ICatalogManager<SearchIndexProfile>>(sp => sp.GetRequiredService<ISearchIndexProfileManager>());
services.TryAddScoped<INamedCatalogManager<SearchIndexProfile>>(sp => sp.GetRequiredService<ISearchIndexProfileManager>());
services.TryAddScoped<ISearchIndexProfileProvisioningService, SearchIndexProfileProvisioningService>();

return services;
Expand Down
Loading
Loading