Skip to content

Stabilize .NET 10 serialization, streaming, and engine contracts - #123

Merged
magiccodingman merged 1 commit into
masterfrom
feat/stabilize-engine-contracts
Aug 21, 2026
Merged

Stabilize .NET 10 serialization, streaming, and engine contracts#123
magiccodingman merged 1 commit into
masterfrom
feat/stabilize-engine-contracts

Conversation

@magiccodingman

@magiccodingman magiccodingman commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Summary

This consolidates the useful bug reports and implementation ideas from the open constructor, debug logging, serializer/memory, and framework-upgrade work into one .NET 10 stabilization change. It also addresses additional engine issues found while tracing those paths end to end.

The package version is moved to 3.0.0 because the current package targets net10.0; applications that must remain on .NET 8 can remain on the 2.x NuGet line.

Consumer/API contract

Normal Magic IndexedDB usage is unchanged: service registration, Query<T>(), database operations, and query composition keep their existing syntax.

Public API decisions:

  • Add [MagicConstructor] as an opt-in materialization selector.
  • Keep [JsonConstructor] fully supported.
  • Keep ITypedArgument.Serialize(), SerializeToJsonElement(), and SerializeToJsonString() public.
  • Add a Stream serialization overload without removing or changing the existing StreamWriter overload's encoding behavior.
  • Add MagicConstructorException for actionable ambiguous-constructor failures.

Constructor precedence is documented and deterministic:

  1. one [MagicConstructor];
  2. one [JsonConstructor];
  3. a public parameterless constructor;
  4. the only public constructor;
  5. the legacy most-parameters convention, with deterministic tie-breaking.

Parameter binding is case-insensitive, optional defaults are honored, read-only constructor-bound properties work, and remaining writable properties are populated after construction. Parameterized nested types no longer fail during cache discovery merely because they lack a parameterless constructor.

Engine corrections

Serialization and interop

  • Preserve escaped strings, backslashes, newlines, tabs, control characters, and Unicode through valid System.Text.Json writing.
  • Honor configured converters for enums and other simple types instead of forcing enum values through Int32.
  • Restore nested collection structure, arrays, HashSet<T>, dictionaries, and Magic property names inside nested complex collections.
  • Stop swallowing property conversion failures; errors now retain property/type context.
  • Introduce a versioned internal wire envelope whose parameters are real JSON values rather than JSON strings embedded inside JSON. JavaScript retains the v1 parser for compatibility.
  • Serialize the internal envelope directly to a stream, and remove the forced GC.Collect()/finalizer pause.
  • Preserve 0, false, "", and null JavaScript results rather than replacing falsey values with {}.
  • Propagate JavaScript errors instead of returning error-shaped data that later fails misleadingly during model deserialization.

Yield streaming and resource lifetime

  • Register chunk consumers before starting JavaScript.
  • Drain chunks concurrently with the JavaScript async producer, so AsAsyncEnumerable() is progressive rather than fully buffered.
  • Detect producer faults or a missing completion marker.
  • Dispose DotNetStreamReference, DotNetObjectReference, IJSStreamReference, response streams, and cancellation sources.
  • Measure chunks by UTF-8 bytes without splitting Unicode code points.

Runtime and browser helpers

  • Wire the existing isDebug registration value to JavaScript debugLog at runtime.
  • Use debug logging for informational database/migration output while retaining real warnings and errors.
  • Make assembly scanning resilient to ReflectionTypeLoadException by using successfully loaded types.
  • Recognize explicit enum conversions in query comparisons.
  • Fix closeAll() to close the cached Dexie instances.
  • Fix multi-database creation to pass each complete store definition to createDb.
  • Remove the UTF-8 BOM that made the Dexie source map invalid JSON in affected tooling/browsers.

How this incorporates the existing work

The existing PRs and Ard's fork surfaced real issues and directly informed this consolidation. The differences below are about preserving the project's longer-term contracts, not dismissing that work.

  • Replace JsonConstructor by MagicConstructor #107 / MagicConstructor: adopted the dedicated attribute and the important “populate properties not consumed by the constructor” behavior. The final version also retains [JsonConstructor], rejects multiple annotations explicitly, matches parameter names case-insensitively, honors optional defaults, avoids unconditional activation of parameter-only nested types, and keeps a deterministic legacy fallback.
  • Use debuglog method more consistantly #109 / debug logging: adopted consistent debugLog use. Instead of a module-level constant that remains false, AddMagicBlazorDB(..., isDebug) configures the JavaScript module at runtime.
  • Reduce memory useage on serializing #113 / serializer memory work: adopted nested collection support, direct stream serialization for the internal envelope, complete resource disposal, and removal of forced GC. The raw string-writing approach was not copied because escaping only quotes still corrupts backslashes, control characters, and other valid JSON cases. The explicit enum-to-Int32 path was not copied because it overflows wider enum backing types and bypasses configured converters. Public ITypedArgument serialization methods were retained for API stability.
  • Upgrade to .Net 9 #121 / framework upgrade: master already targets .NET 10 via Upgrade projects to .NET 10 #122, so this does not reintroduce the older .NET 9 target. It also avoids a global hard-coded 120-second JavaScript timeout, keeps the existing test applications, and uses the major package version to communicate the runtime floor.
  • Ard fork cleanup: the resilient assembly scan and useful debug/serialization findings are included. The broad TestWasm deletion and unrelated example rewrites are intentionally left out so this PR stays focused and preserves useful manual test surfaces.

Tests

  • dotnet test Magic.IndexedDb.UnitTests/Magic.IndexedDb.UnitTests.csproj --no-restore
    • 16 passed, 0 failed
  • dotnet test E2eTest/E2eTest.csproj --no-restore
    • 98 passed, 0 failed
  • dotnet build Magic.IndexedDb.sln -c Release --no-restore
    • 0 errors (the repository's existing nullable warnings remain)
  • JavaScript syntax checks pass for all changed modules.
  • The Dexie source map parses as JSON and is BOM-free.
  • The generated Magic.IndexedDb.3.0.0.nupkg contains lib/net10.0 and the expected static web assets.

The new unit project covers constructor precedence and ambiguity, immutable/hybrid/parameter-only models, optional defaults, public API preservation, escaped JSON, dictionaries, arrays/nested collections/sets, Magic names in nested complex collections, configured enum and simple-type converters, enum expression casts, wire-envelope values including null/false/zero, and chunk reconstruction/completion ordering.

The browser additions cover escaped Unicode and nested values through IndexedDB, a zero count (the falsey-result regression), and the real AsAsyncEnumerable() transport path.

Documentation included and website follow-up

README.md and MagicIndexDbWiki/Version-3.0-Upgrade.md now document the .NET 10/3.0 boundary, constructor behavior, API compatibility, migration status, and progressive-ordering caveat.

The hosted documentation should subsequently be aligned in these areas:

  • Installation/runtime pages: version 3 requires .NET 10; .NET 8 applications remain on 2.x.
  • Add the constructor precedence and [MagicConstructor] example while noting [JsonConstructor] compatibility.
  • Replace .Close() / .Delete() examples with the actual async APIs.
  • Remove or clearly mark obsolete Query(IndexedDbSet) / QueryOverride examples.
  • Remove examples for multi/all-database methods that are not part of the current public API.
  • Correct Skip().Take() examples to the supported query-stage ordering.
  • State that automated migrations are still under construction rather than currently automatic.
  • Distinguish materialized query ordering from progressive AsAsyncEnumerable() arrival order.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant