# Dependencies ## Internal Dependencies ```mermaid graph TD api["SlpModularCms.Api
Client"] slave["SlpModularCms.Api.Slave
Client"] core["SlpModularCms.Core"] mid["Modules.Identity"] mav["Modules.Availability"] mma["Modules.Master"] tcore["Core.Tests"] tid["Modules.Identity.Tests"] tav["Modules.Availability.Tests"] tma["Modules.Master.Tests"] fe["frontend
admin SPA"] api --> core api --> mid api --> mav api --> mma slave --> core slave --> mid slave --> mav mid --> core mav --> core mma --> core tcore --> core tid --> mid tav --> mav tma --> mma fe -->|"REST at runtime"| api api -->|"pnpm build at publish"| fe classDef client fill:#90cdf4,stroke:#2b6cb0,stroke-width:1px,color:#000; classDef corelayer fill:#fbd38d,stroke:#c05621,stroke-width:1px,color:#000; classDef module fill:#9ae6b4,stroke:#2f855a,stroke-width:1px,color:#000; classDef test fill:#e2e8f0,stroke:#4a5568,stroke-width:1px,color:#000; classDef frontend fill:#d6bcfa,stroke:#6b46c1,stroke-width:1px,color:#000; class api,slave client; class core corelayer; class mid,mav,mma module; class tcore,tid,tav,tma test; class fe frontend; ``` Text alternative: Both host projects reference Core and their modules; every module references only Core; each test project targets its own production project; the admin SPA calls the API at runtime and is itself built by the API project at publish time — a bidirectional coupling between backend and frontend. ### Dependency Details #### `SlpModularCms.Api` → `SlpModularCms.Core` - **Type**: Compile - **Reason**: `AddCoreInfrastructure`, `AddCmsCors`, `AddCmsRateLimiting`, `ModuleOrchestrator`, `ApiPrefixConvention`. #### `SlpModularCms.Api` → `Modules.Identity`, `Modules.Availability`, `Modules.Master` - **Type**: Compile (present to force the module DLLs into the output directory) - **Reason**: `ModuleOrchestrator` discovers modules by globbing `SlpModularCms.Modules.*.dll` in the app base directory, so a project reference is how a module ends up deployed. The code itself does not call into the modules directly. **Consequence for deployment: which modules an instance has is determined by which DLLs the published output contains.** #### `SlpModularCms.Api.Slave` → `Core`, `Modules.Identity`, `Modules.Availability` - **Type**: Compile - **Reason**: Same as above, minus the Master module — the omission is the entire point of this host. #### `Modules.Identity` → `Core` - **Type**: Compile - **Reason**: Identity entities, `IAuthService`, `IInvitationService`, `ISetupService`, request/response models, authorization policies. #### `Modules.Availability` → `Core` - **Type**: Compile - **Reason**: `IAvailabilityService`, `AvailabilityStatus`, `AvailabilityOptions`, `MasterControlledAvailabilityException`, `IModule`. #### `Modules.Master` → `Core` - **Type**: Compile - **Reason**: `IModule`, authorization policies, shared exception types. #### Test projects → their production project (and transitively `Core`) - **Type**: Test - **Reason**: Each suite mirrors one production project, per the solution-folder rules in `CLAUDE.md`. #### `frontend` → `SlpModularCms.Api` - **Type**: Runtime (HTTP/REST) - **Reason**: All data comes from `/api/v1/**` at `VITE_API_BASE_URL`, with credentials so the refresh cookie travels. #### `SlpModularCms.Api` → `frontend` (build-time, reverse direction) - **Type**: Build - **Reason**: The `BuildAndCopyAdminFrontend` MSBuild target runs `pnpm install --frozen-lockfile` and `pnpm build` in `frontend/` before publish and copies `dist/**` into `wwwroot/admin/`. **This makes Node and pnpm hard prerequisites of `dotnet publish` on any build agent.** ### Cross-instance runtime dependencies Not project references, but real coupling between deployed instances: - A **Master** instance calls each registered slave's `POST /api/v1/master/register` and `POST /api/v1/master/status`, authenticated with `X-Master-Api-Key`, through a resilience pipeline (2 retries, exponential backoff with jitter, configurable timeout). - Each **slave** calls its Master's `GET /api/v1/SlaveStatus` on a timer (`MasterPolling:PollIntervalSeconds`), and fails open to Available after `MasterPolling:FailOpenAfterMinutes` of unreachability. - Both directions require the two instances to be reachable over HTTP from one another, which is a deployment/network consideration rather than a code one. ## External Dependencies ### Backend — `SlpModularCms.Core` | Package | Version | Purpose | License | |---|---|---|---| | `Microsoft.EntityFrameworkCore.SqlServer` | 10.0.9 | SQL Server persistence | MIT | | `Microsoft.AspNetCore.Identity.EntityFrameworkCore` | 10.0.9 | Users, roles, password hashing | MIT | | `Microsoft.AspNetCore.Authentication.JwtBearer` | 10.0.9 | Bearer token validation | MIT | | `Microsoft.AspNetCore.OpenApi` | 10.0.9 | OpenAPI document generation | MIT | | `Asp.Versioning.Mvc` | 10.0.0 | API version reporting | MIT | | `Microsoft.Extensions.DependencyInjection.Abstractions` | 10.0.9 | DI abstractions | MIT | | `Microsoft.Extensions.Hosting.Abstractions` | 10.0.9 | Hosting abstractions | MIT | | `Microsoft.Extensions.Logging.Abstractions` | 10.0.9 | Logging abstractions | MIT | | `FrameworkReference: Microsoft.AspNetCore.App` | net10.0 | Lets a class library use ASP.NET Core types, incl. Data Protection | MIT | ### Backend — `SlpModularCms.Api` | Package | Version | Purpose | License | |---|---|---|---| | `Asp.Versioning.Mvc` | 10.0.0 | API versioning | MIT | | `Microsoft.AspNetCore.Authentication.JwtBearer` | 10.0.9 | Bearer auth | MIT | | `Microsoft.AspNetCore.OpenApi` | 10.0.9 | OpenAPI | MIT | | `Microsoft.EntityFrameworkCore.Design` | 10.0.9 (PrivateAssets) | `dotnet ef` tooling | MIT | | `Scalar.AspNetCore` | 2.16.3 | `/scalar` API reference, Development only | MIT | `SlpModularCms.Api.Slave` carries `Microsoft.EntityFrameworkCore.Design` and `Scalar.AspNetCore` at the same versions. ### Backend — modules | Project | Package | Version | Purpose | License | |---|---|---|---|---| | `Modules.Master` | `Microsoft.Extensions.Http.Resilience` | 9.6.0 | Retry/timeout for master→slave calls (pulls in Polly) | MIT | | `Modules.Availability` | — | — | No external packages beyond Core's transitives | — | | `Modules.Identity` | — | — | No external packages beyond Core's transitives | — | **Version note**: `Microsoft.Extensions.Http.Resilience` 9.6.0 is a 9.x package on `net10.0` targets. It works, but it is the one dependency out of step with the otherwise uniform 10.0.x line — worth pinning deliberately rather than by accident in any CI setup. ### Backend — test projects (all four, identical set) | Package | Version | Purpose | License | |---|---|---|---| | `Microsoft.NET.Test.Sdk` | 17.14.1 | Test host | MIT | | `xunit` | 2.9.3 | Test framework | Apache-2.0 | | `xunit.runner.visualstudio` | 3.1.4 | Test adapter | Apache-2.0 | | `FluentAssertions` | 8.10.0 | Assertions | Dual: free for non-commercial / paid commercial from v8 — **worth verifying against how this project is used** | | `NSubstitute` | 5.3.0 | Mocking | BSD-3-Clause | | `AutoFixture` | 4.18.1 | Test data generation | MIT | | `Microsoft.EntityFrameworkCore.InMemory` | 10.0.9 | In-memory provider for tests | MIT | | `coverlet.collector` | 6.0.4 | Coverage collection | MIT | ### Frontend — runtime (`frontend/package.json` dependencies) | Package | Version | Purpose | License | |---|---|---|---| | `react`, `react-dom` | ^19.2.6 | UI framework | MIT | | `@tanstack/react-router` | ^1.170.16 | Routing (honours `BASE_URL`, so `/admin` works) | MIT | | `@tanstack/react-query` | ^5.101.0 | Server state | MIT | | `@radix-ui/react-{dialog,dropdown-menu,label,select,slot}` | 1.x–2.x | Accessible primitives | MIT | | `react-hook-form` | ^7.79.0 | Forms | MIT | | `@hookform/resolvers` | ^5.4.0 | Validation bridge | MIT | | `zod` | ^4.4.3 | Schema validation (also validates app config) | MIT | | `i18next`, `react-i18next`, `i18next-browser-languagedetector` | 26 / 17 / 8 | NL/EN localisation | MIT | | `lucide-react` | ^1.21.0 | Icons | ISC | | `sonner` | ^2.0.7 | Toasts | MIT | | `class-variance-authority`, `clsx`, `tailwind-merge` | 0.7 / 2.1 / 3.6 | Class composition | MIT | ### Frontend — build and test (devDependencies) | Package | Version | Purpose | License | |---|---|---|---| | `vite` | ^8.0.12 | Build tool / dev server | MIT | | `@vitejs/plugin-react` | ^6.0.1 | React support | MIT | | `typescript` | ~6.0.2 | Type checking (`tsc -b` gates the build) | Apache-2.0 | | `tailwindcss`, `@tailwindcss/vite` | ^4.3.1 | Styling | MIT | | `vitest`, `@vitest/coverage-v8` | ^4.1.9 | Tests and coverage | MIT | | `jsdom` | ^29.1.1 | DOM for tests | MIT | | `@testing-library/{react,jest-dom,user-event}` | 16.3 / 6.9 / 14.6 | Component testing | MIT | | `msw` | ^2.14.6 | Request mocking | MIT | | `eslint`, `typescript-eslint`, `eslint-plugin-react-hooks`, `eslint-plugin-react-refresh`, `@eslint/js`, `globals` | 10 / 8.59 / 7.1 / 0.5 / 10 / 17.6 | Linting | MIT | | `prettier` | ^3.8.4 | Formatting | MIT | | `concurrently` | ^9.1.2 | Runs `dev:all` (master + slave dev servers) | MIT | | `@types/{node,react,react-dom}` | 24 / 19.2 / 19.2 | Type definitions | MIT | ### Lockfiles and reproducibility - `frontend/pnpm-lock.yaml` exists, and the publish target uses `--frozen-lockfile`, so frontend installs are reproducible. - There is **no `packages.lock.json`** for any .NET project — NuGet restore is not locked, so `dotnet restore` can resolve differently over time on floating transitive versions. Relevant if reproducible CI builds matter. ### Dependencies that are absent but expected by this feature No package currently provides uptime, analytics, error tracking or structured logging. Adding **Sentry** (a `Sentry.AspNetCore` package on the backend and `@sentry/react` on the frontend) and **Umami** (a script tag, no package) would be new dependencies; **UptimeRobot** is external and needs only an HTTP endpoint to probe.