Skip to content

Deepstaging

Attributes in, working software out. Write your domain types, annotate them with C# attributes, and the compiler generates persistence, dispatch, API endpoints, test infrastructure, and observability. No reflection. No runtime magic. Just generated C# you can read and debug.

dotnet add package Deepstaging --prerelease

Why Deepstaging? Walkthrough → Guides →


Every line here is domain logic

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

[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;

From these declarations, the compiler generates: a CRUD store interface and in-memory implementation, an input validator from the [Required] and [StringLength] annotations, a typed dispatch method, an audit trail entry, DI registration, and a test runtime. You wrote the domain. The compiler wrote the infrastructure.

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 — no mocking framework, no DI container setup:

[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();
    }
}

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


What the compiler generates

You write The compiler generates
[StoredEntity] on a record CRUD interface, in-memory store, effect methods, DI registration
[CommandHandler] on a method Typed dispatch, validation pipeline, audit hook, API endpoint
[EffectsModule] on a class Eff<RT, A> wrappers with OpenTelemetry tracing for every method
[Runtime] on a class Composition root with auto-discovered capabilities and startup validation
[TestRuntime] on a class Test stubs, seed builders, dual-mode unit/integration switching
[HttpPost] on a handler Minimal API endpoint with OpenAPI, auth, and typed responses
[ConfigRoot] on a class Typed config with JSON Schema, secrets routing, and startup validation

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


All modules

Model Your Domain

Module What It Generates
Typed IDs Type-safe ID structs with serialization converters
Data Store CRUD interfaces, in-memory implementations, effect methods, DI registration
Event Store Event sourcing with projections, upcasting, and Marten backend
Configuration Typed config providers with JSON Schema and secrets management
Dispatch Typed CQRS dispatch with validation, authorization, and audit
Cache Typed caching backed by IDistributedCache

Build Your API

Module What It Generates
REST API Minimal API endpoints from dispatch handlers with OpenAPI
HTTP Client Declarative HTTP clients from annotated methods
WebSocket Real-time WebSocket endpoints with typed messages
Webhooks Inbound webhook validation and dispatch

React to Events

Module What It Generates
Event Queue In-process event queues backed by Channel<T> with background workers
Integration Events Cross-service events with stable wire names and transport abstraction
Background Jobs Scheduled and recurring jobs with Hangfire integration

Cross-Cutting Concerns

Module What It Generates
Tenancy Multi-tenant isolation with per-tenant schemas and provisioning
Authentication User context and identity resolution
Idempotency Request deduplication with pluggable stores
Rate Limiting Per-handler rate limiting policies
Audit Trail Structured audit log entries with effect methods
Soft Delete Logical deletion with automatic query filtering
Clock Testable time via IClock with FakeTimeProvider support

Communicate

Module What It Generates
Email Transactional email with template rendering
SMS SMS messaging with provider abstraction
Notifications Multi-channel notification dispatch and storage

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.