Modular Ops
Modules let you define a command set once and mount it at multiple points in your command graph — DRY at the routing level.
Defining a module
Section titled “Defining a module”A module is a method (or class) that registers commands onto an IReplMap:
void RegisterCrudCommands<T>(IReplMap map, IStore<T> store) where T : IEntity{ map.Map("list", () => store.All()); map.Map("show {id:int}", (int id) => store.Get(id)); map.Map("delete {id:int}", (int id) => { store.Delete(id); return Results.Success($"Deleted {id}."); });}Mounting the module
Section titled “Mounting the module”app.Context("contacts", contacts => RegisterCrudCommands(contacts, contactStore));
app.Context("products", products => RegisterCrudCommands(products, productStore));Both contacts and products now expose the same list, show, and delete commands against their respective stores.
With DI and open generics
Section titled “With DI and open generics”Register a generic store and let the DI container resolve per-type:
var app = ReplApp.Create(services =>{ services.AddSingleton(typeof(IStore<>), typeof(InMemoryStore<>));});app.Context("contacts", contacts =>{ contacts.Map("list", static (IStore<Contact> store) => store.All()); contacts.Map("show {id:int}", static (int id, IStore<Contact> store) => store.Get(id));});Conditional modules
Section titled “Conditional modules”Use MapModule(..., predicate) when an entire module should be available only for selected runtime channels:
sealed class AdminModule : IReplModule{ public void Map(IReplMap map) { map.Context("admin", admin => { admin.Map("purge-all", static (IStore<Contact> store) => store.DeleteAll()); }); }}
app.MapModule( new AdminModule(), static context => context.Channel != ReplRuntimeChannel.Programmatic);The predicate receives a ModulePresenceContext, so it can inspect the active ReplRuntimeChannel and session state before the module is included in the command graph.
For a single human-only command, keep the module mounted and call .AutomationHidden() on the command builder returned by Map(...).
Reusing the same module under multiple contexts
Section titled “Reusing the same module under multiple contexts”void RegisterAuditLog(IReplMap map, string entityType){ map.Map("log", (IAuditLog log) => log.GetEntries(entityType)); map.Map("log clear", (IAuditLog log) => { log.Clear(entityType); return Results.Success("Log cleared."); });}
app.Context("contacts", contacts =>{ RegisterCrudCommands(contacts, contactStore); RegisterAuditLog(contacts, "contact");});
app.Context("products", products =>{ RegisterCrudCommands(products, productStore); RegisterAuditLog(products, "product");});This composes cleanly: each context has both CRUD and audit commands without copy-pasting routes.
Further reading
Section titled “Further reading”- Modules — complete
MapModuleAPI: DI-resolved generic form, instance form, conditional and DI-injected predicates, dynamic context routes. - Dependency Injection — service lifetimes and how services are resolved inside module constructors and handlers.
- Best Practices — when to extract a module and how to keep the command graph maintainable.