{"id":22408355,"url":"https://github.com/phmatray/mutty","last_synced_at":"2026-03-09T07:05:02.130Z","repository":{"id":253764702,"uuid":"843819375","full_name":"phmatray/Mutty","owner":"phmatray","description":"Immutable Record Mutation Made Easy","archived":false,"fork":false,"pushed_at":"2026-02-24T15:29:24.000Z","size":1268,"stargazers_count":5,"open_issues_count":5,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-24T17:52:18.547Z","etag":null,"topics":["csharp","dotnet","dotnet-library","immer","immutable","nuget","record","state-management"],"latest_commit_sha":null,"homepage":"https://phmatray.github.io/Mutty/","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/phmatray.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-08-17T14:14:47.000Z","updated_at":"2026-02-24T15:32:59.000Z","dependencies_parsed_at":"2024-08-19T11:51:01.063Z","dependency_job_id":"d829d06b-c8ab-4f51-b341-b3ee76cec658","html_url":"https://github.com/phmatray/Mutty","commit_stats":null,"previous_names":["phmatray/mutty"],"tags_count":16,"template":false,"template_full_name":null,"purl":"pkg:github/phmatray/Mutty","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phmatray%2FMutty","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phmatray%2FMutty/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phmatray%2FMutty/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phmatray%2FMutty/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phmatray","download_url":"https://codeload.github.com/phmatray/Mutty/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phmatray%2FMutty/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29876369,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-26T22:37:10.609Z","status":"ssl_error","status_checked_at":"2026-02-26T22:37:09.019Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","dotnet","dotnet-library","immer","immutable","nuget","record","state-management"],"created_at":"2024-12-05T11:17:22.749Z","updated_at":"2026-02-26T23:04:20.687Z","avatar_url":"https://github.com/phmatray.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Mutty\n\nImmutable Record Mutation Made Easy\n\n| ![Logo Mutty](https://raw.githubusercontent.com/phmatray/Mutty/main/logo.png) | Mutty is a C# Incremental Source Generator that provides a convenient way to work with immutable records by generating mutable wrappers for them. These wrappers allow you to modify properties of immutable records in a clean, controlled manner and then convert them back into immutable records. |\n|-------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n\n[![phmatray - Mutty](https://img.shields.io/static/v1?label=phmatray\u0026message=Mutty\u0026color=blue\u0026logo=github)](https://github.com/phmatray/Mutty \"Go to GitHub repo\")\n[![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)\n[![stars - Mutty](https://img.shields.io/github/stars/phmatray/Mutty?style=social)](https://github.com/phmatray/Mutty)\n[![forks - Mutty](https://img.shields.io/github/forks/phmatray/Mutty?style=social)](https://github.com/phmatray/Mutty)\n\n[![GitHub tag](https://img.shields.io/github/tag/phmatray/Mutty?include_prereleases=\u0026sort=semver\u0026color=blue)](https://github.com/phmatray/Mutty/releases/)\n[![issues - Mutty](https://img.shields.io/github/issues/phmatray/Mutty)](https://github.com/phmatray/Mutty/issues)\n[![GitHub pull requests](https://img.shields.io/github/issues-pr/phmatray/Mutty)](https://github.com/phmatray/Mutty/pulls)\n[![GitHub contributors](https://img.shields.io/github/contributors/phmatray/Mutty)](https://github.com/phmatray/Mutty/graphs/contributors)\n[![GitHub last commit](https://img.shields.io/github/last-commit/phmatray/Mutty)](https://github.com/phmatray/Mutty/commits/master)\n\n---\n\n## 📝 Table of Contents\n\n\u003c!-- TOC --\u003e\n* [Mutty](#mutty)\n  * [📝 Table of Contents](#-table-of-contents)\n  * [📌 Features](#-features)\n    * [Current Features](#current-features)\n  * [How Mutty Works](#how-mutty-works)\n    * [Example Usage](#example-usage)\n    * [How to Use the Generated Code](#how-to-use-the-generated-code)\n      * [Deep Nesting Example](#deep-nesting-example)\n      * [Comparison with `with` Notation](#comparison-with-with-notation)\n    * [Ideal for Flux Architecture](#ideal-for-flux-architecture)\n    * [Installation](#installation)\n    * [Best Practices](#best-practices)\n    * [Contributing](#contributing)\n    * [License](#license)\n\u003c!-- TOC --\u003e\n\n---\n\n## 📌 Features\n\n### Current Features\n\n- [x] **Automated Mutable Wrappers**: Automatically generates mutable wrapper classes for your immutable records using Roslyn's Incremental Source Generation.\n- [x] **Deep Nesting Support**: Easily handle complex nested structures without tedious and error-prone manual code.\n- [x] **Immutable to Mutable Conversion**: Seamlessly switch between immutable and mutable versions of your records using implicit conversions.\n- [x] **Ideal for Flux Architecture**: Works great with Flux architecture, allowing you to manage state changes in a predictable and immutable way.\n- [x] **Helper Methods**:\n    - [x] Provides a `Produce` method to apply mutations to your immutable records using the generated mutable wrappers.\n    - [x] Also includes `CreateDraft` and `FinishDraft` methods for more granular control...\n    - [x] ...and `AsMutable` and `ToImmutable` extension methods for collections.\n\n## How Mutty Works\n\nMutty uses a custom attribute `[MutableGeneration]` to mark immutable records for which you want to generate mutable wrappers.\nThe Incremental Source Generator detects these records and generates corresponding mutable wrapper classes and extension methods.\n\nThe basic idea is that with Mutty, you will apply all your changes to a temporary mutable wrapper, which acts as a proxy of the immutable record.\nOnce all your mutations are completed, Mutty will produce the next immutable state based on the mutations to the mutable wrapper.\nThis means that you can interact with your data by simply modifying it while keeping all the benefits of immutable data.\n\n![Mutty Overview](Writerside/images/mutty-overview.png)\n\nUsing Mutty is like having a personal assistant. The assistant takes a letter (the current state) and gives you a copy (mutable wrapper) to jot changes onto.\nOnce you are done, the assistant will take your draft and produce the real immutable, final letter for you (the next state).\n\n### Example Usage\n\nSuppose you have the following immutable records:\n\n```csharp\nnamespace Mutty.ConsoleApp;\n\n[MutableGeneration]\npublic record Student(string Email, StudentDetails Details, ImmutableList\u003cEnrollment\u003e Enrollments);\n\n[MutableGeneration]\npublic record StudentDetails(string Name, int Age);\n\n[MutableGeneration]\npublic record Enrollment(Course Course, DateTime EnrollmentDate);\n\n[MutableGeneration]\npublic record Course(string Title, string Description, ImmutableList\u003cModule\u003e Modules);\n\n[MutableGeneration]\npublic record Module(string Name, ImmutableList\u003cLesson\u003e Lessons);\n\n[MutableGeneration]\npublic record Lesson(string Title, string Content);\n```\n\n\u003e **Note**: For simplicity, this example focuses on the `Student` record, but Mutty also generates similar mutable wrappers for `StudentDetails`, `Enrollment`, `Course`, `Module`, and `Lesson`.\n\nWhen you add the `[MutableGeneration]` attribute to your records, Mutty will automatically generate the corresponding mutable wrapper classes:\n\n```csharp\n// \u003cauto-generated /\u003e\n// This file is auto-generated by Mutty.\n\nusing System.Collections.Immutable;\n\nnamespace Mutty.ConsoleApp\n{\n    /// \u003csummary\u003e\n    /// The mutable wrapper for the \u003csee cref=\"Student\"/\u003e record.\n    /// \u003c/summary\u003e\n    public partial class MutableStudent\n    {\n        private Student _record;\n\n        /// \u003csummary\u003e\n        /// Initializes a new instance of the \u003csee cref=\"MutableStudent\"/\u003e class.\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"record\"\u003eThe record to wrap.\u003c/param\u003e\n        public MutableStudent(Student record)\n        {\n            _record = record;\n\n            Email = _record.Email;\n            Details = _record.Details;\n            Enrollments = _record.Enrollments.AsMutable();\n        }\n\n        /// \u003csummary\u003e\n        /// Builds a new instance of the \u003csee cref=\"Student\"/\u003e class.\n        /// \u003c/summary\u003e\n        public Student Build()\n        {\n            return _record with\n            {\n                Email = this.Email,\n                Details = this.Details,\n                Enrollments = this.Enrollments.ToImmutable(),\n            };\n        }\n\n        /// \u003csummary\u003e\n        /// Performs an implicit conversion from \u003csee cref=\"Student\"/\u003e to \u003csee cref=\"MutableStudent\"/\u003e.\n        /// \u003c/summary\u003e\n        public static implicit operator MutableStudent(Student record)\n        {\n            return new MutableStudent(record);\n        }\n\n        /// \u003csummary\u003e\n        /// Performs an implicit conversion from \u003csee cref=\"MutableStudent\"/\u003e to \u003csee cref=\"Student\"/\u003e.\n        /// \u003c/summary\u003e\n        public static implicit operator Student(MutableStudent mutable)\n        {\n            return mutable.Build();\n        }\n\n        /// \u003csummary\u003e\n        /// Gets or sets the Email.\n        /// \u003c/summary\u003e\n        public string Email { get; set; }\n\n        /// \u003csummary\u003e\n        /// Gets or sets the Record Details.\n        /// \u003c/summary\u003e\n        public MutableStudentDetails Details { get; set; }\n\n        /// \u003csummary\u003e\n        /// Gets or sets the ImmutableCollection Enrollments.\n        /// \u003c/summary\u003e\n        public List\u003cMutableEnrollment\u003e Enrollments { get; set; }\n    }\n}\n```\n\n### How to Use the Generated Code\n\nOnce the code is generated, you can use the mutable wrappers to modify your immutable records as needed.\n\n#### Deep Nesting Example\n\nHere's an example demonstrating how easy it is to handle deeply nested structures using Mutty:\n\n```csharp\npublic sealed class ExampleImmutableArray : ExampleBase\n{\n    public override void Run()\n    {\n        DisplayHeader(\"ImmutableArray Example\");\n\n        // Initialize original immutable objects\n        Student student = Factories.CreateJohnDoe();\n\n        // Use the Produce method to create an updated student object with mutations\n        Student updatedStudent = student.Produce(mutable =\u003e\n        {\n            // Modify the title of the first lesson in the first module of the first course\n            mutable.Enrollments[0].Course.Modules[0].Lessons[0].Title = \"=== NEW TITLE ===\";\n        });\n\n        // Display the original and updated student objects\n        DisplayStudentTree(student, 4);\n        DisplayStudentTree(updatedStudent, 4);\n    }\n}\n```\n\n#### Comparison with `with` Notation\n\nWithout Mutty, updating deeply nested structures using the `with` expression can become cumbersome and error-prone:\n\n```csharp\n// Using 'with' notation\nvar updatedStudent = student with\n{\n    Enrollments = student.Enrollments.SetItem(0, student.Enrollments[0] with\n    {\n        Course = student.Enrollments[0].Course with\n        {\n            Modules = student.Enrollments[0].Course.Modules.SetItem(0, student.Enrollments[0].Course.Modules[0] with\n            {\n                Lessons = student.Enrollments[0].Course.Modules[0].Lessons.SetItem(0, student.Enrollments[0].Course.Modules[0].Lessons[0] with\n                {\n                    Title = \"=== NEW TITLE ===\"\n                })\n            })\n        }\n    })\n};\n```\n\nUsing Mutty, the same operation is simpler and more intuitive:\n\n```csharp\n// Using Mutty\nStudent updatedStudent = student.Produce(mutable =\u003e\n{\n    mutable.Enrollments[0].Course.Modules[0].Lessons[0].Title = \"=== NEW TITLE ===\";\n});\n```\n\n### Ideal for Flux Architecture\n\nMutty is an excellent fit for state management patterns like Flux. With Mutty, you can maintain immutable state while easily applying updates through the mutable wrappers. This keeps your state management predictable and efficient, especially in complex applications with deeply nested state.\n\n### Installation\n\nTo use Mutty in your project:\n\n1. **Add the Mutty package**:\n    - You can add it as a NuGet package (if it's available as a package).\n\n2. **Annotate Your Records**:\n    - Simply annotate your records with `[MutableGeneration]` to indicate that Mutty should generate a mutable wrapper for them.\n\n3. **Build Your Project**:\n    - The Incremental Source Generator will automatically detect the annotated records and generate the corresponding mutable wrappers and extension methods during the build process.\n\n### Best Practices\n\n- **Immutable by Default**: Use immutable records for your core data models to ensure thread safety and prevent unintended side effects.\n- **Mutate with Care**: Use the generated mutable wrappers when you need to make changes, but remember to always convert back to the immutable form before exposing the data.\n- **Leverage the Implicit Conversion**: Mutty provides implicit conversions between the immutable and mutable versions of your records, making it easy to switch between the two.\n\n### Contributing\n\nIf you want to contribute to Mutty or report issues:\n\n- **GitHub Repository**: [Mutty on GitHub](https://github.com/phmatray/mutty)\n- **Issues**: Use the GitHub Issues tab to report bugs or request features.\n\n### License\n\nMutty is open-source software licensed under the [Apache License 2.0](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphmatray%2Fmutty","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphmatray%2Fmutty","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphmatray%2Fmutty/lists"}