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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .folio/content/en/analyzer.md
Original file line numberDiff line numberDiff line change
@@ -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.
9 changes: 9 additions & 0 deletions .folio/content/en/overview.md
Original file line numberDiff line numberDiff line change
@@ -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.
25 changes: 25 additions & 0 deletions .folio/content/en/story.md
Original file line numberDiff line numberDiff line change
@@ -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.
22 changes: 22 additions & 0 deletions .folio/content/nl/analyzer.md
Original file line numberDiff line numberDiff line change
@@ -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.
9 changes: 9 additions & 0 deletions .folio/content/nl/overview.md
Original file line numberDiff line numberDiff line change
@@ -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.
27 changes: 27 additions & 0 deletions .folio/content/nl/story.md
Original file line numberDiff line numberDiff line change
@@ -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.
5 changes: 5 additions & 0 deletions .folio/locales/en.toml
Original file line numberDiff line numberDiff line change
@@ -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"
5 changes: 5 additions & 0 deletions .folio/locales/nl.toml
Original file line numberDiff line numberDiff line change
@@ -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"
28 changes: 28 additions & 0 deletions .folio/project.toml
Original file line numberDiff line numberDiff line change
@@ -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"
Loading