From 9a93eaf57a9818fa3c7d3f208b5acda70daaf4eb Mon Sep 17 00:00:00 2001 From: Ard van der Marel <109339598+Ard2025@users.noreply.github.com> Date: Wed, 4 Jun 2025 09:30:59 +0200 Subject: [PATCH 1/3] refactor to file scoped namespace --- Magic.IndexedDb/Cache.cs | 17 +- .../Extensions/MagicCompoundExtension.cs | 21 +- .../Extensions/MagicJsChunkProcessor.cs | 121 +- Magic.IndexedDb/Extensions/MagicJsInvoke.cs | 353 +++-- Magic.IndexedDb/Extensions/MagicTableTools.cs | 30 +- Magic.IndexedDb/Extensions/MagicUtilities.cs | 49 +- .../Extensions/ServiceCollectionExtensions.cs | 61 +- .../Extensions/SharedQueryExtensions.cs | 145 ++- Magic.IndexedDb/Factories/IMagicIndexedDb.cs | 103 +- Magic.IndexedDb/Factories/MagicDbFactory.cs | 217 ++-- Magic.IndexedDb/Helpers/AttributeHelpers.cs | 101 +- .../Helpers/ExpandoToTypeConverter.cs | 59 +- .../Helpers/ExpressionFlattener.cs | 394 +++--- .../Helpers/MagicSerializationHelper.cs | 141 +- Magic.IndexedDb/Helpers/MagicValidator.cs | 151 ++- .../Helpers/PropertyMappingCache.cs | 882 +++++++------ Magic.IndexedDb/Helpers/SchemaHelper.cs | 375 +++--- Magic.IndexedDb/IndexDbManager.cs | 489 ++++--- Magic.IndexedDb/IndexedDbFunctions.cs | 59 +- Magic.IndexedDb/Interfaces/IColumnNamed.cs | 12 +- .../Interfaces/IMagicCompoundIndex.cs | 13 +- .../Interfaces/IMagicCompoundKey.cs | 15 +- .../Interfaces/IMagicConnectRepository.cs | 9 +- .../Interfaces/IMagicRepository.cs | 9 +- Magic.IndexedDb/Interfaces/IMagicTable.cs | 11 +- Magic.IndexedDb/Interfaces/IMagicTableBase.cs | 23 +- Magic.IndexedDb/Interfaces/IMagicUtilities.cs | 9 +- Magic.IndexedDb/Interfaces/ITypedArgument.cs | 15 +- .../Extensions/MagicCursorExtension.cs | 103 +- .../Extensions/MagicQueryExtensions.cs | 269 ++-- .../Extensions/UniversalExpressionBuilder.cs | 1148 ++++++++--------- .../Interfaces/IMagicCursor.cs | 55 +- .../Interfaces/IMagicCursorSkip.cs | 15 +- .../Interfaces/IMagicCursorStage.cs | 37 +- .../Interfaces/IMagicDatabase.cs | 33 +- .../Interfaces/IMagicExecute.cs | 43 +- .../LinqTranslation/Interfaces/IMagicQuery.cs | 87 +- .../Interfaces/IMagicQueryFinal.cs | 37 +- .../Interfaces/IMagicQueryOrderable.cs | 47 +- .../Interfaces/IMagicQueryOrderableTable.cs | 21 +- .../Interfaces/IMagicQueryPaginationTake.cs | 39 +- .../Interfaces/IMagicQueryStaging.cs | 37 +- .../Models/LogicalFilterNode.cs | 109 +- .../LinqTranslation/Models/MagicCursor.cs | 77 +- .../Models/MagicDatabaseGlobal.cs | 11 +- .../Models/MagicDatabaseScoped.cs | 59 +- .../LinqTranslation/Models/MagicQuery.cs | 259 ++-- .../LinqTranslation/Models/MagicWhereLinq.cs | 9 +- Magic.IndexedDb/MagicEncryptAttribute.cs | 25 +- Magic.IndexedDb/Models/DbMigration.cs | 15 +- .../Models/DbMigrationInstruction.cs | 15 +- Magic.IndexedDb/Models/DbStore.cs | 21 +- Magic.IndexedDb/Models/IndexFilterValue.cs | 21 +- Magic.IndexedDb/Models/IndexedDbSet.cs | 15 +- .../Models/InternalMagicCompoundIndex.cs | 139 +- .../Models/InternalMagicCompoundKey.cs | 173 ++- .../Models/MagicContractResolver.cs | 707 +++++----- Magic.IndexedDb/Models/MagicException.cs | 12 +- Magic.IndexedDb/Models/MagicJsPackage.cs | 21 +- .../Models/MagicJsonSerializationSettings.cs | 53 +- Magic.IndexedDb/Models/PredicateVisitor.cs | 133 +- Magic.IndexedDb/Models/StoreRecord.cs | 15 +- Magic.IndexedDb/Models/StoreSchema.cs | 43 +- Magic.IndexedDb/Models/StoredMagicQuery.cs | 55 +- .../Models/Structs/MagicAttributesEntry.cs | 59 +- .../Models/Structs/MagicPropertyEntry.cs | 261 ++-- .../Structs/MagicPropertySearchEntry.cs | 61 +- .../Models/Structs/MagicTableEntry.cs | 23 +- Magic.IndexedDb/Models/TypedArgument.cs | 42 +- .../UniversalOperations/AndFilterGroup.cs | 23 +- .../UniversalOperations/FilterCondition.cs | 75 +- .../UniversalOperations/NestedOrFilter.cs | 43 +- .../UniversalOperations/OrFilterGroup.cs | 23 +- Magic.IndexedDb/Models/UpdateRecord.cs | 9 +- .../SchemaAnnotations/MagicIndexAttribute.cs | 35 +- .../SchemaAnnotations/MagicNameAttribute.cs | 29 +- .../MagicNotMappedAttribute.cs | 7 +- .../MagicPrimaryKeyAttribute.cs | 47 +- .../SchemaAnnotations/MagicTableAttribute.cs | 31 +- .../MagicUniqueIndexAttribute.cs | 35 +- Magic.IndexedDb/SnapshotBuilder/BuildTools.cs | 129 +- Magic.IndexedDb/Testing/Helpers/AllPaths.cs | 165 ++- .../Testing/Helpers/MagicQueryPathWalker.cs | 465 ++++--- Magic.IndexedDb/Testing/Helpers/TestHelper.cs | 233 ++-- .../Testing/Models/TestResponse.cs | 13 +- TestServer/TestServer/Models/DbNames.cs | 11 +- TestServer/TestServer/Models/Person.cs | 127 +- .../TestServer/Repository/IndexDbContext.cs | 15 +- TestWasm/Models/DbNames.cs | 11 +- TestWasm/Models/Person.cs | 129 +- TestWasm/Repository/IndexDbContext.cs | 15 +- 91 files changed, 4924 insertions(+), 5034 deletions(-) diff --git a/Magic.IndexedDb/Cache.cs b/Magic.IndexedDb/Cache.cs index 1f5d556..bae4680 100644 --- a/Magic.IndexedDb/Cache.cs +++ b/Magic.IndexedDb/Cache.cs @@ -4,13 +4,12 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +internal static class Cache { - internal static class Cache - { - /// - /// this is the wwwroot path of "./magicDB.js" for importing the script - /// - public const string MagicDbJsImportPath = "./magicDB.js"; - } -} + /// + /// this is the wwwroot path of "./magicDB.js" for importing the script + /// + public const string MagicDbJsImportPath = "./magicDB.js"; +} \ No newline at end of file diff --git a/Magic.IndexedDb/Extensions/MagicCompoundExtension.cs b/Magic.IndexedDb/Extensions/MagicCompoundExtension.cs index 04773a8..0c6068f 100644 --- a/Magic.IndexedDb/Extensions/MagicCompoundExtension.cs +++ b/Magic.IndexedDb/Extensions/MagicCompoundExtension.cs @@ -6,18 +6,17 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +internal static class MagicCompoundExtension { - internal static class MagicCompoundExtension + public static IMagicCompoundIndex CreateIndex(params Expression>[] keySelectors) { - public static IMagicCompoundIndex CreateIndex(params Expression>[] keySelectors) - { - return InternalMagicCompoundIndex.Create(keySelectors); - } + return InternalMagicCompoundIndex.Create(keySelectors); + } - public static IMagicCompoundKey CreateKey(bool autoIncrement, params Expression>[] keySelectors) - { - return InternalMagicCompoundKey.Create(autoIncrement, keySelectors); - } + public static IMagicCompoundKey CreateKey(bool autoIncrement, params Expression>[] keySelectors) + { + return InternalMagicCompoundKey.Create(autoIncrement, keySelectors); } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/Extensions/MagicJsChunkProcessor.cs b/Magic.IndexedDb/Extensions/MagicJsChunkProcessor.cs index 34415bd..934459f 100644 --- a/Magic.IndexedDb/Extensions/MagicJsChunkProcessor.cs +++ b/Magic.IndexedDb/Extensions/MagicJsChunkProcessor.cs @@ -5,87 +5,86 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Extensions +namespace Magic.IndexedDb.Extensions; + +public static class MagicJsChunkProcessor { - public static class MagicJsChunkProcessor + private static readonly ConcurrentDictionary>> _chunkedMessages = new(); + private static readonly ConcurrentDictionary> _instanceCompleteItems = new(); + private static readonly ConcurrentDictionary> _instanceOrderedItems = new(); + + public static void RegisterInstance(string instanceId) { - private static readonly ConcurrentDictionary>> _chunkedMessages = new(); - private static readonly ConcurrentDictionary> _instanceCompleteItems = new(); - private static readonly ConcurrentDictionary> _instanceOrderedItems = new(); + _chunkedMessages.TryAdd(instanceId, new ConcurrentDictionary>()); + _instanceCompleteItems.TryAdd(instanceId, new ConcurrentQueue()); + _instanceOrderedItems.TryAdd(instanceId, new SortedDictionary()); + } - public static void RegisterInstance(string instanceId) + public static void AddChunk(string instanceId, string chunkInstanceId, int yieldOrderIndex, string chunk, int chunkIndex, int totalChunks) + { + // ✅ Ensure the instance exists before proceeding + var messageStore = _chunkedMessages.GetOrAdd(instanceId, _ => new ConcurrentDictionary>()); + var orderedItems = _instanceOrderedItems.GetOrAdd(instanceId, _ => new SortedDictionary()); + var completedQueue = _instanceCompleteItems.GetOrAdd(instanceId, _ => new ConcurrentQueue()); + + if (chunkInstanceId == "STREAM_COMPLETE") { - _chunkedMessages.TryAdd(instanceId, new ConcurrentDictionary>()); - _instanceCompleteItems.TryAdd(instanceId, new ConcurrentQueue()); - _instanceOrderedItems.TryAdd(instanceId, new SortedDictionary()); + completedQueue.Enqueue("STREAM_COMPLETE"); + return; } - public static void AddChunk(string instanceId, string chunkInstanceId, int yieldOrderIndex, string chunk, int chunkIndex, int totalChunks) + // ✅ Ensure chunkInstanceId exists + var messageChunks = messageStore.GetOrAdd(chunkInstanceId, _ => new Dictionary()); + messageChunks[chunkIndex] = chunk; + + // ✅ Check if all chunks are received + if (messageChunks.Count == totalChunks) { - // ✅ Ensure the instance exists before proceeding - var messageStore = _chunkedMessages.GetOrAdd(instanceId, _ => new ConcurrentDictionary>()); - var orderedItems = _instanceOrderedItems.GetOrAdd(instanceId, _ => new SortedDictionary()); - var completedQueue = _instanceCompleteItems.GetOrAdd(instanceId, _ => new ConcurrentQueue()); + var fullMessage = string.Join("", messageChunks.OrderBy(kvp => kvp.Key).Select(kvp => kvp.Value)); - if (chunkInstanceId == "STREAM_COMPLETE") + lock (orderedItems) { - completedQueue.Enqueue("STREAM_COMPLETE"); - return; + orderedItems[yieldOrderIndex] = fullMessage; } - // ✅ Ensure chunkInstanceId exists - var messageChunks = messageStore.GetOrAdd(chunkInstanceId, _ => new Dictionary()); - messageChunks[chunkIndex] = chunk; - - // ✅ Check if all chunks are received - if (messageChunks.Count == totalChunks) - { - var fullMessage = string.Join("", messageChunks.OrderBy(kvp => kvp.Key).Select(kvp => kvp.Value)); - - lock (orderedItems) - { - orderedItems[yieldOrderIndex] = fullMessage; - } - - messageStore.TryRemove(chunkInstanceId, out _); // ✅ Corrected removal - } + messageStore.TryRemove(chunkInstanceId, out _); // ✅ Corrected removal } + } - public static string? GetCompletedItem(string instanceId) - { - var orderedItems = _instanceOrderedItems.GetOrAdd(instanceId, _ => new SortedDictionary()); - var completedQueue = _instanceCompleteItems.GetOrAdd(instanceId, _ => new ConcurrentQueue()); + public static string? GetCompletedItem(string instanceId) + { + var orderedItems = _instanceOrderedItems.GetOrAdd(instanceId, _ => new SortedDictionary()); + var completedQueue = _instanceCompleteItems.GetOrAdd(instanceId, _ => new ConcurrentQueue()); - lock (orderedItems) + lock (orderedItems) + { + // If we still have items to return, prioritize them + if (orderedItems.Count > 0) { - // If we still have items to return, prioritize them - if (orderedItems.Count > 0) - { - var firstKey = orderedItems.Keys.First(); - var message = orderedItems[firstKey]; - - orderedItems.Remove(firstKey); - return message; - } - } + var firstKey = orderedItems.Keys.First(); + var message = orderedItems[firstKey]; - // Only return "STREAM_COMPLETE" if ALL items have been returned - if (completedQueue.TryPeek(out var completedMarker) && completedMarker == "STREAM_COMPLETE") - { - completedQueue.TryDequeue(out _); // Remove completion marker now that we're truly done - return "STREAM_COMPLETE"; + orderedItems.Remove(firstKey); + return message; } + } - return null; + // Only return "STREAM_COMPLETE" if ALL items have been returned + if (completedQueue.TryPeek(out var completedMarker) && completedMarker == "STREAM_COMPLETE") + { + completedQueue.TryDequeue(out _); // Remove completion marker now that we're truly done + return "STREAM_COMPLETE"; } + return null; + } - public static void RemoveInstance(string instanceId) - { - _chunkedMessages.TryRemove(instanceId, out _); - _instanceCompleteItems.TryRemove(instanceId, out _); - _instanceOrderedItems.TryRemove(instanceId, out _); - } + + public static void RemoveInstance(string instanceId) + { + _chunkedMessages.TryRemove(instanceId, out _); + _instanceCompleteItems.TryRemove(instanceId, out _); + _instanceOrderedItems.TryRemove(instanceId, out _); } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/Extensions/MagicJsInvoke.cs b/Magic.IndexedDb/Extensions/MagicJsInvoke.cs index c25d6bb..d676779 100644 --- a/Magic.IndexedDb/Extensions/MagicJsInvoke.cs +++ b/Magic.IndexedDb/Extensions/MagicJsInvoke.cs @@ -11,245 +11,242 @@ using System.Threading.Tasks; using static System.Runtime.InteropServices.JavaScript.JSType; -namespace Magic.IndexedDb.Extensions +namespace Magic.IndexedDb.Extensions; + +internal class MagicJsInvoke { - internal class MagicJsInvoke + private readonly IJSObjectReference _jsModule; + private readonly long _jsMessageSizeBytes; + + public MagicJsInvoke(IJSObjectReference jsModule, long jsMessageSizeBytes) { - private readonly IJSObjectReference _jsModule; - private readonly long _jsMessageSizeBytes; + _jsModule = jsModule; + _jsMessageSizeBytes = jsMessageSizeBytes; + } - public MagicJsInvoke(IJSObjectReference jsModule, long jsMessageSizeBytes) - { - _jsModule = jsModule; - _jsMessageSizeBytes = jsMessageSizeBytes; - } + internal async Task CallJsAsync(string modulePath, string functionName, + CancellationToken token, params ITypedArgument[] args) + { + await MagicVoidStreamJsAsync(modulePath, functionName, token, args); + } - internal async Task CallJsAsync(string modulePath, string functionName, - CancellationToken token, params ITypedArgument[] args) - { - await MagicVoidStreamJsAsync(modulePath, functionName, token, args); - } + internal async Task CallJsAsync(string modulePath, string functionName, + CancellationToken token, params ITypedArgument[] args) + { - internal async Task CallJsAsync(string modulePath, string functionName, - CancellationToken token, params ITypedArgument[] args) - { + return await MagicStreamJsAsync(modulePath, functionName, token, args) ?? default; + } - return await MagicStreamJsAsync(modulePath, functionName, token, args) ?? default; - } + /// + internal async Task CallInvokeDefaultJsAsync(string modulePath, string functionName, + params object[] args) + { - /// - internal async Task CallInvokeDefaultJsAsync(string modulePath, string functionName, - params object[] args) - { + var throwAway = await CallInvokeDefaultJsAsync(modulePath, functionName, args); - var throwAway = await CallInvokeDefaultJsAsync(modulePath, functionName, args); + return; + } - return; - } + /// + /// Utilizes InvokeAsync normally with no special serialization or streaming. + /// + /// + /// + /// + /// + /// + /// + internal async Task CallInvokeDefaultJsAsync(string modulePath, string functionName, + params object[] args) + { + return await TrueCallInvokeDefaultJsAsync(modulePath, functionName, false, args); + } - /// - /// Utilizes InvokeAsync normally with no special serialization or streaming. - /// - /// - /// - /// - /// - /// - /// - internal async Task CallInvokeDefaultJsAsync(string modulePath, string functionName, - params object[] args) - { - return await TrueCallInvokeDefaultJsAsync(modulePath, functionName, false, args); - } + private async Task TrueCallInvokeDefaultJsAsync(string modulePath, string functionName, + bool isVoid, + params object[] args) + { + var response = await _jsModule.InvokeAsync("JsHandler", isVoid, modulePath, functionName, args); + return response; + } - private async Task TrueCallInvokeDefaultJsAsync(string modulePath, string functionName, - bool isVoid, - params object[] args) - { - var response = await _jsModule.InvokeAsync("JsHandler", isVoid, modulePath, functionName, args); - return response; - } + internal async Task CallInvokeVoidDefaultJsAsync(string modulePath, string functionName, + params object[] args) + { + await TrueCallInvokeDefaultJsAsync(modulePath, functionName, true, args); + } - internal async Task CallInvokeVoidDefaultJsAsync(string modulePath, string functionName, - params object[] args) + internal async IAsyncEnumerable CallYieldJsAsync( + string modulePath, + string functionName, + [EnumeratorCancellation] CancellationToken token, + params ITypedArgument[] args) + { + await foreach (var item in MagicYieldJsAsync(modulePath, functionName, token, args) + .WithCancellation(token)) // Ensure cancellation works in the async stream { - await TrueCallInvokeDefaultJsAsync(modulePath, functionName, true, args); + yield return item; // Yield items as they arrive } + } - internal async IAsyncEnumerable CallYieldJsAsync( - string modulePath, - string functionName, - [EnumeratorCancellation] CancellationToken token, - params ITypedArgument[] args) - { - await foreach (var item in MagicYieldJsAsync(modulePath, functionName, token, args) - .WithCancellation(token)) // Ensure cancellation works in the async stream - { - yield return item; // Yield items as they arrive - } - } + private async Task MagicStreamJsAsync(string modulePath, string functionName, CancellationToken token, params ITypedArgument[] args) + { + return await TrueMagicStreamJsAsync(modulePath, functionName, token, false, args); + } + private async Task TrueMagicStreamJsAsync(string modulePath, string functionName, + CancellationToken token, bool isVoid, params ITypedArgument[] args) + { + var settings = new MagicJsonSerializationSettings() { UseCamelCase = true }; - private async Task MagicStreamJsAsync(string modulePath, string functionName, CancellationToken token, params ITypedArgument[] args) - { - return await TrueMagicStreamJsAsync(modulePath, functionName, token, false, args); - } - private async Task TrueMagicStreamJsAsync(string modulePath, string functionName, - CancellationToken token, bool isVoid, params ITypedArgument[] args) + var package = new MagicJsPackage { - var settings = new MagicJsonSerializationSettings() { UseCamelCase = true }; + YieldResults = false, + ModulePath = modulePath, + MethodName = functionName, + Parameters = MagicSerializationHelper.SerializeObjectsToString(args, settings), + IsVoid = isVoid + }; - var package = new MagicJsPackage - { - YieldResults = false, - ModulePath = modulePath, - MethodName = functionName, - Parameters = MagicSerializationHelper.SerializeObjectsToString(args, settings), - IsVoid = isVoid - }; - - string instanceId = Guid.NewGuid().ToString(); + string instanceId = Guid.NewGuid().ToString(); #if DEBUG - package.IsDebug = true; + package.IsDebug = true; #else package.IsDebug = false; #endif - using var stream = new MemoryStream(); - await using (var writer = new StreamWriter(stream, leaveOpen: true)) - { - await MagicSerializationHelper.SerializeObjectToStreamAsync(writer, package, settings); - } + using var stream = new MemoryStream(); + await using (var writer = new StreamWriter(stream, leaveOpen: true)) + { + await MagicSerializationHelper.SerializeObjectToStreamAsync(writer, package, settings); + } - // ✅ Immediately release reference to `package` - package = null; - GC.Collect(); - GC.WaitForPendingFinalizers(); + // ✅ Immediately release reference to `package` + package = null; + GC.Collect(); + GC.WaitForPendingFinalizers(); - stream.Position = 0; + stream.Position = 0; - var streamRef = new DotNetStreamReference(stream); + var streamRef = new DotNetStreamReference(stream); - // Send to JS - var responseStreamRef = await _jsModule.InvokeAsync("streamedJsHandler", - streamRef, instanceId, DotNetObjectReference.Create(this), _jsMessageSizeBytes); + // Send to JS + var responseStreamRef = await _jsModule.InvokeAsync("streamedJsHandler", + streamRef, instanceId, DotNetObjectReference.Create(this), _jsMessageSizeBytes); - // 🚀 Convert the stream reference back to JSON in C# - await using var responseStream = await responseStreamRef.OpenReadStreamAsync(long.MaxValue, token); - using var reader = new StreamReader(responseStream); + // 🚀 Convert the stream reference back to JSON in C# + await using var responseStream = await responseStreamRef.OpenReadStreamAsync(long.MaxValue, token); + using var reader = new StreamReader(responseStream); - string jsonResponse = await reader.ReadToEndAsync(); - return MagicSerializationHelper.DeserializeObject(jsonResponse, settings); - } + string jsonResponse = await reader.ReadToEndAsync(); + return MagicSerializationHelper.DeserializeObject(jsonResponse, settings); + } - private async IAsyncEnumerable MagicYieldJsAsync( - string modulePath, string functionName, - [EnumeratorCancellation] CancellationToken token, - params ITypedArgument[] args) - { - var settings = new MagicJsonSerializationSettings() { UseCamelCase = true }; + private async IAsyncEnumerable MagicYieldJsAsync( + string modulePath, string functionName, + [EnumeratorCancellation] CancellationToken token, + params ITypedArgument[] args) + { + var settings = new MagicJsonSerializationSettings() { UseCamelCase = true }; - var package = new MagicJsPackage - { - ModulePath = modulePath, - MethodName = functionName, - Parameters = MagicSerializationHelper.SerializeObjectsToString(args, settings), - IsVoid = false, - YieldResults = true - }; + var package = new MagicJsPackage + { + ModulePath = modulePath, + MethodName = functionName, + Parameters = MagicSerializationHelper.SerializeObjectsToString(args, settings), + IsVoid = false, + YieldResults = true + }; - string instanceId = Guid.NewGuid().ToString(); + string instanceId = Guid.NewGuid().ToString(); - using var stream = new MemoryStream(); - await using (var writer = new StreamWriter(stream, leaveOpen: true)) - { - await MagicSerializationHelper.SerializeObjectToStreamAsync(writer, package, settings); - } + using var stream = new MemoryStream(); + await using (var writer = new StreamWriter(stream, leaveOpen: true)) + { + await MagicSerializationHelper.SerializeObjectToStreamAsync(writer, package, settings); + } - stream.Position = 0; - var streamRef = new DotNetStreamReference(stream); + stream.Position = 0; + var streamRef = new DotNetStreamReference(stream); - // Call JS with our instanceId - await _jsModule.InvokeVoidAsync("streamedJsHandler", token, streamRef, instanceId, DotNetObjectReference.Create(this)); + // Call JS with our instanceId + await _jsModule.InvokeVoidAsync("streamedJsHandler", token, streamRef, instanceId, DotNetObjectReference.Create(this)); - MagicJsChunkProcessor.RegisterInstance(instanceId); + MagicJsChunkProcessor.RegisterInstance(instanceId); - bool isCompleted = false; + bool isCompleted = false; - try + try + { + while (!isCompleted) { - while (!isCompleted) + string? completedItem; + try { - string? completedItem; - try - { - completedItem = MagicJsChunkProcessor.GetCompletedItem(instanceId); - } - catch (Exception queueError) + completedItem = MagicJsChunkProcessor.GetCompletedItem(instanceId); + } + catch (Exception queueError) + { + MagicJsChunkProcessor.RemoveInstance(instanceId); + throw new InvalidOperationException($"Failed to retrieve chunk for instance {instanceId}.", queueError); + } + + if (completedItem != null) + { + if (completedItem == "STREAM_COMPLETE") { - MagicJsChunkProcessor.RemoveInstance(instanceId); - throw new InvalidOperationException($"Failed to retrieve chunk for instance {instanceId}.", queueError); + isCompleted = true; + break; } - if (completedItem != null) + T? deserializedItem; + try { - if (completedItem == "STREAM_COMPLETE") - { - isCompleted = true; - break; - } - - T? deserializedItem; - try - { - deserializedItem = MagicSerializationHelper.DeserializeObject(completedItem, settings); - } - catch (Exception deserializationError) - { - MagicJsChunkProcessor.RemoveInstance(instanceId); - throw new InvalidOperationException($"Failed to deserialize chunk for instance {instanceId}.", deserializationError); - } - - yield return deserializedItem; + deserializedItem = MagicSerializationHelper.DeserializeObject(completedItem, settings); } - else + catch (Exception deserializationError) { - await Task.Delay(15, token); + MagicJsChunkProcessor.RemoveInstance(instanceId); + throw new InvalidOperationException($"Failed to deserialize chunk for instance {instanceId}.", deserializationError); } + + yield return deserializedItem; + } + else + { + await Task.Delay(15, token); } } - finally - { - // Ensure cleanup happens even if an error occurs - MagicJsChunkProcessor.RemoveInstance(instanceId); - } } + finally + { + // Ensure cleanup happens even if an error occurs + MagicJsChunkProcessor.RemoveInstance(instanceId); + } + } - [JSInvokable("ProcessJsChunk")] - public Task ProcessJsChunk(string instanceId, string chunkInstanceId, int yieldOrderIndex, string chunk, int chunkIndex, int totalChunks) + [JSInvokable("ProcessJsChunk")] + public Task ProcessJsChunk(string instanceId, string chunkInstanceId, int yieldOrderIndex, string chunk, int chunkIndex, int totalChunks) + { + if (chunkInstanceId == "STREAM_COMPLETE") { - if (chunkInstanceId == "STREAM_COMPLETE") - { - MagicJsChunkProcessor.AddChunk(instanceId, "STREAM_COMPLETE", -1, "", 0, 1); - return Task.CompletedTask; - } - - MagicJsChunkProcessor.AddChunk(instanceId, chunkInstanceId, yieldOrderIndex, chunk, chunkIndex, totalChunks); + MagicJsChunkProcessor.AddChunk(instanceId, "STREAM_COMPLETE", -1, "", 0, 1); return Task.CompletedTask; } + MagicJsChunkProcessor.AddChunk(instanceId, chunkInstanceId, yieldOrderIndex, chunk, chunkIndex, totalChunks); + return Task.CompletedTask; + } - private async Task MagicVoidStreamJsAsync(string modulePath, string functionName, CancellationToken token, params ITypedArgument[] args) - { - await TrueMagicStreamJsAsync(modulePath, functionName, token, true, args); - } - } - -} + private async Task MagicVoidStreamJsAsync(string modulePath, string functionName, CancellationToken token, params ITypedArgument[] args) + { + await TrueMagicStreamJsAsync(modulePath, functionName, token, true, args); + } +} \ No newline at end of file diff --git a/Magic.IndexedDb/Extensions/MagicTableTools.cs b/Magic.IndexedDb/Extensions/MagicTableTools.cs index 4f59ec6..8622079 100644 --- a/Magic.IndexedDb/Extensions/MagicTableTools.cs +++ b/Magic.IndexedDb/Extensions/MagicTableTools.cs @@ -6,24 +6,22 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +public class MagicTableTool where T : class, IMagicTableBase, new() { - public class MagicTableTool where T : class, IMagicTableBase, new() + protected IMagicCompoundIndex CreateCompoundIndex(params Expression>[] keySelectors) { - protected IMagicCompoundIndex CreateCompoundIndex(params Expression>[] keySelectors) - { - return MagicCompoundExtension.CreateIndex(keySelectors); - } - - protected IMagicCompoundKey CreateCompoundKey(params Expression>[] keySelectors) - { - return MagicCompoundExtension.CreateKey(false, keySelectors); - } + return MagicCompoundExtension.CreateIndex(keySelectors); + } - protected IMagicCompoundKey CreatePrimaryKey(Expression> keySelector, bool autoIncrement) - { - return MagicCompoundExtension.CreateKey(autoIncrement, keySelector); - } + protected IMagicCompoundKey CreateCompoundKey(params Expression>[] keySelectors) + { + return MagicCompoundExtension.CreateKey(false, keySelectors); } -} + protected IMagicCompoundKey CreatePrimaryKey(Expression> keySelector, bool autoIncrement) + { + return MagicCompoundExtension.CreateKey(autoIncrement, keySelector); + } +} \ No newline at end of file diff --git a/Magic.IndexedDb/Extensions/MagicUtilities.cs b/Magic.IndexedDb/Extensions/MagicUtilities.cs index 6a68e5c..70833b5 100644 --- a/Magic.IndexedDb/Extensions/MagicUtilities.cs +++ b/Magic.IndexedDb/Extensions/MagicUtilities.cs @@ -6,34 +6,33 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Extensions +namespace Magic.IndexedDb.Extensions; + +internal class MagicUtilities : IMagicUtilities { - internal class MagicUtilities : IMagicUtilities - { - readonly IJSObjectReference _jsModule; - private readonly long _jsMessageSizeBytes; + readonly IJSObjectReference _jsModule; + private readonly long _jsMessageSizeBytes; - /// - /// Ctor - /// - /// - /// - public MagicUtilities(IJSObjectReference jsRuntime, long jsMessageSizeBytes) - { - this._jsModule = jsRuntime; - _jsMessageSizeBytes = jsMessageSizeBytes; - } + /// + /// Ctor + /// + /// + /// + public MagicUtilities(IJSObjectReference jsRuntime, long jsMessageSizeBytes) + { + this._jsModule = jsRuntime; + _jsMessageSizeBytes = jsMessageSizeBytes; + } - /// - /// Returns Mb - /// - /// - public Task GetStorageEstimateAsync(CancellationToken cancellationToken = default) - { - return new MagicJsInvoke(_jsModule, _jsMessageSizeBytes). - CallJsAsync(Cache.MagicDbJsImportPath, + /// + /// Returns Mb + /// + /// + public Task GetStorageEstimateAsync(CancellationToken cancellationToken = default) + { + return new MagicJsInvoke(_jsModule, _jsMessageSizeBytes). + CallJsAsync(Cache.MagicDbJsImportPath, IndexedDbFunctions.GET_STORAGE_ESTIMATE, cancellationToken, [])!; - } } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/Extensions/ServiceCollectionExtensions.cs b/Magic.IndexedDb/Extensions/ServiceCollectionExtensions.cs index 9b86b3a..6a44635 100644 --- a/Magic.IndexedDb/Extensions/ServiceCollectionExtensions.cs +++ b/Magic.IndexedDb/Extensions/ServiceCollectionExtensions.cs @@ -12,43 +12,42 @@ using System.Text.Json; using System.Threading.Tasks; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +public enum BlazorInteropMode : long { - public enum BlazorInteropMode : long - { - /// - /// SignalR default interop send/receive is 32 KB. - /// This will default to 31 KB for safety. - /// - SignalR = 31 * 1024, // 31 KB in bytes + /// + /// SignalR default interop send/receive is 32 KB. + /// This will default to 31 KB for safety. + /// + SignalR = 31 * 1024, // 31 KB in bytes + + /// + /// WASM default interop send/receive is 16 MB. + /// This will default to 15 MB for safety. + /// + WASM = 15 * 1024 * 1024 // 15 MB in bytes +} - /// - /// WASM default interop send/receive is 16 MB. - /// This will default to 15 MB for safety. - /// - WASM = 15 * 1024 * 1024 // 15 MB in bytes +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddMagicBlazorDB(this IServiceCollection services, + BlazorInteropMode interoptMode, bool isDebug) + { + return services.AddMagicBlazorDB((long)interoptMode, isDebug); } - public static class ServiceCollectionExtensions + public static IServiceCollection AddMagicBlazorDB(this IServiceCollection services, + long jsMessageSizeBytes, bool isDebug) { - public static IServiceCollection AddMagicBlazorDB(this IServiceCollection services, - BlazorInteropMode interoptMode, bool isDebug) - { - return services.AddMagicBlazorDB((long)interoptMode, isDebug); - } + services.AddScoped(sp => + new MagicDbFactory(sp.GetRequiredService(), jsMessageSizeBytes)); - public static IServiceCollection AddMagicBlazorDB(this IServiceCollection services, - long jsMessageSizeBytes, bool isDebug) + if (isDebug) { - services.AddScoped(sp => - new MagicDbFactory(sp.GetRequiredService(), jsMessageSizeBytes)); - - if (isDebug) - { - Magic.IndexedDb.Helpers.MagicValidator.ValidateTables(); - } - - return services; + Magic.IndexedDb.Helpers.MagicValidator.ValidateTables(); } + + return services; } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/Extensions/SharedQueryExtensions.cs b/Magic.IndexedDb/Extensions/SharedQueryExtensions.cs index 280db6c..1cc4ad7 100644 --- a/Magic.IndexedDb/Extensions/SharedQueryExtensions.cs +++ b/Magic.IndexedDb/Extensions/SharedQueryExtensions.cs @@ -9,99 +9,98 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Extensions +namespace Magic.IndexedDb.Extensions; + +internal static class SharedQueryExtensions { - internal static class SharedQueryExtensions - { - internal static MagicQuery Take(MagicQuery magicQuery, int amount) + internal static MagicQuery Take(MagicQuery magicQuery, int amount) where T : class + { + var _MagicQuery = new MagicQuery(magicQuery); + var smq = new StoredMagicQuery { - var _MagicQuery = new MagicQuery(magicQuery); - var smq = new StoredMagicQuery - { - additionFunction = MagicQueryFunctions.Take, - intValue = amount - }; - - _MagicQuery.StoredMagicQueries.Add(smq); - return _MagicQuery; - } + additionFunction = MagicQueryFunctions.Take, + intValue = amount + }; - public static MagicQuery TakeLast(MagicQuery magicQuery, int amount) where T : class - { - var _MagicQuery = new MagicQuery(magicQuery); - _MagicQuery.StoredMagicQueries.Add(new StoredMagicQuery - { - additionFunction = MagicQueryFunctions.Take_Last, - intValue = amount - }); - return _MagicQuery; - } + _MagicQuery.StoredMagicQueries.Add(smq); + return _MagicQuery; + } - public static MagicQuery Skip(MagicQuery magicQuery, int amount) where T : class + public static MagicQuery TakeLast(MagicQuery magicQuery, int amount) where T : class + { + var _MagicQuery = new MagicQuery(magicQuery); + _MagicQuery.StoredMagicQueries.Add(new StoredMagicQuery { - var _MagicQuery = new MagicQuery(magicQuery); - _MagicQuery.StoredMagicQueries.Add(new StoredMagicQuery - { - additionFunction = MagicQueryFunctions.Skip, - intValue = amount - }); - return _MagicQuery; - } + additionFunction = MagicQueryFunctions.Take_Last, + intValue = amount + }); + return _MagicQuery; + } - public static MagicQuery OrderBy(MagicQuery magicQuery, Expression> predicate) where T : class + public static MagicQuery Skip(MagicQuery magicQuery, int amount) where T : class + { + var _MagicQuery = new MagicQuery(magicQuery); + _MagicQuery.StoredMagicQueries.Add(new StoredMagicQuery { - var memberExpression = GetMemberExpressionFromLambda(predicate); - var propertyInfo = memberExpression.Member as PropertyInfo; - - if (propertyInfo == null) - throw new ArgumentException("The expression must represent a single property access."); + additionFunction = MagicQueryFunctions.Skip, + intValue = amount + }); + return _MagicQuery; + } - MagicPropertyEntry mpe = PropertyMappingCache.GetPropertyEntry(propertyInfo); + public static MagicQuery OrderBy(MagicQuery magicQuery, Expression> predicate) where T : class + { + var memberExpression = GetMemberExpressionFromLambda(predicate); + var propertyInfo = memberExpression.Member as PropertyInfo; - if (!mpe.PrimaryKey && !mpe.Indexed && !mpe.UniqueIndex) - { - // Intentionally preserved your comment - // throw new ArgumentException(...); - } + if (propertyInfo == null) + throw new ArgumentException("The expression must represent a single property access."); - var _MagicQuery = new MagicQuery(magicQuery); - _MagicQuery.StoredMagicQueries.Add(new StoredMagicQuery - { - additionFunction = MagicQueryFunctions.Order_By, - property = mpe.JsPropertyName - }); + MagicPropertyEntry mpe = PropertyMappingCache.GetPropertyEntry(propertyInfo); - return _MagicQuery; + if (!mpe.PrimaryKey && !mpe.Indexed && !mpe.UniqueIndex) + { + // Intentionally preserved your comment + // throw new ArgumentException(...); } - public static MagicQuery OrderByDescending(MagicQuery magicQuery, Expression> predicate) where T : class + var _MagicQuery = new MagicQuery(magicQuery); + _MagicQuery.StoredMagicQueries.Add(new StoredMagicQuery { - var memberExpression = GetMemberExpressionFromLambda(predicate); - var propertyInfo = memberExpression.Member as PropertyInfo; + additionFunction = MagicQueryFunctions.Order_By, + property = mpe.JsPropertyName + }); - if (propertyInfo == null) - throw new ArgumentException("The expression must represent a single property access."); + return _MagicQuery; + } - var _MagicQuery = new MagicQuery(magicQuery); - _MagicQuery.StoredMagicQueries.Add(new StoredMagicQuery - { - additionFunction = MagicQueryFunctions.Order_By_Descending, - property = PropertyMappingCache.GetJsPropertyName(propertyInfo) - }); + public static MagicQuery OrderByDescending(MagicQuery magicQuery, Expression> predicate) where T : class + { + var memberExpression = GetMemberExpressionFromLambda(predicate); + var propertyInfo = memberExpression.Member as PropertyInfo; - return _MagicQuery; - } + if (propertyInfo == null) + throw new ArgumentException("The expression must represent a single property access."); - private static MemberExpression GetMemberExpressionFromLambda(Expression> expression) + var _MagicQuery = new MagicQuery(magicQuery); + _MagicQuery.StoredMagicQueries.Add(new StoredMagicQuery { - if (expression.Body is MemberExpression m) - return m; + additionFunction = MagicQueryFunctions.Order_By_Descending, + property = PropertyMappingCache.GetJsPropertyName(propertyInfo) + }); - if (expression.Body is UnaryExpression u && u.Operand is MemberExpression um) - return um; + return _MagicQuery; + } - throw new ArgumentException("The expression must represent a single property access."); - } + private static MemberExpression GetMemberExpressionFromLambda(Expression> expression) + { + if (expression.Body is MemberExpression m) + return m; + + if (expression.Body is UnaryExpression u && u.Operand is MemberExpression um) + return um; + + throw new ArgumentException("The expression must represent a single property access."); } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/Factories/IMagicIndexedDb.cs b/Magic.IndexedDb/Factories/IMagicIndexedDb.cs index e6963fe..4d2cdec 100644 --- a/Magic.IndexedDb/Factories/IMagicIndexedDb.cs +++ b/Magic.IndexedDb/Factories/IMagicIndexedDb.cs @@ -2,57 +2,56 @@ using Magic.IndexedDb.LinqTranslation.Interfaces; using Magic.IndexedDb.Models; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +public interface IMagicIndexedDb { - public interface IMagicIndexedDb - { - - /// - /// Allows manually inserting database names and schema names via strings. - /// Please be careful, Magic IndexDB can't protect you from potential issues - /// if you use this. - /// - /// - /// - /// - /// - //ValueTask> QueryOverride(string? databaseNameOverride = null, - // string? schemaNameOverride = null) where T : class, IMagicTableBase, new(); - - /// - /// Opens a ready query to utilize IndexDB database and capabilities utilizing LINQ to IndexDB. - /// Use example: IMagicQuery query = await _MagicDb.Query(); - /// - /// - /// - /// - /// - ValueTask> Query() - where T : class, IMagicTableBase, new(); - - /// - /// Opens a query for a table to a specified database. - /// - /// - /// - /// - ValueTask> Query(Func dbSetSelector) where T : class, IMagicTableBase, new(); - - /// - /// Utilize any Database you want, but be careful that it's assigned! - /// Highly suggested you utilize `Query(Func dbSetSelector)` - /// - /// - /// - /// - //ValueTask> Query(IndexedDbSet indexedDbSet) - // where T : class, IMagicTableBase, new(); - - // I think this should be under a utilities functionality? - Task GetStorageEstimateAsync(CancellationToken cancellationToken = default); - - //ValueTask Database(); - - ValueTask Database(IndexedDbSet indexedDbSet); - } + + /// + /// Allows manually inserting database names and schema names via strings. + /// Please be careful, Magic IndexDB can't protect you from potential issues + /// if you use this. + /// + /// + /// + /// + /// + //ValueTask> QueryOverride(string? databaseNameOverride = null, + // string? schemaNameOverride = null) where T : class, IMagicTableBase, new(); + + /// + /// Opens a ready query to utilize IndexDB database and capabilities utilizing LINQ to IndexDB. + /// Use example: IMagicQuery query = await _MagicDb.Query(); + /// + /// + /// + /// + /// + ValueTask> Query() + where T : class, IMagicTableBase, new(); + + /// + /// Opens a query for a table to a specified database. + /// + /// + /// + /// + ValueTask> Query(Func dbSetSelector) where T : class, IMagicTableBase, new(); + + /// + /// Utilize any Database you want, but be careful that it's assigned! + /// Highly suggested you utilize `Query(Func dbSetSelector)` + /// + /// + /// + /// + //ValueTask> Query(IndexedDbSet indexedDbSet) + // where T : class, IMagicTableBase, new(); + + // I think this should be under a utilities functionality? + Task GetStorageEstimateAsync(CancellationToken cancellationToken = default); + + //ValueTask Database(); + + ValueTask Database(IndexedDbSet indexedDbSet); } \ No newline at end of file diff --git a/Magic.IndexedDb/Factories/MagicDbFactory.cs b/Magic.IndexedDb/Factories/MagicDbFactory.cs index 3915b60..0e86a6d 100644 --- a/Magic.IndexedDb/Factories/MagicDbFactory.cs +++ b/Magic.IndexedDb/Factories/MagicDbFactory.cs @@ -10,25 +10,25 @@ using System.Diagnostics; using System.Threading; -namespace Magic.IndexedDb.Factories -{ - internal class MagicDbFactory : IMagicIndexedDb, IAsyncDisposable - { - // null value indicates that the factory is disposed - Lazy>? _jsModule; - Lazy> _magicJsManager; - private readonly long _jsMessageSizeBytes; - public long JsMessageSizeBytes => _jsMessageSizeBytes; - - public MagicDbFactory(IJSRuntime jSRuntime, long jsMessageSizeBytes) - { - _jsMessageSizeBytes = jsMessageSizeBytes; - this._jsModule = new(() => jSRuntime.InvokeAsync( +namespace Magic.IndexedDb.Factories; + +internal class MagicDbFactory : IMagicIndexedDb, IAsyncDisposable +{ + // null value indicates that the factory is disposed + Lazy>? _jsModule; + Lazy> _magicJsManager; + private readonly long _jsMessageSizeBytes; + public long JsMessageSizeBytes => _jsMessageSizeBytes; + + public MagicDbFactory(IJSRuntime jSRuntime, long jsMessageSizeBytes) + { + _jsMessageSizeBytes = jsMessageSizeBytes; + this._jsModule = new(() => jSRuntime.InvokeAsync( "import", "./_content/Magic.IndexedDb/magicDbMethods.js").AsTask(), - isThreadSafe: true); + isThreadSafe: true); - this._magicJsManager = new(async () => + this._magicJsManager = new(async () => { var jsModule = await this._jsModule.Value; @@ -59,124 +59,123 @@ public MagicDbFactory(IJSRuntime jSRuntime, long jsMessageSizeBytes) return manager; }, isThreadSafe: true); - } - - public async ValueTask DisposeAsync() - { - var js = _jsModule; - _jsModule = null; + } - if (js is null || !js.IsValueCreated) - return; + public async ValueTask DisposeAsync() + { + var js = _jsModule; + _jsModule = null; - IJSObjectReference module; - try - { - module = await js.Value; - } - catch - { - return; - } - - try - { - var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10)); - await module.InvokeVoidAsync(IndexedDbFunctions.CLOSE_ALL, timeout.Token); - } - catch - { - // do nothing - } + if (js is null || !js.IsValueCreated) + return; - try - { - await module.DisposeAsync(); - } - catch - { - // do nothing - } + IJSObjectReference module; + try + { + module = await js.Value; + } + catch + { + return; } - /// - /// Get storage estimate using the shared JS module. - /// - public async Task GetStorageEstimateAsync(CancellationToken cancellationToken = default) + try + { + var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + await module.InvokeVoidAsync(IndexedDbFunctions.CLOSE_ALL, timeout.Token); + } + catch { - ObjectDisposedException.ThrowIf(this._jsModule is null, this); + // do nothing + } - var jsModule = await this._jsModule.Value; - var magicUtility = new MagicUtilities(jsModule, _jsMessageSizeBytes); - return await magicUtility.GetStorageEstimateAsync(); + try + { + await module.DisposeAsync(); } - [Obsolete("Not fully implemented yet until full migration protocol finished.")] - public async ValueTask> Query(IndexedDbSet indexedDbSet) - where T : class, IMagicTableBase, new() + catch { - ObjectDisposedException.ThrowIf(this._jsModule is null, this); + // do nothing + } + } - // Get database name and schema name - string databaseName = indexedDbSet.DatabaseName; - string schemaName = SchemaHelper.GetTableName(); + /// + /// Get storage estimate using the shared JS module. + /// + public async Task GetStorageEstimateAsync(CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(this._jsModule is null, this); - return await QueryOverride(databaseName, schemaName); - } + var jsModule = await this._jsModule.Value; + var magicUtility = new MagicUtilities(jsModule, _jsMessageSizeBytes); + return await magicUtility.GetStorageEstimateAsync(); + } + [Obsolete("Not fully implemented yet until full migration protocol finished.")] + public async ValueTask> Query(IndexedDbSet indexedDbSet) + where T : class, IMagicTableBase, new() + { + ObjectDisposedException.ThrowIf(this._jsModule is null, this); + // Get database name and schema name + string databaseName = indexedDbSet.DatabaseName; + string schemaName = SchemaHelper.GetTableName(); - public async ValueTask> Query( - Func dbSetSelector) - where T : class, IMagicTableBase, new() - { - ObjectDisposedException.ThrowIf(this._jsModule is null, this); + return await QueryOverride(databaseName, schemaName); + } + + + public async ValueTask> Query( + Func dbSetSelector) + where T : class, IMagicTableBase, new() + { + ObjectDisposedException.ThrowIf(this._jsModule is null, this); - // Create an instance of T to access `DbSets` - var modelInstance = new T(); + // Create an instance of T to access `DbSets` + var modelInstance = new T(); - // Retrieve the IndexedDbSet using the provided predicate - IndexedDbSet selectedDbSet = dbSetSelector(modelInstance); + // Retrieve the IndexedDbSet using the provided predicate + IndexedDbSet selectedDbSet = dbSetSelector(modelInstance); - // Get database name and schema name - string databaseName = selectedDbSet.DatabaseName; - string schemaName = SchemaHelper.GetTableName(); + // Get database name and schema name + string databaseName = selectedDbSet.DatabaseName; + string schemaName = SchemaHelper.GetTableName(); #pragma warning disable CS0618 - return await QueryOverride(databaseName, schemaName); + return await QueryOverride(databaseName, schemaName); #pragma warning restore CS0618 - } - - /// - /// Query the database for a given type. Automatically opens the database if needed. - /// - public async ValueTask> Query() - where T : class, IMagicTableBase, new() - { - ObjectDisposedException.ThrowIf(this._jsModule is null, this); + } - string databaseName = SchemaHelper.GetDefaultDatabaseName(); - string schemaName = SchemaHelper.GetTableName(); - var dbManager = await this._magicJsManager.Value; + /// + /// Query the database for a given type. Automatically opens the database if needed. + /// + public async ValueTask> Query() + where T : class, IMagicTableBase, new() + { + ObjectDisposedException.ThrowIf(this._jsModule is null, this); + + string databaseName = SchemaHelper.GetDefaultDatabaseName(); + string schemaName = SchemaHelper.GetTableName(); + var dbManager = await this._magicJsManager.Value; #pragma warning disable CS0618 - return await QueryOverride(databaseName, schemaName); + return await QueryOverride(databaseName, schemaName); #pragma warning restore CS0618 - } + } - [Obsolete("Not decided if this will be built in further or removed")] - public async ValueTask> QueryOverride(string databaseNameOverride, string schemaNameOverride) - where T: class, IMagicTableBase, new () - { - ObjectDisposedException.ThrowIf(this._jsModule is null, this); + [Obsolete("Not decided if this will be built in further or removed")] + public async ValueTask> QueryOverride(string databaseNameOverride, string schemaNameOverride) + where T: class, IMagicTableBase, new () + { + ObjectDisposedException.ThrowIf(this._jsModule is null, this); - var dbManager = await this._magicJsManager.Value; - return dbManager.Query(databaseNameOverride, schemaNameOverride); - } + var dbManager = await this._magicJsManager.Value; + return dbManager.Query(databaseNameOverride, schemaNameOverride); + } - public async ValueTask Database(IndexedDbSet indexedDbSet) - { - ObjectDisposedException.ThrowIf(this._jsModule is null, this); + public async ValueTask Database(IndexedDbSet indexedDbSet) + { + ObjectDisposedException.ThrowIf(this._jsModule is null, this); - var dbManager = await this._magicJsManager.Value; - return dbManager.Database(dbManager, indexedDbSet); - } + var dbManager = await this._magicJsManager.Value; + return dbManager.Database(dbManager, indexedDbSet); } } \ No newline at end of file diff --git a/Magic.IndexedDb/Helpers/AttributeHelpers.cs b/Magic.IndexedDb/Helpers/AttributeHelpers.cs index a968eba..b8792f4 100644 --- a/Magic.IndexedDb/Helpers/AttributeHelpers.cs +++ b/Magic.IndexedDb/Helpers/AttributeHelpers.cs @@ -7,72 +7,71 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Helpers +namespace Magic.IndexedDb.Helpers; + +public class PrimaryKeys { - public class PrimaryKeys - { - public string JsName { get; set; } - public object Value { get; set; } - } - public static class AttributeHelpers - { - private static readonly ConcurrentDictionary _primaryKeyCache = new(); + public string JsName { get; set; } + public object Value { get; set; } +} +public static class AttributeHelpers +{ + private static readonly ConcurrentDictionary _primaryKeyCache = new(); - public static List GetPrimaryKeys(T item) where T : class - { - if (item is null) - throw new ArgumentNullException(nameof(item)); + public static List GetPrimaryKeys(T item) where T : class + { + if (item is null) + throw new ArgumentNullException(nameof(item)); - var primaryKeyProps = GetPrimaryKeyProperties(typeof(T)).PropertyInfos; + var primaryKeyProps = GetPrimaryKeyProperties(typeof(T)).PropertyInfos; - return primaryKeyProps - .Select(p => new PrimaryKeys - { - JsName = PropertyMappingCache.GetJsPropertyName(p), // Convert this if needed (e.g., camelCase conversion) - Value = p.GetValue(item)! - }) - .ToList(); - } + return primaryKeyProps + .Select(p => new PrimaryKeys + { + JsName = PropertyMappingCache.GetJsPropertyName(p), // Convert this if needed (e.g., camelCase conversion) + Value = p.GetValue(item)! + }) + .ToList(); + } - public static Type[] GetPrimaryKeyTypes() where T : IMagicTableBase - { - return GetPrimaryKeyProperties(typeof(T)).PropertyInfos.Select(p => p.PropertyType).ToArray(); - } + public static Type[] GetPrimaryKeyTypes() where T : IMagicTableBase + { + return GetPrimaryKeyProperties(typeof(T)).PropertyInfos.Select(p => p.PropertyType).ToArray(); + } - public static void ValidatePrimaryKey(object[] keys) where T : IMagicTableBase - { - var expectedTypes = GetPrimaryKeyTypes(); + public static void ValidatePrimaryKey(object[] keys) where T : IMagicTableBase + { + var expectedTypes = GetPrimaryKeyTypes(); - if (keys.Length != expectedTypes.Length) - throw new ArgumentException($"Invalid number of keys. Expected: {expectedTypes.Length}, received: {keys.Length}."); + if (keys.Length != expectedTypes.Length) + throw new ArgumentException($"Invalid number of keys. Expected: {expectedTypes.Length}, received: {keys.Length}."); - for (int i = 0; i < keys.Length; i++) + for (int i = 0; i < keys.Length; i++) + { + if (keys[i] == null || !expectedTypes[i].IsInstanceOfType(keys[i])) { - if (keys[i] == null || !expectedTypes[i].IsInstanceOfType(keys[i])) - { - throw new ArgumentException($"Invalid key type at index {i}. Expected: {expectedTypes[i]}, received: {keys[i]?.GetType()}."); - } + throw new ArgumentException($"Invalid key type at index {i}. Expected: {expectedTypes[i]}, received: {keys[i]?.GetType()}."); } } + } - private static IMagicCompoundKey GetPrimaryKeyProperties(Type type) + private static IMagicCompoundKey GetPrimaryKeyProperties(Type type) + { + return _primaryKeyCache.GetOrAdd(type, t => { - return _primaryKeyCache.GetOrAdd(type, t => - { - if (!typeof(IMagicTableBase).IsAssignableFrom(t)) - throw new InvalidOperationException($"Type '{t.Name}' must implement IMagicTableBase."); + if (!typeof(IMagicTableBase).IsAssignableFrom(t)) + throw new InvalidOperationException($"Type '{t.Name}' must implement IMagicTableBase."); - var instance = Activator.CreateInstance(t) as IMagicTableBase; - if (instance == null) - throw new InvalidOperationException($"Unable to create an instance of '{t.Name}'."); + var instance = Activator.CreateInstance(t) as IMagicTableBase; + if (instance == null) + throw new InvalidOperationException($"Unable to create an instance of '{t.Name}'."); - var compoundKey = instance.GetKeys(); - if (compoundKey == null || compoundKey.PropertyInfos.Length == 0) - throw new InvalidOperationException($"Type '{t.Name}' must have at least one primary key."); + var compoundKey = instance.GetKeys(); + if (compoundKey == null || compoundKey.PropertyInfos.Length == 0) + throw new InvalidOperationException($"Type '{t.Name}' must have at least one primary key."); - return compoundKey; - }); - } + return compoundKey; + }); } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/Helpers/ExpandoToTypeConverter.cs b/Magic.IndexedDb/Helpers/ExpandoToTypeConverter.cs index c8387c2..50ae100 100644 --- a/Magic.IndexedDb/Helpers/ExpandoToTypeConverter.cs +++ b/Magic.IndexedDb/Helpers/ExpandoToTypeConverter.cs @@ -1,46 +1,43 @@ using System.Dynamic; using System.Linq.Expressions; -namespace Magic.IndexedDb.Helpers +namespace Magic.IndexedDb.Helpers; + +public static class ExpandoToTypeConverter { - public static class ExpandoToTypeConverter - { - private static readonly Dictionary> PropertySetters = new(); - private static readonly Dictionary NonConcreteTypeCache = new(); + private static readonly Dictionary> PropertySetters = new(); + private static readonly Dictionary NonConcreteTypeCache = new(); - private static readonly bool IsConcrete; - private static readonly bool HasParameterlessConstructor; + private static readonly bool IsConcrete; + private static readonly bool HasParameterlessConstructor; + + static ExpandoToTypeConverter() + { + Type type = typeof(T); + IsConcrete = !(type.IsAbstract || type.IsInterface); + HasParameterlessConstructor = type.GetConstructor(Type.EmptyTypes) != null; - static ExpandoToTypeConverter() + if (IsConcrete && HasParameterlessConstructor) { - Type type = typeof(T); - IsConcrete = !(type.IsAbstract || type.IsInterface); - HasParameterlessConstructor = type.GetConstructor(Type.EmptyTypes) != null; - - if (IsConcrete && HasParameterlessConstructor) - { - PrecomputePropertySetters(type); - } + PrecomputePropertySetters(type); } + } - private static void PrecomputePropertySetters(Type type) + private static void PrecomputePropertySetters(Type type) + { + foreach (var prop in type.GetProperties().Where(p => p.CanWrite)) { - foreach (var prop in type.GetProperties().Where(p => p.CanWrite)) - { - var targetExp = Expression.Parameter(type); - var valueExp = Expression.Parameter(typeof(object)); + var targetExp = Expression.Parameter(type); + var valueExp = Expression.Parameter(typeof(object)); - var convertedValueExp = Expression.Convert(valueExp, prop.PropertyType); + var convertedValueExp = Expression.Convert(valueExp, prop.PropertyType); - var propertySetterExp = Expression.Lambda>( - Expression.Assign(Expression.Property(targetExp, prop), convertedValueExp), - targetExp, valueExp - ); + var propertySetterExp = Expression.Lambda>( + Expression.Assign(Expression.Property(targetExp, prop), convertedValueExp), + targetExp, valueExp + ); - PropertySetters[prop.Name] = propertySetterExp.Compile(); - } + PropertySetters[prop.Name] = propertySetterExp.Compile(); } } - - -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/Helpers/ExpressionFlattener.cs b/Magic.IndexedDb/Helpers/ExpressionFlattener.cs index 544d7e7..a7a55bc 100644 --- a/Magic.IndexedDb/Helpers/ExpressionFlattener.cs +++ b/Magic.IndexedDb/Helpers/ExpressionFlattener.cs @@ -5,257 +5,255 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Helpers +namespace Magic.IndexedDb.Helpers; + +/// +/// IndexDB requires flattening complex nested OR statements as it's not supported by default. +/// Flattening resolves this issue but without optimizations flattened methods will call +/// large numbers of redundant queries without some help. +/// +public static class ExpressionFlattener { - /// - /// IndexDB requires flattening complex nested OR statements as it's not supported by default. - /// Flattening resolves this issue but without optimizations flattened methods will call - /// large numbers of redundant queries without some help. - /// - public static class ExpressionFlattener + public static Expression> FlattenAndOptimize(Expression> expr) { - public static Expression> FlattenAndOptimize(Expression> expr) - { - // Step 1: Flatten OrElse structures - var flattenedBody = FlattenOrElseRecursive(expr.Body); + // Step 1: Flatten OrElse structures + var flattenedBody = FlattenOrElseRecursive(expr.Body); - // Step 2: Optimize the expression (deduplicate and simplify) - var optimizedBody = OptimizeExpression(flattenedBody); + // Step 2: Optimize the expression (deduplicate and simplify) + var optimizedBody = OptimizeExpression(flattenedBody); - return Expression.Lambda>(optimizedBody, expr.Parameters); - } + return Expression.Lambda>(optimizedBody, expr.Parameters); + } - private static Expression FlattenOrElseRecursive(Expression expr) + private static Expression FlattenOrElseRecursive(Expression expr) + { + if (expr is BinaryExpression binaryExpr) { - if (expr is BinaryExpression binaryExpr) + if (binaryExpr.NodeType == ExpressionType.OrElse) { - if (binaryExpr.NodeType == ExpressionType.OrElse) - { - var terms = new HashSet(new LogicalExpressionComparer()); - CollectOrTerms(binaryExpr, terms); - return BuildOrChain(terms); - } - else if (binaryExpr.NodeType == ExpressionType.AndAlso) - { - var left = FlattenOrElseRecursive(binaryExpr.Left); - var right = FlattenOrElseRecursive(binaryExpr.Right); + var terms = new HashSet(new LogicalExpressionComparer()); + CollectOrTerms(binaryExpr, terms); + return BuildOrChain(terms); + } + else if (binaryExpr.NodeType == ExpressionType.AndAlso) + { + var left = FlattenOrElseRecursive(binaryExpr.Left); + var right = FlattenOrElseRecursive(binaryExpr.Right); - if (ContainsOrElse(left) || ContainsOrElse(right)) - { - return DistributeAndOverOr(left, right); - } - return SimplifyAndChain(left, right); + if (ContainsOrElse(left) || ContainsOrElse(right)) + { + return DistributeAndOverOr(left, right); } + return SimplifyAndChain(left, right); } - return expr; } + return expr; + } - /// - /// Recursively collects all expressions in an OrElse chain. - /// - private static void CollectOrTerms(Expression expr, HashSet terms) + /// + /// Recursively collects all expressions in an OrElse chain. + /// + private static void CollectOrTerms(Expression expr, HashSet terms) + { + if (expr is BinaryExpression be && be.NodeType == ExpressionType.OrElse) { - if (expr is BinaryExpression be && be.NodeType == ExpressionType.OrElse) - { - CollectOrTerms(be.Left, terms); - CollectOrTerms(be.Right, terms); - } - else - { - terms.Add(expr); - } + CollectOrTerms(be.Left, terms); + CollectOrTerms(be.Right, terms); } - - /// - /// Rebuilds an OrElse chain from a set of expressions. - /// - private static Expression BuildOrChain(IEnumerable terms) + else { - var termList = terms.Distinct(LogicalExpressionComparer.Instance).ToList(); - termList.Sort(LogicalExpressionComparer.Instance); - return termList.Aggregate(Expression.OrElse); + terms.Add(expr); } + } - private static Expression DistributeAndOverOr(Expression left, Expression right) - { - var leftTerms = new HashSet(new LogicalExpressionComparer()); - var rightTerms = new HashSet(new LogicalExpressionComparer()); + /// + /// Rebuilds an OrElse chain from a set of expressions. + /// + private static Expression BuildOrChain(IEnumerable terms) + { + var termList = terms.Distinct(LogicalExpressionComparer.Instance).ToList(); + termList.Sort(LogicalExpressionComparer.Instance); + return termList.Aggregate(Expression.OrElse); + } + + private static Expression DistributeAndOverOr(Expression left, Expression right) + { + var leftTerms = new HashSet(new LogicalExpressionComparer()); + var rightTerms = new HashSet(new LogicalExpressionComparer()); - CollectOrTerms(left, leftTerms); - CollectOrTerms(right, rightTerms); + CollectOrTerms(left, leftTerms); + CollectOrTerms(right, rightTerms); - var newTerms = new HashSet(new LogicalExpressionComparer()); - foreach (var l in leftTerms) + var newTerms = new HashSet(new LogicalExpressionComparer()); + foreach (var l in leftTerms) + { + foreach (var r in rightTerms) { - foreach (var r in rightTerms) - { - newTerms.Add(Expression.AndAlso(l, r)); - } + newTerms.Add(Expression.AndAlso(l, r)); } - - return BuildOrChain(newTerms); } - private static bool ContainsOrElse(Expression expr) + return BuildOrChain(newTerms); + } + + private static bool ContainsOrElse(Expression expr) + { + return expr is BinaryExpression binaryExpr && + (binaryExpr.NodeType == ExpressionType.OrElse || + ContainsOrElse(binaryExpr.Left) || + ContainsOrElse(binaryExpr.Right)); + } + + /// + /// Recursively collects all expressions in an AndAlso chain. + /// + private static void CollectAndTerms(Expression expr, HashSet terms) + { + if (expr is BinaryExpression be && be.NodeType == ExpressionType.AndAlso) { - return expr is BinaryExpression binaryExpr && - (binaryExpr.NodeType == ExpressionType.OrElse || - ContainsOrElse(binaryExpr.Left) || - ContainsOrElse(binaryExpr.Right)); + CollectAndTerms(be.Left, terms); + CollectAndTerms(be.Right, terms); } - - /// - /// Recursively collects all expressions in an AndAlso chain. - /// - private static void CollectAndTerms(Expression expr, HashSet terms) + else { - if (expr is BinaryExpression be && be.NodeType == ExpressionType.AndAlso) - { - CollectAndTerms(be.Left, terms); - CollectAndTerms(be.Right, terms); - } - else - { - terms.Add(expr); - } + terms.Add(expr); } + } - /// - /// Optimizes an expression by recursively flattening AndAlso/OrElse trees, - /// deduplicating equivalent subexpressions, and short-circuiting redundant branches. - /// - private static Expression OptimizeExpression(Expression expr) + /// + /// Optimizes an expression by recursively flattening AndAlso/OrElse trees, + /// deduplicating equivalent subexpressions, and short-circuiting redundant branches. + /// + private static Expression OptimizeExpression(Expression expr) + { + if (expr is BinaryExpression be) { - if (expr is BinaryExpression be) + // Recursively optimize children first. + if (be.NodeType == ExpressionType.AndAlso) { - // Recursively optimize children first. - if (be.NodeType == ExpressionType.AndAlso) - { - var left = OptimizeExpression(be.Left); - var right = OptimizeExpression(be.Right); + var left = OptimizeExpression(be.Left); + var right = OptimizeExpression(be.Right); - // If both sides are identical, return one. - if (LogicalExpressionComparer.Instance.Equals(left, right)) - return left; + // If both sides are identical, return one. + if (LogicalExpressionComparer.Instance.Equals(left, right)) + return left; - // Flatten all AndAlso terms into a set. - var andTerms = new HashSet(LogicalExpressionComparer.Instance); - CollectAndTerms(left, andTerms); - CollectAndTerms(right, andTerms); + // Flatten all AndAlso terms into a set. + var andTerms = new HashSet(LogicalExpressionComparer.Instance); + CollectAndTerms(left, andTerms); + CollectAndTerms(right, andTerms); - // If after deduplication we only have one term, just return it. - if (andTerms.Count == 1) - return andTerms.First(); + // If after deduplication we only have one term, just return it. + if (andTerms.Count == 1) + return andTerms.First(); - return BuildAndChain(andTerms); - } - else if (be.NodeType == ExpressionType.OrElse) - { - var left = OptimizeExpression(be.Left); - var right = OptimizeExpression(be.Right); + return BuildAndChain(andTerms); + } + else if (be.NodeType == ExpressionType.OrElse) + { + var left = OptimizeExpression(be.Left); + var right = OptimizeExpression(be.Right); - // If both sides are identical, return one. - if (LogicalExpressionComparer.Instance.Equals(left, right)) - return left; + // If both sides are identical, return one. + if (LogicalExpressionComparer.Instance.Equals(left, right)) + return left; - // Flatten all OrElse terms into a set. - var orTerms = new HashSet(LogicalExpressionComparer.Instance); - CollectOrTerms(left, orTerms); - CollectOrTerms(right, orTerms); + // Flatten all OrElse terms into a set. + var orTerms = new HashSet(LogicalExpressionComparer.Instance); + CollectOrTerms(left, orTerms); + CollectOrTerms(right, orTerms); - if (orTerms.Count == 1) - return orTerms.First(); + if (orTerms.Count == 1) + return orTerms.First(); - return BuildOrChain(orTerms); - } + return BuildOrChain(orTerms); } - return expr; } + return expr; + } - private static Expression SimplifyAndChain(Expression left, Expression right) - { - var andTerms = new HashSet(new LogicalExpressionComparer()) { left, right }; + private static Expression SimplifyAndChain(Expression left, Expression right) + { + var andTerms = new HashSet(new LogicalExpressionComparer()) { left, right }; - if (andTerms.Count == 1) return andTerms.First(); // A && A → A + if (andTerms.Count == 1) return andTerms.First(); // A && A → A - return BuildAndChain(andTerms); - } + return BuildAndChain(andTerms); + } - /// - /// Rebuilds an AndAlso chain from a set of expressions. - /// - private static Expression BuildAndChain(IEnumerable terms) - { - var termList = terms.Distinct(LogicalExpressionComparer.Instance).ToList(); - termList.Sort(LogicalExpressionComparer.Instance); - return termList.Aggregate(Expression.AndAlso); - } + /// + /// Rebuilds an AndAlso chain from a set of expressions. + /// + private static Expression BuildAndChain(IEnumerable terms) + { + var termList = terms.Distinct(LogicalExpressionComparer.Instance).ToList(); + termList.Sort(LogicalExpressionComparer.Instance); + return termList.Aggregate(Expression.AndAlso); + } - /// - /// A custom comparer that determines logical equivalence of expressions. - /// Adjust or extend this to detect more cases if needed. - /// - private class LogicalExpressionComparer : IEqualityComparer, IComparer - { - public static readonly LogicalExpressionComparer Instance = new(); + /// + /// A custom comparer that determines logical equivalence of expressions. + /// Adjust or extend this to detect more cases if needed. + /// + private class LogicalExpressionComparer : IEqualityComparer, IComparer + { + public static readonly LogicalExpressionComparer Instance = new(); + + public bool Equals(Expression? x, Expression? y) => Compare(x, y) is 0; - public bool Equals(Expression? x, Expression? y) => Compare(x, y) is 0; + public int GetHashCode(Expression obj) => obj.ToString().GetHashCode(); - public int GetHashCode(Expression obj) => obj.ToString().GetHashCode(); + public int Compare(Expression? x, Expression? y) + { + if (ReferenceEquals(x, y)) return 0; + if (x is null) return -1; + if (y is null) return 1; - public int Compare(Expression? x, Expression? y) + if (x.NodeType != y.NodeType) + return x.NodeType.CompareTo(y.NodeType); + + switch (x) { - if (ReferenceEquals(x, y)) return 0; - if (x is null) return -1; - if (y is null) return 1; + case ConstantExpression cx when y is ConstantExpression cy: + return Comparer.Default.Compare(cx.Value, cy.Value); - if (x.NodeType != y.NodeType) - return x.NodeType.CompareTo(y.NodeType); + case MemberExpression mx when y is MemberExpression my: + return string.Compare(mx.Member.Name, my.Member.Name, StringComparison.Ordinal); - switch (x) - { - case ConstantExpression cx when y is ConstantExpression cy: - return Comparer.Default.Compare(cx.Value, cy.Value); - - case MemberExpression mx when y is MemberExpression my: - return string.Compare(mx.Member.Name, my.Member.Name, StringComparison.Ordinal); - - case BinaryExpression bx when y is BinaryExpression by: - // For commutative operations, check both orders. - if (IsCommutative(bx.NodeType) && - Equals(bx.Left, by.Right) && - Equals(bx.Right, by.Left)) - { - return 0; - } - int leftCompare = Compare(bx.Left, by.Left); - return leftCompare != 0 ? leftCompare : Compare(bx.Right, by.Right); - - case MethodCallExpression mx when y is MethodCallExpression my: - int methodCompare = string.Compare(mx.Method.Name, my.Method.Name, StringComparison.Ordinal); - if (methodCompare != 0) return methodCompare; - // Compare arguments sequentially. - for (int i = 0; i < Math.Min(mx.Arguments.Count, my.Arguments.Count); i++) - { - int argCompare = Compare(mx.Arguments[i], my.Arguments[i]); - if (argCompare != 0) return argCompare; - } - return mx.Arguments.Count.CompareTo(my.Arguments.Count); - - default: - return string.Compare(x.ToString(), y.ToString(), StringComparison.Ordinal); - } - } + case BinaryExpression bx when y is BinaryExpression by: + // For commutative operations, check both orders. + if (IsCommutative(bx.NodeType) && + Equals(bx.Left, by.Right) && + Equals(bx.Right, by.Left)) + { + return 0; + } + int leftCompare = Compare(bx.Left, by.Left); + return leftCompare != 0 ? leftCompare : Compare(bx.Right, by.Right); + + case MethodCallExpression mx when y is MethodCallExpression my: + int methodCompare = string.Compare(mx.Method.Name, my.Method.Name, StringComparison.Ordinal); + if (methodCompare != 0) return methodCompare; + // Compare arguments sequentially. + for (int i = 0; i < Math.Min(mx.Arguments.Count, my.Arguments.Count); i++) + { + int argCompare = Compare(mx.Arguments[i], my.Arguments[i]); + if (argCompare != 0) return argCompare; + } + return mx.Arguments.Count.CompareTo(my.Arguments.Count); - private static bool IsCommutative(ExpressionType type) - { - return type == ExpressionType.Equal || - type == ExpressionType.NotEqual || - type == ExpressionType.OrElse || - type == ExpressionType.AndAlso; + default: + return string.Compare(x.ToString(), y.ToString(), StringComparison.Ordinal); } } - } -} + private static bool IsCommutative(ExpressionType type) + { + return type == ExpressionType.Equal || + type == ExpressionType.NotEqual || + type == ExpressionType.OrElse || + type == ExpressionType.AndAlso; + } + } +} \ No newline at end of file diff --git a/Magic.IndexedDb/Helpers/MagicSerializationHelper.cs b/Magic.IndexedDb/Helpers/MagicSerializationHelper.cs index a4659ca..3fd4025 100644 --- a/Magic.IndexedDb/Helpers/MagicSerializationHelper.cs +++ b/Magic.IndexedDb/Helpers/MagicSerializationHelper.cs @@ -4,100 +4,97 @@ using System.Text; using System.Text.Json; -namespace Magic.IndexedDb.Helpers +namespace Magic.IndexedDb.Helpers; + +/// +/// Helper to serialize between the Magic Library content to the JS. To communicate with Dexie.JS - +/// Note I left this public only to allow it to be targeted by external projects for testing. +/// +public static class MagicSerializationHelper { - /// - /// Helper to serialize between the Magic Library content to the JS. To communicate with Dexie.JS - - /// Note I left this public only to allow it to be targeted by external projects for testing. - /// - public static class MagicSerializationHelper - { - public static object[] SerializeObjects(ITypedArgument[] objs, MagicJsonSerializationSettings? settings = null) - { - return objs.Select(arg => arg.SerializeToJsonElement(settings)).Cast().ToArray(); - } + public static object[] SerializeObjects(ITypedArgument[] objs, MagicJsonSerializationSettings? settings = null) + { + return objs.Select(arg => arg.SerializeToJsonElement(settings)).Cast().ToArray(); + } - public static string[] SerializeObjectsToString(ITypedArgument[] objs, MagicJsonSerializationSettings? settings = null) - { - return objs.Select(arg => arg.SerializeToJsonString(settings)).ToArray(); - } + public static string[] SerializeObjectsToString(ITypedArgument[] objs, MagicJsonSerializationSettings? settings = null) + { + return objs.Select(arg => arg.SerializeToJsonString(settings)).ToArray(); + } - public static JsonElement SerializeObjectToJsonElement(T value, MagicJsonSerializationSettings? settings = null) - { - if (settings == null) - settings = new MagicJsonSerializationSettings(); + public static JsonElement SerializeObjectToJsonElement(T value, MagicJsonSerializationSettings? settings = null) + { + if (settings == null) + settings = new MagicJsonSerializationSettings(); - if (value == null) - throw new ArgumentNullException(nameof(value), "Object cannot be null"); + if (value == null) + throw new ArgumentNullException(nameof(value), "Object cannot be null"); - var options = settings.GetOptionsWithResolver(); // Ensure the correct resolver is applied - string jsonString = JsonSerializer.Serialize(value, options); // Serialize using your settings + var options = settings.GetOptionsWithResolver(); // Ensure the correct resolver is applied + string jsonString = JsonSerializer.Serialize(value, options); // Serialize using your settings - // Convert the string to a JsonElement so that Blazor treats it as a structured object - using JsonDocument doc = JsonDocument.Parse(jsonString); - return doc.RootElement.Clone(); // Clone to prevent disposal issues - } + // Convert the string to a JsonElement so that Blazor treats it as a structured object + using JsonDocument doc = JsonDocument.Parse(jsonString); + return doc.RootElement.Clone(); // Clone to prevent disposal issues + } - public static async Task SerializeObjectToStreamAsync(StreamWriter writer, T value, MagicJsonSerializationSettings? settings = null) - { - if (settings == null) - settings = new MagicJsonSerializationSettings(); + public static async Task SerializeObjectToStreamAsync(StreamWriter writer, T value, MagicJsonSerializationSettings? settings = null) + { + if (settings == null) + settings = new MagicJsonSerializationSettings(); - if (value == null) - throw new ArgumentNullException(nameof(value), "Object cannot be null"); + if (value == null) + throw new ArgumentNullException(nameof(value), "Object cannot be null"); - var options = settings.GetOptionsWithResolver(); - string jsonString = JsonSerializer.Serialize(value, options); // Use your serializer + var options = settings.GetOptionsWithResolver(); + string jsonString = JsonSerializer.Serialize(value, options); // Use your serializer - await writer.WriteAsync(jsonString); - await writer.FlushAsync(); - } + await writer.WriteAsync(jsonString); + await writer.FlushAsync(); + } - public static string SerializeObject(T? value, MagicJsonSerializationSettings? settings = null) - { - if (value == null) - return "null"; + public static string SerializeObject(T? value, MagicJsonSerializationSettings? settings = null) + { + if (value == null) + return "null"; - if (settings == null) - settings = new MagicJsonSerializationSettings(); + if (settings == null) + settings = new MagicJsonSerializationSettings(); - if (value == null) - throw new ArgumentNullException(nameof(value), "Object cannot be null"); + if (value == null) + throw new ArgumentNullException(nameof(value), "Object cannot be null"); - var options = settings.GetOptionsWithResolver(); // Ensure the correct resolver is applied + var options = settings.GetOptionsWithResolver(); // Ensure the correct resolver is applied - return JsonSerializer.Serialize(value, options); - } + return JsonSerializer.Serialize(value, options); + } - public static T? DeserializeObject(string json, MagicJsonSerializationSettings? settings = null) - { - if (string.IsNullOrWhiteSpace(json)) - throw new ArgumentException("JSON cannot be null or empty.", nameof(json)); + public static T? DeserializeObject(string json, MagicJsonSerializationSettings? settings = null) + { + if (string.IsNullOrWhiteSpace(json)) + throw new ArgumentException("JSON cannot be null or empty.", nameof(json)); - if (settings == null) - settings = new MagicJsonSerializationSettings(); + if (settings == null) + settings = new MagicJsonSerializationSettings(); - var options = settings.GetOptionsWithResolver(); // Ensure correct resolver for deserialization + var options = settings.GetOptionsWithResolver(); // Ensure correct resolver for deserialization - return JsonSerializer.Deserialize(json, options); - } + return JsonSerializer.Deserialize(json, options); + } - public static void PopulateObject(T source, T target) - { - if (source == null || target == null) - throw new ArgumentNullException("Source and target cannot be null"); + public static void PopulateObject(T source, T target) + { + if (source == null || target == null) + throw new ArgumentNullException("Source and target cannot be null"); - var json = JsonSerializer.Serialize(source); - var deserialized = JsonSerializer.Deserialize(json); + var json = JsonSerializer.Serialize(source); + var deserialized = JsonSerializer.Deserialize(json); - foreach (var prop in typeof(T).GetProperties()) - { - var value = prop.GetValue(deserialized); - prop.SetValue(target, value); - } + foreach (var prop in typeof(T).GetProperties()) + { + var value = prop.GetValue(deserialized); + prop.SetValue(target, value); } } - - -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/Helpers/MagicValidator.cs b/Magic.IndexedDb/Helpers/MagicValidator.cs index 52e5eec..3b2b511 100644 --- a/Magic.IndexedDb/Helpers/MagicValidator.cs +++ b/Magic.IndexedDb/Helpers/MagicValidator.cs @@ -7,66 +7,66 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Helpers +namespace Magic.IndexedDb.Helpers; + +public static class MagicValidator { - public static class MagicValidator + public static void ValidateTables(List? magicTableClasses = null) { - public static void ValidateTables(List? magicTableClasses = null) + var errors = new StringBuilder(); + magicTableClasses ??= SchemaHelper.GetAllMagicTables(); + + foreach (var type in magicTableClasses) { - var errors = new StringBuilder(); - magicTableClasses ??= SchemaHelper.GetAllMagicTables(); + var properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - foreach (var type in magicTableClasses) + if (!properties.Any()) { - var properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - - if (!properties.Any()) - { - errors.AppendLine($"Error: Class '{type.Name}' is marked as a Magic Table but has no properties."); - continue; - } + errors.AppendLine($"Error: Class '{type.Name}' is marked as a Magic Table but has no properties."); + continue; + } - var instance = Activator.CreateInstance(type) as IMagicTableBase; - if (instance == null) - { - errors.AppendLine($"Error: Unable to create an instance of '{type.Name}'."); - continue; - } + var instance = Activator.CreateInstance(type) as IMagicTableBase; + if (instance == null) + { + errors.AppendLine($"Error: Unable to create an instance of '{type.Name}'."); + continue; + } - IMagicCompoundKey compoundKey = instance.GetKeys(); - List? compoundIndexes = instance.GetCompoundIndexes(); + IMagicCompoundKey compoundKey = instance.GetKeys(); + List? compoundIndexes = instance.GetCompoundIndexes(); - if (compoundKey == null || compoundKey.ColumnNamesInCompoundKey.Length == 0) + if (compoundKey == null || compoundKey.ColumnNamesInCompoundKey.Length == 0) + { + errors.AppendLine($"Error: Class '{type.Name}' must have at least one primary key."); + continue; + } + + // If there's only one key, treat it as a primary key; otherwise, enforce compound key rules. + if (compoundKey.ColumnNamesInCompoundKey.Length == 1) + { + var primaryKeyProperty = compoundKey.PropertyInfos.First(); + if (compoundKey.AutoIncrement && !IsNumericType(primaryKeyProperty.PropertyType)) { - errors.AppendLine($"Error: Class '{type.Name}' must have at least one primary key."); - continue; + errors.AppendLine($"Error: Primary key '{primaryKeyProperty.Name}' in class '{type.Name}' is marked as auto-increment, but its type '{primaryKeyProperty.PropertyType.Name}' is not numeric."); } - // If there's only one key, treat it as a primary key; otherwise, enforce compound key rules. - if (compoundKey.ColumnNamesInCompoundKey.Length == 1) + if (Nullable.GetUnderlyingType(primaryKeyProperty.PropertyType) != null) { - var primaryKeyProperty = compoundKey.PropertyInfos.First(); - if (compoundKey.AutoIncrement && !IsNumericType(primaryKeyProperty.PropertyType)) - { - errors.AppendLine($"Error: Primary key '{primaryKeyProperty.Name}' in class '{type.Name}' is marked as auto-increment, but its type '{primaryKeyProperty.PropertyType.Name}' is not numeric."); - } - - if (Nullable.GetUnderlyingType(primaryKeyProperty.PropertyType) != null) - { - errors.AppendLine($"Error: Primary key '{primaryKeyProperty.Name}' in class '{type.Name}' cannot be nullable."); - } + errors.AppendLine($"Error: Primary key '{primaryKeyProperty.Name}' in class '{type.Name}' cannot be nullable."); } - else + } + else + { + // Validate compound key rules + var keyNames = new HashSet(compoundKey.ColumnNamesInCompoundKey); + if (keyNames.Count != compoundKey.ColumnNamesInCompoundKey.Length) { - // Validate compound key rules - var keyNames = new HashSet(compoundKey.ColumnNamesInCompoundKey); - if (keyNames.Count != compoundKey.ColumnNamesInCompoundKey.Length) - { - errors.AppendLine($"Error: Class '{type.Name}' has duplicate column names in its compound key definition."); - } + errors.AppendLine($"Error: Class '{type.Name}' has duplicate column names in its compound key definition."); } + } - var magicAttributes = new List + var magicAttributes = new List { typeof(MagicIndexAttribute), typeof(MagicNotMappedAttribute), @@ -74,48 +74,47 @@ public static void ValidateTables(List? magicTableClasses = null) typeof(MagicUniqueIndexAttribute) }; - foreach (var prop in properties) - { - var appliedMagicAttributes = prop.GetCustomAttributes() - .Select(attr => attr.GetType()) - .Where(attrType => magicAttributes.Contains(attrType)) - .ToList(); + foreach (var prop in properties) + { + var appliedMagicAttributes = prop.GetCustomAttributes() + .Select(attr => attr.GetType()) + .Where(attrType => magicAttributes.Contains(attrType)) + .ToList(); - if (appliedMagicAttributes.Count > 1) - { - errors.AppendLine($"Error: Property '{prop.Name}' in class '{type.Name}' has multiple Magic attributes ({string.Join(", ", appliedMagicAttributes.Select(a => a.Name))}). A property can have at most one Magic attribute."); - } + if (appliedMagicAttributes.Count > 1) + { + errors.AppendLine($"Error: Property '{prop.Name}' in class '{type.Name}' has multiple Magic attributes ({string.Join(", ", appliedMagicAttributes.Select(a => a.Name))}). A property can have at most one Magic attribute."); } + } - if (compoundIndexes != null) + if (compoundIndexes != null) + { + var compoundIndexSets = new HashSet(); + foreach (var index in compoundIndexes) { - var compoundIndexSets = new HashSet(); - foreach (var index in compoundIndexes) + string indexKey = string.Join(",", index.ColumnNamesInCompoundIndex.OrderBy(x => x)); + if (!compoundIndexSets.Add(indexKey)) { - string indexKey = string.Join(",", index.ColumnNamesInCompoundIndex.OrderBy(x => x)); - if (!compoundIndexSets.Add(indexKey)) - { - errors.AppendLine($"Error: Duplicate compound index detected in class '{type.Name}' with the same properties ({indexKey})."); - } + errors.AppendLine($"Error: Duplicate compound index detected in class '{type.Name}' with the same properties ({indexKey})."); } } } - - if (errors.Length > 0) - { - throw new Exception($"Magic Table Validation Errors:\n{errors}"); - } } - private static bool IsNumericType(Type type) + if (errors.Length > 0) { - Type underlyingType = Nullable.GetUnderlyingType(type) ?? type; - return underlyingType == typeof(byte) || underlyingType == typeof(sbyte) || - underlyingType == typeof(short) || underlyingType == typeof(ushort) || - underlyingType == typeof(int) || underlyingType == typeof(uint) || - underlyingType == typeof(long) || underlyingType == typeof(ulong) || - underlyingType == typeof(float) || underlyingType == typeof(double) || - underlyingType == typeof(decimal); + throw new Exception($"Magic Table Validation Errors:\n{errors}"); } } -} + + private static bool IsNumericType(Type type) + { + Type underlyingType = Nullable.GetUnderlyingType(type) ?? type; + return underlyingType == typeof(byte) || underlyingType == typeof(sbyte) || + underlyingType == typeof(short) || underlyingType == typeof(ushort) || + underlyingType == typeof(int) || underlyingType == typeof(uint) || + underlyingType == typeof(long) || underlyingType == typeof(ulong) || + underlyingType == typeof(float) || underlyingType == typeof(double) || + underlyingType == typeof(decimal); + } +} \ No newline at end of file diff --git a/Magic.IndexedDb/Helpers/PropertyMappingCache.cs b/Magic.IndexedDb/Helpers/PropertyMappingCache.cs index 1117b43..aa3ed3c 100644 --- a/Magic.IndexedDb/Helpers/PropertyMappingCache.cs +++ b/Magic.IndexedDb/Helpers/PropertyMappingCache.cs @@ -11,598 +11,596 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Helpers -{ +namespace Magic.IndexedDb.Helpers; - public struct SearchPropEntry +public struct SearchPropEntry +{ + public SearchPropEntry(Type type, Dictionary _propertyEntries, ConstructorInfo[] constructors) { - public SearchPropEntry(Type type, Dictionary _propertyEntries, ConstructorInfo[] constructors) + propertyEntries = _propertyEntries; + jsNameToCsName = new Dictionary(StringComparer.OrdinalIgnoreCase); + ConstructorParameterMappings = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var entry in propertyEntries) { - propertyEntries = _propertyEntries; - jsNameToCsName = new Dictionary(StringComparer.OrdinalIgnoreCase); - ConstructorParameterMappings = new Dictionary(StringComparer.OrdinalIgnoreCase); + jsNameToCsName[entry.Value.JsPropertyName] = entry.Value.CsharpPropertyName; + } - foreach (var entry in propertyEntries) + // Extract IMagicTableBase info + if (typeof(IMagicTableBase).IsAssignableFrom(type)) + { + var instance = Activator.CreateInstance(type) as IMagicTableBase; + if (instance != null) { - jsNameToCsName[entry.Value.JsPropertyName] = entry.Value.CsharpPropertyName; + EffectiveTableName = instance.GetTableName(); // Use the provided table name + EnforcePascalCase = true; // Prevent camel casing } + } + else + { + EffectiveTableName = type.Name; // Default to class name + EnforcePascalCase = false; + } - // Extract IMagicTableBase info - if (typeof(IMagicTableBase).IsAssignableFrom(type)) - { - var instance = Activator.CreateInstance(type) as IMagicTableBase; - if (instance != null) - { - EffectiveTableName = instance.GetTableName(); // Use the provided table name - EnforcePascalCase = true; // Prevent camel casing - } - } - else + // 🔥 Pick the best constructor: Prefer a parameterized one, else fallback to parameterless + var constructor = constructors.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault(); + Constructor = constructor; // ✅ Assign to instance variable + HasConstructorParameters = constructor != null && constructor.GetParameters().Length > 0; + + // 🔥 Cache constructor parameter mappings + if (HasConstructorParameters) + { + var parameters = constructor.GetParameters(); + for (int i = 0; i < parameters.Length; i++) { - EffectiveTableName = type.Name; // Default to class name - EnforcePascalCase = false; + ConstructorParameterMappings[parameters[i].Name!] = i; } + } - // 🔥 Pick the best constructor: Prefer a parameterized one, else fallback to parameterless - var constructor = constructors.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault(); - Constructor = constructor; // ✅ Assign to instance variable - HasConstructorParameters = constructor != null && constructor.GetParameters().Length > 0; + // 🔥 Store constructor in a local variable before using in lambda (Fix for struct issue) + var localConstructor = constructor; - // 🔥 Cache constructor parameter mappings - if (HasConstructorParameters) + // 🔥 Cache fast instance creator + if (localConstructor != null) + { + InstanceCreator = (args) => localConstructor.Invoke(args); + } + else + { + InstanceCreator = (_) => { - var parameters = constructor.GetParameters(); - for (int i = 0; i < parameters.Length; i++) + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) { - ConstructorParameterMappings[parameters[i].Name!] = i; + // Instantiate a List when an IEnumerable is requested + Type listType = typeof(List<>).MakeGenericType(type.GetGenericArguments()); + return Activator.CreateInstance(listType); } - } - // 🔥 Store constructor in a local variable before using in lambda (Fix for struct issue) - var localConstructor = constructor; - - // 🔥 Cache fast instance creator - if (localConstructor != null) - { - InstanceCreator = (args) => localConstructor.Invoke(args); - } - else - { - InstanceCreator = (_) => + if (IsInstantiable(type)) { - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) - { - // Instantiate a List when an IEnumerable is requested - Type listType = typeof(List<>).MakeGenericType(type.GetGenericArguments()); - return Activator.CreateInstance(listType); - } - - if (IsInstantiable(type)) - { - return Activator.CreateInstance(type); - } - else - { - throw new InvalidOperationException($"Cannot instantiate abstract/interface type {type.FullName}"); - } - }; - } - + return Activator.CreateInstance(type); + } + else + { + throw new InvalidOperationException($"Cannot instantiate abstract/interface type {type.FullName}"); + } + }; } - public string EffectiveTableName { get; } // ✅ Stores the final name (SchemaName or C# class name) - public bool EnforcePascalCase { get; } // ✅ If true, prevents camel casing + } - public ConstructorInfo? Constructor { get; } // ✅ Stores the most relevant constructor - public bool HasConstructorParameters { get; } // ✅ Cached flag to avoid checking length - public Func InstanceCreator { get; } // ✅ Cached instance creator + public string EffectiveTableName { get; } // ✅ Stores the final name (SchemaName or C# class name) + public bool EnforcePascalCase { get; } // ✅ If true, prevents camel casing - public Dictionary propertyEntries { get; } - public Dictionary jsNameToCsName { get; } - public Dictionary ConstructorParameterMappings { get; } // ✅ Stores constructor parameter indexes + public ConstructorInfo? Constructor { get; } // ✅ Stores the most relevant constructor + public bool HasConstructorParameters { get; } // ✅ Cached flag to avoid checking length + public Func InstanceCreator { get; } // ✅ Cached instance creator - /// - /// Determines whether a type can be instantiated. - /// - private static bool IsInstantiable(Type type) - { - if (type.IsAbstract || type.IsInterface || type.IsGenericTypeDefinition) - return false; + public Dictionary propertyEntries { get; } + public Dictionary jsNameToCsName { get; } + public Dictionary ConstructorParameterMappings { get; } // ✅ Stores constructor parameter indexes - // Handle generic collection interfaces like IEnumerable, ICollection, etc. - if (type.IsGenericType) + /// + /// Determines whether a type can be instantiated. + /// + private static bool IsInstantiable(Type type) + { + if (type.IsAbstract || type.IsInterface || type.IsGenericTypeDefinition) + return false; + + // Handle generic collection interfaces like IEnumerable, ICollection, etc. + if (type.IsGenericType) + { + Type genericTypeDef = type.GetGenericTypeDefinition(); + if (genericTypeDef == typeof(IEnumerable<>) || + genericTypeDef == typeof(ICollection<>) || + genericTypeDef == typeof(IList<>)) { - Type genericTypeDef = type.GetGenericTypeDefinition(); - if (genericTypeDef == typeof(IEnumerable<>) || - genericTypeDef == typeof(ICollection<>) || - genericTypeDef == typeof(IList<>)) - { - return true; // Can instantiate List - } + return true; // Can instantiate List } - - return true; } + return true; } - public static class PropertyMappingCache - { - internal static readonly ConcurrentDictionary _propertyCache = new(); +} +public static class PropertyMappingCache +{ + internal static readonly ConcurrentDictionary _propertyCache = new(); - public static List GetPrimaryKeysOfType(Type type) - { - return GetTypeOfTProperties(type).propertyEntries - .Where(prop => prop.Value.PrimaryKey) - .Select(prop => prop.Value) - .ToList(); - } + public static List GetPrimaryKeysOfType(Type type) + { + return GetTypeOfTProperties(type).propertyEntries + .Where(prop => prop.Value.PrimaryKey) + .Select(prop => prop.Value) + .ToList(); + } - public static SearchPropEntry GetTypeOfTProperties(Type type) + + public static SearchPropEntry GetTypeOfTProperties(Type type) + { + EnsureTypeIsCached(type); + if (_propertyCache.TryGetValue(type!, out var properties)) { - EnsureTypeIsCached(type); - if (_propertyCache.TryGetValue(type!, out var properties)) - { - return properties; - } - throw new Exception("Something went very wrong getting GetTypeOfTProperties"); + return properties; } + throw new Exception("Something went very wrong getting GetTypeOfTProperties"); + } - private static readonly HashSet _simpleTypes = new() -{ - typeof(string), typeof(decimal), typeof(DateTime), typeof(DateTimeOffset), - typeof(Guid), typeof(Uri), typeof(TimeSpan) -}; + private static readonly HashSet _simpleTypes = new() + { + typeof(string), typeof(decimal), typeof(DateTime), typeof(DateTimeOffset), + typeof(Guid), typeof(Uri), typeof(TimeSpan) + }; - // Cache lookups for extreme performance - private static readonly ConcurrentDictionary _typeCache = new(); - private static readonly Type ObjectType = typeof(object); // ✅ Cached for performance + // Cache lookups for extreme performance + private static readonly ConcurrentDictionary _typeCache = new(); + private static readonly Type ObjectType = typeof(object); // ✅ Cached for performance - public static bool IsSimpleType(Type type) - { - if (type == null) - return false; + public static bool IsSimpleType(Type type) + { + if (type == null) + return false; - // First, check cache - if (_typeCache.TryGetValue(type, out bool cachedResult)) - return cachedResult; + // First, check cache + if (_typeCache.TryGetValue(type, out bool cachedResult)) + return cachedResult; - // If the type is Nullable, extract T. - if (Nullable.GetUnderlyingType(type) is Type underlyingType) - type = underlyingType; + // If the type is Nullable, extract T. + if (Nullable.GetUnderlyingType(type) is Type underlyingType) + type = underlyingType; - // Anonymous = complex - if (IsAnonymousType(type)) - return true; + // Anonymous = complex + if (IsAnonymousType(type)) + return true; - // Unwrap System.Text.Json.Nodes.JsonValueCustomized (avoiding .FullName for perf) - if (type.IsGenericType) + // Unwrap System.Text.Json.Nodes.JsonValueCustomized (avoiding .FullName for perf) + if (type.IsGenericType) + { + var genericType = type.GetGenericTypeDefinition(); + if (genericType.Namespace == "System.Text.Json.Nodes" && genericType.Name.StartsWith("JsonValueCustomized")) { - var genericType = type.GetGenericTypeDefinition(); - if (genericType.Namespace == "System.Text.Json.Nodes" && genericType.Name.StartsWith("JsonValueCustomized")) + type = type.GetGenericArguments()[0]; // Extract T from JsonValueCustomized + + // If T is still just object, force serialization as a simple type + if (type == ObjectType) { - type = type.GetGenericArguments()[0]; // Extract T from JsonValueCustomized - - // If T is still just object, force serialization as a simple type - if (type == ObjectType) - { - _typeCache.TryAdd(type, true); - return true; - } + _typeCache.TryAdd(type, true); + return true; } } - - // If the final unwrapped type is still object, force serialization as-is - if (type == ObjectType) - { - _typeCache.TryAdd(type, true); - return true; - } - - // Check if the final unwrapped type is simple - bool result = type.IsPrimitive || type.IsEnum || _simpleTypes.Contains(type); - - // Store result in cache - _typeCache.TryAdd(type, result); - - return result; } - private static bool IsAnonymousType(Type type) + // If the final unwrapped type is still object, force serialization as-is + if (type == ObjectType) { - return Attribute.IsDefined(type, typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute)) - && type.IsGenericType - && type.Name.Contains("AnonymousType") - && (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$")) - && type.Namespace == null; + _typeCache.TryAdd(type, true); + return true; } + // Check if the final unwrapped type is simple + bool result = type.IsPrimitive || type.IsEnum || _simpleTypes.Contains(type); + // Store result in cache + _typeCache.TryAdd(type, result); + return result; + } + private static bool IsAnonymousType(Type type) + { + return Attribute.IsDefined(type, typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute)) + && type.IsGenericType + && type.Name.Contains("AnonymousType") + && (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$")) + && type.Namespace == null; + } - /* - public static bool IsSimpleType(Type type) - { - return type.IsPrimitive || - type.IsEnum || - type == typeof(string) || - type == typeof(decimal) || - type == typeof(DateTime) || - type == typeof(DateTimeOffset) || - type == typeof(Guid) || - type == typeof(Uri) || - type == typeof(TimeSpan); - }*/ - - - public static IEnumerable GetAllNestedComplexTypes(IEnumerable properties) - { - HashSet complexTypes = new(); - Stack typeStack = new(); - // Initial population of the stack - foreach (var property in properties) - { - if (IsComplexType(property.PropertyType)) - { - typeStack.Push(property.PropertyType); - complexTypes.Add(property.PropertyType); - } - } - // Process all nested complex types - while (typeStack.Count > 0) - { - var currentType = typeStack.Pop(); - var nestedProperties = currentType.GetProperties(BindingFlags.Public | BindingFlags.Instance); - foreach (var nestedProperty in nestedProperties) - { - if (IsComplexType(nestedProperty.PropertyType) && !complexTypes.Contains(nestedProperty.PropertyType)) - { - complexTypes.Add(nestedProperty.PropertyType); - typeStack.Push(nestedProperty.PropertyType); - } - } - } - return complexTypes; - } + /* + public static bool IsSimpleType(Type type) + { + return type.IsPrimitive || + type.IsEnum || + type == typeof(string) || + type == typeof(decimal) || + type == typeof(DateTime) || + type == typeof(DateTimeOffset) || + type == typeof(Guid) || + type == typeof(Uri) || + type == typeof(TimeSpan); + }*/ + + + public static IEnumerable GetAllNestedComplexTypes(IEnumerable properties) + { + HashSet complexTypes = new(); + Stack typeStack = new(); - public static bool IsComplexType(Type type) + // Initial population of the stack + foreach (var property in properties) { - return !(IsSimpleType(type) - || type == typeof(string) - || typeof(IEnumerable).IsAssignableFrom(type) // Non-generic IEnumerable - || (type.IsGenericType && typeof(IEnumerable<>).IsAssignableFrom(type.GetGenericTypeDefinition())) // Generic IEnumerable - || type.IsArray); // Arrays are collections too + if (IsComplexType(property.PropertyType)) + { + typeStack.Push(property.PropertyType); + complexTypes.Add(property.PropertyType); + } } - /*private static readonly ConcurrentDictionary _complexTypeCache = new(); - - public static bool IsComplexType(Type type) + // Process all nested complex types + while (typeStack.Count > 0) { - return _complexTypeCache.GetOrAdd(type, t => - { - if (IsSimpleType(t) || t == typeof(string)) - return false; + var currentType = typeStack.Pop(); + var nestedProperties = currentType.GetProperties(BindingFlags.Public | BindingFlags.Instance); - if (t.IsGenericType) + foreach (var nestedProperty in nestedProperties) + { + if (IsComplexType(nestedProperty.PropertyType) && !complexTypes.Contains(nestedProperty.PropertyType)) { - Type genericTypeDef = t.GetGenericTypeDefinition(); - if (typeof(IEnumerable<>).IsAssignableFrom(genericTypeDef)) - { - return IsComplexType(t.GetGenericArguments()[0]); - } - return t.GetGenericArguments().Any(IsComplexType); + complexTypes.Add(nestedProperty.PropertyType); + typeStack.Push(nestedProperty.PropertyType); } + } + } - if (typeof(IEnumerable).IsAssignableFrom(t) || t.IsArray) - return false; + return complexTypes; + } - return true; - }); - }*/ + public static bool IsComplexType(Type type) + { + return !(IsSimpleType(type) + || type == typeof(string) + || typeof(IEnumerable).IsAssignableFrom(type) // Non-generic IEnumerable + || (type.IsGenericType && typeof(IEnumerable<>).IsAssignableFrom(type.GetGenericTypeDefinition())) // Generic IEnumerable + || type.IsArray); // Arrays are collections too + } + /*private static readonly ConcurrentDictionary _complexTypeCache = new(); - /*public static bool IsComplexType(Type type) + public static bool IsComplexType(Type type) + { + return _complexTypeCache.GetOrAdd(type, t => { - if (IsSimpleType(type) || type == typeof(string)) + if (IsSimpleType(t) || t == typeof(string)) return false; - // Handle generic collections like List, Dictionary - if (type.IsGenericType) + if (t.IsGenericType) { - Type genericTypeDef = type.GetGenericTypeDefinition(); - - // If it's a generic IEnumerable, get the type argument and check if it's complex + Type genericTypeDef = t.GetGenericTypeDefinition(); if (typeof(IEnumerable<>).IsAssignableFrom(genericTypeDef)) { - Type itemType = type.GetGenericArguments()[0]; - return IsComplexType(itemType); + return IsComplexType(t.GetGenericArguments()[0]); } - - // Otherwise, it might be a generic class like StoreRecord - return type.GetGenericArguments().Any(IsComplexType); + return t.GetGenericArguments().Any(IsComplexType); } - // Handle non-generic collections like arrays - if (typeof(IEnumerable).IsAssignableFrom(type) || type.IsArray) + if (typeof(IEnumerable).IsAssignableFrom(t) || t.IsArray) return false; - return true; // Consider anything else a complex object - }*/ - + return true; + }); + }*/ - /// - /// Gets the C# property name given a JavaScript property name. - /// - public static string GetCsharpPropertyName(string jsPropertyName) - { - return GetCsharpPropertyName(jsPropertyName, typeof(T)); - } + /*public static bool IsComplexType(Type type) + { + if (IsSimpleType(type) || type == typeof(string)) + return false; - /// - /// Gets the C# property name given a JavaScript property name. - /// - public static string GetCsharpPropertyName(string jsPropertyName, Type type) + // Handle generic collections like List, Dictionary + if (type.IsGenericType) { - EnsureTypeIsCached(type); - string typeKey = type.FullName!; + Type genericTypeDef = type.GetGenericTypeDefinition(); - try - { - if (_propertyCache.TryGetValue(type, out var search)) - { - return search.GetCsharpPropertyName(jsPropertyName); - } - } - catch (Exception ex) + // If it's a generic IEnumerable, get the type argument and check if it's complex + if (typeof(IEnumerable<>).IsAssignableFrom(genericTypeDef)) { - throw new Exception($"Error retrieving C# property name for JS property '{jsPropertyName}' in type {type.FullName}.", ex); + Type itemType = type.GetGenericArguments()[0]; + return IsComplexType(itemType); } - return jsPropertyName; // Fallback to original name if not found + // Otherwise, it might be a generic class like StoreRecord + return type.GetGenericArguments().Any(IsComplexType); } - public static MagicPropertyEntry GetPropertyByCsharpName(this SearchPropEntry propCachee, string csharpName) - { - string errorMsg = $"Error retrieving C# property by the name of '{csharpName}'."; - try - { - if (propCachee.propertyEntries.TryGetValue(csharpName, out var entry)) - { - return entry; - } - } - catch (Exception ex) - { - throw new Exception(errorMsg, ex); - } + // Handle non-generic collections like arrays + if (typeof(IEnumerable).IsAssignableFrom(type) || type.IsArray) + return false; - throw new Exception(errorMsg); - } + return true; // Consider anything else a complex object + }*/ + + + + /// + /// Gets the C# property name given a JavaScript property name. + /// + public static string GetCsharpPropertyName(string jsPropertyName) + { + return GetCsharpPropertyName(jsPropertyName, typeof(T)); + } + + /// + /// Gets the C# property name given a JavaScript property name. + /// + public static string GetCsharpPropertyName(string jsPropertyName, Type type) + { + EnsureTypeIsCached(type); + string typeKey = type.FullName!; - public static string GetCsharpPropertyName(this SearchPropEntry propCachee, string jsPropertyName) + try { - try + if (_propertyCache.TryGetValue(type, out var search)) { - if (propCachee.jsNameToCsName.TryGetValue(jsPropertyName, out var csName) - && propCachee.propertyEntries.TryGetValue(csName, out var entry)) - { - return entry.CsharpPropertyName; - } - } - catch (Exception ex) - { - throw new Exception($"Error retrieving C# property name for JS property '{jsPropertyName}'.", ex); + return search.GetCsharpPropertyName(jsPropertyName); } - - return jsPropertyName; // Fallback to original name if not found } - - - /// - /// Gets the JavaScript property name (ColumnName) given a C# property name. - /// - public static string GetJsPropertyName(string csharpPropertyName) + catch (Exception ex) { - return GetJsPropertyName(csharpPropertyName, typeof(T)); + throw new Exception($"Error retrieving C# property name for JS property '{jsPropertyName}' in type {type.FullName}.", ex); } - public static string GetJsPropertyName(PropertyInfo prop) + return jsPropertyName; // Fallback to original name if not found + } + + public static MagicPropertyEntry GetPropertyByCsharpName(this SearchPropEntry propCachee, string csharpName) + { + string errorMsg = $"Error retrieving C# property by the name of '{csharpName}'."; + try { - return GetJsPropertyName(prop.Name, typeof(T)); + if (propCachee.propertyEntries.TryGetValue(csharpName, out var entry)) + { + return entry; + } } - - public static string GetJsPropertyName(PropertyInfo prop, Type type) + catch (Exception ex) { - return GetJsPropertyName(prop.Name, type); + throw new Exception(errorMsg, ex); } - /// - /// Gets the JavaScript property name (ColumnName) given a C# property name. - /// - public static string GetJsPropertyName(string csharpPropertyName, Type type) - { - EnsureTypeIsCached(type); - string typeKey = type.FullName!; + throw new Exception(errorMsg); + } - try - { - if (_propertyCache.TryGetValue(type, out var properties) && - properties.propertyEntries.TryGetValue(csharpPropertyName, out var entry)) - { - return entry.JsPropertyName; - } - } - catch (Exception ex) + public static string GetCsharpPropertyName(this SearchPropEntry propCachee, string jsPropertyName) + { + try + { + if (propCachee.jsNameToCsName.TryGetValue(jsPropertyName, out var csName) + && propCachee.propertyEntries.TryGetValue(csName, out var entry)) { - throw new Exception($"Error retrieving JS property name for C# property '{csharpPropertyName}' in type {type.FullName}.", ex); + return entry.CsharpPropertyName; } - - return csharpPropertyName; // Fallback to original name if not found } - - /// - /// Gets the cached MagicPropertyEntry for a given property name. - /// - public static MagicPropertyEntry GetPropertyEntry(string propertyName) + catch (Exception ex) { - return GetPropertyEntry(propertyName, typeof(T)); + throw new Exception($"Error retrieving C# property name for JS property '{jsPropertyName}'.", ex); } - /// - /// Gets the cached MagicPropertyEntry for a given property name. - /// - public static MagicPropertyEntry GetPropertyEntry(string propertyName, Type type) - { - EnsureTypeIsCached(type); - string typeKey = type.FullName!; + return jsPropertyName; // Fallback to original name if not found + } - try - { - if (_propertyCache.TryGetValue(type, out var properties) && - properties.propertyEntries.TryGetValue(propertyName, out var entry)) - { - return entry; - } - } - catch (Exception ex) - { - throw new Exception($"Error retrieving property entry for '{propertyName}' in type {type.FullName}.", ex); - } - throw new Exception($"Error: Property '{propertyName}' not found in type {type.FullName}."); - } + /// + /// Gets the JavaScript property name (ColumnName) given a C# property name. + /// + public static string GetJsPropertyName(string csharpPropertyName) + { + return GetJsPropertyName(csharpPropertyName, typeof(T)); + } + + public static string GetJsPropertyName(PropertyInfo prop) + { + return GetJsPropertyName(prop.Name, typeof(T)); + } - /// - /// Gets the cached MagicPropertyEntry for a given PropertyInfo reference. - /// - public static MagicPropertyEntry GetPropertyEntry(PropertyInfo property) + public static string GetJsPropertyName(PropertyInfo prop, Type type) + { + return GetJsPropertyName(prop.Name, type); + } + + /// + /// Gets the JavaScript property name (ColumnName) given a C# property name. + /// + public static string GetJsPropertyName(string csharpPropertyName, Type type) + { + EnsureTypeIsCached(type); + string typeKey = type.FullName!; + + try { - return GetPropertyEntry(property.Name, typeof(T)); + if (_propertyCache.TryGetValue(type, out var properties) && + properties.propertyEntries.TryGetValue(csharpPropertyName, out var entry)) + { + return entry.JsPropertyName; + } } - - /// - /// Gets the cached MagicPropertyEntry for a given PropertyInfo reference. - /// - public static MagicPropertyEntry GetPropertyEntry(PropertyInfo property, Type type) + catch (Exception ex) { - return GetPropertyEntry(property.Name, type); + throw new Exception($"Error retrieving JS property name for C# property '{csharpPropertyName}' in type {type.FullName}.", ex); } + return csharpPropertyName; // Fallback to original name if not found + } - /// - /// Ensures that both schema and property caches are built for the given type. - /// - internal static void EnsureTypeIsCached() + /// + /// Gets the cached MagicPropertyEntry for a given property name. + /// + public static MagicPropertyEntry GetPropertyEntry(string propertyName) + { + return GetPropertyEntry(propertyName, typeof(T)); + } + + /// + /// Gets the cached MagicPropertyEntry for a given property name. + /// + public static MagicPropertyEntry GetPropertyEntry(string propertyName, Type type) + { + EnsureTypeIsCached(type); + string typeKey = type.FullName!; + + try { - Type type = typeof(T); + if (_propertyCache.TryGetValue(type, out var properties) && + properties.propertyEntries.TryGetValue(propertyName, out var entry)) + { + return entry; + } } - - internal static void EnsureTypeIsCached(Type type) + catch (Exception ex) { - //string typeKey = type.FullName!; + throw new Exception($"Error retrieving property entry for '{propertyName}' in type {type.FullName}.", ex); + } - // Avoid re-registering types if the typeKey already exists - if (_propertyCache.ContainsKey(type)) - return; + throw new Exception($"Error: Property '{propertyName}' not found in type {type.FullName}."); + } - // Ensure schema metadata is cached - SchemaHelper.EnsureSchemaIsCached(type); + /// + /// Gets the cached MagicPropertyEntry for a given PropertyInfo reference. + /// + public static MagicPropertyEntry GetPropertyEntry(PropertyInfo property) + { + return GetPropertyEntry(property.Name, typeof(T)); + } - // Initialize the dictionary for this type - var propertyEntries = new Dictionary(StringComparer.OrdinalIgnoreCase); + /// + /// Gets the cached MagicPropertyEntry for a given PropertyInfo reference. + /// + public static MagicPropertyEntry GetPropertyEntry(PropertyInfo property, Type type) + { + return GetPropertyEntry(property.Name, type); + } - bool isMagicTable = SchemaHelper.HasMagicTableInterface(type); - var instance = Activator.CreateInstance(type) as IMagicTableBase; + /// + /// Ensures that both schema and property caches are built for the given type. + /// + internal static void EnsureTypeIsCached() + { + Type type = typeof(T); + } - IMagicCompoundKey compoundKey; - List? compoundIndexes = new List(); - HashSet keyNames = new HashSet(); - if (instance != null) - { - compoundKey = instance.GetKeys(); - compoundIndexes = instance.GetCompoundIndexes(); - keyNames = new HashSet(compoundKey.PropertyInfos.Select(p => p.Name)); - } + internal static void EnsureTypeIsCached(Type type) + { + //string typeKey = type.FullName!; - List newMagicPropertyEntry = new List(); - foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy)) - { - if (property.GetIndexParameters().Length > 0) - continue; // 🔥 Skip indexers entirely + // Avoid re-registering types if the typeKey already exists + if (_propertyCache.ContainsKey(type)) + return; - string propertyKey = property.Name; // Now stored as string, not PropertyInfo + // Ensure schema metadata is cached + SchemaHelper.EnsureSchemaIsCached(type); - var columnAttribute = GetPropertyColumnAttribute(property); + // Initialize the dictionary for this type + var propertyEntries = new Dictionary(StringComparer.OrdinalIgnoreCase); - bool isPrimaryKey = keyNames.Contains(property.Name); + bool isMagicTable = SchemaHelper.HasMagicTableInterface(type); - bool isCompoundIndexed = false; - if(compoundIndexes != null && compoundIndexes.Any()) - { - isCompoundIndexed = compoundIndexes.SelectMany(x => x.PropertyInfos).Any(x => x.Name == property.Name); - } + var instance = Activator.CreateInstance(type) as IMagicTableBase; - var magicEntry = new MagicPropertyEntry( - property, - columnAttribute, - property.IsDefined(typeof(MagicIndexAttribute), inherit: true) - || isPrimaryKey || isCompoundIndexed - , - property.IsDefined(typeof(MagicUniqueIndexAttribute), inherit: true), - isPrimaryKey, - property.IsDefined(typeof(MagicNotMappedAttribute), inherit: true), - isMagicTable - || property.IsDefined(typeof(MagicNameAttribute), inherit: true) - ); - - newMagicPropertyEntry.Add(magicEntry); - propertyEntries[propertyKey] = magicEntry; // Store property entry with string key - } + IMagicCompoundKey compoundKey; + List? compoundIndexes = new List(); + HashSet keyNames = new HashSet(); + if (instance != null) + { + compoundKey = instance.GetKeys(); + compoundIndexes = instance.GetCompoundIndexes(); + keyNames = new HashSet(compoundKey.PropertyInfos.Select(p => p.Name)); + } + + List newMagicPropertyEntry = new List(); + foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy)) + { + if (property.GetIndexParameters().Length > 0) + continue; // 🔥 Skip indexers entirely - // 🔥 Extract constructor metadata - var constructors = type.GetConstructors(); + string propertyKey = property.Name; // Now stored as string, not PropertyInfo - // Cache the properties for this type - _propertyCache[type] = new SearchPropEntry(type, propertyEntries, - constructors); + var columnAttribute = GetPropertyColumnAttribute(property); - var complexTypes = GetAllNestedComplexTypes(newMagicPropertyEntry.Select(x => x.Property)); - if (complexTypes != null && complexTypes.Any()) + bool isPrimaryKey = keyNames.Contains(property.Name); + + bool isCompoundIndexed = false; + if(compoundIndexes != null && compoundIndexes.Any()) { - foreach (var comp in complexTypes) - { - EnsureTypeIsCached(comp); - } + isCompoundIndexed = compoundIndexes.SelectMany(x => x.PropertyInfos).Any(x => x.Name == property.Name); } + + var magicEntry = new MagicPropertyEntry( + property, + columnAttribute, + property.IsDefined(typeof(MagicIndexAttribute), inherit: true) + || isPrimaryKey || isCompoundIndexed + , + property.IsDefined(typeof(MagicUniqueIndexAttribute), inherit: true), + isPrimaryKey, + property.IsDefined(typeof(MagicNotMappedAttribute), inherit: true), + isMagicTable + || property.IsDefined(typeof(MagicNameAttribute), inherit: true) + ); + + newMagicPropertyEntry.Add(magicEntry); + propertyEntries[propertyKey] = magicEntry; // Store property entry with string key } - internal static IColumnNamed? GetPropertyColumnAttribute(PropertyInfo property) - { - var columnAttribute = property.GetCustomAttributes() - .FirstOrDefault(attr => attr is IColumnNamed) as IColumnNamed; + // 🔥 Extract constructor metadata + var constructors = type.GetConstructors(); - if (columnAttribute != null && string.IsNullOrWhiteSpace(columnAttribute.ColumnName)) + // Cache the properties for this type + _propertyCache[type] = new SearchPropEntry(type, propertyEntries, + constructors); + + var complexTypes = GetAllNestedComplexTypes(newMagicPropertyEntry.Select(x => x.Property)); + if (complexTypes != null && complexTypes.Any()) + { + foreach (var comp in complexTypes) { - columnAttribute = null; + EnsureTypeIsCached(comp); } - - return columnAttribute; } + } - internal static string GetJsPropertyNameNoCache(IColumnNamed? columnAttribute, string PropertyName) + internal static IColumnNamed? GetPropertyColumnAttribute(PropertyInfo property) + { + var columnAttribute = property.GetCustomAttributes() + .FirstOrDefault(attr => attr is IColumnNamed) as IColumnNamed; + + if (columnAttribute != null && string.IsNullOrWhiteSpace(columnAttribute.ColumnName)) { - return columnAttribute?.ColumnName ?? PropertyName; + columnAttribute = null; } + + return columnAttribute; } -} + + internal static string GetJsPropertyNameNoCache(IColumnNamed? columnAttribute, string PropertyName) + { + return columnAttribute?.ColumnName ?? PropertyName; + } +} \ No newline at end of file diff --git a/Magic.IndexedDb/Helpers/SchemaHelper.cs b/Magic.IndexedDb/Helpers/SchemaHelper.cs index 1c5e23c..1a3d1f0 100644 --- a/Magic.IndexedDb/Helpers/SchemaHelper.cs +++ b/Magic.IndexedDb/Helpers/SchemaHelper.cs @@ -7,252 +7,251 @@ using System.Reflection; using System.Threading.Tasks; -namespace Magic.IndexedDb.Helpers +namespace Magic.IndexedDb.Helpers; + +public static class SchemaHelper { - public static class SchemaHelper - { - //private static readonly ConcurrentDictionary> _databaseSchemasCache = new(); - internal static readonly ConcurrentDictionary _schemaCache = new(); + //private static readonly ConcurrentDictionary> _databaseSchemasCache = new(); + internal static readonly ConcurrentDictionary _schemaCache = new(); - internal static void EnsureSchemaIsCached(Type type) + internal static void EnsureSchemaIsCached(Type type) + { + _schemaCache.GetOrAdd(type, _ => { - _schemaCache.GetOrAdd(type, _ => - { - // Check if the type implements IMagicTableBase - return typeof(IMagicTableBase).IsAssignableFrom(type) - ? (Activator.CreateInstance(type) as IMagicTableBase) // Store the instance if valid - : null; // Store null if it doesn’t implement IMagicTableBase - }); - } + // Check if the type implements IMagicTableBase + return typeof(IMagicTableBase).IsAssignableFrom(type) + ? (Activator.CreateInstance(type) as IMagicTableBase) // Store the instance if valid + : null; // Store null if it doesn’t implement IMagicTableBase + }); + } - public static bool ImplementsIMagicTable(Type type) - { - return type.GetInterfaces().Any(i => i.IsGenericType - && i.GetGenericTypeDefinition() == typeof(IMagicTable<>)); - } + public static bool ImplementsIMagicTable(Type type) + { + return type.GetInterfaces().Any(i => i.IsGenericType + && i.GetGenericTypeDefinition() == typeof(IMagicTable<>)); + } - public static List? GetAllMagicTables() - { - var assemblies = AppDomain.CurrentDomain.GetAssemblies(); - return assemblies - .SelectMany(a => a.GetTypes()) - .Where(t => t.IsClass && !t.IsAbstract && SchemaHelper.ImplementsIMagicTable(t)) - .ToList(); - } + public static List? GetAllMagicTables() + { + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + return assemblies + .SelectMany(a => a.GetTypes()) + .Where(t => t.IsClass && !t.IsAbstract && SchemaHelper.ImplementsIMagicTable(t)) + .ToList(); + } - public static List? GetAllMagicRepositories() - { - var assemblies = AppDomain.CurrentDomain.GetAssemblies(); - return assemblies - .SelectMany(a => a.GetTypes()) - .Where(t => t.IsClass && !t.IsAbstract && ImplementsIMagicRepository(t)) - .ToList(); - } + public static List? GetAllMagicRepositories() + { + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + return assemblies + .SelectMany(a => a.GetTypes()) + .Where(t => t.IsClass && !t.IsAbstract && ImplementsIMagicRepository(t)) + .ToList(); + } - public static List GetAllIndexedDbSets() - { - var result = new List(); + public static List GetAllIndexedDbSets() + { + var result = new List(); - var repoTypes = GetAllMagicRepositories(); - if (repoTypes == null || repoTypes.Count == 0) - return result; + var repoTypes = GetAllMagicRepositories(); + if (repoTypes == null || repoTypes.Count == 0) + return result; - foreach (var type in repoTypes) - { - var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance); + foreach (var type in repoTypes) + { + var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance); - foreach (var field in fields) + foreach (var field in fields) + { + if (field.FieldType == typeof(IndexedDbSet)) { - if (field.FieldType == typeof(IndexedDbSet)) + // Handle static or instance field + object? instance = null; + if (!field.IsStatic) { - // Handle static or instance field - object? instance = null; - if (!field.IsStatic) + try { - try - { - instance = Activator.CreateInstance(type); - } - catch - { - continue; // Can't create instance, skip - } + instance = Activator.CreateInstance(type); } - - var value = field.GetValue(instance); - if (value is IndexedDbSet set) + catch { - result.Add(set); + continue; // Can't create instance, skip } } + + var value = field.GetValue(instance); + if (value is IndexedDbSet set) + { + result.Add(set); + } } } - - return result; - } - - - public static bool ImplementsIMagicRepository(Type type) - { - return type.GetInterfaces().Any(i => i == typeof(IMagicRepository)); } + return result; + } - private static readonly ConcurrentDictionary _tableNameCache = new(); - private static readonly ConcurrentDictionary _databaseNameCache = new(); - public static string GetDefaultDatabaseName() where T : IMagicTableBase, new() - { - Type type = typeof(T); + public static bool ImplementsIMagicRepository(Type type) + { + return type.GetInterfaces().Any(i => i == typeof(IMagicRepository)); + } - if (_databaseNameCache.TryGetValue(type, out string cachedDatabaseName)) - { - return cachedDatabaseName; - } - // Create an instance of T and retrieve database name - var instance = new T(); - string databaseName = instance.GetDefaultDatabase().DatabaseName; + private static readonly ConcurrentDictionary _tableNameCache = new(); + private static readonly ConcurrentDictionary _databaseNameCache = new(); - _databaseNameCache[type] = databaseName; - return databaseName; - } + public static string GetDefaultDatabaseName() where T : IMagicTableBase, new() + { + Type type = typeof(T); - public static string GetTableName() where T : class + if (_databaseNameCache.TryGetValue(type, out string cachedDatabaseName)) { - Type type = typeof(T); - - if (_tableNameCache.TryGetValue(type, out string cachedTableName)) - { - return cachedTableName; - } - - // Check if the type implements IMagicTableBase - if (!typeof(IMagicTableBase).IsAssignableFrom(type)) - { - throw new InvalidOperationException($"Type {type.Name} does not implement IMagicTableBase."); - } + return cachedDatabaseName; + } - // Create an instance dynamically and retrieve the table name - if (Activator.CreateInstance(type) is not IMagicTableBase instance) - { - throw new InvalidOperationException($"Failed to create an instance of {type.Name}."); - } + // Create an instance of T and retrieve database name + var instance = new T(); + string databaseName = instance.GetDefaultDatabase().DatabaseName; - string tableName = instance.GetTableName(); + _databaseNameCache[type] = databaseName; + return databaseName; + } - if (string.IsNullOrWhiteSpace(tableName)) - { - throw new InvalidOperationException($"Type {type.Name} returned an invalid table name."); - } + public static string GetTableName() where T : class + { + Type type = typeof(T); - _tableNameCache[type] = tableName; - return tableName; + if (_tableNameCache.TryGetValue(type, out string cachedTableName)) + { + return cachedTableName; } - public static bool HasMagicTableInterface(Type type) + // Check if the type implements IMagicTableBase + if (!typeof(IMagicTableBase).IsAssignableFrom(type)) { - return typeof(IMagicTableBase).IsAssignableFrom(type); + throw new InvalidOperationException($"Type {type.Name} does not implement IMagicTableBase."); } - /// - /// Retrieves all schemas for a given database name. - /// - public static List GetAllSchemas(string? databaseName = null) + // Create an instance dynamically and retrieve the table name + if (Activator.CreateInstance(type) is not IMagicTableBase instance) { - var schemas = new List(); + throw new InvalidOperationException($"Failed to create an instance of {type.Name}."); + } - // 🔹 Retrieve all valid magic tables - var magicTables = GetAllMagicTables(); + string tableName = instance.GetTableName(); - foreach (var type in magicTables) - { - // Ensure schema is cached - EnsureSchemaIsCached(type); + if (string.IsNullOrWhiteSpace(tableName)) + { + throw new InvalidOperationException($"Type {type.Name} returned an invalid table name."); + } - // Retrieve cached entry - if (!_schemaCache.TryGetValue(type, out var instance) || instance == null) - continue; // Skip if the type is not a valid IMagicTableBase + _tableNameCache[type] = tableName; + return tableName; + } - // 🚀 Get the store schema - var schema = GetStoreSchema(type); - schemas.Add(schema); - } + public static bool HasMagicTableInterface(Type type) + { + return typeof(IMagicTableBase).IsAssignableFrom(type); + } - return schemas; - } + /// + /// Retrieves all schemas for a given database name. + /// + public static List GetAllSchemas(string? databaseName = null) + { + var schemas = new List(); + // 🔹 Retrieve all valid magic tables + var magicTables = GetAllMagicTables(); - /// - /// Retrieves the store schema from a given type. - /// Now fully optimized by leveraging cached attributes and property mappings. - /// - public static StoreSchema GetStoreSchema(Type type) + foreach (var type in magicTables) { - PropertyMappingCache.EnsureTypeIsCached(type); + // Ensure schema is cached EnsureSchemaIsCached(type); - // Retrieve the cached entry - if (!_schemaCache.TryGetValue(type, out var instance)) - { - throw new InvalidOperationException($"Type {type.Name} is not cached."); - } + // Retrieve cached entry + if (!_schemaCache.TryGetValue(type, out var instance) || instance == null) + continue; // Skip if the type is not a valid IMagicTableBase - // Ensure the type implements IMagicTableBase - if (instance == null) - { - throw new InvalidOperationException($"Type {type.Name} does not implement IMagicTableBase and cannot be used as a Magic Table."); - } + // 🚀 Get the store schema + var schema = GetStoreSchema(type); + schemas.Add(schema); + } - // Retrieve table name - var tableName = instance.GetTableName(); - if (string.IsNullOrWhiteSpace(tableName)) - { - throw new InvalidOperationException($"Type {type.Name} returned an invalid table name."); - } + return schemas; + } - var schema = new StoreSchema - { - TableName = tableName - }; - // Extract Compound Key - var compoundKey = instance.GetKeys(); - if (compoundKey == null || compoundKey.PropertyInfos.Length == 0) - { - throw new InvalidOperationException($"The entity {type.Name} does not have a valid primary key."); - } + /// + /// Retrieves the store schema from a given type. + /// Now fully optimized by leveraging cached attributes and property mappings. + /// + public static StoreSchema GetStoreSchema(Type type) + { + PropertyMappingCache.EnsureTypeIsCached(type); + EnsureSchemaIsCached(type); - // If only one key → Single primary key, otherwise compound key - schema.ColumnNamesInCompoundKey = compoundKey.PropertyInfos - .Select(prop => PropertyMappingCache.GetJsPropertyName(prop, type)) - .ToList(); + // Retrieve the cached entry + if (!_schemaCache.TryGetValue(type, out var instance)) + { + throw new InvalidOperationException($"Type {type.Name} is not cached."); + } - schema.PrimaryKeyAuto = compoundKey.AutoIncrement; // AutoIncrement only applies to single primary keys + // Ensure the type implements IMagicTableBase + if (instance == null) + { + throw new InvalidOperationException($"Type {type.Name} does not implement IMagicTableBase and cannot be used as a Magic Table."); + } - // Extract Unique Indexes - schema.UniqueIndexes = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy) - .Where(prop => PropertyMappingCache.GetPropertyEntry(prop, type).UniqueIndex) - .Select(prop => PropertyMappingCache.GetJsPropertyName(prop, type)) - .ToList(); + // Retrieve table name + var tableName = instance.GetTableName(); + if (string.IsNullOrWhiteSpace(tableName)) + { + throw new InvalidOperationException($"Type {type.Name} returned an invalid table name."); + } - // ✅ Extract Standard Indexes - schema.Indexes = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy) - .Where(prop => PropertyMappingCache.GetPropertyEntry(prop, type).Indexed) - .Select(prop => PropertyMappingCache.GetJsPropertyName(prop, type)) - .ToList(); + var schema = new StoreSchema + { + TableName = tableName + }; - // Extract Compound Indexes - var compoundIndexes = instance.GetCompoundIndexes(); - if (compoundIndexes != null) - { - schema.ColumnNamesInCompoundIndex = compoundIndexes - .Select(index => index.ColumnNamesInCompoundIndex.ToList()) - .ToList(); - } + // Extract Compound Key + var compoundKey = instance.GetKeys(); + if (compoundKey == null || compoundKey.PropertyInfos.Length == 0) + { + throw new InvalidOperationException($"The entity {type.Name} does not have a valid primary key."); + } + + // If only one key → Single primary key, otherwise compound key + schema.ColumnNamesInCompoundKey = compoundKey.PropertyInfos + .Select(prop => PropertyMappingCache.GetJsPropertyName(prop, type)) + .ToList(); - return schema; + schema.PrimaryKeyAuto = compoundKey.AutoIncrement; // AutoIncrement only applies to single primary keys + + // Extract Unique Indexes + schema.UniqueIndexes = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy) + .Where(prop => PropertyMappingCache.GetPropertyEntry(prop, type).UniqueIndex) + .Select(prop => PropertyMappingCache.GetJsPropertyName(prop, type)) + .ToList(); + + // ✅ Extract Standard Indexes + schema.Indexes = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy) + .Where(prop => PropertyMappingCache.GetPropertyEntry(prop, type).Indexed) + .Select(prop => PropertyMappingCache.GetJsPropertyName(prop, type)) + .ToList(); + + // Extract Compound Indexes + var compoundIndexes = instance.GetCompoundIndexes(); + if (compoundIndexes != null) + { + schema.ColumnNamesInCompoundIndex = compoundIndexes + .Select(index => index.ColumnNamesInCompoundIndex.ToList()) + .ToList(); } + + return schema; } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/IndexDbManager.cs b/Magic.IndexedDb/IndexDbManager.cs index 6de6993..a5405a3 100644 --- a/Magic.IndexedDb/IndexDbManager.cs +++ b/Magic.IndexedDb/IndexDbManager.cs @@ -17,312 +17,311 @@ using Magic.IndexedDb.LinqTranslation.Interfaces; using Magic.IndexedDb.LinqTranslation.Models; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +/// +/// Provides functionality for accessing IndexedDB from Blazor application +/// +internal class IndexedDbManager { + private readonly IJSObjectReference _jsModule; // Shared JS module + private readonly long _jsMessageSizeBytes; + /// - /// Provides functionality for accessing IndexedDB from Blazor application + /// Ctor /// - internal class IndexedDbManager + /// + /// + internal IndexedDbManager(IJSObjectReference jsModule, long jsMessageSizeBytes) { - private readonly IJSObjectReference _jsModule; // Shared JS module - private readonly long _jsMessageSizeBytes; - - /// - /// Ctor - /// - /// - /// - internal IndexedDbManager(IJSObjectReference jsModule, long jsMessageSizeBytes) - { - _jsModule = jsModule; // Use shared JS module reference - _jsMessageSizeBytes = jsMessageSizeBytes; - } + _jsModule = jsModule; // Use shared JS module reference + _jsMessageSizeBytes = jsMessageSizeBytes; + } - //public string DbName => _dbStore.Name; + //public string DbName => _dbStore.Name; - /// - /// Deletes the database corresponding to the dbName passed in - /// - /// The name of database to delete - /// - internal Task DeleteDbAsync(string dbName) + /// + /// Deletes the database corresponding to the dbName passed in + /// + /// The name of database to delete + /// + internal Task DeleteDbAsync(string dbName) + { + if (string.IsNullOrEmpty(dbName)) { - if (string.IsNullOrEmpty(dbName)) - { - throw new ArgumentException("dbName cannot be null or empty", nameof(dbName)); - } - return new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallInvokeVoidDefaultJsAsync(Cache.MagicDbJsImportPath, - IndexedDbFunctions.DELETE_DB, dbName); + throw new ArgumentException("dbName cannot be null or empty", nameof(dbName)); } + return new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallInvokeVoidDefaultJsAsync(Cache.MagicDbJsImportPath, + IndexedDbFunctions.DELETE_DB, dbName); + } - internal Task CloseDbAsync(string dbName) + internal Task CloseDbAsync(string dbName) + { + if (string.IsNullOrEmpty(dbName)) { - if (string.IsNullOrEmpty(dbName)) - { - throw new ArgumentException("dbName cannot be null or empty", nameof(dbName)); - } - return new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallInvokeVoidDefaultJsAsync(Cache.MagicDbJsImportPath, - IndexedDbFunctions.CLOSE_DB, dbName); + throw new ArgumentException("dbName cannot be null or empty", nameof(dbName)); } + return new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallInvokeVoidDefaultJsAsync(Cache.MagicDbJsImportPath, + IndexedDbFunctions.CLOSE_DB, dbName); + } - internal Task OpenDbAsync(string dbName) + internal Task OpenDbAsync(string dbName) + { + if (string.IsNullOrEmpty(dbName)) { - if (string.IsNullOrEmpty(dbName)) - { - throw new ArgumentException("dbName cannot be null or empty", nameof(dbName)); - } - return new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallInvokeVoidDefaultJsAsync(Cache.MagicDbJsImportPath, - IndexedDbFunctions.OPEN_DB, dbName); + throw new ArgumentException("dbName cannot be null or empty", nameof(dbName)); } + return new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallInvokeVoidDefaultJsAsync(Cache.MagicDbJsImportPath, + IndexedDbFunctions.OPEN_DB, dbName); + } - internal Task DoesDbExist(string dbName) + internal Task DoesDbExist(string dbName) + { + if (string.IsNullOrEmpty(dbName)) { - if (string.IsNullOrEmpty(dbName)) - { - throw new ArgumentException("dbName cannot be null or empty", nameof(dbName)); - } - return new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallInvokeDefaultJsAsync(Cache.MagicDbJsImportPath, - IndexedDbFunctions.DOES_DB_EXIST, dbName); + throw new ArgumentException("dbName cannot be null or empty", nameof(dbName)); } + return new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallInvokeDefaultJsAsync(Cache.MagicDbJsImportPath, + IndexedDbFunctions.DOES_DB_EXIST, dbName); + } - internal Task IsDbOpen(string dbName) + internal Task IsDbOpen(string dbName) + { + if (string.IsNullOrEmpty(dbName)) { - if (string.IsNullOrEmpty(dbName)) - { - throw new ArgumentException("dbName cannot be null or empty", nameof(dbName)); - } - return new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallInvokeDefaultJsAsync(Cache.MagicDbJsImportPath, - IndexedDbFunctions.IS_DB_CLosed, dbName); + throw new ArgumentException("dbName cannot be null or empty", nameof(dbName)); } + return new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallInvokeDefaultJsAsync(Cache.MagicDbJsImportPath, + IndexedDbFunctions.IS_DB_CLosed, dbName); + } + + internal async Task AddAsync(T record, string dbName, CancellationToken cancellationToken = default) where T : class + { + string schemaName = SchemaHelper.GetTableName(); - internal async Task AddAsync(T record, string dbName, CancellationToken cancellationToken = default) where T : class + StoreRecord RecordToSend = new StoreRecord() { - string schemaName = SchemaHelper.GetTableName(); + DbName = dbName, + StoreName = schemaName, + Record = record + }; + return await new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallJsAsync(Cache.MagicDbJsImportPath, + IndexedDbFunctions.ADD_ITEM, cancellationToken, new TypedArgument>(RecordToSend)) + ?? throw new Exception($"An Error occurred trying to add your record of type: {typeof(T).Name}"); + } - StoreRecord RecordToSend = new StoreRecord() - { - DbName = dbName, - StoreName = schemaName, - Record = record - }; - return await new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallJsAsync(Cache.MagicDbJsImportPath, - IndexedDbFunctions.ADD_ITEM, cancellationToken, new TypedArgument>(RecordToSend)) - ?? throw new Exception($"An Error occurred trying to add your record of type: {typeof(T).Name}"); - } + /// + /// Adds records/objects to the specified store in bulk + /// Waits for response + /// + /// + /// An instance of StoreRecord that provides the store name and the data to add + /// + internal Task BulkAddRecordAsync( + string storeName, string dbName, + IEnumerable recordsToBulkAdd, + CancellationToken cancellationToken = default) + { + // TODO: https://github.com/magiccodingman/Magic.IndexedDb/issues/9 - /// - /// Adds records/objects to the specified store in bulk - /// Waits for response - /// - /// - /// An instance of StoreRecord that provides the store name and the data to add - /// - internal Task BulkAddRecordAsync( - string storeName, string dbName, - IEnumerable recordsToBulkAdd, - CancellationToken cancellationToken = default) - { - // TODO: https://github.com/magiccodingman/Magic.IndexedDb/issues/9 + return new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallJsAsync(Cache.MagicDbJsImportPath, IndexedDbFunctions.BULKADD_ITEM, cancellationToken, + new ITypedArgument[] { new TypedArgument(dbName), + new TypedArgument(storeName), + new TypedArgument>(recordsToBulkAdd) }); + } - return new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallJsAsync(Cache.MagicDbJsImportPath, IndexedDbFunctions.BULKADD_ITEM, cancellationToken, - new ITypedArgument[] { new TypedArgument(dbName), - new TypedArgument(storeName), - new TypedArgument>(recordsToBulkAdd) }); - } + internal async Task UpdateAsync(T item, string dbName, CancellationToken cancellationToken = default) where T : class + { + string schemaName = SchemaHelper.GetTableName(); + + List primaryKeyValue = AttributeHelpers.GetPrimaryKeys(item); + if (primaryKeyValue is null) + throw new ArgumentException("Item being updated must have a key."); - internal async Task UpdateAsync(T item, string dbName, CancellationToken cancellationToken = default) where T : class + UpdateRecord record = new UpdateRecord() { - string schemaName = SchemaHelper.GetTableName(); + Key = primaryKeyValue, + DbName = dbName, + StoreName = schemaName, + Record = item + }; + + return await new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallJsAsync(Cache.MagicDbJsImportPath, + IndexedDbFunctions.UPDATE_ITEM, cancellationToken, new TypedArgument>(record)); + } + internal async Task CountEntireTableAsync(string schemaName, string dbName) + { + return await new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallInvokeDefaultJsAsync(Cache.MagicDbJsImportPath, + IndexedDbFunctions.COUNT_TABLE, + dbName, schemaName + ); + } + + internal async Task UpdateRangeAsync( + IEnumerable items, string dbName, + CancellationToken cancellationToken = default) where T : class + { + string schemaName = SchemaHelper.GetTableName(); + + var recordsToUpdate = items.Select(item => + { List primaryKeyValue = AttributeHelpers.GetPrimaryKeys(item); if (primaryKeyValue is null) throw new ArgumentException("Item being updated must have a key."); - UpdateRecord record = new UpdateRecord() + return new UpdateRecord() { Key = primaryKeyValue, DbName = dbName, StoreName = schemaName, Record = item }; + }); - return await new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallJsAsync(Cache.MagicDbJsImportPath, - IndexedDbFunctions.UPDATE_ITEM, cancellationToken, new TypedArgument>(record)); - } + return await new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallJsAsync(Cache.MagicDbJsImportPath, + IndexedDbFunctions.BULKADD_UPDATE, cancellationToken, new TypedArgument>>(recordsToUpdate)); + } - internal async Task CountEntireTableAsync(string schemaName, string dbName) - { - return await new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallInvokeDefaultJsAsync(Cache.MagicDbJsImportPath, - IndexedDbFunctions.COUNT_TABLE, - dbName, schemaName - ); - } + internal IMagicQuery Query(string databaseName, string schemaName) where T : class + { + MagicQuery query = new MagicQuery(databaseName, schemaName, this); + return query; + } - internal async Task UpdateRangeAsync( - IEnumerable items, string dbName, - CancellationToken cancellationToken = default) where T : class - { - string schemaName = SchemaHelper.GetTableName(); + internal IMagicDatabaseScoped Database(IndexedDbManager manager, IndexedDbSet indexedDbSet) + { + return new MagicDatabaseScoped(manager, indexedDbSet); + } - var recordsToUpdate = items.Select(item => - { - List primaryKeyValue = AttributeHelpers.GetPrimaryKeys(item); - if (primaryKeyValue is null) - throw new ArgumentException("Item being updated must have a key."); - - return new UpdateRecord() - { - Key = primaryKeyValue, - DbName = dbName, - StoreName = schemaName, - Record = item - }; - }); - - return await new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallJsAsync(Cache.MagicDbJsImportPath, - IndexedDbFunctions.BULKADD_UPDATE, cancellationToken, new TypedArgument>>(recordsToUpdate)); - } + /// + /// Results do not come back in the same order. + /// + /// + /// + /// + /// + /// + /// + internal async Task?> LinqToIndexedDb( + FilterNode nestedOrFilter, MagicQuery query, + CancellationToken cancellationToken) where T : class + { + //if (nestedOrFilter.universalFalse == true) + // return default; + + var args = new ITypedArgument[] { + new TypedArgument(query.DatabaseName), + new TypedArgument(query.SchemaName), + new TypedArgument(nestedOrFilter), + new TypedArgument?>(query?.StoredMagicQueries), + new TypedArgument(query?.ForceCursorMode??false), + }; + + return await new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallJsAsync> + (Cache.MagicDbJsImportPath, IndexedDbFunctions.MAGIC_QUERY_ASYNC, cancellationToken, + args); + } - internal IMagicQuery Query(string databaseName, string schemaName) where T : class - { - MagicQuery query = new MagicQuery(databaseName, schemaName, this); - return query; - } + /// + /// Applying the returned ordering on this isn't possible to be guarenteed. + /// Developer must be informed that ordering is correct in the IndexDB response + /// but that the returned values may not be in the same order, so they must + /// manually re-apply the ordering desired. + /// + /// + /// + /// + /// + /// + /// + internal async IAsyncEnumerable LinqToIndexedDbYield( + FilterNode nestedOrFilter, MagicQuery query, + [EnumeratorCancellation] CancellationToken cancellationToken) where T : class + { - internal IMagicDatabaseScoped Database(IndexedDbManager manager, IndexedDbSet indexedDbSet) - { - return new MagicDatabaseScoped(manager, indexedDbSet); - } + var args = new ITypedArgument[] { + new TypedArgument(query.DatabaseName), + new TypedArgument(query.SchemaName), + new TypedArgument(nestedOrFilter), + new TypedArgument?>(query?.StoredMagicQueries), + new TypedArgument(query?.ForceCursorMode??false), + }; - /// - /// Results do not come back in the same order. - /// - /// - /// - /// - /// - /// - /// - internal async Task?> LinqToIndexedDb( - FilterNode nestedOrFilter, MagicQuery query, - CancellationToken cancellationToken) where T : class + // Yield results **as they arrive** from JS + await foreach (var item in new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallYieldJsAsync(Cache.MagicDbJsImportPath, IndexedDbFunctions.MAGIC_QUERY_YIELD, cancellationToken, args)) { - //if (nestedOrFilter.universalFalse == true) - // return default; - - var args = new ITypedArgument[] { - new TypedArgument(query.DatabaseName), - new TypedArgument(query.SchemaName), - new TypedArgument(nestedOrFilter), - new TypedArgument?>(query?.StoredMagicQueries), - new TypedArgument(query?.ForceCursorMode??false), - }; - - return await new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallJsAsync> - (Cache.MagicDbJsImportPath, IndexedDbFunctions.MAGIC_QUERY_ASYNC, cancellationToken, - args); + yield return item; // Stream each item immediately } + } - /// - /// Applying the returned ordering on this isn't possible to be guarenteed. - /// Developer must be informed that ordering is correct in the IndexDB response - /// but that the returned values may not be in the same order, so they must - /// manually re-apply the ordering desired. - /// - /// - /// - /// - /// - /// - /// - internal async IAsyncEnumerable LinqToIndexedDbYield( - FilterNode nestedOrFilter, MagicQuery query, - [EnumeratorCancellation] CancellationToken cancellationToken) where T : class - { - - var args = new ITypedArgument[] { - new TypedArgument(query.DatabaseName), - new TypedArgument(query.SchemaName), - new TypedArgument(nestedOrFilter), - new TypedArgument?>(query?.StoredMagicQueries), - new TypedArgument(query?.ForceCursorMode??false), - }; + internal async Task DeleteAsync(T item, string dbName, CancellationToken cancellationToken = default) where T : class + { + string schemaName = SchemaHelper.GetTableName(); - // Yield results **as they arrive** from JS - await foreach (var item in new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallYieldJsAsync(Cache.MagicDbJsImportPath, IndexedDbFunctions.MAGIC_QUERY_YIELD, cancellationToken, args)) - { - yield return item; // Stream each item immediately - } - } + List primaryKeyValue = AttributeHelpers.GetPrimaryKeys(item); - internal async Task DeleteAsync(T item, string dbName, CancellationToken cancellationToken = default) where T : class + UpdateRecord record = new UpdateRecord() { - string schemaName = SchemaHelper.GetTableName(); - - List primaryKeyValue = AttributeHelpers.GetPrimaryKeys(item); + Key = primaryKeyValue, + DbName = dbName, + StoreName = schemaName, + Record = item + }; - UpdateRecord record = new UpdateRecord() - { - Key = primaryKeyValue, - DbName = dbName, - StoreName = schemaName, - Record = item - }; + await new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallJsAsync(Cache.MagicDbJsImportPath, IndexedDbFunctions.DELETE_ITEM, cancellationToken, new TypedArgument>(record)); + } - await new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallJsAsync(Cache.MagicDbJsImportPath, IndexedDbFunctions.DELETE_ITEM, cancellationToken, new TypedArgument>(record)); - } + internal async Task DeleteRangeAsync( + IEnumerable items, string dbName, CancellationToken cancellationToken = default) where T : class + { + string schemaName = SchemaHelper.GetTableName(); - internal async Task DeleteRangeAsync( - IEnumerable items, string dbName, CancellationToken cancellationToken = default) where T : class + var keys = items.Select(item => { - string schemaName = SchemaHelper.GetTableName(); - - var keys = items.Select(item => - { - List primaryKeyValue = AttributeHelpers.GetPrimaryKeys(item); - if (primaryKeyValue is null) - throw new ArgumentException("Item being deleted must have a key."); - return primaryKeyValue; - }); - - var args = new ITypedArgument[] { - new TypedArgument(dbName), - new TypedArgument(schemaName), - new TypedArgument?>(keys) }; - - return await new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallJsAsync(Cache.MagicDbJsImportPath, - IndexedDbFunctions.BULK_DELETE, cancellationToken, - args); - } + List primaryKeyValue = AttributeHelpers.GetPrimaryKeys(item); + if (primaryKeyValue is null) + throw new ArgumentException("Item being deleted must have a key."); + return primaryKeyValue; + }); + + var args = new ITypedArgument[] { + new TypedArgument(dbName), + new TypedArgument(schemaName), + new TypedArgument?>(keys) }; + + return await new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallJsAsync(Cache.MagicDbJsImportPath, + IndexedDbFunctions.BULK_DELETE, cancellationToken, + args); + } - /// - /// Clears all data from a Table but keeps the table - /// Wait for response - /// - /// - /// - internal Task ClearTableAsync(string storeName, string dbName) - { - return new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallInvokeVoidDefaultJsAsync(Cache.MagicDbJsImportPath, - IndexedDbFunctions.CLEAR_TABLE, dbName, storeName); - } + /// + /// Clears all data from a Table but keeps the table + /// Wait for response + /// + /// + /// + internal Task ClearTableAsync(string storeName, string dbName) + { + return new MagicJsInvoke(_jsModule, _jsMessageSizeBytes).CallInvokeVoidDefaultJsAsync(Cache.MagicDbJsImportPath, + IndexedDbFunctions.CLEAR_TABLE, dbName, storeName); + } - /// - /// Clears all data from a Table but keeps the table - /// Wait for response - /// - /// - /* internal Task ClearTableAsync(CancellationToken cancellationToken = default) where T : class - { - string schemaName = SchemaHelper.GetTableName(); - string databaseName = SchemaHelper.GetDefaultDatabaseName(); - return ClearTableAsync(schemaName, databaseName, cancellationToken); - }*/ + /// + /// Clears all data from a Table but keeps the table + /// Wait for response + /// + /// + /* internal Task ClearTableAsync(CancellationToken cancellationToken = default) where T : class + { + string schemaName = SchemaHelper.GetTableName(); + string databaseName = SchemaHelper.GetDefaultDatabaseName(); + return ClearTableAsync(schemaName, databaseName, cancellationToken); + }*/ - } } \ No newline at end of file diff --git a/Magic.IndexedDb/IndexedDbFunctions.cs b/Magic.IndexedDb/IndexedDbFunctions.cs index 2309d45..dda7b8d 100644 --- a/Magic.IndexedDb/IndexedDbFunctions.cs +++ b/Magic.IndexedDb/IndexedDbFunctions.cs @@ -1,35 +1,34 @@ -namespace Magic.IndexedDb -{ - internal struct IndexedDbFunctions - { - public const string CREATE_DATABASES = "createDatabases"; - public const string CREATE_LEGACY = "createDb"; - public const string CLOSE_ALL = "closeAll"; - public const string DELETE_DB = "deleteDb"; - public const string CLOSE_DB = "closeDb"; - public const string OPEN_DB = "openDb"; - public const string DOES_DB_EXIST = "doesDbExist"; - public const string IS_DB_CLosed = "isDbOpen"; - public const string ADD_ITEM = "addItem"; - public const string BULKADD_ITEM = "bulkAddItem"; - public const string COUNT_TABLE = "countTable"; +namespace Magic.IndexedDb; - // TODO: not referenced? - public const string PUT_ITEM = "putItem"; +internal struct IndexedDbFunctions +{ + public const string CREATE_DATABASES = "createDatabases"; + public const string CREATE_LEGACY = "createDb"; + public const string CLOSE_ALL = "closeAll"; + public const string DELETE_DB = "deleteDb"; + public const string CLOSE_DB = "closeDb"; + public const string OPEN_DB = "openDb"; + public const string DOES_DB_EXIST = "doesDbExist"; + public const string IS_DB_CLosed = "isDbOpen"; + public const string ADD_ITEM = "addItem"; + public const string BULKADD_ITEM = "bulkAddItem"; + public const string COUNT_TABLE = "countTable"; - // hooking up - public const string BULKPUT_ITEM = "bulkPutItems"; + // TODO: not referenced? + public const string PUT_ITEM = "putItem"; + // hooking up + public const string BULKPUT_ITEM = "bulkPutItems"; - public const string UPDATE_ITEM = "updateItem"; - public const string BULKADD_UPDATE = "bulkUpdateItem"; - public const string BULK_DELETE = "bulkDelete"; - public const string DELETE_ITEM = "deleteItem"; - public const string CLEAR_TABLE = "clear"; - public const string FIND_ITEM = "findItem"; - public const string TOARRAY = "toArray"; - public const string GET_STORAGE_ESTIMATE = "getStorageEstimate"; - public const string MAGIC_QUERY_ASYNC = "wrapperMagicQueryAsync"; - public const string MAGIC_QUERY_YIELD = "wrapperMagicQueryYield"; - } + + public const string UPDATE_ITEM = "updateItem"; + public const string BULKADD_UPDATE = "bulkUpdateItem"; + public const string BULK_DELETE = "bulkDelete"; + public const string DELETE_ITEM = "deleteItem"; + public const string CLEAR_TABLE = "clear"; + public const string FIND_ITEM = "findItem"; + public const string TOARRAY = "toArray"; + public const string GET_STORAGE_ESTIMATE = "getStorageEstimate"; + public const string MAGIC_QUERY_ASYNC = "wrapperMagicQueryAsync"; + public const string MAGIC_QUERY_YIELD = "wrapperMagicQueryYield"; } \ No newline at end of file diff --git a/Magic.IndexedDb/Interfaces/IColumnNamed.cs b/Magic.IndexedDb/Interfaces/IColumnNamed.cs index be856ed..48bfd25 100644 --- a/Magic.IndexedDb/Interfaces/IColumnNamed.cs +++ b/Magic.IndexedDb/Interfaces/IColumnNamed.cs @@ -4,11 +4,9 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Interfaces -{ - public interface IColumnNamed - { - string ColumnName { get; } - } +namespace Magic.IndexedDb.Interfaces; -} +public interface IColumnNamed +{ + string ColumnName { get; } +} \ No newline at end of file diff --git a/Magic.IndexedDb/Interfaces/IMagicCompoundIndex.cs b/Magic.IndexedDb/Interfaces/IMagicCompoundIndex.cs index 2b0d9e6..39ee16d 100644 --- a/Magic.IndexedDb/Interfaces/IMagicCompoundIndex.cs +++ b/Magic.IndexedDb/Interfaces/IMagicCompoundIndex.cs @@ -5,11 +5,10 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +public interface IMagicCompoundIndex { - public interface IMagicCompoundIndex - { - string[] ColumnNamesInCompoundIndex { get; } - PropertyInfo[] PropertyInfos { get; } - } -} + string[] ColumnNamesInCompoundIndex { get; } + PropertyInfo[] PropertyInfos { get; } +} \ No newline at end of file diff --git a/Magic.IndexedDb/Interfaces/IMagicCompoundKey.cs b/Magic.IndexedDb/Interfaces/IMagicCompoundKey.cs index 81d7f8d..13fdbbb 100644 --- a/Magic.IndexedDb/Interfaces/IMagicCompoundKey.cs +++ b/Magic.IndexedDb/Interfaces/IMagicCompoundKey.cs @@ -5,12 +5,11 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +public interface IMagicCompoundKey { - public interface IMagicCompoundKey - { - string[] ColumnNamesInCompoundKey { get; } - bool AutoIncrement { get; } - PropertyInfo[] PropertyInfos { get; } - } -} + string[] ColumnNamesInCompoundKey { get; } + bool AutoIncrement { get; } + PropertyInfo[] PropertyInfos { get; } +} \ No newline at end of file diff --git a/Magic.IndexedDb/Interfaces/IMagicConnectRepository.cs b/Magic.IndexedDb/Interfaces/IMagicConnectRepository.cs index 72f7197..5af7428 100644 --- a/Magic.IndexedDb/Interfaces/IMagicConnectRepository.cs +++ b/Magic.IndexedDb/Interfaces/IMagicConnectRepository.cs @@ -4,9 +4,8 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Interfaces +namespace Magic.IndexedDb.Interfaces; + +public interface IMagicConnectRepository { - public interface IMagicConnectRepository - { - } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/Interfaces/IMagicRepository.cs b/Magic.IndexedDb/Interfaces/IMagicRepository.cs index 48330a7..ecfa864 100644 --- a/Magic.IndexedDb/Interfaces/IMagicRepository.cs +++ b/Magic.IndexedDb/Interfaces/IMagicRepository.cs @@ -5,9 +5,8 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Interfaces +namespace Magic.IndexedDb.Interfaces; + +public interface IMagicRepository { - public interface IMagicRepository - { - } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/Interfaces/IMagicTable.cs b/Magic.IndexedDb/Interfaces/IMagicTable.cs index 4bbdd14..c22394c 100644 --- a/Magic.IndexedDb/Interfaces/IMagicTable.cs +++ b/Magic.IndexedDb/Interfaces/IMagicTable.cs @@ -7,10 +7,9 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +public interface IMagicTable : IMagicTableBase { - public interface IMagicTable : IMagicTableBase - { - TDbSets Databases { get; } // Enforce that every model has a `DbSets` instance - } -} + TDbSets Databases { get; } // Enforce that every model has a `DbSets` instance +} \ No newline at end of file diff --git a/Magic.IndexedDb/Interfaces/IMagicTableBase.cs b/Magic.IndexedDb/Interfaces/IMagicTableBase.cs index eb6e9f6..ce51f6b 100644 --- a/Magic.IndexedDb/Interfaces/IMagicTableBase.cs +++ b/Magic.IndexedDb/Interfaces/IMagicTableBase.cs @@ -4,19 +4,18 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Interfaces +namespace Magic.IndexedDb.Interfaces; + +public interface IMagicTableBase { - public interface IMagicTableBase - { - string GetTableName(); + string GetTableName(); - List? GetCompoundIndexes(); + List? GetCompoundIndexes(); - IMagicCompoundKey GetKeys(); + IMagicCompoundKey GetKeys(); - // - /// Set the default database most commonly utilized for this table. - /// - IndexedDbSet GetDefaultDatabase(); - } -} + // + /// Set the default database most commonly utilized for this table. + /// + IndexedDbSet GetDefaultDatabase(); +} \ No newline at end of file diff --git a/Magic.IndexedDb/Interfaces/IMagicUtilities.cs b/Magic.IndexedDb/Interfaces/IMagicUtilities.cs index 533533e..bed9efe 100644 --- a/Magic.IndexedDb/Interfaces/IMagicUtilities.cs +++ b/Magic.IndexedDb/Interfaces/IMagicUtilities.cs @@ -4,9 +4,8 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +public interface IMagicUtilities { - public interface IMagicUtilities - { - } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/Interfaces/ITypedArgument.cs b/Magic.IndexedDb/Interfaces/ITypedArgument.cs index dc7b14a..5b17c65 100644 --- a/Magic.IndexedDb/Interfaces/ITypedArgument.cs +++ b/Magic.IndexedDb/Interfaces/ITypedArgument.cs @@ -6,12 +6,11 @@ using System.Text.Json; using System.Threading.Tasks; -namespace Magic.IndexedDb.Interfaces +namespace Magic.IndexedDb.Interfaces; + +public interface ITypedArgument { - public interface ITypedArgument - { - string Serialize(); // Still needed for some cases - JsonElement SerializeToJsonElement(MagicJsonSerializationSettings? settings = null); // Ensures proper object passing - string SerializeToJsonString(MagicJsonSerializationSettings? settings = null); - } -} + string Serialize(); // Still needed for some cases + JsonElement SerializeToJsonElement(MagicJsonSerializationSettings? settings = null); // Ensures proper object passing + string SerializeToJsonString(MagicJsonSerializationSettings? settings = null); +} \ No newline at end of file diff --git a/Magic.IndexedDb/LinqTranslation/Extensions/MagicCursorExtension.cs b/Magic.IndexedDb/LinqTranslation/Extensions/MagicCursorExtension.cs index 597f6af..15411bd 100644 --- a/Magic.IndexedDb/LinqTranslation/Extensions/MagicCursorExtension.cs +++ b/Magic.IndexedDb/LinqTranslation/Extensions/MagicCursorExtension.cs @@ -14,68 +14,67 @@ using Magic.IndexedDb.Models.UniversalOperations; using Magic.IndexedDb.Extensions; -namespace Magic.IndexedDb.LinqTranslation.Extensions +namespace Magic.IndexedDb.LinqTranslation.Extensions; + +internal class MagicCursorExtension : IMagicCursorStage, IMagicCursorSkip, IMagicCursorPaginationTake, IMagicCursorFinal where T : class { - internal class MagicCursorExtension : IMagicCursorStage, IMagicCursorSkip, IMagicCursorPaginationTake, IMagicCursorFinal where T : class + public MagicQuery MagicQuery { get; set; } + public MagicCursorExtension(MagicQuery _magicQuery) { - public MagicQuery MagicQuery { get; set; } - public MagicCursorExtension(MagicQuery _magicQuery) - { - MagicQuery = _magicQuery; + MagicQuery = _magicQuery; - } + } - public IMagicCursorPaginationTake Take(int amount) - { - return new MagicCursorExtension(SharedQueryExtensions.Take(this.MagicQuery, amount)); - } + public IMagicCursorPaginationTake Take(int amount) + { + return new MagicCursorExtension(SharedQueryExtensions.Take(this.MagicQuery, amount)); + } - public IMagicCursorPaginationTake TakeLast(int amount) - { - return new MagicCursorExtension( - SharedQueryExtensions.TakeLast(this.MagicQuery, amount) - ); - } - /*public IMagicCursorSkip Skip(int amount) - => new MagicCursorExtension(MagicQuery).Skip(amount);*/ - public IMagicCursorSkip Skip(int amount) - { - return new MagicCursorExtension( - SharedQueryExtensions.Skip(this.MagicQuery, amount) - ); - } + public IMagicCursorPaginationTake TakeLast(int amount) + { + return new MagicCursorExtension( + SharedQueryExtensions.TakeLast(this.MagicQuery, amount) + ); + } + /*public IMagicCursorSkip Skip(int amount) + => new MagicCursorExtension(MagicQuery).Skip(amount);*/ + public IMagicCursorSkip Skip(int amount) + { + return new MagicCursorExtension( + SharedQueryExtensions.Skip(this.MagicQuery, amount) + ); + } - public IMagicCursorStage OrderBy(Expression> predicate) - { - return new MagicCursorExtension( - SharedQueryExtensions.OrderBy(this.MagicQuery, predicate) - ); - } + public IMagicCursorStage OrderBy(Expression> predicate) + { + return new MagicCursorExtension( + SharedQueryExtensions.OrderBy(this.MagicQuery, predicate) + ); + } - public IMagicCursorStage OrderByDescending(Expression> predicate) - { - return new MagicCursorExtension( - SharedQueryExtensions.OrderByDescending(this.MagicQuery, predicate) - ); - } + public IMagicCursorStage OrderByDescending(Expression> predicate) + { + return new MagicCursorExtension( + SharedQueryExtensions.OrderByDescending(this.MagicQuery, predicate) + ); + } - public async IAsyncEnumerable AsAsyncEnumerable( - [EnumeratorCancellation] CancellationToken cancellationToken = default) + public async IAsyncEnumerable AsAsyncEnumerable( + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + await foreach (var item in new MagicQueryExtensions(MagicQuery).AsAsyncEnumerable(cancellationToken) + .WithCancellation(cancellationToken)) { - await foreach (var item in new MagicQueryExtensions(MagicQuery).AsAsyncEnumerable(cancellationToken) - .WithCancellation(cancellationToken)) - { - yield return item; - } + yield return item; } + } - public async Task> ToListAsync() - => await new MagicQueryExtensions(MagicQuery).ToListAsync(); + public async Task> ToListAsync() + => await new MagicQueryExtensions(MagicQuery).ToListAsync(); - public async Task FirstOrDefaultAsync() - => await new MagicQueryExtensions(MagicQuery).FirstOrDefaultAsync(); + public async Task FirstOrDefaultAsync() + => await new MagicQueryExtensions(MagicQuery).FirstOrDefaultAsync(); - public async Task LastOrDefaultAsync() - => await new MagicQueryExtensions(MagicQuery).LastOrDefaultAsync(); - } -} + public async Task LastOrDefaultAsync() + => await new MagicQueryExtensions(MagicQuery).LastOrDefaultAsync(); +} \ No newline at end of file diff --git a/Magic.IndexedDb/LinqTranslation/Extensions/MagicQueryExtensions.cs b/Magic.IndexedDb/LinqTranslation/Extensions/MagicQueryExtensions.cs index cc65b0e..44df555 100644 --- a/Magic.IndexedDb/LinqTranslation/Extensions/MagicQueryExtensions.cs +++ b/Magic.IndexedDb/LinqTranslation/Extensions/MagicQueryExtensions.cs @@ -15,169 +15,168 @@ using Magic.IndexedDb.LinqTranslation.Models; using Magic.IndexedDb.Extensions; -namespace Magic.IndexedDb.LinqTranslation.Extensions +namespace Magic.IndexedDb.LinqTranslation.Extensions; + +internal class MagicQueryExtensions : + IMagicQueryPaginationTake, IMagicQueryOrderable, + IMagicQueryOrderableTable, IMagicQueryFinal + where T : class { - internal class MagicQueryExtensions : - IMagicQueryPaginationTake, IMagicQueryOrderable, - IMagicQueryOrderableTable, IMagicQueryFinal - where T : class - { - public MagicQuery MagicQuery { get; set; } + public MagicQuery MagicQuery { get; set; } - public MagicQueryExtensions(MagicQuery _magicQuery) - { - MagicQuery = _magicQuery; + public MagicQueryExtensions(MagicQuery _magicQuery) + { + MagicQuery = _magicQuery; - } + } - /// - /// EXPERIMENTAL FEATURE: - /// True IAsyncEnumerable between C# Blazor and JS. How?! - /// It's god damn magic! IMPORTANT NOTE: the order in which items - /// are returned may not be the order you specified. Your ordering - /// is properly utilized inside of IndexDB, but the returned process - /// due to IndexDB limitations can't return the same order. Please re-apply - /// your desired ordering after your results are brought back if order is important. - /// - /// - /// - public async IAsyncEnumerable AsAsyncEnumerable([EnumeratorCancellation] CancellationToken cancellationToken = default) + /// + /// EXPERIMENTAL FEATURE: + /// True IAsyncEnumerable between C# Blazor and JS. How?! + /// It's god damn magic! IMPORTANT NOTE: the order in which items + /// are returned may not be the order you specified. Your ordering + /// is properly utilized inside of IndexDB, but the returned process + /// due to IndexDB limitations can't return the same order. Please re-apply + /// your desired ordering after your results are brought back if order is important. + /// + /// + /// + public async IAsyncEnumerable AsAsyncEnumerable([EnumeratorCancellation] CancellationToken cancellationToken = default) + { + await foreach (var item in MagicQuery.Manager.LinqToIndexedDbYield(nestedOrFilter, MagicQuery, cancellationToken)) { - await foreach (var item in MagicQuery.Manager.LinqToIndexedDbYield(nestedOrFilter, MagicQuery, cancellationToken)) + if (item is not null) // Ensure non-null items { - if (item is not null) // Ensure non-null items - { - yield return item; - } + yield return item; } } + } - /// - /// The order you apply does get applied correctly in the query, - /// but the returned results will not be in the same order. - /// If order matters, you must apply the order again on return. - /// This is a fundemental limitation of IndexDB. - /// - /// - /// - /// - public async Task> WhereAsync( - Expression> predicate) - { - var items = await ToListAsync(); - return items.Where(predicate.Compile()); // Apply predicate after materialization - } + /// + /// The order you apply does get applied correctly in the query, + /// but the returned results will not be in the same order. + /// If order matters, you must apply the order again on return. + /// This is a fundemental limitation of IndexDB. + /// + /// + /// + /// + public async Task> WhereAsync( + Expression> predicate) + { + var items = await ToListAsync(); + return items.Where(predicate.Compile()); // Apply predicate after materialization + } - private FilterNode nestedOrFilter { get => GetCollectedBinaryJsonExpressions(); } + private FilterNode nestedOrFilter { get => GetCollectedBinaryJsonExpressions(); } - /// - /// The order you apply does get applied correctly in the query, - /// but the returned results will not be in the same order. - /// If order matters, you must apply the order again on return. - /// This is a fundemental limitation of IndexDB. - /// - /// - public async Task> ToListAsync() - { - return (await MagicQuery.Manager.LinqToIndexedDb( - nestedOrFilter, MagicQuery, default))?.ToList() ?? new List(); - } + /// + /// The order you apply does get applied correctly in the query, + /// but the returned results will not be in the same order. + /// If order matters, you must apply the order again on return. + /// This is a fundemental limitation of IndexDB. + /// + /// + public async Task> ToListAsync() + { + return (await MagicQuery.Manager.LinqToIndexedDb( + nestedOrFilter, MagicQuery, default))?.ToList() ?? new List(); + } - public IMagicQueryPaginationTake Take(int amount) - { - return new MagicQueryExtensions(SharedQueryExtensions.Take(this.MagicQuery, amount)); - } + public IMagicQueryPaginationTake Take(int amount) + { + return new MagicQueryExtensions(SharedQueryExtensions.Take(this.MagicQuery, amount)); + } - public async Task FirstOrDefaultAsync() - { - var _MagicQuery = new MagicQuery(this.MagicQuery); - StoredMagicQuery smq = new StoredMagicQuery(); - smq.additionFunction = MagicQueryFunctions.First; - _MagicQuery.StoredMagicQueries.Add(smq); + public async Task FirstOrDefaultAsync() + { + var _MagicQuery = new MagicQuery(this.MagicQuery); + StoredMagicQuery smq = new StoredMagicQuery(); + smq.additionFunction = MagicQueryFunctions.First; + _MagicQuery.StoredMagicQueries.Add(smq); - var items = await new MagicQueryExtensions(_MagicQuery).ToListAsync(); - return items.FirstOrDefault(); - } + var items = await new MagicQueryExtensions(_MagicQuery).ToListAsync(); + return items.FirstOrDefault(); + } - public async Task LastOrDefaultAsync() - { - var _MagicQuery = new MagicQuery(this.MagicQuery); - StoredMagicQuery smq = new StoredMagicQuery(); - smq.additionFunction = MagicQueryFunctions.Last; - _MagicQuery.StoredMagicQueries.Add(smq); - var items = await new MagicQueryExtensions(_MagicQuery).ToListAsync(); - return items.LastOrDefault(); - } + public async Task LastOrDefaultAsync() + { + var _MagicQuery = new MagicQuery(this.MagicQuery); + StoredMagicQuery smq = new StoredMagicQuery(); + smq.additionFunction = MagicQueryFunctions.Last; + _MagicQuery.StoredMagicQueries.Add(smq); + var items = await new MagicQueryExtensions(_MagicQuery).ToListAsync(); + return items.LastOrDefault(); + } - public async Task FirstOrDefaultAsync(Expression> predicate) - { - var _MagicQuery = new MagicQuery(this.MagicQuery); - _MagicQuery.Predicates.Add(predicate); - return await new MagicQueryExtensions(_MagicQuery).FirstOrDefaultAsync(); - } + public async Task FirstOrDefaultAsync(Expression> predicate) + { + var _MagicQuery = new MagicQuery(this.MagicQuery); + _MagicQuery.Predicates.Add(predicate); + return await new MagicQueryExtensions(_MagicQuery).FirstOrDefaultAsync(); + } - public async Task LastOrDefaultAsync(Expression> predicate) - { - var _MagicQuery = new MagicQuery(this.MagicQuery); - _MagicQuery.Predicates.Add(predicate); - return await new MagicQueryExtensions(_MagicQuery).LastOrDefaultAsync(); - } + public async Task LastOrDefaultAsync(Expression> predicate) + { + var _MagicQuery = new MagicQuery(this.MagicQuery); + _MagicQuery.Predicates.Add(predicate); + return await new MagicQueryExtensions(_MagicQuery).LastOrDefaultAsync(); + } - public IMagicQueryFinal TakeLast(int amount) - { - return new MagicQueryExtensions( - SharedQueryExtensions.TakeLast(this.MagicQuery, amount) - ); - } + public IMagicQueryFinal TakeLast(int amount) + { + return new MagicQueryExtensions( + SharedQueryExtensions.TakeLast(this.MagicQuery, amount) + ); + } - public IMagicQueryFinal Skip(int amount) - { - return new MagicQueryExtensions( - SharedQueryExtensions.Skip(this.MagicQuery, amount) - ); - } + public IMagicQueryFinal Skip(int amount) + { + return new MagicQueryExtensions( + SharedQueryExtensions.Skip(this.MagicQuery, amount) + ); + } - // Not currently available in Dexie version 1,2, or 3 - public IMagicQueryOrderableTable OrderBy(Expression> predicate) - { - return new MagicQueryExtensions( - SharedQueryExtensions.OrderBy(this.MagicQuery, predicate) - ); - } + // Not currently available in Dexie version 1,2, or 3 + public IMagicQueryOrderableTable OrderBy(Expression> predicate) + { + return new MagicQueryExtensions( + SharedQueryExtensions.OrderBy(this.MagicQuery, predicate) + ); + } - // Not currently available in Dexie version 1,2, or 3 - public IMagicQueryOrderableTable OrderByDescending(Expression> predicate) - { - return new MagicQueryExtensions( - SharedQueryExtensions.OrderByDescending(this.MagicQuery, predicate) - ); - } + // Not currently available in Dexie version 1,2, or 3 + public IMagicQueryOrderableTable OrderByDescending(Expression> predicate) + { + return new MagicQueryExtensions( + SharedQueryExtensions.OrderByDescending(this.MagicQuery, predicate) + ); + } - private FilterNode GetCollectedBinaryJsonExpressions() - { - Expression> preprocessedPredicate = PreprocessPredicate(); + private FilterNode GetCollectedBinaryJsonExpressions() + { + Expression> preprocessedPredicate = PreprocessPredicate(); - var builder = new UniversalExpressionBuilder(preprocessedPredicate); - var result = builder.Build(); - return result; - } + var builder = new UniversalExpressionBuilder(preprocessedPredicate); + var result = builder.Build(); + return result; + } - private bool IsUniversalFalse(Expression> predicate) - { - return predicate.Body is ConstantExpression constant && constant.Value is bool value && !value; - } + private bool IsUniversalFalse(Expression> predicate) + { + return predicate.Body is ConstantExpression constant && constant.Value is bool value && !value; + } - private Expression> PreprocessPredicate() - { - Expression> predicate = MagicQuery.GetFinalPredicate(); - var visitor = new PredicateVisitor(); - var newExpression = visitor.Visit(predicate.Body); + private Expression> PreprocessPredicate() + { + Expression> predicate = MagicQuery.GetFinalPredicate(); + var visitor = new PredicateVisitor(); + var newExpression = visitor.Visit(predicate.Body); - return Expression.Lambda>(newExpression, predicate.Parameters); - } + return Expression.Lambda>(newExpression, predicate.Parameters); } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/LinqTranslation/Extensions/UniversalExpressionBuilder.cs b/Magic.IndexedDb/LinqTranslation/Extensions/UniversalExpressionBuilder.cs index 84690f6..a32f80e 100644 --- a/Magic.IndexedDb/LinqTranslation/Extensions/UniversalExpressionBuilder.cs +++ b/Magic.IndexedDb/LinqTranslation/Extensions/UniversalExpressionBuilder.cs @@ -11,46 +11,73 @@ using System.Text.Json.Nodes; using System.Threading.Tasks; -namespace Magic.IndexedDb.LinqTranslation.Extensions +namespace Magic.IndexedDb.LinqTranslation.Extensions; + +public class UniversalExpressionBuilder { - public class UniversalExpressionBuilder + private readonly Expression> _predicate; + + public UniversalExpressionBuilder(Expression> predicate) { - private readonly Expression> _predicate; + _predicate = predicate; + } - public UniversalExpressionBuilder(Expression> predicate) - { - _predicate = predicate; - } + /// + /// Builds and returns the FilterNode (root) that represents the entire predicate. + /// + public FilterNode Build() + { + return ParseExpression(_predicate.Body); + } - /// - /// Builds and returns the FilterNode (root) that represents the entire predicate. - /// - public FilterNode Build() + private FilterNode ParseExpression(Expression expression) + { + return expression switch { - return ParseExpression(_predicate.Body); - } + BinaryExpression binaryExpr => ParseBinaryExpression(binaryExpr), + MethodCallExpression methodCall => ParseMethodCallExpression(methodCall), + UnaryExpression unaryExpr when unaryExpr.NodeType == ExpressionType.Not => ParseNotExpression(unaryExpr), + ConstantExpression constExpr => HandleConstantBoolean(constExpr), + MemberExpression memberExpr => HandleMemberBoolean(memberExpr), + _ => throw new InvalidOperationException($"Unsupported expression node: {expression}") + }; + } - private FilterNode ParseExpression(Expression expression) + private FilterNode HandleConstantBoolean(ConstantExpression expr) + { + if (expr.Type == typeof(bool)) { - return expression switch + var condition = new FilterCondition( + _property: "__constant", + _operation: "Equal", + _value: (bool)expr.Value!, + _isString: false, + _caseSensitive: false + ); + + return new FilterNode { - BinaryExpression binaryExpr => ParseBinaryExpression(binaryExpr), - MethodCallExpression methodCall => ParseMethodCallExpression(methodCall), - UnaryExpression unaryExpr when unaryExpr.NodeType == ExpressionType.Not => ParseNotExpression(unaryExpr), - ConstantExpression constExpr => HandleConstantBoolean(constExpr), - MemberExpression memberExpr => HandleMemberBoolean(memberExpr), - _ => throw new InvalidOperationException($"Unsupported expression node: {expression}") + NodeType = FilterNodeType.Condition, + Condition = condition }; } - private FilterNode HandleConstantBoolean(ConstantExpression expr) + throw new InvalidOperationException($"Unsupported constant type: {expr.Type}"); + } + + private FilterNode HandleMemberBoolean(MemberExpression memberExpr) + { + if (IsParameterMember(memberExpr)) { - if (expr.Type == typeof(bool)) + var propInfo = typeof(T).GetProperty(memberExpr.Member.Name); + if (propInfo != null && propInfo.PropertyType == typeof(bool)) { + string universalProp = PropertyMappingCache.GetJsPropertyName(propInfo); + var condition = new FilterCondition( - _property: "__constant", + _property: universalProp, _operation: "Equal", - _value: (bool)expr.Value!, + _value: true, _isString: false, _caseSensitive: false ); @@ -61,194 +88,71 @@ private FilterNode HandleConstantBoolean(ConstantExpression expr) Condition = condition }; } - - throw new InvalidOperationException($"Unsupported constant type: {expr.Type}"); } - private FilterNode HandleMemberBoolean(MemberExpression memberExpr) - { - if (IsParameterMember(memberExpr)) - { - var propInfo = typeof(T).GetProperty(memberExpr.Member.Name); - if (propInfo != null && propInfo.PropertyType == typeof(bool)) - { - string universalProp = PropertyMappingCache.GetJsPropertyName(propInfo); - - var condition = new FilterCondition( - _property: universalProp, - _operation: "Equal", - _value: true, - _isString: false, - _caseSensitive: false - ); - - return new FilterNode - { - NodeType = FilterNodeType.Condition, - Condition = condition - }; - } - } - - throw new InvalidOperationException($"Unsupported member expression: {memberExpr}"); - } + throw new InvalidOperationException($"Unsupported member expression: {memberExpr}"); + } - private FilterNode ParseBinaryExpression(BinaryExpression bin) + private FilterNode ParseBinaryExpression(BinaryExpression bin) + { + if (bin.NodeType == ExpressionType.AndAlso || bin.NodeType == ExpressionType.OrElse) { - if (bin.NodeType == ExpressionType.AndAlso || bin.NodeType == ExpressionType.OrElse) - { - var op = bin.NodeType == ExpressionType.AndAlso - ? FilterLogicalOperator.And - : FilterLogicalOperator.Or; + var op = bin.NodeType == ExpressionType.AndAlso + ? FilterLogicalOperator.And + : FilterLogicalOperator.Or; - return new FilterNode - { - NodeType = FilterNodeType.Logical, - Operator = op, - Children = new List + return new FilterNode + { + NodeType = FilterNodeType.Logical, + Operator = op, + Children = new List { ParseExpression(bin.Left), ParseExpression(bin.Right) } - }; - } - else - { - return BuildComparisonLeaf(bin); - } + }; } - - private FilterNode ParseMethodCallExpression(MethodCallExpression call) + else { - if (TryFlattenContains(call, out var flattenedConditions)) - { - return new FilterNode - { - NodeType = FilterNodeType.Logical, - Operator = FilterLogicalOperator.Or, - Children = flattenedConditions - .Select(c => new FilterNode - { - NodeType = FilterNodeType.Condition, - Condition = c - }).ToList() - }; - } - - string name = call.Method.Name; - string operation = name switch - { - "Equals" => "StringEquals", - _ => name - }; - - bool caseSensitive = ExtractCaseSensitivity(call); - - if (operation is "Contains" or "StartsWith" or "EndsWith" or "StringEquals") - { - var leftExpr = call.Object as MemberExpression; - var rightVal = ToConst(call.Arguments[0]); - - if (leftExpr == null || rightVal == null) - throw new InvalidOperationException($"Cannot parse method call: {call}"); - - var cond = BuildConditionFromMemberAndConstant(leftExpr, rightVal, operation, caseSensitive); - - return new FilterNode - { - NodeType = FilterNodeType.Condition, - Condition = cond - }; - } - else if (SupportedUnaryMethod(operation)) - { - var leftExpr = call.Arguments.FirstOrDefault() as MemberExpression ?? call.Object as MemberExpression; - ConstantExpression? rightVal = - call.Arguments.Count > 1 ? call.Arguments[1] as ConstantExpression : null; - - if (leftExpr == null) - throw new InvalidOperationException($"Unsupported method expression: {call}"); - - var cond = BuildConditionFromMemberAndConstant(leftExpr, rightVal, operation, caseSensitive); - - return new FilterNode - { - NodeType = FilterNodeType.Condition, - Condition = cond - }; - } - - throw new InvalidOperationException($"Unsupported method call: {call}"); + return BuildComparisonLeaf(bin); } + } - private FilterNode ParseNotExpression(UnaryExpression notExpr) + private FilterNode ParseMethodCallExpression(MethodCallExpression call) + { + if (TryFlattenContains(call, out var flattenedConditions)) { - var inner = notExpr.Operand; - - if (inner is BinaryExpression bin) + return new FilterNode { - if (bin.NodeType == ExpressionType.OrElse || bin.NodeType == ExpressionType.AndAlso) - { - // De Morgan’s Law: !(A || B) => !A && !B, !(A && B) => !A || !B - var invertedOperator = bin.NodeType == ExpressionType.OrElse - ? FilterLogicalOperator.And - : FilterLogicalOperator.Or; - - return new FilterNode + NodeType = FilterNodeType.Logical, + Operator = FilterLogicalOperator.Or, + Children = flattenedConditions + .Select(c => new FilterNode { - NodeType = FilterNodeType.Logical, - Operator = invertedOperator, - Children = new List - { - ParseNotExpression(Expression.Not(bin.Left)), - ParseNotExpression(Expression.Not(bin.Right)) - } - }; - } - else - { - return HandleNotBinary(bin); - } - } - else if (inner is MethodCallExpression call) - { - return HandleNotMethod(call); - } - - throw new InvalidOperationException($"Unsupported NOT expression: {inner}"); + NodeType = FilterNodeType.Condition, + Condition = c + }).ToList() + }; } - - private FilterNode HandleNotBinary(BinaryExpression bin) + string name = call.Method.Name; + string operation = name switch { - string invertedOp = bin.NodeType switch - { - ExpressionType.GreaterThan => "LessThanOrEqual", - ExpressionType.LessThan => "GreaterThanOrEqual", - ExpressionType.GreaterThanOrEqual => "LessThan", - ExpressionType.LessThanOrEqual => "GreaterThan", - ExpressionType.Equal => "NotEquals", - ExpressionType.NotEqual => "StringEquals", - _ => throw new InvalidOperationException($"Unsupported NOT binary: {bin}") - }; + "Equals" => "StringEquals", + _ => name + }; - return BuildComparisonLeaf(bin, forceOperation: invertedOp); - } + bool caseSensitive = ExtractCaseSensitivity(call); - private FilterNode HandleNotMethod(MethodCallExpression call) + if (operation is "Contains" or "StartsWith" or "EndsWith" or "StringEquals") { - if (!SupportedMethodNameForNegation(call.Method.Name)) - throw new InvalidOperationException($"Unsupported NOT call: {call}"); - - string inverted = Invert(call.Method.Name); var leftExpr = call.Object as MemberExpression; var rightVal = ToConst(call.Arguments[0]); - bool caseSensitive = ExtractCaseSensitivity(call); if (leftExpr == null || rightVal == null) - throw new InvalidOperationException($"Cannot parse NOT method: {call}"); + throw new InvalidOperationException($"Cannot parse method call: {call}"); - var cond = BuildConditionFromMemberAndConstant(leftExpr, rightVal, inverted, caseSensitive); + var cond = BuildConditionFromMemberAndConstant(leftExpr, rightVal, operation, caseSensitive); return new FilterNode { @@ -256,393 +160,489 @@ private FilterNode HandleNotMethod(MethodCallExpression call) Condition = cond }; } - - private FilterNode BuildComparisonLeaf(BinaryExpression bin, string? forceOperation = null) + else if (SupportedUnaryMethod(operation)) { - string operation = forceOperation ?? bin.NodeType.ToString(); + var leftExpr = call.Arguments.FirstOrDefault() as MemberExpression ?? call.Object as MemberExpression; + ConstantExpression? rightVal = + call.Arguments.Count > 1 ? call.Arguments[1] as ConstantExpression : null; - if (TryRecognizeSpecialOperation(bin, operation, out var specialNode)) + if (leftExpr == null) + throw new InvalidOperationException($"Unsupported method expression: {call}"); + + var cond = BuildConditionFromMemberAndConstant(leftExpr, rightVal, operation, caseSensitive); + + return new FilterNode { - return specialNode; - } + NodeType = FilterNodeType.Condition, + Condition = cond + }; + } + + throw new InvalidOperationException($"Unsupported method call: {call}"); + } + + private FilterNode ParseNotExpression(UnaryExpression notExpr) + { + var inner = notExpr.Operand; - if (IsParameterMember(bin.Left) && !IsParameterMember(bin.Right)) + if (inner is BinaryExpression bin) + { + if (bin.NodeType == ExpressionType.OrElse || bin.NodeType == ExpressionType.AndAlso) { - var left = bin.Left as MemberExpression; - var right = ToConst(bin.Right); - var cond = BuildConditionFromMemberAndConstant(left, right, operation); + // De Morgan’s Law: !(A || B) => !A && !B, !(A && B) => !A || !B + var invertedOperator = bin.NodeType == ExpressionType.OrElse + ? FilterLogicalOperator.And + : FilterLogicalOperator.Or; return new FilterNode { - NodeType = FilterNodeType.Condition, - Condition = cond + NodeType = FilterNodeType.Logical, + Operator = invertedOperator, + Children = new List + { + ParseNotExpression(Expression.Not(bin.Left)), + ParseNotExpression(Expression.Not(bin.Right)) + } }; } - else if (!IsParameterMember(bin.Left) && IsParameterMember(bin.Right)) + else { - operation = InvertBinary(operation); - var left = bin.Right as MemberExpression; - var right = ToConst(bin.Left); - var cond = BuildConditionFromMemberAndConstant(left, right, operation); - - return new FilterNode - { - NodeType = FilterNodeType.Condition, - Condition = cond - }; + return HandleNotBinary(bin); } - - throw new InvalidOperationException($"Unsupported binary expression: {bin}"); + } + else if (inner is MethodCallExpression call) + { + return HandleNotMethod(call); } - private bool TryRecognizeSpecialOperation(BinaryExpression bin, string operation, out FilterNode node) + throw new InvalidOperationException($"Unsupported NOT expression: {inner}"); + } + + + private FilterNode HandleNotBinary(BinaryExpression bin) + { + string invertedOp = bin.NodeType switch { - node = null!; + ExpressionType.GreaterThan => "LessThanOrEqual", + ExpressionType.LessThan => "GreaterThanOrEqual", + ExpressionType.GreaterThanOrEqual => "LessThan", + ExpressionType.LessThanOrEqual => "GreaterThan", + ExpressionType.Equal => "NotEquals", + ExpressionType.NotEqual => "StringEquals", + _ => throw new InvalidOperationException($"Unsupported NOT binary: {bin}") + }; + + return BuildComparisonLeaf(bin, forceOperation: invertedOp); + } + private FilterNode HandleNotMethod(MethodCallExpression call) + { + if (!SupportedMethodNameForNegation(call.Method.Name)) + throw new InvalidOperationException($"Unsupported NOT call: {call}"); - if (TryRecognizeLengthProperty(bin, operation, out node)) - return true; + string inverted = Invert(call.Method.Name); + var leftExpr = call.Object as MemberExpression; + var rightVal = ToConst(call.Arguments[0]); + bool caseSensitive = ExtractCaseSensitivity(call); - // Recognize things like: x => x.DateOfBirth.Value.Year >= 2020 - if (TryRecognizeDateProperty(bin, operation, out node)) - return true; + if (leftExpr == null || rightVal == null) + throw new InvalidOperationException($"Cannot parse NOT method: {call}"); - // Future recognizers: - // if (TryRecognizeEnumFlag(bin, operation, out node)) return true; + var cond = BuildConditionFromMemberAndConstant(leftExpr, rightVal, inverted, caseSensitive); - return false; - } + return new FilterNode + { + NodeType = FilterNodeType.Condition, + Condition = cond + }; + } + private FilterNode BuildComparisonLeaf(BinaryExpression bin, string? forceOperation = null) + { + string operation = forceOperation ?? bin.NodeType.ToString(); - private bool TryRecognizeLengthProperty(BinaryExpression bin, string operation, out FilterNode node) + if (TryRecognizeSpecialOperation(bin, operation, out var specialNode)) { - node = null!; - var memberPath = GetMemberAccessPath(bin.Left); + return specialNode; + } - if (memberPath == null || memberPath.Count == 0) - return false; + if (IsParameterMember(bin.Left) && !IsParameterMember(bin.Right)) + { + var left = bin.Left as MemberExpression; + var right = ToConst(bin.Right); + var cond = BuildConditionFromMemberAndConstant(left, right, operation); - // Error if .Value is the last thing appended (illegal usage) - if (memberPath[^1] == "Value") + return new FilterNode { - throw new InvalidOperationException("You cannot end an expression with '.Value'. Only specific extensions like '.Length' are allowed."); - } + NodeType = FilterNodeType.Condition, + Condition = cond + }; + } + else if (!IsParameterMember(bin.Left) && IsParameterMember(bin.Right)) + { + operation = InvertBinary(operation); + var left = bin.Right as MemberExpression; + var right = ToConst(bin.Left); + var cond = BuildConditionFromMemberAndConstant(left, right, operation); - // Only allow if the final segment is "Length" - if (memberPath[^1] != "Length") - return false; + return new FilterNode + { + NodeType = FilterNodeType.Condition, + Condition = cond + }; + } - // Must have at least 2 parts: root + Length - if (memberPath.Count < 2) - return false; + throw new InvalidOperationException($"Unsupported binary expression: {bin}"); + } - SearchPropEntry spe = PropertyMappingCache.GetTypeOfTProperties(typeof(T)); + private bool TryRecognizeSpecialOperation(BinaryExpression bin, string operation, out FilterNode node) + { + node = null!; - //string rootPropName = memberPath[memberPath.Count - 2]; // i.e., "Name" - string rootPropName = ExtractRootProperty(memberPath); - MagicPropertyEntry mpe = spe.GetPropertyByCsharpName(rootPropName); - string jsProp = mpe.JsPropertyName; + if (TryRecognizeLengthProperty(bin, operation, out node)) + return true; - - var value = ToConst(bin.Right).Value; - if (value == null || value is not int) - return false; + // Recognize things like: x => x.DateOfBirth.Value.Year >= 2020 + if (TryRecognizeDateProperty(bin, operation, out node)) + return true; - string op = operation switch - { - "Equal" => "LengthEqual", - "NotEqual" => "NotLengthEqual", - "GreaterThan" => "LengthGreaterThan", - "GreaterThanOrEqual" => "LengthGreaterThanOrEqual", - "LessThan" => "LengthLessThan", - "LessThanOrEqual" => "LengthLessThanOrEqual", - _ => throw new InvalidOperationException($"Unsupported operator '{operation}' for .Length") - }; + // Future recognizers: + // if (TryRecognizeEnumFlag(bin, operation, out node)) return true; - // TODO: Replace this with your isString detection logic - bool isString = mpe.Property.PropertyType == typeof(string); + return false; + } - node = new FilterNode - { - NodeType = FilterNodeType.Condition, - Condition = new FilterCondition( - jsProp, - op, - value, - isString, - false - ) - }; - return true; - } + private bool TryRecognizeLengthProperty(BinaryExpression bin, string operation, out FilterNode node) + { + node = null!; + var memberPath = GetMemberAccessPath(bin.Left); - private static string ExtractRootProperty(List path) - { - if (path.Count < 2) - throw new InvalidOperationException("Invalid member access path."); + if (memberPath == null || memberPath.Count == 0) + return false; - return path[^2] == "Value" && path.Count >= 3 - ? path[^3] - : path[^2]; + // Error if .Value is the last thing appended (illegal usage) + if (memberPath[^1] == "Value") + { + throw new InvalidOperationException("You cannot end an expression with '.Value'. Only specific extensions like '.Length' are allowed."); } + // Only allow if the final segment is "Length" + if (memberPath[^1] != "Length") + return false; - private bool TryRecognizeDateProperty(BinaryExpression bin, string operation, out FilterNode node) - { - node = null!; + // Must have at least 2 parts: root + Length + if (memberPath.Count < 2) + return false; - Expression leftExpr = UnwrapConvert(bin.Left); - var memberPath = GetMemberAccessPath(leftExpr); - if (memberPath == null || memberPath.Count < 2) - return false; + SearchPropEntry spe = PropertyMappingCache.GetTypeOfTProperties(typeof(T)); + //string rootPropName = memberPath[memberPath.Count - 2]; // i.e., "Name" + string rootPropName = ExtractRootProperty(memberPath); - var finalSegment = memberPath[^1]; - var rootSegment = memberPath[^2]; + MagicPropertyEntry mpe = spe.GetPropertyByCsharpName(rootPropName); + string jsProp = mpe.JsPropertyName; - SearchPropEntry spe = PropertyMappingCache.GetTypeOfTProperties(typeof(T)); + + var value = ToConst(bin.Right).Value; + if (value == null || value is not int) + return false; - string rootPropName = ExtractRootProperty(memberPath); - MagicPropertyEntry mpe = spe.GetPropertyByCsharpName(rootPropName); + string op = operation switch + { + "Equal" => "LengthEqual", + "NotEqual" => "NotLengthEqual", + "GreaterThan" => "LengthGreaterThan", + "GreaterThanOrEqual" => "LengthGreaterThanOrEqual", + "LessThan" => "LengthLessThan", + "LessThanOrEqual" => "LengthLessThanOrEqual", + _ => throw new InvalidOperationException($"Unsupported operator '{operation}' for .Length") + }; + + // TODO: Replace this with your isString detection logic + bool isString = mpe.Property.PropertyType == typeof(string); + + node = new FilterNode + { + NodeType = FilterNodeType.Condition, + Condition = new FilterCondition( + jsProp, + op, + value, + isString, + false + ) + }; - string jsProp = mpe.JsPropertyName; + return true; + } - if (!IsDateType(mpe.Property.PropertyType)) - return false; + private static string ExtractRootProperty(List path) + { + if (path.Count < 2) + throw new InvalidOperationException("Invalid member access path."); - var rightUnwrapped = UnwrapConvert(bin.Right); - object? rawConst = ToConst(rightUnwrapped).Value; + return path[^2] == "Value" && path.Count >= 3 + ? path[^3] + : path[^2]; + } - if (rawConst == null) - return false; - switch (finalSegment) - { - case "Date": - node = BuildDateEqualityRange(jsProp, rawConst, operation); - return true; + private bool TryRecognizeDateProperty(BinaryExpression bin, string operation, out FilterNode node) + { + node = null!; - case "Year": - node = BuildDateYearNode(jsProp, rawConst, operation); - return true; + Expression leftExpr = UnwrapConvert(bin.Left); + var memberPath = GetMemberAccessPath(leftExpr); + if (memberPath == null || memberPath.Count < 2) + return false; - case "Month": - node = BuildComponentCondition(jsProp, rawConst, operation, "Month"); - return true; - case "Day": - node = BuildComponentCondition(jsProp, rawConst, operation, "Day"); - return true; + var finalSegment = memberPath[^1]; + var rootSegment = memberPath[^2]; - case "DayOfYear": - node = BuildComponentCondition(jsProp, rawConst, operation, "DayOfYear"); - return true; + SearchPropEntry spe = PropertyMappingCache.GetTypeOfTProperties(typeof(T)); - case "DayOfWeek": - node = BuildDayOfWeekNode(jsProp, bin.Right, operation); - return true; + string rootPropName = ExtractRootProperty(memberPath); + MagicPropertyEntry mpe = spe.GetPropertyByCsharpName(rootPropName); - default: - return false; - } - } + string jsProp = mpe.JsPropertyName; - private FilterNode BuildDateYearNode(string jsProp, object value, string operation) + if (!IsDateType(mpe.Property.PropertyType)) + return false; + + var rightUnwrapped = UnwrapConvert(bin.Right); + object? rawConst = ToConst(rightUnwrapped).Value; + + if (rawConst == null) + return false; + + switch (finalSegment) { - if (value is not int year) - throw new InvalidOperationException("Expected integer constant for .Year comparison"); + case "Date": + node = BuildDateEqualityRange(jsProp, rawConst, operation); + return true; - string finalOp = operation switch - { - "Equal" => "YearEqual", - "NotEqual" => "NotYearEqual", - "GreaterThan" => "YearGreaterThan", - "GreaterThanOrEqual" => "YearGreaterThanOrEqual", - "LessThan" => "YearLessThan", - "LessThanOrEqual" => "YearLessThanOrEqual", - _ => throw new InvalidOperationException($"Unsupported operator '{operation}' for .Year") - }; + case "Year": + node = BuildDateYearNode(jsProp, rawConst, operation); + return true; - return new FilterNode - { - NodeType = FilterNodeType.Condition, - Condition = new FilterCondition( - jsProp, - finalOp, - year, - false, - false - ) - }; - } + case "Month": + node = BuildComponentCondition(jsProp, rawConst, operation, "Month"); + return true; + case "Day": + node = BuildComponentCondition(jsProp, rawConst, operation, "Day"); + return true; + case "DayOfYear": + node = BuildComponentCondition(jsProp, rawConst, operation, "DayOfYear"); + return true; - private static bool IsDateType(Type type) - { - var actual = Nullable.GetUnderlyingType(type) ?? type; - return actual == typeof(DateTime) || actual == typeof(DateOnly); + case "DayOfWeek": + node = BuildDayOfWeekNode(jsProp, bin.Right, operation); + return true; + + default: + return false; } + } + private FilterNode BuildDateYearNode(string jsProp, object value, string operation) + { + if (value is not int year) + throw new InvalidOperationException("Expected integer constant for .Year comparison"); - private FilterNode BuildDateEqualityRange(string jsProp, object rawConst, string op) + string finalOp = operation switch + { + "Equal" => "YearEqual", + "NotEqual" => "NotYearEqual", + "GreaterThan" => "YearGreaterThan", + "GreaterThanOrEqual" => "YearGreaterThanOrEqual", + "LessThan" => "YearLessThan", + "LessThanOrEqual" => "YearLessThanOrEqual", + _ => throw new InvalidOperationException($"Unsupported operator '{operation}' for .Year") + }; + + return new FilterNode { - if (rawConst is not DateTime dt) - throw new InvalidOperationException("Expected DateTime constant for .Date comparison"); + NodeType = FilterNodeType.Condition, + Condition = new FilterCondition( + jsProp, + finalOp, + year, + false, + false + ) + }; + } - DateTime startOfDay = dt.Date; - DateTime nextDay = startOfDay.AddDays(1); - if (op is "Equal") - { - return new FilterNode - { - NodeType = FilterNodeType.Logical, - Operator = FilterLogicalOperator.And, - Children = new List - { - new FilterNode - { - NodeType = FilterNodeType.Condition, - Condition = new FilterCondition(jsProp, "GreaterThanOrEqual", startOfDay, false, false) - }, - new FilterNode - { - NodeType = FilterNodeType.Condition, - Condition = new FilterCondition(jsProp, "LessThan", nextDay, false, false) - } - } - }; - } - // For <, <=, >, >=, NotEqual, etc. - return new FilterNode - { - NodeType = FilterNodeType.Condition, - Condition = new FilterCondition(jsProp, op, startOfDay, false, false) - }; - } + private static bool IsDateType(Type type) + { + var actual = Nullable.GetUnderlyingType(type) ?? type; + return actual == typeof(DateTime) || actual == typeof(DateOnly); + } - private FilterNode BuildComponentCondition(string jsProp, object value, string operation, string component) - { - string finalOp = operation switch - { - "Equal" => $"{component}Equal", - "NotEqual" => $"Not{component}Equal", - "GreaterThan" => $"{component}GreaterThan", - "GreaterThanOrEqual" => $"{component}GreaterThanOrEqual", - "LessThan" => $"{component}LessThan", - "LessThanOrEqual" => $"{component}LessThanOrEqual", - _ => throw new InvalidOperationException($"Unsupported operator '{operation}' for .{component}") - }; + private FilterNode BuildDateEqualityRange(string jsProp, object rawConst, string op) + { + if (rawConst is not DateTime dt) + throw new InvalidOperationException("Expected DateTime constant for .Date comparison"); + DateTime startOfDay = dt.Date; + DateTime nextDay = startOfDay.AddDays(1); + + if (op is "Equal") + { return new FilterNode { - NodeType = FilterNodeType.Condition, - Condition = new FilterCondition(jsProp, finalOp, value, false, false) + NodeType = FilterNodeType.Logical, + Operator = FilterLogicalOperator.And, + Children = new List + { + new FilterNode + { + NodeType = FilterNodeType.Condition, + Condition = new FilterCondition(jsProp, "GreaterThanOrEqual", startOfDay, false, false) + }, + new FilterNode + { + NodeType = FilterNodeType.Condition, + Condition = new FilterCondition(jsProp, "LessThan", nextDay, false, false) + } + } }; } + // For <, <=, >, >=, NotEqual, etc. + return new FilterNode + { + NodeType = FilterNodeType.Condition, + Condition = new FilterCondition(jsProp, op, startOfDay, false, false) + }; + } - private FilterNode BuildDayOfWeekNode(string jsProp, Expression expr, string operation) + + private FilterNode BuildComponentCondition(string jsProp, object value, string operation, string component) + { + string finalOp = operation switch + { + "Equal" => $"{component}Equal", + "NotEqual" => $"Not{component}Equal", + "GreaterThan" => $"{component}GreaterThan", + "GreaterThanOrEqual" => $"{component}GreaterThanOrEqual", + "LessThan" => $"{component}LessThan", + "LessThanOrEqual" => $"{component}LessThanOrEqual", + _ => throw new InvalidOperationException($"Unsupported operator '{operation}' for .{component}") + }; + + return new FilterNode { - // Unwrap the Convert to get the raw int expression (already a DayOfWeek enum converted to int) - Expression cleanExpr = UnwrapConvert(expr); + NodeType = FilterNodeType.Condition, + Condition = new FilterCondition(jsProp, finalOp, value, false, false) + }; + } - object? result = Expression.Lambda(cleanExpr).Compile().DynamicInvoke(); - if (result is not int jsDayOfWeek) - throw new InvalidOperationException("Expected integer value for .DayOfWeek comparison"); + private FilterNode BuildDayOfWeekNode(string jsProp, Expression expr, string operation) + { + // Unwrap the Convert to get the raw int expression (already a DayOfWeek enum converted to int) + Expression cleanExpr = UnwrapConvert(expr); - return BuildComponentCondition(jsProp, jsDayOfWeek, operation, "DayOfWeek"); - } + object? result = Expression.Lambda(cleanExpr).Compile().DynamicInvoke(); + + if (result is not int jsDayOfWeek) + throw new InvalidOperationException("Expected integer value for .DayOfWeek comparison"); + + return BuildComponentCondition(jsProp, jsDayOfWeek, operation, "DayOfWeek"); + } - private static Expression UnwrapConvert(Expression expr) + private static Expression UnwrapConvert(Expression expr) + { + while (expr.NodeType == ExpressionType.Convert || expr.NodeType == ExpressionType.ConvertChecked) { - while (expr.NodeType == ExpressionType.Convert || expr.NodeType == ExpressionType.ConvertChecked) - { - expr = ((UnaryExpression)expr).Operand; - } - return expr; + expr = ((UnaryExpression)expr).Operand; } + return expr; + } - private List? GetMemberAccessPath(Expression expr) + private List? GetMemberAccessPath(Expression expr) + { + var path = new List(); + while (expr is MemberExpression memberExpr) { - var path = new List(); - while (expr is MemberExpression memberExpr) - { - path.Insert(0, memberExpr.Member.Name); - expr = memberExpr.Expression!; - } - - return expr is ParameterExpression ? path : null; + path.Insert(0, memberExpr.Member.Name); + expr = memberExpr.Expression!; } + return expr is ParameterExpression ? path : null; + } - // ------------------------------ - // "Contains" flattening logic: - // ------------------------------ - private bool TryFlattenContains(MethodCallExpression call, out IEnumerable flattened) - { - flattened = Enumerable.Empty(); - if (call.Method.Name != "Contains") - return false; + // ------------------------------ + // "Contains" flattening logic: + // ------------------------------ + private bool TryFlattenContains(MethodCallExpression call, out IEnumerable flattened) + { + flattened = Enumerable.Empty(); - if (call.Method.DeclaringType == typeof(string)) - return false; + if (call.Method.Name != "Contains") + return false; - // Case 1: Static-style => myArray.Contains(x.SomeProp) - if (call.Object == null && call.Arguments.Count == 2) - { - var maybeCollection = call.Arguments[0]; - var maybeProp = call.Arguments[1]; + if (call.Method.DeclaringType == typeof(string)) + return false; - if ((maybeCollection is MemberExpression || maybeCollection is ConstantExpression) && - maybeProp is MemberExpression propExpr && - IsParameterMember(propExpr)) + // Case 1: Static-style => myArray.Contains(x.SomeProp) + if (call.Object == null && call.Arguments.Count == 2) + { + var maybeCollection = call.Arguments[0]; + var maybeProp = call.Arguments[1]; + + if ((maybeCollection is MemberExpression || maybeCollection is ConstantExpression) && + maybeProp is MemberExpression propExpr && + IsParameterMember(propExpr)) + { + var collection = Expression.Lambda(maybeCollection).Compile().DynamicInvoke(); + if (collection is IEnumerable enumerable) { - var collection = Expression.Lambda(maybeCollection).Compile().DynamicInvoke(); - if (collection is IEnumerable enumerable) - { - var propInfo = typeof(T).GetProperty(propExpr.Member.Name); - if (propInfo == null) return false; - - // Suppose you have a way to map property name to something universal: - string universalProp = PropertyMappingCache.GetJsPropertyName(propInfo); - - flattened = enumerable - .Cast() - .Select(val => new FilterCondition( - universalProp, - "Equal", - val, - val is string, - false - )); - return true; - } + var propInfo = typeof(T).GetProperty(propExpr.Member.Name); + if (propInfo == null) return false; + + // Suppose you have a way to map property name to something universal: + string universalProp = PropertyMappingCache.GetJsPropertyName(propInfo); + + flattened = enumerable + .Cast() + .Select(val => new FilterCondition( + universalProp, + "Equal", + val, + val is string, + false + )); + return true; } } + } - // Case 2: Instance-style => x.CollectionProperty.Contains(10) - if (call.Object is MemberExpression collectionMember && - call.Arguments.Count == 1 && - call.Arguments[0] is ConstantExpression constant) - { - var propInfo = typeof(T).GetProperty(collectionMember.Member.Name); - if (propInfo == null) return false; + // Case 2: Instance-style => x.CollectionProperty.Contains(10) + if (call.Object is MemberExpression collectionMember && + call.Arguments.Count == 1 && + call.Arguments[0] is ConstantExpression constant) + { + var propInfo = typeof(T).GetProperty(collectionMember.Member.Name); + if (propInfo == null) return false; - string universalProp = PropertyMappingCache.GetJsPropertyName(propInfo); + string universalProp = PropertyMappingCache.GetJsPropertyName(propInfo); - flattened = new[] - { + flattened = new[] + { new FilterCondition( universalProp, "ArrayContains", @@ -651,134 +651,132 @@ maybeProp is MemberExpression propExpr && false ) }; - return true; - } - - return false; + return true; } - // ------------------------------ - // Internal Helpers - // ------------------------------ - - private FilterCondition BuildConditionFromMemberAndConstant( - MemberExpression? memberExpr, - ConstantExpression? constExpr, - string operation, - bool caseSensitive = false) - { - if (memberExpr == null || constExpr == null) - { - throw new InvalidOperationException("Cannot build filter condition from null expressions."); - } - - var propInfo = typeof(T).GetProperty(memberExpr.Member.Name); - if (propInfo == null) - { - throw new InvalidOperationException($"Property {memberExpr.Member.Name} not found on type {typeof(T).Name}."); - } - - // Possibly convert to a JSON node or keep as raw object. - // If you absolutely need a JSON representation, do: - // object? val = constExpr.Value != null ? JsonValue.Create(constExpr.Value) : null; - // Otherwise, you can just store the raw object in FilterCondition.value: - object? val = constExpr.Value; + return false; + } - // e.g. "name", "age" - string universalProp = PropertyMappingCache.GetJsPropertyName(propInfo); + // ------------------------------ + // Internal Helpers + // ------------------------------ - // isString is relevant only if the constant is indeed a string - bool isString = val is string; + private FilterCondition BuildConditionFromMemberAndConstant( + MemberExpression? memberExpr, + ConstantExpression? constExpr, + string operation, + bool caseSensitive = false) + { + if (memberExpr == null || constExpr == null) + { + throw new InvalidOperationException("Cannot build filter condition from null expressions."); + } - return new FilterCondition( - universalProp, - operation, - val, - isString, - caseSensitive - ); + var propInfo = typeof(T).GetProperty(memberExpr.Member.Name); + if (propInfo == null) + { + throw new InvalidOperationException($"Property {memberExpr.Member.Name} not found on type {typeof(T).Name}."); } - private static bool SupportedMethodNameForNegation(string name) - => name is "Contains" or "StartsWith" or "EndsWith" or "Equals"; + // Possibly convert to a JSON node or keep as raw object. + // If you absolutely need a JSON representation, do: + // object? val = constExpr.Value != null ? JsonValue.Create(constExpr.Value) : null; + // Otherwise, you can just store the raw object in FilterCondition.value: + object? val = constExpr.Value; + + // e.g. "name", "age" + string universalProp = PropertyMappingCache.GetJsPropertyName(propInfo); + + // isString is relevant only if the constant is indeed a string + bool isString = val is string; + + return new FilterCondition( + universalProp, + operation, + val, + isString, + caseSensitive + ); + } - private static string Invert(string methodName) - { - // e.g. Contains -> NotContains, StartsWith -> NotStartsWith, etc. - return methodName switch - { - "Contains" => "NotContains", - "StartsWith" => "NotStartsWith", - "EndsWith" => "NotEndsWith", - "Equals" => "NotEquals", - _ => throw new InvalidOperationException($"Cannot invert unsupported method: {methodName}") - }; - } + private static bool SupportedMethodNameForNegation(string name) + => name is "Contains" or "StartsWith" or "EndsWith" or "Equals"; - private static string InvertBinary(string op) + private static string Invert(string methodName) + { + // e.g. Contains -> NotContains, StartsWith -> NotStartsWith, etc. + return methodName switch { - return op switch - { - "GreaterThan" => "LessThan", - "LessThan" => "GreaterThan", - "GreaterThanOrEqual" => "LessThanOrEqual", - "LessThanOrEqual" => "GreaterThanOrEqual", - _ => op - }; - } + "Contains" => "NotContains", + "StartsWith" => "NotStartsWith", + "EndsWith" => "NotEndsWith", + "Equals" => "NotEquals", + _ => throw new InvalidOperationException($"Cannot invert unsupported method: {methodName}") + }; + } - private static bool SupportedUnaryMethod(string name) + private static string InvertBinary(string op) + { + return op switch { - // Your custom logic for "GetDay", "IsNull", etc. - return name.StartsWith("TypeOf") - || name.StartsWith("NotTypeOf") - || name.StartsWith("Length") - || name.StartsWith("NotLength") - || name is "IsNull" or "IsNotNull" or "NotContains"; - } + "GreaterThan" => "LessThan", + "LessThan" => "GreaterThan", + "GreaterThanOrEqual" => "LessThanOrEqual", + "LessThanOrEqual" => "GreaterThanOrEqual", + _ => op + }; + } - private static bool ExtractCaseSensitivity(MethodCallExpression call) - { - // Mimic your logic that checks if a StringComparison was passed in - if (call.Arguments.Count > 1 && call.Arguments[1] is ConstantExpression cmp && cmp.Value is StringComparison cmpVal) - { - // If it’s ordinal or current-culture, consider it case-sensitive - return cmpVal == StringComparison.Ordinal || cmpVal == StringComparison.CurrentCulture; - } - // Otherwise default to "true" or "false" depending on your preference: - return true; - } + private static bool SupportedUnaryMethod(string name) + { + // Your custom logic for "GetDay", "IsNull", etc. + return name.StartsWith("TypeOf") + || name.StartsWith("NotTypeOf") + || name.StartsWith("Length") + || name.StartsWith("NotLength") + || name is "IsNull" or "IsNotNull" or "NotContains"; + } - private static bool IsParameterMember(Expression expr) + private static bool ExtractCaseSensitivity(MethodCallExpression call) + { + // Mimic your logic that checks if a StringComparison was passed in + if (call.Arguments.Count > 1 && call.Arguments[1] is ConstantExpression cmp && cmp.Value is StringComparison cmpVal) { - return expr is MemberExpression member && - member.Expression is ParameterExpression; + // If it’s ordinal or current-culture, consider it case-sensitive + return cmpVal == StringComparison.Ordinal || cmpVal == StringComparison.CurrentCulture; } + // Otherwise default to "true" or "false" depending on your preference: + return true; + } - private static ConstantExpression ToConst(Expression expr) - { - expr = StripConvert(expr); // <-- handle Convert wrappers + private static bool IsParameterMember(Expression expr) + { + return expr is MemberExpression member && + member.Expression is ParameterExpression; + } - return expr switch - { - ConstantExpression c => c, + private static ConstantExpression ToConst(Expression expr) + { + expr = StripConvert(expr); // <-- handle Convert wrappers - // e.g., new DateTime(...) or anything not marked constant but compile-safe - NewExpression or MemberExpression or MethodCallExpression => - Expression.Constant(Expression.Lambda(expr).Compile().DynamicInvoke()), + return expr switch + { + ConstantExpression c => c, - _ => throw new InvalidOperationException($"Unsupported or non-constant expression: {expr}") - }; - } + // e.g., new DateTime(...) or anything not marked constant but compile-safe + NewExpression or MemberExpression or MethodCallExpression => + Expression.Constant(Expression.Lambda(expr).Compile().DynamicInvoke()), - private static Expression StripConvert(Expression expr) + _ => throw new InvalidOperationException($"Unsupported or non-constant expression: {expr}") + }; + } + + private static Expression StripConvert(Expression expr) + { + while (expr is UnaryExpression unary && expr.NodeType == ExpressionType.Convert) { - while (expr is UnaryExpression unary && expr.NodeType == ExpressionType.Convert) - { - expr = unary.Operand; - } - return expr; + expr = unary.Operand; } + return expr; } -} - +} \ No newline at end of file diff --git a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicCursor.cs b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicCursor.cs index d590a54..fec0ad5 100644 --- a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicCursor.cs +++ b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicCursor.cs @@ -7,36 +7,35 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +public interface IMagicCursor : IMagicExecute where T : class { - public interface IMagicCursor : IMagicExecute where T : class - { - /// - /// Pure 100% cursor query. No limitations on any appended additions. - /// - /// - /// - IMagicCursor Cursor(Expression> predicate); + /// + /// Pure 100% cursor query. No limitations on any appended additions. + /// + /// + /// + IMagicCursor Cursor(Expression> predicate); - IMagicCursorPaginationTake Take(int amount); - IMagicCursorPaginationTake TakeLast(int amount); - IMagicCursorSkip Skip(int amount); + IMagicCursorPaginationTake Take(int amount); + IMagicCursorPaginationTake TakeLast(int amount); + IMagicCursorSkip Skip(int amount); - Task FirstOrDefaultAsync(); - Task LastOrDefaultAsync(); + Task FirstOrDefaultAsync(); + Task LastOrDefaultAsync(); - /// - /// This always orders first by the primary key, then by whatever is appended afterwards - /// - /// - /// - IMagicCursorStage OrderBy(Expression> predicate); + /// + /// This always orders first by the primary key, then by whatever is appended afterwards + /// + /// + /// + IMagicCursorStage OrderBy(Expression> predicate); - /// - /// This always orders by descending by the primary key first, then by whatever is appended afterwards - /// - /// - /// - IMagicCursorStage OrderByDescending(Expression> predicate); - } -} + /// + /// This always orders by descending by the primary key first, then by whatever is appended afterwards + /// + /// + /// + IMagicCursorStage OrderByDescending(Expression> predicate); +} \ No newline at end of file diff --git a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicCursorSkip.cs b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicCursorSkip.cs index 7c24e79..2e0237a 100644 --- a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicCursorSkip.cs +++ b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicCursorSkip.cs @@ -7,13 +7,12 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +public interface IMagicCursorSkip : IMagicExecute where T : class { - public interface IMagicCursorSkip : IMagicExecute where T : class - { - //IMagicCursorSkip Skip(int amount); - Task FirstOrDefaultAsync(); - Task LastOrDefaultAsync(); + //IMagicCursorSkip Skip(int amount); + Task FirstOrDefaultAsync(); + Task LastOrDefaultAsync(); - } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicCursorStage.cs b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicCursorStage.cs index 3d9bdc7..bda28ea 100644 --- a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicCursorStage.cs +++ b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicCursorStage.cs @@ -7,26 +7,25 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +public interface IMagicCursorFinal : IMagicExecute where T : class +{ + Task FirstOrDefaultAsync(); + Task LastOrDefaultAsync(); +} +public interface IMagicCursorPaginationTake : IMagicExecute where T : class { - public interface IMagicCursorFinal : IMagicExecute where T : class - { - Task FirstOrDefaultAsync(); - Task LastOrDefaultAsync(); - } - public interface IMagicCursorPaginationTake : IMagicExecute where T : class - { - IMagicCursorSkip Skip(int amount); + IMagicCursorSkip Skip(int amount); - } - public interface IMagicCursorStage : IMagicExecute where T : class - { +} +public interface IMagicCursorStage : IMagicExecute where T : class +{ - IMagicCursorPaginationTake Take(int amount); - IMagicCursorPaginationTake TakeLast(int amount); - IMagicCursorSkip Skip(int amount); + IMagicCursorPaginationTake Take(int amount); + IMagicCursorPaginationTake TakeLast(int amount); + IMagicCursorSkip Skip(int amount); - Task FirstOrDefaultAsync(); - Task LastOrDefaultAsync(); - } -} + Task FirstOrDefaultAsync(); + Task LastOrDefaultAsync(); +} \ No newline at end of file diff --git a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicDatabase.cs b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicDatabase.cs index 608ba69..4b6fc6f 100644 --- a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicDatabase.cs +++ b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicDatabase.cs @@ -4,23 +4,20 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.LinqTranslation.Interfaces -{ - //public interface IMagicDatabaseGlobal - //{ - // Task DeleteAll(); - // Task ClearAll(); - //} - - public interface IMagicDatabaseScoped - { - Task DeleteAsync(); - Task CloseAsync(); +namespace Magic.IndexedDb.LinqTranslation.Interfaces; +//public interface IMagicDatabaseGlobal +//{ +// Task DeleteAll(); +// Task ClearAll(); +//} - Task IsOpenAsync(); - Task OpenAsync(); - Task DoesExistAsync(); - //Task Clear(); - } +public interface IMagicDatabaseScoped +{ + Task DeleteAsync(); + Task CloseAsync(); -} + Task IsOpenAsync(); + Task OpenAsync(); + Task DoesExistAsync(); + //Task Clear(); +} \ No newline at end of file diff --git a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicExecute.cs b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicExecute.cs index 675a105..2801662 100644 --- a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicExecute.cs +++ b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicExecute.cs @@ -6,27 +6,26 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +public interface IMagicExecute where T : class { - public interface IMagicExecute where T : class - { - /// - /// The order you apply does get applied correctly in the query, - /// but the returned results will not be in the same order. - /// If order matters, you must apply the order again on return. - /// This is a fundemental limitation of IndexDB. - /// - /// - /// - IAsyncEnumerable AsAsyncEnumerable(CancellationToken cancellationToken = default); + /// + /// The order you apply does get applied correctly in the query, + /// but the returned results will not be in the same order. + /// If order matters, you must apply the order again on return. + /// This is a fundemental limitation of IndexDB. + /// + /// + /// + IAsyncEnumerable AsAsyncEnumerable(CancellationToken cancellationToken = default); - /// - /// The order you apply does get applied correctly in the query, - /// but the returned results will not be in the same order. - /// If order matters, you must apply the order again on return. - /// This is a fundemental limitation of IndexDB. - /// - /// - Task> ToListAsync(); - } -} + /// + /// The order you apply does get applied correctly in the query, + /// but the returned results will not be in the same order. + /// If order matters, you must apply the order again on return. + /// This is a fundemental limitation of IndexDB. + /// + /// + Task> ToListAsync(); +} \ No newline at end of file diff --git a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQuery.cs b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQuery.cs index fcf0c32..c520ab1 100644 --- a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQuery.cs +++ b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQuery.cs @@ -7,61 +7,60 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +public interface IMagicQuery : IMagicExecute where T : class { - public interface IMagicQuery : IMagicExecute where T : class - { - string DatabaseName { get; } - public string SchemaName { get; } - /// - /// The order you apply does get applied correctly in the query, - /// but the returned results will not be in the same order. - /// If order matters, you must apply the order again on return. - /// This is a fundemental limitation of IndexDB. - /// - /// - /// - IMagicQueryStaging Where(Expression> predicate); + string DatabaseName { get; } + public string SchemaName { get; } + /// + /// The order you apply does get applied correctly in the query, + /// but the returned results will not be in the same order. + /// If order matters, you must apply the order again on return. + /// This is a fundemental limitation of IndexDB. + /// + /// + /// + IMagicQueryStaging Where(Expression> predicate); - IMagicCursor Cursor(Expression> predicate); + IMagicCursor Cursor(Expression> predicate); - Task FirstOrDefaultAsync(); - Task LastOrDefaultAsync(); + Task FirstOrDefaultAsync(); + Task LastOrDefaultAsync(); - Task FirstOrDefaultAsync(Expression> predicate); - Task LastOrDefaultAsync(Expression> predicate); + Task FirstOrDefaultAsync(Expression> predicate); + Task LastOrDefaultAsync(Expression> predicate); - Task CountAsync(); + Task CountAsync(); - IMagicQueryPaginationTake Take(int amount); - IMagicQueryFinal TakeLast(int amount); - IMagicQueryFinal Skip(int amount); + IMagicQueryPaginationTake Take(int amount); + IMagicQueryFinal TakeLast(int amount); + IMagicQueryFinal Skip(int amount); - /// - /// This always orders first by the primary key, then by whatever is appended afterwards - /// - /// - /// - IMagicQueryOrderableTable OrderBy(Expression> predicate); + /// + /// This always orders first by the primary key, then by whatever is appended afterwards + /// + /// + /// + IMagicQueryOrderableTable OrderBy(Expression> predicate); - /// - /// This always orders by descending by the primary key first, then by whatever is appended afterwards - /// - /// - /// - IMagicQueryOrderableTable OrderByDescending(Expression> predicate); + /// + /// This always orders by descending by the primary key first, then by whatever is appended afterwards + /// + /// + /// + IMagicQueryOrderableTable OrderByDescending(Expression> predicate); - Task AddRangeAsync(IEnumerable records, CancellationToken cancellationToken = default); + Task AddRangeAsync(IEnumerable records, CancellationToken cancellationToken = default); - Task UpdateAsync(T item, CancellationToken cancellationToken = default); + Task UpdateAsync(T item, CancellationToken cancellationToken = default); - Task UpdateRangeAsync(IEnumerable items, CancellationToken cancellationToken = default); + Task UpdateRangeAsync(IEnumerable items, CancellationToken cancellationToken = default); - Task DeleteAsync(T item, CancellationToken cancellationToken = default); + Task DeleteAsync(T item, CancellationToken cancellationToken = default); - Task DeleteRangeAsync(IEnumerable items, CancellationToken cancellationToken = default); + Task DeleteRangeAsync(IEnumerable items, CancellationToken cancellationToken = default); - Task AddAsync(T record, CancellationToken cancellationToken = default); - Task ClearTable(); - } -} + Task AddAsync(T record, CancellationToken cancellationToken = default); + Task ClearTable(); +} \ No newline at end of file diff --git a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryFinal.cs b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryFinal.cs index 5da4466..1822158 100644 --- a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryFinal.cs +++ b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryFinal.cs @@ -7,25 +7,24 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +/// +/// You did it, you've found the end! There's no more IndexDB operations +/// you can append. Anything else is memory based. So off, venture forth! +/// has the potential of utilizing indexes. +/// +/// +public interface IMagicQueryFinal : IMagicExecute where T : class { + /// - /// You did it, you've found the end! There's no more IndexDB operations - /// you can append. Anything else is memory based. So off, venture forth! - /// has the potential of utilizing indexes. + /// In memory processing from this point forward! IndexDB + /// does not support complex query WHERE statements after appended + /// queries like, 'Take', 'Skip', 'OrderBy', and others are utilized. /// - /// - public interface IMagicQueryFinal : IMagicExecute where T : class - { - - /// - /// In memory processing from this point forward! IndexDB - /// does not support complex query WHERE statements after appended - /// queries like, 'Take', 'Skip', 'OrderBy', and others are utilized. - /// - /// - /// - /// - Task> WhereAsync(Expression> predicate); - } -} + /// + /// + /// + Task> WhereAsync(Expression> predicate); +} \ No newline at end of file diff --git a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryOrderable.cs b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryOrderable.cs index 2edcece..bfd488a 100644 --- a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryOrderable.cs +++ b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryOrderable.cs @@ -7,30 +7,29 @@ using System.Threading.Tasks; using Magic.IndexedDb.LinqTranslation.Interfaces; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +/// +/// You are in the staging phase. This is still a fully supported query that +/// has the potential of utilizing indexes. But you can apply only a few more +/// operations. +/// +/// +public interface IMagicQueryOrderable : IMagicExecute where T : class { + IMagicQueryPaginationTake Take(int amount); + IMagicQueryFinal TakeLast(int amount); + IMagicQueryFinal Skip(int amount); + Task FirstOrDefaultAsync(); + Task LastOrDefaultAsync(); + /// - /// You are in the staging phase. This is still a fully supported query that - /// has the potential of utilizing indexes. But you can apply only a few more - /// operations. + /// In memory processing from this point forward! IndexDB + /// does not support complex query WHERE statements after appended + /// queries like, 'Take', 'Skip', 'OrderBy', and others are utilized. /// - /// - public interface IMagicQueryOrderable : IMagicExecute where T : class - { - IMagicQueryPaginationTake Take(int amount); - IMagicQueryFinal TakeLast(int amount); - IMagicQueryFinal Skip(int amount); - Task FirstOrDefaultAsync(); - Task LastOrDefaultAsync(); - - /// - /// In memory processing from this point forward! IndexDB - /// does not support complex query WHERE statements after appended - /// queries like, 'Take', 'Skip', 'OrderBy', and others are utilized. - /// - /// - /// - /// - Task> WhereAsync(Expression> predicate); - } -} + /// + /// + /// + Task> WhereAsync(Expression> predicate); +} \ No newline at end of file diff --git a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryOrderableTable.cs b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryOrderableTable.cs index 625b2be..1932e67 100644 --- a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryOrderableTable.cs +++ b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryOrderableTable.cs @@ -5,15 +5,14 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb -{ - /// - /// Direct ordering applied to a table without where statements. - /// - public interface IMagicQueryOrderableTable : IMagicQueryOrderable, IMagicExecute +namespace Magic.IndexedDb; + +/// +/// Direct ordering applied to a table without where statements. +/// +public interface IMagicQueryOrderableTable : IMagicQueryOrderable, IMagicExecute where T : class - { - Task FirstOrDefaultAsync(Expression> predicate); - Task LastOrDefaultAsync(Expression> predicate); - } -} +{ + Task FirstOrDefaultAsync(Expression> predicate); + Task LastOrDefaultAsync(Expression> predicate); +} \ No newline at end of file diff --git a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryPaginationTake.cs b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryPaginationTake.cs index 27f3e50..182e604 100644 --- a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryPaginationTake.cs +++ b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryPaginationTake.cs @@ -7,26 +7,25 @@ using System.Threading.Tasks; using Magic.IndexedDb.LinqTranslation.Interfaces; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +/// +/// You are in the staging phase. This is still a fully supported query that +/// has the potential of utilizing indexes. But you can apply only a few more +/// operations. +/// +/// +public interface IMagicQueryPaginationTake : IMagicExecute where T : class { + IMagicQueryFinal Skip(int amount); + /// - /// You are in the staging phase. This is still a fully supported query that - /// has the potential of utilizing indexes. But you can apply only a few more - /// operations. + /// In memory processing from this point forward! IndexDB + /// does not support complex query WHERE statements after appended + /// queries like, 'Take', 'Skip', 'OrderBy', and others are utilized. /// - /// - public interface IMagicQueryPaginationTake : IMagicExecute where T : class - { - IMagicQueryFinal Skip(int amount); - - /// - /// In memory processing from this point forward! IndexDB - /// does not support complex query WHERE statements after appended - /// queries like, 'Take', 'Skip', 'OrderBy', and others are utilized. - /// - /// - /// - /// - Task> WhereAsync(Expression> predicate); - } -} + /// + /// + /// + Task> WhereAsync(Expression> predicate); +} \ No newline at end of file diff --git a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryStaging.cs b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryStaging.cs index 9f513b1..f3e5bfc 100644 --- a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryStaging.cs +++ b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryStaging.cs @@ -7,24 +7,23 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +public interface IMagicQueryStaging : IMagicExecute where T : class { - public interface IMagicQueryStaging : IMagicExecute where T : class - { - /// - /// The order you apply does get applied correctly in the query, - /// but the returned results will not be in the same order. - /// If order matters, you must apply the order again on return. - /// This is a fundemental limitation of IndexDB. - /// - /// - /// - IMagicQueryStaging Where(Expression> predicate); - IMagicQueryPaginationTake Take(int amount); - IMagicQueryFinal TakeLast(int amount); - IMagicQueryFinal Skip(int amount); + /// + /// The order you apply does get applied correctly in the query, + /// but the returned results will not be in the same order. + /// If order matters, you must apply the order again on return. + /// This is a fundemental limitation of IndexDB. + /// + /// + /// + IMagicQueryStaging Where(Expression> predicate); + IMagicQueryPaginationTake Take(int amount); + IMagicQueryFinal TakeLast(int amount); + IMagicQueryFinal Skip(int amount); - Task FirstOrDefaultAsync(); - Task LastOrDefaultAsync(); - } -} + Task FirstOrDefaultAsync(); + Task LastOrDefaultAsync(); +} \ No newline at end of file diff --git a/Magic.IndexedDb/LinqTranslation/Models/LogicalFilterNode.cs b/Magic.IndexedDb/LinqTranslation/Models/LogicalFilterNode.cs index 9dfae1e..c80487c 100644 --- a/Magic.IndexedDb/LinqTranslation/Models/LogicalFilterNode.cs +++ b/Magic.IndexedDb/LinqTranslation/Models/LogicalFilterNode.cs @@ -6,71 +6,70 @@ using System.Text.Json.Serialization; using System.Threading.Tasks; -namespace Magic.IndexedDb.LinqTranslation.Models +namespace Magic.IndexedDb.LinqTranslation.Models; + +public class FilterNode { - public class FilterNode - { - public FilterNodeType NodeType { get; set; } - public FilterLogicalOperator Operator { get; set; } - public List? Children { get; set; } - public FilterCondition? Condition { get; set; } - } + public FilterNodeType NodeType { get; set; } + public FilterLogicalOperator Operator { get; set; } + public List? Children { get; set; } + public FilterCondition? Condition { get; set; } +} - /// - /// Represents logical operators in the filter tree. - /// - public enum FilterLogicalOperator - { - And = 0, - Or = 1, - } +/// +/// Represents logical operators in the filter tree. +/// +public enum FilterLogicalOperator +{ + And = 0, + Or = 1, +} - public enum FilterNodeType - { - Logical = 0, - Condition = 1 - } +public enum FilterNodeType +{ + Logical = 0, + Condition = 1 +} - ///// - ///// The base class for any node in our filter tree. - ///// - //public class FilterNode - //{ - // public FilterNodeType NodeType { get; set; } - // public FilterLogicalOperator? Operator { get; set; } - // public List? Children { get; set; } - // public FilterCondition? Condition { get; set; } - //} +///// +///// The base class for any node in our filter tree. +///// +//public class FilterNode +//{ +// public FilterNodeType NodeType { get; set; } +// public FilterLogicalOperator? Operator { get; set; } +// public List? Children { get; set; } +// public FilterCondition? Condition { get; set; } +//} - ///// - ///// A node that holds a logical operator (AND/OR) and child nodes. - ///// - //public class LogicalFilterNode : FilterNode - //{ - // public FilterLogicalOperator Operator { get; set; } +///// +///// A node that holds a logical operator (AND/OR) and child nodes. +///// +//public class LogicalFilterNode : FilterNode +//{ +// public FilterLogicalOperator Operator { get; set; } - // /// - // /// The child nodes under this operator (could be conditions or nested operators). - // /// - // public List Children { get; set; } = new(); - //} +// /// +// /// The child nodes under this operator (could be conditions or nested operators). +// /// +// public List Children { get; set; } = new(); +//} - ///// - ///// A node that holds a single FilterCondition (leaf). - ///// - //public class ConditionFilterNode : FilterNode - //{ - // public FilterCondition Condition { get; set; } +///// +///// A node that holds a single FilterCondition (leaf). +///// +//public class ConditionFilterNode : FilterNode +//{ +// public FilterCondition Condition { get; set; } - // public ConditionFilterNode(FilterCondition condition) - // { - // Condition = condition; - // } +// public ConditionFilterNode(FilterCondition condition) +// { +// Condition = condition; +// } - // public ConditionFilterNode() { } - //} -} +// public ConditionFilterNode() { } +//} \ No newline at end of file diff --git a/Magic.IndexedDb/LinqTranslation/Models/MagicCursor.cs b/Magic.IndexedDb/LinqTranslation/Models/MagicCursor.cs index 5797ada..51cce09 100644 --- a/Magic.IndexedDb/LinqTranslation/Models/MagicCursor.cs +++ b/Magic.IndexedDb/LinqTranslation/Models/MagicCursor.cs @@ -7,58 +7,57 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.LinqTranslation.Models +namespace Magic.IndexedDb.LinqTranslation.Models; + +internal class MagicCursor : IMagicCursor where T : class { - internal class MagicCursor : IMagicCursor where T : class + public MagicQuery MagicQuery { get; set; } + public MagicCursor(MagicQuery _magicQuery) { - public MagicQuery MagicQuery { get; set; } - public MagicCursor(MagicQuery _magicQuery) - { - _magicQuery.ForceCursorMode = true; - MagicQuery = _magicQuery; + _magicQuery.ForceCursorMode = true; + MagicQuery = _magicQuery; - } + } - public IMagicCursor Cursor(Expression> predicate) - { + public IMagicCursor Cursor(Expression> predicate) + { - var _MagicQuery = new MagicQuery(MagicQuery); - _MagicQuery.Predicates.Add(predicate); - return new MagicCursor(_MagicQuery); // Enable method chaining - } + var _MagicQuery = new MagicQuery(MagicQuery); + _MagicQuery.Predicates.Add(predicate); + return new MagicCursor(_MagicQuery); // Enable method chaining + } - public IMagicCursorPaginationTake Take(int amount) - => new MagicCursorExtension(MagicQuery).Take(amount); + public IMagicCursorPaginationTake Take(int amount) + => new MagicCursorExtension(MagicQuery).Take(amount); - public IMagicCursorPaginationTake TakeLast(int amount) - => new MagicCursorExtension(MagicQuery).TakeLast(amount); + public IMagicCursorPaginationTake TakeLast(int amount) + => new MagicCursorExtension(MagicQuery).TakeLast(amount); - public IMagicCursorSkip Skip(int amount) - => new MagicCursorExtension(MagicQuery).Skip(amount); + public IMagicCursorSkip Skip(int amount) + => new MagicCursorExtension(MagicQuery).Skip(amount); - public IMagicCursorStage OrderBy(Expression> predicate) - => new MagicCursorExtension(MagicQuery).OrderBy(predicate); + public IMagicCursorStage OrderBy(Expression> predicate) + => new MagicCursorExtension(MagicQuery).OrderBy(predicate); - public IMagicCursorStage OrderByDescending(Expression> predicate) - => new MagicCursorExtension(MagicQuery).OrderByDescending(predicate); + public IMagicCursorStage OrderByDescending(Expression> predicate) + => new MagicCursorExtension(MagicQuery).OrderByDescending(predicate); - public async IAsyncEnumerable AsAsyncEnumerable( - [EnumeratorCancellation] CancellationToken cancellationToken = default) + public async IAsyncEnumerable AsAsyncEnumerable( + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + await foreach (var item in new MagicQueryExtensions(MagicQuery).AsAsyncEnumerable(cancellationToken) + .WithCancellation(cancellationToken)) { - await foreach (var item in new MagicQueryExtensions(MagicQuery).AsAsyncEnumerable(cancellationToken) - .WithCancellation(cancellationToken)) - { - yield return item; - } + yield return item; } + } - public async Task> ToListAsync() - => await new MagicQueryExtensions(MagicQuery).ToListAsync(); + public async Task> ToListAsync() + => await new MagicQueryExtensions(MagicQuery).ToListAsync(); - public async Task FirstOrDefaultAsync() - => await new MagicQueryExtensions(MagicQuery).FirstOrDefaultAsync(); + public async Task FirstOrDefaultAsync() + => await new MagicQueryExtensions(MagicQuery).FirstOrDefaultAsync(); - public async Task LastOrDefaultAsync() - => await new MagicQueryExtensions(MagicQuery).LastOrDefaultAsync(); - } -} + public async Task LastOrDefaultAsync() + => await new MagicQueryExtensions(MagicQuery).LastOrDefaultAsync(); +} \ No newline at end of file diff --git a/Magic.IndexedDb/LinqTranslation/Models/MagicDatabaseGlobal.cs b/Magic.IndexedDb/LinqTranslation/Models/MagicDatabaseGlobal.cs index 4c5cfc3..00bc961 100644 --- a/Magic.IndexedDb/LinqTranslation/Models/MagicDatabaseGlobal.cs +++ b/Magic.IndexedDb/LinqTranslation/Models/MagicDatabaseGlobal.cs @@ -5,9 +5,8 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.LinqTranslation.Models -{ - //internal class MagicDatabaseGlobal : IMagicDatabaseGlobal - //{ - //} -} +namespace Magic.IndexedDb.LinqTranslation.Models; + +//internal class MagicDatabaseGlobal : IMagicDatabaseGlobal +//{ +//} \ No newline at end of file diff --git a/Magic.IndexedDb/LinqTranslation/Models/MagicDatabaseScoped.cs b/Magic.IndexedDb/LinqTranslation/Models/MagicDatabaseScoped.cs index cd79b7b..26be7ef 100644 --- a/Magic.IndexedDb/LinqTranslation/Models/MagicDatabaseScoped.cs +++ b/Magic.IndexedDb/LinqTranslation/Models/MagicDatabaseScoped.cs @@ -5,41 +5,40 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.LinqTranslation.Models +namespace Magic.IndexedDb.LinqTranslation.Models; + +internal class MagicDatabaseScoped : IMagicDatabaseScoped { - internal class MagicDatabaseScoped : IMagicDatabaseScoped + IndexedDbSet _IndexedDbSet; + IndexedDbManager _Manager; + public MagicDatabaseScoped(IndexedDbManager manager, IndexedDbSet indexedDbSet) { - IndexedDbSet _IndexedDbSet; - IndexedDbManager _Manager; - public MagicDatabaseScoped(IndexedDbManager manager, IndexedDbSet indexedDbSet) - { - _IndexedDbSet = indexedDbSet; - _Manager = manager; - } + _IndexedDbSet = indexedDbSet; + _Manager = manager; + } - public async Task DeleteAsync() - { - await _Manager.DeleteDbAsync(_IndexedDbSet.DatabaseName); - } + public async Task DeleteAsync() + { + await _Manager.DeleteDbAsync(_IndexedDbSet.DatabaseName); + } - public async Task CloseAsync() - { - await _Manager.CloseDbAsync(_IndexedDbSet.DatabaseName); - } + public async Task CloseAsync() + { + await _Manager.CloseDbAsync(_IndexedDbSet.DatabaseName); + } - public async Task IsOpenAsync() - { - return await _Manager.IsDbOpen(_IndexedDbSet.DatabaseName); - } + public async Task IsOpenAsync() + { + return await _Manager.IsDbOpen(_IndexedDbSet.DatabaseName); + } - public async Task DoesExistAsync() - { - return await _Manager.DoesDbExist(_IndexedDbSet.DatabaseName); - } + public async Task DoesExistAsync() + { + return await _Manager.DoesDbExist(_IndexedDbSet.DatabaseName); + } - public async Task OpenAsync() - { - await _Manager.OpenDbAsync(_IndexedDbSet.DatabaseName); - } + public async Task OpenAsync() + { + await _Manager.OpenDbAsync(_IndexedDbSet.DatabaseName); } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/LinqTranslation/Models/MagicQuery.cs b/Magic.IndexedDb/LinqTranslation/Models/MagicQuery.cs index 618e152..1604af2 100644 --- a/Magic.IndexedDb/LinqTranslation/Models/MagicQuery.cs +++ b/Magic.IndexedDb/LinqTranslation/Models/MagicQuery.cs @@ -14,170 +14,169 @@ using System.Text.Json; using System.Threading.Tasks; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +internal class MagicQuery : IMagicQuery, IMagicQueryStaging where T : class { - internal class MagicQuery : IMagicQuery, IMagicQueryStaging where T : class + /// + /// table name + /// + public string SchemaName { get; } + + /// + /// database name + /// + public string DatabaseName { get; } + internal IndexedDbManager Manager { get; } + internal List StoredMagicQueries { get; set; } = new List(); + internal bool ForceCursorMode { get; set; } = false; + + internal bool ResultsUnique { get; set; } = true; + internal List>> Predicates { get; } = new List>>(); + + internal MagicQuery(string databaseName, string schemaName, IndexedDbManager manager) { - /// - /// table name - /// - public string SchemaName { get; } - - /// - /// database name - /// - public string DatabaseName { get; } - internal IndexedDbManager Manager { get; } - internal List StoredMagicQueries { get; set; } = new List(); - internal bool ForceCursorMode { get; set; } = false; - - internal bool ResultsUnique { get; set; } = true; - internal List>> Predicates { get; } = new List>>(); - - internal MagicQuery(string databaseName, string schemaName, IndexedDbManager manager) - { - Manager = manager; - SchemaName = schemaName; - DatabaseName = databaseName; - } - - public MagicQuery(MagicQuery _MagicQuery) - { - SchemaName = _MagicQuery.SchemaName; // Keep reference - DatabaseName = _MagicQuery.DatabaseName; // Keep reference - Manager = _MagicQuery.Manager; // Keep reference - StoredMagicQueries = new List(_MagicQuery.StoredMagicQueries); // Deep copy - ResultsUnique = _MagicQuery.ResultsUnique; - Predicates = new List>>(_MagicQuery.Predicates); // Deep copy - } + Manager = manager; + SchemaName = schemaName; + DatabaseName = databaseName; + } - public IMagicQueryStaging Where(Expression> predicate) - { - var _MagicQuery = new MagicQuery(this); - _MagicQuery.Predicates.Add(predicate); - return _MagicQuery; // Enable method chaining - } + public MagicQuery(MagicQuery _MagicQuery) + { + SchemaName = _MagicQuery.SchemaName; // Keep reference + DatabaseName = _MagicQuery.DatabaseName; // Keep reference + Manager = _MagicQuery.Manager; // Keep reference + StoredMagicQueries = new List(_MagicQuery.StoredMagicQueries); // Deep copy + ResultsUnique = _MagicQuery.ResultsUnique; + Predicates = new List>>(_MagicQuery.Predicates); // Deep copy + } - internal Expression> GetFinalPredicate() - { - if (Predicates.Count == 0) - return x => true; // Default to always-true if no predicates exist + public IMagicQueryStaging Where(Expression> predicate) + { + var _MagicQuery = new MagicQuery(this); + _MagicQuery.Predicates.Add(predicate); + return _MagicQuery; // Enable method chaining + } - Expression> finalPredicate = Predicates[0]; + internal Expression> GetFinalPredicate() + { + if (Predicates.Count == 0) + return x => true; // Default to always-true if no predicates exist - for (int i = 1; i < Predicates.Count; i++) - { - finalPredicate = CombineExpressions(finalPredicate, Predicates[i]); - } + Expression> finalPredicate = Predicates[0]; - return finalPredicate; + for (int i = 1; i < Predicates.Count; i++) + { + finalPredicate = CombineExpressions(finalPredicate, Predicates[i]); } - private Expression> CombineExpressions(Expression> first, Expression> second) - { - var parameter = Expression.Parameter(typeof(T), "x"); + return finalPredicate; + } - var combinedBody = Expression.AndAlso( - new PredicateVisitor().Visit(first.Body), - new PredicateVisitor().Visit(second.Body) - ); + private Expression> CombineExpressions(Expression> first, Expression> second) + { + var parameter = Expression.Parameter(typeof(T), "x"); - return Expression.Lambda>(combinedBody, parameter); - } + var combinedBody = Expression.AndAlso( + new PredicateVisitor().Visit(first.Body), + new PredicateVisitor().Visit(second.Body) + ); + + return Expression.Lambda>(combinedBody, parameter); + } - public IMagicQueryPaginationTake Take(int amount) - => new MagicQueryExtensions(this).Take(amount); + public IMagicQueryPaginationTake Take(int amount) + => new MagicQueryExtensions(this).Take(amount); - public IMagicQueryFinal TakeLast(int amount) - => new MagicQueryExtensions(this).TakeLast(amount); + public IMagicQueryFinal TakeLast(int amount) + => new MagicQueryExtensions(this).TakeLast(amount); - public IMagicQueryFinal Skip(int amount) - => new MagicQueryExtensions(this).Skip(amount); + public IMagicQueryFinal Skip(int amount) + => new MagicQueryExtensions(this).Skip(amount); - public IMagicQueryOrderableTable OrderBy(Expression> predicate) - => new MagicQueryExtensions(this).OrderBy(predicate); + public IMagicQueryOrderableTable OrderBy(Expression> predicate) + => new MagicQueryExtensions(this).OrderBy(predicate); - public IMagicQueryOrderableTable OrderByDescending(Expression> predicate) - => new MagicQueryExtensions(this).OrderByDescending(predicate); + public IMagicQueryOrderableTable OrderByDescending(Expression> predicate) + => new MagicQueryExtensions(this).OrderByDescending(predicate); - /*IMagicQueryOrderable IMagicQueryStaging.OrderBy(Expression> predicate) - => OrderBy(predicate); + /*IMagicQueryOrderable IMagicQueryStaging.OrderBy(Expression> predicate) + => OrderBy(predicate); - IMagicQueryOrderable IMagicQueryStaging.OrderByDescending(Expression> predicate) - => OrderByDescending(predicate);*/ + IMagicQueryOrderable IMagicQueryStaging.OrderByDescending(Expression> predicate) + => OrderByDescending(predicate);*/ - public IMagicCursor Cursor(Expression> predicate) - => new MagicCursor(this).Cursor(predicate); + public IMagicCursor Cursor(Expression> predicate) + => new MagicCursor(this).Cursor(predicate); - public async Task FirstOrDefaultAsync() - => await new MagicQueryExtensions(this).FirstOrDefaultAsync(); + public async Task FirstOrDefaultAsync() + => await new MagicQueryExtensions(this).FirstOrDefaultAsync(); - public async Task LastOrDefaultAsync() - => await new MagicQueryExtensions(this).LastOrDefaultAsync(); + public async Task LastOrDefaultAsync() + => await new MagicQueryExtensions(this).LastOrDefaultAsync(); - public async Task FirstOrDefaultAsync(Expression> predicate) - => await new MagicQueryExtensions(this).FirstOrDefaultAsync(predicate); + public async Task FirstOrDefaultAsync(Expression> predicate) + => await new MagicQueryExtensions(this).FirstOrDefaultAsync(predicate); - public async Task LastOrDefaultAsync(Expression> predicate) - => await new MagicQueryExtensions(this).LastOrDefaultAsync(predicate); + public async Task LastOrDefaultAsync(Expression> predicate) + => await new MagicQueryExtensions(this).LastOrDefaultAsync(predicate); - public async IAsyncEnumerable AsAsyncEnumerable( - [EnumeratorCancellation] CancellationToken cancellationToken = default) + public async IAsyncEnumerable AsAsyncEnumerable( + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + await foreach (var item in new MagicQueryExtensions(this).AsAsyncEnumerable(cancellationToken) + .WithCancellation(cancellationToken)) { - await foreach (var item in new MagicQueryExtensions(this).AsAsyncEnumerable(cancellationToken) - .WithCancellation(cancellationToken)) - { - yield return item; - } + yield return item; } + } - public async Task> ToListAsync() - => await new MagicQueryExtensions(this).ToListAsync(); + public async Task> ToListAsync() + => await new MagicQueryExtensions(this).ToListAsync(); - public async Task CountAsync() - { - return await Manager.CountEntireTableAsync(SchemaName, DatabaseName); - } + public async Task CountAsync() + { + return await Manager.CountEntireTableAsync(SchemaName, DatabaseName); + } - public async Task AddRangeAsync( - IEnumerable records, CancellationToken cancellationToken = default) - { - await Manager.BulkAddRecordAsync(SchemaName, DatabaseName, records, cancellationToken); - } + public async Task AddRangeAsync( + IEnumerable records, CancellationToken cancellationToken = default) + { + await Manager.BulkAddRecordAsync(SchemaName, DatabaseName, records, cancellationToken); + } - public async Task UpdateAsync(T item, CancellationToken cancellationToken = default) - { - return await Manager.UpdateAsync(item, DatabaseName, cancellationToken); - } + public async Task UpdateAsync(T item, CancellationToken cancellationToken = default) + { + return await Manager.UpdateAsync(item, DatabaseName, cancellationToken); + } - public async Task UpdateRangeAsync( - IEnumerable items, - CancellationToken cancellationToken = default) - { - return await Manager.UpdateRangeAsync(items, DatabaseName, cancellationToken); - } + public async Task UpdateRangeAsync( + IEnumerable items, + CancellationToken cancellationToken = default) + { + return await Manager.UpdateRangeAsync(items, DatabaseName, cancellationToken); + } - public async Task DeleteAsync(T item, CancellationToken cancellationToken = default) - { - await Manager.DeleteAsync(item, DatabaseName, cancellationToken); - } + public async Task DeleteAsync(T item, CancellationToken cancellationToken = default) + { + await Manager.DeleteAsync(item, DatabaseName, cancellationToken); + } - public async Task DeleteRangeAsync( - IEnumerable items, - CancellationToken cancellationToken = default) - { - return await Manager.DeleteRangeAsync(items, DatabaseName, cancellationToken); - } + public async Task DeleteRangeAsync( + IEnumerable items, + CancellationToken cancellationToken = default) + { + return await Manager.DeleteRangeAsync(items, DatabaseName, cancellationToken); + } - public async Task AddAsync(T record, CancellationToken cancellationToken = default) - { - _ = await Manager.AddAsync(record, DatabaseName, cancellationToken); - } + public async Task AddAsync(T record, CancellationToken cancellationToken = default) + { + _ = await Manager.AddAsync(record, DatabaseName, cancellationToken); + } - public async Task ClearTable() - { - await Manager.ClearTableAsync(SchemaName, DatabaseName); - } + public async Task ClearTable() + { + await Manager.ClearTableAsync(SchemaName, DatabaseName); } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/LinqTranslation/Models/MagicWhereLinq.cs b/Magic.IndexedDb/LinqTranslation/Models/MagicWhereLinq.cs index 2c475e5..40fce45 100644 --- a/Magic.IndexedDb/LinqTranslation/Models/MagicWhereLinq.cs +++ b/Magic.IndexedDb/LinqTranslation/Models/MagicWhereLinq.cs @@ -4,10 +4,9 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +internal class MagicWhereLinq { - internal class MagicWhereLinq - { - } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/MagicEncryptAttribute.cs b/Magic.IndexedDb/MagicEncryptAttribute.cs index cddd74b..5308703 100644 --- a/Magic.IndexedDb/MagicEncryptAttribute.cs +++ b/Magic.IndexedDb/MagicEncryptAttribute.cs @@ -5,22 +5,21 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +[Obsolete("Future refactor is planned for significantly improved encryption system. This feature is no longer supported nor working.")] +[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] +public class MagicEncryptAttribute : Attribute { - [Obsolete("Future refactor is planned for significantly improved encryption system. This feature is no longer supported nor working.")] - [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] - public class MagicEncryptAttribute : Attribute + public MagicEncryptAttribute() { - public MagicEncryptAttribute() - { - } + } - public void Validate(PropertyInfo property) + public void Validate(PropertyInfo property) + { + if (property.PropertyType != typeof(string)) { - if (property.PropertyType != typeof(string)) - { - throw new ArgumentException("EncryptDbAttribute can only be used on string properties"); - } + throw new ArgumentException("EncryptDbAttribute can only be used on string properties"); } } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/Models/DbMigration.cs b/Magic.IndexedDb/Models/DbMigration.cs index 056f8a1..aa7e5b0 100644 --- a/Magic.IndexedDb/Models/DbMigration.cs +++ b/Magic.IndexedDb/Models/DbMigration.cs @@ -4,12 +4,11 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +public class DbMigration { - public class DbMigration - { - public string? FromVersion { get; set; } - public string? ToVersion { get; set; } - public List Instructions { get; set; } = new List(); - } -} + public string? FromVersion { get; set; } + public string? ToVersion { get; set; } + public List Instructions { get; set; } = new List(); +} \ No newline at end of file diff --git a/Magic.IndexedDb/Models/DbMigrationInstruction.cs b/Magic.IndexedDb/Models/DbMigrationInstruction.cs index a215d7a..3937843 100644 --- a/Magic.IndexedDb/Models/DbMigrationInstruction.cs +++ b/Magic.IndexedDb/Models/DbMigrationInstruction.cs @@ -4,12 +4,11 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +public class DbMigrationInstruction { - public class DbMigrationInstruction - { - public string Action { get; set; } - public string StoreName { get; set; } - public string Details { get; set; } - } -} + public string Action { get; set; } + public string StoreName { get; set; } + public string Details { get; set; } +} \ No newline at end of file diff --git a/Magic.IndexedDb/Models/DbStore.cs b/Magic.IndexedDb/Models/DbStore.cs index 97d9579..5727923 100644 --- a/Magic.IndexedDb/Models/DbStore.cs +++ b/Magic.IndexedDb/Models/DbStore.cs @@ -5,17 +5,16 @@ using System.Text.Json.Serialization; using System.Threading.Tasks; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +public class DbStore { - public class DbStore - { - public string Name { get; set; } - public int Version { get; set; } + public string Name { get; set; } + public int Version { get; set; } - [JsonPropertyName("storeSchemas")] - public List StoreSchemas { get; set; } + [JsonPropertyName("storeSchemas")] + public List StoreSchemas { get; set; } - [Obsolete("NOT SUPPORTED: This will likely be created to work with the Truth Protocol. Stay tuned.")] - public List DbMigrations { get; set; } = new List(); - } -} + [Obsolete("NOT SUPPORTED: This will likely be created to work with the Truth Protocol. Stay tuned.")] + public List DbMigrations { get; set; } = new List(); +} \ No newline at end of file diff --git a/Magic.IndexedDb/Models/IndexFilterValue.cs b/Magic.IndexedDb/Models/IndexFilterValue.cs index 1191b3d..443ae7f 100644 --- a/Magic.IndexedDb/Models/IndexFilterValue.cs +++ b/Magic.IndexedDb/Models/IndexFilterValue.cs @@ -4,17 +4,16 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Models +namespace Magic.IndexedDb.Models; + +public class IndexFilterValue { - public class IndexFilterValue + public IndexFilterValue(string indexName, object filterValue) { - public IndexFilterValue(string indexName, object filterValue) - { - IndexName = indexName; - FilterValue = filterValue; - } - - public string IndexName { get; set; } - public object FilterValue { get; set; } + IndexName = indexName; + FilterValue = filterValue; } -} + + public string IndexName { get; set; } + public object FilterValue { get; set; } +} \ No newline at end of file diff --git a/Magic.IndexedDb/Models/IndexedDbSet.cs b/Magic.IndexedDb/Models/IndexedDbSet.cs index d3d7262..fd0204b 100644 --- a/Magic.IndexedDb/Models/IndexedDbSet.cs +++ b/Magic.IndexedDb/Models/IndexedDbSet.cs @@ -4,14 +4,13 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +public class IndexedDbSet { - public class IndexedDbSet + public string DatabaseName { get; } + public IndexedDbSet(string databaseName) { - public string DatabaseName { get; } - public IndexedDbSet(string databaseName) - { - DatabaseName = databaseName; - } + DatabaseName = databaseName; } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/Models/InternalMagicCompoundIndex.cs b/Magic.IndexedDb/Models/InternalMagicCompoundIndex.cs index 0064929..8ba69bf 100644 --- a/Magic.IndexedDb/Models/InternalMagicCompoundIndex.cs +++ b/Magic.IndexedDb/Models/InternalMagicCompoundIndex.cs @@ -8,93 +8,92 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Models +namespace Magic.IndexedDb.Models; + +internal class InternalMagicCompoundIndex : IMagicCompoundIndex { - internal class InternalMagicCompoundIndex : IMagicCompoundIndex - { - public string[] ColumnNamesInCompoundIndex { get; } - public PropertyInfo[] PropertyInfos { get; } + public string[] ColumnNamesInCompoundIndex { get; } + public PropertyInfo[] PropertyInfos { get; } - private InternalMagicCompoundIndex(params Expression>[] properties) + private InternalMagicCompoundIndex(params Expression>[] properties) + { + if (properties == null || properties.Length < 2) { - if (properties == null || properties.Length < 2) - { - throw new ArgumentException("Compound indexes require at least 2 properties.", nameof(properties)); - } + throw new ArgumentException("Compound indexes require at least 2 properties.", nameof(properties)); + } - // 🔥 Step 1: Retrieve PropertyInfos - PropertyInfos = properties - .Select(GetPropertyInfo) - .ToArray(); + // 🔥 Step 1: Retrieve PropertyInfos + PropertyInfos = properties + .Select(GetPropertyInfo) + .ToArray(); - // 🔥 Step 2: Retrieve JS property names **without causing infinite recursion** - ColumnNamesInCompoundIndex = PropertyInfos - .Select(x => PropertyMappingCache.GetJsPropertyNameNoCache( - PropertyMappingCache.GetPropertyColumnAttribute(x), x.Name)) // ✅ Correct JS name retrieval - .ToArray(); + // 🔥 Step 2: Retrieve JS property names **without causing infinite recursion** + ColumnNamesInCompoundIndex = PropertyInfos + .Select(x => PropertyMappingCache.GetJsPropertyNameNoCache( + PropertyMappingCache.GetPropertyColumnAttribute(x), x.Name)) // ✅ Correct JS name retrieval + .ToArray(); - // 🔥 Step 3: Validate the compound index properties - ValidateIndexes(); - } + // 🔥 Step 3: Validate the compound index properties + ValidateIndexes(); + } - internal static IMagicCompoundIndex Create(params Expression>[] keySelectors) + internal static IMagicCompoundIndex Create(params Expression>[] keySelectors) + { + return new InternalMagicCompoundIndex(keySelectors); + } + + private void ValidateIndexes() + { + if (ColumnNamesInCompoundIndex.Distinct().Count() != ColumnNamesInCompoundIndex.Length) { - return new InternalMagicCompoundIndex(keySelectors); + throw new InvalidOperationException( + $"Duplicate properties detected in the compound index for type '{typeof(T).Name}'. Each property must be unique."); } + } - private void ValidateIndexes() + /// + /// Extracts PropertyInfo from the provided expression. + /// + private static PropertyInfo GetPropertyInfo(Expression> propertyExpression) + { + if (propertyExpression.Body is MemberExpression memberExpr) { - if (ColumnNamesInCompoundIndex.Distinct().Count() != ColumnNamesInCompoundIndex.Length) - { - throw new InvalidOperationException( - $"Duplicate properties detected in the compound index for type '{typeof(T).Name}'. Each property must be unique."); - } + return ValidateAndGetPropertyInfo(memberExpr); } - - /// - /// Extracts PropertyInfo from the provided expression. - /// - private static PropertyInfo GetPropertyInfo(Expression> propertyExpression) + else if (propertyExpression.Body is UnaryExpression unaryExpr && unaryExpr.Operand is MemberExpression unaryMemberExpr) { - if (propertyExpression.Body is MemberExpression memberExpr) - { - return ValidateAndGetPropertyInfo(memberExpr); - } - else if (propertyExpression.Body is UnaryExpression unaryExpr && unaryExpr.Operand is MemberExpression unaryMemberExpr) - { - return ValidateAndGetPropertyInfo(unaryMemberExpr); - } - else - { - throw new ArgumentException("Invalid expression format. Use property selectors like: `x => x.PropertyName`."); - } + return ValidateAndGetPropertyInfo(unaryMemberExpr); } - - /// - /// Ensures the property exists and is valid for indexing. - /// - private static PropertyInfo ValidateAndGetPropertyInfo(MemberExpression memberExpr) + else { - var property = typeof(T).GetProperty(memberExpr.Member.Name); - if (property == null) - { - throw new ArgumentException($"Property '{memberExpr.Member.Name}' does not exist on type '{typeof(T).Name}'."); - } + throw new ArgumentException("Invalid expression format. Use property selectors like: `x => x.PropertyName`."); + } + } - if (memberExpr.Expression is MemberExpression) - { - throw new InvalidOperationException( - $"Cannot use nested properties like '{memberExpr.Member.Name}' in a Compound Index for type '{typeof(T).Name}'. " + - "Only top-level properties can be indexed."); - } + /// + /// Ensures the property exists and is valid for indexing. + /// + private static PropertyInfo ValidateAndGetPropertyInfo(MemberExpression memberExpr) + { + var property = typeof(T).GetProperty(memberExpr.Member.Name); + if (property == null) + { + throw new ArgumentException($"Property '{memberExpr.Member.Name}' does not exist on type '{typeof(T).Name}'."); + } - if (property.GetCustomAttribute() != null) - { - throw new InvalidOperationException( - $"Cannot use the non-mapped property '{property.Name}' in a Compound Index for type '{typeof(T).Name}'."); - } + if (memberExpr.Expression is MemberExpression) + { + throw new InvalidOperationException( + $"Cannot use nested properties like '{memberExpr.Member.Name}' in a Compound Index for type '{typeof(T).Name}'. " + + "Only top-level properties can be indexed."); + } - return property; + if (property.GetCustomAttribute() != null) + { + throw new InvalidOperationException( + $"Cannot use the non-mapped property '{property.Name}' in a Compound Index for type '{typeof(T).Name}'."); } + + return property; } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/Models/InternalMagicCompoundKey.cs b/Magic.IndexedDb/Models/InternalMagicCompoundKey.cs index ade7696..7b0838d 100644 --- a/Magic.IndexedDb/Models/InternalMagicCompoundKey.cs +++ b/Magic.IndexedDb/Models/InternalMagicCompoundKey.cs @@ -8,113 +8,112 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Models +namespace Magic.IndexedDb.Models; + +internal class InternalMagicCompoundKey : IMagicCompoundKey { - internal class InternalMagicCompoundKey : IMagicCompoundKey - { - public string[] ColumnNamesInCompoundKey { get; } - public PropertyInfo[] PropertyInfos { get; } - public bool AutoIncrement { get; } - public bool IsCompoundKey { get; } // New flag to track if it's a compound key + public string[] ColumnNamesInCompoundKey { get; } + public PropertyInfo[] PropertyInfos { get; } + public bool AutoIncrement { get; } + public bool IsCompoundKey { get; } // New flag to track if it's a compound key - private InternalMagicCompoundKey(bool autoIncrement, params Expression>[] properties) - { - AutoIncrement = autoIncrement; + private InternalMagicCompoundKey(bool autoIncrement, params Expression>[] properties) + { + AutoIncrement = autoIncrement; - PropertyInfos = properties - .Select(GetPropertyInfo) - .ToArray(); + PropertyInfos = properties + .Select(GetPropertyInfo) + .ToArray(); - ColumnNamesInCompoundKey = PropertyInfos - .Select(x => PropertyMappingCache.GetJsPropertyNameNoCache( + ColumnNamesInCompoundKey = PropertyInfos + .Select(x => PropertyMappingCache.GetJsPropertyNameNoCache( PropertyMappingCache.GetPropertyColumnAttribute(x) , x.Name)) - .ToArray(); + .ToArray(); + + IsCompoundKey = PropertyInfos.Length > 1; // If more than one key, it's a compound key + + ValidateKeys(); + } + + internal static IMagicCompoundKey Create(bool autoIncrement, params Expression>[] keySelectors) + { + return new InternalMagicCompoundKey(autoIncrement, keySelectors); + } + + private void ValidateKeys() + { + string keyType = IsCompoundKey ? "Compound Key" : "Primary Key"; + + // **Check for duplicate column names** + if (ColumnNamesInCompoundKey.Distinct(StringComparer.OrdinalIgnoreCase).Count() != ColumnNamesInCompoundKey.Length) + { + throw new InvalidOperationException( + $"Duplicate properties detected in the {keyType} for type '{typeof(T).Name}'. Each property must be unique."); + } - IsCompoundKey = PropertyInfos.Length > 1; // If more than one key, it's a compound key + // **Prevent 'id' (case-insensitive) from being in compound keys** + if (IsCompoundKey && ColumnNamesInCompoundKey.Any(col => col.Equals("id", StringComparison.OrdinalIgnoreCase))) + { + throw new InvalidOperationException( + $"Invalid Compound Key in '{typeof(T).Name}': The column name 'id' cannot be part of a Compound Key. " + + "IndexedDB does not allow an explicit 'id' field when using compound keys."); + } - ValidateKeys(); + // **AutoIncrement should not be allowed for compound keys** + if (IsCompoundKey && AutoIncrement) + { + throw new InvalidOperationException( + $"Cannot mark a Compound Key as AutoIncrement for type '{typeof(T).Name}'. " + + "AutoIncrement is only allowed on single Primary Keys."); } + } - internal static IMagicCompoundKey Create(bool autoIncrement, params Expression>[] keySelectors) + + private static PropertyInfo GetPropertyInfo(Expression> propertyExpression) + { + if (propertyExpression.Body is MemberExpression memberExpr) + { + return ValidateAndGetPropertyInfo(memberExpr); + } + else if (propertyExpression.Body is UnaryExpression unaryExpr && unaryExpr.Operand is MemberExpression unaryMemberExpr) { - return new InternalMagicCompoundKey(autoIncrement, keySelectors); + return ValidateAndGetPropertyInfo(unaryMemberExpr); } + else + { + throw new ArgumentException("Invalid expression format. Use property selectors like: `x => x.PropertyName`."); + } + } - private void ValidateKeys() + private static PropertyInfo ValidateAndGetPropertyInfo(MemberExpression memberExpr) + { + var property = typeof(T).GetProperty(memberExpr.Member.Name); + if (property == null) { - string keyType = IsCompoundKey ? "Compound Key" : "Primary Key"; - - // **Check for duplicate column names** - if (ColumnNamesInCompoundKey.Distinct(StringComparer.OrdinalIgnoreCase).Count() != ColumnNamesInCompoundKey.Length) - { - throw new InvalidOperationException( - $"Duplicate properties detected in the {keyType} for type '{typeof(T).Name}'. Each property must be unique."); - } - - // **Prevent 'id' (case-insensitive) from being in compound keys** - if (IsCompoundKey && ColumnNamesInCompoundKey.Any(col => col.Equals("id", StringComparison.OrdinalIgnoreCase))) - { - throw new InvalidOperationException( - $"Invalid Compound Key in '{typeof(T).Name}': The column name 'id' cannot be part of a Compound Key. " + - "IndexedDB does not allow an explicit 'id' field when using compound keys."); - } - - // **AutoIncrement should not be allowed for compound keys** - if (IsCompoundKey && AutoIncrement) - { - throw new InvalidOperationException( - $"Cannot mark a Compound Key as AutoIncrement for type '{typeof(T).Name}'. " + - "AutoIncrement is only allowed on single Primary Keys."); - } + throw new ArgumentException($"Property '{memberExpr.Member.Name}' does not exist on type '{typeof(T).Name}'."); } + if (memberExpr.Expression is MemberExpression) + { + throw new InvalidOperationException( + $"Cannot use nested properties like '{memberExpr.Member.Name}' in a Primary or Compound Key for type '{typeof(T).Name}'. " + + "Only top-level properties can be indexed."); + } - private static PropertyInfo GetPropertyInfo(Expression> propertyExpression) + if (property.GetCustomAttribute() != null) { - if (propertyExpression.Body is MemberExpression memberExpr) - { - return ValidateAndGetPropertyInfo(memberExpr); - } - else if (propertyExpression.Body is UnaryExpression unaryExpr && unaryExpr.Operand is MemberExpression unaryMemberExpr) - { - return ValidateAndGetPropertyInfo(unaryMemberExpr); - } - else - { - throw new ArgumentException("Invalid expression format. Use property selectors like: `x => x.PropertyName`."); - } + throw new InvalidOperationException( + $"Cannot use the non-mapped property '{property.Name}' in a Primary or Compound Key for type '{typeof(T).Name}'."); } - private static PropertyInfo ValidateAndGetPropertyInfo(MemberExpression memberExpr) + if (property.GetCustomAttribute() != null) { - var property = typeof(T).GetProperty(memberExpr.Member.Name); - if (property == null) - { - throw new ArgumentException($"Property '{memberExpr.Member.Name}' does not exist on type '{typeof(T).Name}'."); - } - - if (memberExpr.Expression is MemberExpression) - { - throw new InvalidOperationException( - $"Cannot use nested properties like '{memberExpr.Member.Name}' in a Primary or Compound Key for type '{typeof(T).Name}'. " + - "Only top-level properties can be indexed."); - } - - if (property.GetCustomAttribute() != null) - { - throw new InvalidOperationException( - $"Cannot use the non-mapped property '{property.Name}' in a Primary or Compound Key for type '{typeof(T).Name}'."); - } - - if (property.GetCustomAttribute() != null) - { - throw new InvalidOperationException( - $"Cannot define a Primary or Compound Key including '{property.Name}' because it is already marked as a unique index " + - $"on type '{typeof(T).Name}'. Either remove the unique index or exclude it from the key."); - } - - return property; + throw new InvalidOperationException( + $"Cannot define a Primary or Compound Key including '{property.Name}' because it is already marked as a unique index " + + $"on type '{typeof(T).Name}'. Either remove the unique index or exclude it from the key."); } + + return property; } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/Models/MagicContractResolver.cs b/Magic.IndexedDb/Models/MagicContractResolver.cs index fb95adb..195197f 100644 --- a/Magic.IndexedDb/Models/MagicContractResolver.cs +++ b/Magic.IndexedDb/Models/MagicContractResolver.cs @@ -10,472 +10,471 @@ using Magic.IndexedDb.SchemaAnnotations; using Microsoft.Extensions.Options; -namespace Magic.IndexedDb.Models +namespace Magic.IndexedDb.Models; + +internal class MagicContractResolver : JsonConverter { - internal class MagicContractResolver : JsonConverter + public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + // ✅ Return default(T) if null is encountered + if (reader.TokenType == JsonTokenType.Null) { - // ✅ Return default(T) if null is encountered - if (reader.TokenType == JsonTokenType.Null) - { - return default; - } - - // ✅ Handle primitive types before assuming it's complex - if (PropertyMappingCache.IsSimpleType(typeToConvert)) - { - return (T?)ReadSimpleType(ref reader, typeToConvert); - } - - // ✅ Explicitly check if the type is `JsonElement` - if (typeToConvert == typeof(JsonElement)) - { - JsonElement element = JsonSerializer.Deserialize(ref reader); // Extract JsonElement - - // ✅ Re-run null check for JsonElement - if (IsSimpleJsonNull(element)) - return default; + return default; + } - // ✅ Re-run primitive check for JsonElement - if (IsSimpleJsonElement(element)) - return (T?)(object)element; // 🚀 Directly cast JsonElement to T - } + // ✅ Handle primitive types before assuming it's complex + if (PropertyMappingCache.IsSimpleType(typeToConvert)) + { + return (T?)ReadSimpleType(ref reader, typeToConvert); + } - // ✅ Handle root-level arrays correctly - if (reader.TokenType == JsonTokenType.StartArray) - { - if (!typeof(IEnumerable).IsAssignableFrom(typeToConvert)) - throw new JsonException($"Expected an object but got an array for type {typeToConvert.Name}."); + // ✅ Explicitly check if the type is `JsonElement` + if (typeToConvert == typeof(JsonElement)) + { + JsonElement element = JsonSerializer.Deserialize(ref reader); // Extract JsonElement - return (T?)ReadIEnumerable(ref reader, typeToConvert, options); - } + // ✅ Re-run null check for JsonElement + if (IsSimpleJsonNull(element)) + return default; - // ✅ If it's neither a primitive nor an array, assume it's a complex object - if (reader.TokenType == JsonTokenType.StartObject) - { - return (T?)ReadComplexObject(ref reader, typeToConvert, options); - } + // ✅ Re-run primitive check for JsonElement + if (IsSimpleJsonElement(element)) + return (T?)(object)element; // 🚀 Directly cast JsonElement to T + } - // ✅ Return default(T) if EndArray is encountered - if (reader.TokenType == JsonTokenType.EndArray) - { - return default; - } + // ✅ Handle root-level arrays correctly + if (reader.TokenType == JsonTokenType.StartArray) + { + if (!typeof(IEnumerable).IsAssignableFrom(typeToConvert)) + throw new JsonException($"Expected an object but got an array for type {typeToConvert.Name}."); - throw new JsonException($"Unexpected JSON token: {reader.TokenType} when deserializing {typeToConvert.Name}."); + return (T?)ReadIEnumerable(ref reader, typeToConvert, options); } - private object CreateObjectFromDictionary(Type type, Dictionary propertyValues, SearchPropEntry search) + // ✅ If it's neither a primitive nor an array, assume it's a complex object + if (reader.TokenType == JsonTokenType.StartObject) { - // 🚀 If there's a constructor with parameters, use it - if (search.ConstructorParameterMappings.Count > 0) - { - var constructorArgs = new object?[search.ConstructorParameterMappings.Count]; + return (T?)ReadComplexObject(ref reader, typeToConvert, options); + } - foreach (var (paramName, index) in search.ConstructorParameterMappings) - { - if (propertyValues.TryGetValue(paramName, out var value)) - constructorArgs[index] = value; - else - constructorArgs[index] = GetDefaultValue(type.GetProperty(paramName)?.PropertyType ?? typeof(object)); - } + // ✅ Return default(T) if EndArray is encountered + if (reader.TokenType == JsonTokenType.EndArray) + { + return default; + } - return search.InstanceCreator(constructorArgs) ?? throw new InvalidOperationException($"Failed to create instance of type {type.Name}."); - } + throw new JsonException($"Unexpected JSON token: {reader.TokenType} when deserializing {typeToConvert.Name}."); + } - // 🚀 Use parameterless constructor - var obj = search.InstanceCreator(Array.Empty()) ?? throw new InvalidOperationException($"Failed to create instance of type {type.Name}."); + private object CreateObjectFromDictionary(Type type, Dictionary propertyValues, SearchPropEntry search) + { + // 🚀 If there's a constructor with parameters, use it + if (search.ConstructorParameterMappings.Count > 0) + { + var constructorArgs = new object?[search.ConstructorParameterMappings.Count]; - // 🚀 Assign property values - foreach (var (propName, value) in propertyValues) + foreach (var (paramName, index) in search.ConstructorParameterMappings) { - if (search.propertyEntries.TryGetValue(propName, out var propEntry)) - { - propEntry.Setter(obj, value); - } + if (propertyValues.TryGetValue(paramName, out var value)) + constructorArgs[index] = value; + else + constructorArgs[index] = GetDefaultValue(type.GetProperty(paramName)?.PropertyType ?? typeof(object)); } - return obj; + return search.InstanceCreator(constructorArgs) ?? throw new InvalidOperationException($"Failed to create instance of type {type.Name}."); } - private bool IsSimpleJsonElement(JsonElement element) - { - return element.ValueKind == JsonValueKind.String || - element.ValueKind == JsonValueKind.Number || - element.ValueKind == JsonValueKind.True || - element.ValueKind == JsonValueKind.False; - } - private bool IsSimpleJsonNull(JsonElement element) + // 🚀 Use parameterless constructor + var obj = search.InstanceCreator(Array.Empty()) ?? throw new InvalidOperationException($"Failed to create instance of type {type.Name}."); + + // 🚀 Assign property values + foreach (var (propName, value) in propertyValues) { - return element.ValueKind == JsonValueKind.Null; + if (search.propertyEntries.TryGetValue(propName, out var propEntry)) + { + propEntry.Setter(obj, value); + } } - /// - /// Recursively reads a complex object while correctly mapping JSON properties to C# properties. - /// - private object? ReadComplexObject(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.Null) - return null; + return obj; + } + + private bool IsSimpleJsonElement(JsonElement element) + { + return element.ValueKind == JsonValueKind.String || + element.ValueKind == JsonValueKind.Number || + element.ValueKind == JsonValueKind.True || + element.ValueKind == JsonValueKind.False; + } + private bool IsSimpleJsonNull(JsonElement element) + { + return element.ValueKind == JsonValueKind.Null; + } + + /// + /// Recursively reads a complex object while correctly mapping JSON properties to C# properties. + /// + private object? ReadComplexObject(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + return null; - if (reader.TokenType != JsonTokenType.StartObject) - throw new JsonException($"Expected StartObject token for type {type.Name}."); + if (reader.TokenType != JsonTokenType.StartObject) + throw new JsonException($"Expected StartObject token for type {type.Name}."); - // 🔥 Step 1: Create a dictionary to store extracted values - var propertyValues = new Dictionary(); + // 🔥 Step 1: Create a dictionary to store extracted values + var propertyValues = new Dictionary(); - var properties = PropertyMappingCache.GetTypeOfTProperties(type); - while (reader.Read()) + var properties = PropertyMappingCache.GetTypeOfTProperties(type); + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) { - if (reader.TokenType == JsonTokenType.EndObject) - { - // 🔥 Step 3: Convert the dictionary into the final object - var result = CreateObjectFromDictionary(type, propertyValues, properties); - return result; - } + // 🔥 Step 3: Convert the dictionary into the final object + var result = CreateObjectFromDictionary(type, propertyValues, properties); + return result; + } - if (reader.TokenType != JsonTokenType.PropertyName) - throw new JsonException("Expected PropertyName token."); + if (reader.TokenType != JsonTokenType.PropertyName) + throw new JsonException("Expected PropertyName token."); - string jsonPropertyName = reader.GetString()!; - if (!reader.Read()) - throw new JsonException("Unexpected end of JSON."); + string jsonPropertyName = reader.GetString()!; + if (!reader.Read()) + throw new JsonException("Unexpected end of JSON."); - string csharpPropertyName = properties.GetCsharpPropertyName(jsonPropertyName); + string csharpPropertyName = properties.GetCsharpPropertyName(jsonPropertyName); - //string csharpPropertyName = PropertyMappingCache.GetCsharpPropertyName(jsonPropertyName, type); + //string csharpPropertyName = PropertyMappingCache.GetCsharpPropertyName(jsonPropertyName, type); - if (properties.propertyEntries.TryGetValue(csharpPropertyName, out var mpe)) + if (properties.propertyEntries.TryGetValue(csharpPropertyName, out var mpe)) + { + if (mpe.NotMapped) { - if (mpe.NotMapped) - { - reader.Skip(); - continue; - } + reader.Skip(); + continue; + } - // Skip interface-based properties that should never be assigned - if (mpe.Property.DeclaringType?.IsInterface == true || !mpe.Property.CanWrite) - { - reader.Skip(); - continue; - } + // Skip interface-based properties that should never be assigned + if (mpe.Property.DeclaringType?.IsInterface == true || !mpe.Property.CanWrite) + { + reader.Skip(); + continue; + } - try - { - // Extract values and store them in the dictionary - object? value = ReadPropertyValue(ref reader, mpe, options); - propertyValues[csharpPropertyName] = value; - } - catch - { - // do nothing - } + try + { + // Extract values and store them in the dictionary + object? value = ReadPropertyValue(ref reader, mpe, options); + propertyValues[csharpPropertyName] = value; } - else + catch { - reader.Skip(); + // do nothing } } - - throw new JsonException("Unexpected end of JSON while reading an object."); + else + { + reader.Skip(); + } } - private static readonly ConcurrentDictionary _defaultValues = new(); - - public static object? GetDefaultValue(Type type) - { - return _defaultValues.GetOrAdd(type, t => t.IsValueType ? Activator.CreateInstance(t) : null); - } + throw new JsonException("Unexpected end of JSON while reading an object."); + } + private static readonly ConcurrentDictionary _defaultValues = new(); + public static object? GetDefaultValue(Type type) + { + return _defaultValues.GetOrAdd(type, t => t.IsValueType ? Activator.CreateInstance(t) : null); + } - /// - /// Reads and assigns a property value, detecting collections, simple types, and complex objects. - /// - private object? ReadPropertyValue(ref Utf8JsonReader reader, MagicPropertyEntry mpe, JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.Null) - return null; - Type propertyType = mpe.Property.PropertyType; - if (typeof(IEnumerable).IsAssignableFrom(propertyType) && propertyType != typeof(string)) - { - return ReadIEnumerable(ref reader, propertyType, options); - } + /// + /// Reads and assigns a property value, detecting collections, simple types, and complex objects. + /// + private object? ReadPropertyValue(ref Utf8JsonReader reader, MagicPropertyEntry mpe, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + return null; - if (mpe.IsComplexType) - { - return ReadComplexObject(ref reader, propertyType, options); - } + Type propertyType = mpe.Property.PropertyType; - return ReadSimpleType(ref reader, propertyType); + if (typeof(IEnumerable).IsAssignableFrom(propertyType) && propertyType != typeof(string)) + { + return ReadIEnumerable(ref reader, propertyType, options); } - /// - /// Reads primitive and simple types efficiently. - /// - private object? ReadSimpleType(ref Utf8JsonReader reader, Type type) + if (mpe.IsComplexType) { - if (reader.TokenType == JsonTokenType.Null) - return null; - - return JsonSerializer.Deserialize(ref reader, type); + return ReadComplexObject(ref reader, propertyType, options); } - /// - /// Reads a collection (List, Array, HashSet, etc.), keeping its structure intact. - /// - private object? ReadIEnumerable(ref Utf8JsonReader reader, Type collectionType, JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.Null) - return null; + return ReadSimpleType(ref reader, propertyType); + } - if (reader.TokenType != JsonTokenType.StartArray) - throw new JsonException($"Expected StartArray token but got {reader.TokenType}."); + /// + /// Reads primitive and simple types efficiently. + /// + private object? ReadSimpleType(ref Utf8JsonReader reader, Type type) + { + if (reader.TokenType == JsonTokenType.Null) + return null; - // Determine the item type of the collection - Type itemType = collectionType.IsArray - ? collectionType.GetElementType()! - : collectionType.GenericTypeArguments.FirstOrDefault() ?? typeof(object); + return JsonSerializer.Deserialize(ref reader, type); + } - var listType = typeof(List<>).MakeGenericType(itemType); - var list = (IList)Activator.CreateInstance(listType)!; + /// + /// Reads a collection (List, Array, HashSet, etc.), keeping its structure intact. + /// + private object? ReadIEnumerable(ref Utf8JsonReader reader, Type collectionType, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + return null; - while (reader.Read()) - { - if (reader.TokenType == JsonTokenType.EndArray) - break; + if (reader.TokenType != JsonTokenType.StartArray) + throw new JsonException($"Expected StartArray token but got {reader.TokenType}."); - object? item; + // Determine the item type of the collection + Type itemType = collectionType.IsArray + ? collectionType.GetElementType()! + : collectionType.GenericTypeArguments.FirstOrDefault() ?? typeof(object); - // 🔥 If it's a complex type, we need to deserialize it recursively - if (PropertyMappingCache.IsComplexType(itemType)) - { - item = ReadComplexObject(ref reader, itemType, options); - } - else - { - item = ReadSimpleType(ref reader, itemType); - } + var listType = typeof(List<>).MakeGenericType(itemType); + var list = (IList)Activator.CreateInstance(listType)!; - list.Add(item); - } + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) + break; - // Convert to array if original type was an array - if (collectionType.IsArray) + object? item; + + // 🔥 If it's a complex type, we need to deserialize it recursively + if (PropertyMappingCache.IsComplexType(itemType)) + { + item = ReadComplexObject(ref reader, itemType, options); + } + else { - var array = Array.CreateInstance(itemType, list.Count); - list.CopyTo(array, 0); - return array; + item = ReadSimpleType(ref reader, itemType); } - return list; + list.Add(item); } - public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + // Convert to array if original type was an array + if (collectionType.IsArray) { - if (value == null) - { - writer.WriteNullValue(); - return; - } - - var type = typeof(T); + var array = Array.CreateInstance(itemType, list.Count); + list.CopyTo(array, 0); + return array; + } - // Handle collections - if (SerializeIEnumerable(writer, value, options)) - { - return; - } + return list; + } - if (SerializeSimple(writer, value)) - { - return; - } + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + if (value == null) + { + writer.WriteNullValue(); + return; + } + var type = typeof(T); - var properties = PropertyMappingCache.GetTypeOfTProperties(type); + // Handle collections + if (SerializeIEnumerable(writer, value, options)) + { + return; + } - // 🔥 Handle complex objects recursively - writer.WriteStartObject(); - SerializeComplexProperties(writer, value, properties.propertyEntries, options); - writer.WriteEndObject(); + if (SerializeSimple(writer, value)) + { + return; } - /// - /// 🔥 Serializes primitive & simple types - /// - private bool SerializeSimple(Utf8JsonWriter writer, object value) - { - if (value == null) - { - writer.WriteNullValue(); - return true; - } + var properties = PropertyMappingCache.GetTypeOfTProperties(type); - var type = value.GetType(); + // 🔥 Handle complex objects recursively + writer.WriteStartObject(); + SerializeComplexProperties(writer, value, properties.propertyEntries, options); + writer.WriteEndObject(); + } - // Handle simple or primitive types directly - if (type == typeof(string) || PropertyMappingCache.IsSimpleType(type)) - { - WriteSimpleType(writer, value); - return true; - } - return false; + /// + /// 🔥 Serializes primitive & simple types + /// + private bool SerializeSimple(Utf8JsonWriter writer, object value) + { + if (value == null) + { + writer.WriteNullValue(); + return true; } - /// - /// 🔥 Serializes lists (IEnumerable) - /// - private bool SerializeIEnumerable(Utf8JsonWriter writer, object? value, JsonSerializerOptions options) + var type = value.GetType(); + + // Handle simple or primitive types directly + if (type == typeof(string) || PropertyMappingCache.IsSimpleType(type)) { - if (value is null) - { - writer.WriteNullValue(); - return true; - } + WriteSimpleType(writer, value); + return true; + } - var type = value.GetType(); + return false; + } - if (value is IEnumerable enumerable && type != typeof(string)) + /// + /// 🔥 Serializes lists (IEnumerable) + /// + private bool SerializeIEnumerable(Utf8JsonWriter writer, object? value, JsonSerializerOptions options) + { + if (value is null) + { + writer.WriteNullValue(); + return true; + } + + var type = value.GetType(); + + if (value is IEnumerable enumerable && type != typeof(string)) + { + writer.WriteStartArray(); + foreach (var item in enumerable) { - writer.WriteStartArray(); - foreach (var item in enumerable) + if (SerializeSimple(writer, item)) { - if (SerializeSimple(writer, item)) - { - continue; - } + continue; + } - if (item != null) + if (item != null) + { + Type itemType = item.GetType(); + if (PropertyMappingCache.IsComplexType(itemType)) { - Type itemType = item.GetType(); - if (PropertyMappingCache.IsComplexType(itemType)) - { - var nestedProperties = PropertyMappingCache.GetTypeOfTProperties(itemType); - writer.WriteStartObject(); - SerializeComplexProperties(writer, item, nestedProperties.propertyEntries, options); - writer.WriteEndObject(); - } - else - { - WriteSimpleType(writer, item); - } + var nestedProperties = PropertyMappingCache.GetTypeOfTProperties(itemType); + writer.WriteStartObject(); + SerializeComplexProperties(writer, item, nestedProperties.propertyEntries, options); + writer.WriteEndObject(); } else { - writer.WriteNullValue(); + WriteSimpleType(writer, item); } } - writer.WriteEndArray(); - return true; + else + { + writer.WriteNullValue(); + } } - - return false; + writer.WriteEndArray(); + return true; } - private void WriteSimpleType(Utf8JsonWriter writer, object value) + return false; + } + + private void WriteSimpleType(Utf8JsonWriter writer, object value) + { + switch (value) { - switch (value) - { - case string str: - writer.WriteStringValue(str); - break; - case bool b: - writer.WriteBooleanValue(b); - break; - case int i: - writer.WriteNumberValue(i); - break; - case long l: - writer.WriteNumberValue(l); - break; - case double d: - writer.WriteNumberValue(d); - break; - case decimal dec: - writer.WriteNumberValue(dec); - break; - case DateTime dt: - writer.WriteStringValue(dt.ToString("o")); // ISO format - break; - case Guid guid: - writer.WriteStringValue(guid.ToString()); - break; - default: - JsonSerializer.Serialize(writer, value); - break; - } + case string str: + writer.WriteStringValue(str); + break; + case bool b: + writer.WriteBooleanValue(b); + break; + case int i: + writer.WriteNumberValue(i); + break; + case long l: + writer.WriteNumberValue(l); + break; + case double d: + writer.WriteNumberValue(d); + break; + case decimal dec: + writer.WriteNumberValue(dec); + break; + case DateTime dt: + writer.WriteStringValue(dt.ToString("o")); // ISO format + break; + case Guid guid: + writer.WriteStringValue(guid.ToString()); + break; + default: + JsonSerializer.Serialize(writer, value); + break; } + } - /// - /// 🔥 Serializes complex objects recursively - /// - private void SerializeComplexProperties(Utf8JsonWriter writer, object value, Dictionary properties, JsonSerializerOptions options) - { - var type = value.GetType(); - var cache = PropertyMappingCache.GetTypeOfTProperties(type); + /// + /// 🔥 Serializes complex objects recursively + /// + private void SerializeComplexProperties(Utf8JsonWriter writer, object value, Dictionary properties, JsonSerializerOptions options) + { + var type = value.GetType(); + var cache = PropertyMappingCache.GetTypeOfTProperties(type); - foreach (var (propertyName, mpe) in properties) - { - if (mpe.NotMapped) - continue; + foreach (var (propertyName, mpe) in properties) + { + if (mpe.NotMapped) + continue; - // 💡 Handle constructor-only properties by using reflection - object? propValue = null; + // 💡 Handle constructor-only properties by using reflection + object? propValue = null; - try - { - propValue = mpe.Getter(value); - } - catch - { - // If it's a constructor-only param with no backing field or getter, ignore - continue; - } + try + { + propValue = mpe.Getter(value); + } + catch + { + // If it's a constructor-only param with no backing field or getter, ignore + continue; + } - // Skip default primary key if needed - if (mpe.PrimaryKey && IsDefaultValue(propValue, mpe)) - continue; + // Skip default primary key if needed + if (mpe.PrimaryKey && IsDefaultValue(propValue, mpe)) + continue; - // Figure out the actual output property name - string finalPropertyName = mpe.NeverCamelCase - ? mpe.JsPropertyName - : (options.PropertyNamingPolicy == JsonNamingPolicy.CamelCase - ? char.ToLowerInvariant(mpe.JsPropertyName[0]) + mpe.JsPropertyName.Substring(1) - : mpe.JsPropertyName); + // Figure out the actual output property name + string finalPropertyName = mpe.NeverCamelCase + ? mpe.JsPropertyName + : (options.PropertyNamingPolicy == JsonNamingPolicy.CamelCase + ? char.ToLowerInvariant(mpe.JsPropertyName[0]) + mpe.JsPropertyName.Substring(1) + : mpe.JsPropertyName); - writer.WritePropertyName(finalPropertyName); + writer.WritePropertyName(finalPropertyName); - // Handle primitives/collections - if (SerializeIEnumerable(writer, propValue, options) || SerializeSimple(writer, propValue)) - continue; + // Handle primitives/collections + if (SerializeIEnumerable(writer, propValue, options) || SerializeSimple(writer, propValue)) + continue; - // Handle complex types - if (propValue != null && mpe.IsComplexType) - { - var nestedProps = PropertyMappingCache.GetTypeOfTProperties(propValue.GetType()); - writer.WriteStartObject(); - SerializeComplexProperties(writer, propValue, nestedProps.propertyEntries, options); - writer.WriteEndObject(); - } + // Handle complex types + if (propValue != null && mpe.IsComplexType) + { + var nestedProps = PropertyMappingCache.GetTypeOfTProperties(propValue.GetType()); + writer.WriteStartObject(); + SerializeComplexProperties(writer, propValue, nestedProps.propertyEntries, options); + writer.WriteEndObject(); } } + } - private bool IsDefaultValue(object? value, MagicPropertyEntry mpe) - { - if (value == null) - return true; - - return value.Equals(mpe.DefaultValue); // ✅ Use precomputed default value - } + private bool IsDefaultValue(object? value, MagicPropertyEntry mpe) + { + if (value == null) + return true; + return value.Equals(mpe.DefaultValue); // ✅ Use precomputed default value } -} + +} \ No newline at end of file diff --git a/Magic.IndexedDb/Models/MagicException.cs b/Magic.IndexedDb/Models/MagicException.cs index c6bbdd2..ae24b4c 100644 --- a/Magic.IndexedDb/Models/MagicException.cs +++ b/Magic.IndexedDb/Models/MagicException.cs @@ -1,9 +1,7 @@ -namespace Magic.IndexedDb.Models -{ +namespace Magic.IndexedDb.Models; - [Serializable] - public class MagicException : Exception - { - public MagicException(string message, Exception? inner = null) : base(message, inner) { } - } +[Serializable] +public class MagicException : Exception +{ + public MagicException(string message, Exception? inner = null) : base(message, inner) { } } \ No newline at end of file diff --git a/Magic.IndexedDb/Models/MagicJsPackage.cs b/Magic.IndexedDb/Models/MagicJsPackage.cs index 1bc0b20..8f9cb53 100644 --- a/Magic.IndexedDb/Models/MagicJsPackage.cs +++ b/Magic.IndexedDb/Models/MagicJsPackage.cs @@ -4,16 +4,15 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Models +namespace Magic.IndexedDb.Models; + +internal class MagicJsPackage { - internal class MagicJsPackage - { - public bool YieldResults { get; set; } = false; - public string ModulePath { get; set; } - public string MethodName { get; set; } - public string?[]? Parameters { get; set; } - public bool IsVoid { get; set; } = false; + public bool YieldResults { get; set; } = false; + public string ModulePath { get; set; } + public string MethodName { get; set; } + public string?[]? Parameters { get; set; } + public bool IsVoid { get; set; } = false; - public bool IsDebug { get; set; } = false; - } -} + public bool IsDebug { get; set; } = false; +} \ No newline at end of file diff --git a/Magic.IndexedDb/Models/MagicJsonSerializationSettings.cs b/Magic.IndexedDb/Models/MagicJsonSerializationSettings.cs index c50f418..8abb46d 100644 --- a/Magic.IndexedDb/Models/MagicJsonSerializationSettings.cs +++ b/Magic.IndexedDb/Models/MagicJsonSerializationSettings.cs @@ -1,37 +1,36 @@ using System.Text.Json; -namespace Magic.IndexedDb.Models +namespace Magic.IndexedDb.Models; + +public class MagicJsonSerializationSettings { - public class MagicJsonSerializationSettings - { - private JsonSerializerOptions _options = new(); + private JsonSerializerOptions _options = new(); - public JsonSerializerOptions Options - { - get => _options; - set => _options = value ?? new JsonSerializerOptions(); // Ensure it's never null - } + public JsonSerializerOptions Options + { + get => _options; + set => _options = value ?? new JsonSerializerOptions(); // Ensure it's never null + } - public bool UseCamelCase + public bool UseCamelCase + { + get => _options.PropertyNamingPolicy == JsonNamingPolicy.CamelCase; + set { - get => _options.PropertyNamingPolicy == JsonNamingPolicy.CamelCase; - set + _options = new JsonSerializerOptions(_options) // Clone existing settings { - _options = new JsonSerializerOptions(_options) // Clone existing settings - { - PropertyNamingPolicy = value ? JsonNamingPolicy.CamelCase : null - }; - } + PropertyNamingPolicy = value ? JsonNamingPolicy.CamelCase : null + }; } + } - /// - /// Ensures the MagicContractResolver is applied for a specific type at runtime. - /// - public JsonSerializerOptions GetOptionsWithResolver() - { - var newOptions = new JsonSerializerOptions(Options); // Clone settings - newOptions.Converters.Add(new MagicContractResolver()); // Ensure the correct resolver is added - return newOptions; - } + /// + /// Ensures the MagicContractResolver is applied for a specific type at runtime. + /// + public JsonSerializerOptions GetOptionsWithResolver() + { + var newOptions = new JsonSerializerOptions(Options); // Clone settings + newOptions.Converters.Add(new MagicContractResolver()); // Ensure the correct resolver is added + return newOptions; } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/Models/PredicateVisitor.cs b/Magic.IndexedDb/Models/PredicateVisitor.cs index 2588138..b6df0a4 100644 --- a/Magic.IndexedDb/Models/PredicateVisitor.cs +++ b/Magic.IndexedDb/Models/PredicateVisitor.cs @@ -6,95 +6,90 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Models +namespace Magic.IndexedDb.Models; + +public class PredicateVisitor : ExpressionVisitor { - public class PredicateVisitor : ExpressionVisitor + protected override Expression VisitMethodCall(MethodCallExpression node) { - protected override Expression VisitMethodCall(MethodCallExpression node) + if (node.Method.Name == "Any" && node.Arguments[0] is MemberExpression member) { - if (node.Method.Name == "Any" && node.Arguments[0] is MemberExpression member) - { - // Handle Any expressions - var lambda = GetLambdaExpression(node.Arguments[1]); - var values = GetIEnumerableItems(member); - return values.Select(value => ReplaceParameter(lambda, value)).Aggregate((left, right) => Expression.OrElse(left, right)); - } - else if (node.Method.Name == "All" && node.Arguments[0] is MemberExpression member3) - { - // Handle All expressions - var lambda = GetLambdaExpression(node.Arguments[1]); - var values = GetIEnumerableItems(member3); - return values.Select(value => ReplaceParameter(lambda, value)).Aggregate((left, right) => Expression.AndAlso(left, right)); - } - else - { - return base.VisitMethodCall(node); - } + // Handle Any expressions + var lambda = GetLambdaExpression(node.Arguments[1]); + var values = GetIEnumerableItems(member); + return values.Select(value => ReplaceParameter(lambda, value)).Aggregate((left, right) => Expression.OrElse(left, right)); } - - private LambdaExpression GetLambdaExpression(Expression expression) + else if (node.Method.Name == "All" && node.Arguments[0] is MemberExpression member3) { - if (expression is UnaryExpression unaryExpression) - { - if (unaryExpression.Operand is LambdaExpression lambdaExpression) - { - return lambdaExpression; - } - } - else if (expression is LambdaExpression lambda) - { - return lambda; - } - - throw new InvalidOperationException("Invalid expression type."); + // Handle All expressions + var lambda = GetLambdaExpression(node.Arguments[1]); + var values = GetIEnumerableItems(member3); + return values.Select(value => ReplaceParameter(lambda, value)).Aggregate((left, right) => Expression.AndAlso(left, right)); } - - private IEnumerable GetIEnumerableItems(MemberExpression member) + else { - var compiledMember = Expression.Lambda>(member).Compile(); - var enumerable = compiledMember(); - return enumerable.OfType(); + return base.VisitMethodCall(node); } + } - private Expression ReplaceParameter(LambdaExpression lambda, object value) + private LambdaExpression GetLambdaExpression(Expression expression) + { + if (expression is UnaryExpression unaryExpression) { - var parameter = lambda.Parameters.FirstOrDefault(); - if (parameter != null) - { - var constant = Expression.Constant(value, parameter.Type); - var body = new ParameterReplacer(parameter, constant).Visit(lambda.Body); - return body; - } - else + if (unaryExpression.Operand is LambdaExpression lambdaExpression) { - return Expression.Empty(); + return lambdaExpression; } } - - private class ParameterReplacer : ExpressionVisitor + else if (expression is LambdaExpression lambda) { - private readonly ParameterExpression _parameter; - private readonly Expression _replacement; + return lambda; + } - public ParameterReplacer(ParameterExpression parameter, Expression replacement) - { - _parameter = parameter; - _replacement = replacement; - } + throw new InvalidOperationException("Invalid expression type."); + } - protected override Expression VisitParameter(ParameterExpression node) - { - if (node == _parameter) - { - return _replacement; - } + private IEnumerable GetIEnumerableItems(MemberExpression member) + { + var compiledMember = Expression.Lambda>(member).Compile(); + var enumerable = compiledMember(); + return enumerable.OfType(); + } - return base.VisitParameter(node); - } + private Expression ReplaceParameter(LambdaExpression lambda, object value) + { + var parameter = lambda.Parameters.FirstOrDefault(); + if (parameter != null) + { + var constant = Expression.Constant(value, parameter.Type); + var body = new ParameterReplacer(parameter, constant).Visit(lambda.Body); + return body; + } + else + { + return Expression.Empty(); } } + private class ParameterReplacer : ExpressionVisitor + { + private readonly ParameterExpression _parameter; + private readonly Expression _replacement; + public ParameterReplacer(ParameterExpression parameter, Expression replacement) + { + _parameter = parameter; + _replacement = replacement; + } + protected override Expression VisitParameter(ParameterExpression node) + { + if (node == _parameter) + { + return _replacement; + } -} + return base.VisitParameter(node); + } + } +} \ No newline at end of file diff --git a/Magic.IndexedDb/Models/StoreRecord.cs b/Magic.IndexedDb/Models/StoreRecord.cs index 8586456..d7028cf 100644 --- a/Magic.IndexedDb/Models/StoreRecord.cs +++ b/Magic.IndexedDb/Models/StoreRecord.cs @@ -1,9 +1,8 @@ -namespace Magic.IndexedDb -{ - public class StoreRecord - { - public string? DbName { get; set; } - public string? StoreName { get; set; } - public T? Record { get; set; } - } +namespace Magic.IndexedDb; + +public class StoreRecord +{ + public string? DbName { get; set; } + public string? StoreName { get; set; } + public T? Record { get; set; } } \ No newline at end of file diff --git a/Magic.IndexedDb/Models/StoreSchema.cs b/Magic.IndexedDb/Models/StoreSchema.cs index 5d7ad5e..148357e 100644 --- a/Magic.IndexedDb/Models/StoreSchema.cs +++ b/Magic.IndexedDb/Models/StoreSchema.cs @@ -1,26 +1,25 @@ -namespace Magic.IndexedDb -{ - public class StoreSchema - { - public string TableName { get; set; } +namespace Magic.IndexedDb; - public int Version { get; set; } +public class StoreSchema +{ + public string TableName { get; set; } - /// - /// will the primary key automatically increment? - /// - public bool PrimaryKeyAuto { get; set; } + public int Version { get; set; } - /// - /// IndexDB column names that will be provided a unique index - /// - public List UniqueIndexes { get; set; } = new List(); + /// + /// will the primary key automatically increment? + /// + public bool PrimaryKeyAuto { get; set; } - /// - /// IndexDB column names that will be automatically indexed. - /// - public List Indexes { get; set; } = new List(); - public List> ColumnNamesInCompoundIndex { get; set; } = new List>(); - public List ColumnNamesInCompoundKey { get; set; } = new List(); - } -} + /// + /// IndexDB column names that will be provided a unique index + /// + public List UniqueIndexes { get; set; } = new List(); + + /// + /// IndexDB column names that will be automatically indexed. + /// + public List Indexes { get; set; } = new List(); + public List> ColumnNamesInCompoundIndex { get; set; } = new List>(); + public List ColumnNamesInCompoundKey { get; set; } = new List(); +} \ No newline at end of file diff --git a/Magic.IndexedDb/Models/StoredMagicQuery.cs b/Magic.IndexedDb/Models/StoredMagicQuery.cs index 545fa59..9d0ba5f 100644 --- a/Magic.IndexedDb/Models/StoredMagicQuery.cs +++ b/Magic.IndexedDb/Models/StoredMagicQuery.cs @@ -4,37 +4,36 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Models +namespace Magic.IndexedDb.Models; + +internal struct MagicQueryFunctions { - internal struct MagicQueryFunctions - { - public const string Take = "take"; - public const string Take_Last = "takeLast"; - public const string Skip = "skip"; - public const string Order_By = "orderBy"; - public const string Order_By_Descending = "orderByDescending"; - public const string Reverse = "reverse"; - public const string First = "first"; - public const string Last = "last"; + public const string Take = "take"; + public const string Take_Last = "takeLast"; + public const string Skip = "skip"; + public const string Order_By = "orderBy"; + public const string Order_By_Descending = "orderByDescending"; + public const string Reverse = "reverse"; + public const string First = "first"; + public const string Last = "last"; - } - public class StoredMagicQuery - { - /// - /// additionFunction string name - /// - public string? additionFunction { get; set; } +} +public class StoredMagicQuery +{ + /// + /// additionFunction string name + /// + public string? additionFunction { get; set; } - /// - /// The int value for take, skip, and take last - /// - public int intValue { get; set; } = 0; + /// + /// The int value for take, skip, and take last + /// + public int intValue { get; set; } = 0; - /// - /// The property we're targetting - /// - public string? property { get; set; } - } -} + /// + /// The property we're targetting + /// + public string? property { get; set; } +} \ No newline at end of file diff --git a/Magic.IndexedDb/Models/Structs/MagicAttributesEntry.cs b/Magic.IndexedDb/Models/Structs/MagicAttributesEntry.cs index dd4040c..9e1a5d9 100644 --- a/Magic.IndexedDb/Models/Structs/MagicAttributesEntry.cs +++ b/Magic.IndexedDb/Models/Structs/MagicAttributesEntry.cs @@ -4,39 +4,38 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Models.Structs +namespace Magic.IndexedDb.Models.Structs; + +public struct MagicAttributesEntry { - public struct MagicAttributesEntry + /// + /// Constructor for initializing MagicPropertyEntry while reducing memory footprint. + /// + public MagicAttributesEntry(bool indexed, bool uniqueIndex, bool primaryKey, bool notMapped) { - /// - /// Constructor for initializing MagicPropertyEntry while reducing memory footprint. - /// - public MagicAttributesEntry(bool indexed, bool uniqueIndex, bool primaryKey, bool notMapped) - { - Indexed = indexed; - UniqueIndex = uniqueIndex; - PrimaryKey = primaryKey; - NotMapped = notMapped; - } + Indexed = indexed; + UniqueIndex = uniqueIndex; + PrimaryKey = primaryKey; + NotMapped = notMapped; + } - /// - /// Property with the, "MagicIndexAttribute" attribute appended - /// - public bool Indexed { get; set; } + /// + /// Property with the, "MagicIndexAttribute" attribute appended + /// + public bool Indexed { get; set; } - /// - /// Property with the, "MagicUniqueIndexAttribute" attribute appended - /// - public bool UniqueIndex { get; set; } + /// + /// Property with the, "MagicUniqueIndexAttribute" attribute appended + /// + public bool UniqueIndex { get; set; } - /// - /// Property with the, "MagicPrimaryKeyAttribute" attribute appended - /// - public bool PrimaryKey { get; set; } + /// + /// Property with the, "MagicPrimaryKeyAttribute" attribute appended + /// + public bool PrimaryKey { get; set; } - /// - /// Property with the, "MagicNotMappedAttribute" attribute appended - /// - public bool NotMapped { get; set; } - } -} + /// + /// Property with the, "MagicNotMappedAttribute" attribute appended + /// + public bool NotMapped { get; set; } +} \ No newline at end of file diff --git a/Magic.IndexedDb/Models/Structs/MagicPropertyEntry.cs b/Magic.IndexedDb/Models/Structs/MagicPropertyEntry.cs index cb3638d..3a7b8f2 100644 --- a/Magic.IndexedDb/Models/Structs/MagicPropertyEntry.cs +++ b/Magic.IndexedDb/Models/Structs/MagicPropertyEntry.cs @@ -10,156 +10,155 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Models +namespace Magic.IndexedDb.Models; + +public struct MagicPropertyEntry { - public struct MagicPropertyEntry + /// + /// Constructor for initializing MagicPropertyEntry while reducing memory footprint. + /// + public MagicPropertyEntry(PropertyInfo property, IColumnNamed? columnNamedAttribute, + bool indexed, bool uniqueIndex, bool primaryKey, bool notMapped, + bool overrideNeverCamel = false) { - /// - /// Constructor for initializing MagicPropertyEntry while reducing memory footprint. - /// - public MagicPropertyEntry(PropertyInfo property, IColumnNamed? columnNamedAttribute, - bool indexed, bool uniqueIndex, bool primaryKey, bool notMapped, - bool overrideNeverCamel = false) - { - Property = property; - _columnNamedAttribute = columnNamedAttribute; - Indexed = indexed; - UniqueIndex = uniqueIndex; - PrimaryKey = primaryKey; - NotMapped = notMapped; - OverrideNeverCamel = overrideNeverCamel; - - IsComplexType = PropertyMappingCache.IsComplexType(property.PropertyType); - - // 🔥 Identify if this property is a constructor parameter - var declaringType = property.DeclaringType!; - var constructor = declaringType.GetConstructors().FirstOrDefault(); - - // 🔥 Precompute and cache the default value - DefaultValue = property.PropertyType.IsValueType ? Activator.CreateInstance(property.PropertyType) : null; - - // 🔥 Create delegates for performance 🔥 - if (property.CanRead) - { - Getter = CreateGetter(property); - } - else - { - Getter = _ => null; // No getter available - } - - if (property.CanWrite) - { - Setter = CreateSetter(property); - } - else - { - Setter = (_, __) => { }; // No setter available - } - } + Property = property; + _columnNamedAttribute = columnNamedAttribute; + Indexed = indexed; + UniqueIndex = uniqueIndex; + PrimaryKey = primaryKey; + NotMapped = notMapped; + OverrideNeverCamel = overrideNeverCamel; + + IsComplexType = PropertyMappingCache.IsComplexType(property.PropertyType); - public object? DefaultValue { get; } + // 🔥 Identify if this property is a constructor parameter + var declaringType = property.DeclaringType!; + var constructor = declaringType.GetConstructors().FirstOrDefault(); - public bool OverrideNeverCamel { get; } + // 🔥 Precompute and cache the default value + DefaultValue = property.PropertyType.IsValueType ? Activator.CreateInstance(property.PropertyType) : null; - /// - /// If any Magic attribute was placed on a property. We never - /// camel case if that's the current json setting. These must stay - /// as they were initially designated. - /// - public bool NeverCamelCase + // 🔥 Create delegates for performance 🔥 + if (property.CanRead) + { + Getter = CreateGetter(property); + } + else { - get - { - if (PrimaryKey || UniqueIndex || Indexed || OverrideNeverCamel) - return true; - else - return false; - } + Getter = _ => null; // No getter available } - public bool IsComplexType { get; } + if (property.CanWrite) + { + Setter = CreateSetter(property); + } + else + { + Setter = (_, __) => { }; // No setter available + } + } - /// 🔥 **Precomputed Getter Delegate** 🔥 - public Func Getter { get; } + public object? DefaultValue { get; } - /// 🔥 **Precomputed Setter Delegate** 🔥 - public Action Setter { get; } + public bool OverrideNeverCamel { get; } - private static Func CreateGetter(PropertyInfo property) + /// + /// If any Magic attribute was placed on a property. We never + /// camel case if that's the current json setting. These must stay + /// as they were initially designated. + /// + public bool NeverCamelCase + { + get { - var method = property.GetGetMethod(nonPublic: true); - if (method == null) return _ => null; + if (PrimaryKey || UniqueIndex || Indexed || OverrideNeverCamel) + return true; + else + return false; + } + } - var instanceParam = Expression.Parameter(typeof(object), "instance"); - var castInstance = Expression.Convert(instanceParam, property.DeclaringType!); - var propertyAccess = Expression.Property(castInstance, property); - var castResult = Expression.Convert(propertyAccess, typeof(object)); + public bool IsComplexType { get; } - return Expression.Lambda>(castResult, instanceParam).Compile(); - } + /// 🔥 **Precomputed Getter Delegate** 🔥 + public Func Getter { get; } - private static Action CreateSetter(PropertyInfo property) - { - var method = property.GetSetMethod(nonPublic: true); - if (method == null) return (_, __) => { }; + /// 🔥 **Precomputed Setter Delegate** 🔥 + public Action Setter { get; } - var instanceParam = Expression.Parameter(typeof(object), "instance"); - var valueParam = Expression.Parameter(typeof(object), "value"); + private static Func CreateGetter(PropertyInfo property) + { + var method = property.GetGetMethod(nonPublic: true); + if (method == null) return _ => null; - var castInstance = Expression.Convert(instanceParam, property.DeclaringType!); - var castValue = Expression.Convert(valueParam, property.PropertyType); + var instanceParam = Expression.Parameter(typeof(object), "instance"); + var castInstance = Expression.Convert(instanceParam, property.DeclaringType!); + var propertyAccess = Expression.Property(castInstance, property); + var castResult = Expression.Convert(propertyAccess, typeof(object)); - var propertySetter = Expression.Call(castInstance, method, castValue); + return Expression.Lambda>(castResult, instanceParam).Compile(); + } - return Expression.Lambda>(propertySetter, instanceParam, valueParam).Compile(); - } + private static Action CreateSetter(PropertyInfo property) + { + var method = property.GetSetMethod(nonPublic: true); + if (method == null) return (_, __) => { }; + + var instanceParam = Expression.Parameter(typeof(object), "instance"); + var valueParam = Expression.Parameter(typeof(object), "value"); + var castInstance = Expression.Convert(instanceParam, property.DeclaringType!); + var castValue = Expression.Convert(valueParam, property.PropertyType); - /// - /// Reference to the IColumnNamed attribute if present, otherwise null. - /// This prevents saving the original string provided unecessary which - /// saves minimum 20 bytes if the IColumnName originally was empty. - /// Aka it means we're saving much more than 20 bytes per item. - /// - private readonly IColumnNamed? _columnNamedAttribute; - - /// - /// The JavaScript/Column Name mapping - /// - public string JsPropertyName => - PropertyMappingCache.GetJsPropertyNameNoCache(_columnNamedAttribute, Property.Name); - - /// - /// Reference to the PropertyInfo instead of storing the C# name as a string. - /// Which reduces memory print from the minimum empty string size of 20 bytes - /// to now only 8 bytes (within 64 bit systems). - /// - public PropertyInfo Property { get; set; } - - /// - /// The C# Property Name mapping - /// - public string CsharpPropertyName => Property.Name; - - /// - /// Property with the, "MagicIndexAttribute" attribute appended - /// - public bool Indexed { get; set; } - - /// - /// Property with the, "MagicUniqueIndexAttribute" attribute appended - /// - public bool UniqueIndex { get; set; } - - /// - /// Property with the, "MagicPrimaryKeyAttribute" attribute appended - /// - public bool PrimaryKey { get; set; } - - /// - /// Property with the, "MagicNotMappedAttribute" attribute appended - /// - public bool NotMapped { get; set; } + var propertySetter = Expression.Call(castInstance, method, castValue); + + return Expression.Lambda>(propertySetter, instanceParam, valueParam).Compile(); } -} + + + /// + /// Reference to the IColumnNamed attribute if present, otherwise null. + /// This prevents saving the original string provided unecessary which + /// saves minimum 20 bytes if the IColumnName originally was empty. + /// Aka it means we're saving much more than 20 bytes per item. + /// + private readonly IColumnNamed? _columnNamedAttribute; + + /// + /// The JavaScript/Column Name mapping + /// + public string JsPropertyName => + PropertyMappingCache.GetJsPropertyNameNoCache(_columnNamedAttribute, Property.Name); + + /// + /// Reference to the PropertyInfo instead of storing the C# name as a string. + /// Which reduces memory print from the minimum empty string size of 20 bytes + /// to now only 8 bytes (within 64 bit systems). + /// + public PropertyInfo Property { get; set; } + + /// + /// The C# Property Name mapping + /// + public string CsharpPropertyName => Property.Name; + + /// + /// Property with the, "MagicIndexAttribute" attribute appended + /// + public bool Indexed { get; set; } + + /// + /// Property with the, "MagicUniqueIndexAttribute" attribute appended + /// + public bool UniqueIndex { get; set; } + + /// + /// Property with the, "MagicPrimaryKeyAttribute" attribute appended + /// + public bool PrimaryKey { get; set; } + + /// + /// Property with the, "MagicNotMappedAttribute" attribute appended + /// + public bool NotMapped { get; set; } +} \ No newline at end of file diff --git a/Magic.IndexedDb/Models/Structs/MagicPropertySearchEntry.cs b/Magic.IndexedDb/Models/Structs/MagicPropertySearchEntry.cs index 008ea59..252ace3 100644 --- a/Magic.IndexedDb/Models/Structs/MagicPropertySearchEntry.cs +++ b/Magic.IndexedDb/Models/Structs/MagicPropertySearchEntry.cs @@ -6,39 +6,38 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Models +namespace Magic.IndexedDb.Models; + +public struct MagicPropertySearchEntry { - public struct MagicPropertySearchEntry + public MagicPropertySearchEntry(PropertyInfo property, IColumnNamed? columnNamedAttribute) { - public MagicPropertySearchEntry(PropertyInfo property, IColumnNamed? columnNamedAttribute) - { - Property = property; - _columnNamedAttribute = columnNamedAttribute; - } - /// - /// Reference to the IColumnNamed attribute if present, otherwise null. - /// This prevents saving the original string provided unecessary which - /// saves minimum 20 bytes if the IColumnName originally was empty. - /// Aka it means we're saving much more than 20 bytes per item. - /// - public readonly IColumnNamed? _columnNamedAttribute; + Property = property; + _columnNamedAttribute = columnNamedAttribute; + } + /// + /// Reference to the IColumnNamed attribute if present, otherwise null. + /// This prevents saving the original string provided unecessary which + /// saves minimum 20 bytes if the IColumnName originally was empty. + /// Aka it means we're saving much more than 20 bytes per item. + /// + public readonly IColumnNamed? _columnNamedAttribute; - /// - /// The JavaScript/Column Name mapping - /// - public string JsPropertyName => - _columnNamedAttribute?.ColumnName ?? Property.Name; + /// + /// The JavaScript/Column Name mapping + /// + public string JsPropertyName => + _columnNamedAttribute?.ColumnName ?? Property.Name; - /// - /// Reference to the PropertyInfo instead of storing the C# name as a string. - /// Which reduces memory print from the minimum empty string size of 20 bytes - /// to now only 8 bytes (within 64 bit systems). - /// - public PropertyInfo Property { get; set; } + /// + /// Reference to the PropertyInfo instead of storing the C# name as a string. + /// Which reduces memory print from the minimum empty string size of 20 bytes + /// to now only 8 bytes (within 64 bit systems). + /// + public PropertyInfo Property { get; set; } - /// - /// The C# Property Name mapping - /// - public string CsharpPropertyName => Property.Name; - } -} + /// + /// The C# Property Name mapping + /// + public string CsharpPropertyName => Property.Name; +} \ No newline at end of file diff --git a/Magic.IndexedDb/Models/Structs/MagicTableEntry.cs b/Magic.IndexedDb/Models/Structs/MagicTableEntry.cs index 5bd5a05..df7956f 100644 --- a/Magic.IndexedDb/Models/Structs/MagicTableEntry.cs +++ b/Magic.IndexedDb/Models/Structs/MagicTableEntry.cs @@ -4,19 +4,18 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Models +namespace Magic.IndexedDb.Models; + +public struct MagicTableEntry { - public struct MagicTableEntry - { - /// - /// Property with the, "MagicTableAttribute" attribute appended - /// - public bool IsIndexDbTable { get; } + /// + /// Property with the, "MagicTableAttribute" attribute appended + /// + public bool IsIndexDbTable { get; } - public MagicTableEntry(bool isIndexDbTable) - { - IsIndexDbTable = isIndexDbTable; - } + public MagicTableEntry(bool isIndexDbTable) + { + IsIndexDbTable = isIndexDbTable; } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/Models/TypedArgument.cs b/Magic.IndexedDb/Models/TypedArgument.cs index 83012ab..929d1b4 100644 --- a/Magic.IndexedDb/Models/TypedArgument.cs +++ b/Magic.IndexedDb/Models/TypedArgument.cs @@ -7,31 +7,29 @@ using System.Text.Json; using System.Threading.Tasks; -namespace Magic.IndexedDb.Models -{ - public class TypedArgument : ITypedArgument - { - public T? Value { get; } +namespace Magic.IndexedDb.Models; - public TypedArgument(T? value) - { - Value = value; - } +public class TypedArgument : ITypedArgument +{ + public T? Value { get; } - public string Serialize() - { - return MagicSerializationHelper.SerializeObject(Value); - } + public TypedArgument(T? value) + { + Value = value; + } - public JsonElement SerializeToJsonElement(MagicJsonSerializationSettings? settings = null) - { - return MagicSerializationHelper.SerializeObjectToJsonElement(Value, settings); - } + public string Serialize() + { + return MagicSerializationHelper.SerializeObject(Value); + } - public string SerializeToJsonString(MagicJsonSerializationSettings? settings = null) - { - return MagicSerializationHelper.SerializeObject(Value, settings); - } + public JsonElement SerializeToJsonElement(MagicJsonSerializationSettings? settings = null) + { + return MagicSerializationHelper.SerializeObjectToJsonElement(Value, settings); } -} + public string SerializeToJsonString(MagicJsonSerializationSettings? settings = null) + { + return MagicSerializationHelper.SerializeObject(Value, settings); + } +} \ No newline at end of file diff --git a/Magic.IndexedDb/Models/UniversalOperations/AndFilterGroup.cs b/Magic.IndexedDb/Models/UniversalOperations/AndFilterGroup.cs index e86d5d6..8cdfa4d 100644 --- a/Magic.IndexedDb/Models/UniversalOperations/AndFilterGroup.cs +++ b/Magic.IndexedDb/Models/UniversalOperations/AndFilterGroup.cs @@ -4,18 +4,17 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Models.UniversalOperations +namespace Magic.IndexedDb.Models.UniversalOperations; + +/// +/// Represents a group of conditions that are connected using AND (&&). +/// All conditions inside this group must be TRUE for the filter to apply. +/// Example: (Age > 35 && Name StartsWith "J") +/// +public class AndFilterGroup { /// - /// Represents a group of conditions that are connected using AND (&&). - /// All conditions inside this group must be TRUE for the filter to apply. - /// Example: (Age > 35 && Name StartsWith "J") + /// A list of conditions that are joined using AND (&&). /// - public class AndFilterGroup - { - /// - /// A list of conditions that are joined using AND (&&). - /// - public List conditions { get; set; } = new(); - } -} + public List conditions { get; set; } = new(); +} \ No newline at end of file diff --git a/Magic.IndexedDb/Models/UniversalOperations/FilterCondition.cs b/Magic.IndexedDb/Models/UniversalOperations/FilterCondition.cs index da5c94c..82c26b9 100644 --- a/Magic.IndexedDb/Models/UniversalOperations/FilterCondition.cs +++ b/Magic.IndexedDb/Models/UniversalOperations/FilterCondition.cs @@ -4,50 +4,49 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Models.UniversalOperations +namespace Magic.IndexedDb.Models.UniversalOperations; + +/// +/// Represents a single filtering condition applied to a property. +/// Example: "name StartsWith 'J'", "age > 35", etc. +/// +public struct FilterCondition { /// - /// Represents a single filtering condition applied to a property. - /// Example: "name StartsWith 'J'", "age > 35", etc. + /// The name of the property to filter on. + /// Example: "name", "age" /// - public struct FilterCondition - { - /// - /// The name of the property to filter on. - /// Example: "name", "age" - /// - public string property { get; set; } + public string property { get; set; } - /// - /// The comparison operation to apply. - /// Example: "Equal", "StartsWith", "GreaterThan" - /// - public string operation { get; set; } + /// + /// The comparison operation to apply. + /// Example: "Equal", "StartsWith", "GreaterThan" + /// + public string operation { get; set; } - /// - /// The value being compared against. - /// Example: "John" (for strings), 35 (for numbers). - /// - public object? value { get; set; } + /// + /// The value being compared against. + /// Example: "John" (for strings), 35 (for numbers). + /// + public object? value { get; set; } - /// - /// Indicates whether the value should be treated as a string. - /// - public bool isString { get; set; } + /// + /// Indicates whether the value should be treated as a string. + /// + public bool isString { get; set; } - /// - /// Specifies whether the comparison should be case-sensitive (for strings). - /// - public bool caseSensitive { get; set; } + /// + /// Specifies whether the comparison should be case-sensitive (for strings). + /// + public bool caseSensitive { get; set; } - public FilterCondition(string _property, string _operation, object? _value, - bool _isString = false, bool _caseSensitive = false) - { - property = _property; - operation = _operation; - value = _value; - isString = _isString; - caseSensitive = _caseSensitive; - } + public FilterCondition(string _property, string _operation, object? _value, + bool _isString = false, bool _caseSensitive = false) + { + property = _property; + operation = _operation; + value = _value; + isString = _isString; + caseSensitive = _caseSensitive; } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/Models/UniversalOperations/NestedOrFilter.cs b/Magic.IndexedDb/Models/UniversalOperations/NestedOrFilter.cs index 838e5f8..4f9dc77 100644 --- a/Magic.IndexedDb/Models/UniversalOperations/NestedOrFilter.cs +++ b/Magic.IndexedDb/Models/UniversalOperations/NestedOrFilter.cs @@ -4,29 +4,28 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Models.UniversalOperations +namespace Magic.IndexedDb.Models.UniversalOperations; + +/// +/// Supported in Magic IndexDB Blazor C# code, but not currently supported purely through the +/// universal LINQ JS layer. Built for future capabilities for flattening on the JS side +/// instead of C# in future refactors. Only used currently for future scaleability. +/// +/// Represents a complex query that allows deeply nested OR conditions. +/// This enables advanced filtering logic with multi-layered conditions. +/// Example: +/// ( +/// (age > 35 && testInt == 9) || +/// (name StartsWith "J") +/// ) || +/// (name Contains "bo") +/// +public class NestedOrFilter { /// - /// Supported in Magic IndexDB Blazor C# code, but not currently supported purely through the - /// universal LINQ JS layer. Built for future capabilities for flattening on the JS side - /// instead of C# in future refactors. Only used currently for future scaleability. - /// - /// Represents a complex query that allows deeply nested OR conditions. - /// This enables advanced filtering logic with multi-layered conditions. - /// Example: - /// ( - /// (age > 35 && testInt == 9) || - /// (name StartsWith "J") - /// ) || - /// (name Contains "bo") + /// A list of OR-groups, where each group represents a possible matching condition set. /// - public class NestedOrFilter - { - /// - /// A list of OR-groups, where each group represents a possible matching condition set. - /// - public List orGroups { get; set; } = new(); + public List orGroups { get; set; } = new(); - public bool universalFalse { get; set; } = false; - } -} + public bool universalFalse { get; set; } = false; +} \ No newline at end of file diff --git a/Magic.IndexedDb/Models/UniversalOperations/OrFilterGroup.cs b/Magic.IndexedDb/Models/UniversalOperations/OrFilterGroup.cs index 088c97a..78f93ad 100644 --- a/Magic.IndexedDb/Models/UniversalOperations/OrFilterGroup.cs +++ b/Magic.IndexedDb/Models/UniversalOperations/OrFilterGroup.cs @@ -4,18 +4,17 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Models.UniversalOperations +namespace Magic.IndexedDb.Models.UniversalOperations; + +/// +/// Represents a group of AND-filter groups that are connected using OR (||). +/// At least one of these AND groups must be TRUE for the filter to apply. +/// Example: (age > 35 && name StartsWith "J") || (name Contains "bo") +/// +public class OrFilterGroup { /// - /// Represents a group of AND-filter groups that are connected using OR (||). - /// At least one of these AND groups must be TRUE for the filter to apply. - /// Example: (age > 35 && name StartsWith "J") || (name Contains "bo") + /// A list of AND-groups, where each group represents an AND-filter set. /// - public class OrFilterGroup - { - /// - /// A list of AND-groups, where each group represents an AND-filter set. - /// - public List andGroups { get; set; } = new(); - } -} + public List andGroups { get; set; } = new(); +} \ No newline at end of file diff --git a/Magic.IndexedDb/Models/UpdateRecord.cs b/Magic.IndexedDb/Models/UpdateRecord.cs index 6d04d1a..bfebc66 100644 --- a/Magic.IndexedDb/Models/UpdateRecord.cs +++ b/Magic.IndexedDb/Models/UpdateRecord.cs @@ -1,9 +1,8 @@ using Magic.IndexedDb.Helpers; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +public class UpdateRecord : StoreRecord { - public class UpdateRecord : StoreRecord - { - public List Key { get; set; } - } + public List Key { get; set; } } \ No newline at end of file diff --git a/Magic.IndexedDb/SchemaAnnotations/MagicIndexAttribute.cs b/Magic.IndexedDb/SchemaAnnotations/MagicIndexAttribute.cs index f507277..e2bbabd 100644 --- a/Magic.IndexedDb/SchemaAnnotations/MagicIndexAttribute.cs +++ b/Magic.IndexedDb/SchemaAnnotations/MagicIndexAttribute.cs @@ -6,26 +6,25 @@ using Magic.IndexedDb.Interfaces; using Magic.IndexedDb.SchemaAnnotations; -namespace Magic.IndexedDb.SchemaAnnotations +namespace Magic.IndexedDb.SchemaAnnotations; + +/// +/// Indexes this key +/// +[AttributeUsage(AttributeTargets.Property)] +public class MagicIndexAttribute : Attribute, IColumnNamed { - /// - /// Indexes this key - /// - [AttributeUsage(AttributeTargets.Property)] - public class MagicIndexAttribute : Attribute, IColumnNamed - { - public string ColumnName { get; } + public string ColumnName { get; } - public MagicIndexAttribute(string columnName = null) + public MagicIndexAttribute(string columnName = null) + { + if (!String.IsNullOrWhiteSpace(columnName)) + { + ColumnName = columnName; + } + else { - if (!String.IsNullOrWhiteSpace(columnName)) - { - ColumnName = columnName; - } - else - { - ColumnName = null; - } + ColumnName = null; } } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/SchemaAnnotations/MagicNameAttribute.cs b/Magic.IndexedDb/SchemaAnnotations/MagicNameAttribute.cs index 43ec442..db88e6b 100644 --- a/Magic.IndexedDb/SchemaAnnotations/MagicNameAttribute.cs +++ b/Magic.IndexedDb/SchemaAnnotations/MagicNameAttribute.cs @@ -6,23 +6,22 @@ using Magic.IndexedDb.Interfaces; using Magic.IndexedDb.SchemaAnnotations; -namespace Magic.IndexedDb.SchemaAnnotations +namespace Magic.IndexedDb.SchemaAnnotations; + +[AttributeUsage(AttributeTargets.Property)] +public class MagicNameAttribute : Attribute, IColumnNamed { - [AttributeUsage(AttributeTargets.Property)] - public class MagicNameAttribute : Attribute, IColumnNamed - { - public string ColumnName { get; } + public string ColumnName { get; } - public MagicNameAttribute(string columnName) + public MagicNameAttribute(string columnName) + { + if (!string.IsNullOrWhiteSpace(columnName)) + { + ColumnName = columnName; + } + else { - if (!string.IsNullOrWhiteSpace(columnName)) - { - ColumnName = columnName; - } - else - { - throw new Exception("You have a MagicName attribute with no column name string provided!"); - } + throw new Exception("You have a MagicName attribute with no column name string provided!"); } } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/SchemaAnnotations/MagicNotMappedAttribute.cs b/Magic.IndexedDb/SchemaAnnotations/MagicNotMappedAttribute.cs index 44b8f7b..95a3730 100644 --- a/Magic.IndexedDb/SchemaAnnotations/MagicNotMappedAttribute.cs +++ b/Magic.IndexedDb/SchemaAnnotations/MagicNotMappedAttribute.cs @@ -4,7 +4,6 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.SchemaAnnotations -{ - public class MagicNotMappedAttribute : Attribute { } -} +namespace Magic.IndexedDb.SchemaAnnotations; + +public class MagicNotMappedAttribute : Attribute { } \ No newline at end of file diff --git a/Magic.IndexedDb/SchemaAnnotations/MagicPrimaryKeyAttribute.cs b/Magic.IndexedDb/SchemaAnnotations/MagicPrimaryKeyAttribute.cs index 696fe7f..034f4ac 100644 --- a/Magic.IndexedDb/SchemaAnnotations/MagicPrimaryKeyAttribute.cs +++ b/Magic.IndexedDb/SchemaAnnotations/MagicPrimaryKeyAttribute.cs @@ -6,33 +6,32 @@ using Magic.IndexedDb.Interfaces; using Magic.IndexedDb.SchemaAnnotations; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +/// +/// sets as the primary key +/// +/*[AttributeUsage(AttributeTargets.Property)] +public class MagicPrimaryKeyAttribute : Attribute, IColumnNamed { + public string ColumnName { get; } + public bool AutoIncrement { get; } + /// - /// sets as the primary key + /// /// - /*[AttributeUsage(AttributeTargets.Property)] - public class MagicPrimaryKeyAttribute : Attribute, IColumnNamed + /// whether the primary key automatically increments when new rows are added. + /// + public MagicPrimaryKeyAttribute(bool autoIncrement, string columnName = null) { - public string ColumnName { get; } - public bool AutoIncrement { get; } - - /// - /// - /// - /// whether the primary key automatically increments when new rows are added. - /// - public MagicPrimaryKeyAttribute(bool autoIncrement, string columnName = null) + AutoIncrement = autoIncrement; + if (!String.IsNullOrWhiteSpace(columnName)) + { + ColumnName = columnName; + } + else { - AutoIncrement = autoIncrement; - if (!String.IsNullOrWhiteSpace(columnName)) - { - ColumnName = columnName; - } - else - { - ColumnName = null; - } + ColumnName = null; } - }*/ -} + } +}*/ \ No newline at end of file diff --git a/Magic.IndexedDb/SchemaAnnotations/MagicTableAttribute.cs b/Magic.IndexedDb/SchemaAnnotations/MagicTableAttribute.cs index 1a07e94..c142c23 100644 --- a/Magic.IndexedDb/SchemaAnnotations/MagicTableAttribute.cs +++ b/Magic.IndexedDb/SchemaAnnotations/MagicTableAttribute.cs @@ -5,22 +5,21 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +/*public class MagicTableAttribute : Attribute { - /*public class MagicTableAttribute : Attribute - { - public const string defaultNone = "DefaultedNone"; - public string SchemaName { get; } - public string DatabaseName { get; } + public const string defaultNone = "DefaultedNone"; + public string SchemaName { get; } + public string DatabaseName { get; } - public MagicTableAttribute(string schemaName, string databaseName = null) - { - SchemaName = schemaName; + public MagicTableAttribute(string schemaName, string databaseName = null) + { + SchemaName = schemaName; - if (!String.IsNullOrWhiteSpace(databaseName)) - DatabaseName = databaseName; - else - DatabaseName = defaultNone; - } - }*/ -} + if (!String.IsNullOrWhiteSpace(databaseName)) + DatabaseName = databaseName; + else + DatabaseName = defaultNone; + } +}*/ \ No newline at end of file diff --git a/Magic.IndexedDb/SchemaAnnotations/MagicUniqueIndexAttribute.cs b/Magic.IndexedDb/SchemaAnnotations/MagicUniqueIndexAttribute.cs index 0132248..9f6e146 100644 --- a/Magic.IndexedDb/SchemaAnnotations/MagicUniqueIndexAttribute.cs +++ b/Magic.IndexedDb/SchemaAnnotations/MagicUniqueIndexAttribute.cs @@ -6,26 +6,25 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb +namespace Magic.IndexedDb; + +/// +/// Creates a unique key +/// +[AttributeUsage(AttributeTargets.Property)] +public class MagicUniqueIndexAttribute : Attribute, IColumnNamed { - /// - /// Creates a unique key - /// - [AttributeUsage(AttributeTargets.Property)] - public class MagicUniqueIndexAttribute : Attribute, IColumnNamed - { - public string ColumnName { get; } + public string ColumnName { get; } - public MagicUniqueIndexAttribute(string columnName = null) + public MagicUniqueIndexAttribute(string columnName = null) + { + if (!String.IsNullOrWhiteSpace(columnName)) + { + ColumnName = columnName; + } + else { - if (!String.IsNullOrWhiteSpace(columnName)) - { - ColumnName = columnName; - } - else - { - ColumnName = null; - } + ColumnName = null; } } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/SnapshotBuilder/BuildTools.cs b/Magic.IndexedDb/SnapshotBuilder/BuildTools.cs index 9dd736e..9ce8788 100644 --- a/Magic.IndexedDb/SnapshotBuilder/BuildTools.cs +++ b/Magic.IndexedDb/SnapshotBuilder/BuildTools.cs @@ -7,95 +7,94 @@ using System.Text.Json; using System.Threading.Tasks; -namespace Magic.IndexedDb.SnapshotBuilder +namespace Magic.IndexedDb.SnapshotBuilder; + +public static class BuildTools { - public static class BuildTools + /// + /// This is wrong find again! + /// + /// + public static void GenerateSchemaJson(string projectPath) { - /// - /// This is wrong find again! - /// - /// - public static void GenerateSchemaJson(string projectPath) - { - Console.WriteLine("Starting schema generation..."); + Console.WriteLine("Starting schema generation..."); - // Ensure the build path exists - string buildOutputPath = Path.Combine(projectPath, "bin", "Debug", "net8.0"); + // Ensure the build path exists + string buildOutputPath = Path.Combine(projectPath, "bin", "Debug", "net8.0"); - // Load the consuming project's assemblies - LoadProjectAssemblies(buildOutputPath); + // Load the consuming project's assemblies + LoadProjectAssemblies(buildOutputPath); - // Get schemas dynamically - var allSchemas = SchemaHelper.GetAllSchemas(); + // Get schemas dynamically + var allSchemas = SchemaHelper.GetAllSchemas(); - string wwwrootPath = Path.Combine(projectPath, "wwwroot"); - string magicIndexDbPath = Path.Combine(wwwrootPath, "MagicIndexedDb"); + string wwwrootPath = Path.Combine(projectPath, "wwwroot"); + string magicIndexDbPath = Path.Combine(wwwrootPath, "MagicIndexedDb"); - if (!Directory.Exists(wwwrootPath)) - { - Console.WriteLine($"ERROR: `wwwroot` is missing in {projectPath}."); - Environment.Exit(1); - } + if (!Directory.Exists(wwwrootPath)) + { + Console.WriteLine($"ERROR: `wwwroot` is missing in {projectPath}."); + Environment.Exit(1); + } - if (!Directory.Exists(magicIndexDbPath)) - { - Directory.CreateDirectory(magicIndexDbPath); - } + if (!Directory.Exists(magicIndexDbPath)) + { + Directory.CreateDirectory(magicIndexDbPath); + } - foreach (var schema in allSchemas) - { - string schemaFilePath = Path.Combine(magicIndexDbPath, $"{schema.TableName}.json"); + foreach (var schema in allSchemas) + { + string schemaFilePath = Path.Combine(magicIndexDbPath, $"{schema.TableName}.json"); - List schemaList = new(); + List schemaList = new(); - if (File.Exists(schemaFilePath)) + if (File.Exists(schemaFilePath)) + { + try { - try - { - string existingJson = File.ReadAllText(schemaFilePath); - schemaList = JsonSerializer.Deserialize>(existingJson) ?? new List(); - } - catch (Exception) - { - Console.WriteLine($"Warning: Failed to read {schemaFilePath}. Overwriting..."); - } + string existingJson = File.ReadAllText(schemaFilePath); + schemaList = JsonSerializer.Deserialize>(existingJson) ?? new List(); } - - if (!schemaList.Any(s => s.TableName == schema.TableName)) + catch (Exception) { - schemaList.Add(schema); + Console.WriteLine($"Warning: Failed to read {schemaFilePath}. Overwriting..."); } + } - string json = JsonSerializer.Serialize(schemaList, new JsonSerializerOptions { WriteIndented = true }); - File.WriteAllText(schemaFilePath, json); - - Console.WriteLine($"Updated: {schemaFilePath}"); + if (!schemaList.Any(s => s.TableName == schema.TableName)) + { + schemaList.Add(schema); } - Console.WriteLine("Magic IndexedDB schemas generated successfully."); + string json = JsonSerializer.Serialize(schemaList, new JsonSerializerOptions { WriteIndented = true }); + File.WriteAllText(schemaFilePath, json); + + Console.WriteLine($"Updated: {schemaFilePath}"); } - private static void LoadProjectAssemblies(string buildOutputPath) + Console.WriteLine("Magic IndexedDB schemas generated successfully."); + } + + private static void LoadProjectAssemblies(string buildOutputPath) + { + if (!Directory.Exists(buildOutputPath)) { - if (!Directory.Exists(buildOutputPath)) + Console.WriteLine($"Warning: Build output directory does not exist ({buildOutputPath}). Skipping assembly loading."); + return; + } + + var dllFiles = Directory.GetFiles(buildOutputPath, "*.dll"); + foreach (var dll in dllFiles) + { + try { - Console.WriteLine($"Warning: Build output directory does not exist ({buildOutputPath}). Skipping assembly loading."); - return; + Assembly.LoadFrom(dll); + Console.WriteLine($"Loaded assembly: {dll}"); } - - var dllFiles = Directory.GetFiles(buildOutputPath, "*.dll"); - foreach (var dll in dllFiles) + catch (Exception ex) { - try - { - Assembly.LoadFrom(dll); - Console.WriteLine($"Loaded assembly: {dll}"); - } - catch (Exception ex) - { - Console.WriteLine($"Failed to load {dll}: {ex.Message}"); - } + Console.WriteLine($"Failed to load {dll}: {ex.Message}"); } } } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/Testing/Helpers/AllPaths.cs b/Magic.IndexedDb/Testing/Helpers/AllPaths.cs index 2665605..f255f18 100644 --- a/Magic.IndexedDb/Testing/Helpers/AllPaths.cs +++ b/Magic.IndexedDb/Testing/Helpers/AllPaths.cs @@ -5,103 +5,102 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Testing.Helpers +namespace Magic.IndexedDb.Testing.Helpers; + +public class QueryTestBlueprint { - public class QueryTestBlueprint - { - public List>? IndexOrderingProperties { get; set; } - public List>> WherePredicates { get; set; } = new(); - public List>> OrderBys { get; set; } = new(); - public List>> OrderByDescendings { get; set; } = new(); - public List TakeValues { get; set; } = new(); - public List SkipValues { get; set; } = new(); - public List TakeLastValues { get; set; } = new(); - } + public List>? IndexOrderingProperties { get; set; } + public List>> WherePredicates { get; set; } = new(); + public List>> OrderBys { get; set; } = new(); + public List>> OrderByDescendings { get; set; } = new(); + public List TakeValues { get; set; } = new(); + public List SkipValues { get; set; } = new(); + public List TakeLastValues { get; set; } = new(); +} - public static class AllPaths +public static class AllPaths +{ + public class Transition { - public class Transition - { - public string Name { get; set; } = string.Empty; - public Func, object> Execute { get; set; } = default!; - public int MaxRepetitions { get; set; } = 1; - public Type? Returns { get; set; } + public string Name { get; set; } = string.Empty; + public Func, object> Execute { get; set; } = default!; + public int MaxRepetitions { get; set; } = 1; + public Type? Returns { get; set; } - } + } + + private static int RepeatMax = 2; - private static int RepeatMax = 2; + public static Dictionary>> BuildTransitionMap(Dictionary? overrides = null) where T : class + { + int GetMax(string method, int defaultVal) => + overrides != null && overrides.TryGetValue(method, out var val) ? val : defaultVal; - public static Dictionary>> BuildTransitionMap(Dictionary? overrides = null) where T : class + return new Dictionary>> { - int GetMax(string method, int defaultVal) => - overrides != null && overrides.TryGetValue(method, out var val) ? val : defaultVal; + [typeof(IMagicQuery)] = new List> + { + new() { Name = "Where", Execute = (q, bp) => ((IMagicQuery)q).Where(bp.WherePredicates[0]), MaxRepetitions = GetMax("Where", RepeatMax), Returns = typeof(IMagicQueryStaging) }, + new() { Name = "Cursor", Execute = (q, bp) => ((IMagicQuery)q).Cursor(bp.WherePredicates[0]), MaxRepetitions = GetMax("Cursor", RepeatMax), Returns = typeof(IMagicCursor) }, + new() { Name = "Take", Execute = (q, bp) => ((IMagicQuery)q).Take(bp.TakeValues[0]), MaxRepetitions = GetMax("Take", RepeatMax), Returns = typeof(IMagicQueryPaginationTake) }, + new() { Name = "TakeLast", Execute = (q, bp) => ((IMagicQuery)q).TakeLast(bp.TakeLastValues[0]), MaxRepetitions = GetMax("TakeLast", RepeatMax), Returns = typeof(IMagicQueryFinal) }, + new() { Name = "Skip", Execute = (q, bp) => ((IMagicQuery)q).Skip(bp.SkipValues[0]), MaxRepetitions = GetMax("Skip", RepeatMax), Returns = typeof(IMagicQueryFinal) }, + new() { Name = "OrderBy", Execute = (q, bp) => ((IMagicQuery)q).OrderBy(bp.OrderBys[0]), MaxRepetitions = GetMax("OrderBy", RepeatMax), Returns = typeof(IMagicQueryOrderableTable) }, + new() { Name = "OrderByDescending", Execute = (q, bp) => ((IMagicQuery)q).OrderByDescending(bp.OrderByDescendings[0]), MaxRepetitions = GetMax("OrderByDescending", RepeatMax), Returns = typeof(IMagicQueryOrderableTable) } + }, - return new Dictionary>> + [typeof(IMagicQueryStaging)] = new List> { - [typeof(IMagicQuery)] = new List> - { - new() { Name = "Where", Execute = (q, bp) => ((IMagicQuery)q).Where(bp.WherePredicates[0]), MaxRepetitions = GetMax("Where", RepeatMax), Returns = typeof(IMagicQueryStaging) }, - new() { Name = "Cursor", Execute = (q, bp) => ((IMagicQuery)q).Cursor(bp.WherePredicates[0]), MaxRepetitions = GetMax("Cursor", RepeatMax), Returns = typeof(IMagicCursor) }, - new() { Name = "Take", Execute = (q, bp) => ((IMagicQuery)q).Take(bp.TakeValues[0]), MaxRepetitions = GetMax("Take", RepeatMax), Returns = typeof(IMagicQueryPaginationTake) }, - new() { Name = "TakeLast", Execute = (q, bp) => ((IMagicQuery)q).TakeLast(bp.TakeLastValues[0]), MaxRepetitions = GetMax("TakeLast", RepeatMax), Returns = typeof(IMagicQueryFinal) }, - new() { Name = "Skip", Execute = (q, bp) => ((IMagicQuery)q).Skip(bp.SkipValues[0]), MaxRepetitions = GetMax("Skip", RepeatMax), Returns = typeof(IMagicQueryFinal) }, - new() { Name = "OrderBy", Execute = (q, bp) => ((IMagicQuery)q).OrderBy(bp.OrderBys[0]), MaxRepetitions = GetMax("OrderBy", RepeatMax), Returns = typeof(IMagicQueryOrderableTable) }, - new() { Name = "OrderByDescending", Execute = (q, bp) => ((IMagicQuery)q).OrderByDescending(bp.OrderByDescendings[0]), MaxRepetitions = GetMax("OrderByDescending", RepeatMax), Returns = typeof(IMagicQueryOrderableTable) } - }, - - [typeof(IMagicQueryStaging)] = new List> - { - new() { Name = "Where", Execute = (q, bp) => ((IMagicQueryStaging)q).Where(bp.WherePredicates[0]), MaxRepetitions = GetMax("Where", RepeatMax), Returns = typeof(IMagicQueryStaging) }, - new() { Name = "Take", Execute = (q, bp) => ((IMagicQueryStaging)q).Take(bp.TakeValues[0]), MaxRepetitions = GetMax("Take", RepeatMax), Returns = typeof(IMagicQueryPaginationTake) }, - new() { Name = "TakeLast", Execute = (q, bp) => ((IMagicQueryStaging)q).TakeLast(bp.TakeLastValues[0]), MaxRepetitions = GetMax("TakeLast", RepeatMax), Returns = typeof(IMagicQueryFinal) }, - new() { Name = "Skip", Execute = (q, bp) => ((IMagicQueryStaging)q).Skip(bp.SkipValues[0]), MaxRepetitions = GetMax("Skip", RepeatMax), Returns = typeof(IMagicQueryFinal) } - }, + new() { Name = "Where", Execute = (q, bp) => ((IMagicQueryStaging)q).Where(bp.WherePredicates[0]), MaxRepetitions = GetMax("Where", RepeatMax), Returns = typeof(IMagicQueryStaging) }, + new() { Name = "Take", Execute = (q, bp) => ((IMagicQueryStaging)q).Take(bp.TakeValues[0]), MaxRepetitions = GetMax("Take", RepeatMax), Returns = typeof(IMagicQueryPaginationTake) }, + new() { Name = "TakeLast", Execute = (q, bp) => ((IMagicQueryStaging)q).TakeLast(bp.TakeLastValues[0]), MaxRepetitions = GetMax("TakeLast", RepeatMax), Returns = typeof(IMagicQueryFinal) }, + new() { Name = "Skip", Execute = (q, bp) => ((IMagicQueryStaging)q).Skip(bp.SkipValues[0]), MaxRepetitions = GetMax("Skip", RepeatMax), Returns = typeof(IMagicQueryFinal) } + }, - [typeof(IMagicQueryPaginationTake)] = new List>() { + [typeof(IMagicQueryPaginationTake)] = new List>() { new() { Name = "Skip", Execute = (q, bp) => ((IMagicQuery)q).Skip(bp.SkipValues[0]), MaxRepetitions = GetMax("Skip", RepeatMax), Returns = typeof(IMagicQueryFinal) } - }, + }, - [typeof(IMagicQueryFinal)] = new List>() /* terminal */, + [typeof(IMagicQueryFinal)] = new List>() /* terminal */, - [typeof(IMagicQueryOrderableTable)] = new List> - { - new() { Name = "Take", Execute = (q, bp) => ((IMagicQueryOrderableTable)q).Take(bp.TakeValues[0]), MaxRepetitions = GetMax("Take", RepeatMax), Returns = typeof(IMagicQueryPaginationTake) }, - new() { Name = "TakeLast", Execute = (q, bp) => ((IMagicQueryOrderableTable)q).TakeLast(bp.TakeLastValues[0]), MaxRepetitions = GetMax("TakeLast", RepeatMax), Returns = typeof(IMagicQueryFinal) }, - new() { Name = "Skip", Execute = (q, bp) => ((IMagicQueryOrderableTable)q).Skip(bp.SkipValues[0]), MaxRepetitions = GetMax("Skip", 1), Returns = typeof(IMagicQueryFinal) } - }, + [typeof(IMagicQueryOrderableTable)] = new List> + { + new() { Name = "Take", Execute = (q, bp) => ((IMagicQueryOrderableTable)q).Take(bp.TakeValues[0]), MaxRepetitions = GetMax("Take", RepeatMax), Returns = typeof(IMagicQueryPaginationTake) }, + new() { Name = "TakeLast", Execute = (q, bp) => ((IMagicQueryOrderableTable)q).TakeLast(bp.TakeLastValues[0]), MaxRepetitions = GetMax("TakeLast", RepeatMax), Returns = typeof(IMagicQueryFinal) }, + new() { Name = "Skip", Execute = (q, bp) => ((IMagicQueryOrderableTable)q).Skip(bp.SkipValues[0]), MaxRepetitions = GetMax("Skip", 1), Returns = typeof(IMagicQueryFinal) } + }, - [typeof(IMagicQueryOrderable)] = new List> - { - new() { Name = "Take", Execute = (q, bp) => ((IMagicQueryOrderableTable)q).Take(bp.TakeValues[0]), MaxRepetitions = GetMax("Take", RepeatMax), Returns = typeof(IMagicQueryPaginationTake) }, - new() { Name = "TakeLast", Execute = (q, bp) => ((IMagicQueryOrderableTable)q).TakeLast(bp.TakeLastValues[0]), MaxRepetitions = GetMax("TakeLast", RepeatMax), Returns = typeof(IMagicQueryFinal) }, - new() { Name = "Skip", Execute = (q, bp) => ((IMagicQueryOrderableTable)q).Skip(bp.SkipValues[0]), MaxRepetitions = GetMax("Skip", 1), Returns = typeof(IMagicQueryFinal) } - }, + [typeof(IMagicQueryOrderable)] = new List> + { + new() { Name = "Take", Execute = (q, bp) => ((IMagicQueryOrderableTable)q).Take(bp.TakeValues[0]), MaxRepetitions = GetMax("Take", RepeatMax), Returns = typeof(IMagicQueryPaginationTake) }, + new() { Name = "TakeLast", Execute = (q, bp) => ((IMagicQueryOrderableTable)q).TakeLast(bp.TakeLastValues[0]), MaxRepetitions = GetMax("TakeLast", RepeatMax), Returns = typeof(IMagicQueryFinal) }, + new() { Name = "Skip", Execute = (q, bp) => ((IMagicQueryOrderableTable)q).Skip(bp.SkipValues[0]), MaxRepetitions = GetMax("Skip", 1), Returns = typeof(IMagicQueryFinal) } + }, - [typeof(IMagicCursor)] = new List> - { - new() { Name = "Cursor", Execute = (q, bp) => ((IMagicCursor)q).Cursor(bp.WherePredicates[0]), MaxRepetitions = GetMax("Cursor", RepeatMax), Returns = typeof(IMagicCursor) }, - new() { Name = "Take", Execute = (q, bp) => ((IMagicCursor)q).Take(bp.TakeValues[0]), MaxRepetitions = GetMax("Take", RepeatMax), Returns = typeof(IMagicCursorPaginationTake) }, - new() { Name = "TakeLast", Execute = (q, bp) => ((IMagicCursor)q).TakeLast(bp.TakeLastValues[0]), MaxRepetitions = GetMax("TakeLast", RepeatMax), Returns = typeof(IMagicCursorPaginationTake) }, - new() { Name = "Skip", Execute = (q, bp) => ((IMagicCursor)q).Skip(bp.SkipValues[0]), MaxRepetitions = GetMax("Skip", RepeatMax), Returns = typeof(IMagicCursorSkip) }, - new() { Name = "OrderBy", Execute = (q, bp) => ((IMagicCursor)q).OrderBy(bp.OrderBys[0]), MaxRepetitions = GetMax("OrderBy", RepeatMax), Returns = typeof(IMagicCursorStage) }, - new() { Name = "OrderByDescending", Execute = (q, bp) => ((IMagicCursor)q).OrderByDescending(bp.OrderByDescendings[0]), MaxRepetitions = GetMax("OrderByDescending", RepeatMax), Returns = typeof(IMagicCursorStage) } - }, - - [typeof(IMagicCursorStage)] = new List> - { - new() { Name = "Take", Execute = (q, bp) => ((IMagicCursorStage)q).Take(bp.TakeValues[0]), MaxRepetitions = GetMax("Take", RepeatMax), Returns = typeof(IMagicCursorPaginationTake) }, - new() { Name = "TakeLast", Execute = (q, bp) => ((IMagicCursorStage)q).TakeLast(bp.TakeLastValues[0]), MaxRepetitions = GetMax("TakeLast", RepeatMax), Returns = typeof(IMagicCursorPaginationTake) }, - new() { Name = "Skip", Execute = (q, bp) => ((IMagicCursorStage)q).Skip(bp.SkipValues[0]), MaxRepetitions = GetMax("Skip", RepeatMax), Returns = typeof(IMagicCursorSkip) } - }, - - [typeof(IMagicCursorSkip)] = new List>(), /* terminal */ - [typeof(IMagicCursorFinal)] = new List>(), /* terminal */ - [typeof(IMagicCursorPaginationTake)] = new List>() - { - new() { Name = "Skip", Execute = (q, bp) => ((IMagicCursorStage)q).Skip(bp.SkipValues[0]), MaxRepetitions = GetMax("Skip", RepeatMax), Returns = typeof(IMagicCursorSkip) } - } - }; - } + [typeof(IMagicCursor)] = new List> + { + new() { Name = "Cursor", Execute = (q, bp) => ((IMagicCursor)q).Cursor(bp.WherePredicates[0]), MaxRepetitions = GetMax("Cursor", RepeatMax), Returns = typeof(IMagicCursor) }, + new() { Name = "Take", Execute = (q, bp) => ((IMagicCursor)q).Take(bp.TakeValues[0]), MaxRepetitions = GetMax("Take", RepeatMax), Returns = typeof(IMagicCursorPaginationTake) }, + new() { Name = "TakeLast", Execute = (q, bp) => ((IMagicCursor)q).TakeLast(bp.TakeLastValues[0]), MaxRepetitions = GetMax("TakeLast", RepeatMax), Returns = typeof(IMagicCursorPaginationTake) }, + new() { Name = "Skip", Execute = (q, bp) => ((IMagicCursor)q).Skip(bp.SkipValues[0]), MaxRepetitions = GetMax("Skip", RepeatMax), Returns = typeof(IMagicCursorSkip) }, + new() { Name = "OrderBy", Execute = (q, bp) => ((IMagicCursor)q).OrderBy(bp.OrderBys[0]), MaxRepetitions = GetMax("OrderBy", RepeatMax), Returns = typeof(IMagicCursorStage) }, + new() { Name = "OrderByDescending", Execute = (q, bp) => ((IMagicCursor)q).OrderByDescending(bp.OrderByDescendings[0]), MaxRepetitions = GetMax("OrderByDescending", RepeatMax), Returns = typeof(IMagicCursorStage) } + }, + + [typeof(IMagicCursorStage)] = new List> + { + new() { Name = "Take", Execute = (q, bp) => ((IMagicCursorStage)q).Take(bp.TakeValues[0]), MaxRepetitions = GetMax("Take", RepeatMax), Returns = typeof(IMagicCursorPaginationTake) }, + new() { Name = "TakeLast", Execute = (q, bp) => ((IMagicCursorStage)q).TakeLast(bp.TakeLastValues[0]), MaxRepetitions = GetMax("TakeLast", RepeatMax), Returns = typeof(IMagicCursorPaginationTake) }, + new() { Name = "Skip", Execute = (q, bp) => ((IMagicCursorStage)q).Skip(bp.SkipValues[0]), MaxRepetitions = GetMax("Skip", RepeatMax), Returns = typeof(IMagicCursorSkip) } + }, + [typeof(IMagicCursorSkip)] = new List>(), /* terminal */ + [typeof(IMagicCursorFinal)] = new List>(), /* terminal */ + [typeof(IMagicCursorPaginationTake)] = new List>() + { + new() { Name = "Skip", Execute = (q, bp) => ((IMagicCursorStage)q).Skip(bp.SkipValues[0]), MaxRepetitions = GetMax("Skip", RepeatMax), Returns = typeof(IMagicCursorSkip) } + } + }; } -} + +} \ No newline at end of file diff --git a/Magic.IndexedDb/Testing/Helpers/MagicQueryPathWalker.cs b/Magic.IndexedDb/Testing/Helpers/MagicQueryPathWalker.cs index 3eda817..8b46c9e 100644 --- a/Magic.IndexedDb/Testing/Helpers/MagicQueryPathWalker.cs +++ b/Magic.IndexedDb/Testing/Helpers/MagicQueryPathWalker.cs @@ -5,300 +5,299 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Testing.Helpers +namespace Magic.IndexedDb.Testing.Helpers; + +internal static class MagicInMemoryExecutor { - internal static class MagicInMemoryExecutor + public static List Execute( + IEnumerable allItems, + List trail, + QueryTestBlueprint bp + ) where T : class, IMagicTableBase, new() { - public static List Execute( - IEnumerable allItems, - List trail, - QueryTestBlueprint bp - ) where T : class, IMagicTableBase, new() + IEnumerable result = allItems; + + // STEP 1: Apply Where/Cursor filters + foreach (var step in trail) { - IEnumerable result = allItems; + if (step == "Where" || step == "Cursor") + result = result.Where(bp.WherePredicates[0].Compile()); + } - // STEP 1: Apply Where/Cursor filters - foreach (var step in trail) + // STEP 2: Apply explicit OrderBy / OrderByDescending + IOrderedEnumerable? ordered = null; + bool hasExplicitOrder = false; + + foreach (var step in trail) + { + if (step == "OrderBy") { - if (step == "Where" || step == "Cursor") - result = result.Where(bp.WherePredicates[0].Compile()); + ordered = result.OrderBy(bp.OrderBys[0].Compile()); + hasExplicitOrder = true; } - - // STEP 2: Apply explicit OrderBy / OrderByDescending - IOrderedEnumerable? ordered = null; - bool hasExplicitOrder = false; - - foreach (var step in trail) + else if (step == "OrderByDescending") { - if (step == "OrderBy") - { - ordered = result.OrderBy(bp.OrderBys[0].Compile()); - hasExplicitOrder = true; - } - else if (step == "OrderByDescending") - { - ordered = result.OrderByDescending(bp.OrderByDescendings[0].Compile()); - hasExplicitOrder = true; - } + ordered = result.OrderByDescending(bp.OrderByDescendings[0].Compile()); + hasExplicitOrder = true; } + } - // STEP 3: If ordered, add compound key tiebreakers for determinism - if (ordered != null) + // STEP 3: If ordered, add compound key tiebreakers for determinism + if (ordered != null) + { + var compoundKey = new T().GetKeys(); + if (compoundKey?.PropertyInfos?.Length > 0) { - var compoundKey = new T().GetKeys(); - if (compoundKey?.PropertyInfos?.Length > 0) - { - foreach (var prop in compoundKey.PropertyInfos) - ordered = ordered.ThenBy(x => prop.GetValue(x) ?? ""); - } - - result = ordered; + foreach (var prop in compoundKey.PropertyInfos) + ordered = ordered.ThenBy(x => prop.GetValue(x) ?? ""); } - // STEP 4: If no explicit order, apply default ordering if Take/Skip is used - if (!hasExplicitOrder && (trail.Contains("Take") || trail.Contains("Skip") || trail.Contains("TakeLast"))) - { - IOrderedEnumerable? stableOrdered = null; + result = ordered; + } - // Step 1: Apply IndexOrderingProperties first (e.g., from .Index("TestInt")) - if (bp.IndexOrderingProperties?.Count > 0) + // STEP 4: If no explicit order, apply default ordering if Take/Skip is used + if (!hasExplicitOrder && (trail.Contains("Take") || trail.Contains("Skip") || trail.Contains("TakeLast"))) + { + IOrderedEnumerable? stableOrdered = null; + + // Step 1: Apply IndexOrderingProperties first (e.g., from .Index("TestInt")) + if (bp.IndexOrderingProperties?.Count > 0) + { + foreach (var selector in bp.IndexOrderingProperties) { - foreach (var selector in bp.IndexOrderingProperties) - { - stableOrdered = stableOrdered == null - ? result.OrderBy(selector) - : stableOrdered.ThenBy(selector); - } + stableOrdered = stableOrdered == null + ? result.OrderBy(selector) + : stableOrdered.ThenBy(selector); } + } - // Step 2: ALWAYS apply compound key fallback to break ties - var compoundKey = new T().GetKeys(); - if (compoundKey?.PropertyInfos?.Length > 0) + // Step 2: ALWAYS apply compound key fallback to break ties + var compoundKey = new T().GetKeys(); + if (compoundKey?.PropertyInfos?.Length > 0) + { + foreach (var prop in compoundKey.PropertyInfos) { - foreach (var prop in compoundKey.PropertyInfos) - { - stableOrdered = stableOrdered == null - ? result.OrderBy(x => prop.GetValue(x) ?? "") - : stableOrdered.ThenBy(x => prop.GetValue(x) ?? ""); - } + stableOrdered = stableOrdered == null + ? result.OrderBy(x => prop.GetValue(x) ?? "") + : stableOrdered.ThenBy(x => prop.GetValue(x) ?? ""); } - - if (stableOrdered != null) - result = stableOrdered; } + if (stableOrdered != null) + result = stableOrdered; + } - // STEP 5: Detect if Skip should come before Take - bool hasTake = trail.Contains("Take"); - bool hasSkip = trail.Contains("Skip"); - bool flipSkipAndTake = hasTake && hasSkip && trail.IndexOf("Take") < trail.IndexOf("Skip"); - if (flipSkipAndTake) - { - // IndexedDB-style → flip to match SQL semantics - result = result.Skip(bp.SkipValues[0]); - result = result.Take(bp.TakeValues[0]); - } - else + // STEP 5: Detect if Skip should come before Take + bool hasTake = trail.Contains("Take"); + bool hasSkip = trail.Contains("Skip"); + bool flipSkipAndTake = hasTake && hasSkip && trail.IndexOf("Take") < trail.IndexOf("Skip"); + + if (flipSkipAndTake) + { + // IndexedDB-style → flip to match SQL semantics + result = result.Skip(bp.SkipValues[0]); + result = result.Take(bp.TakeValues[0]); + } + else + { + foreach (var step in trail) { - foreach (var step in trail) - { - if (step == "Skip") - result = result.Skip(bp.SkipValues[0]); - else if (step == "Take") - result = result.Take(bp.TakeValues[0]); - else if (step == "TakeLast") - result = result.Reverse().Take(bp.TakeLastValues[0]).Reverse(); - } + if (step == "Skip") + result = result.Skip(bp.SkipValues[0]); + else if (step == "Take") + result = result.Take(bp.TakeValues[0]); + else if (step == "TakeLast") + result = result.Reverse().Take(bp.TakeLastValues[0]).Reverse(); } + } - return result.ToList(); - } + return result.ToList(); } +} - public static class MagicQueryPathWalker +public static class MagicQueryPathWalker +{ + public static List> GenerateAllPaths( + IMagicQuery baseQuery, + List allPeople, + QueryTestBlueprint blueprint, + int maxDepth = 6, + Dictionary? repetitionOverrides = null, + Func, IMagicCursor>? cursorProvider = null, + int? overrideMaxRepetitions = null + ) where T : class, IMagicTableBase, new() { - public static List> GenerateAllPaths( - IMagicQuery baseQuery, - List allPeople, - QueryTestBlueprint blueprint, - int maxDepth = 6, - Dictionary? repetitionOverrides = null, - Func, IMagicCursor>? cursorProvider = null, - int? overrideMaxRepetitions = null - ) where T : class, IMagicTableBase, new() - { - var map = AllPaths.BuildTransitionMap(repetitionOverrides); - var results = new List>(); - var seenPaths = new HashSet(); - - Explore(map, baseQuery, typeof(IMagicQuery), allPeople, blueprint, results, seenPaths, maxDepth, null, null, null, 0, overrideMaxRepetitions); + var map = AllPaths.BuildTransitionMap(repetitionOverrides); + var results = new List>(); + var seenPaths = new HashSet(); - if (cursorProvider != null) - { - var cursorQuery = cursorProvider(blueprint); - Explore(map, cursorQuery, typeof(IMagicCursor), allPeople, blueprint, results, seenPaths, maxDepth, null, null, null, 0, overrideMaxRepetitions); - } + Explore(map, baseQuery, typeof(IMagicQuery), allPeople, blueprint, results, seenPaths, maxDepth, null, null, null, 0, overrideMaxRepetitions); - return results; + if (cursorProvider != null) + { + var cursorQuery = cursorProvider(blueprint); + Explore(map, cursorQuery, typeof(IMagicCursor), allPeople, blueprint, results, seenPaths, maxDepth, null, null, null, 0, overrideMaxRepetitions); } - private static void Explore( - Dictionary>> map, - object queryObj, - Type currentType, - List allPeople, - QueryTestBlueprint blueprint, - List> results, - HashSet seenPaths, - int maxDepth, - List? trail = null, - Func, IQueryable>? linq = null, - Dictionary? used = null, - int depth = 0, - int? overrideMaxRepetitions = null -) where T : class, IMagicTableBase, new() - { - if (depth > maxDepth || !map.TryGetValue(currentType, out var transitions)) - return; + return results; + } - trail ??= new(); - linq ??= q => q; - used ??= new(); + private static void Explore( + Dictionary>> map, + object queryObj, + Type currentType, + List allPeople, + QueryTestBlueprint blueprint, + List> results, + HashSet seenPaths, + int maxDepth, + List? trail = null, + Func, IQueryable>? linq = null, + Dictionary? used = null, + int depth = 0, + int? overrideMaxRepetitions = null + ) where T : class, IMagicTableBase, new() + { + if (depth > maxDepth || !map.TryGetValue(currentType, out var transitions)) + return; - foreach (var transition in transitions) - { - int reps = transition.MaxRepetitions; - if (overrideMaxRepetitions != null) - reps = overrideMaxRepetitions ?? transition.MaxRepetitions; + trail ??= new(); + linq ??= q => q; + used ??= new(); - var nextUsed = new Dictionary(used); - if (!nextUsed.TryGetValue(transition.Name, out var count)) - count = 0; - if (count >= reps) continue; - nextUsed[transition.Name] = count + 1; + foreach (var transition in transitions) + { + int reps = transition.MaxRepetitions; + if (overrideMaxRepetitions != null) + reps = overrideMaxRepetitions ?? transition.MaxRepetitions; - List newTrail = trail.Append(transition.Name).ToList(); + var nextUsed = new Dictionary(used); + if (!nextUsed.TryGetValue(transition.Name, out var count)) + count = 0; + if (count >= reps) continue; + nextUsed[transition.Name] = count + 1; - try - { - object nextQuery = transition.Execute(queryObj, blueprint); + List newTrail = trail.Append(transition.Name).ToList(); + + try + { + object nextQuery = transition.Execute(queryObj, blueprint); - // Only follow the explicitly declared Returns interface - Type? nextInterface = transition.Returns; - if (nextInterface == null || !map.ContainsKey(nextInterface)) - continue; + // Only follow the explicitly declared Returns interface + Type? nextInterface = transition.Returns; + if (nextInterface == null || !map.ContainsKey(nextInterface)) + continue; - string pathKey = $"{string.Join("→", newTrail)}||{nextInterface.Name}"; - if (!seenPaths.Add(pathKey)) continue; + string pathKey = $"{string.Join("→", newTrail)}||{nextInterface.Name}"; + if (!seenPaths.Add(pathKey)) continue; - Func, IQueryable> newLinq = q => - ApplyLinqTrail(q, newTrail, blueprint); + Func, IQueryable> newLinq = q => + ApplyLinqTrail(q, newTrail, blueprint); - if (nextQuery is IMagicExecute) - { - // Create a blueprint clone with filtered IndexOrderingProperties - var localBlueprint = new QueryTestBlueprint - { - WherePredicates = blueprint.WherePredicates, - OrderBys = blueprint.OrderBys, - OrderByDescendings = blueprint.OrderByDescendings, - SkipValues = blueprint.SkipValues, - TakeValues = blueprint.TakeValues, - TakeLastValues = blueprint.TakeLastValues, - - // Only apply index ordering if a relevant Where is in the trail - IndexOrderingProperties = newTrail.Contains("Where") || newTrail.Contains("Cursor") - ? blueprint.IndexOrderingProperties - : null - }; - - results.Add(new ExecutionPath - { - Name = pathKey, - ExecuteDb = async () => await ((IMagicExecute)nextQuery).ToListAsync(), - ExecuteInMemory = () => MagicInMemoryExecutor.Execute(allPeople, newTrail, localBlueprint) - }); - - } - - Explore( - map, - nextQuery, - nextInterface, - allPeople, - blueprint, - results, - seenPaths, - maxDepth, - newTrail, - newLinq, - nextUsed, - depth + 1, - overrideMaxRepetitions - ); - } - catch + if (nextQuery is IMagicExecute) { - // Swallow invalid transitions + // Create a blueprint clone with filtered IndexOrderingProperties + var localBlueprint = new QueryTestBlueprint + { + WherePredicates = blueprint.WherePredicates, + OrderBys = blueprint.OrderBys, + OrderByDescendings = blueprint.OrderByDescendings, + SkipValues = blueprint.SkipValues, + TakeValues = blueprint.TakeValues, + TakeLastValues = blueprint.TakeLastValues, + + // Only apply index ordering if a relevant Where is in the trail + IndexOrderingProperties = newTrail.Contains("Where") || newTrail.Contains("Cursor") + ? blueprint.IndexOrderingProperties + : null + }; + + results.Add(new ExecutionPath + { + Name = pathKey, + ExecuteDb = async () => await ((IMagicExecute)nextQuery).ToListAsync(), + ExecuteInMemory = () => MagicInMemoryExecutor.Execute(allPeople, newTrail, localBlueprint) + }); + } + Explore( + map, + nextQuery, + nextInterface, + allPeople, + blueprint, + results, + seenPaths, + maxDepth, + newTrail, + newLinq, + nextUsed, + depth + 1, + overrideMaxRepetitions + ); + } + catch + { + // Swallow invalid transitions } - } + } + } - private static IQueryable ApplyLinqTrail(IQueryable query, List trail, QueryTestBlueprint bp) where T : class - { - var transformed = query; - var methods = new List(trail); - // Special case: Take then Skip → reverse to Skip then Take for LINQ to match IndexedDB - if (methods.Contains("Take") && methods.Contains("Skip")) - { - var takeIndex = methods.IndexOf("Take"); - var skipIndex = methods.IndexOf("Skip"); - if (takeIndex < skipIndex) - { - // Move Skip before Take - methods.RemoveAt(skipIndex); - methods.Insert(takeIndex, "Skip"); - methods.Remove("Take"); - methods.Insert(takeIndex + 1, "Take"); - } - } + private static IQueryable ApplyLinqTrail(IQueryable query, List trail, QueryTestBlueprint bp) where T : class + { + var transformed = query; + var methods = new List(trail); - foreach (var step in methods) + // Special case: Take then Skip → reverse to Skip then Take for LINQ to match IndexedDB + if (methods.Contains("Take") && methods.Contains("Skip")) + { + var takeIndex = methods.IndexOf("Take"); + var skipIndex = methods.IndexOf("Skip"); + if (takeIndex < skipIndex) { - transformed = ApplyLinqStep(transformed, step, bp); + // Move Skip before Take + methods.RemoveAt(skipIndex); + methods.Insert(takeIndex, "Skip"); + methods.Remove("Take"); + methods.Insert(takeIndex + 1, "Take"); } - - return transformed; } - private static IQueryable ApplyLinqStep(IQueryable query, string method, QueryTestBlueprint bp) where T : class + foreach (var step in methods) { - return method switch - { - "Where" => query.Where(bp.WherePredicates[0]), - "Cursor" => query.Where(bp.WherePredicates[0]), - "OrderBy" => query.OrderBy(bp.OrderBys[0]), - "OrderByDescending" => query.OrderByDescending(bp.OrderByDescendings[0]), - "Take" => query.Take(bp.TakeValues[0]), - "TakeLast" => query.Reverse().Take(bp.TakeLastValues[0]).Reverse(), - "Skip" => query.Skip(bp.SkipValues[0]), - _ => query - }; + transformed = ApplyLinqStep(transformed, step, bp); } + + return transformed; } - public class ExecutionPath where T : class + private static IQueryable ApplyLinqStep(IQueryable query, string method, QueryTestBlueprint bp) where T : class { - public string Name { get; set; } = string.Empty; - public Func>> ExecuteDb { get; set; } = default!; - public Func> ExecuteInMemory { get; set; } = default!; + return method switch + { + "Where" => query.Where(bp.WherePredicates[0]), + "Cursor" => query.Where(bp.WherePredicates[0]), + "OrderBy" => query.OrderBy(bp.OrderBys[0]), + "OrderByDescending" => query.OrderByDescending(bp.OrderByDescendings[0]), + "Take" => query.Take(bp.TakeValues[0]), + "TakeLast" => query.Reverse().Take(bp.TakeLastValues[0]).Reverse(), + "Skip" => query.Skip(bp.SkipValues[0]), + _ => query + }; } } + +public class ExecutionPath where T : class +{ + public string Name { get; set; } = string.Empty; + public Func>> ExecuteDb { get; set; } = default!; + public Func> ExecuteInMemory { get; set; } = default!; +} \ No newline at end of file diff --git a/Magic.IndexedDb/Testing/Helpers/TestHelper.cs b/Magic.IndexedDb/Testing/Helpers/TestHelper.cs index 03b22d8..b539eb4 100644 --- a/Magic.IndexedDb/Testing/Helpers/TestHelper.cs +++ b/Magic.IndexedDb/Testing/Helpers/TestHelper.cs @@ -11,158 +11,157 @@ using System.Text.Json; using System.Threading.Tasks; -namespace Magic.IndexedDb.Testing.Helpers +namespace Magic.IndexedDb.Testing.Helpers; + +public static class TestValidator { - public static class TestValidator + public static TestResponse ValidateLists(IEnumerable correctResults, IEnumerable testResults) { - public static TestResponse ValidateLists(IEnumerable correctResults, IEnumerable testResults) - { - if (correctResults == null || testResults == null) - return new TestResponse { Success = false, Message = "Error: One or both input lists are null." }; + if (correctResults == null || testResults == null) + return new TestResponse { Success = false, Message = "Error: One or both input lists are null." }; - var correctList = correctResults.ToList(); - var testList = testResults.ToList(); + var correctList = correctResults.ToList(); + var testList = testResults.ToList(); - if (correctList.Count != testList.Count) - return new TestResponse - { - Success = false, - Message = $"List size mismatch:\nExpected count: {correctList.Count}\nActual count: {testList.Count}" - }; - - // 🔑 Retrieve primary key properties - List primaryKeys = PropertyMappingCache.GetPrimaryKeysOfType(typeof(T)) - .Where(pk => pk.PrimaryKey) + if (correctList.Count != testList.Count) + return new TestResponse + { + Success = false, + Message = $"List size mismatch:\nExpected count: {correctList.Count}\nActual count: {testList.Count}" + }; + + // 🔑 Retrieve primary key properties + List primaryKeys = PropertyMappingCache.GetPrimaryKeysOfType(typeof(T)) + .Where(pk => pk.PrimaryKey) + .ToList(); + + if (!primaryKeys.Any()) + return new TestResponse { Success = false, Message = "Error: No primary keys found for type." }; + + var failureDetails = new List(); + + // 🔍 Convert correct results into a dictionary for fast lookup by **property name** + var correctDictionary = correctList.ToDictionary( + item => primaryKeys.ToDictionary( + pk => pk.Property.DeclaringType.FullName + "." + pk.Property.Name, // **Use fully qualified property name** + pk => pk.Property.GetValue(item) + ), + item => item + ); + + foreach (var actualItem in testList) + { + // Extract key properties from the actual item + var actualKeyValues = primaryKeys + .Select(pk => (Property: pk.Property.Name, Value: pk.Property.GetValue(actualItem))) .ToList(); - if (!primaryKeys.Any()) - return new TestResponse { Success = false, Message = "Error: No primary keys found for type." }; - - var failureDetails = new List(); - - // 🔍 Convert correct results into a dictionary for fast lookup by **property name** - var correctDictionary = correctList.ToDictionary( - item => primaryKeys.ToDictionary( - pk => pk.Property.DeclaringType.FullName + "." + pk.Property.Name, // **Use fully qualified property name** - pk => pk.Property.GetValue(item) - ), - item => item + // Find matching item in correctList using key properties + var expectedItem = correctList.FirstOrDefault(correctItem => + primaryKeys.All(pk => + Equals(pk.Property.GetValue(correctItem), pk.Property.GetValue(actualItem)) + ) ); - foreach (var actualItem in testList) + if (expectedItem == null) { - // Extract key properties from the actual item - var actualKeyValues = primaryKeys - .Select(pk => (Property: pk.Property.Name, Value: pk.Property.GetValue(actualItem))) - .ToList(); - - // Find matching item in correctList using key properties - var expectedItem = correctList.FirstOrDefault(correctItem => - primaryKeys.All(pk => - Equals(pk.Property.GetValue(correctItem), pk.Property.GetValue(actualItem)) - ) - ); - - if (expectedItem == null) - { - failureDetails.Add($"❌ No matching item found for Primary Key [{FormatKey(actualKeyValues)}]."); - continue; - } + failureDetails.Add($"❌ No matching item found for Primary Key [{FormatKey(actualKeyValues)}]."); + continue; + } - // **Deeply compare object properties** - var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(prop => !Attribute.IsDefined(prop, typeof(MagicNotMappedAttribute))) - .ToArray(); + // **Deeply compare object properties** + var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(prop => !Attribute.IsDefined(prop, typeof(MagicNotMappedAttribute))) + .ToArray(); - var propertyDifferences = CompareObjects(expectedItem, actualItem, properties, $"Item[{FormatKey(actualKeyValues)}]"); + var propertyDifferences = CompareObjects(expectedItem, actualItem, properties, $"Item[{FormatKey(actualKeyValues)}]"); - if (propertyDifferences.Any()) - { - failureDetails.Add($"Mismatch in object with Primary Key [{FormatKey(actualKeyValues)}]:\n" + string.Join("\n", propertyDifferences)); - } + if (propertyDifferences.Any()) + { + failureDetails.Add($"Mismatch in object with Primary Key [{FormatKey(actualKeyValues)}]:\n" + string.Join("\n", propertyDifferences)); } + } - return failureDetails.Any() - ? new TestResponse { Success = false, Message = string.Join("\n\n", failureDetails) } - : new TestResponse { Success = true }; - } + return failureDetails.Any() + ? new TestResponse { Success = false, Message = string.Join("\n\n", failureDetails) } + : new TestResponse { Success = true }; + } - private static string FormatKey(List<(string Property, object Value)> keyValues) - { - if (keyValues == null || keyValues.Count == 0) - return "NULL"; + private static string FormatKey(List<(string Property, object Value)> keyValues) + { + if (keyValues == null || keyValues.Count == 0) + return "NULL"; - return string.Join(" | ", keyValues - .OrderBy(kv => kv.Property) // Ensure stable order for comparison - .Select(kv => $"{kv.Property}={kv.Value?.ToString() ?? "NULL"}")); - } + return string.Join(" | ", keyValues + .OrderBy(kv => kv.Property) // Ensure stable order for comparison + .Select(kv => $"{kv.Property}={kv.Value?.ToString() ?? "NULL"}")); + } - private static List CompareObjects(object expected, object actual, PropertyInfo[] properties, string path) + private static List CompareObjects(object expected, object actual, PropertyInfo[] properties, string path) + { + var differences = new List(); + + foreach (var property in properties) { - var differences = new List(); + object expectedValue = property.GetValue(expected); + object actualValue = property.GetValue(actual); + + if (expectedValue == null && actualValue == null) + continue; // Both are null, no difference. - foreach (var property in properties) + if ((expectedValue == null && actualValue != null) || (expectedValue != null && actualValue == null)) { - object expectedValue = property.GetValue(expected); - object actualValue = property.GetValue(actual); + differences.Add($" - ❌ {path}.{property.Name}: Expected **[{expectedValue}]**, but got **[{actualValue}]** (null mismatch)."); + continue; + } + + Type propType = property.PropertyType; - if (expectedValue == null && actualValue == null) - continue; // Both are null, no difference. + // 🔥 Special case: if it's object or anonymous, compare via JSON string + if (propType == typeof(object) || IsAnonymousType(expectedValue.GetType()) || IsAnonymousType(actualValue.GetType())) + { + var expectedJson = JsonSerializer.Serialize(expectedValue); + var actualJson = JsonSerializer.Serialize(actualValue); - if ((expectedValue == null && actualValue != null) || (expectedValue != null && actualValue == null)) + if (expectedJson != actualJson) { - differences.Add($" - ❌ {path}.{property.Name}: Expected **[{expectedValue}]**, but got **[{actualValue}]** (null mismatch)."); - continue; + differences.Add($" - ❌ {path}.{property.Name}: Expected JSON **[{expectedJson}]**, but got **[{actualJson}]**."); } - Type propType = property.PropertyType; + continue; + } - // 🔥 Special case: if it's object or anonymous, compare via JSON string - if (propType == typeof(object) || IsAnonymousType(expectedValue.GetType()) || IsAnonymousType(actualValue.GetType())) + // 💡 Deep compare if complex + if (!expectedValue.Equals(actualValue)) + { + if (PropertyMappingCache.IsComplexType(propType)) { - var expectedJson = JsonSerializer.Serialize(expectedValue); - var actualJson = JsonSerializer.Serialize(actualValue); - - if (expectedJson != actualJson) - { - differences.Add($" - ❌ {path}.{property.Name}: Expected JSON **[{expectedJson}]**, but got **[{actualJson}]**."); - } + var nestedProperties = propType.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(prop => !Attribute.IsDefined(prop, typeof(MagicNotMappedAttribute))) + .ToArray(); - continue; + var nestedDifferences = CompareObjects(expectedValue, actualValue, nestedProperties, $"{path}.{property.Name}"); + differences.AddRange(nestedDifferences); } - - // 💡 Deep compare if complex - if (!expectedValue.Equals(actualValue)) + else { - if (PropertyMappingCache.IsComplexType(propType)) - { - var nestedProperties = propType.GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(prop => !Attribute.IsDefined(prop, typeof(MagicNotMappedAttribute))) - .ToArray(); - - var nestedDifferences = CompareObjects(expectedValue, actualValue, nestedProperties, $"{path}.{property.Name}"); - differences.AddRange(nestedDifferences); - } - else - { - differences.Add($" - ❌ {path}.{property.Name}: Expected **[{expectedValue}]**, but got **[{actualValue}]**."); - } + differences.Add($" - ❌ {path}.{property.Name}: Expected **[{expectedValue}]**, but got **[{actualValue}]**."); } } - - return differences; - } - private static bool IsAnonymousType(Type type) - { - return Attribute.IsDefined(type, typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute)) - && type.IsGenericType - && type.Name.Contains("AnonymousType") - && (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$")) - && type.Namespace == null; } + + return differences; + } + private static bool IsAnonymousType(Type type) + { + return Attribute.IsDefined(type, typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute)) + && type.IsGenericType + && type.Name.Contains("AnonymousType") + && (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$")) + && type.Namespace == null; } -} +} \ No newline at end of file diff --git a/Magic.IndexedDb/Testing/Models/TestResponse.cs b/Magic.IndexedDb/Testing/Models/TestResponse.cs index bc9b114..8e0f321 100644 --- a/Magic.IndexedDb/Testing/Models/TestResponse.cs +++ b/Magic.IndexedDb/Testing/Models/TestResponse.cs @@ -4,11 +4,10 @@ using System.Text; using System.Threading.Tasks; -namespace Magic.IndexedDb.Testing.Models +namespace Magic.IndexedDb.Testing.Models; + +public class TestResponse { - public class TestResponse - { - public bool Success { get; set; } = false; - public string? Message { get; set; } - } -} + public bool Success { get; set; } = false; + public string? Message { get; set; } +} \ No newline at end of file diff --git a/TestServer/TestServer/Models/DbNames.cs b/TestServer/TestServer/Models/DbNames.cs index bf4af67..31f1d41 100644 --- a/TestServer/TestServer/Models/DbNames.cs +++ b/TestServer/TestServer/Models/DbNames.cs @@ -1,7 +1,6 @@ -namespace TestWasm.Models +namespace TestWasm.Models; + +public static class DbNames { - public static class DbNames - { - public const string Client = "client"; - } -} + public const string Client = "client"; +} \ No newline at end of file diff --git a/TestServer/TestServer/Models/Person.cs b/TestServer/TestServer/Models/Person.cs index 3b2af57..d89b658 100644 --- a/TestServer/TestServer/Models/Person.cs +++ b/TestServer/TestServer/Models/Person.cs @@ -4,92 +4,91 @@ using TestWasm.Repository; using static TestWasm.Models.Person; -namespace TestWasm.Models +namespace TestWasm.Models; + +public class Nested { - public class Nested - { - public string Value { get; set; } = "abc"; - } + public string Value { get; set; } = "abc"; +} - public class Person : MagicTableTool, IMagicTable - { - public List GetCompoundIndexes() => - new List() { +public class Person : MagicTableTool, IMagicTable +{ + public List GetCompoundIndexes() => + new List() { CreateCompoundIndex(x => x.TestIntStable2, x => x.Name) - }; - - public IMagicCompoundKey GetKeys() => - CreateCompoundKey(x => x.TestIntStable2, x => x.TestIntStable); + }; - //public IMagicCompoundKey GetKeys() => - // CreatePrimaryKey(x => x._Id, true); + public IMagicCompoundKey GetKeys() => + CreateCompoundKey(x => x.TestIntStable2, x => x.TestIntStable); - public string GetTableName() => "Person"; - public IndexedDbSet GetDefaultDatabase() => IndexDbContext.Client; - public DbSets Databases { get; } = new(); - public sealed class DbSets - { - public readonly IndexedDbSet Client = IndexDbContext.Client; - public readonly IndexedDbSet Employee = IndexDbContext.Employee; - } + //public IMagicCompoundKey GetKeys() => + // CreatePrimaryKey(x => x._Id, true); + public string GetTableName() => "Person"; + public IndexedDbSet GetDefaultDatabase() => IndexDbContext.Client; + public DbSets Databases { get; } = new(); + public sealed class DbSets + { + public readonly IndexedDbSet Client = IndexDbContext.Client; + public readonly IndexedDbSet Employee = IndexDbContext.Employee; + } - public int TestIntStable { get; set; } - public int TestIntStable2 { get; set; } = 10; - public Nested Nested { get; set; } = new Nested(); + public int TestIntStable { get; set; } + public int TestIntStable2 { get; set; } = 10; - [MagicName("_id")] - public int _Id { get; set; } + public Nested Nested { get; set; } = new Nested(); - [MagicName("guid1")] - public Guid Guid1 { get; set; } = new Guid(); + [MagicName("_id")] + public int _Id { get; set; } - [MagicName("guid2")] - public Guid Guid2 { get; set; } = new Guid(); + [MagicName("guid1")] + public Guid Guid1 { get; set; } = new Guid(); - [MagicIndex] - public string Name { get; set; } + [MagicName("guid2")] + public Guid Guid2 { get; set; } = new Guid(); - [MagicName("Age")] - public int _Age { get; set; } + [MagicIndex] + public string Name { get; set; } - [MagicIndex("TestInt")] - public int TestInt { get; set; } + [MagicName("Age")] + public int _Age { get; set; } - public DateTime? DateOfBirth { get; set; } + [MagicIndex("TestInt")] + public int TestInt { get; set; } - [MagicUniqueIndex("guid")] - public Guid GUIY { get; set; } = Guid.NewGuid(); - public string Secret { get; set; } + public DateTime? DateOfBirth { get; set; } - [MagicNotMapped] - public string DoNotMapTest { get; set; } + [MagicUniqueIndex("guid")] + public Guid GUIY { get; set; } = Guid.NewGuid(); + public string Secret { get; set; } - [MagicNotMapped] - public static string DoNotMapTest2 { get; set; } + [MagicNotMapped] + public string DoNotMapTest { get; set; } - [MagicNotMapped] - public string SecretDecrypted { get; set; } + [MagicNotMapped] + public static string DoNotMapTest2 { get; set; } - private bool testPrivate { get; set; } = false; + [MagicNotMapped] + public string SecretDecrypted { get; set; } - public bool GetTest() - { - return true; - } + private bool testPrivate { get; set; } = false; - [Flags] - public enum Permissions - { - None = 0, - CanRead = 1, - CanWrite = 1 << 1, - CanDelete = 1 << 2, - CanCreate = 1 << 3 - } + public bool GetTest() + { + return true; + } - public Permissions Access { get; set; } + [Flags] + public enum Permissions + { + None = 0, + CanRead = 1, + CanWrite = 1 << 1, + CanDelete = 1 << 2, + CanCreate = 1 << 3 } -} + + public Permissions Access { get; set; } +} \ No newline at end of file diff --git a/TestServer/TestServer/Repository/IndexDbContext.cs b/TestServer/TestServer/Repository/IndexDbContext.cs index 7dd5858..690bc7d 100644 --- a/TestServer/TestServer/Repository/IndexDbContext.cs +++ b/TestServer/TestServer/Repository/IndexDbContext.cs @@ -2,12 +2,11 @@ using Magic.IndexedDb.Interfaces; using TestWasm.Models; -namespace TestWasm.Repository +namespace TestWasm.Repository; + +public class IndexDbContext : IMagicRepository { - public class IndexDbContext : IMagicRepository - { - public static readonly IndexedDbSet Client = new ("Client"); - public static readonly IndexedDbSet Employee = new ("Employee"); - public static readonly IndexedDbSet Animal = new ("Animal"); - } -} + public static readonly IndexedDbSet Client = new ("Client"); + public static readonly IndexedDbSet Employee = new ("Employee"); + public static readonly IndexedDbSet Animal = new ("Animal"); +} \ No newline at end of file diff --git a/TestWasm/Models/DbNames.cs b/TestWasm/Models/DbNames.cs index bf4af67..31f1d41 100644 --- a/TestWasm/Models/DbNames.cs +++ b/TestWasm/Models/DbNames.cs @@ -1,7 +1,6 @@ -namespace TestWasm.Models +namespace TestWasm.Models; + +public static class DbNames { - public static class DbNames - { - public const string Client = "client"; - } -} + public const string Client = "client"; +} \ No newline at end of file diff --git a/TestWasm/Models/Person.cs b/TestWasm/Models/Person.cs index 46e92c6..20cd2a5 100644 --- a/TestWasm/Models/Person.cs +++ b/TestWasm/Models/Person.cs @@ -4,95 +4,94 @@ using TestWasm.Repository; using static TestWasm.Models.Person; -namespace TestWasm.Models +namespace TestWasm.Models; + +public class Nested { - public class Nested - { - public string Value { get; set; } = "abc"; - } + public string Value { get; set; } = "abc"; +} - public class Person : MagicTableTool, IMagicTable - { - public List GetCompoundIndexes() => - new List() { +public class Person : MagicTableTool, IMagicTable +{ + public List GetCompoundIndexes() => + new List() { CreateCompoundIndex(x => x.TestIntStable2, x => x.Name) - }; - - //public IMagicCompoundKey GetKeys() => - // CreateCompoundKey(x => x.TestIntStable2, x => x.TestIntStable); + }; - public IMagicCompoundKey GetKeys() => - CreatePrimaryKey(x => x._Id, true); + //public IMagicCompoundKey GetKeys() => + // CreateCompoundKey(x => x.TestIntStable2, x => x.TestIntStable); - public string GetTableName() => "Person"; - public IndexedDbSet GetDefaultDatabase() => IndexDbContext.Client; - public DbSets Databases { get; } = new(); - public sealed class DbSets - { - public readonly IndexedDbSet Client = IndexDbContext.Client; - public readonly IndexedDbSet Employee = IndexDbContext.Employee; - } + public IMagicCompoundKey GetKeys() => + CreatePrimaryKey(x => x._Id, true); + public string GetTableName() => "Person"; + public IndexedDbSet GetDefaultDatabase() => IndexDbContext.Client; + public DbSets Databases { get; } = new(); + public sealed class DbSets + { + public readonly IndexedDbSet Client = IndexDbContext.Client; + public readonly IndexedDbSet Employee = IndexDbContext.Employee; + } - public int TestIntStable { get; set; } - public int TestIntStable2 { get; set; } = 10; - public Nested Nested { get; set; } = new Nested(); + public int TestIntStable { get; set; } + public int TestIntStable2 { get; set; } = 10; - [MagicName("_id")] - public int _Id { get; set; } + public Nested Nested { get; set; } = new Nested(); - [MagicName("guid1")] - public Guid Guid1 { get; set; } = new Guid(); + [MagicName("_id")] + public int _Id { get; set; } - [MagicName("guid2")] - public Guid Guid2 { get; set; } = new Guid(); + [MagicName("guid1")] + public Guid Guid1 { get; set; } = new Guid(); - [MagicIndex] - public string Name { get; set; } + [MagicName("guid2")] + public Guid Guid2 { get; set; } = new Guid(); - [MagicName("Age")] - public int _Age { get; set; } + [MagicIndex] + public string Name { get; set; } - [MagicIndex("TestInt")] - public int TestInt { get; set; } + [MagicName("Age")] + public int _Age { get; set; } - public DateTime? DateOfBirth { get; set; } + [MagicIndex("TestInt")] + public int TestInt { get; set; } - [MagicUniqueIndex("guid")] - public Guid GUIY { get; set; } = Guid.NewGuid(); - public string Secret { get; set; } + public DateTime? DateOfBirth { get; set; } - [MagicNotMapped] - public string DoNotMapTest { get; set; } + [MagicUniqueIndex("guid")] + public Guid GUIY { get; set; } = Guid.NewGuid(); + public string Secret { get; set; } - [MagicNotMapped] - public static string DoNotMapTest2 { get; set; } + [MagicNotMapped] + public string DoNotMapTest { get; set; } - [MagicNotMapped] - public string SecretDecrypted { get; set; } + [MagicNotMapped] + public static string DoNotMapTest2 { get; set; } + [MagicNotMapped] + public string SecretDecrypted { get; set; } - public object Payload { get; set; } = new { Property = "test" }; - private bool testPrivate { get; set; } = false; + public object Payload { get; set; } = new { Property = "test" }; - public bool GetTest() - { - return true; - } + private bool testPrivate { get; set; } = false; - [Flags] - public enum Permissions - { - None = 0, - CanRead = 1, - CanWrite = 1 << 1, - CanDelete = 1 << 2, - CanCreate = 1 << 3 - } + public bool GetTest() + { + return true; + } - public Permissions Access { get; set; } + [Flags] + public enum Permissions + { + None = 0, + CanRead = 1, + CanWrite = 1 << 1, + CanDelete = 1 << 2, + CanCreate = 1 << 3 } -} + + public Permissions Access { get; set; } +} \ No newline at end of file diff --git a/TestWasm/Repository/IndexDbContext.cs b/TestWasm/Repository/IndexDbContext.cs index 7dd5858..690bc7d 100644 --- a/TestWasm/Repository/IndexDbContext.cs +++ b/TestWasm/Repository/IndexDbContext.cs @@ -2,12 +2,11 @@ using Magic.IndexedDb.Interfaces; using TestWasm.Models; -namespace TestWasm.Repository +namespace TestWasm.Repository; + +public class IndexDbContext : IMagicRepository { - public class IndexDbContext : IMagicRepository - { - public static readonly IndexedDbSet Client = new ("Client"); - public static readonly IndexedDbSet Employee = new ("Employee"); - public static readonly IndexedDbSet Animal = new ("Animal"); - } -} + public static readonly IndexedDbSet Client = new ("Client"); + public static readonly IndexedDbSet Employee = new ("Employee"); + public static readonly IndexedDbSet Animal = new ("Animal"); +} \ No newline at end of file From e6862dec09060d51a329b62972ad5ee418657a3a Mon Sep 17 00:00:00 2001 From: Ard van der Marel <109339598+Ard2025@users.noreply.github.com> Date: Wed, 4 Jun 2025 09:32:26 +0200 Subject: [PATCH 2/3] Remove unused usings in project --- E2eTest/Entities/DatabaseInfo.cs | 8 +------- E2eTest/Extensions/AssertExtensions.cs | 11 +---------- E2eTest/Extensions/PageExtensions.cs | 7 ------- E2eTest/SingleRecordBasicTest.cs | 3 +-- E2eTest/TestBase.cs | 4 +--- E2eTest/WhereTest.cs | 3 +-- E2eTestWebApp/Program.cs | 1 - E2eTestWebApp/TestPages/OpenTestPage.cs | 2 -- .../TestPages/SingleRecordBasicTestPage.cs | 3 --- E2eTestWebApp/TestPages/TestPageBase.razor.cs | 7 ------- Magic.IndexedDb/Cache.cs | 8 +------- .../Extensions/MagicCompoundExtension.cs | 5 ----- .../Extensions/MagicJsChunkProcessor.cs | 7 +------ Magic.IndexedDb/Extensions/MagicJsInvoke.cs | 7 ------- Magic.IndexedDb/Extensions/MagicTableTools.cs | 5 ----- Magic.IndexedDb/Extensions/MagicUtilities.cs | 5 ----- .../Extensions/ServiceCollectionExtensions.cs | 10 ---------- .../Extensions/SharedQueryExtensions.cs | 6 ------ Magic.IndexedDb/Factories/MagicDbFactory.cs | 5 ----- Magic.IndexedDb/Helpers/AttributeHelpers.cs | 6 ------ Magic.IndexedDb/Helpers/ExpandoToTypeConverter.cs | 3 +-- Magic.IndexedDb/Helpers/ExpressionFlattener.cs | 7 +------ .../Helpers/MagicSerializationHelper.cs | 2 -- Magic.IndexedDb/Helpers/MagicValidator.cs | 4 ---- Magic.IndexedDb/Helpers/PropertyMappingCache.cs | 6 ------ Magic.IndexedDb/Helpers/SchemaHelper.cs | 5 ----- Magic.IndexedDb/IndexDbManager.cs | 10 ---------- Magic.IndexedDb/Interfaces/IColumnNamed.cs | 8 +------- Magic.IndexedDb/Interfaces/IMagicCompoundIndex.cs | 7 +------ Magic.IndexedDb/Interfaces/IMagicCompoundKey.cs | 7 +------ .../Interfaces/IMagicConnectRepository.cs | 8 +------- Magic.IndexedDb/Interfaces/IMagicRepository.cs | 9 +-------- Magic.IndexedDb/Interfaces/IMagicTable.cs | 7 ------- Magic.IndexedDb/Interfaces/IMagicTableBase.cs | 8 +------- Magic.IndexedDb/Interfaces/IMagicUtilities.cs | 8 +------- Magic.IndexedDb/Interfaces/ITypedArgument.cs | 5 ----- .../Extensions/MagicCursorExtension.cs | 14 +------------- .../Extensions/MagicQueryExtensions.cs | 11 ----------- .../Extensions/UniversalExpressionBuilder.cs | 6 ------ .../LinqTranslation/Interfaces/IMagicCursor.cs | 9 +-------- .../LinqTranslation/Interfaces/IMagicCursorSkip.cs | 11 +---------- .../Interfaces/IMagicCursorStage.cs | 11 +---------- .../LinqTranslation/Interfaces/IMagicDatabase.cs | 8 +------- .../LinqTranslation/Interfaces/IMagicExecute.cs | 10 +--------- .../LinqTranslation/Interfaces/IMagicQuery.cs | 9 +-------- .../LinqTranslation/Interfaces/IMagicQueryFinal.cs | 9 +-------- .../Interfaces/IMagicQueryOrderable.cs | 9 +-------- .../Interfaces/IMagicQueryOrderableTable.cs | 7 +------ .../Interfaces/IMagicQueryPaginationTake.cs | 9 +-------- .../Interfaces/IMagicQueryStaging.cs | 9 +-------- .../LinqTranslation/Models/LogicalFilterNode.cs | 6 ------ .../LinqTranslation/Models/MagicCursor.cs | 5 ----- .../LinqTranslation/Models/MagicDatabaseGlobal.cs | 9 +-------- .../LinqTranslation/Models/MagicDatabaseScoped.cs | 5 ----- .../LinqTranslation/Models/MagicQuery.cs | 11 +---------- .../LinqTranslation/Models/MagicWhereLinq.cs | 8 +------- Magic.IndexedDb/MagicEncryptAttribute.cs | 7 +------ Magic.IndexedDb/Models/DbMigration.cs | 8 +------- Magic.IndexedDb/Models/DbMigrationInstruction.cs | 8 +------- Magic.IndexedDb/Models/DbStore.cs | 7 +------ Magic.IndexedDb/Models/IndexFilterValue.cs | 8 +------- Magic.IndexedDb/Models/IndexedDbSet.cs | 8 +------- .../Models/InternalMagicCompoundIndex.cs | 5 ----- Magic.IndexedDb/Models/InternalMagicCompoundKey.cs | 5 ----- Magic.IndexedDb/Models/MagicContractResolver.cs | 8 +------- Magic.IndexedDb/Models/MagicJsPackage.cs | 8 +------- Magic.IndexedDb/Models/PredicateVisitor.cs | 7 +------ Magic.IndexedDb/Models/QuotaUsage.cs | 8 +------- Magic.IndexedDb/Models/StoredMagicQuery.cs | 8 +------- .../Models/Structs/MagicAttributesEntry.cs | 8 +------- .../Models/Structs/MagicPropertyEntry.cs | 7 ------- .../Models/Structs/MagicPropertySearchEntry.cs | 5 ----- Magic.IndexedDb/Models/Structs/MagicTableEntry.cs | 8 +------- Magic.IndexedDb/Models/TypedArgument.cs | 5 ----- .../Models/UniversalOperations/AndFilterGroup.cs | 8 +------- .../Models/UniversalOperations/FilterCondition.cs | 8 +------- .../Models/UniversalOperations/NestedOrFilter.cs | 8 +------- .../Models/UniversalOperations/OrFilterGroup.cs | 8 +------- .../SchemaAnnotations/MagicIndexAttribute.cs | 8 +------- .../SchemaAnnotations/MagicNameAttribute.cs | 8 +------- .../SchemaAnnotations/MagicNotMappedAttribute.cs | 8 +------- .../SchemaAnnotations/MagicPrimaryKeyAttribute.cs | 10 +--------- .../SchemaAnnotations/MagicTableAttribute.cs | 9 +-------- .../SchemaAnnotations/MagicUniqueIndexAttribute.cs | 6 ------ Magic.IndexedDb/SnapshotBuilder/BuildTools.cs | 5 ----- Magic.IndexedDb/Testing/Helpers/AllPaths.cs | 7 +------ .../Testing/Helpers/MagicQueryPathWalker.cs | 5 ----- Magic.IndexedDb/Testing/Helpers/TestHelper.cs | 6 ------ Magic.IndexedDb/Testing/Models/TestResponse.cs | 8 +------- .../TestServer/Components/Pages/CarTests.razor | 2 -- TestServer/TestServer/Components/Pages/Home.razor | 2 -- TestServer/TestServer/Program.cs | 1 - TestServer/TestServer/Repository/IndexDbContext.cs | 1 - TestWasm/Pages/Home.razor | 1 - TestWasm/Program.cs | 2 -- TestWasm/Repository/IndexDbContext.cs | 1 - TestWasm/Share/PanelCustomTests.razor | 1 - 97 files changed, 55 insertions(+), 589 deletions(-) diff --git a/E2eTest/Entities/DatabaseInfo.cs b/E2eTest/Entities/DatabaseInfo.cs index c6e58c9..919599f 100644 --- a/E2eTest/Entities/DatabaseInfo.cs +++ b/E2eTest/Entities/DatabaseInfo.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace E2eTest.Entities; +namespace E2eTest.Entities; internal sealed class DatabaseInfo { public string? Name { get; set; } diff --git a/E2eTest/Extensions/AssertExtensions.cs b/E2eTest/Extensions/AssertExtensions.cs index 8049d24..a6a3030 100644 --- a/E2eTest/Extensions/AssertExtensions.cs +++ b/E2eTest/Extensions/AssertExtensions.cs @@ -1,14 +1,5 @@ -using E2eTest.Entities; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.Playwright; -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Text; +using System.Diagnostics.CodeAnalysis; using System.Text.Json; -using System.Threading.Tasks; -using System.Xml.Linq; namespace E2eTest.Extensions; internal static class AssertExtensions diff --git a/E2eTest/Extensions/PageExtensions.cs b/E2eTest/Extensions/PageExtensions.cs index 6a3891a..e0b5d6c 100644 --- a/E2eTest/Extensions/PageExtensions.cs +++ b/E2eTest/Extensions/PageExtensions.cs @@ -1,12 +1,5 @@ using E2eTest.Entities; -using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Playwright; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Xml.Linq; namespace E2eTest.Extensions; internal static class PageExtensions diff --git a/E2eTest/SingleRecordBasicTest.cs b/E2eTest/SingleRecordBasicTest.cs index 23b9018..ade0d14 100644 --- a/E2eTest/SingleRecordBasicTest.cs +++ b/E2eTest/SingleRecordBasicTest.cs @@ -1,5 +1,4 @@ -using E2eTest.Entities; -using E2eTest.Extensions; +using E2eTest.Extensions; using E2eTestWebApp.TestPages; namespace E2eTest; diff --git a/E2eTest/TestBase.cs b/E2eTest/TestBase.cs index ac47f2c..178f981 100644 --- a/E2eTest/TestBase.cs +++ b/E2eTest/TestBase.cs @@ -1,12 +1,10 @@ // works with TestPageBase in E2eTestWebApp -using E2eTest.Entities; using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Playwright; using System.Linq.Expressions; using System.Reflection; -using System.Text.Json; + namespace E2eTest; diff --git a/E2eTest/WhereTest.cs b/E2eTest/WhereTest.cs index 4fcffd1..13643b3 100644 --- a/E2eTest/WhereTest.cs +++ b/E2eTest/WhereTest.cs @@ -1,5 +1,4 @@ -using E2eTest.Entities; -using E2eTest.Extensions; +using E2eTest.Extensions; using E2eTestWebApp.TestPages; namespace E2eTest; diff --git a/E2eTestWebApp/Program.cs b/E2eTestWebApp/Program.cs index 5ea7563..ae3a29c 100644 --- a/E2eTestWebApp/Program.cs +++ b/E2eTestWebApp/Program.cs @@ -1,5 +1,4 @@ using Magic.IndexedDb; -using Magic.IndexedDb.Extensions; using System.Diagnostics; namespace E2eTestWebApp; diff --git a/E2eTestWebApp/TestPages/OpenTestPage.cs b/E2eTestWebApp/TestPages/OpenTestPage.cs index 1660515..74a9973 100644 --- a/E2eTestWebApp/TestPages/OpenTestPage.cs +++ b/E2eTestWebApp/TestPages/OpenTestPage.cs @@ -1,6 +1,4 @@ using Magic.IndexedDb; -using Magic.IndexedDb.Helpers; -using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components; namespace E2eTestWebApp.TestPages; diff --git a/E2eTestWebApp/TestPages/SingleRecordBasicTestPage.cs b/E2eTestWebApp/TestPages/SingleRecordBasicTestPage.cs index 2f9ca62..f604636 100644 --- a/E2eTestWebApp/TestPages/SingleRecordBasicTestPage.cs +++ b/E2eTestWebApp/TestPages/SingleRecordBasicTestPage.cs @@ -1,9 +1,6 @@ using Magic.IndexedDb; using Magic.IndexedDb.Helpers; -using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components; -using System.Runtime.CompilerServices; -using System.Text.Json.Serialization; using Magic.IndexedDb.SchemaAnnotations; using System.Text.Json; diff --git a/E2eTestWebApp/TestPages/TestPageBase.razor.cs b/E2eTestWebApp/TestPages/TestPageBase.razor.cs index 16cf2c5..0f86056 100644 --- a/E2eTestWebApp/TestPages/TestPageBase.razor.cs +++ b/E2eTestWebApp/TestPages/TestPageBase.razor.cs @@ -1,10 +1,3 @@ -using Magic.IndexedDb; -using Magic.IndexedDb.Helpers; -using Microsoft.AspNetCore.Components.Rendering; -using Microsoft.AspNetCore.Components; -using System.Diagnostics; -using System.Text.Json; - namespace E2eTestWebApp.TestPages; partial class TestPageBase diff --git a/Magic.IndexedDb/Cache.cs b/Magic.IndexedDb/Cache.cs index bae4680..5eb32f9 100644 --- a/Magic.IndexedDb/Cache.cs +++ b/Magic.IndexedDb/Cache.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb; +namespace Magic.IndexedDb; internal static class Cache { diff --git a/Magic.IndexedDb/Extensions/MagicCompoundExtension.cs b/Magic.IndexedDb/Extensions/MagicCompoundExtension.cs index 0c6068f..7771f6e 100644 --- a/Magic.IndexedDb/Extensions/MagicCompoundExtension.cs +++ b/Magic.IndexedDb/Extensions/MagicCompoundExtension.cs @@ -1,10 +1,5 @@ using Magic.IndexedDb.Models; -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; namespace Magic.IndexedDb; diff --git a/Magic.IndexedDb/Extensions/MagicJsChunkProcessor.cs b/Magic.IndexedDb/Extensions/MagicJsChunkProcessor.cs index 934459f..d5f9c51 100644 --- a/Magic.IndexedDb/Extensions/MagicJsChunkProcessor.cs +++ b/Magic.IndexedDb/Extensions/MagicJsChunkProcessor.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Concurrent; namespace Magic.IndexedDb.Extensions; diff --git a/Magic.IndexedDb/Extensions/MagicJsInvoke.cs b/Magic.IndexedDb/Extensions/MagicJsInvoke.cs index d676779..c516da9 100644 --- a/Magic.IndexedDb/Extensions/MagicJsInvoke.cs +++ b/Magic.IndexedDb/Extensions/MagicJsInvoke.cs @@ -2,14 +2,7 @@ using Magic.IndexedDb.Interfaces; using Magic.IndexedDb.Models; using Microsoft.JSInterop; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; -using static System.Runtime.InteropServices.JavaScript.JSType; namespace Magic.IndexedDb.Extensions; diff --git a/Magic.IndexedDb/Extensions/MagicTableTools.cs b/Magic.IndexedDb/Extensions/MagicTableTools.cs index 8622079..68f65bb 100644 --- a/Magic.IndexedDb/Extensions/MagicTableTools.cs +++ b/Magic.IndexedDb/Extensions/MagicTableTools.cs @@ -1,10 +1,5 @@ using Magic.IndexedDb.Interfaces; -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; namespace Magic.IndexedDb; diff --git a/Magic.IndexedDb/Extensions/MagicUtilities.cs b/Magic.IndexedDb/Extensions/MagicUtilities.cs index 70833b5..a5bb832 100644 --- a/Magic.IndexedDb/Extensions/MagicUtilities.cs +++ b/Magic.IndexedDb/Extensions/MagicUtilities.cs @@ -1,10 +1,5 @@ using Magic.IndexedDb.Models; using Microsoft.JSInterop; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Magic.IndexedDb.Extensions; diff --git a/Magic.IndexedDb/Extensions/ServiceCollectionExtensions.cs b/Magic.IndexedDb/Extensions/ServiceCollectionExtensions.cs index 6a44635..4815a70 100644 --- a/Magic.IndexedDb/Extensions/ServiceCollectionExtensions.cs +++ b/Magic.IndexedDb/Extensions/ServiceCollectionExtensions.cs @@ -1,16 +1,6 @@ using Magic.IndexedDb.Factories; -using Magic.IndexedDb.Helpers; -using Magic.IndexedDb.Interfaces; -using Magic.IndexedDb.Models; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.JSInterop; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; namespace Magic.IndexedDb; diff --git a/Magic.IndexedDb/Extensions/SharedQueryExtensions.cs b/Magic.IndexedDb/Extensions/SharedQueryExtensions.cs index 1cc4ad7..ba4b63b 100644 --- a/Magic.IndexedDb/Extensions/SharedQueryExtensions.cs +++ b/Magic.IndexedDb/Extensions/SharedQueryExtensions.cs @@ -1,13 +1,7 @@ using Magic.IndexedDb.Helpers; using Magic.IndexedDb.Models; -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; namespace Magic.IndexedDb.Extensions; diff --git a/Magic.IndexedDb/Factories/MagicDbFactory.cs b/Magic.IndexedDb/Factories/MagicDbFactory.cs index 0e86a6d..bd6cf92 100644 --- a/Magic.IndexedDb/Factories/MagicDbFactory.cs +++ b/Magic.IndexedDb/Factories/MagicDbFactory.cs @@ -1,14 +1,9 @@ -using Microsoft.Extensions.DependencyInjection; using Microsoft.JSInterop; using Magic.IndexedDb.Models; using Magic.IndexedDb.Helpers; using Magic.IndexedDb.Interfaces; using Magic.IndexedDb.Extensions; -using System.Collections.Concurrent; -using System.Reflection; using Magic.IndexedDb.LinqTranslation.Interfaces; -using System.Diagnostics; -using System.Threading; namespace Magic.IndexedDb.Factories; diff --git a/Magic.IndexedDb/Helpers/AttributeHelpers.cs b/Magic.IndexedDb/Helpers/AttributeHelpers.cs index b8792f4..481d851 100644 --- a/Magic.IndexedDb/Helpers/AttributeHelpers.cs +++ b/Magic.IndexedDb/Helpers/AttributeHelpers.cs @@ -1,11 +1,5 @@ using Magic.IndexedDb.Interfaces; -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; namespace Magic.IndexedDb.Helpers; diff --git a/Magic.IndexedDb/Helpers/ExpandoToTypeConverter.cs b/Magic.IndexedDb/Helpers/ExpandoToTypeConverter.cs index 50ae100..ea4c139 100644 --- a/Magic.IndexedDb/Helpers/ExpandoToTypeConverter.cs +++ b/Magic.IndexedDb/Helpers/ExpandoToTypeConverter.cs @@ -1,5 +1,4 @@ -using System.Dynamic; -using System.Linq.Expressions; +using System.Linq.Expressions; namespace Magic.IndexedDb.Helpers; diff --git a/Magic.IndexedDb/Helpers/ExpressionFlattener.cs b/Magic.IndexedDb/Helpers/ExpressionFlattener.cs index a7a55bc..cfc6ee7 100644 --- a/Magic.IndexedDb/Helpers/ExpressionFlattener.cs +++ b/Magic.IndexedDb/Helpers/ExpressionFlattener.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; +using System.Linq.Expressions; namespace Magic.IndexedDb.Helpers; diff --git a/Magic.IndexedDb/Helpers/MagicSerializationHelper.cs b/Magic.IndexedDb/Helpers/MagicSerializationHelper.cs index 3fd4025..54ff7bb 100644 --- a/Magic.IndexedDb/Helpers/MagicSerializationHelper.cs +++ b/Magic.IndexedDb/Helpers/MagicSerializationHelper.cs @@ -1,7 +1,5 @@ using Magic.IndexedDb.Interfaces; using Magic.IndexedDb.Models; -using System.Linq; -using System.Text; using System.Text.Json; namespace Magic.IndexedDb.Helpers; diff --git a/Magic.IndexedDb/Helpers/MagicValidator.cs b/Magic.IndexedDb/Helpers/MagicValidator.cs index 3b2b511..fbf2c8f 100644 --- a/Magic.IndexedDb/Helpers/MagicValidator.cs +++ b/Magic.IndexedDb/Helpers/MagicValidator.cs @@ -1,11 +1,7 @@ using Magic.IndexedDb.Interfaces; using Magic.IndexedDb.SchemaAnnotations; -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; using System.Text; -using System.Threading.Tasks; namespace Magic.IndexedDb.Helpers; diff --git a/Magic.IndexedDb/Helpers/PropertyMappingCache.cs b/Magic.IndexedDb/Helpers/PropertyMappingCache.cs index aa3ed3c..840c58e 100644 --- a/Magic.IndexedDb/Helpers/PropertyMappingCache.cs +++ b/Magic.IndexedDb/Helpers/PropertyMappingCache.cs @@ -1,15 +1,9 @@ using Magic.IndexedDb.Interfaces; using Magic.IndexedDb.Models; using Magic.IndexedDb.SchemaAnnotations; -using System; using System.Collections; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; using System.Reflection; -using System.Text; -using System.Threading.Tasks; namespace Magic.IndexedDb.Helpers; diff --git a/Magic.IndexedDb/Helpers/SchemaHelper.cs b/Magic.IndexedDb/Helpers/SchemaHelper.cs index 1a3d1f0..540f344 100644 --- a/Magic.IndexedDb/Helpers/SchemaHelper.cs +++ b/Magic.IndexedDb/Helpers/SchemaHelper.cs @@ -1,11 +1,6 @@ using Magic.IndexedDb.Interfaces; -using Magic.IndexedDb.SchemaAnnotations; -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; using System.Reflection; -using System.Threading.Tasks; namespace Magic.IndexedDb.Helpers; diff --git a/Magic.IndexedDb/IndexDbManager.cs b/Magic.IndexedDb/IndexDbManager.cs index a5405a3..b231dd3 100644 --- a/Magic.IndexedDb/IndexDbManager.cs +++ b/Magic.IndexedDb/IndexDbManager.cs @@ -1,19 +1,9 @@ -using System.Dynamic; -using System.Linq.Expressions; -using System.Reflection; -using System.Text.Json; -using Magic.IndexedDb.Factories; using Magic.IndexedDb.Helpers; using Magic.IndexedDb.Models; -using Magic.IndexedDb.SchemaAnnotations; using Microsoft.JSInterop; -using System.Text.Json.Nodes; using Magic.IndexedDb.Interfaces; -using static System.Runtime.InteropServices.JavaScript.JSType; -using Microsoft.Extensions.Options; using Magic.IndexedDb.Extensions; using System.Runtime.CompilerServices; -using Magic.IndexedDb.Models.UniversalOperations; using Magic.IndexedDb.LinqTranslation.Interfaces; using Magic.IndexedDb.LinqTranslation.Models; diff --git a/Magic.IndexedDb/Interfaces/IColumnNamed.cs b/Magic.IndexedDb/Interfaces/IColumnNamed.cs index 48bfd25..7d6cfb1 100644 --- a/Magic.IndexedDb/Interfaces/IColumnNamed.cs +++ b/Magic.IndexedDb/Interfaces/IColumnNamed.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb.Interfaces; +namespace Magic.IndexedDb.Interfaces; public interface IColumnNamed { diff --git a/Magic.IndexedDb/Interfaces/IMagicCompoundIndex.cs b/Magic.IndexedDb/Interfaces/IMagicCompoundIndex.cs index 39ee16d..c30f6f8 100644 --- a/Magic.IndexedDb/Interfaces/IMagicCompoundIndex.cs +++ b/Magic.IndexedDb/Interfaces/IMagicCompoundIndex.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; +using System.Reflection; namespace Magic.IndexedDb; diff --git a/Magic.IndexedDb/Interfaces/IMagicCompoundKey.cs b/Magic.IndexedDb/Interfaces/IMagicCompoundKey.cs index 13fdbbb..de77ba4 100644 --- a/Magic.IndexedDb/Interfaces/IMagicCompoundKey.cs +++ b/Magic.IndexedDb/Interfaces/IMagicCompoundKey.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; +using System.Reflection; namespace Magic.IndexedDb; diff --git a/Magic.IndexedDb/Interfaces/IMagicConnectRepository.cs b/Magic.IndexedDb/Interfaces/IMagicConnectRepository.cs index 5af7428..6052fd9 100644 --- a/Magic.IndexedDb/Interfaces/IMagicConnectRepository.cs +++ b/Magic.IndexedDb/Interfaces/IMagicConnectRepository.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb.Interfaces; +namespace Magic.IndexedDb.Interfaces; public interface IMagicConnectRepository { diff --git a/Magic.IndexedDb/Interfaces/IMagicRepository.cs b/Magic.IndexedDb/Interfaces/IMagicRepository.cs index ecfa864..a83861e 100644 --- a/Magic.IndexedDb/Interfaces/IMagicRepository.cs +++ b/Magic.IndexedDb/Interfaces/IMagicRepository.cs @@ -1,11 +1,4 @@ -using Magic.IndexedDb.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb.Interfaces; +namespace Magic.IndexedDb.Interfaces; public interface IMagicRepository { diff --git a/Magic.IndexedDb/Interfaces/IMagicTable.cs b/Magic.IndexedDb/Interfaces/IMagicTable.cs index c22394c..f8194af 100644 --- a/Magic.IndexedDb/Interfaces/IMagicTable.cs +++ b/Magic.IndexedDb/Interfaces/IMagicTable.cs @@ -1,11 +1,4 @@ using Magic.IndexedDb.Interfaces; -using Magic.IndexedDb.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; namespace Magic.IndexedDb; diff --git a/Magic.IndexedDb/Interfaces/IMagicTableBase.cs b/Magic.IndexedDb/Interfaces/IMagicTableBase.cs index ce51f6b..270c5cc 100644 --- a/Magic.IndexedDb/Interfaces/IMagicTableBase.cs +++ b/Magic.IndexedDb/Interfaces/IMagicTableBase.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb.Interfaces; +namespace Magic.IndexedDb.Interfaces; public interface IMagicTableBase { diff --git a/Magic.IndexedDb/Interfaces/IMagicUtilities.cs b/Magic.IndexedDb/Interfaces/IMagicUtilities.cs index bed9efe..f4d56c3 100644 --- a/Magic.IndexedDb/Interfaces/IMagicUtilities.cs +++ b/Magic.IndexedDb/Interfaces/IMagicUtilities.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb; +namespace Magic.IndexedDb; public interface IMagicUtilities { diff --git a/Magic.IndexedDb/Interfaces/ITypedArgument.cs b/Magic.IndexedDb/Interfaces/ITypedArgument.cs index 5b17c65..098f65d 100644 --- a/Magic.IndexedDb/Interfaces/ITypedArgument.cs +++ b/Magic.IndexedDb/Interfaces/ITypedArgument.cs @@ -1,10 +1,5 @@ using Magic.IndexedDb.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Text.Json; -using System.Threading.Tasks; namespace Magic.IndexedDb.Interfaces; diff --git a/Magic.IndexedDb/LinqTranslation/Extensions/MagicCursorExtension.cs b/Magic.IndexedDb/LinqTranslation/Extensions/MagicCursorExtension.cs index 15411bd..73c6807 100644 --- a/Magic.IndexedDb/LinqTranslation/Extensions/MagicCursorExtension.cs +++ b/Magic.IndexedDb/LinqTranslation/Extensions/MagicCursorExtension.cs @@ -1,17 +1,5 @@ -using Magic.IndexedDb.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Magic.IndexedDb.LinqTranslation.Interfaces; -using Magic.IndexedDb.Helpers; -using System.Linq.Expressions; -using System.Reflection; +using System.Linq.Expressions; using System.Runtime.CompilerServices; -using System.Text.Json.Nodes; -using System.Collections; -using Magic.IndexedDb.Models.UniversalOperations; using Magic.IndexedDb.Extensions; namespace Magic.IndexedDb.LinqTranslation.Extensions; diff --git a/Magic.IndexedDb/LinqTranslation/Extensions/MagicQueryExtensions.cs b/Magic.IndexedDb/LinqTranslation/Extensions/MagicQueryExtensions.cs index 44df555..7ce3aef 100644 --- a/Magic.IndexedDb/LinqTranslation/Extensions/MagicQueryExtensions.cs +++ b/Magic.IndexedDb/LinqTranslation/Extensions/MagicQueryExtensions.cs @@ -1,17 +1,6 @@ using Magic.IndexedDb.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Magic.IndexedDb.LinqTranslation.Interfaces; -using Magic.IndexedDb.Helpers; using System.Linq.Expressions; -using System.Reflection; using System.Runtime.CompilerServices; -using System.Text.Json.Nodes; -using System.Collections; -using Magic.IndexedDb.Models.UniversalOperations; using Magic.IndexedDb.LinqTranslation.Models; using Magic.IndexedDb.Extensions; diff --git a/Magic.IndexedDb/LinqTranslation/Extensions/UniversalExpressionBuilder.cs b/Magic.IndexedDb/LinqTranslation/Extensions/UniversalExpressionBuilder.cs index a32f80e..34c24c8 100644 --- a/Magic.IndexedDb/LinqTranslation/Extensions/UniversalExpressionBuilder.cs +++ b/Magic.IndexedDb/LinqTranslation/Extensions/UniversalExpressionBuilder.cs @@ -2,14 +2,8 @@ using Magic.IndexedDb.LinqTranslation.Models; using Magic.IndexedDb.Models; using Magic.IndexedDb.Models.UniversalOperations; -using System; using System.Collections; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Text.Json.Nodes; -using System.Threading.Tasks; namespace Magic.IndexedDb.LinqTranslation.Extensions; diff --git a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicCursor.cs b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicCursor.cs index fec0ad5..18d3789 100644 --- a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicCursor.cs +++ b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicCursor.cs @@ -1,11 +1,4 @@ -using Magic.IndexedDb.LinqTranslation.Interfaces; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; +using System.Linq.Expressions; namespace Magic.IndexedDb; diff --git a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicCursorSkip.cs b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicCursorSkip.cs index 2e0237a..6f6e7c7 100644 --- a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicCursorSkip.cs +++ b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicCursorSkip.cs @@ -1,13 +1,4 @@ -using Magic.IndexedDb.LinqTranslation.Interfaces; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb; +namespace Magic.IndexedDb; public interface IMagicCursorSkip : IMagicExecute where T : class { diff --git a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicCursorStage.cs b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicCursorStage.cs index bda28ea..062bf8f 100644 --- a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicCursorStage.cs +++ b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicCursorStage.cs @@ -1,13 +1,4 @@ -using Magic.IndexedDb.LinqTranslation.Interfaces; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb; +namespace Magic.IndexedDb; public interface IMagicCursorFinal : IMagicExecute where T : class { diff --git a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicDatabase.cs b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicDatabase.cs index 4b6fc6f..06cf7fb 100644 --- a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicDatabase.cs +++ b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicDatabase.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb.LinqTranslation.Interfaces; +namespace Magic.IndexedDb.LinqTranslation.Interfaces; //public interface IMagicDatabaseGlobal //{ // Task DeleteAll(); diff --git a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicExecute.cs b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicExecute.cs index 2801662..45fa947 100644 --- a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicExecute.cs +++ b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicExecute.cs @@ -1,12 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb; +namespace Magic.IndexedDb; public interface IMagicExecute where T : class { diff --git a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQuery.cs b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQuery.cs index c520ab1..0bdc58c 100644 --- a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQuery.cs +++ b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQuery.cs @@ -1,11 +1,4 @@ -using Magic.IndexedDb.LinqTranslation.Interfaces; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; +using System.Linq.Expressions; namespace Magic.IndexedDb; diff --git a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryFinal.cs b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryFinal.cs index 1822158..55509e1 100644 --- a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryFinal.cs +++ b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryFinal.cs @@ -1,11 +1,4 @@ -using Magic.IndexedDb.LinqTranslation.Interfaces; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; +using System.Linq.Expressions; namespace Magic.IndexedDb; diff --git a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryOrderable.cs b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryOrderable.cs index bfd488a..f6ba61e 100644 --- a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryOrderable.cs +++ b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryOrderable.cs @@ -1,11 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; -using Magic.IndexedDb.LinqTranslation.Interfaces; +using System.Linq.Expressions; namespace Magic.IndexedDb; diff --git a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryOrderableTable.cs b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryOrderableTable.cs index 1932e67..71acaed 100644 --- a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryOrderableTable.cs +++ b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryOrderableTable.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; +using System.Linq.Expressions; namespace Magic.IndexedDb; diff --git a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryPaginationTake.cs b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryPaginationTake.cs index 182e604..dd2c898 100644 --- a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryPaginationTake.cs +++ b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryPaginationTake.cs @@ -1,11 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; -using Magic.IndexedDb.LinqTranslation.Interfaces; +using System.Linq.Expressions; namespace Magic.IndexedDb; diff --git a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryStaging.cs b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryStaging.cs index f3e5bfc..b72de28 100644 --- a/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryStaging.cs +++ b/Magic.IndexedDb/LinqTranslation/Interfaces/IMagicQueryStaging.cs @@ -1,11 +1,4 @@ -using Magic.IndexedDb.LinqTranslation.Interfaces; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; +using System.Linq.Expressions; namespace Magic.IndexedDb; diff --git a/Magic.IndexedDb/LinqTranslation/Models/LogicalFilterNode.cs b/Magic.IndexedDb/LinqTranslation/Models/LogicalFilterNode.cs index c80487c..5044620 100644 --- a/Magic.IndexedDb/LinqTranslation/Models/LogicalFilterNode.cs +++ b/Magic.IndexedDb/LinqTranslation/Models/LogicalFilterNode.cs @@ -1,10 +1,4 @@ using Magic.IndexedDb.Models.UniversalOperations; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.Json.Serialization; -using System.Threading.Tasks; namespace Magic.IndexedDb.LinqTranslation.Models; diff --git a/Magic.IndexedDb/LinqTranslation/Models/MagicCursor.cs b/Magic.IndexedDb/LinqTranslation/Models/MagicCursor.cs index 51cce09..abd837b 100644 --- a/Magic.IndexedDb/LinqTranslation/Models/MagicCursor.cs +++ b/Magic.IndexedDb/LinqTranslation/Models/MagicCursor.cs @@ -1,11 +1,6 @@ using Magic.IndexedDb.LinqTranslation.Extensions; -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; namespace Magic.IndexedDb.LinqTranslation.Models; diff --git a/Magic.IndexedDb/LinqTranslation/Models/MagicDatabaseGlobal.cs b/Magic.IndexedDb/LinqTranslation/Models/MagicDatabaseGlobal.cs index 00bc961..ca0b12d 100644 --- a/Magic.IndexedDb/LinqTranslation/Models/MagicDatabaseGlobal.cs +++ b/Magic.IndexedDb/LinqTranslation/Models/MagicDatabaseGlobal.cs @@ -1,11 +1,4 @@ -using Magic.IndexedDb.LinqTranslation.Interfaces; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb.LinqTranslation.Models; +namespace Magic.IndexedDb.LinqTranslation.Models; //internal class MagicDatabaseGlobal : IMagicDatabaseGlobal //{ diff --git a/Magic.IndexedDb/LinqTranslation/Models/MagicDatabaseScoped.cs b/Magic.IndexedDb/LinqTranslation/Models/MagicDatabaseScoped.cs index 26be7ef..1064d54 100644 --- a/Magic.IndexedDb/LinqTranslation/Models/MagicDatabaseScoped.cs +++ b/Magic.IndexedDb/LinqTranslation/Models/MagicDatabaseScoped.cs @@ -1,9 +1,4 @@ using Magic.IndexedDb.LinqTranslation.Interfaces; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Magic.IndexedDb.LinqTranslation.Models; diff --git a/Magic.IndexedDb/LinqTranslation/Models/MagicQuery.cs b/Magic.IndexedDb/LinqTranslation/Models/MagicQuery.cs index 1604af2..8abf32e 100644 --- a/Magic.IndexedDb/LinqTranslation/Models/MagicQuery.cs +++ b/Magic.IndexedDb/LinqTranslation/Models/MagicQuery.cs @@ -1,18 +1,9 @@ -using Magic.IndexedDb.Helpers; -using Magic.IndexedDb.LinqTranslation.Extensions; -using Magic.IndexedDb.LinqTranslation.Interfaces; +using Magic.IndexedDb.LinqTranslation.Extensions; using Magic.IndexedDb.LinqTranslation.Models; using Magic.IndexedDb.Models; -using Magic.IndexedDb.SchemaAnnotations; -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Reflection; using System.Runtime.CompilerServices; -using System.Text; using System.Text.Json; -using System.Threading.Tasks; namespace Magic.IndexedDb; diff --git a/Magic.IndexedDb/LinqTranslation/Models/MagicWhereLinq.cs b/Magic.IndexedDb/LinqTranslation/Models/MagicWhereLinq.cs index 40fce45..95c967b 100644 --- a/Magic.IndexedDb/LinqTranslation/Models/MagicWhereLinq.cs +++ b/Magic.IndexedDb/LinqTranslation/Models/MagicWhereLinq.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb; +namespace Magic.IndexedDb; internal class MagicWhereLinq { diff --git a/Magic.IndexedDb/MagicEncryptAttribute.cs b/Magic.IndexedDb/MagicEncryptAttribute.cs index 5308703..1f7e262 100644 --- a/Magic.IndexedDb/MagicEncryptAttribute.cs +++ b/Magic.IndexedDb/MagicEncryptAttribute.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; +using System.Reflection; namespace Magic.IndexedDb; diff --git a/Magic.IndexedDb/Models/DbMigration.cs b/Magic.IndexedDb/Models/DbMigration.cs index aa7e5b0..ee14e1e 100644 --- a/Magic.IndexedDb/Models/DbMigration.cs +++ b/Magic.IndexedDb/Models/DbMigration.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb; +namespace Magic.IndexedDb; public class DbMigration { diff --git a/Magic.IndexedDb/Models/DbMigrationInstruction.cs b/Magic.IndexedDb/Models/DbMigrationInstruction.cs index 3937843..5245a34 100644 --- a/Magic.IndexedDb/Models/DbMigrationInstruction.cs +++ b/Magic.IndexedDb/Models/DbMigrationInstruction.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb; +namespace Magic.IndexedDb; public class DbMigrationInstruction { diff --git a/Magic.IndexedDb/Models/DbStore.cs b/Magic.IndexedDb/Models/DbStore.cs index 5727923..e3a3739 100644 --- a/Magic.IndexedDb/Models/DbStore.cs +++ b/Magic.IndexedDb/Models/DbStore.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.Json.Serialization; -using System.Threading.Tasks; +using System.Text.Json.Serialization; namespace Magic.IndexedDb; diff --git a/Magic.IndexedDb/Models/IndexFilterValue.cs b/Magic.IndexedDb/Models/IndexFilterValue.cs index 443ae7f..1637a1a 100644 --- a/Magic.IndexedDb/Models/IndexFilterValue.cs +++ b/Magic.IndexedDb/Models/IndexFilterValue.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb.Models; +namespace Magic.IndexedDb.Models; public class IndexFilterValue { diff --git a/Magic.IndexedDb/Models/IndexedDbSet.cs b/Magic.IndexedDb/Models/IndexedDbSet.cs index fd0204b..ae5a20f 100644 --- a/Magic.IndexedDb/Models/IndexedDbSet.cs +++ b/Magic.IndexedDb/Models/IndexedDbSet.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb; +namespace Magic.IndexedDb; public class IndexedDbSet { diff --git a/Magic.IndexedDb/Models/InternalMagicCompoundIndex.cs b/Magic.IndexedDb/Models/InternalMagicCompoundIndex.cs index 8ba69bf..c53f8a9 100644 --- a/Magic.IndexedDb/Models/InternalMagicCompoundIndex.cs +++ b/Magic.IndexedDb/Models/InternalMagicCompoundIndex.cs @@ -1,12 +1,7 @@ using Magic.IndexedDb.Helpers; using Magic.IndexedDb.SchemaAnnotations; -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Text; -using System.Threading.Tasks; namespace Magic.IndexedDb.Models; diff --git a/Magic.IndexedDb/Models/InternalMagicCompoundKey.cs b/Magic.IndexedDb/Models/InternalMagicCompoundKey.cs index 7b0838d..82f3af7 100644 --- a/Magic.IndexedDb/Models/InternalMagicCompoundKey.cs +++ b/Magic.IndexedDb/Models/InternalMagicCompoundKey.cs @@ -1,12 +1,7 @@ using Magic.IndexedDb.Helpers; using Magic.IndexedDb.SchemaAnnotations; -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Text; -using System.Threading.Tasks; namespace Magic.IndexedDb.Models; diff --git a/Magic.IndexedDb/Models/MagicContractResolver.cs b/Magic.IndexedDb/Models/MagicContractResolver.cs index 195197f..60a33c4 100644 --- a/Magic.IndexedDb/Models/MagicContractResolver.cs +++ b/Magic.IndexedDb/Models/MagicContractResolver.cs @@ -1,14 +1,8 @@ -using System; -using System.Collections; +using System.Collections; using System.Collections.Concurrent; -using System.Linq; -using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; using Magic.IndexedDb.Helpers; -using Magic.IndexedDb.Interfaces; -using Magic.IndexedDb.SchemaAnnotations; -using Microsoft.Extensions.Options; namespace Magic.IndexedDb.Models; diff --git a/Magic.IndexedDb/Models/MagicJsPackage.cs b/Magic.IndexedDb/Models/MagicJsPackage.cs index 8f9cb53..644c418 100644 --- a/Magic.IndexedDb/Models/MagicJsPackage.cs +++ b/Magic.IndexedDb/Models/MagicJsPackage.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb.Models; +namespace Magic.IndexedDb.Models; internal class MagicJsPackage { diff --git a/Magic.IndexedDb/Models/PredicateVisitor.cs b/Magic.IndexedDb/Models/PredicateVisitor.cs index b6df0a4..92e3133 100644 --- a/Magic.IndexedDb/Models/PredicateVisitor.cs +++ b/Magic.IndexedDb/Models/PredicateVisitor.cs @@ -1,10 +1,5 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; +using System.Collections; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; namespace Magic.IndexedDb.Models; diff --git a/Magic.IndexedDb/Models/QuotaUsage.cs b/Magic.IndexedDb/Models/QuotaUsage.cs index 75cf39e..da380b9 100644 --- a/Magic.IndexedDb/Models/QuotaUsage.cs +++ b/Magic.IndexedDb/Models/QuotaUsage.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb.Models; +namespace Magic.IndexedDb.Models; public sealed record QuotaUsage(long Quota, long Usage) { private static double ConvertBytesToMegabytes(long bytes) diff --git a/Magic.IndexedDb/Models/StoredMagicQuery.cs b/Magic.IndexedDb/Models/StoredMagicQuery.cs index 9d0ba5f..490f6bf 100644 --- a/Magic.IndexedDb/Models/StoredMagicQuery.cs +++ b/Magic.IndexedDb/Models/StoredMagicQuery.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb.Models; +namespace Magic.IndexedDb.Models; internal struct MagicQueryFunctions { diff --git a/Magic.IndexedDb/Models/Structs/MagicAttributesEntry.cs b/Magic.IndexedDb/Models/Structs/MagicAttributesEntry.cs index 9e1a5d9..c4d99b0 100644 --- a/Magic.IndexedDb/Models/Structs/MagicAttributesEntry.cs +++ b/Magic.IndexedDb/Models/Structs/MagicAttributesEntry.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb.Models.Structs; +namespace Magic.IndexedDb.Models.Structs; public struct MagicAttributesEntry { diff --git a/Magic.IndexedDb/Models/Structs/MagicPropertyEntry.cs b/Magic.IndexedDb/Models/Structs/MagicPropertyEntry.cs index 3a7b8f2..4898e61 100644 --- a/Magic.IndexedDb/Models/Structs/MagicPropertyEntry.cs +++ b/Magic.IndexedDb/Models/Structs/MagicPropertyEntry.cs @@ -1,14 +1,7 @@ using Magic.IndexedDb.Helpers; using Magic.IndexedDb.Interfaces; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.Metrics; -using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Text; -using System.Threading.Tasks; namespace Magic.IndexedDb.Models; diff --git a/Magic.IndexedDb/Models/Structs/MagicPropertySearchEntry.cs b/Magic.IndexedDb/Models/Structs/MagicPropertySearchEntry.cs index 252ace3..31168f7 100644 --- a/Magic.IndexedDb/Models/Structs/MagicPropertySearchEntry.cs +++ b/Magic.IndexedDb/Models/Structs/MagicPropertySearchEntry.cs @@ -1,10 +1,5 @@ using Magic.IndexedDb.Interfaces; -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; -using System.Text; -using System.Threading.Tasks; namespace Magic.IndexedDb.Models; diff --git a/Magic.IndexedDb/Models/Structs/MagicTableEntry.cs b/Magic.IndexedDb/Models/Structs/MagicTableEntry.cs index df7956f..d1c1861 100644 --- a/Magic.IndexedDb/Models/Structs/MagicTableEntry.cs +++ b/Magic.IndexedDb/Models/Structs/MagicTableEntry.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb.Models; +namespace Magic.IndexedDb.Models; public struct MagicTableEntry { diff --git a/Magic.IndexedDb/Models/TypedArgument.cs b/Magic.IndexedDb/Models/TypedArgument.cs index 929d1b4..6074336 100644 --- a/Magic.IndexedDb/Models/TypedArgument.cs +++ b/Magic.IndexedDb/Models/TypedArgument.cs @@ -1,11 +1,6 @@ using Magic.IndexedDb.Helpers; using Magic.IndexedDb.Interfaces; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Text.Json; -using System.Threading.Tasks; namespace Magic.IndexedDb.Models; diff --git a/Magic.IndexedDb/Models/UniversalOperations/AndFilterGroup.cs b/Magic.IndexedDb/Models/UniversalOperations/AndFilterGroup.cs index 8cdfa4d..88a00c0 100644 --- a/Magic.IndexedDb/Models/UniversalOperations/AndFilterGroup.cs +++ b/Magic.IndexedDb/Models/UniversalOperations/AndFilterGroup.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb.Models.UniversalOperations; +namespace Magic.IndexedDb.Models.UniversalOperations; /// /// Represents a group of conditions that are connected using AND (&&). diff --git a/Magic.IndexedDb/Models/UniversalOperations/FilterCondition.cs b/Magic.IndexedDb/Models/UniversalOperations/FilterCondition.cs index 82c26b9..22ed704 100644 --- a/Magic.IndexedDb/Models/UniversalOperations/FilterCondition.cs +++ b/Magic.IndexedDb/Models/UniversalOperations/FilterCondition.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb.Models.UniversalOperations; +namespace Magic.IndexedDb.Models.UniversalOperations; /// /// Represents a single filtering condition applied to a property. diff --git a/Magic.IndexedDb/Models/UniversalOperations/NestedOrFilter.cs b/Magic.IndexedDb/Models/UniversalOperations/NestedOrFilter.cs index 4f9dc77..03307bc 100644 --- a/Magic.IndexedDb/Models/UniversalOperations/NestedOrFilter.cs +++ b/Magic.IndexedDb/Models/UniversalOperations/NestedOrFilter.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb.Models.UniversalOperations; +namespace Magic.IndexedDb.Models.UniversalOperations; /// /// Supported in Magic IndexDB Blazor C# code, but not currently supported purely through the diff --git a/Magic.IndexedDb/Models/UniversalOperations/OrFilterGroup.cs b/Magic.IndexedDb/Models/UniversalOperations/OrFilterGroup.cs index 78f93ad..39b618a 100644 --- a/Magic.IndexedDb/Models/UniversalOperations/OrFilterGroup.cs +++ b/Magic.IndexedDb/Models/UniversalOperations/OrFilterGroup.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb.Models.UniversalOperations; +namespace Magic.IndexedDb.Models.UniversalOperations; /// /// Represents a group of AND-filter groups that are connected using OR (||). diff --git a/Magic.IndexedDb/SchemaAnnotations/MagicIndexAttribute.cs b/Magic.IndexedDb/SchemaAnnotations/MagicIndexAttribute.cs index e2bbabd..d51ac82 100644 --- a/Magic.IndexedDb/SchemaAnnotations/MagicIndexAttribute.cs +++ b/Magic.IndexedDb/SchemaAnnotations/MagicIndexAttribute.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Magic.IndexedDb.Interfaces; -using Magic.IndexedDb.SchemaAnnotations; +using Magic.IndexedDb.Interfaces; namespace Magic.IndexedDb.SchemaAnnotations; diff --git a/Magic.IndexedDb/SchemaAnnotations/MagicNameAttribute.cs b/Magic.IndexedDb/SchemaAnnotations/MagicNameAttribute.cs index db88e6b..f13bb99 100644 --- a/Magic.IndexedDb/SchemaAnnotations/MagicNameAttribute.cs +++ b/Magic.IndexedDb/SchemaAnnotations/MagicNameAttribute.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Magic.IndexedDb.Interfaces; -using Magic.IndexedDb.SchemaAnnotations; +using Magic.IndexedDb.Interfaces; namespace Magic.IndexedDb.SchemaAnnotations; diff --git a/Magic.IndexedDb/SchemaAnnotations/MagicNotMappedAttribute.cs b/Magic.IndexedDb/SchemaAnnotations/MagicNotMappedAttribute.cs index 95a3730..03fb354 100644 --- a/Magic.IndexedDb/SchemaAnnotations/MagicNotMappedAttribute.cs +++ b/Magic.IndexedDb/SchemaAnnotations/MagicNotMappedAttribute.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb.SchemaAnnotations; +namespace Magic.IndexedDb.SchemaAnnotations; public class MagicNotMappedAttribute : Attribute { } \ No newline at end of file diff --git a/Magic.IndexedDb/SchemaAnnotations/MagicPrimaryKeyAttribute.cs b/Magic.IndexedDb/SchemaAnnotations/MagicPrimaryKeyAttribute.cs index 034f4ac..25351bb 100644 --- a/Magic.IndexedDb/SchemaAnnotations/MagicPrimaryKeyAttribute.cs +++ b/Magic.IndexedDb/SchemaAnnotations/MagicPrimaryKeyAttribute.cs @@ -1,12 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Magic.IndexedDb.Interfaces; -using Magic.IndexedDb.SchemaAnnotations; - -namespace Magic.IndexedDb; +namespace Magic.IndexedDb; /// /// sets as the primary key diff --git a/Magic.IndexedDb/SchemaAnnotations/MagicTableAttribute.cs b/Magic.IndexedDb/SchemaAnnotations/MagicTableAttribute.cs index c142c23..69f5e8a 100644 --- a/Magic.IndexedDb/SchemaAnnotations/MagicTableAttribute.cs +++ b/Magic.IndexedDb/SchemaAnnotations/MagicTableAttribute.cs @@ -1,11 +1,4 @@ -using Magic.IndexedDb.Helpers; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb; +namespace Magic.IndexedDb; /*public class MagicTableAttribute : Attribute { diff --git a/Magic.IndexedDb/SchemaAnnotations/MagicUniqueIndexAttribute.cs b/Magic.IndexedDb/SchemaAnnotations/MagicUniqueIndexAttribute.cs index 9f6e146..1bf3e32 100644 --- a/Magic.IndexedDb/SchemaAnnotations/MagicUniqueIndexAttribute.cs +++ b/Magic.IndexedDb/SchemaAnnotations/MagicUniqueIndexAttribute.cs @@ -1,10 +1,4 @@ using Magic.IndexedDb.Interfaces; -using Magic.IndexedDb.SchemaAnnotations; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Magic.IndexedDb; diff --git a/Magic.IndexedDb/SnapshotBuilder/BuildTools.cs b/Magic.IndexedDb/SnapshotBuilder/BuildTools.cs index 9ce8788..6522bed 100644 --- a/Magic.IndexedDb/SnapshotBuilder/BuildTools.cs +++ b/Magic.IndexedDb/SnapshotBuilder/BuildTools.cs @@ -1,11 +1,6 @@ using Magic.IndexedDb.Helpers; -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; -using System.Text; using System.Text.Json; -using System.Threading.Tasks; namespace Magic.IndexedDb.SnapshotBuilder; diff --git a/Magic.IndexedDb/Testing/Helpers/AllPaths.cs b/Magic.IndexedDb/Testing/Helpers/AllPaths.cs index f255f18..d646cb4 100644 --- a/Magic.IndexedDb/Testing/Helpers/AllPaths.cs +++ b/Magic.IndexedDb/Testing/Helpers/AllPaths.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; +using System.Linq.Expressions; namespace Magic.IndexedDb.Testing.Helpers; diff --git a/Magic.IndexedDb/Testing/Helpers/MagicQueryPathWalker.cs b/Magic.IndexedDb/Testing/Helpers/MagicQueryPathWalker.cs index 8b46c9e..45c68dc 100644 --- a/Magic.IndexedDb/Testing/Helpers/MagicQueryPathWalker.cs +++ b/Magic.IndexedDb/Testing/Helpers/MagicQueryPathWalker.cs @@ -1,9 +1,4 @@ using Magic.IndexedDb.Interfaces; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Magic.IndexedDb.Testing.Helpers; diff --git a/Magic.IndexedDb/Testing/Helpers/TestHelper.cs b/Magic.IndexedDb/Testing/Helpers/TestHelper.cs index b539eb4..73f7ba8 100644 --- a/Magic.IndexedDb/Testing/Helpers/TestHelper.cs +++ b/Magic.IndexedDb/Testing/Helpers/TestHelper.cs @@ -2,14 +2,8 @@ using Magic.IndexedDb.Models; using Magic.IndexedDb.SchemaAnnotations; using Magic.IndexedDb.Testing.Models; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; using System.Reflection; -using System.Text; using System.Text.Json; -using System.Threading.Tasks; namespace Magic.IndexedDb.Testing.Helpers; diff --git a/Magic.IndexedDb/Testing/Models/TestResponse.cs b/Magic.IndexedDb/Testing/Models/TestResponse.cs index 8e0f321..db0a11c 100644 --- a/Magic.IndexedDb/Testing/Models/TestResponse.cs +++ b/Magic.IndexedDb/Testing/Models/TestResponse.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Magic.IndexedDb.Testing.Models; +namespace Magic.IndexedDb.Testing.Models; public class TestResponse { diff --git a/TestServer/TestServer/Components/Pages/CarTests.razor b/TestServer/TestServer/Components/Pages/CarTests.razor index 62e429d..9061e6a 100644 --- a/TestServer/TestServer/Components/Pages/CarTests.razor +++ b/TestServer/TestServer/Components/Pages/CarTests.razor @@ -1,10 +1,8 @@ @page "/CarTests" @using Magic.IndexedDb @using Magic.IndexedDb.Testing.Helpers -@using System.Text.Json @using Magic.IndexedDb.Testing.Models @using TestWasm.Models -@using TestWasm.Repository @using System.Linq; @rendermode InteractiveServer diff --git a/TestServer/TestServer/Components/Pages/Home.razor b/TestServer/TestServer/Components/Pages/Home.razor index 82c1b01..4568016 100644 --- a/TestServer/TestServer/Components/Pages/Home.razor +++ b/TestServer/TestServer/Components/Pages/Home.razor @@ -1,10 +1,8 @@ @page "/" @using Magic.IndexedDb @using Magic.IndexedDb.Testing.Helpers -@using System.Text.Json @using Magic.IndexedDb.Testing.Models @using TestWasm.Models -@using TestWasm.Repository @using System.Linq; @rendermode InteractiveServer diff --git a/TestServer/TestServer/Program.cs b/TestServer/TestServer/Program.cs index ee3a8cb..fad4978 100644 --- a/TestServer/TestServer/Program.cs +++ b/TestServer/TestServer/Program.cs @@ -1,6 +1,5 @@ using Magic.IndexedDb; using MudBlazor.Services; -using TestServer.Client.Pages; using TestServer.Components; var builder = WebApplication.CreateBuilder(args); diff --git a/TestServer/TestServer/Repository/IndexDbContext.cs b/TestServer/TestServer/Repository/IndexDbContext.cs index 690bc7d..59a007d 100644 --- a/TestServer/TestServer/Repository/IndexDbContext.cs +++ b/TestServer/TestServer/Repository/IndexDbContext.cs @@ -1,6 +1,5 @@ using Magic.IndexedDb; using Magic.IndexedDb.Interfaces; -using TestWasm.Models; namespace TestWasm.Repository; diff --git a/TestWasm/Pages/Home.razor b/TestWasm/Pages/Home.razor index a50baad..2f204cd 100644 --- a/TestWasm/Pages/Home.razor +++ b/TestWasm/Pages/Home.razor @@ -1,7 +1,6 @@ @page "/" @using Magic.IndexedDb @using Magic.IndexedDb.Testing.Helpers -@using System.Text.Json @using Magic.IndexedDb.Testing.Models @using TestWasm.Models @using TestWasm.Repository diff --git a/TestWasm/Program.cs b/TestWasm/Program.cs index 73e1c8a..6f79b39 100644 --- a/TestWasm/Program.cs +++ b/TestWasm/Program.cs @@ -1,10 +1,8 @@ using Magic.IndexedDb; -using Magic.IndexedDb.Helpers; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using MudBlazor.Services; using TestWasm; -using TestWasm.Models; var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("#app"); diff --git a/TestWasm/Repository/IndexDbContext.cs b/TestWasm/Repository/IndexDbContext.cs index 690bc7d..59a007d 100644 --- a/TestWasm/Repository/IndexDbContext.cs +++ b/TestWasm/Repository/IndexDbContext.cs @@ -1,6 +1,5 @@ using Magic.IndexedDb; using Magic.IndexedDb.Interfaces; -using TestWasm.Models; namespace TestWasm.Repository; diff --git a/TestWasm/Share/PanelCustomTests.razor b/TestWasm/Share/PanelCustomTests.razor index f23b037..70eae34 100644 --- a/TestWasm/Share/PanelCustomTests.razor +++ b/TestWasm/Share/PanelCustomTests.razor @@ -1,5 +1,4 @@ @using Magic.IndexedDb -@using Magic.IndexedDb.Interfaces @using Magic.IndexedDb.Testing.Helpers @using Magic.IndexedDb.Testing.Models @using TestWasm.Models From 1ddc9ec779d3c1f4af7d57c81a81d69bd2bd52f3 Mon Sep 17 00:00:00 2001 From: Ard van der Marel <109339598+Ard2025@users.noreply.github.com> Date: Wed, 4 Jun 2025 11:57:19 +0200 Subject: [PATCH 3/3] minor syntax improvements --- .../Extensions/MagicJsChunkProcessor.cs | 2 - Magic.IndexedDb/Extensions/MagicJsInvoke.cs | 6 - Magic.IndexedDb/Extensions/MagicUtilities.cs | 1 - .../Helpers/PropertyMappingCache.cs | 78 ----- Magic.IndexedDb/Helpers/SchemaHelper.cs | 2 - .../Extensions/MagicQueryExtensions.cs | 3 - .../Extensions/UniversalExpressionBuilder.cs | 9 - .../LinqTranslation/Models/MagicCursor.cs | 2 - .../Models/MagicContractResolver.cs | 5 - Magic.IndexedDb/Testing/Helpers/TestHelper.cs | 4 - .../Components/Layout/NavMenu.razor | 6 - .../Components/Pages/CarTests.razor | 9 +- .../TestServer/Components/Pages/Home.razor | 296 ++++++++---------- .../TestServer/Components/Pages/Weather.razor | 64 ---- 14 files changed, 142 insertions(+), 345 deletions(-) delete mode 100644 TestServer/TestServer/Components/Pages/Weather.razor diff --git a/Magic.IndexedDb/Extensions/MagicJsChunkProcessor.cs b/Magic.IndexedDb/Extensions/MagicJsChunkProcessor.cs index d5f9c51..996bf39 100644 --- a/Magic.IndexedDb/Extensions/MagicJsChunkProcessor.cs +++ b/Magic.IndexedDb/Extensions/MagicJsChunkProcessor.cs @@ -74,8 +74,6 @@ public static void AddChunk(string instanceId, string chunkInstanceId, int yield return null; } - - public static void RemoveInstance(string instanceId) { _chunkedMessages.TryRemove(instanceId, out _); diff --git a/Magic.IndexedDb/Extensions/MagicJsInvoke.cs b/Magic.IndexedDb/Extensions/MagicJsInvoke.cs index c516da9..cf6eaf3 100644 --- a/Magic.IndexedDb/Extensions/MagicJsInvoke.cs +++ b/Magic.IndexedDb/Extensions/MagicJsInvoke.cs @@ -219,8 +219,6 @@ internal async Task CallInvokeVoidDefaultJsAsync(string modulePath, string funct } } - - [JSInvokable("ProcessJsChunk")] public Task ProcessJsChunk(string instanceId, string chunkInstanceId, int yieldOrderIndex, string chunk, int chunkIndex, int totalChunks) { @@ -234,10 +232,6 @@ public Task ProcessJsChunk(string instanceId, string chunkInstanceId, int yieldO return Task.CompletedTask; } - - - - private async Task MagicVoidStreamJsAsync(string modulePath, string functionName, CancellationToken token, params ITypedArgument[] args) { await TrueMagicStreamJsAsync(modulePath, functionName, token, true, args); diff --git a/Magic.IndexedDb/Extensions/MagicUtilities.cs b/Magic.IndexedDb/Extensions/MagicUtilities.cs index a5bb832..401d800 100644 --- a/Magic.IndexedDb/Extensions/MagicUtilities.cs +++ b/Magic.IndexedDb/Extensions/MagicUtilities.cs @@ -19,7 +19,6 @@ public MagicUtilities(IJSObjectReference jsRuntime, long jsMessageSizeBytes) _jsMessageSizeBytes = jsMessageSizeBytes; } - /// /// Returns Mb /// diff --git a/Magic.IndexedDb/Helpers/PropertyMappingCache.cs b/Magic.IndexedDb/Helpers/PropertyMappingCache.cs index 840c58e..475d321 100644 --- a/Magic.IndexedDb/Helpers/PropertyMappingCache.cs +++ b/Magic.IndexedDb/Helpers/PropertyMappingCache.cs @@ -213,26 +213,6 @@ private static bool IsAnonymousType(Type type) && type.Namespace == null; } - - - - - - /* - public static bool IsSimpleType(Type type) - { - return type.IsPrimitive || - type.IsEnum || - type == typeof(string) || - type == typeof(decimal) || - type == typeof(DateTime) || - type == typeof(DateTimeOffset) || - type == typeof(Guid) || - type == typeof(Uri) || - type == typeof(TimeSpan); - }*/ - - public static IEnumerable GetAllNestedComplexTypes(IEnumerable properties) { HashSet complexTypes = new(); @@ -276,63 +256,6 @@ public static bool IsComplexType(Type type) || type.IsArray); // Arrays are collections too } - /*private static readonly ConcurrentDictionary _complexTypeCache = new(); - - public static bool IsComplexType(Type type) - { - return _complexTypeCache.GetOrAdd(type, t => - { - if (IsSimpleType(t) || t == typeof(string)) - return false; - - if (t.IsGenericType) - { - Type genericTypeDef = t.GetGenericTypeDefinition(); - if (typeof(IEnumerable<>).IsAssignableFrom(genericTypeDef)) - { - return IsComplexType(t.GetGenericArguments()[0]); - } - return t.GetGenericArguments().Any(IsComplexType); - } - - if (typeof(IEnumerable).IsAssignableFrom(t) || t.IsArray) - return false; - - return true; - }); - }*/ - - - /*public static bool IsComplexType(Type type) - { - if (IsSimpleType(type) || type == typeof(string)) - return false; - - // Handle generic collections like List, Dictionary - if (type.IsGenericType) - { - Type genericTypeDef = type.GetGenericTypeDefinition(); - - // If it's a generic IEnumerable, get the type argument and check if it's complex - if (typeof(IEnumerable<>).IsAssignableFrom(genericTypeDef)) - { - Type itemType = type.GetGenericArguments()[0]; - return IsComplexType(itemType); - } - - // Otherwise, it might be a generic class like StoreRecord - return type.GetGenericArguments().Any(IsComplexType); - } - - // Handle non-generic collections like arrays - if (typeof(IEnumerable).IsAssignableFrom(type) || type.IsArray) - return false; - - return true; // Consider anything else a complex object - }*/ - - - /// /// Gets the C# property name given a JavaScript property name. /// @@ -491,7 +414,6 @@ public static MagicPropertyEntry GetPropertyEntry(PropertyInfo property, Type ty return GetPropertyEntry(property.Name, type); } - /// /// Ensures that both schema and property caches are built for the given type. /// diff --git a/Magic.IndexedDb/Helpers/SchemaHelper.cs b/Magic.IndexedDb/Helpers/SchemaHelper.cs index 540f344..9d504d5 100644 --- a/Magic.IndexedDb/Helpers/SchemaHelper.cs +++ b/Magic.IndexedDb/Helpers/SchemaHelper.cs @@ -20,7 +20,6 @@ internal static void EnsureSchemaIsCached(Type type) }); } - public static bool ImplementsIMagicTable(Type type) { return type.GetInterfaces().Any(i => i.IsGenericType @@ -178,7 +177,6 @@ public static List GetAllSchemas(string? databaseName = null) return schemas; } - /// /// Retrieves the store schema from a given type. /// Now fully optimized by leveraging cached attributes and property mappings. diff --git a/Magic.IndexedDb/LinqTranslation/Extensions/MagicQueryExtensions.cs b/Magic.IndexedDb/LinqTranslation/Extensions/MagicQueryExtensions.cs index 7ce3aef..c0b9088 100644 --- a/Magic.IndexedDb/LinqTranslation/Extensions/MagicQueryExtensions.cs +++ b/Magic.IndexedDb/LinqTranslation/Extensions/MagicQueryExtensions.cs @@ -152,14 +152,11 @@ private FilterNode GetCollectedBinaryJsonExpressions() return result; } - - private bool IsUniversalFalse(Expression> predicate) { return predicate.Body is ConstantExpression constant && constant.Value is bool value && !value; } - private Expression> PreprocessPredicate() { Expression> predicate = MagicQuery.GetFinalPredicate(); diff --git a/Magic.IndexedDb/LinqTranslation/Extensions/UniversalExpressionBuilder.cs b/Magic.IndexedDb/LinqTranslation/Extensions/UniversalExpressionBuilder.cs index 34c24c8..3b1dc53 100644 --- a/Magic.IndexedDb/LinqTranslation/Extensions/UniversalExpressionBuilder.cs +++ b/Magic.IndexedDb/LinqTranslation/Extensions/UniversalExpressionBuilder.cs @@ -293,7 +293,6 @@ private bool TryRecognizeSpecialOperation(BinaryExpression bin, string operation { node = null!; - if (TryRecognizeLengthProperty(bin, operation, out node)) return true; @@ -307,7 +306,6 @@ private bool TryRecognizeSpecialOperation(BinaryExpression bin, string operation return false; } - private bool TryRecognizeLengthProperty(BinaryExpression bin, string operation, out FilterNode node) { node = null!; @@ -382,7 +380,6 @@ private static string ExtractRootProperty(List path) : path[^2]; } - private bool TryRecognizeDateProperty(BinaryExpression bin, string operation, out FilterNode node) { node = null!; @@ -472,15 +469,12 @@ private FilterNode BuildDateYearNode(string jsProp, object value, string operati }; } - - private static bool IsDateType(Type type) { var actual = Nullable.GetUnderlyingType(type) ?? type; return actual == typeof(DateTime) || actual == typeof(DateOnly); } - private FilterNode BuildDateEqualityRange(string jsProp, object rawConst, string op) { if (rawConst is not DateTime dt) @@ -564,7 +558,6 @@ private static Expression UnwrapConvert(Expression expr) return expr; } - private List? GetMemberAccessPath(Expression expr) { var path = new List(); @@ -577,8 +570,6 @@ private static Expression UnwrapConvert(Expression expr) return expr is ParameterExpression ? path : null; } - - // ------------------------------ // "Contains" flattening logic: // ------------------------------ diff --git a/Magic.IndexedDb/LinqTranslation/Models/MagicCursor.cs b/Magic.IndexedDb/LinqTranslation/Models/MagicCursor.cs index abd837b..a7e9487 100644 --- a/Magic.IndexedDb/LinqTranslation/Models/MagicCursor.cs +++ b/Magic.IndexedDb/LinqTranslation/Models/MagicCursor.cs @@ -11,12 +11,10 @@ public MagicCursor(MagicQuery _magicQuery) { _magicQuery.ForceCursorMode = true; MagicQuery = _magicQuery; - } public IMagicCursor Cursor(Expression> predicate) { - var _MagicQuery = new MagicQuery(MagicQuery); _MagicQuery.Predicates.Add(predicate); return new MagicCursor(_MagicQuery); // Enable method chaining diff --git a/Magic.IndexedDb/Models/MagicContractResolver.cs b/Magic.IndexedDb/Models/MagicContractResolver.cs index 60a33c4..3d694a0 100644 --- a/Magic.IndexedDb/Models/MagicContractResolver.cs +++ b/Magic.IndexedDb/Models/MagicContractResolver.cs @@ -183,9 +183,6 @@ private bool IsSimpleJsonNull(JsonElement element) return _defaultValues.GetOrAdd(type, t => t.IsValueType ? Activator.CreateInstance(t) : null); } - - - /// /// Reads and assigns a property value, detecting collections, simple types, and complex objects. /// @@ -462,7 +459,6 @@ private void SerializeComplexProperties(Utf8JsonWriter writer, object value, Dic } } - private bool IsDefaultValue(object? value, MagicPropertyEntry mpe) { if (value == null) @@ -470,5 +466,4 @@ private bool IsDefaultValue(object? value, MagicPropertyEntry mpe) return value.Equals(mpe.DefaultValue); // ✅ Use precomputed default value } - } \ No newline at end of file diff --git a/Magic.IndexedDb/Testing/Helpers/TestHelper.cs b/Magic.IndexedDb/Testing/Helpers/TestHelper.cs index 73f7ba8..4647aeb 100644 --- a/Magic.IndexedDb/Testing/Helpers/TestHelper.cs +++ b/Magic.IndexedDb/Testing/Helpers/TestHelper.cs @@ -76,7 +76,6 @@ public static TestResponse ValidateLists(IEnumerable correctResults, IEnum } } - return failureDetails.Any() ? new TestResponse { Success = false, Message = string.Join("\n\n", failureDetails) } : new TestResponse { Success = true }; @@ -92,9 +91,6 @@ private static string FormatKey(List<(string Property, object Value)> keyValues) .Select(kv => $"{kv.Property}={kv.Value?.ToString() ?? "NULL"}")); } - - - private static List CompareObjects(object expected, object actual, PropertyInfo[] properties, string path) { var differences = new List(); diff --git a/TestServer/TestServer/Components/Layout/NavMenu.razor b/TestServer/TestServer/Components/Layout/NavMenu.razor index 3b2a5b0..683b639 100644 --- a/TestServer/TestServer/Components/Layout/NavMenu.razor +++ b/TestServer/TestServer/Components/Layout/NavMenu.razor @@ -25,12 +25,6 @@ Counter - - diff --git a/TestServer/TestServer/Components/Pages/CarTests.razor b/TestServer/TestServer/Components/Pages/CarTests.razor index 9061e6a..f99bd66 100644 --- a/TestServer/TestServer/Components/Pages/CarTests.razor +++ b/TestServer/TestServer/Components/Pages/CarTests.razor @@ -135,10 +135,11 @@ IMagicQuery carQuery = await _MagicDb.Query(); await carQuery.ClearTable(); - List cars = []; - cars.Add(new Car { Brand = "Audi", Created = DateTime.UtcNow.AddDays(1) }); - cars.Add(new Car { Brand = "Toyota", Created = DateTime.UtcNow.AddDays(2) }); - cars.Add(new Car { Brand = "Ferrari", Created = DateTime.UtcNow.AddDays(3) }); + List cars = [ + new Car { Brand = "Audi", Created = DateTime.UtcNow.AddDays(1) }, + new Car { Brand = "Toyota", Created = DateTime.UtcNow.AddDays(2) }, + new Car { Brand = "Ferrari", Created = DateTime.UtcNow.AddDays(3) } + ]; await carQuery.AddRangeAsync(cars); diff --git a/TestServer/TestServer/Components/Pages/Home.razor b/TestServer/TestServer/Components/Pages/Home.razor index 4568016..938d2be 100644 --- a/TestServer/TestServer/Components/Pages/Home.razor +++ b/TestServer/TestServer/Components/Pages/Home.razor @@ -156,125 +156,125 @@ { if (firstRender) { - // Targets the default database automatically if called without any parameters. - IMagicQuery personQuery = await _MagicDb.Query(); - await personQuery.ClearTable(); - - // Choose an assigned database to Person with strictly typed enforced connected Db's. - IMagicQuery employeeDbQuery = await _MagicDb.Query(x => x.Databases.Client); - - // Not implemented yet - // // // Highly not suggested, but you're allowed to target databases not assigned to the Person table - // IMagicQuery animalDbQuery = await _MagicDb.Query(IndexDbContext.Animal); - - // // // DO NOT DO THIS! I only am allowing this for maximum flexibility but this is very dangerous. - // IMagicQuery unassignedDbQuery = await _MagicDb.QueryOverride("DbName", "SchemaName"); - - - - - Person[] persons = new Person[] { - new Person { Name = "Zack", DateOfBirth = null, TestInt = 9, _Age = 45, GUIY = Guid.NewGuid(), DoNotMapTest = "I buried treasure behind my house", Access=Person.Permissions.CanRead}, - new Person { Name = "Luna", TestInt = 9, DateOfBirth = GetDateWithSameMonthDay(GetRandomYear()), _Age = 35, GUIY = Guid.NewGuid(), DoNotMapTest = "Jerry is my husband and I had an affair with Bob.", Access = Person.Permissions.CanRead|Person.Permissions.CanWrite}, - new Person { Name = "Jerry", TestInt = 9, DateOfBirth = GetDateWithSameMonthDay(GetRandomYear()), _Age = 35, GUIY = Guid.NewGuid(), DoNotMapTest = "My wife is amazing", Access = Person.Permissions.CanRead|Person.Permissions.CanWrite|Person.Permissions.CanCreate}, - new Person { Name = "Jamie", TestInt = 9, DateOfBirth = GetDateWithSameMonthDay(GetRandomYear()), _Age = 35, GUIY = Guid.NewGuid(), DoNotMapTest = "My wife is amazing", Access = Person.Permissions.CanRead|Person.Permissions.CanWrite|Person.Permissions.CanCreate}, - new Person { Name = "James", TestInt = 9, DateOfBirth = GetDateWithSameMonthDay(GetRandomYear()), _Age = 35, GUIY = Guid.NewGuid(), DoNotMapTest = "My wife is amazing", Access = Person.Permissions.CanRead|Person.Permissions.CanWrite|Person.Permissions.CanCreate}, - new Person { Name = "Jack", TestInt = 9, DateOfBirth = GetDateWithSameMonthDay(GetRandomYear()), _Age = 35, GUIY = Guid.NewGuid(), DoNotMapTest = "My wife is amazing", Access = Person.Permissions.CanRead|Person.Permissions.CanWrite|Person.Permissions.CanCreate}, - new Person { Name = "Jon", TestInt = 9, DateOfBirth = GetDateWithSameMonthDay(GetRandomYear()), _Age = 37, GUIY = Guid.NewGuid(), DoNotMapTest = "I black mail Luna for money because I know her secret", Access = Person.Permissions.CanRead}, - new Person { Name = "Jack", TestInt = 9, DateOfBirth = GetDateWithSameMonthDay(GetRandomYear()), _Age = 37, GUIY = Guid.NewGuid(), DoNotMapTest = "I have a drug problem", Access = Person.Permissions.CanRead|Person.Permissions.CanWrite}, - new Person { Name = "Cathy", TestInt = 9, DateOfBirth = GetDateWithSameMonthDay(GetRandomYear()), _Age = 22, GUIY = Guid.NewGuid(), DoNotMapTest = "I got away with reading Bobs diary.", Access = Person.Permissions.CanRead | Person.Permissions.CanWrite}, - new Person { Name = "Bob", TestInt = 3 , DateOfBirth = GetDateWithSameMonthDay(GetRandomYear()), _Age = 69, GUIY = Guid.NewGuid(), DoNotMapTest = "I caught Cathy reading my diary, but I'm too shy to confront her.", Access = Person.Permissions.CanRead }, - new Person { Name = "Alex", TestInt = 3 , DateOfBirth = null, _Age = 80, GUIY = Guid.NewGuid(), DoNotMapTest = "I'm naked! But nobody can know!" }, - new Person { Name = "Zapoo", DateOfBirth = null, TestInt = 9, _Age = 45, GUIY = Guid.NewGuid(), DoNotMapTest = "I buried treasure behind my house", Access=Person.Permissions.CanRead}, - - new Person { Name = "Sarah", TestInt = -1, _Age = 30, GUIY = Guid.NewGuid(), DoNotMapTest = "I hate my job", Access=Person.Permissions.CanRead}, - new Person { Name = "Michael", TestInt = 15, _Age = 50, GUIY = Guid.NewGuid(), DoNotMapTest = "I'm hiding a big secret", Access=Person.Permissions.CanRead | Person.Permissions.CanWrite}, - new Person { Name = "Tommy", TestInt = 7, _Age = 12, GUIY = Guid.NewGuid(), DoNotMapTest = "I am just a kid" }, - new Person { Name = "Grace", TestInt = 3, _Age = 90, GUIY = Guid.NewGuid(), DoNotMapTest = "I have seen the world" }, - new Person { Name = "Xylophone", TestInt = 9, _Age = 27, GUIY = Guid.NewGuid(), DoNotMapTest = "I have the weirdest name" }, - new Person { Name = "Yasmine", TestInt = 9, _Age = 40, GUIY = Guid.NewGuid(), DoNotMapTest = null }, - // Additional test case persons to stress-test LINQ validation - new Person { Name = "Alicia", TestInt = 42, _Age = 16, GUIY = Guid.NewGuid(), DoNotMapTest = "I just got my driver's license" }, - new Person { Name = "Ben", TestInt = 0, _Age = 25, GUIY = Guid.NewGuid(), DoNotMapTest = "I have no TestInt value" }, - new Person { Name = "Clara", TestInt = 100, _Age = 65, GUIY = Guid.NewGuid(), DoNotMapTest = "I retired last week", Access = Person.Permissions.CanRead | Person.Permissions.CanWrite }, - new Person { Name = "Danny", TestInt = 9, _Age = 40, GUIY = Guid.NewGuid(), DoNotMapTest = null }, // Null handling - new Person { Name = "Elliot", TestInt = -20, _Age = 55, GUIY = Guid.NewGuid(), DoNotMapTest = "My test int is negative" }, - new Person { Name = "Fiona", TestInt = 11, _Age = 33, GUIY = Guid.NewGuid(), DoNotMapTest = "I like puzzles" }, - new Person { Name = "George", TestInt = 8, _Age = 72, GUIY = Guid.NewGuid(), DoNotMapTest = "I fought in a war", Access = Person.Permissions.CanRead | Person.Permissions.CanWrite | Person.Permissions.CanCreate }, - new Person { Name = "Henry", TestInt = 99, _Age = 29, GUIY = Guid.NewGuid(), DoNotMapTest = "I almost made it to 100 TestInt" }, - new Person { Name = "Isla", TestInt = 2, _Age = 18, GUIY = Guid.NewGuid(), DoNotMapTest = "I just turned into an adult" }, - new Person { Name = "Jackie", TestInt = 75, _Age = 60, GUIY = Guid.NewGuid(), DoNotMapTest = "I love cooking" }, - new Person { Name = "Kevin", TestInt = 5, _Age = 48, GUIY = Guid.NewGuid(), DoNotMapTest = "I own a small business" }, - new Person { Name = "Liam", TestInt = 9, _Age = 55, GUIY = Guid.NewGuid(), DoNotMapTest = "I just became a grandfather" }, - new Person { Name = "Mona", TestInt = 88, _Age = 35, GUIY = Guid.NewGuid(), DoNotMapTest = "I am a detective", Access = Person.Permissions.CanRead | Person.Permissions.CanWrite }, - new Person { Name = "Nathan", TestInt = 7, _Age = 27, GUIY = Guid.NewGuid(), DoNotMapTest = "I play guitar" }, - new Person { Name = "Olivia", TestInt = 13, _Age = 45, GUIY = Guid.NewGuid(), DoNotMapTest = "I run marathons" }, - new Person { Name = "Patrick", TestInt = 3, _Age = 52, GUIY = Guid.NewGuid(), DoNotMapTest = "I work in IT" }, - new Person { Name = "Quinn", TestInt = 22, _Age = 42, GUIY = Guid.NewGuid(), DoNotMapTest = "I design board games" }, - new Person { Name = "Rachel", TestInt = 77, _Age = 36, GUIY = Guid.NewGuid(), DoNotMapTest = "I am a pilot" }, - new Person { Name = "Steve", TestInt = 9, _Age = 38, GUIY = Guid.NewGuid(), DoNotMapTest = "I am an engineer" }, - new Person { Name = "Tina", TestInt = 3, _Age = 68, GUIY = Guid.NewGuid(), DoNotMapTest = "I just got my pension" }, - new Person { Name = "Uma", TestInt = 14, _Age = 39, GUIY = Guid.NewGuid(), DoNotMapTest = "I teach yoga" }, - new Person { Name = "Victor", TestInt = 6, _Age = 31, GUIY = Guid.NewGuid(), DoNotMapTest = "I am an artist" }, - new Person { Name = "Wendy", TestInt = 50, _Age = 50, GUIY = Guid.NewGuid(), DoNotMapTest = "My age matches my test int" }, - new Person { Name = "Xander", TestInt = 19, _Age = 21, GUIY = Guid.NewGuid(), DoNotMapTest = "I am a college student" }, - new Person { Name = "Yara", TestInt = 90, _Age = 32, GUIY = Guid.NewGuid(), DoNotMapTest = "I work in finance" }, - new Person { Name = "Zane", TestInt = 101, _Age = 47, DateOfBirth = new DateTime(2020, 2, 10), GUIY = Guid.NewGuid(), DoNotMapTest = "I love motorcycles" }, - - }; - - - var count = 0; - foreach (var p in persons) - { - count += 1; - p._Id = count; - p.TestIntStable = count; - p.TestIntStable2 = 10; - } + // Targets the default database automatically if called without any parameters. + IMagicQuery personQuery = await _MagicDb.Query(); + await personQuery.ClearTable(); + + // Choose an assigned database to Person with strictly typed enforced connected Db's. + IMagicQuery employeeDbQuery = await _MagicDb.Query(x => x.Databases.Client); + + // Not implemented yet + // // // Highly not suggested, but you're allowed to target databases not assigned to the Person table + // IMagicQuery animalDbQuery = await _MagicDb.Query(IndexDbContext.Animal); + + // // // DO NOT DO THIS! I only am allowing this for maximum flexibility but this is very dangerous. + // IMagicQuery unassignedDbQuery = await _MagicDb.QueryOverride("DbName", "SchemaName"); + + + + + Person[] persons = new Person[] { + new Person { Name = "Zack", DateOfBirth = null, TestInt = 9, _Age = 45, GUIY = Guid.NewGuid(), DoNotMapTest = "I buried treasure behind my house", Access=Person.Permissions.CanRead}, + new Person { Name = "Luna", TestInt = 9, DateOfBirth = GetDateWithSameMonthDay(GetRandomYear()), _Age = 35, GUIY = Guid.NewGuid(), DoNotMapTest = "Jerry is my husband and I had an affair with Bob.", Access = Person.Permissions.CanRead|Person.Permissions.CanWrite}, + new Person { Name = "Jerry", TestInt = 9, DateOfBirth = GetDateWithSameMonthDay(GetRandomYear()), _Age = 35, GUIY = Guid.NewGuid(), DoNotMapTest = "My wife is amazing", Access = Person.Permissions.CanRead|Person.Permissions.CanWrite|Person.Permissions.CanCreate}, + new Person { Name = "Jamie", TestInt = 9, DateOfBirth = GetDateWithSameMonthDay(GetRandomYear()), _Age = 35, GUIY = Guid.NewGuid(), DoNotMapTest = "My wife is amazing", Access = Person.Permissions.CanRead|Person.Permissions.CanWrite|Person.Permissions.CanCreate}, + new Person { Name = "James", TestInt = 9, DateOfBirth = GetDateWithSameMonthDay(GetRandomYear()), _Age = 35, GUIY = Guid.NewGuid(), DoNotMapTest = "My wife is amazing", Access = Person.Permissions.CanRead|Person.Permissions.CanWrite|Person.Permissions.CanCreate}, + new Person { Name = "Jack", TestInt = 9, DateOfBirth = GetDateWithSameMonthDay(GetRandomYear()), _Age = 35, GUIY = Guid.NewGuid(), DoNotMapTest = "My wife is amazing", Access = Person.Permissions.CanRead|Person.Permissions.CanWrite|Person.Permissions.CanCreate}, + new Person { Name = "Jon", TestInt = 9, DateOfBirth = GetDateWithSameMonthDay(GetRandomYear()), _Age = 37, GUIY = Guid.NewGuid(), DoNotMapTest = "I black mail Luna for money because I know her secret", Access = Person.Permissions.CanRead}, + new Person { Name = "Jack", TestInt = 9, DateOfBirth = GetDateWithSameMonthDay(GetRandomYear()), _Age = 37, GUIY = Guid.NewGuid(), DoNotMapTest = "I have a drug problem", Access = Person.Permissions.CanRead|Person.Permissions.CanWrite}, + new Person { Name = "Cathy", TestInt = 9, DateOfBirth = GetDateWithSameMonthDay(GetRandomYear()), _Age = 22, GUIY = Guid.NewGuid(), DoNotMapTest = "I got away with reading Bobs diary.", Access = Person.Permissions.CanRead | Person.Permissions.CanWrite}, + new Person { Name = "Bob", TestInt = 3 , DateOfBirth = GetDateWithSameMonthDay(GetRandomYear()), _Age = 69, GUIY = Guid.NewGuid(), DoNotMapTest = "I caught Cathy reading my diary, but I'm too shy to confront her.", Access = Person.Permissions.CanRead }, + new Person { Name = "Alex", TestInt = 3 , DateOfBirth = null, _Age = 80, GUIY = Guid.NewGuid(), DoNotMapTest = "I'm naked! But nobody can know!" }, + new Person { Name = "Zapoo", DateOfBirth = null, TestInt = 9, _Age = 45, GUIY = Guid.NewGuid(), DoNotMapTest = "I buried treasure behind my house", Access=Person.Permissions.CanRead}, + + new Person { Name = "Sarah", TestInt = -1, _Age = 30, GUIY = Guid.NewGuid(), DoNotMapTest = "I hate my job", Access=Person.Permissions.CanRead}, + new Person { Name = "Michael", TestInt = 15, _Age = 50, GUIY = Guid.NewGuid(), DoNotMapTest = "I'm hiding a big secret", Access=Person.Permissions.CanRead | Person.Permissions.CanWrite}, + new Person { Name = "Tommy", TestInt = 7, _Age = 12, GUIY = Guid.NewGuid(), DoNotMapTest = "I am just a kid" }, + new Person { Name = "Grace", TestInt = 3, _Age = 90, GUIY = Guid.NewGuid(), DoNotMapTest = "I have seen the world" }, + new Person { Name = "Xylophone", TestInt = 9, _Age = 27, GUIY = Guid.NewGuid(), DoNotMapTest = "I have the weirdest name" }, + new Person { Name = "Yasmine", TestInt = 9, _Age = 40, GUIY = Guid.NewGuid(), DoNotMapTest = null }, + + // Additional test case persons to stress-test LINQ validation + new Person { Name = "Alicia", TestInt = 42, _Age = 16, GUIY = Guid.NewGuid(), DoNotMapTest = "I just got my driver's license" }, + new Person { Name = "Ben", TestInt = 0, _Age = 25, GUIY = Guid.NewGuid(), DoNotMapTest = "I have no TestInt value" }, + new Person { Name = "Clara", TestInt = 100, _Age = 65, GUIY = Guid.NewGuid(), DoNotMapTest = "I retired last week", Access = Person.Permissions.CanRead | Person.Permissions.CanWrite }, + new Person { Name = "Danny", TestInt = 9, _Age = 40, GUIY = Guid.NewGuid(), DoNotMapTest = null }, // Null handling + new Person { Name = "Elliot", TestInt = -20, _Age = 55, GUIY = Guid.NewGuid(), DoNotMapTest = "My test int is negative" }, + new Person { Name = "Fiona", TestInt = 11, _Age = 33, GUIY = Guid.NewGuid(), DoNotMapTest = "I like puzzles" }, + new Person { Name = "George", TestInt = 8, _Age = 72, GUIY = Guid.NewGuid(), DoNotMapTest = "I fought in a war", Access = Person.Permissions.CanRead | Person.Permissions.CanWrite | Person.Permissions.CanCreate }, + new Person { Name = "Henry", TestInt = 99, _Age = 29, GUIY = Guid.NewGuid(), DoNotMapTest = "I almost made it to 100 TestInt" }, + new Person { Name = "Isla", TestInt = 2, _Age = 18, GUIY = Guid.NewGuid(), DoNotMapTest = "I just turned into an adult" }, + new Person { Name = "Jackie", TestInt = 75, _Age = 60, GUIY = Guid.NewGuid(), DoNotMapTest = "I love cooking" }, + new Person { Name = "Kevin", TestInt = 5, _Age = 48, GUIY = Guid.NewGuid(), DoNotMapTest = "I own a small business" }, + new Person { Name = "Liam", TestInt = 9, _Age = 55, GUIY = Guid.NewGuid(), DoNotMapTest = "I just became a grandfather" }, + new Person { Name = "Mona", TestInt = 88, _Age = 35, GUIY = Guid.NewGuid(), DoNotMapTest = "I am a detective", Access = Person.Permissions.CanRead | Person.Permissions.CanWrite }, + new Person { Name = "Nathan", TestInt = 7, _Age = 27, GUIY = Guid.NewGuid(), DoNotMapTest = "I play guitar" }, + new Person { Name = "Olivia", TestInt = 13, _Age = 45, GUIY = Guid.NewGuid(), DoNotMapTest = "I run marathons" }, + new Person { Name = "Patrick", TestInt = 3, _Age = 52, GUIY = Guid.NewGuid(), DoNotMapTest = "I work in IT" }, + new Person { Name = "Quinn", TestInt = 22, _Age = 42, GUIY = Guid.NewGuid(), DoNotMapTest = "I design board games" }, + new Person { Name = "Rachel", TestInt = 77, _Age = 36, GUIY = Guid.NewGuid(), DoNotMapTest = "I am a pilot" }, + new Person { Name = "Steve", TestInt = 9, _Age = 38, GUIY = Guid.NewGuid(), DoNotMapTest = "I am an engineer" }, + new Person { Name = "Tina", TestInt = 3, _Age = 68, GUIY = Guid.NewGuid(), DoNotMapTest = "I just got my pension" }, + new Person { Name = "Uma", TestInt = 14, _Age = 39, GUIY = Guid.NewGuid(), DoNotMapTest = "I teach yoga" }, + new Person { Name = "Victor", TestInt = 6, _Age = 31, GUIY = Guid.NewGuid(), DoNotMapTest = "I am an artist" }, + new Person { Name = "Wendy", TestInt = 50, _Age = 50, GUIY = Guid.NewGuid(), DoNotMapTest = "My age matches my test int" }, + new Person { Name = "Xander", TestInt = 19, _Age = 21, GUIY = Guid.NewGuid(), DoNotMapTest = "I am a college student" }, + new Person { Name = "Yara", TestInt = 90, _Age = 32, GUIY = Guid.NewGuid(), DoNotMapTest = "I work in finance" }, + new Person { Name = "Zane", TestInt = 101, _Age = 47, DateOfBirth = new DateTime(2020, 2, 10), GUIY = Guid.NewGuid(), DoNotMapTest = "I love motorcycles" }, + }; + + + var count = 0; + foreach (var p in persons) + { + count += 1; + p._Id = count; + p.TestIntStable = count; + p.TestIntStable2 = 10; + } - //var storageInfo = await _MagicDb.GetStorageEstimateAsync(); - //storageQuota = storageInfo.QuotaInMegabytes; - //storageUsage = storageInfo.UsageInMegabytes; + //var storageInfo = await _MagicDb.GetStorageEstimateAsync(); + //storageQuota = storageInfo.QuotaInMegabytes; + //storageUsage = storageInfo.UsageInMegabytes; - // WhereExample = await (manager.Where(x => x.Name.StartsWith("c", StringComparison.OrdinalIgnoreCase) - // || x.Name.StartsWith("l", StringComparison.OrdinalIgnoreCase) - // || x.Name.StartsWith("j", StringComparison.OrdinalIgnoreCase) && x._Age > 35 - // || x.Name.Contains("bo", StringComparison.OrdinalIgnoreCase) - // ).OrderBy(x => x._Id).Skip(1).AsAsyncEnumerable()).ToListAsync(); + // WhereExample = await (manager.Where(x => x.Name.StartsWith("c", StringComparison.OrdinalIgnoreCase) + // || x.Name.StartsWith("l", StringComparison.OrdinalIgnoreCase) + // || x.Name.StartsWith("j", StringComparison.OrdinalIgnoreCase) && x._Age > 35 + // || x.Name.Contains("bo", StringComparison.OrdinalIgnoreCase) + // ).OrderBy(x => x._Id).Skip(1).AsAsyncEnumerable()).ToListAsync(); - //IMagicQuery personQuery = manager.Query(); + //IMagicQuery personQuery = manager.Query(); - await personQuery.AddRangeAsync(persons); + await personQuery.AddRangeAsync(persons); - allPeople = await personQuery.ToListAsync(); + allPeople = await personQuery.ToListAsync(); - List people = await personQuery.ToListAsync(); - StateHasChanged(); + List people = await personQuery.ToListAsync(); + StateHasChanged(); - // //await manager.ClearTableAsync(); - // var db = await _MagicDb.Database(IndexDbContext.Client); - // //await db.DeleteAsync(); - // await db.CloseAsync(); - // bool doesExist = await db.DoesExistAsync(); - // bool isOpen = await db.IsOpenAsync(); - // await db.OpenAsync(); - // bool isOpenNow = await db.IsOpenAsync(); + // //await manager.ClearTableAsync(); + // var db = await _MagicDb.Database(IndexDbContext.Client); + // //await db.DeleteAsync(); + // await db.CloseAsync(); + // bool doesExist = await db.DoesExistAsync(); + // bool isOpen = await db.IsOpenAsync(); + // await db.OpenAsync(); + // bool isOpenNow = await db.IsOpenAsync(); - var d = new DateTime(); - var asdf = d.DayOfWeek; - var asdf2 = d.DayOfYear; + var d = new DateTime(); + var asdf = d.DayOfWeek; + var asdf2 = d.DayOfYear; - // I know the validation of "allPeople" targets differently but I know this is the right result. - // This is a weird .NET memory grab weirdess. + // I know the validation of "allPeople" targets differently but I know this is the right result. + // This is a weird .NET memory grab weirdess. @@ -379,12 +379,9 @@ await personQuery.Where(x => x.DateOfBirth.Value.DayOfWeek <= DayOfWeek.Monday).ToListAsync(), allPeople.Where(x => x.DateOfBirth.HasValue && x.DateOfBirth.Value.DayOfWeek <= DayOfWeek.Monday)); - - RunTest("String Length Test", await personQuery.Where(x => x.Name.Length > 4).ToListAsync(), allPeople.Where(x => x.Name.Length > 4)); - RunTest("Nullable Date Time Test", await personQuery.Where(x => x.DateOfBirth == new DateTime(2020, 2, 10)).ToListAsync(), allPeople.Where(x => x.Name == "Zane")); @@ -397,7 +394,6 @@ RunTest("One Char contains", await personQuery.Where(x => x.TestInt >= 1 && x.TestInt < 10).ToListAsync(), allPeople.Where(x => x.TestInt >= 1 && x.TestInt < 10)); - RunTest("One Char contains", await personQuery.Where(x => x.Name.Contains("J")).ToListAsync(), allPeople.Where(x => x.Name.Contains("J"))); @@ -431,7 +427,6 @@ RunTest("Not Contains Test", await personQuery.Where(x => !x.Name.Contains("zac", StringComparison.OrdinalIgnoreCase)).ToListAsync(), allPeople.Where(x => !x.Name.Contains("zac", StringComparison.OrdinalIgnoreCase))); - RunTest("Basic Index Equal Test", await personQuery.Where(x => x.Name == "Zack").ToListAsync(), allPeople.Where(x => x.Name == "Zack")); @@ -537,8 +532,6 @@ RunTest("Index Last Or Default Where Test", new List() { await personQuery.Cursor(x => x.Name == "Victor").OrderBy(x => x._Id).LastOrDefaultAsync() }, new List() { allPeople.Where(x => x.Name == "Victor").OrderBy(x => x._Id).LastOrDefault() }); - - RunTest("TakeLast Cursor Test", await personQuery.OrderBy(x => x._Age).TakeLast(2).ToListAsync(), allPeople.OrderBy(x => x._Age).ThenBy(x => x._Id).TakeLast(2)); @@ -608,54 +601,46 @@ // 🧩 Complex Nested Conditions RunTest("Nested OR within AND", await personQuery.Where(// EXAMPLE of a deeply nested expression: - p => - ( - // Group 1: TestInt matches - (p.TestInt == 9 || p.TestInt == 3 || p.TestInt == 7) - - && - - // Group 2: Specific names or age range - ( - (p.Name == "Luna" || p.Name == "Jerry" || p.Name == "Jamie") - || (p._Age >= 35 && p._Age <= 40) - || (p.Name == "Zane" && p._Age > 45) - ) - - && - - // Group 3: Age-based logic only - ( - p._Age < 30 || p._Age > 50 || p._Age == 35 - ) - ) - - ) - .ToListAsync(), + p => + ( + // Group 1: TestInt matches + (p.TestInt == 9 || p.TestInt == 3 || p.TestInt == 7) + + && + + // Group 2: Specific names or age range + ( + (p.Name == "Luna" || p.Name == "Jerry" || p.Name == "Jamie") + || (p._Age >= 35 && p._Age <= 40) + || (p.Name == "Zane" && p._Age > 45) + ) + + && + + // Group 3: Age-based logic only + (p._Age < 30 || p._Age > 50 || p._Age == 35) + ) + ).ToListAsync(), allPeople.Where(// EXAMPLE of a deeply nested expression: - p => - ( - // Group 1: TestInt matches - (p.TestInt == 9 || p.TestInt == 3 || p.TestInt == 7) - - && + p => + ( + // Group 1: TestInt matches + (p.TestInt == 9 || p.TestInt == 3 || p.TestInt == 7) - // Group 2: Specific names or age range - ( - (p.Name == "Luna" || p.Name == "Jerry" || p.Name == "Jamie") - || (p._Age >= 35 && p._Age <= 40) - || (p.Name == "Zane" && p._Age > 45) - ) + && - && + // Group 2: Specific names or age range + ( + (p.Name == "Luna" || p.Name == "Jerry" || p.Name == "Jamie") + || (p._Age >= 35 && p._Age <= 40) + || (p.Name == "Zane" && p._Age > 45) + ) - // Group 3: Age-based logic only - ( - p._Age < 30 || p._Age > 50 || p._Age == 35 - ) - ) + && - )); + // Group 3: Age-based logic only + (p._Age < 30 || p._Age > 50 || p._Age == 35) + )).ToList()); RunTest("Nested AND within OR", await personQuery.Where(x => (x._Age > 40 && x.TestInt == 9) || x.Name.Contains("bo", StringComparison.OrdinalIgnoreCase)) @@ -705,8 +690,6 @@ await personQuery.Where(x => false).ToListAsync(), allPeople.Where(x => false)); - - RunTest("Cursor Test Equals", await personQuery.Cursor(x => x.Name == "Zack").ToListAsync(), allPeople.Where(x => x.Name == "Zack")); @@ -734,7 +717,6 @@ await personQuery.Where(x => x.DoNotMapTest.Contains("diary")).ToListAsync(), allPeople.Where(x => x.DoNotMapTest != null && x.DoNotMapTest.Contains("diary"))); - RunTest("Where + OrderBy + Skip + TakeLast", await personQuery.Cursor(x => x.TestInt > 2) .OrderBy(x => x._Id) @@ -760,9 +742,6 @@ .ToListAsync(), allPeople.Where(x => x._Age > 30 && x.TestInt == 9).OrderBy(x => x._Age).ThenBy(x => x._Id).Skip(1).Take(3)); - - - // ends with not yet supported // // 🔀 Complex AND/OR Mix (Nested Multiple Layers) // RunTest("Deeply Nested AND within OR", @@ -770,7 +749,6 @@ // .ToListAsync(), // allPeople.Where(x => (x._Age > 30 && (x.TestInt == 9 || x.Name.StartsWith("J"))) || (x.TestInt == 3 && x.Name.EndsWith("b", StringComparison.OrdinalIgnoreCase)))); - StateHasChanged(); } } diff --git a/TestServer/TestServer/Components/Pages/Weather.razor b/TestServer/TestServer/Components/Pages/Weather.razor deleted file mode 100644 index 381bbd2..0000000 --- a/TestServer/TestServer/Components/Pages/Weather.razor +++ /dev/null @@ -1,64 +0,0 @@ -@page "/weather" -@attribute [StreamRendering] - -Weather - -

Weather

- -

This component demonstrates showing data.

- -@if (forecasts == null) -{ -

Loading...

-} -else -{ - - - - - - - - - - - @foreach (var forecast in forecasts) - { - - - - - - - } - -
DateTemp. (C)Temp. (F)Summary
@forecast.Date.ToShortDateString()@forecast.TemperatureC@forecast.TemperatureF@forecast.Summary
-} - -@code { - private WeatherForecast[]? forecasts; - - protected override async Task OnInitializedAsync() - { - // Simulate asynchronous loading to demonstrate streaming rendering - await Task.Delay(500); - - var startDate = DateOnly.FromDateTime(DateTime.Now); - var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; - forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = startDate.AddDays(index), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = summaries[Random.Shared.Next(summaries.Length)] - }).ToArray(); - } - - private class WeatherForecast - { - public DateOnly Date { get; set; } - public int TemperatureC { get; set; } - public string? Summary { get; set; } - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - } -}