+
@@ -13,17 +14,96 @@
## Overview
-Short description goes here.
+**squid-std** is a batteries-included standard library for .NET, distilled from years and years
+of building real-world server software. Instead of re-solving the same problems on every project,
+it bundles the foundations you reach for again and again behind small, well-defined contracts:
+
+- **Security & hashing** — password hashing and verification (`HashUtils`), AES-GCM secret
+ protection and a pluggable secret store (`ISecretProtector` / `ISecretStore`).
+- **Configuration** — a YAML-backed config manager with section registration and environment-variable expansion.
+- **Serialization** — unified JSON/YAML utilities (`JsonUtils`, `YamlUtils`) and a shared `IDataSerializer` / `IDataDeserializer`.
+- **String & platform helpers** — case converters (camel/kebab/snake/pascal/…), network, version, platform and resource utilities.
+- **Runtime services** — DI bootstrap, event bus, job system, timer/cron scheduler, metrics and storage.
+- **Infrastructure modules** — messaging (in-memory + RabbitMQ), caching (in-memory + Redis),
+ database access, networking (TCP/UDP) and Lua scripting.
+
+Everything is modular: take only the packages you need, each behind a clean abstraction with an
+in-memory implementation for tests and an external backend for production.
+
+## Requirements
+
+- [.NET 10 SDK](https://dotnet.microsoft.com/download)
+- [Docker](https://www.docker.com/) — only for running the integration tests (Testcontainers spin up RabbitMQ and Redis).
+
+## Quick Start
+
+```bash
+dotnet add package SquidStd.Services.Core
+dotnet add package SquidStd.Caching
+```
+
+```csharp
+using SquidStd.Caching.Abstractions.Interfaces;
+using SquidStd.Caching.Extensions;
+using SquidStd.Services.Core.Services.Bootstrap;
+
+// Create the bootstrapper (registers the core services), then opt into the modules you need.
+var bootstrap = SquidStdBootstrap.Create()
+ .ConfigureServices(container => container.AddInMemoryCache());
+
+await bootstrap.StartAsync();
+
+var cache = bootstrap.Resolve();
+await cache.SetAsync("answer", 42, TimeSpan.FromMinutes(5));
+var answer = await cache.GetAsync("answer");
+
+await bootstrap.StopAsync();
+```
+
+`SquidStdBootstrap` owns a DryIoc container, wires the core services, and drives the
+`StartAsync` / `StopAsync` lifecycle of every registered `ISquidStdService`. Use `RunAsync` to
+block until cancellation for long-running hosts.
## Packages
-| Package | Version | Downloads |
-|---------|---------|-----------|
-| `SquidStd.Abstractions` | [](https://www.nuget.org/packages/SquidStd.Abstractions/) |  |
-| `SquidStd.Core` | [](https://www.nuget.org/packages/SquidStd.Core/) |  |
-| `SquidStd.Network` | [](https://www.nuget.org/packages/SquidStd.Network/) |  |
-| `SquidStd.Plugin.Abstractions` | [](https://www.nuget.org/packages/SquidStd.Plugin.Abstractions/) |  |
-| `SquidStd.Services.Core` | [](https://www.nuget.org/packages/SquidStd.Services.Core/) |  |
+| Package | Description | Links |
+|---------|-------------|-------|
+| `SquidStd.Core` | Foundational contracts & utilities (config, event bus, jobs, metrics, storage, YAML/JSON, Serilog sink). | [readme](src/SquidStd.Core/README.md) · [](https://www.nuget.org/packages/SquidStd.Core/) |
+| `SquidStd.Abstractions` | DI registration plumbing (`ISquidStdService`, `RegisterStdService`, `RegisterConfigSection`). | [readme](src/SquidStd.Abstractions/README.md) · [](https://www.nuget.org/packages/SquidStd.Abstractions/) |
+| `SquidStd.Services.Core` | Concrete services: config, event bus, jobs, timer/cron scheduler, dispatcher, metrics, storage. | [readme](src/SquidStd.Services.Core/README.md) · [](https://www.nuget.org/packages/SquidStd.Services.Core/) |
+| `SquidStd.AspNetCore` | ASP.NET Core host integration for the SquidStd service stack. | [readme](src/SquidStd.AspNetCore/README.md) · [](https://www.nuget.org/packages/SquidStd.AspNetCore/) |
+| `SquidStd.Network` | TCP/UDP servers & clients, sessions, framing/middleware pipeline, span readers/writers. | [readme](src/SquidStd.Network/README.md) · [](https://www.nuget.org/packages/SquidStd.Network/) |
+| `SquidStd.Plugin.Abstractions` | Plugin contracts (`ISquidStdPlugin`, metadata, context). | [readme](src/SquidStd.Plugin.Abstractions/README.md) · [](https://www.nuget.org/packages/SquidStd.Plugin.Abstractions/) |
+| `SquidStd.Database.Abstractions` | Provider-agnostic data-access contracts (`IDataAccess`, `BaseEntity`, `PagedResultData`). | [readme](src/SquidStd.Database.Abstractions/README.md) · [](https://www.nuget.org/packages/SquidStd.Database.Abstractions/) |
+| `SquidStd.Database` | FreeSql-backed data access (CRUD/bulk/paging, URI connection strings, ZLinq helpers). | [readme](src/SquidStd.Database/README.md) · [](https://www.nuget.org/packages/SquidStd.Database/) |
+| `SquidStd.Messaging.Abstractions` | Messaging contracts (`IMessageQueue`, `IQueueProvider`, serializer/metrics, listeners). | [readme](src/SquidStd.Messaging.Abstractions/README.md) · [](https://www.nuget.org/packages/SquidStd.Messaging.Abstractions/) |
+| `SquidStd.Messaging` | In-memory messaging transport (`AddInMemoryMessaging`). | [readme](src/SquidStd.Messaging/README.md) · [](https://www.nuget.org/packages/SquidStd.Messaging/) |
+| `SquidStd.Messaging.RabbitMq` | RabbitMQ messaging transport (`AddRabbitMqMessaging`). | [readme](src/SquidStd.Messaging.RabbitMq/README.md) · [](https://www.nuget.org/packages/SquidStd.Messaging.RabbitMq/) |
+| `SquidStd.Caching.Abstractions` | Caching contracts (`ICacheService`, `ICacheProvider`, `CacheService` facade, metrics, connection string). | [readme](src/SquidStd.Caching.Abstractions/README.md) · [](https://www.nuget.org/packages/SquidStd.Caching.Abstractions/) |
+| `SquidStd.Caching` | In-memory cache backend (`AddInMemoryCache`). | [readme](src/SquidStd.Caching/README.md) · [](https://www.nuget.org/packages/SquidStd.Caching/) |
+| `SquidStd.Caching.Redis` | Redis cache backend (`AddRedisCache`). | [readme](src/SquidStd.Caching.Redis/README.md) · [](https://www.nuget.org/packages/SquidStd.Caching.Redis/) |
+| `SquidStd.Scripting.Lua` | Lua scripting engine with attribute-based modules and event bridging. | [readme](src/SquidStd.Scripting.Lua/README.md) · [](https://www.nuget.org/packages/SquidStd.Scripting.Lua/) |
+
+## Architecture
+
+squid-std follows a few consistent principles across every module:
+
+- **KISS** — small, focused types; one class / record / enum per file.
+- **Abstractions first** — each capability is split into an `*.Abstractions` package (interfaces,
+ DTOs, shared facade) plus one or more provider packages. Consumers depend on the abstraction.
+- **In-memory + external provider** — every infrastructure module ships an in-memory provider
+ (great for tests and local dev) and a production backend behind the same interface
+ (messaging: in-memory / RabbitMQ; caching: in-memory / Redis).
+- **DI-driven** — services are registered with DryIoc through `AddXxx(...)` extensions and resolved
+ through `SquidStdBootstrap`, which manages the `ISquidStdService` lifecycle.
+- **Convention over restructure** — interfaces under `Interfaces`, DTOs under `Data`, enums under
+ `Types`, internals under `Internal`. See [`CODE_CONVENTION.md`](CODE_CONVENTION.md).
+
+## Documentation
+
+Full API documentation is published with DocFX to GitHub Pages:
+**[tgiachi.github.io/SquidStd](https://tgiachi.github.io/SquidStd/)**. Each package also ships its
+own README (linked in the table above).
## Build
@@ -37,6 +117,23 @@ dotnet build SquidStd.slnx
dotnet test SquidStd.slnx
```
+Integration tests (RabbitMQ, Redis) need Docker running; they use Testcontainers to start
+disposable containers automatically.
+
+## Contributing
+
+- Work happens on `develop`; `main` holds released code.
+- Use [Conventional Commits](https://www.conventionalcommits.org/) for messages
+ (`feat:`, `fix:`, `refactor:`, `docs:`, `build:`, `test:`).
+- Follow the project conventions in [`CODE_CONVENTION.md`](CODE_CONVENTION.md).
+- Add tests for new behaviour and keep the suite green before opening a PR.
+
+## Versioning & Releases
+
+Releases are automated with [semantic-release](https://semantic-release.gitbook.io/): version
+numbers and the changelog are derived from the conventional-commit history, and the NuGet packages
+are published on release. Package versions follow [Semantic Versioning](https://semver.org/).
+
## License
MIT - see [LICENSE](LICENSE).
diff --git a/SquidStd.slnx b/SquidStd.slnx
index 81f362b6..12acb065 100644
--- a/SquidStd.slnx
+++ b/SquidStd.slnx
@@ -1,8 +1,22 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 00000000..b7772619
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,4 @@
+_site/
+api/*.yml
+api/.manifest
+obj/
diff --git a/docs/api/index.md b/docs/api/index.md
new file mode 100644
index 00000000..f94ad68c
--- /dev/null
+++ b/docs/api/index.md
@@ -0,0 +1,5 @@
+# API Reference
+
+Auto-generated reference for all published SquidStd packages, grouped by namespace. Use the sidebar to
+browse types and members. Each package's purpose and a usage example are in its
+[article](../articles/getting-started.md).
diff --git a/docs/articles/abstractions.md b/docs/articles/abstractions.md
new file mode 100644
index 00000000..bf188aec
--- /dev/null
+++ b/docs/articles/abstractions.md
@@ -0,0 +1 @@
+[!include[](../../src/SquidStd.Abstractions/README.md)]
diff --git a/docs/articles/aspnetcore.md b/docs/articles/aspnetcore.md
new file mode 100644
index 00000000..63458816
--- /dev/null
+++ b/docs/articles/aspnetcore.md
@@ -0,0 +1 @@
+[!include[](../../src/SquidStd.AspNetCore/README.md)]
diff --git a/docs/articles/caching-abstractions.md b/docs/articles/caching-abstractions.md
new file mode 100644
index 00000000..722af412
--- /dev/null
+++ b/docs/articles/caching-abstractions.md
@@ -0,0 +1 @@
+[!include[](../../src/SquidStd.Caching.Abstractions/README.md)]
diff --git a/docs/articles/caching-redis.md b/docs/articles/caching-redis.md
new file mode 100644
index 00000000..f013c25c
--- /dev/null
+++ b/docs/articles/caching-redis.md
@@ -0,0 +1 @@
+[!include[](../../src/SquidStd.Caching.Redis/README.md)]
diff --git a/docs/articles/caching.md b/docs/articles/caching.md
new file mode 100644
index 00000000..f98cf138
--- /dev/null
+++ b/docs/articles/caching.md
@@ -0,0 +1 @@
+[!include[](../../src/SquidStd.Caching/README.md)]
diff --git a/docs/articles/core.md b/docs/articles/core.md
new file mode 100644
index 00000000..1c84ca7f
--- /dev/null
+++ b/docs/articles/core.md
@@ -0,0 +1 @@
+[!include[](../../src/SquidStd.Core/README.md)]
diff --git a/docs/articles/database-abstractions.md b/docs/articles/database-abstractions.md
new file mode 100644
index 00000000..2bc73ad3
--- /dev/null
+++ b/docs/articles/database-abstractions.md
@@ -0,0 +1 @@
+[!include[](../../src/SquidStd.Database.Abstractions/README.md)]
diff --git a/docs/articles/database.md b/docs/articles/database.md
new file mode 100644
index 00000000..b739dab6
--- /dev/null
+++ b/docs/articles/database.md
@@ -0,0 +1 @@
+[!include[](../../src/SquidStd.Database/README.md)]
diff --git a/docs/articles/getting-started.md b/docs/articles/getting-started.md
new file mode 100644
index 00000000..88533d39
--- /dev/null
+++ b/docs/articles/getting-started.md
@@ -0,0 +1,20 @@
+# Getting Started
+
+Install the package(s) you need and bootstrap the core services.
+
+```bash
+dotnet add package SquidStd.Services.Core
+```
+
+```csharp
+using DryIoc;
+using SquidStd.Services.Core.Extensions;
+
+var container = new Container();
+
+// config manager + event bus + jobs + timer wheel + dispatcher + metrics + storage + secrets
+container.RegisterCoreServices("squidstd", Directory.GetCurrentDirectory());
+```
+
+From here, add focused packages as needed — see the per-package guides in the sidebar and the
+[API reference](../api/index.md).
diff --git a/docs/articles/messaging-abstractions.md b/docs/articles/messaging-abstractions.md
new file mode 100644
index 00000000..7868e11e
--- /dev/null
+++ b/docs/articles/messaging-abstractions.md
@@ -0,0 +1 @@
+[!include[](../../src/SquidStd.Messaging.Abstractions/README.md)]
diff --git a/docs/articles/messaging-rabbitmq.md b/docs/articles/messaging-rabbitmq.md
new file mode 100644
index 00000000..1f0e3f35
--- /dev/null
+++ b/docs/articles/messaging-rabbitmq.md
@@ -0,0 +1 @@
+[!include[](../../src/SquidStd.Messaging.RabbitMq/README.md)]
diff --git a/docs/articles/messaging.md b/docs/articles/messaging.md
new file mode 100644
index 00000000..070fedeb
--- /dev/null
+++ b/docs/articles/messaging.md
@@ -0,0 +1 @@
+[!include[](../../src/SquidStd.Messaging/README.md)]
diff --git a/docs/articles/network.md b/docs/articles/network.md
new file mode 100644
index 00000000..9d4f9475
--- /dev/null
+++ b/docs/articles/network.md
@@ -0,0 +1 @@
+[!include[](../../src/SquidStd.Network/README.md)]
diff --git a/docs/articles/plugin-abstractions.md b/docs/articles/plugin-abstractions.md
new file mode 100644
index 00000000..63250ddb
--- /dev/null
+++ b/docs/articles/plugin-abstractions.md
@@ -0,0 +1 @@
+[!include[](../../src/SquidStd.Plugin.Abstractions/README.md)]
diff --git a/docs/articles/scripting-lua.md b/docs/articles/scripting-lua.md
new file mode 100644
index 00000000..38a403f6
--- /dev/null
+++ b/docs/articles/scripting-lua.md
@@ -0,0 +1 @@
+[!include[](../../src/SquidStd.Scripting.Lua/README.md)]
diff --git a/docs/articles/services-core.md b/docs/articles/services-core.md
new file mode 100644
index 00000000..0079679d
--- /dev/null
+++ b/docs/articles/services-core.md
@@ -0,0 +1 @@
+[!include[](../../src/SquidStd.Services.Core/README.md)]
diff --git a/docs/articles/toc.yml b/docs/articles/toc.yml
new file mode 100644
index 00000000..730dd3da
--- /dev/null
+++ b/docs/articles/toc.yml
@@ -0,0 +1,32 @@
+- name: Getting Started
+ href: getting-started.md
+- name: SquidStd.Core
+ href: core.md
+- name: SquidStd.Abstractions
+ href: abstractions.md
+- name: SquidStd.Services.Core
+ href: services-core.md
+- name: SquidStd.AspNetCore
+ href: aspnetcore.md
+- name: SquidStd.Network
+ href: network.md
+- name: SquidStd.Plugin.Abstractions
+ href: plugin-abstractions.md
+- name: SquidStd.Database.Abstractions
+ href: database-abstractions.md
+- name: SquidStd.Database
+ href: database.md
+- name: SquidStd.Messaging.Abstractions
+ href: messaging-abstractions.md
+- name: SquidStd.Messaging
+ href: messaging.md
+- name: SquidStd.Messaging.RabbitMq
+ href: messaging-rabbitmq.md
+- name: SquidStd.Caching.Abstractions
+ href: caching-abstractions.md
+- name: SquidStd.Caching
+ href: caching.md
+- name: SquidStd.Caching.Redis
+ href: caching-redis.md
+- name: SquidStd.Scripting.Lua
+ href: scripting-lua.md
diff --git a/docs/docfx.json b/docs/docfx.json
new file mode 100644
index 00000000..399777c9
--- /dev/null
+++ b/docs/docfx.json
@@ -0,0 +1,34 @@
+{
+ "metadata": [
+ {
+ "src": [
+ {
+ "src": "../",
+ "files": [ "src/**/*.csproj" ],
+ "exclude": [ "**/bin/**", "**/obj/**" ]
+ }
+ ],
+ "dest": "api",
+ "properties": { "TargetFramework": "net10.0" }
+ }
+ ],
+ "build": {
+ "content": [
+ { "files": [ "api/**.{yml,md}" ] },
+ { "files": [ "articles/**.{md,yml}", "*.md", "toc.yml" ] }
+ ],
+ "resource": [
+ { "files": [ "images/**" ] }
+ ],
+ "output": "_site",
+ "template": [ "default", "modern", "templates/squidstd" ],
+ "globalMetadata": {
+ "_appName": "SquidStd",
+ "_appTitle": "SquidStd",
+ "_appLogoPath": "images/logo.png",
+ "_appFaviconPath": "images/favicon.png",
+ "_enableSearch": true,
+ "_gitContribute": { "repo": "https://github.com/tgiachi/SquidStd", "branch": "main" }
+ }
+ }
+}
diff --git a/docs/images/favicon.png b/docs/images/favicon.png
new file mode 100644
index 00000000..948c92e7
Binary files /dev/null and b/docs/images/favicon.png differ
diff --git a/docs/images/logo.png b/docs/images/logo.png
new file mode 100644
index 00000000..1b45462c
Binary files /dev/null and b/docs/images/logo.png differ
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 00000000..5e765d5f
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,16 @@
+---
+_layout: landing
+---
+
+
+
+
+
+# SquidStd
+
+A modular .NET toolkit: foundational service contracts and utilities, a DryIoc-based service stack,
+networking, plugins, data access, messaging, and Lua scripting — published as focused NuGet packages.
+
+- **[Getting started](articles/getting-started.md)** — install and bootstrap the core services.
+- **[API reference](api/index.md)** — the full type/member documentation.
+- **Packages** — see the per-package guides under [Articles](articles/getting-started.md).
diff --git a/docs/templates/squidstd/public/main.css b/docs/templates/squidstd/public/main.css
new file mode 100644
index 00000000..f98ddc0c
--- /dev/null
+++ b/docs/templates/squidstd/public/main.css
@@ -0,0 +1,54 @@
+/* SquidStd theme — palette derived from the logo. */
+:root {
+ --sqd-brand: #1390A3;
+ --sqd-brand-dark: #0F404C;
+ --sqd-brand-deep: #0D7786;
+ --sqd-accent: #4BB4BD;
+ --sqd-warm: #E3BEBB;
+ --sqd-ink: #0B1418;
+
+ --bs-primary: var(--sqd-brand);
+ --bs-primary-rgb: 19, 144, 163;
+ --bs-link-color: var(--sqd-brand-deep);
+ --bs-link-hover-color: var(--sqd-brand);
+}
+
+[data-bs-theme="dark"] {
+ --bs-primary: var(--sqd-accent);
+ --bs-primary-rgb: 75, 180, 189;
+ --bs-link-color: var(--sqd-accent);
+ --bs-link-hover-color: #7fd0d7;
+ --bs-body-bg: var(--sqd-ink);
+}
+
+/* Navbar branded in brand-dark with light contrast. */
+.navbar {
+ background-color: var(--sqd-brand-dark) !important;
+ border-bottom: 2px solid var(--sqd-brand);
+}
+.navbar .navbar-brand,
+.navbar .nav-link,
+.navbar .navbar-nav .nav-link.active {
+ color: #eaf6f8 !important;
+}
+.navbar .nav-link:hover { color: var(--sqd-accent) !important; }
+
+/* Buttons / primary accents. */
+.btn-primary {
+ --bs-btn-bg: var(--sqd-brand);
+ --bs-btn-border-color: var(--sqd-brand);
+ --bs-btn-hover-bg: var(--sqd-brand-deep);
+ --bs-btn-hover-border-color: var(--sqd-brand-deep);
+ --bs-btn-active-bg: var(--sqd-brand-deep);
+}
+
+/* Active TOC item + focus ring. */
+.toc .nav-link.active { color: var(--sqd-brand) !important; }
+:focus-visible { outline-color: var(--sqd-accent); }
+
+/* Inline code accent + table header tint. */
+:not(pre) > code { color: var(--sqd-brand-deep); }
+table > thead { background-color: rgba(75, 180, 189, 0.12); }
+
+/* Affix (right-hand "In this article") active marker. */
+.affix ul li.active > a { color: var(--sqd-brand); border-left-color: var(--sqd-brand); }
diff --git a/docs/toc.yml b/docs/toc.yml
new file mode 100644
index 00000000..019419e8
--- /dev/null
+++ b/docs/toc.yml
@@ -0,0 +1,6 @@
+- name: Home
+ href: index.md
+- name: Articles
+ href: articles/
+- name: API
+ href: api/
diff --git a/src/SquidStd.Abstractions/Extensions/Config/RegisterConfigSectionExtension.cs b/src/SquidStd.Abstractions/Extensions/Config/RegisterConfigSectionExtension.cs
index 4479c40d..98326a6f 100644
--- a/src/SquidStd.Abstractions/Extensions/Config/RegisterConfigSectionExtension.cs
+++ b/src/SquidStd.Abstractions/Extensions/Config/RegisterConfigSectionExtension.cs
@@ -42,10 +42,8 @@ public static IContainer RegisterConfigSection(
}
}
- var factory = createDefault ?? (() => new TConfig());
- container.AddToRegisterTypedList(
- new ConfigRegistrationData(sectionName, configType, () => factory(), priority)
- );
+ var factory = createDefault ?? (() => new());
+ container.AddToRegisterTypedList(new ConfigRegistrationData(sectionName, configType, () => factory(), priority));
return container;
}
diff --git a/src/SquidStd.Abstractions/Extensions/Services/RegisterStdServiceExtension.cs b/src/SquidStd.Abstractions/Extensions/Services/RegisterStdServiceExtension.cs
index e2ffdc7b..10cb1f99 100644
--- a/src/SquidStd.Abstractions/Extensions/Services/RegisterStdServiceExtension.cs
+++ b/src/SquidStd.Abstractions/Extensions/Services/RegisterStdServiceExtension.cs
@@ -6,8 +6,7 @@ namespace SquidStd.Abstractions.Extensions.Services;
public static class RegisterStdServiceExtension
{
-
- public static IContainer RegisterStdService(this IContainer container, int priority = 0)
+ public static IContainer RegisterStdService(this IContainer container, int priority = 0)
where TService : class
where TImplementation : class, TService
{
@@ -17,5 +16,4 @@ public static IContainer RegisterStdService(this ICont
return container;
}
-
}
diff --git a/src/SquidStd.Abstractions/Interfaces/Services/ISquidStdService.cs b/src/SquidStd.Abstractions/Interfaces/Services/ISquidStdService.cs
index 3acc621c..4e9b5066 100644
--- a/src/SquidStd.Abstractions/Interfaces/Services/ISquidStdService.cs
+++ b/src/SquidStd.Abstractions/Interfaces/Services/ISquidStdService.cs
@@ -14,5 +14,4 @@ public interface ISquidStdService
/// Token used to cancel the asynchronous operation.
/// A task that represents the asynchronous stop operation.
ValueTask StopAsync(CancellationToken cancellationToken = default);
-
}
diff --git a/src/SquidStd.Abstractions/README.md b/src/SquidStd.Abstractions/README.md
new file mode 100644
index 00000000..000247c5
--- /dev/null
+++ b/src/SquidStd.Abstractions/README.md
@@ -0,0 +1,56 @@
+
+
+
+
+
SquidStd.Abstractions
+
+
+
+
+
+
+
+DryIoc-based dependency-injection plumbing for SquidStd. It defines the `ISquidStdService` lifecycle
+contract and the container extensions used to register services and configuration sections in a uniform,
+discoverable way (tracked through ordered registration lists).
+
+## Install
+
+```bash
+dotnet add package SquidStd.Abstractions
+```
+
+## Features
+
+- `ISquidStdService` — a `StartAsync`/`StopAsync` lifecycle contract for managed services.
+- `RegisterStdService()` — register a singleton service and record it in the
+ ordered service list (with optional priority).
+- `RegisterConfigSection(sectionName)` — register a YAML config section for the config manager.
+- `AddToRegisterTypedList(...)` — maintain ordered registration lists in the container.
+
+## Usage
+
+```csharp
+using DryIoc;
+using SquidStd.Abstractions.Extensions.Config;
+using SquidStd.Abstractions.Extensions.Services;
+
+var container = new Container();
+
+container.RegisterStdService();
+container.RegisterConfigSection("my");
+```
+
+## Key types
+
+| Type | Purpose |
+|------|---------|
+| `ISquidStdService` | Async start/stop lifecycle for managed services. |
+| `RegisterStdServiceExtension` | `RegisterStdService<,>` container extension. |
+| `RegisterConfigSectionExtension` | `RegisterConfigSection<>` container extension. |
+| `ServiceRegistrationData` | Ordered service registration record. |
+| `ConfigRegistrationData` | Config section registration record. |
+
+## License
+
+MIT — part of [SquidStd](https://github.com/tgiachi/SquidStd).
diff --git a/src/SquidStd.Abstractions/SquidStd.Abstractions.csproj b/src/SquidStd.Abstractions/SquidStd.Abstractions.csproj
index 0b7093d9..d214510d 100644
--- a/src/SquidStd.Abstractions/SquidStd.Abstractions.csproj
+++ b/src/SquidStd.Abstractions/SquidStd.Abstractions.csproj
@@ -1,13 +1,14 @@
+ truenet10.0enableenable
-
+
diff --git a/src/SquidStd.AspNetCore/Extensions/SquidStdAspNetCoreBuilderExtensions.cs b/src/SquidStd.AspNetCore/Extensions/SquidStdAspNetCoreBuilderExtensions.cs
new file mode 100644
index 00000000..8d995a7c
--- /dev/null
+++ b/src/SquidStd.AspNetCore/Extensions/SquidStdAspNetCoreBuilderExtensions.cs
@@ -0,0 +1,71 @@
+using DryIoc;
+using DryIoc.Microsoft.DependencyInjection;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using SquidStd.AspNetCore.Services;
+using SquidStd.Core.Data.Bootstrap;
+using SquidStd.Services.Core.Services.Bootstrap;
+
+namespace SquidStd.AspNetCore.Extensions;
+
+///
+/// Extension methods for connecting SquidStd to ASP.NET Core Minimal API applications.
+///
+public static class SquidStdAspNetCoreBuilderExtensions
+{
+ /// ASP.NET Core application builder.
+ extension(WebApplicationBuilder builder)
+ {
+ ///
+ /// Registers SquidStd using DryIoc as the ASP.NET Core service provider.
+ ///
+ /// Optional SquidStd bootstrap options callback.
+ /// The same builder for chaining.
+ public WebApplicationBuilder UseSquidStd(Action? configureOptions = null)
+ => builder.UseSquidStd(configureOptions, null);
+
+ ///
+ /// Registers SquidStd using DryIoc as the ASP.NET Core service provider.
+ ///
+ /// Optional SquidStd bootstrap options callback.
+ /// Optional DryIoc registration callback.
+ /// The same builder for chaining.
+ public WebApplicationBuilder UseSquidStd(
+ Action? configureOptions,
+ Func? configureContainer
+ )
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ var options = new SquidStdOptions
+ {
+ RootDirectory = builder.Environment.ContentRootPath
+ };
+ configureOptions?.Invoke(options);
+ ValidateOptions(options);
+
+ var container = new Container();
+ builder.Host.UseServiceProviderFactory(new DryIocServiceProviderFactory(container));
+ SquidStdBootstrap.Create(options, container);
+
+ var configuredContainer = configureContainer?.Invoke(container) ?? container;
+
+ if (!ReferenceEquals(configuredContainer, container))
+ {
+ throw new InvalidOperationException("ConfigureSquidStdContainer must return the DryIoc container instance.");
+ }
+
+ builder.Services.AddHostedService();
+
+ return builder;
+ }
+ }
+
+ private static void ValidateOptions(SquidStdOptions options)
+ {
+ ArgumentNullException.ThrowIfNull(options);
+ ArgumentException.ThrowIfNullOrWhiteSpace(options.ConfigName);
+ ArgumentException.ThrowIfNullOrWhiteSpace(options.RootDirectory);
+ }
+}
diff --git a/src/SquidStd.AspNetCore/README.md b/src/SquidStd.AspNetCore/README.md
new file mode 100644
index 00000000..ff627d45
--- /dev/null
+++ b/src/SquidStd.AspNetCore/README.md
@@ -0,0 +1,55 @@
+
+
+
+
+
SquidStd.AspNetCore
+
+
+
+
+
+
+
+ASP.NET Core integration for SquidStd. A single `builder.UseSquidStd(...)` call wires the SquidStd
+DryIoc container into the web host and registers a hosted service that starts and stops every
+`ISquidStdService` alongside the application lifecycle.
+
+## Install
+
+```bash
+dotnet add package SquidStd.AspNetCore
+```
+
+## Features
+
+- `WebApplicationBuilder.UseSquidStd(...)` — plug the SquidStd container and services into a web app.
+- Configures DryIoc as the host's service-provider factory.
+- Registers `SquidStdHostedService` to start/stop SquidStd services with the host.
+- Optional `SquidStdOptions` configuration callback.
+
+## Usage
+
+```csharp
+using SquidStd.AspNetCore.Extensions;
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.UseSquidStd(options =>
+{
+ // configure SquidStd options here
+});
+
+var app = builder.Build();
+app.Run();
+```
+
+## Key types
+
+| Type | Purpose |
+|------|---------|
+| `SquidStdAspNetCoreBuilderExtensions` | `UseSquidStd(...)` builder extension. |
+| `SquidStdHostedService` | Hosted service bridging SquidStd service lifecycle to the host. |
+
+## License
+
+MIT — part of [SquidStd](https://github.com/tgiachi/SquidStd).
diff --git a/src/SquidStd.AspNetCore/Services/SquidStdHostedService.cs b/src/SquidStd.AspNetCore/Services/SquidStdHostedService.cs
new file mode 100644
index 00000000..158e7d04
--- /dev/null
+++ b/src/SquidStd.AspNetCore/Services/SquidStdHostedService.cs
@@ -0,0 +1,33 @@
+using Microsoft.Extensions.Hosting;
+using SquidStd.Core.Interfaces.Bootstrap;
+
+namespace SquidStd.AspNetCore.Services;
+
+///
+/// Bridges the ASP.NET Core host lifecycle to the SquidStd bootstrap lifecycle.
+///
+internal sealed class SquidStdHostedService : IHostedService
+{
+ private readonly ISquidStdBootstrap _bootstrap;
+
+ ///
+ /// Initializes the hosted service.
+ ///
+ /// SquidStd bootstrap instance started with the ASP.NET host.
+ public SquidStdHostedService(ISquidStdBootstrap bootstrap)
+ {
+ _bootstrap = bootstrap;
+ }
+
+ ///
+ public async Task StartAsync(CancellationToken cancellationToken)
+ {
+ await _bootstrap.StartAsync(cancellationToken);
+ }
+
+ ///
+ public async Task StopAsync(CancellationToken cancellationToken)
+ {
+ await _bootstrap.StopAsync(cancellationToken);
+ }
+}
diff --git a/src/SquidStd.AspNetCore/SquidStd.AspNetCore.csproj b/src/SquidStd.AspNetCore/SquidStd.AspNetCore.csproj
new file mode 100644
index 00000000..994d0dbb
--- /dev/null
+++ b/src/SquidStd.AspNetCore/SquidStd.AspNetCore.csproj
@@ -0,0 +1,26 @@
+
+
+
+ true
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/SquidStd.Caching.Abstractions/Data/Config/CacheConnectionString.cs b/src/SquidStd.Caching.Abstractions/Data/Config/CacheConnectionString.cs
new file mode 100644
index 00000000..33ab8f4f
--- /dev/null
+++ b/src/SquidStd.Caching.Abstractions/Data/Config/CacheConnectionString.cs
@@ -0,0 +1,87 @@
+using System.Collections.Frozen;
+using System.Web;
+
+namespace SquidStd.Caching.Abstractions.Data.Config;
+
+///
+/// Parsed cache connection string of the form scheme://[user:pass@]host[:port][?params].
+///
+public sealed class CacheConnectionString
+{
+ private CacheConnectionString(
+ string scheme,
+ string host,
+ int? port,
+ string? userName,
+ string? password,
+ IReadOnlyDictionary parameters
+ )
+ {
+ Scheme = scheme;
+ Host = host;
+ Port = port;
+ UserName = userName;
+ Password = password;
+ Parameters = parameters;
+ }
+
+ /// URI scheme, e.g. "memory" or "redis".
+ public string Scheme { get; }
+
+ /// Host component.
+ public string Host { get; }
+
+ /// Port, when specified.
+ public int? Port { get; }
+
+ /// User name from the user-info component, when present.
+ public string? UserName { get; }
+
+ /// Password from the user-info component, when present.
+ public string? Password { get; }
+
+ /// Query-string parameters.
+ public IReadOnlyDictionary Parameters { get; }
+
+ /// Parses a cache connection string.
+ public static CacheConnectionString Parse(string connectionString)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
+
+ var uri = new Uri(connectionString, UriKind.Absolute);
+
+ string? userName = null;
+ string? password = null;
+
+ if (!string.IsNullOrEmpty(uri.UserInfo))
+ {
+ var parts = uri.UserInfo.Split(':', 2);
+ userName = Uri.UnescapeDataString(parts[0]);
+ password = parts.Length > 1 ? Uri.UnescapeDataString(parts[1]) : null;
+ }
+
+ var query = HttpUtility.ParseQueryString(uri.Query);
+ var parameters = query.AllKeys
+ .Where(static key => key is not null)
+ .ToFrozenDictionary(key => key!, key => query[key] ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+
+ return new(
+ uri.Scheme,
+ uri.Host,
+ uri.Port > 0 ? uri.Port : null,
+ userName,
+ password,
+ parameters
+ );
+ }
+
+ /// Builds from the query parameters.
+ public CacheOptions ToCacheOptions()
+ => new()
+ {
+ DefaultTtl = Parameters.TryGetValue("defaultTtlSeconds", out var ttl) && int.TryParse(ttl, out var seconds)
+ ? TimeSpan.FromSeconds(seconds)
+ : null,
+ KeyPrefix = Parameters.TryGetValue("keyPrefix", out var prefix) ? prefix : string.Empty
+ };
+}
diff --git a/src/SquidStd.Caching.Abstractions/Data/Config/CacheOptions.cs b/src/SquidStd.Caching.Abstractions/Data/Config/CacheOptions.cs
new file mode 100644
index 00000000..68fda982
--- /dev/null
+++ b/src/SquidStd.Caching.Abstractions/Data/Config/CacheOptions.cs
@@ -0,0 +1,13 @@
+namespace SquidStd.Caching.Abstractions.Data.Config;
+
+///
+/// Options shared by all cache providers.
+///
+public sealed class CacheOptions
+{
+ /// Default time-to-live applied when a set call passes no TTL. null means no expiry.
+ public TimeSpan? DefaultTtl { get; set; }
+
+ /// Prefix prepended to every key. Default empty.
+ public string KeyPrefix { get; set; } = string.Empty;
+}
diff --git a/src/SquidStd.Caching.Abstractions/Interfaces/ICacheMetrics.cs b/src/SquidStd.Caching.Abstractions/Interfaces/ICacheMetrics.cs
new file mode 100644
index 00000000..cf7be0af
--- /dev/null
+++ b/src/SquidStd.Caching.Abstractions/Interfaces/ICacheMetrics.cs
@@ -0,0 +1,19 @@
+namespace SquidStd.Caching.Abstractions.Interfaces;
+
+///
+/// Sink for cache instrumentation events.
+///
+public interface ICacheMetrics
+{
+ /// Records a cache hit.
+ void OnHit(string key);
+
+ /// Records a cache miss.
+ void OnMiss(string key);
+
+ /// Records a value being stored.
+ void OnSet(string key);
+
+ /// Records a key being removed.
+ void OnRemove(string key);
+}
diff --git a/src/SquidStd.Caching.Abstractions/Interfaces/ICacheProvider.cs b/src/SquidStd.Caching.Abstractions/Interfaces/ICacheProvider.cs
new file mode 100644
index 00000000..7b27ddab
--- /dev/null
+++ b/src/SquidStd.Caching.Abstractions/Interfaces/ICacheProvider.cs
@@ -0,0 +1,21 @@
+using SquidStd.Abstractions.Interfaces.Services;
+
+namespace SquidStd.Caching.Abstractions.Interfaces;
+
+///
+/// Byte-level cache backend. Implemented by each provider (in-memory, Redis, ...).
+///
+public interface ICacheProvider : ISquidStdService
+{
+ /// Gets the raw value for a key, or null when absent.
+ Task?> GetAsync(string key, CancellationToken cancellationToken = default);
+
+ /// Stores a raw value with an optional time-to-live.
+ Task SetAsync(string key, ReadOnlyMemory value, TimeSpan? ttl, CancellationToken cancellationToken = default);
+
+ /// Removes a key; returns whether it existed.
+ Task RemoveAsync(string key, CancellationToken cancellationToken = default);
+
+ /// Returns whether a key exists.
+ Task ExistsAsync(string key, CancellationToken cancellationToken = default);
+}
diff --git a/src/SquidStd.Caching.Abstractions/Interfaces/ICacheService.cs b/src/SquidStd.Caching.Abstractions/Interfaces/ICacheService.cs
new file mode 100644
index 00000000..8d8dec96
--- /dev/null
+++ b/src/SquidStd.Caching.Abstractions/Interfaces/ICacheService.cs
@@ -0,0 +1,27 @@
+namespace SquidStd.Caching.Abstractions.Interfaces;
+
+///
+/// Typed cache-aside facade over an .
+///
+public interface ICacheService
+{
+ /// Gets a typed value, or default when absent.
+ Task GetAsync(string key, CancellationToken cancellationToken = default);
+
+ /// Stores a typed value with an optional time-to-live (falls back to the default TTL).
+ Task SetAsync(string key, T value, TimeSpan? ttl = null, CancellationToken cancellationToken = default);
+
+ /// Removes a key; returns whether it existed.
+ Task RemoveAsync(string key, CancellationToken cancellationToken = default);
+
+ /// Returns whether a key exists.
+ Task ExistsAsync(string key, CancellationToken cancellationToken = default);
+
+ /// Returns the cached value, or computes, stores and returns it on a miss.
+ Task GetOrSetAsync(
+ string key,
+ Func> factory,
+ TimeSpan? ttl = null,
+ CancellationToken cancellationToken = default
+ );
+}
diff --git a/src/SquidStd.Caching.Abstractions/README.md b/src/SquidStd.Caching.Abstractions/README.md
new file mode 100644
index 00000000..5adae69a
--- /dev/null
+++ b/src/SquidStd.Caching.Abstractions/README.md
@@ -0,0 +1,55 @@
+
+
+
+
+
SquidStd.Caching.Abstractions
+
+
+
+
+
+
+
+Backend-agnostic caching contracts for SquidStd. It defines the typed cache-aside facade
+(`ICacheService`), the low-level byte provider/metrics contracts, and the shared `CacheService`
+facade that applies key-prefixing, default TTL and cache-aside once over any provider. Pick a
+backend implementation (in-memory or Redis) from a companion package.
+
+## Install
+
+```bash
+dotnet add package SquidStd.Caching.Abstractions
+```
+
+## Features
+
+- `ICacheService` — typed `GetAsync` / `SetAsync` / `RemoveAsync` / `ExistsAsync` / `GetOrSetAsync` facade.
+- `ICacheProvider` — the raw byte-level backend contract implemented per provider.
+- `CacheService` — shared facade that serializes values, applies the key prefix and default TTL, and implements cache-aside.
+- `ICacheMetrics` (+ `CacheMetricsProvider`, `NoOpCacheMetrics`) — hit/miss/set/remove metrics.
+- `CacheOptions` and `CacheConnectionString` — configuration and connection parsing.
+
+## Usage
+
+```csharp
+using SquidStd.Caching.Abstractions.Interfaces;
+
+// Resolve ICacheService from a backend package (in-memory or Redis).
+public Task GetOrComputeAsync(ICacheService cache)
+ => cache.GetOrSetAsync("answer", _ => Task.FromResult(42), TimeSpan.FromMinutes(5));
+```
+
+## Key types
+
+| Type | Purpose |
+|------|---------|
+| `ICacheService` | Typed cache-aside facade. |
+| `ICacheProvider` | Byte-level backend contract (per provider). |
+| `CacheService` | Shared facade: serialization, key prefix, default TTL, cache-aside. |
+| `ICacheMetrics` | Hit/miss/set/remove metrics sink. |
+| `CacheOptions` | Default TTL and key prefix. |
+| `CacheConnectionString` | `scheme://host[?params]` parsing into `CacheOptions`. |
+
+## License
+
+MIT — part of [SquidStd](https://github.com/tgiachi/SquidStd).
diff --git a/src/SquidStd.Caching.Abstractions/Services/CacheMetricsProvider.cs b/src/SquidStd.Caching.Abstractions/Services/CacheMetricsProvider.cs
new file mode 100644
index 00000000..36722cb3
--- /dev/null
+++ b/src/SquidStd.Caching.Abstractions/Services/CacheMetricsProvider.cs
@@ -0,0 +1,56 @@
+using SquidStd.Caching.Abstractions.Interfaces;
+using SquidStd.Core.Data.Metrics;
+using SquidStd.Core.Interfaces.Metrics;
+using SquidStd.Core.Types.Metrics;
+
+namespace SquidStd.Caching.Abstractions.Services;
+
+///
+/// Accumulates aggregate cache metrics and exposes them to the metrics collection system.
+///
+public sealed class CacheMetricsProvider : ICacheMetrics, IMetricProvider
+{
+ private long _hits;
+ private long _misses;
+ private long _sets;
+ private long _removes;
+
+ ///
+ public string ProviderName => "cache";
+
+ ///
+ public void OnHit(string key)
+ => Interlocked.Increment(ref _hits);
+
+ ///
+ public void OnMiss(string key)
+ => Interlocked.Increment(ref _misses);
+
+ ///
+ public void OnSet(string key)
+ => Interlocked.Increment(ref _sets);
+
+ ///
+ public void OnRemove(string key)
+ => Interlocked.Increment(ref _removes);
+
+ ///
+ public ValueTask> CollectAsync(CancellationToken cancellationToken = default)
+ {
+ var hits = Interlocked.Read(ref _hits);
+ var misses = Interlocked.Read(ref _misses);
+ var total = hits + misses;
+ var hitRatio = total == 0 ? 0d : (double)hits / total;
+
+ var samples = new List
+ {
+ new("hits", hits, Type: MetricType.Counter),
+ new("misses", misses, Type: MetricType.Counter),
+ new("sets", Interlocked.Read(ref _sets), Type: MetricType.Counter),
+ new("removes", Interlocked.Read(ref _removes), Type: MetricType.Counter),
+ new("hit_ratio", hitRatio)
+ };
+
+ return ValueTask.FromResult>(samples);
+ }
+}
diff --git a/src/SquidStd.Caching.Abstractions/Services/CacheService.cs b/src/SquidStd.Caching.Abstractions/Services/CacheService.cs
new file mode 100644
index 00000000..be189fa6
--- /dev/null
+++ b/src/SquidStd.Caching.Abstractions/Services/CacheService.cs
@@ -0,0 +1,108 @@
+using SquidStd.Caching.Abstractions.Data.Config;
+using SquidStd.Caching.Abstractions.Interfaces;
+using SquidStd.Core.Interfaces.Serialization;
+
+namespace SquidStd.Caching.Abstractions.Services;
+
+///
+/// Typed cache-aside facade: serializes values, applies the key prefix and default TTL, and
+/// implements once over any .
+///
+public sealed class CacheService : ICacheService
+{
+ private readonly ICacheProvider _provider;
+ private readonly IDataSerializer _serializer;
+ private readonly IDataDeserializer _deserializer;
+ private readonly ICacheMetrics _metrics;
+ private readonly TimeSpan? _defaultTtl;
+ private readonly string _keyPrefix;
+
+ public CacheService(
+ ICacheProvider provider,
+ IDataSerializer serializer,
+ IDataDeserializer deserializer,
+ CacheOptions options,
+ ICacheMetrics? metrics = null
+ )
+ {
+ ArgumentNullException.ThrowIfNull(options);
+
+ _provider = provider;
+ _serializer = serializer;
+ _deserializer = deserializer;
+ _metrics = metrics ?? NoOpCacheMetrics.Instance;
+ _defaultTtl = options.DefaultTtl;
+ _keyPrefix = options.KeyPrefix;
+ }
+
+ ///
+ public async Task GetAsync(string key, CancellationToken cancellationToken = default)
+ {
+ var bytes = await _provider.GetAsync(Prefixed(key), cancellationToken);
+
+ if (!bytes.HasValue)
+ {
+ _metrics.OnMiss(key);
+
+ return default;
+ }
+
+ _metrics.OnHit(key);
+
+ return _deserializer.Deserialize(bytes.Value);
+ }
+
+ ///
+ public async Task SetAsync(string key, T value, TimeSpan? ttl = null, CancellationToken cancellationToken = default)
+ {
+ var bytes = _serializer.Serialize(value);
+ await _provider.SetAsync(Prefixed(key), bytes, ttl ?? _defaultTtl, cancellationToken);
+ _metrics.OnSet(key);
+ }
+
+ ///
+ public async Task RemoveAsync(string key, CancellationToken cancellationToken = default)
+ {
+ var removed = await _provider.RemoveAsync(Prefixed(key), cancellationToken);
+
+ if (removed)
+ {
+ _metrics.OnRemove(key);
+ }
+
+ return removed;
+ }
+
+ ///
+ public Task ExistsAsync(string key, CancellationToken cancellationToken = default)
+ => _provider.ExistsAsync(Prefixed(key), cancellationToken);
+
+ ///
+ public async Task GetOrSetAsync(
+ string key,
+ Func> factory,
+ TimeSpan? ttl = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ ArgumentNullException.ThrowIfNull(factory);
+
+ var bytes = await _provider.GetAsync(Prefixed(key), cancellationToken);
+
+ if (bytes.HasValue)
+ {
+ _metrics.OnHit(key);
+
+ return _deserializer.Deserialize(bytes.Value);
+ }
+
+ _metrics.OnMiss(key);
+ var value = await factory(cancellationToken);
+ await SetAsync(key, value, ttl, cancellationToken);
+
+ return value;
+ }
+
+ private string Prefixed(string key)
+ => _keyPrefix.Length == 0 ? key : _keyPrefix + key;
+}
diff --git a/src/SquidStd.Caching.Abstractions/Services/NoOpCacheMetrics.cs b/src/SquidStd.Caching.Abstractions/Services/NoOpCacheMetrics.cs
new file mode 100644
index 00000000..c52877a1
--- /dev/null
+++ b/src/SquidStd.Caching.Abstractions/Services/NoOpCacheMetrics.cs
@@ -0,0 +1,28 @@
+using SquidStd.Caching.Abstractions.Interfaces;
+
+namespace SquidStd.Caching.Abstractions.Services;
+
+///
+/// Metrics sink that ignores all events. Used when no metrics are configured.
+///
+public sealed class NoOpCacheMetrics : ICacheMetrics
+{
+ /// Shared instance.
+ public static NoOpCacheMetrics Instance { get; } = new();
+
+ public void OnHit(string key)
+ {
+ }
+
+ public void OnMiss(string key)
+ {
+ }
+
+ public void OnSet(string key)
+ {
+ }
+
+ public void OnRemove(string key)
+ {
+ }
+}
diff --git a/src/SquidStd.Caching.Abstractions/SquidStd.Caching.Abstractions.csproj b/src/SquidStd.Caching.Abstractions/SquidStd.Caching.Abstractions.csproj
new file mode 100644
index 00000000..f73ae1d5
--- /dev/null
+++ b/src/SquidStd.Caching.Abstractions/SquidStd.Caching.Abstractions.csproj
@@ -0,0 +1,15 @@
+
+
+
+ net10.0
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
diff --git a/src/SquidStd.Caching.Redis/Data/Config/RedisCacheOptions.cs b/src/SquidStd.Caching.Redis/Data/Config/RedisCacheOptions.cs
new file mode 100644
index 00000000..30dd72d4
--- /dev/null
+++ b/src/SquidStd.Caching.Redis/Data/Config/RedisCacheOptions.cs
@@ -0,0 +1,10 @@
+namespace SquidStd.Caching.Redis.Data.Config;
+
+///
+/// Connection options for the Redis cache provider.
+///
+public sealed class RedisCacheOptions
+{
+ /// StackExchange.Redis configuration string. Default "localhost:6379".
+ public string Configuration { get; init; } = "localhost:6379";
+}
diff --git a/src/SquidStd.Caching.Redis/Extensions/RedisCacheRegistrationExtensions.cs b/src/SquidStd.Caching.Redis/Extensions/RedisCacheRegistrationExtensions.cs
new file mode 100644
index 00000000..24eda31a
--- /dev/null
+++ b/src/SquidStd.Caching.Redis/Extensions/RedisCacheRegistrationExtensions.cs
@@ -0,0 +1,66 @@
+using DryIoc;
+using SquidStd.Caching.Abstractions.Data.Config;
+using SquidStd.Caching.Abstractions.Interfaces;
+using SquidStd.Caching.Abstractions.Services;
+using SquidStd.Caching.Redis.Data.Config;
+using SquidStd.Caching.Redis.Services;
+using SquidStd.Core.Interfaces.Metrics;
+using SquidStd.Core.Interfaces.Serialization;
+using SquidStd.Core.Json;
+
+namespace SquidStd.Caching.Redis.Extensions;
+
+///
+/// DryIoc registration helpers for the Redis cache provider.
+///
+public static class RedisCacheRegistrationExtensions
+{
+ /// Registers the Redis cache from explicit options.
+ public static IContainer AddRedisCache(
+ this IContainer container,
+ RedisCacheOptions options,
+ CacheOptions? cacheOptions = null
+ )
+ {
+ ArgumentNullException.ThrowIfNull(container);
+ ArgumentNullException.ThrowIfNull(options);
+
+ container.RegisterInstance(cacheOptions ?? new CacheOptions());
+ container.RegisterInstance(options);
+
+ var serializer = new JsonDataSerializer();
+ container.RegisterInstance(serializer, IfAlreadyRegistered.Keep);
+ container.RegisterInstance(serializer, IfAlreadyRegistered.Keep);
+
+ var metrics = new CacheMetricsProvider();
+ container.RegisterInstance(metrics);
+ container.RegisterInstance(metrics);
+
+ container.Register(Reuse.Singleton);
+ container.Register(Reuse.Singleton);
+
+ return container;
+ }
+
+ /// Registers the Redis cache from a connection string (scheme must be "redis").
+ public static IContainer AddRedisCache(this IContainer container, string connectionString)
+ {
+ ArgumentNullException.ThrowIfNull(container);
+
+ var cs = CacheConnectionString.Parse(connectionString);
+
+ if (!string.Equals(cs.Scheme, "redis", StringComparison.OrdinalIgnoreCase))
+ {
+ throw new ArgumentException(
+ $"Expected a 'redis://' connection string but got '{cs.Scheme}://'.",
+ nameof(connectionString)
+ );
+ }
+
+ var host = string.IsNullOrEmpty(cs.Host) ? "localhost" : cs.Host;
+ var port = cs.Port ?? 6379;
+ var options = new RedisCacheOptions { Configuration = $"{host}:{port}" };
+
+ return container.AddRedisCache(options, cs.ToCacheOptions());
+ }
+}
diff --git a/src/SquidStd.Caching.Redis/README.md b/src/SquidStd.Caching.Redis/README.md
new file mode 100644
index 00000000..7e100ece
--- /dev/null
+++ b/src/SquidStd.Caching.Redis/README.md
@@ -0,0 +1,56 @@
+
+
+
+
+
SquidStd.Caching.Redis
+
+
+
+
+
+
+
+Redis backend for SquidStd.Caching. Implements `ICacheProvider` on top of StackExchange.Redis,
+so the same `ICacheService` API reads and writes a real Redis server with native key expiry.
+Registered with a single `AddRedisCache(...)` call.
+
+## Install
+
+```bash
+dotnet add package SquidStd.Caching.Redis
+```
+
+## Features
+
+- One-line registration: `container.AddRedisCache(connectionString)` or with `RedisCacheOptions`.
+- Redis-backed `ICacheProvider` reusing the shared `ICacheService` facade and serializer.
+- Native TTL via Redis key expiry (`SET ... EX`).
+- Connection via a `redis://` connection string or an explicit StackExchange.Redis configuration string.
+- Built-in hit/miss metrics via `CacheMetricsProvider`.
+
+## Usage
+
+```csharp
+using DryIoc;
+using SquidStd.Caching.Abstractions.Interfaces;
+using SquidStd.Caching.Redis.Extensions;
+
+var container = new Container();
+container.AddRedisCache("redis://localhost:6379?defaultTtlSeconds=300&keyPrefix=app:");
+
+var cache = container.Resolve();
+await cache.SetAsync("user:1", new { Name = "squid" }, TimeSpan.FromMinutes(10));
+var user = await cache.GetAsync