Skip to content

Deepstaging

Source generators for .NET that eliminate infrastructure boilerplate. Declare your intent with attributes — the compiler generates DI registration, effect wrappers, test stubs, HTTP clients, persistence, dispatch, and observability.

dotnet add package Deepstaging --prerelease

Why Deepstaging? Walkthrough → Guides →


How It Works

You annotate your types with C# attributes. The Roslyn source generator reads them at compile time and emits all the infrastructure code — DI registration, effect wrappers, test stubs, API endpoints. No reflection, no runtime code generation.

Attributes you write  →  Source generator  →  Generated C# code  →  Compiled into your app

The generated code is visible in your generated/ folder. You can read it, debug into it, and stop using Deepstaging at any time — the generated code still compiles against Deepstaging.Runtime.


A working app in 20 lines

This compiles, runs, and is testable — with generated persistence, validation, dispatch, DI registration, and test infrastructure. No database. No message broker.

[TypedId]
public readonly partial struct ArticleId;

[StoredEntity]
public sealed record Article(ArticleId Id, string Title, string Body, string Author);

[DataStore]
public static partial class ScribeStore;

public sealed record CreateArticle(
    [Required] string Title,
    [StringLength(5000)] string Body,
    string Author) : ICommand;

public static class ArticleCommands
{
    [CommandHandler(Validated = true, Audited = true)]
    public static Eff<ScribeRuntime, ArticleId> Handle(CreateArticle cmd) =>
        let id = ArticleId.New()
        from _ in ScribeStore.Articles.Save<ScribeRuntime>(
            new Article(id, cmd.Title, cmd.Body, cmd.Author))
        select id;
}

[Runtime]
public sealed partial class ScribeRuntime;

[DispatchModule]
public static partial class ScribeDispatch;

What's Eff<RT, T>?

Eff<ScribeRuntime, ArticleId> is a lazy computation that requires a ScribeRuntime environment and produces an ArticleId. The from/select syntax is C# LINQ — each from sequences an effect, and errors short-circuit the pipeline. Nothing executes until you call RunAsync. See Effects Composition for the full guide.

Now test it:

[TestRuntime<ScribeRuntime>]
public partial class TestScribeRuntime;

public class CreateArticleTests
{
    [Test]
    public async Task CreatesArticle()
    {
        var program = ScribeDispatch.Dispatch(
            new CreateArticle("Hello", "World", "Alice"));

        var result = await program.RunAsync(
            TestScribeRuntime.CreateConfigured());

        await Assert.That(result).IsSucc();
    }
}

No mocking framework. No DI container setup. CreateConfigured() wires in-memory stubs for every store and service. The same test runs against real Postgres with INTEGRATION=true.


Feature Highlights

Effects — wrap any service as composable pipeline

[EffectsModule(typeof(IEmailService))]
public sealed partial class EmailEffects;

// Use in handlers:
from _ in EmailEffects.EmailService.SendAsync<AppRuntime>(message)
select unit;

The generator produces Eff<RT, A> wrappers with error context and OpenTelemetry tracing for every method on IEmailService. See Effects.

REST API — dispatch handlers become HTTP endpoints

[CommandHandler, HttpPost("/articles")]
public static Eff<ScribeRuntime, ArticleId> Handle(CreateArticle cmd) => ...

[QueryHandler, HttpGet("/articles/{id}")]
public static Eff<ScribeRuntime, Option<Article>> Handle(GetArticleById query) => ...

The generator maps annotated handlers to Minimal API endpoints with OpenAPI, auth, and typed responses. See REST API.

Testing — stubs generated alongside production code

// Seed test data, inspect calls — no mocking
var runtime = TestScribeRuntime.CreateConfigured(cfg =>
    cfg.WithArticles(new Article(ArticleId.New(), "Test", "Body", "Alice")));

See Testing.


Modules

Module What It Generates
Effects Eff<RT, A> wrappers with OpenTelemetry tracing for any interface
Runtime Auto-discovered composition root with DI, observability, and startup validation
Testing Generated test runtimes, stubs, dual-mode unit/integration testing
REST API Minimal API endpoints from dispatch handlers with OpenAPI
DataStore CRUD interfaces, in-memory implementations, effect methods, DI registration
Dispatch Typed CQRS dispatch with validation, authorization, and audit
Typed IDs Type-safe ID structs with serialization converters
Configuration Typed config providers with JSON Schema and secrets management
HTTP Client Declarative HTTP clients from annotated methods
Event Queue In-process event queues backed by Channel<T> with background workers
Event Store Event sourcing with projections, upcasting, and Marten backend
Background Jobs Scheduled and recurring jobs with Hangfire integration
Cache Typed caching backed by IDistributedCache
Clock Testable time via IClock with FakeTimeProvider support
Integration Events Cross-service events with stable wire names and transport abstraction

Built On

Deepstaging.Roslyn — a fluent toolkit for building Roslyn source generators, analyzers, and code fixes.

License

RPL-1.5 (Reciprocal Public License) — Real reciprocity, no loopholes.

You can use this code, modify it, and share it freely. But when you deploy it — internally or externally, as a service or within your company — you share your improvements back under the same license.

Why? We believe if you benefit from this code, the community should benefit from your improvements. That's the deal we think is fair.

Personal research and experimentation? No obligations. Go learn, explore, and build.

See LICENSE for the full legal text.