diff --git a/src/SquidStd.Aws.Abstractions/README.md b/src/SquidStd.Aws.Abstractions/README.md
index fe3bb0b..0ba4d03 100644
--- a/src/SquidStd.Aws.Abstractions/README.md
+++ b/src/SquidStd.Aws.Abstractions/README.md
@@ -33,6 +33,13 @@ client at that endpoint instead of the regional AWS endpoint.
|------------------|-----------------------------------------------------------------------------------|
| `AwsConfigEntry` | Region, optional credentials and an optional endpoint override (e.g. LocalStack). |
+## Related
+
+- Article: [AWS abstractions](https://tgiachi.github.io/squid-std/articles/aws-abstractions.html)
+- [`SquidStd.Secrets.Aws`](https://tgiachi.github.io/squid-std/articles/secrets-aws.html) - AWS Secrets Manager provider using this config
+- [`SquidStd.Messaging.Sqs`](https://tgiachi.github.io/squid-std/articles/messaging-sqs.html) - SQS messaging provider using this config
+- [`SquidStd.Storage.S3`](https://tgiachi.github.io/squid-std/articles/storage-s3.html) - S3 storage provider using this config
+
## License
MIT - part of [SquidStd](https://github.com/tgiachi/squid-std).
diff --git a/src/SquidStd.Caching/README.md b/src/SquidStd.Caching/README.md
index 228d759..796a7ef 100644
--- a/src/SquidStd.Caching/README.md
+++ b/src/SquidStd.Caching/README.md
@@ -1,8 +1,13 @@
SquidStd.Caching
-In-memory backend for SquidStd.Caching. Provides an `IMemoryCache`-backed `ICacheProvider` with
-absolute TTL and eviction, wired to the shared typed `ICacheService` facade - registered with a
-single `AddInMemoryCache()` call. Ideal for single-process apps, tests, and local dev.
+In-memory backend for SquidStd.Caching. Provides an `IMemoryCache`-backed `ICacheProvider` with absolute
+TTL and eviction, wired to the shared typed `ICacheService` facade - registered with a single
+`AddInMemoryCache()` call.
+
+The contract lives in `SquidStd.Caching.Abstractions`, so code written against `ICacheService` moves
+unchanged to a distributed cache: use this in-memory provider for single-process apps, tests, and local
+dev, then swap in `SquidStd.Caching.Redis` for production. Values are serialized as JSON in both cases,
+so cached shapes behave the same across providers.
## Install
@@ -12,29 +17,87 @@ dotnet add package SquidStd.Caching
## Usage
+Register through the bootstrap - the provider is an `ISquidStdService`, so `StartAsync` / `StopAsync`
+manage its lifecycle for you:
+
```csharp
-using DryIoc;
using SquidStd.Caching.Abstractions.Interfaces;
using SquidStd.Caching.Extensions;
+using SquidStd.Services.Core.Extensions;
+using SquidStd.Services.Core.Services.Bootstrap;
+
+var bootstrap = SquidStdBootstrap.Create(o => o.ConfigName = "myapp");
+bootstrap.ConfigureServices(c => c.RegisterCoreServices().AddInMemoryCache());
+await bootstrap.StartAsync();
+```
+
+### Get / set with TTL
+
+```csharp
+var cache = bootstrap.Resolve();
+
+await cache.SetAsync("greeting", "hello", TimeSpan.FromMinutes(5));
+var value = await cache.GetAsync("greeting"); // "hello", or default on a miss
+
+var exists = await cache.ExistsAsync("greeting"); // true
+var removed = await cache.RemoveAsync("greeting"); // true if the key existed
+```
+
+Omitting the TTL on `SetAsync` falls back to `CacheOptions.DefaultTtl` (no expiry when that is `null`).
-var container = new Container();
-container.AddInMemoryCache("memory://localhost?defaultTtlSeconds=300&keyPrefix=app:");
+### Cache-aside with `GetOrSetAsync`
-var cache = container.Resolve();
-await cache.SetAsync("user:1", new { Name = "squid" });
-var user = await cache.GetAsync("user:1");
+`GetOrSetAsync` returns the cached value, or runs the factory, stores the result, and returns it on a
+miss - the standard cache-aside pattern in one call:
+
+```csharp
+public sealed record UserProfile(string Id, string Name);
+
+var profile = await cache.GetOrSetAsync(
+ $"user:{id}",
+ async ct => await LoadProfileFromDatabaseAsync(id, ct),
+ TimeSpan.FromMinutes(10)
+);
```
+## Configuration
+
+`AddInMemoryCache` takes an optional `CacheOptions`. Options are code-only - this package registers no
+YAML config section.
+
+```csharp
+c.AddInMemoryCache(new CacheOptions
+{
+ DefaultTtl = TimeSpan.FromMinutes(5),
+ KeyPrefix = "app:"
+});
+```
+
+| Property | Default | Purpose |
+|--------------|----------------|------------------------------------------------------------------|
+| `DefaultTtl` | `null` | TTL applied when a set call passes none; `null` means no expiry. |
+| `KeyPrefix` | `string.Empty` | Prefix prepended to every key. |
+
+A connection-string overload is also available:
+`AddInMemoryCache("memory://local?defaultTtlSeconds=300&keyPrefix=app:")`.
+
+`CacheMetricsProvider` is registered as `ICacheMetrics` / `IMetricProvider` and exposes aggregate hit,
+miss, set, and remove counters plus the hit ratio to the metrics collection system.
+
## Key types
-| Type | Purpose |
-|-------------------------------|-----------------------------------------|
-| `CacheRegistrationExtensions` | `AddInMemoryCache(...)` registration. |
-| `InMemoryCacheProvider` | `IMemoryCache`-backed `ICacheProvider`. |
+| Type | Purpose |
+|-------------------------------|------------------------------------------------------------|
+| `CacheRegistrationExtensions` | `AddInMemoryCache(...)` registration. |
+| `InMemoryCacheProvider` | `IMemoryCache`-backed `ICacheProvider`. |
+| `ICacheService` | Typed facade from `SquidStd.Caching.Abstractions`: get/set/TTL, `GetOrSetAsync`, exists/remove. |
+| `CacheOptions` | Default TTL and key prefix configuration. |
## Related
+- Article: [Caching](https://tgiachi.github.io/squid-std/articles/caching.html)
- Tutorial: [Caching](https://tgiachi.github.io/squid-std/tutorials/caching.html)
+- Production backend: [SquidStd.Caching.Redis](https://tgiachi.github.io/squid-std/articles/caching-redis.html)
## License
diff --git a/src/SquidStd.Messaging.RabbitMq/README.md b/src/SquidStd.Messaging.RabbitMq/README.md
index 5261cf8..9dc3b48 100644
--- a/src/SquidStd.Messaging.RabbitMq/README.md
+++ b/src/SquidStd.Messaging.RabbitMq/README.md
@@ -1,9 +1,17 @@
SquidStd.Messaging.RabbitMq
-RabbitMQ transport for SquidStd.Messaging. Implements `IQueueProvider` on top of the RabbitMQ client,
-so the same `IMessageQueue` API publishes to and consumes from a real broker. Registered with a single
+RabbitMQ transport for SquidStd.Messaging. Implements `IQueueProvider` and `ITopicProvider` on top of the
+RabbitMQ client, so the same `IMessageQueue` / `IMessageTopic` API that runs in-memory during tests
+publishes to and consumes from a real broker in production - registered with a single
`AddRabbitMqMessaging(...)` call.
+The provider declares durable **quorum queues** and publishes persistent messages, so queued work survives
+broker restarts. Each queue gets a companion dead-letter queue (`.dlq` by default), wired via
+`x-delivery-limit` so the broker itself moves a message there after `MaxDeliveryAttempts` failed
+deliveries. Connections are created with automatic recovery enabled, and consumers apply the configured
+prefetch via basic QoS. Topics map to fan-out exchanges with an exclusive, auto-delete queue per
+subscriber - topic delivery is live fan-out, not durable.
+
## Install
```bash
@@ -12,29 +20,84 @@ dotnet add package SquidStd.Messaging.RabbitMq
## Usage
+Register through the bootstrap - the providers are `ISquidStdService`s, so `bootstrap.StartAsync()` opens
+the broker connection and `StopAsync` closes it:
+
```csharp
-using DryIoc;
using SquidStd.Messaging.Abstractions.Interfaces;
+using SquidStd.Messaging.RabbitMq.Data.Config;
using SquidStd.Messaging.RabbitMq.Extensions;
+using SquidStd.Services.Core.Extensions;
+using SquidStd.Services.Core.Services.Bootstrap;
+
+var bootstrap = SquidStdBootstrap.Create(o => o.ConfigName = "myapp");
+
+bootstrap.ConfigureServices(c => c.RegisterCoreServices().AddRabbitMqMessaging(new RabbitMqOptions
+{
+ HostName = "rabbit.internal",
+ UserName = "app",
+ Password = "secret"
+}));
+
+await bootstrap.StartAsync();
+```
+
+Publishing and consuming are identical to the in-memory provider - the contracts come from
+`SquidStd.Messaging.Abstractions`:
-var container = new Container();
-container.AddRabbitMqMessaging("rabbitmq://guest:guest@localhost:5672/");
+```csharp
+var queue = bootstrap.Resolve();
+
+using (queue.Subscribe("orders", new OrderListener())) // IQueueMessageListenerAsync
+{
+ await queue.PublishAsync("orders", new OrderPlaced("order-1"));
+}
-var queue = container.Resolve();
-await queue.PublishAsync("orders", new { Id = 1 });
+var topic = bootstrap.Resolve();
+using var sub = topic.Subscribe("order-events", (order, _) =>
+{
+ Console.WriteLine($"topic saw {order.Id}");
+ return Task.CompletedTask;
+});
```
+## Configuration
+
+`AddRabbitMqMessaging(RabbitMqOptions options, MessagingOptions? messagingOptions = null)` takes the
+connection settings plus the shared messaging options (delivery attempts, dead-letter suffix). Options are
+code-only - this package registers no YAML config section.
+
+| `RabbitMqOptions` property | Default | Purpose |
+|----------------------------|---------------|-----------------------------------------------------|
+| `HostName` | `"localhost"` | Broker host. |
+| `Port` | `5672` | Broker port. |
+| `VirtualHost` | `"/"` | Virtual host. |
+| `UserName` | `"guest"` | User name. |
+| `Password` | `"guest"` | Password. |
+| `Uri` | `null` | AMQP URI; when set it overrides the fields above. |
+| `PrefetchCount` | `10` | Consumer prefetch (basic QoS) per subscriber. |
+
+A connection-string overload is also available:
+`AddRabbitMqMessaging("rabbitmq://app:secret@rabbit.internal:5672/myvhost?prefetch=20&maxDeliveryAttempts=5")`.
+
+`MessagingMetricsProvider` is registered as `IMessagingMetrics` / `IMetricProvider`; the provider reports
+published, delivered, and failed counts plus subscriber counts per queue.
+
## Key types
-| Type | Purpose |
-|-------------------------------------------|-------------------------------------------|
-| `RabbitMqMessagingRegistrationExtensions` | `AddRabbitMqMessaging(...)` registration. |
-| `RabbitMqQueueProvider` | RabbitMQ-backed `IQueueProvider`. |
-| `RabbitMqOptions` | Connection + prefetch configuration. |
+| Type | Purpose |
+|-------------------------------------------|----------------------------------------------------------|
+| `RabbitMqMessagingRegistrationExtensions` | `AddRabbitMqMessaging(...)` registration. |
+| `RabbitMqQueueProvider` | Quorum-queue `IQueueProvider` with broker-side DLQ. |
+| `RabbitMqTopicProvider` | Fan-out exchange `ITopicProvider`. |
+| `RabbitMqOptions` | Connection + prefetch configuration. |
## Related
+- Article: [Messaging with RabbitMQ](https://tgiachi.github.io/squid-std/articles/messaging-rabbitmq.html)
+- Article: [Messaging](https://tgiachi.github.io/squid-std/articles/messaging.html)
- Tutorial: [Messaging](https://tgiachi.github.io/squid-std/tutorials/messaging.html)
+- Siblings: [SquidStd.Messaging (in-memory)](https://tgiachi.github.io/squid-std/articles/messaging.html), [SquidStd.Messaging.Sqs](https://tgiachi.github.io/squid-std/articles/messaging-sqs.html)
## License
diff --git a/src/SquidStd.Messaging/README.md b/src/SquidStd.Messaging/README.md
index e723e09..9d99eb7 100644
--- a/src/SquidStd.Messaging/README.md
+++ b/src/SquidStd.Messaging/README.md
@@ -1,8 +1,15 @@
SquidStd.Messaging
-In-memory transport for SquidStd.Messaging. Provides a channel-backed `IQueueProvider` with per-queue
-buffering, round-robin delivery to subscribers, retry/dead-letter handling, and metrics - registered
-with a single `AddInMemoryMessaging()` call. Ideal for single-process apps, tests, and local dev.
+In-memory transport for SquidStd.Messaging. Provides channel-backed `IQueueProvider` and `ITopicProvider`
+implementations behind the shared `IMessageQueue` / `IMessageTopic` facades: per-queue buffering,
+round-robin (competing-consumers) delivery, retry and dead-letter handling, topic fan-out, and metrics -
+all registered with a single `AddInMemoryMessaging()` call.
+
+Queues deliver each message to exactly one subscriber; topics fan every message out to all subscribers.
+The contracts live in `SquidStd.Messaging.Abstractions`, so code written against `IMessageQueue` and
+`IMessageTopic` moves unchanged to a real broker: use this in-memory provider for single-process apps,
+tests, and local dev, then swap in `SquidStd.Messaging.RabbitMq` or `SquidStd.Messaging.Sqs` for
+production.
## Install
@@ -12,28 +19,108 @@ dotnet add package SquidStd.Messaging
## Usage
+Register through the bootstrap - the providers are `ISquidStdService`s, so `StartAsync` / `StopAsync`
+manage their lifecycle for you:
+
```csharp
-using DryIoc;
using SquidStd.Messaging.Abstractions.Interfaces;
using SquidStd.Messaging.Extensions;
+using SquidStd.Services.Core.Extensions;
+using SquidStd.Services.Core.Services.Bootstrap;
+
+var bootstrap = SquidStdBootstrap.Create(o => o.ConfigName = "myapp");
+bootstrap.ConfigureServices(c => c.RegisterCoreServices().AddInMemoryMessaging());
+await bootstrap.StartAsync();
+```
+
+### Queues (competing consumers)
+
+Each queue message goes to exactly one subscriber. Implement a listener and subscribe it; dispose the
+subscription to unsubscribe.
+
+```csharp
+public sealed record OrderPlaced(string Id);
+
+public sealed class OrderListener : IQueueMessageListenerAsync
+{
+ public Task HandleAsync(OrderPlaced message, CancellationToken cancellationToken)
+ {
+ Console.WriteLine($"queue handled {message.Id}");
+ return Task.CompletedTask;
+ }
+}
+
+var queue = bootstrap.Resolve();
+
+using (queue.Subscribe("orders", new OrderListener()))
+{
+ await queue.PublishAsync("orders", new OrderPlaced("order-1"));
+}
+```
-var container = new Container();
-container.AddInMemoryMessaging();
+A synchronous `IQueueMessageListener` overload of `Subscribe` is also available.
-var queue = container.Resolve();
-await queue.PublishAsync("orders", new { Id = 1 });
+### Topics (fan-out)
+
+Every subscriber receives every message published to the topic.
+
+```csharp
+var topic = bootstrap.Resolve();
+
+using (topic.Subscribe("order-events", (order, _) =>
+{
+ Console.WriteLine($"topic saw {order.Id}");
+ return Task.CompletedTask;
+}))
+{
+ await topic.PublishAsync("order-events", new OrderPlaced("order-2"));
+}
+```
+
+## Configuration
+
+`AddInMemoryMessaging` takes an optional `MessagingOptions`. Options are code-only - this package
+registers no YAML config section.
+
+```csharp
+c.AddInMemoryMessaging(new MessagingOptions
+{
+ MaxDeliveryAttempts = 5,
+ DeadLetterQueueSuffix = ".dead",
+ RetryDelay = TimeSpan.FromMilliseconds(250)
+});
```
+| Property | Default | Purpose |
+|-------------------------|---------|-------------------------------------------------------------|
+| `MaxDeliveryAttempts` | `3` | Delivery attempts before a message is dead-lettered. |
+| `DeadLetterQueueSuffix` | `".dlq"`| Suffix appended to a queue name to form its dead-letter queue. |
+| `RetryDelay` | `Zero` | Delay before a failed message is re-enqueued. |
+
+A connection-string overload is also available:
+`AddInMemoryMessaging("memory://local?maxDeliveryAttempts=5&retryDelayMs=250&deadLetterSuffix=.dead")`.
+
+When a listener throws, the message is re-enqueued (after `RetryDelay`) until `MaxDeliveryAttempts` is
+reached, then moved to `` - subscribe to that queue to inspect failures.
+`MessagingMetricsProvider` is registered as `IMessagingMetrics` / `IMetricProvider` and tracks published,
+delivered, retried, failed, and dead-lettered counts per queue.
+
## Key types
-| Type | Purpose |
-|-----------------------------------|-------------------------------------------|
-| `MessagingRegistrationExtensions` | `AddInMemoryMessaging(...)` registration. |
-| `InMemoryQueueProvider` | Channel-based in-memory `IQueueProvider`. |
+| Type | Purpose |
+|-----------------------------------|------------------------------------------------------------|
+| `MessagingRegistrationExtensions` | `AddInMemoryMessaging(...)` registration. |
+| `InMemoryQueueProvider` | Channel-based in-memory `IQueueProvider` with retry/DLQ. |
+| `InMemoryTopicProvider` | In-memory `ITopicProvider` (fan-out). |
+| `IMessageQueue` / `IMessageTopic` | Typed facades from `SquidStd.Messaging.Abstractions`. |
+| `MessagingOptions` | Retry, dead-letter, and delivery-attempt configuration. |
## Related
+- Article: [Messaging](https://tgiachi.github.io/squid-std/articles/messaging.html)
+- Article: [Messaging abstractions](https://tgiachi.github.io/squid-std/articles/messaging-abstractions.html)
- Tutorial: [Messaging](https://tgiachi.github.io/squid-std/tutorials/messaging.html)
+- Production transports: [SquidStd.Messaging.RabbitMq](https://tgiachi.github.io/squid-std/articles/messaging-rabbitmq.html), [SquidStd.Messaging.Sqs](https://tgiachi.github.io/squid-std/articles/messaging-sqs.html)
## License
diff --git a/src/SquidStd.Persistence.Abstractions/README.md b/src/SquidStd.Persistence.Abstractions/README.md
index d41864a..1c05df7 100644
--- a/src/SquidStd.Persistence.Abstractions/README.md
+++ b/src/SquidStd.Persistence.Abstractions/README.md
@@ -23,6 +23,13 @@ dotnet add package SquidStd.Persistence.Abstractions
| `JournalEntityOperationType` | `Upsert` / `Remove` journal operation discriminator. |
| `SnapshotSaveStartedEvent` / `SnapshotSaveCompletedEvent` | Snapshot lifecycle events (`IEvent`). |
+## Related
+
+- Article: [Persistence abstractions](https://tgiachi.github.io/squid-std/articles/persistence-abstractions.html)
+- Tutorial: [Persistence](https://tgiachi.github.io/squid-std/tutorials/persistence.html)
+- [`SquidStd.Persistence`](https://tgiachi.github.io/squid-std/articles/persistence.html) - the binary persistence engine implementing these contracts
+- [`SquidStd.Persistence.MessagePack`](https://tgiachi.github.io/squid-std/articles/persistence-messagepack.html) - MessagePack entity descriptors for the engine
+
## License
MIT - part of [SquidStd](https://github.com/tgiachi/squid-std).
diff --git a/src/SquidStd.Persistence.MessagePack/README.md b/src/SquidStd.Persistence.MessagePack/README.md
index 3880edf..877b5ed 100644
--- a/src/SquidStd.Persistence.MessagePack/README.md
+++ b/src/SquidStd.Persistence.MessagePack/README.md
@@ -1,8 +1,12 @@
SquidStd.Persistence.MessagePack
-MessagePack-backed binary `IDataSerializer`/`IDataDeserializer` for `SquidStd.Persistence`. The recommended
-compact binary default for entity payloads. Uses the contractless resolver, so plain POCOs need no
-attributes.
+MessagePack-backed binary `IDataSerializer`/`IDataDeserializer` for SquidStd. `MessagePackDataSerializer`
+implements both of SquidStd's serialization contracts over the MessagePack **contractless resolver**, so
+plain public POCOs round-trip with no `[MessagePackObject]` attributes and no key annotations. It is the
+recommended serializer for `SquidStd.Persistence` journals and snapshots: payloads are a fraction of the
+size of JSON and (de)serialization is faster, which matters when every entity write is appended to a
+binary journal. `SquidStd.Core` ships a JSON default; pull this package in when you want compact binary
+payloads instead.
## Install
@@ -12,26 +16,95 @@ dotnet add package SquidStd.Persistence.MessagePack
## Usage
+The serializer is a single stateless class - construct it and use it directly. `Serialize` returns a
+`ReadOnlyMemory` and `Deserialize` reads one back:
+
```csharp
using SquidStd.Core.Interfaces.Serialization;
using SquidStd.Persistence.MessagePack;
-IDataSerializer serializer = new MessagePackDataSerializer();
-IDataDeserializer deserializer = (MessagePackDataSerializer)serializer;
+public sealed class Player
+{
+ public int Id { get; set; }
+ public string Name { get; set; } = string.Empty;
+}
+
+var serializer = new MessagePackDataSerializer();
-// Pass to a PersistenceEntityDescriptor, or register in DI:
-container.RegisterInstance(new MessagePackDataSerializer());
-container.RegisterInstance(new MessagePackDataSerializer());
+ReadOnlyMemory bytes = serializer.Serialize(new Player { Id = 1, Name = "Bob" });
+Player back = serializer.Deserialize(bytes);
```
-> **Note:** the MessagePack contractless resolver requires **public** entity types. For non-public types,
-> use the JSON serializer from `SquidStd.Core` (`JsonDataSerializer`) instead.
+### With SquidStd.Persistence
+
+`PersistenceEntityDescriptor` takes the serializer and deserializer explicitly, so you can use
+MessagePack for entity payloads without touching the rest of the app:
+
+```csharp
+using SquidStd.Persistence.Data;
+using SquidStd.Persistence.MessagePack;
+using SquidStd.Persistence.Services;
+
+var serializer = new MessagePackDataSerializer();
+
+var registry = new PersistenceEntityRegistry();
+registry.Register(new PersistenceEntityDescriptor(
+ serializer, serializer, typeId: 1, typeName: "Player", schemaVersion: 1, keySelector: p => p.Id));
+```
+
+### As the app-wide default serializer
+
+`RegisterCoreServices()` registers the JSON default via `RegisterDataSerializer()`, which uses
+`IfAlreadyRegistered.Keep` - an already-registered serializer wins. So to make MessagePack the app-wide
+`IDataSerializer`/`IDataDeserializer` (used by messaging, caching, and DI-registered persistence
+descriptors), register it **before** `RegisterCoreServices()`:
+
+```csharp
+using DryIoc;
+using SquidStd.Core.Interfaces.Serialization;
+using SquidStd.Persistence.MessagePack;
+using SquidStd.Services.Core.Extensions;
+using SquidStd.Services.Core.Services.Bootstrap;
+
+var bootstrap = SquidStdBootstrap.Create(o => o.ConfigName = "myapp");
+
+bootstrap.ConfigureServices(c =>
+{
+ var serializer = new MessagePackDataSerializer();
+ c.RegisterInstance(serializer);
+ c.RegisterInstance(serializer);
+
+ return c.RegisterCoreServices(); // keeps your MessagePack registration
+});
+
+await bootstrap.StartAsync();
+```
+
+If you register it *after* `RegisterCoreServices()`, the JSON default is already in place and stays -
+the order matters, so always register MessagePack first.
+
+## Notes
+
+- **Public types only**: the contractless resolver requires **public** entity types. For non-public
+ types, use the JSON serializer from `SquidStd.Core` (`JsonDataSerializer`) instead.
+- **Attribute-free**: `ContractlessStandardResolver` serializes by member name, so no
+ `[MessagePackObject]`/`[Key]` attributes are needed - but member *names* become part of the payload
+ contract, so renaming a property is a schema change (bump `schemaVersion` on persisted entities).
+- There is no registration extension - the class is the whole package. Register the same instance for
+ both interfaces so serialize and deserialize agree.
## Key types
-| Type | Purpose |
-|-----------------------------|-----------------------------------------------------------------|
-| `MessagePackDataSerializer` | Contractless MessagePack `IDataSerializer`/`IDataDeserializer`. |
+| Type | Purpose |
+|-----------------------------|------------------------------------------------------------------|
+| `MessagePackDataSerializer` | Contractless MessagePack `IDataSerializer`/`IDataDeserializer`. |
+
+## Related
+
+- Article: [Serialization](https://tgiachi.github.io/squid-std/articles/serialization.html)
+- Article: [SquidStd.Persistence](https://tgiachi.github.io/squid-std/articles/persistence.html)
+- Article: [SquidStd.Persistence.Abstractions](https://tgiachi.github.io/squid-std/articles/persistence-abstractions.html)
+- Tutorial: [Durable entity persistence](https://tgiachi.github.io/squid-std/tutorials/persistence.html)
## License
diff --git a/src/SquidStd.Telemetry.Abstractions/README.md b/src/SquidStd.Telemetry.Abstractions/README.md
index c4f09eb..c4eedbc 100644
--- a/src/SquidStd.Telemetry.Abstractions/README.md
+++ b/src/SquidStd.Telemetry.Abstractions/README.md
@@ -31,6 +31,12 @@ captures every `SquidStd.*` source automatically.
| `OtlpProtocolType` | OTLP transport: gRPC or HTTP. |
| `SquidStdActivity` | Shared `ActivitySource` and the `SquidStd.*` source naming convention. |
+## Related
+
+- Article: [Telemetry abstractions](https://tgiachi.github.io/squid-std/articles/telemetry-abstractions.html)
+- Guide: [Observability](https://tgiachi.github.io/squid-std/articles/guides/observability.html)
+- [`SquidStd.Telemetry.OpenTelemetry`](https://tgiachi.github.io/squid-std/articles/telemetry-opentelemetry.html) - OTLP tracing/metrics provider consuming these options
+
## License
MIT - part of [SquidStd](https://github.com/tgiachi/squid-std).
diff --git a/src/SquidStd.Telemetry.OpenTelemetry/README.md b/src/SquidStd.Telemetry.OpenTelemetry/README.md
index 6f04c93..cc2ac3b 100644
--- a/src/SquidStd.Telemetry.OpenTelemetry/README.md
+++ b/src/SquidStd.Telemetry.OpenTelemetry/README.md
@@ -45,6 +45,12 @@ builder.Services.AddSquidStdTelemetry(new TelemetryOptions { ServiceName = "orde
| `TelemetryService` | Configures tracing/metrics providers and exporters from `TelemetryOptions`. |
| `MetricsSnapshotBridge` | Exports the existing SquidStd metrics snapshot to OTel instruments. |
+## Related
+
+- Article: [Telemetry OpenTelemetry](https://tgiachi.github.io/squid-std/articles/telemetry-opentelemetry.html)
+- Guide: [Observability](https://tgiachi.github.io/squid-std/articles/guides/observability.html)
+- [`SquidStd.Telemetry.Abstractions`](https://tgiachi.github.io/squid-std/articles/telemetry-abstractions.html) - `TelemetryOptions` and the `SquidStd.*` ActivitySource convention
+
## License
MIT - part of [SquidStd](https://github.com/tgiachi/squid-std).
diff --git a/src/SquidStd.Templates/README.md b/src/SquidStd.Templates/README.md
index d7cecd7..8c7c873 100644
--- a/src/SquidStd.Templates/README.md
+++ b/src/SquidStd.Templates/README.md
@@ -1,7 +1,9 @@
SquidStd.Templates
-`dotnet new` templates for scaffolding SquidStd projects. The generated projects reference the SquidStd packages
-at the same version as this template pack.
+`dotnet new` templates for scaffolding SquidStd projects. Each template ships a runnable project - a
+`Program.cs` wired to `SquidStdBootstrap`, a `squidstd.yaml` config file, and (for the service templates)
+a `Dockerfile`. The generated projects reference the SquidStd packages at the same version as the
+template pack, so a scaffolded app always lines up with the libraries it targets.
## Install
@@ -9,11 +11,13 @@ at the same version as this template pack.
dotnet new install SquidStd.Templates
```
+List the installed templates with `dotnet new list squidstd`.
+
## Templates
| Template | Short name | What you get |
|---------------------|-----------------------|-----------------------------------------------------------------|
-| Console host | `squidstd-host` | A `SquidStdBootstrap` console host. |
+| Console host | `squidstd-host` | A `SquidStdBootstrap` console host with `RegisterCoreServices`. |
| ASP.NET minimal API | `squidstd-aspnetcore` | `UseSquidStd` + health checks + a sample endpoint + Dockerfile. |
| Worker microservice | `squidstd-worker` | `AddWorkers` + a sample `IJobHandler` + Dockerfile. |
| Worker manager | `squidstd-manager` | `AddWorkerManager` + `MapWorkerManagerEndpoints` + Dockerfile. |
@@ -21,16 +25,57 @@ dotnet new install SquidStd.Templates
## Usage
```bash
-dotnet new squidstd-worker -n Acme.Resizer
-dotnet new squidstd-worker -n Acme.Resizer --messaging inmemory
+dotnet new squidstd-host -n Acme.Host
+dotnet new squidstd-aspnetcore -n Acme.Api
+dotnet new squidstd-worker -n Acme.Worker
dotnet new squidstd-manager -n Acme.Manager
```
-`--messaging` (`rabbitmq` default | `inmemory`) is available on the worker and manager templates.
+Every generated project builds and runs out of the box:
+
+```bash
+cd Acme.Worker
+dotnet run
+```
+
+The console host, for example, scaffolds the standard bootstrap pattern:
+
+```csharp
+using SquidStd.Services.Core.Extensions;
+using SquidStd.Services.Core.Services.Bootstrap;
+
+var bootstrap = SquidStdBootstrap.Create(options =>
+{
+ options.ConfigName = "squidstd";
+});
+
+bootstrap.ConfigureServices(container => container.RegisterCoreServices());
+
+await bootstrap.RunAsync();
+```
+
+## Template options
+
+| Option | Templates | Values | Default |
+|-----------------|----------------------|---------------------------------|------------|
+| `--messaging` | worker, manager | `rabbitmq` \| `inmemory` | `rabbitmq` |
+| `--skipRestore` | all | `true` \| `false` | `false` |
+
+`--messaging` picks the transport the worker and manager templates wire up: `rabbitmq` for real
+multi-process deployments, `inmemory` for trying it out in a single process.
+
+```bash
+dotnet new squidstd-worker -n Acme.Resizer --messaging inmemory
+dotnet new squidstd-manager -n Acme.Manager --messaging rabbitmq
+```
+
+`--skipRestore` skips the automatic `dotnet restore` post-action after creation.
## Related
- Tutorial: [Scaffolding projects](https://tgiachi.github.io/squid-std/tutorials/scaffolding.html)
+- Article: [SquidStd.Workers](https://tgiachi.github.io/squid-std/articles/workers.html)
+- Article: [SquidStd.Workers.Manager](https://tgiachi.github.io/squid-std/articles/workers-manager.html)
## License
diff --git a/src/SquidStd.Tui/README.md b/src/SquidStd.Tui/README.md
index 32d95a0..aceff55 100644
--- a/src/SquidStd.Tui/README.md
+++ b/src/SquidStd.Tui/README.md
@@ -79,6 +79,11 @@ public sealed class CounterView : TuiComposedView
| `TuiApplicationHost` | Boots the Terminal.Gui loop with a root ViewModel. |
| `RegisterTui()` / `RegisterView<,>()` | DryIoc registration. |
+## Related
+
+- Article: [Terminal UI](https://tgiachi.github.io/squid-std/articles/tui.html)
+- Tutorial: [Terminal UI (MVVM)](https://tgiachi.github.io/squid-std/tutorials/tui.html)
+
## License
MIT - part of [SquidStd](https://github.com/tgiachi/squid-std).
diff --git a/src/SquidStd.Workers/README.md b/src/SquidStd.Workers/README.md
index f750bf3..9737583 100644
--- a/src/SquidStd.Workers/README.md
+++ b/src/SquidStd.Workers/README.md
@@ -1,8 +1,13 @@
SquidStd.Workers
-The SquidStd worker runtime. Consumes `JobRequest`s from the jobs queue, dispatches each to a named
-`IJobHandler` (up to `MaxConcurrency` in parallel), and publishes a `WorkerHeartbeat` on the heartbeat topic
-every few seconds.
+The SquidStd worker runtime. `WorkerConsumerService` consumes `JobRequest`s from the jobs queue and
+dispatches each to the `IJobHandler` registered for its job name (up to `MaxConcurrency` in parallel),
+while `WorkerHeartbeatService` publishes a `WorkerHeartbeat` on the heartbeat topic every few seconds so
+a manager can track the fleet. Use it to build worker microservices that pull jobs off a shared queue;
+pair it with `SquidStd.Workers.Manager` on the producing side to enqueue jobs and watch heartbeats.
+
+The runtime rides on SquidStd messaging, so a transport must be registered alongside it - in-memory for
+a single process, RabbitMQ for real multi-process deployments.
## Install
@@ -12,18 +17,35 @@ dotnet add package SquidStd.Workers
## Usage
+Register a messaging transport, the worker runtime, and your handlers on the bootstrap container:
+
```csharp
-using DryIoc;
using SquidStd.Generators.Workers;
-using SquidStd.Workers.Attributes;
+using SquidStd.Messaging.Extensions;
+using SquidStd.Services.Core.Extensions;
+using SquidStd.Services.Core.Services.Bootstrap;
using SquidStd.Workers.Extensions;
-container.AddInMemoryMessaging(); // or AddRabbitMqMessaging(...)
-container.AddWorkers();
-container.RegisterGeneratedJobHandlers();
+var bootstrap = SquidStdBootstrap.Create(o => o.ConfigName = "myworker");
+
+bootstrap.ConfigureServices(c => c
+ .RegisterCoreServices()
+ .AddInMemoryMessaging() // or AddRabbitMqMessaging(...)
+ .AddWorkers()
+ .RegisterGeneratedJobHandlers()); // source-generated [RegisterJobHandler] registrations
+
+await bootstrap.StartAsync();
```
+A handler implements `IJobHandler`: it names the job it processes and does the work in `HandleAsync`,
+reading string parameters off the `JobRequest`. `[RegisterJobHandler]` opts it into generated
+registration (or register manually with `AddJobHandler()`):
+
```csharp
+using SquidStd.Workers.Abstractions.Data;
+using SquidStd.Workers.Attributes;
+using SquidStd.Workers.Interfaces;
+
[RegisterJobHandler]
public sealed class ResizeImageHandler : IJobHandler
{
@@ -31,24 +53,64 @@ public sealed class ResizeImageHandler : IJobHandler
public Task HandleAsync(JobRequest job, CancellationToken cancellationToken)
{
- // job.Parameters["width"], ...
+ var width = job.Parameters["width"];
+ // ... do the work ...
return Task.CompletedTask;
}
}
```
+On the producing side, `SquidStd.Workers.Manager`'s `IJobScheduler` (registered with
+`AddWorkerManager()`) publishes the `JobRequest` onto the same queue:
+
+```csharp
+var scheduler = bootstrap.Resolve();
+await scheduler.EnqueueAsync("resize-image", new Dictionary { ["width"] = "640" });
+```
+
+An unmatched job name raises `JobHandlerNotFoundException` in the dispatcher.
+
+## Configuration
+
+`AddWorkers()` binds the `workers` section of the YAML config to `WorkersConfig`:
+
+```yaml
+workers:
+ WorkerId: worker-1
+ HeartbeatIntervalSeconds: 10
+ MaxConcurrency: 4
+ JobQueue: squidstd.workers.jobs
+ HeartbeatTopic: squidstd.workers.heartbeat
+```
+
+| Property | Default | Notes |
+|----------------------------|-------------------------------|------------------------------------------------------------------|
+| `WorkerId` | empty | Stable identity; blank falls back to the machine/container name. |
+| `HeartbeatIntervalSeconds` | `10` | Seconds between heartbeats; non-positive falls back to 10. |
+| `MaxConcurrency` | `0` | Parallel jobs; non-positive falls back to the processor count. |
+| `JobQueue` | `squidstd.workers.jobs` | Queue consumed for jobs (`WorkerChannels.JobQueue`). |
+| `HeartbeatTopic` | `squidstd.workers.heartbeat` | Topic heartbeats are published to (`WorkerChannels.HeartbeatTopic`). |
+
+The channel defaults come from `WorkerChannels` in `SquidStd.Workers.Abstractions`, shared with the
+manager so both sides agree out of the box.
+
## Key types
-| Type | Purpose |
-|---------------------------------|------------------------------------------------------------------------------|
-| `IJobHandler` | Handles jobs of one named kind. |
-| `WorkersConfig` | `WorkerId`, `HeartbeatIntervalSeconds`, `MaxConcurrency`, queue/topic names. |
-| `RegisterJobHandlerAttribute` | Marks handlers for generated registration. |
+| Type | Purpose |
+|---------------------------------|-------------------------------------------------------------------------------|
+| `IJobHandler` | Handles jobs of one named kind: `JobName` + `HandleAsync(JobRequest, ct)`. |
+| `IJobDispatcher` | Routes a `JobRequest` to the handler registered for its job name. |
+| `RegisterJobHandlerAttribute` | Marks handlers for source-generated registration. |
+| `WorkerConsumerService` | Lifecycle service that consumes the jobs queue and dispatches. |
+| `WorkerHeartbeatService` | Lifecycle service that publishes periodic `WorkerHeartbeat`s. |
+| `WorkersConfig` | The `workers` config section (identity, heartbeat, concurrency, channels). |
| `WorkersRegistrationExtensions` | `AddWorkers()` and `AddJobHandler()`. |
## Related
-- Tutorial: [Worker system](https://tgiachi.github.io/squid-std/tutorials/worker-system.html)
+- Tutorial: [Build a worker system](https://tgiachi.github.io/squid-std/tutorials/worker-system.html)
+- Article: [SquidStd.Workers.Manager](https://tgiachi.github.io/squid-std/articles/workers-manager.html)
+- Article: [SquidStd.Workers.Abstractions](https://tgiachi.github.io/squid-std/articles/workers-abstractions.html)
## License