diff --git a/CLAUDE.md b/CLAUDE.md index 8c5ef3d6..974093f5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -45,7 +45,7 @@ dotnet run --project template/SimpleModule.Host # runs on https://localhost: ```bash npm install # install all workspace dependencies npm run dev # start development (dotnet + all module watches, unminified JS) -npm run dev:build # build all modules in dev mode (unminified, with source maps) +npm run build:dev # build all modules in dev mode (unminified, with source maps) npm run build # production build (minified, optimized) npm run check # biome lint + format check npm run check:fix # auto-fix lint + formatting @@ -146,7 +146,7 @@ See [docs/CONSTITUTION.md](docs/CONSTITUTION.md) for the authoritative reference - Module boundaries, dependencies, and data ownership - Communication patterns (contracts and events) - Endpoint, frontend, and authorization rules -- Compiler-enforced diagnostics (SM0001-SM0054) +- Compiler-enforced diagnostics (SM0001-SM0061) - Framework contributor guidelines ## Key Constraints @@ -169,8 +169,8 @@ When you add a new `IViewEndpoint`, you **must** register it in your module's `P **Why:** The C# source generator discovers your new endpoint and validates it's properly decorated, but React needs a corresponding entry in the page registry. If you forget: - The endpoint compiles and runs fine on the server -- Navigating to that page in React silently 404s client-side (no error in console, no error response shown to user) -- The developer won't know for hours or until QA finds it +- Navigating to that page throws a descriptive client-side error — the page resolver names the missing page and lists the available ones, the ClientApp shows an error toast, and the error is logged to the browser console +- `npm run validate-pages` catches the mismatch at build time (and in CI) before it reaches the browser **Pattern:** diff --git a/README.md b/README.md index cf3724b4..7f6f5f79 100644 --- a/README.md +++ b/README.md @@ -55,26 +55,30 @@ This starts the .NET backend and Vite watchers for all modules and the ClientApp SimpleModule.AppHost # .NET Aspire orchestration (app + PostgreSQL + pgAdmin) SimpleModule.ServiceDefaults # Aspire service defaults (OpenTelemetry, health checks) framework/ - SimpleModule.Core # IModule, IEndpoint, IViewEndpoint, [Dto], [Module], IEventBus + SimpleModule.Core # IModule, IEndpoint, IViewEndpoint, [Dto], [Module], events, menus SimpleModule.Generator # Roslyn IIncrementalGenerator (netstandard2.0) SimpleModule.Database # Multi-provider DB (SQLite, PostgreSQL, SQL Server) SimpleModule.Hosting # ASP.NET host builder extensions, Inertia page rendering - SimpleModule.DevTools # Developer tooling and diagnostics - SimpleModule.Storage # File storage abstraction with Local, Azure Blob, and S3 providers + SimpleModule.Storage(.Local/.Azure/.S3) # File storage abstraction + provider implementations + SimpleModule.Testing # Shared testing utilities for module test projects modules/ - Admin, Agents, AuditLogs, BackgroundJobs, Dashboard, Email, - FeatureFlags, FileStorage, Localization, Marketplace, Orders, - PageBuilder, Products, Rag, RateLimiting, Settings, Tenants, Users + Admin, AuditLogs, BackgroundJobs, Dashboard, Email, FeatureFlags, + FileStorage, Identity, Keycloak, Localization, Notifications, + RateLimiting, Settings, Tenants, Users OpenIddict # OpenID Connect / OAuth 2.0 via OpenIddict Permissions # RBAC and access control packages/ SimpleModule.Client # Vite plugin + page resolution for module frontends SimpleModule.UI # Radix UI component library with Tailwind SimpleModule.Theme.Default # Tailwind CSS base theme + SimpleModule.Echo # Real-time client for the broadcasting hub (wraps SignalR) + SimpleModule.TsConfig # Shared TypeScript configuration template/ SimpleModule.Host # Host app (net10.0) — wires modules via generated code cli/ SimpleModule.Cli # `sm` CLI tool for scaffolding and validation +tools/ + SimpleModule.DevTools # Developer tooling and diagnostics tests/ # Framework tests, shared test infrastructure, and e2e tests scripts/ # Build/dev orchestrators, type extraction, component scaffolding ``` @@ -83,9 +87,9 @@ scripts/ # Build/dev orchestrators, type extraction, compo ``` Browser request - → ASP.NET route handler calls Inertia.Render("Products/Browse", { products }) + → ASP.NET route handler calls Inertia.Render("Tenants/Browse", { tenants }) → Inertia middleware renders static HTML shell with JSON props - → Client loads React, dynamically imports module's Products.pages.js bundle + → Client loads React, dynamically imports module's Tenants.pages.js bundle → React component hydrates with server-provided props ``` @@ -93,31 +97,31 @@ Subsequent navigations are XHR — Inertia fetches JSON props and swaps the Reac ### Module Anatomy -Each module follows the same structure. Using Products as an example: +Each module follows the same structure. Using the Tenants module as an example: ``` -modules/Products/ +modules/Tenants/ src/ - Products.Contracts/ # Public interface + DTOs (what other modules depend on) - IProductContracts.cs # Service interface - Product.cs # [Dto] — generates TypeScript types - ProductId.cs # Strongly-typed ID - CreateProductRequest.cs # Request DTO - Products/ # Implementation (never referenced by other modules) - ProductsModule.cs # [Module("Products")] + IModule — DI, menus, permissions - ProductsDbContext.cs # Module-scoped EF Core context - ProductService.cs # Implements IProductContracts - Endpoints/Products/ # IEndpoint classes (auto-discovered, auto-mapped) - Views/ # React .tsx pages - Pages/index.ts # Page registry — maps route names to lazy component imports + SimpleModule.Tenants.Contracts/ # Public interface + DTOs (what other modules depend on) + ITenantContracts.cs # Service interface + Tenant.cs # [Dto] — generates TypeScript types + TenantId.cs # Strongly-typed ID + CreateTenantRequest.cs # Request DTO + SimpleModule.Tenants/ # Implementation (never referenced by other modules) + TenantsModule.cs # [Module("Tenants")] + IModule — DI, menus, permissions + TenantsDbContext.cs # Module-scoped EF Core context + TenantService.cs # Implements ITenantContracts + Endpoints/Tenants/ # IEndpoint classes (auto-discovered, auto-mapped) + Pages/ # IViewEndpoint classes + co-located React .tsx pages + Pages/index.ts # Page registry — maps route names to lazy component imports tests/ - Products.Tests/ # xUnit tests for this module + SimpleModule.Tenants.Tests/ # xUnit tests for this module ``` ### Module Communication -- **Contracts** — Modules expose a `.Contracts` project with a public interface (e.g., `IProductContracts`) and `[Dto]` types. Other modules depend on contract interfaces, never on implementation projects. -- **Event bus** — `IEventBus.PublishAsync()` broadcasts to all `IEventHandler` implementations. Handlers execute sequentially; failures are isolated and collected into an `AggregateException`. +- **Contracts** — Modules expose a `.Contracts` project with a public interface (e.g., `ITenantContracts`) and `[Dto]` types. Other modules depend on contract interfaces, never on implementation projects. +- **Event bus** — Modules publish domain events via Wolverine's `IMessageBus.PublishAsync()`. Handlers are discovered by naming convention (a class ending in `Handler`/`Consumer` with a `Handle`/`Consume` method) and dispatched in-process through a durable inbox/outbox — no manual registration needed. ### Database diff --git a/docs/CONSTITUTION.md b/docs/CONSTITUTION.md index 7cdb3a7c..7ac9e632 100644 --- a/docs/CONSTITUTION.md +++ b/docs/CONSTITUTION.md @@ -50,7 +50,7 @@ All hooks are optional. All have default no-op implementations. 7. **ConfigureFeatureFlags** -- register feature flag definitions 8. **ConfigureAgents** -- register AI agent definitions 9. **ConfigureRateLimits** -- register rate limit policies -10. **ConfigureHost** -- configure host-level integrations (e.g., TickerQ, database initialization) after the host is built +10. **ConfigureHost** -- configure host-level integrations (e.g., database initialization and schema creation) after the host is built 11. **OnStartAsync** -- one-time initialization after all services are registered 12. **OnStopAsync** -- graceful shutdown cleanup 13. **CheckHealthAsync** -- report module health status (Healthy, Degraded, Unhealthy) @@ -316,7 +316,7 @@ public sealed class CreateProductRequest : FormRequest - Every `IViewEndpoint` must have a matching entry in `Pages/index.ts`. - The key must match the component name passed to `Inertia.Render()`. -- Missing entries cause silent client-side 404s with no error in the console. +- Missing entries throw a descriptive client-side error (the resolver names the missing page and lists the available ones) and surface an error toast -- they do not fail silently. - Run `npm run validate-pages` to verify all endpoints have matching page entries. ### Dynamic Imports diff --git a/docs/site/frontend/overview.md b/docs/site/frontend/overview.md index 78d0fad3..56c0d6b0 100644 --- a/docs/site/frontend/overview.md +++ b/docs/site/frontend/overview.md @@ -25,7 +25,7 @@ Each module compiles its React pages into a single ES module bundle (`{ModuleNam | Technology | Version | Purpose | |---|---|---| | React | 19 | UI rendering | -| Inertia.js | 2.x | SPA-like navigation without a client-side router | +| Inertia.js | 3.x | SPA-like navigation without a client-side router | | Vite | 6.x | Build tooling and dev server | | Tailwind CSS | 4.x | Utility-first styling | | TypeScript | 5.8 | Type safety | diff --git a/docs/site/getting-started/introduction.md b/docs/site/getting-started/introduction.md index 8b2b2330..6851ecf0 100644 --- a/docs/site/getting-started/introduction.md +++ b/docs/site/getting-started/introduction.md @@ -188,7 +188,7 @@ You don't write registration code, startup configuration, or reflection-based di | Testing | xUnit.v3, FluentAssertions, Bogus, Playwright, BenchmarkDotNet, NBomber | | Database | SQLite, PostgreSQL, SQL Server via EF Core | | File storage | Local, AWS S3, Azure Blob Storage | -| Job scheduling | TickerQ with CRON support | +| Job scheduling | Database-backed queue with CRON (Cronos) support | | CLI | System.CommandLine | ## Next Steps diff --git a/docs/site/getting-started/quick-start.md b/docs/site/getting-started/quick-start.md index d6c0119b..a5a7c3cd 100644 --- a/docs/site/getting-started/quick-start.md +++ b/docs/site/getting-started/quick-start.md @@ -227,7 +227,7 @@ export const pages: Record = { ``` ::: danger Don't Forget the Page Registry -Every `IViewEndpoint` that calls `Inertia.Render("Customers/Something", ...)` **must** have a matching entry in `Pages/index.ts`. If you forget, the endpoint works on the server but silently 404s on the client with no error message. +Every `IViewEndpoint` that calls `Inertia.Render("Customers/Something", ...)` **must** have a matching entry in `Pages/index.ts`. If you forget, the endpoint works on the server but the client throws a descriptive error (and shows an error toast) instead of rendering the page. Run `npm run validate-pages` to catch mismatches. ::: @@ -285,7 +285,7 @@ This starts: - The SimpleModule host app on **http://localhost:8080** - A **PostgreSQL** instance for production-like database behavior -::: tip Development vs Customerion Database +::: tip Development vs Production Database During local development with `npm run dev`, the app uses SQLite by default -- no database server needed. Docker Compose switches to PostgreSQL to match production behavior. ::: @@ -294,8 +294,8 @@ During local development with `npm run dev`, the app uses SQLite by default -- n | Command | What it does | | ------------------------ | ----------------------------------------------- | | `npm run dev` | Start backend + all frontend watchers | -| `npm run build` | Customerion build (minified, optimized) | -| `npm run dev:build` | Dev build (unminified, source maps) | +| `npm run build` | Production build (minified, optimized) | +| `npm run build:dev` | Dev build (unminified, source maps) | | `npm run check` | Lint + format check (Biome) | | `npm run check:fix` | Auto-fix lint + formatting | | `npm run validate-pages` | Verify all endpoints have page registry entries | diff --git a/docs/site/guide/endpoints.md b/docs/site/guide/endpoints.md index 47628222..e890895e 100644 --- a/docs/site/guide/endpoints.md +++ b/docs/site/guide/endpoints.md @@ -246,7 +246,7 @@ public class EditEndpoint : IViewEndpoint ``` ::: warning -When adding a new `IViewEndpoint`, you **must** also register the corresponding component in your module's `Pages/index.ts`. The Inertia component name in `Inertia.Render("Customers/Edit", ...)` must have a matching key in the pages record. If you forget, the page will silently 404 on the client side with no error. +When adding a new `IViewEndpoint`, you **must** also register the corresponding component in your module's `Pages/index.ts`. The Inertia component name in `Inertia.Render("Customers/Edit", ...)` must have a matching key in the pages record. If you forget, the client throws a descriptive error (and shows an error toast) instead of rendering the page. Run `npm run validate-pages` to verify all endpoints have matching frontend entries. ::: diff --git a/docs/site/guide/inertia.md b/docs/site/guide/inertia.md index d20039dc..8b715b5a 100644 --- a/docs/site/guide/inertia.md +++ b/docs/site/guide/inertia.md @@ -270,7 +270,7 @@ export const pages: Record = { ``` ::: danger Critical -Every `IViewEndpoint` that calls `Inertia.Render("Module/Page", ...)` **must** have a matching entry in the module's `Pages/index.ts`. Missing entries silently fail with no error in the console. Run `npm run validate-pages` to catch mismatches. +Every `IViewEndpoint` that calls `Inertia.Render("Module/Page", ...)` **must** have a matching entry in the module's `Pages/index.ts`. Missing entries throw a descriptive client-side error (the resolver lists the available pages) and surface an error toast — they do not fail silently. Run `npm run validate-pages` to catch mismatches at build time. ::: ### Writing a Page Component diff --git a/docs/site/guide/modules.md b/docs/site/guide/modules.md index 3bce1fde..ebcfc3a4 100644 --- a/docs/site/guide/modules.md +++ b/docs/site/guide/modules.md @@ -44,8 +44,9 @@ public interface IModule | `ConfigurePermissions` | Define module-specific permissions for authorization | | `ConfigureSettings` | Register configurable settings for the module | | `ConfigureFeatureFlags` | Register feature flag definitions | +| `ConfigureAgents` | Register AI agent definitions | | `ConfigureRateLimits` | Register rate limit policies | -| `ConfigureHost` | Configure host-level integrations after the host is built (e.g., TickerQ, database initialization) | +| `ConfigureHost` | Configure host-level integrations after the host is built (e.g., database initialization and schema creation) | | `OnStartAsync` | One-time async initialization after all services are registered | | `OnStopAsync` | Graceful shutdown cleanup | | `CheckHealthAsync` | Report per-module health status | diff --git a/docs/site/reference/api.md b/docs/site/reference/api.md index 32a384cb..31df720f 100644 --- a/docs/site/reference/api.md +++ b/docs/site/reference/api.md @@ -42,8 +42,9 @@ public interface IModule | `ConfigurePermissions` | Application startup | Register permission definitions | | `ConfigureSettings` | Application startup | Register settings definitions | | `ConfigureFeatureFlags` | Application startup | Register feature flag definitions | +| `ConfigureAgents` | Application startup | Register AI agent definitions | | `ConfigureRateLimits` | Application startup | Register rate limit policies | -| `ConfigureHost` | After host is built | Configure host-level integrations (TickerQ, DB init) | +| `ConfigureHost` | After host is built | Configure host-level integrations (database init, schema creation) | | `OnStartAsync` | After services registered | One-time async initialization | | `OnStopAsync` | Graceful shutdown | Cleanup, flush buffers, drain work | | `CheckHealthAsync` | Health check endpoint | Report module health status | diff --git a/framework/SimpleModule.Core/README.md b/framework/SimpleModule.Core/README.md index e800f79a..14ab0bf7 100644 --- a/framework/SimpleModule.Core/README.md +++ b/framework/SimpleModule.Core/README.md @@ -28,7 +28,7 @@ public sealed class ProductsModule : IModule - **IModule** interface for defining self-contained modules - **IEndpoint** interface for auto-discovered Minimal API endpoints - **[Dto]** attribute for cross-module data transfer types with TypeScript generation -- **IEventBus** for decoupled module-to-module communication +- **Domain events** (`IEvent` / `DomainEvent`) dispatched in-process via Wolverine's `IMessageBus` for decoupled module-to-module communication - **IMenuRegistry** for dynamic navigation menu registration - **Inertia.js integration** for server-driven React page rendering