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.
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 |
|---|---|
| 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.