From 667fcd5364c948b39f8c0de9fd5fa750325fda61 Mon Sep 17 00:00:00 2001 From: Daniel Bohlin Date: Thu, 11 Jun 2026 08:09:55 +0200 Subject: [PATCH 1/2] Add docs site (cache.tharga.net) and migrate PackageIconUrl - Migrate on all 6 packages from the WordPress-hosted thargelion.se path to https://thargelion.net/assets/component-cache.png - Add DocFX docs site under docs/ published to cache.tharga.net: landing page, 4 articles (getting-started, cache-types, persistence-backends, monitoring), and API reference from the core + Redis/MongoDB/File/Mcp libraries - Use the absolute logo URL with the fixed _master.tmpl overlay so the navbar logo/favicon render correctly on sub-pages (avoids the DocFX 2.78.x ../https:// trap) - Add docs + docs-deploy jobs to build.yml (gated on needs: release) and grant pages/id-token permissions - Link the docs site from README; gitignore DocFX output --- .github/workflows/build.yml | 48 ++++++ .gitignore | 5 + README.md | 2 + .../Tharga.Cache.Blazor.csproj | 2 +- Tharga.Cache.File/Tharga.Cache.File.csproj | 2 +- Tharga.Cache.Mcp/Tharga.Cache.Mcp.csproj | 2 +- .../Tharga.Cache.MongoDB.csproj | 2 +- Tharga.Cache.Redis/Tharga.Cache.Redis.csproj | 2 +- Tharga.Cache/Tharga.Cache.csproj | 2 +- docs/CNAME | 1 + docs/articles/cache-types.md | 95 +++++++++++ docs/articles/getting-started.md | 149 ++++++++++++++++ docs/articles/index.md | 8 + docs/articles/monitoring.md | 74 ++++++++ docs/articles/persistence-backends.md | 85 ++++++++++ docs/articles/toc.yml | 10 ++ docs/docfx.json | 57 +++++++ docs/index.md | 52 ++++++ docs/templates/thg/layout/_master.tmpl | 160 ++++++++++++++++++ docs/templates/thg/public/main.css | 10 ++ docs/toc.yml | 6 + 21 files changed, 768 insertions(+), 6 deletions(-) create mode 100644 docs/CNAME create mode 100644 docs/articles/cache-types.md create mode 100644 docs/articles/getting-started.md create mode 100644 docs/articles/index.md create mode 100644 docs/articles/monitoring.md create mode 100644 docs/articles/persistence-backends.md create mode 100644 docs/articles/toc.yml create mode 100644 docs/docfx.json create mode 100644 docs/index.md create mode 100644 docs/templates/thg/layout/_master.tmpl create mode 100644 docs/templates/thg/public/main.css create mode 100644 docs/toc.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f4afc66..c3f81e7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,6 +13,8 @@ permissions: contents: write pull-requests: write security-events: write + pages: write + id-token: write jobs: build: @@ -286,3 +288,49 @@ jobs: --notes "Pre-release from \`${{ github.head_ref }}\` branch." \ --prerelease \ ./artifacts/*.nupkg + + docs: + needs: release + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/master' && github.event_name == 'push' + + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 8.0.x + 9.0.x + 10.0.x + + - name: Install DocFX + run: dotnet tool install -g docfx + + - name: Restore + run: dotnet restore + + - name: Build site + run: docfx docs/docfx.json + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/_site + + docs-deploy: + needs: docs + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/master' && github.event_name == 'push' + concurrency: + group: pages + cancel-in-progress: false + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 0bc30ee..82e7f0e 100644 --- a/.gitignore +++ b/.gitignore @@ -399,3 +399,8 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml /.claude/settings.local.json + +# DocFX generated output +/docs/_site/ +/docs/api/ +/docs/obj/ diff --git a/README.md b/README.md index 75b145a..bbeb85e 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ A flexible .NET caching library with multiple cache strategies, pluggable persistence backends, eviction policies, and a Blazor monitoring UI. +📖 **Documentation:** [cache.tharga.net](https://cache.tharga.net) + ## Packages | Package | Description | NuGet | diff --git a/Tharga.Cache.Blazor/Tharga.Cache.Blazor.csproj b/Tharga.Cache.Blazor/Tharga.Cache.Blazor.csproj index 32ed938..3ea8413 100644 --- a/Tharga.Cache.Blazor/Tharga.Cache.Blazor.csproj +++ b/Tharga.Cache.Blazor/Tharga.Cache.Blazor.csproj @@ -7,7 +7,7 @@ Thargelion AB Tharga Cache Blazor Component for manageing the Tharga Cache features in blazor. - https://thargelion.se/wp-content/uploads/2025/11/Thargelion-Cache-Icon-150.png + https://thargelion.net/assets/component-cache.png True README.md portable diff --git a/Tharga.Cache.File/Tharga.Cache.File.csproj b/Tharga.Cache.File/Tharga.Cache.File.csproj index d96e804..cdb1b14 100644 --- a/Tharga.Cache.File/Tharga.Cache.File.csproj +++ b/Tharga.Cache.File/Tharga.Cache.File.csproj @@ -7,7 +7,7 @@ Thargelion AB Tharga Cache File File cache features. - https://thargelion.se/wp-content/uploads/2025/11/Thargelion-Cache-Icon-150.png + https://thargelion.net/assets/component-cache.png True https://github.com/Tharga/Cache README.md diff --git a/Tharga.Cache.Mcp/Tharga.Cache.Mcp.csproj b/Tharga.Cache.Mcp/Tharga.Cache.Mcp.csproj index 1d01bf1..9e3bc41 100644 --- a/Tharga.Cache.Mcp/Tharga.Cache.Mcp.csproj +++ b/Tharga.Cache.Mcp/Tharga.Cache.Mcp.csproj @@ -8,7 +8,7 @@ Thargelion AB Tharga Cache MCP Exposes Tharga.Cache monitoring data (cache types, items, persistence health, fetch queue) and actions (clear all, clear stale) via MCP (Model Context Protocol). Plugs into Tharga.Mcp. - https://thargelion.se/wp-content/uploads/2025/11/Thargelion-Cache-Icon-150.png + https://thargelion.net/assets/component-cache.png True https://github.com/Tharga/Cache README.md diff --git a/Tharga.Cache.MongoDB/Tharga.Cache.MongoDB.csproj b/Tharga.Cache.MongoDB/Tharga.Cache.MongoDB.csproj index 11456e4..a95399b 100644 --- a/Tharga.Cache.MongoDB/Tharga.Cache.MongoDB.csproj +++ b/Tharga.Cache.MongoDB/Tharga.Cache.MongoDB.csproj @@ -7,7 +7,7 @@ Thargelion AB Tharga Cache MongoDB MongoDB cache features. - https://thargelion.se/wp-content/uploads/2025/11/Thargelion-Cache-Icon-150.png + https://thargelion.net/assets/component-cache.png True https://github.com/Tharga/Cache README.md diff --git a/Tharga.Cache.Redis/Tharga.Cache.Redis.csproj b/Tharga.Cache.Redis/Tharga.Cache.Redis.csproj index a87bb3d..90606e8 100644 --- a/Tharga.Cache.Redis/Tharga.Cache.Redis.csproj +++ b/Tharga.Cache.Redis/Tharga.Cache.Redis.csproj @@ -8,7 +8,7 @@ Thargelion AB Tharga Cache Redis Redis cache features. - https://thargelion.se/wp-content/uploads/2025/11/Thargelion-Cache-Icon-150.png + https://thargelion.net/assets/component-cache.png True https://github.com/Tharga/Cache README.md diff --git a/Tharga.Cache/Tharga.Cache.csproj b/Tharga.Cache/Tharga.Cache.csproj index d78a429..a6c7f78 100644 --- a/Tharga.Cache/Tharga.Cache.csproj +++ b/Tharga.Cache/Tharga.Cache.csproj @@ -8,7 +8,7 @@ Thargelion AB Tharga Cache Memory cache features. - https://thargelion.se/wp-content/uploads/2025/11/Thargelion-Cache-Icon-150.png + https://thargelion.net/assets/component-cache.png True https://github.com/Tharga/Cache README.md diff --git a/docs/CNAME b/docs/CNAME new file mode 100644 index 0000000..cb1cdc0 --- /dev/null +++ b/docs/CNAME @@ -0,0 +1 @@ +cache.tharga.net diff --git a/docs/articles/cache-types.md b/docs/articles/cache-types.md new file mode 100644 index 0000000..075d3fc --- /dev/null +++ b/docs/articles/cache-types.md @@ -0,0 +1,95 @@ +# Cache types + +Tharga.Cache exposes four cache interfaces, each with a different expiration strategy. Inject the one that matches the lifetime your data needs — they share the same `ICache` operations (see [Common operations](getting-started.md#common-operations)) and differ only in when items expire. + +| Interface | Expiration | Lifetime | Typical use | +|-----------|------------|----------|-------------| +| `IEternalCache` | Never (until explicitly removed) | Singleton | Reference data that rarely changes | +| `ITimeToLiveCache` | Fixed time after insertion (TTL) | Singleton | API responses, computed results | +| `ITimeToIdleCache` | Reset on every access (TTI) | Singleton | Session-like data kept alive while in use | +| `IScopeCache` | End of the DI scope | Scoped | Per-request memoization | + +## IEternalCache + +Data never expires unless explicitly dropped or invalidated. Registered as a singleton. + +```csharp +public class UserService(IEternalCache cache) +{ + public Task GetUserAsync(string userId) => + cache.GetAsync(userId, () => LoadUserAsync(userId)); +} +``` + +## ITimeToLiveCache + +Data expires a fixed time after insertion (TTL). Registered as a singleton. + +```csharp +var data = await ttlCache.GetAsync( + "product-123", + () => LoadProductAsync(123), + TimeSpan.FromMinutes(10)); +``` + +## ITimeToIdleCache + +The expiration clock resets every time the item is accessed (TTI). Useful for session-like data that should stay cached while it is actively being used and expire once it goes quiet. + +```csharp +var session = await ttiCache.GetAsync( + "session-abc", + () => LoadSessionAsync("abc"), + TimeSpan.FromMinutes(30)); +``` + +## IScopeCache + +A scoped cache instance, cleared at the end of the DI scope (for example, per HTTP request). Data never expires within the scope, which makes it a clean way to memoize a value that may be requested several times while handling a single request. + +```csharp +var result = await scopeCache.GetAsync( + "current-context", + () => BuildContextAsync()); +``` + +## Stale-while-revalidate + +For the time-based caches, enabling `StaleWhileRevalidate` returns expired data immediately while fresh data is fetched in the background — eliminating the latency spike of a cache miss. + +```csharp +o.RegisterType(t => +{ + t.StaleWhileRevalidate = true; + t.DefaultFreshSpan = TimeSpan.FromMinutes(5); +}); +``` + +Use `GetWithCallbackAsync` to be notified when the fresh value arrives: + +```csharp +var (data, isFresh) = await cache.GetWithCallbackAsync( + "product-123", + () => LoadProductAsync(123), + async freshData => + { + // Called when the background refresh completes + await NotifyClientsAsync(freshData); + }, + TimeSpan.FromMinutes(5)); + +if (!isFresh) +{ + // data is stale; the callback will fire when fresh data is ready +} +``` + +## Events + +All cache types raise events for observing activity: + +```csharp +cache.DataSetEvent += (sender, args) => Console.WriteLine($"Cached: {args.Key}"); +cache.DataGetEvent += (sender, args) => Console.WriteLine($"Retrieved: {args.Key}"); +cache.DataDropEvent += (sender, args) => Console.WriteLine($"Removed: {args.Key}"); +``` diff --git a/docs/articles/getting-started.md b/docs/articles/getting-started.md new file mode 100644 index 0000000..5696f34 --- /dev/null +++ b/docs/articles/getting-started.md @@ -0,0 +1,149 @@ +# Getting started + +## Install + +``` +dotnet add package Tharga.Cache +``` + +The core package caches in memory and has no external dependencies. Add a backend package only when you want a type to persist outside the process — see [Persistence backends](persistence-backends.md). + +## Register + +```csharp +builder.Services.AddCache(); +``` + +`AddCache` is idempotent — calling it more than once (for example when several libraries each register cache types) merges the registrations instead of throwing. + +## The get-or-load pattern + +Inject one of the four cache interfaces and call `GetAsync` with a key and a fetch delegate. The first call runs the delegate and stores the result; subsequent calls within the fresh span return the cached value without invoking the delegate. + +```csharp +public class WeatherService(ITimeToLiveCache cache) +{ + public Task GetForecastAsync() => + cache.GetAsync( + "weather-forecast", + () => LoadFromApiAsync(), + TimeSpan.FromMinutes(5)); +} +``` + +Pick the interface that matches the lifetime you need — `IEternalCache`, `ITimeToLiveCache`, `ITimeToIdleCache`, or `IScopeCache`. See [Cache types](cache-types.md). + +## Common operations + +Every cache type shares these operations from `ICache`: + +```csharp +// Get or load +var data = await cache.GetAsync("key", () => FetchDataAsync()); + +// Peek without triggering a load (returns default if not cached) +var cached = await cache.PeekAsync("key"); + +// Manually set a value +await cache.SetAsync("key", myData); + +// Remove a specific item +await cache.DropAsync("key"); + +// Mark as stale (triggers reload on next access) +await cache.InvalidateAsync("key"); +``` + +Time-based caches (`ITimeToLiveCache`, `ITimeToIdleCache`) also take an explicit fresh span: + +```csharp +var data = await timeCache.GetAsync("key", () => FetchAsync(), TimeSpan.FromMinutes(5)); +await timeCache.SetAsync("key", myData, TimeSpan.FromHours(1)); +``` + +> The no-span `SetAsync(key, data)` overload requires `DefaultFreshSpan` to be configured on the type registration. The explicit-span overload works unconditionally. + +## Configuration + +### Per-type options + +Use `RegisterType` to configure behavior for a specific cached type: + +```csharp +builder.Services.AddCache(o => +{ + o.RegisterType(t => + { + t.DefaultFreshSpan = TimeSpan.FromMinutes(10); + t.StaleWhileRevalidate = true; + t.MaxCount = 1000; + t.MaxSize = Size.MB * 100; + t.EvictionPolicy = EvictionPolicy.LeastRecentlyUsed; + }); +}); +``` + +| Option | Default | Description | +|--------|---------|-------------| +| `DefaultFreshSpan` | `null` | Default TTL when not specified per call | +| `StaleWhileRevalidate` | `false` | Return stale data immediately while refreshing in the background | +| `ReturnDefaultOnFirstLoad` | `false` | Return `default(T)` on first cache miss instead of blocking; factory runs in the background | +| `MaxCount` | `null` | Maximum number of cached items for this type | +| `MaxSize` | `null` | Maximum total size in bytes for this type | +| `EvictionPolicy` | `FirstInFirstOut` | Strategy when `MaxCount` or `MaxSize` is exceeded | + +### Global options + +```csharp +builder.Services.AddCache(o => +{ + o.MaxConcurrentFetchCount = 20; // Max parallel background fetches (default: 10) + o.WatchDogInterval = TimeSpan.FromMinutes(2); // Stale-cleanup interval (default: 60s) + + o.Default = new CacheTypeOptions // Defaults applied to all types + { + DefaultFreshSpan = TimeSpan.FromSeconds(30) + }; +}); +``` + +### Eviction policies + +When `MaxCount` or `MaxSize` is exceeded, items are evicted according to the configured policy: + +| Policy | Description | +|--------|-------------| +| `FirstInFirstOut` | Removes the oldest items first (default) | +| `LeastRecentlyUsed` | Removes items that haven't been accessed recently | +| `RandomReplacement` | Removes items at random (lowest overhead) | + +### Size constants + +Use the `Size` helper for readable byte values — `Size.KB`, `Size.MB`, `Size.GB`, `Size.TB`: + +```csharp +t.MaxSize = Size.MB * 500; // 500 MB +t.MaxSize = Size.GB * 2; // 2 GB +``` + +## Key building + +Cache keys can be simple strings or composed from multiple parts with `KeyBuilder`: + +```csharp +// Simple string key (implicit conversion) +Key key = "my-cache-key"; + +// Composite key from multiple parts +var key = KeyBuilder + .Set("userId", userId) + .Set("department", department); + +var data = await cache.GetAsync(key, () => LoadProfileAsync(userId, department)); +``` + +## Next steps + +- [Cache types](cache-types.md) — choose the right expiration strategy. +- [Persistence backends](persistence-backends.md) — persist types to Redis, MongoDB, or disk. +- [Monitoring](monitoring.md) — inspect and manage the cache at runtime. diff --git a/docs/articles/index.md b/docs/articles/index.md new file mode 100644 index 0000000..c9438d8 --- /dev/null +++ b/docs/articles/index.md @@ -0,0 +1,8 @@ +# Articles + +Guides for using Tharga.Cache. + +- **[Getting started](getting-started.md)** — install, register, the get-or-load pattern, common operations, and configuration. +- **[Cache types](cache-types.md)** — `IEternalCache`, `ITimeToLiveCache`, `ITimeToIdleCache`, and `IScopeCache`, and when to reach for each. +- **[Persistence backends](persistence-backends.md)** — the default in-memory store plus the Redis, MongoDB, and file backends, and how to mix them per type. +- **[Monitoring](monitoring.md)** — inspect cache state with `ICacheMonitor`, the Blazor dashboard, and the MCP provider. diff --git a/docs/articles/monitoring.md b/docs/articles/monitoring.md new file mode 100644 index 0000000..312eb38 --- /dev/null +++ b/docs/articles/monitoring.md @@ -0,0 +1,74 @@ +# Monitoring + +Tharga.Cache exposes its runtime state three ways: programmatically through `ICacheMonitor`, visually through the Blazor dashboard, and over the Model Context Protocol through the MCP provider. + +## ICacheMonitor + +Inject `ICacheMonitor` to inspect and manage cache state in code: + +```csharp +public class CacheHealthCheck(ICacheMonitor monitor) +{ + public void PrintStats() + { + foreach (var typeInfo in monitor.GetInfos()) + { + Console.WriteLine($"{typeInfo.Type.Name}: {typeInfo.Items.Count} items"); + } + + Console.WriteLine($"Fetch queue: {monitor.GetFetchQueueCount()}"); + } + + public void Cleanup() + { + monitor.ClearStale(); // Remove expired items + monitor.ClearAll(); // Remove everything + } +} +``` + +`GetInfos()` returns one entry per tracked type, each carrying its `PersistType` and per-item details (key, size, access count, staleness, expiration, and load time). + +## Blazor UI + +``` +dotnet add package Tharga.Cache.Blazor +``` + +Drop the components into a Blazor page: + +```razor +@page "/cache" +@rendermode InteractiveServer + + + +``` + +- **SummaryView** shows total item count, total size, fetch-queue depth, and a "Clear Cache" button. +- **ListView** shows a grid of all cached types and their items; an info button opens a detail dialog with the item's JSON content (lazily loaded on expand). + +Both views subscribe to monitor events for live refresh. The host page needs ``, the `tharga.blazor.js` script, and `AddBlazoredLocalStorage()` registered. + +## MCP provider + +``` +dotnet add package Tharga.Cache.Mcp +``` + +Register the provider alongside your other MCP providers: + +```csharp +builder.Services.AddThargaMcp(b => b.AddCache()); +``` + +```csharp +app.UseThargaMcp(); // exposes the endpoint at /mcp +``` + +This surfaces cache state to an AI agent over MCP: + +- **Resources** — `cache://types`, `cache://items`, `cache://health`, `cache://queue`. +- **Tools** — `cache.clear_stale`, `cache.clear_all`. + +The provider runs on System scope; authorization, scopes, and audit flow through Tharga.Mcp as for any other provider. diff --git a/docs/articles/persistence-backends.md b/docs/articles/persistence-backends.md new file mode 100644 index 0000000..2359831 --- /dev/null +++ b/docs/articles/persistence-backends.md @@ -0,0 +1,85 @@ +# Persistence backends + +By default every type is cached in memory (`IMemory`). You can assign a different backend per type so that data survives a process restart or is shared across instances. Each backend ships in its own package; the choice is made at registration time with `RegisterType()`. + +| Backend | Interface | Package | +|---------|-----------|---------| +| In-memory (default) | `IMemory` | Tharga.Cache | +| Redis | `IRedis` | Tharga.Cache.Redis | +| MongoDB | `IMongoDB` | Tharga.Cache.MongoDB | +| File | `IFile` | Tharga.Cache.File | + +> `IMemoryWithRedis` is deprecated. Use `IRedis` or `IMemory` explicitly instead. + +## Redis + +``` +dotnet add package Tharga.Cache.Redis +``` + +```csharp +builder.Services.AddCache(o => +{ + o.AddRedisDBOptions(r => + r.ConnectionStringLoader = sp => "localhost:6379"); + + o.RegisterType(); +}); +``` + +## MongoDB + +``` +dotnet add package Tharga.Cache.MongoDB +``` + +```csharp +builder.Services.AddCache(o => +{ + o.AddMongoDBOptions(m => + { + m.CollectionName = "_cache"; + m.ConfigurationName = "Default"; + }); + + o.RegisterType(); +}); +``` + +## File + +``` +dotnet add package Tharga.Cache.File +``` + +```csharp +builder.Services.AddCache(o => +{ + o.AddFileDBOptions(f => + { + f.CompanyName = "MyCompany"; + f.AppName = "MyApp"; + f.Format = Format.Json; // Json, Base64, GZip, or Brotli + }); + + o.RegisterType(); +}); +``` + +## Mixing backends + +Different types can use different backends in the same application — register each one against the backend that fits it: + +```csharp +builder.Services.AddCache(o => +{ + o.AddRedisDBOptions(r => r.ConnectionStringLoader = sp => "localhost:6379"); + o.AddMongoDBOptions(); + + o.RegisterType(); + o.RegisterType(); + o.RegisterType(); +}); +``` + +Types you never call `RegisterType` for fall back to `IMemory`. diff --git a/docs/articles/toc.yml b/docs/articles/toc.yml new file mode 100644 index 0000000..c116cc0 --- /dev/null +++ b/docs/articles/toc.yml @@ -0,0 +1,10 @@ +- name: Overview + href: index.md +- name: Getting started + href: getting-started.md +- name: Cache types + href: cache-types.md +- name: Persistence backends + href: persistence-backends.md +- name: Monitoring + href: monitoring.md diff --git a/docs/docfx.json b/docs/docfx.json new file mode 100644 index 0000000..d3203ae --- /dev/null +++ b/docs/docfx.json @@ -0,0 +1,57 @@ +{ + "metadata": [ + { + "src": [ + { + "src": "..", + "files": [ + "Tharga.Cache/Tharga.Cache.csproj", + "Tharga.Cache.Redis/Tharga.Cache.Redis.csproj", + "Tharga.Cache.MongoDB/Tharga.Cache.MongoDB.csproj", + "Tharga.Cache.File/Tharga.Cache.File.csproj", + "Tharga.Cache.Mcp/Tharga.Cache.Mcp.csproj" + ] + } + ], + "dest": "api", + "includePrivateMembers": false, + "disableGitFeatures": false, + "disableDefaultFilter": false, + "noRestore": false, + "namespaceLayout": "flattened", + "memberLayout": "samePage", + "EnumSortOrder": "alphabetic", + "allowCompilationErrors": false + } + ], + "build": { + "content": [ + { + "files": ["**/*.{md,yml}"], + "exclude": ["_site/**", "obj/**"] + } + ], + "resource": [ + { + "files": ["CNAME"] + } + ], + "output": "_site", + "globalMetadata": { + "_appName": "Tharga.Cache", + "_appTitle": "Tharga.Cache", + "_appLogoPath": "https://thargelion.net/assets/component-cache.png", + "_appFaviconPath": "https://thargelion.net/assets/component-cache.png", + "_enableSearch": true, + "_disableContribution": false, + "_gitContribute": { + "repo": "https://github.com/Tharga/Cache", + "branch": "master" + } + }, + "template": ["default", "modern", "templates/thg"], + "postProcessors": ["ExtractSearchIndex"], + "keepFileLink": false, + "disableGitFeatures": false + } +} diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..585a8ce --- /dev/null +++ b/docs/index.md @@ -0,0 +1,52 @@ +--- +_layout: landing +--- + +# Tharga.Cache + +A flexible .NET caching library with multiple cache strategies, pluggable persistence backends, eviction policies, and a Blazor monitoring UI. Register one line in DI, inject a cache, and get-or-load with a fetch delegate — the first call loads, subsequent calls within the fresh span return the cached value. + +## Packages + +| Package | What it does | +|---|---| +| [Tharga.Cache](https://www.nuget.org/packages/Tharga.Cache) | Core library with in-memory caching, the four cache interfaces, options, key building, and monitoring. | +| [Tharga.Cache.Redis](https://www.nuget.org/packages/Tharga.Cache.Redis) | Redis persistence backend (`IRedis`). | +| [Tharga.Cache.MongoDB](https://www.nuget.org/packages/Tharga.Cache.MongoDB) | MongoDB persistence backend (`IMongoDB`). | +| [Tharga.Cache.File](https://www.nuget.org/packages/Tharga.Cache.File) | File-based persistence backend (`IFile`). | +| [Tharga.Cache.Blazor](https://www.nuget.org/packages/Tharga.Cache.Blazor) | Blazor monitoring UI components. | +| [Tharga.Cache.Mcp](https://www.nuget.org/packages/Tharga.Cache.Mcp) | MCP (Model Context Protocol) provider for cache monitoring. | + +## Quick start + +``` +dotnet add package Tharga.Cache +``` + +```csharp +builder.Services.AddCache(); +``` + +```csharp +public class WeatherService(ITimeToLiveCache cache) +{ + public Task GetForecastAsync() => + cache.GetAsync( + "weather-forecast", + () => LoadFromApiAsync(), + TimeSpan.FromMinutes(5)); +} +``` + +See [Getting started](articles/getting-started.md) for the full walkthrough. + +## What's in the box + +- **Four cache strategies** — `IEternalCache` (never expires), `ITimeToLiveCache` (TTL), `ITimeToIdleCache` (TTI, clock resets on access), and `IScopeCache` (per-DI-scope). See [Cache types](articles/cache-types.md). +- **Pluggable backends** — in-memory by default, with Redis, MongoDB, and file backends you can assign per type and freely mix. See [Persistence backends](articles/persistence-backends.md). +- **Smart loading** — stale-while-revalidate, background refresh, eviction policies (FIFO / LRU / random), and size/count limits. See [Configuration](articles/getting-started.md#configuration). +- **Monitoring** — inspect cache state via `ICacheMonitor`, a Blazor dashboard, or over MCP. See [Monitoring](articles/monitoring.md). + +## Repo + +[github.com/Tharga/Cache](https://github.com/Tharga/Cache) — source, issues, releases. diff --git a/docs/templates/thg/layout/_master.tmpl b/docs/templates/thg/layout/_master.tmpl new file mode 100644 index 0000000..acac4c1 --- /dev/null +++ b/docs/templates/thg/layout/_master.tmpl @@ -0,0 +1,160 @@ +{{!Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license.}} +{{! Copy of templates/modern/layout/_master.tmpl from DocFX 2.78.5 with two targeted fixes for absolute-URL handling on the favicon and navbar logo. The original prepends _rel (e.g. "../") to _appFaviconPath and _appLogoPath unconditionally, which turns an absolute URL into "../https://..." on sub-pages. The fix moves _rel into the fallback path only, so absolute URLs are rendered as-is. Keep _appFaviconPath / _appLogoPath absolute (or unset to use the fallback). }} +{{!include(/^public/.*/)}} +{{!include(favicon.ico)}} +{{!include(logo.svg)}} + + + + + {{#redirect_url}} + + {{/redirect_url}} + {{^redirect_url}} + {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}} + + + {{#_description}}{{/_description}} + {{#description}}{{/description}} + + + + + + {{#_noindex}}{{/_noindex}} + {{#_enableSearch}}{{/_enableSearch}} + {{#_disableNewTab}}{{/_disableNewTab}} + {{#_disableTocFilter}}{{/_disableTocFilter}} + {{#docurl}}{{/docurl}} + + + + + + + + + + + + + + + + + + {{#_googleAnalyticsTagId}} + + + {{/_googleAnalyticsTagId}} + {{/redirect_url}} + + + {{^redirect_url}} + +
+ {{^_disableNavbar}} + + {{/_disableNavbar}} +
+ +
+ {{^_disableToc}} +
+
+
+
Table of Contents
+ +
+
+ +
+
+
+ {{/_disableToc}} + +
+
+ {{^_disableToc}} + + {{/_disableToc}} + + {{^_disableBreadcrumb}} + + {{/_disableBreadcrumb}} +
+ +
+ {{!body}} +
+ + {{^_disableContribution}} +
+ {{#sourceurl}} + {{__global.improveThisDoc}} + {{/sourceurl}} + {{^sourceurl}}{{#docurl}} + {{__global.improveThisDoc}} + {{/docurl}}{{/sourceurl}} +
+ {{/_disableContribution}} + + {{^_disableNextArticle}} + + {{/_disableNextArticle}} + +
+ + {{^_disableAffix}} +
+ +
+ {{/_disableAffix}} +
+ + {{#_enableSearch}} +
+ {{/_enableSearch}} + +
+
+
+ {{{_appFooter}}}{{^_appFooter}}Made with docfx{{/_appFooter}} +
+
+
+ + {{/redirect_url}} + diff --git a/docs/templates/thg/public/main.css b/docs/templates/thg/public/main.css new file mode 100644 index 0000000..0ffa6fb --- /dev/null +++ b/docs/templates/thg/public/main.css @@ -0,0 +1,10 @@ +/* Custom style overrides for the Tharga.Cache documentation site. + Loaded after the modern template's docfx.min.css, so these win. */ + +/* The source logo (https://thargelion.net/assets/component-cache.png) is 150x150; + constrain its display size in the navbar instead of resizing the image file. */ +#logo, +.navbar-brand img { + height: 32px; + width: auto; +} diff --git a/docs/toc.yml b/docs/toc.yml new file mode 100644 index 0000000..019419e --- /dev/null +++ b/docs/toc.yml @@ -0,0 +1,6 @@ +- name: Home + href: index.md +- name: Articles + href: articles/ +- name: API + href: api/ From cb0017e34afe549f4c62113e82696bff720fd18e Mon Sep 17 00:00:00 2001 From: Daniel Bohlin Date: Thu, 11 Jun 2026 10:36:40 +0200 Subject: [PATCH 2/2] update nuget packages --- Sample/Tharga.Cache.Console/Tharga.Cache.Console.csproj | 2 +- Sample/Tharga.Cache.WebApi/Tharga.Cache.WebApi.csproj | 2 +- Tharga.Cache.Blazor/Tharga.Cache.Blazor.csproj | 2 +- Tharga.Cache.File.Tests/Tharga.Cache.File.Tests.csproj | 4 ++-- Tharga.Cache.Mcp/Tharga.Cache.Mcp.csproj | 2 +- Tharga.Cache.MongoDB.Tests/Tharga.Cache.MongoDB.Tests.csproj | 4 ++-- Tharga.Cache.MongoDB/Tharga.Cache.MongoDB.csproj | 2 +- Tharga.Cache.Redis.Tests/Tharga.Cache.Redis.Tests.csproj | 4 ++-- Tharga.Cache.Redis/Tharga.Cache.Redis.csproj | 4 ++-- Tharga.Cache.Tests/Tharga.Cache.Tests.csproj | 4 ++-- Tharga.Cache/Tharga.Cache.csproj | 2 +- 11 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Sample/Tharga.Cache.Console/Tharga.Cache.Console.csproj b/Sample/Tharga.Cache.Console/Tharga.Cache.Console.csproj index 586bb66..88fd6c5 100644 --- a/Sample/Tharga.Cache.Console/Tharga.Cache.Console.csproj +++ b/Sample/Tharga.Cache.Console/Tharga.Cache.Console.csproj @@ -7,7 +7,7 @@ - + diff --git a/Sample/Tharga.Cache.WebApi/Tharga.Cache.WebApi.csproj b/Sample/Tharga.Cache.WebApi/Tharga.Cache.WebApi.csproj index b55f84c..3da2aa0 100644 --- a/Sample/Tharga.Cache.WebApi/Tharga.Cache.WebApi.csproj +++ b/Sample/Tharga.Cache.WebApi/Tharga.Cache.WebApi.csproj @@ -7,7 +7,7 @@ - + diff --git a/Tharga.Cache.Blazor/Tharga.Cache.Blazor.csproj b/Tharga.Cache.Blazor/Tharga.Cache.Blazor.csproj index 3ea8413..384a02d 100644 --- a/Tharga.Cache.Blazor/Tharga.Cache.Blazor.csproj +++ b/Tharga.Cache.Blazor/Tharga.Cache.Blazor.csproj @@ -27,7 +27,7 @@ - + diff --git a/Tharga.Cache.File.Tests/Tharga.Cache.File.Tests.csproj b/Tharga.Cache.File.Tests/Tharga.Cache.File.Tests.csproj index 9f586c3..1f35435 100644 --- a/Tharga.Cache.File.Tests/Tharga.Cache.File.Tests.csproj +++ b/Tharga.Cache.File.Tests/Tharga.Cache.File.Tests.csproj @@ -7,8 +7,8 @@ - - + + all diff --git a/Tharga.Cache.Mcp/Tharga.Cache.Mcp.csproj b/Tharga.Cache.Mcp/Tharga.Cache.Mcp.csproj index 9e3bc41..d84f08f 100644 --- a/Tharga.Cache.Mcp/Tharga.Cache.Mcp.csproj +++ b/Tharga.Cache.Mcp/Tharga.Cache.Mcp.csproj @@ -33,7 +33,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Tharga.Cache.MongoDB.Tests/Tharga.Cache.MongoDB.Tests.csproj b/Tharga.Cache.MongoDB.Tests/Tharga.Cache.MongoDB.Tests.csproj index 1b19f80..738059d 100644 --- a/Tharga.Cache.MongoDB.Tests/Tharga.Cache.MongoDB.Tests.csproj +++ b/Tharga.Cache.MongoDB.Tests/Tharga.Cache.MongoDB.Tests.csproj @@ -7,8 +7,8 @@ - - + + all diff --git a/Tharga.Cache.MongoDB/Tharga.Cache.MongoDB.csproj b/Tharga.Cache.MongoDB/Tharga.Cache.MongoDB.csproj index a95399b..beaf8af 100644 --- a/Tharga.Cache.MongoDB/Tharga.Cache.MongoDB.csproj +++ b/Tharga.Cache.MongoDB/Tharga.Cache.MongoDB.csproj @@ -39,7 +39,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Tharga.Cache.Redis.Tests/Tharga.Cache.Redis.Tests.csproj b/Tharga.Cache.Redis.Tests/Tharga.Cache.Redis.Tests.csproj index b08c208..b92e581 100644 --- a/Tharga.Cache.Redis.Tests/Tharga.Cache.Redis.Tests.csproj +++ b/Tharga.Cache.Redis.Tests/Tharga.Cache.Redis.Tests.csproj @@ -9,8 +9,8 @@ - - + + all diff --git a/Tharga.Cache.Redis/Tharga.Cache.Redis.csproj b/Tharga.Cache.Redis/Tharga.Cache.Redis.csproj index 90606e8..b6b0b8f 100644 --- a/Tharga.Cache.Redis/Tharga.Cache.Redis.csproj +++ b/Tharga.Cache.Redis/Tharga.Cache.Redis.csproj @@ -40,8 +40,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Tharga.Cache.Tests/Tharga.Cache.Tests.csproj b/Tharga.Cache.Tests/Tharga.Cache.Tests.csproj index 4d5791b..34cc9e0 100644 --- a/Tharga.Cache.Tests/Tharga.Cache.Tests.csproj +++ b/Tharga.Cache.Tests/Tharga.Cache.Tests.csproj @@ -9,8 +9,8 @@ - - + + all diff --git a/Tharga.Cache/Tharga.Cache.csproj b/Tharga.Cache/Tharga.Cache.csproj index a6c7f78..e32773b 100644 --- a/Tharga.Cache/Tharga.Cache.csproj +++ b/Tharga.Cache/Tharga.Cache.csproj @@ -43,7 +43,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive