diff --git a/.folio/content/en/analyzer.md b/.folio/content/en/analyzer.md new file mode 100644 index 0000000..aa55e2b --- /dev/null +++ b/.folio/content/en/analyzer.md @@ -0,0 +1,22 @@ +# An analyzer for the one mistake this style makes easy + +## situation + +Returning failures as values instead of exceptions means a discarded `Result` is silently +a swallowed failure. The compiler has nothing to say about it. + +## task + +Catch discarded results at build time, without asking anyone to install or enable +anything. + +## action + +Shipped `LOOM0001` inside `CodeByDylan.Loom.Results`, so it arrives with the package. +Discarding on purpose stays legal by writing `_ =`, which makes the intent visible in +review. + +## result + +Its first run against existing code found eight unchecked discards in this repository's +own test suite. diff --git a/.folio/content/en/overview.md b/.folio/content/en/overview.md new file mode 100644 index 0000000..ab9112b --- /dev/null +++ b/.folio/content/en/overview.md @@ -0,0 +1,9 @@ +# Overview + +Ten foundational **.NET packages**, versioned in lockstep, each doing one thing and +depending only on the packages below it. + +Clean Architecture with vertical slice structure, made opinionated on purpose: one way +to report a failure, one way to run a handler, one way to turn a failure into a status +code. Where a decision is forced, Loom makes it — where it is taste, it hands you the +object and gets out of the way. \ No newline at end of file diff --git a/.folio/content/en/story.md b/.folio/content/en/story.md new file mode 100644 index 0000000..0c82502 --- /dev/null +++ b/.folio/content/en/story.md @@ -0,0 +1,25 @@ +# Story + +Every new .NET service started the same way. A `Result` type, rewritten slightly +differently than last time. A folder layout argued about again. A decision about whether +a not-found is an exception or a return value, made again, and made differently. + +None of those are hard problems. That is exactly why re-deciding them is wasteful — the +cost is not the thinking, it is that two services in the same solution end up disagreeing +about what a failure looks like. + +So Loom takes the decisions away. There is one `Error`, with a stable code and one of six +categories, and that category is what decides the status code, the log level, and whether +a retry makes sense. An endpoint becomes a thin adapter with no mapping logic of its own. +Handlers are injected and called directly, so "go to definition" lands on the handler +instead of a registry. + +The harder half was deciding what to leave out. No mediator, because indirection you +cannot navigate is a cost paid on every read. No repository, because `DbContext` is +already a unit of work and wrapping it destroys the `IQueryable` composition that makes +specifications work. No option to log a request's contents — not defaulted off, but +absent, because an option is an invitation. + +Ten packages rather than one, so nothing drags in a framework you did not ask for. They +version in lockstep because a matrix of compatible versions is its own maintenance +problem, and this is meant to remove those, not add one. \ No newline at end of file diff --git a/.folio/content/nl/analyzer.md b/.folio/content/nl/analyzer.md new file mode 100644 index 0000000..a3eba1d --- /dev/null +++ b/.folio/content/nl/analyzer.md @@ -0,0 +1,22 @@ +# Een analyzer voor de ene fout die deze stijl makkelijk maakt + +## situation + +Fouten als waarde teruggeven in plaats van als exception betekent dat een weggegooide +`Result` stilzwijgend een genegeerde fout is. De compiler heeft er niets over te zeggen. + +## task + +Weggegooide resultaten opvangen tijdens het bouwen, zonder dat iemand iets hoeft te +installeren of aan te zetten. + +## action + +`LOOM0001` meegeleverd in `CodeByDylan.Loom.Results`, zodat de analyzer met het pakket +meekomt. Bewust weggooien blijft toegestaan door `_ =` te schrijven, waardoor die +bedoeling zichtbaar wordt tijdens review. + +## result + +De eerste run op bestaande code vond acht ongecontroleerde weggooiacties in de testsuite +van deze repository zelf. diff --git a/.folio/content/nl/overview.md b/.folio/content/nl/overview.md new file mode 100644 index 0000000..a7cf8d8 --- /dev/null +++ b/.folio/content/nl/overview.md @@ -0,0 +1,9 @@ +# Overzicht + +Tien fundamentele **.NET-pakketten**, in lockstep geversioneerd, die elk één ding doen +en alleen afhangen van de pakketten eronder. + +Clean Architecture met vertical slices, bewust uitgesproken: één manier om een fout te +melden, één manier om een handler uit te voeren, één manier om een fout naar een +statuscode te vertalen. Waar een keuze onvermijdelijk is, maakt Loom hem — waar het +smaak is, krijg je het object terug en gaat Loom opzij. \ No newline at end of file diff --git a/.folio/content/nl/story.md b/.folio/content/nl/story.md new file mode 100644 index 0000000..3dcea1e --- /dev/null +++ b/.folio/content/nl/story.md @@ -0,0 +1,27 @@ +# Verhaal + +Elke nieuwe .NET-service begon op dezelfde manier. Een `Result`-type, net iets anders +geschreven dan de vorige keer. Een mappenstructuur waar opnieuw over werd gediscussieerd. +De vraag of "niet gevonden" nu een exception is of een retourwaarde, opnieuw beantwoord en +opnieuw anders. + +Geen van die dingen is een moeilijk probleem. Juist daarom is het zonde om ze telkens +opnieuw te beslissen — de kosten zitten niet in het nadenken, maar in het feit dat twee +services binnen dezelfde solution het oneens blijken over hoe een fout eruitziet. + +Loom neemt die beslissingen dus weg. Er is één `Error`, met een vaste code en één van zes +categorieën, en die categorie bepaalt de statuscode, het logniveau en of opnieuw proberen +zinvol is. Een endpoint wordt daarmee een dunne adapter zonder eigen vertaallogica. +Handlers worden geïnjecteerd en direct aangeroepen, zodat "go to definition" bij de handler +uitkomt in plaats van bij een registry. + +De lastigere helft was bepalen wat er níét in moest. Geen mediator, want indirectie die je +niet kunt volgen kost je bij elke keer lezen. Geen repository, want `DbContext` is al een +unit of work, en het inpakken ervan doet de `IQueryable`-compositie teniet waar +specifications juist op leunen. Geen optie om de inhoud van een request te loggen — niet +standaard uitgeschakeld, maar afwezig, want een optie is een uitnodiging. + +Tien pakketten in plaats van één, zodat niets een framework binnenhaalt waar je niet om +hebt gevraagd. Ze worden in lockstep geversioneerd, omdat een matrix van compatibele +versies zelf weer onderhoud oplevert — en dit is bedoeld om dat soort onderhoud weg te +nemen, niet om er iets aan toe te voegen. \ No newline at end of file diff --git a/.folio/locales/en.toml b/.folio/locales/en.toml new file mode 100644 index 0000000..c810c23 --- /dev/null +++ b/.folio/locales/en.toml @@ -0,0 +1,5 @@ +project.name = "Loom" +project.tagline = "Ten .NET packages that make the boring decisions once" + +link.docs = "Guidance for projects built on Loom" +link.package = "Packages on NuGet" diff --git a/.folio/locales/nl.toml b/.folio/locales/nl.toml new file mode 100644 index 0000000..37278db --- /dev/null +++ b/.folio/locales/nl.toml @@ -0,0 +1,5 @@ +project.name = "Loom" +project.tagline = "Tien .NET-pakketten die de saaie keuzes één keer maken" + +link.docs = "Richtlijnen voor projecten op Loom" +link.package = "Pakketten op NuGet" diff --git a/.folio/project.toml b/.folio/project.toml new file mode 100644 index 0000000..5159dbd --- /dev/null +++ b/.folio/project.toml @@ -0,0 +1,28 @@ +version = 1 + +[project] +slug = "loom" +status = "active" +role = "author" +started = "2026-07" +tags = ["csharp", "dotnet", "tooling"] + +[[links]] +type = "docs" +url = "https://github.com/CodeByDylan/Loom/tree/main/docs/agents" + +[[links]] +type = "package" +url = "https://www.nuget.org/packages?q=CodeByDylan.Loom" + +[[sections]] +id = "overview" +file = "overview.md" + +[[sections]] +id = "story" +file = "story.md" + +[[sections]] +id = "analyzer" +file = "analyzer.md"