feat: add advisory event publication for API operations (Gap 7.6)
Add JetStream advisory subject constants to EventSubjects and a lightweight AdvisoryPublisher that publishes stream/consumer lifecycle events to $JS.EVENT.ADVISORY.* subjects without depending on InternalEventSystem directly (testable via Action delegate injection).
This commit is contained in:
@@ -33,6 +33,20 @@ public static class EventSubjects
|
||||
|
||||
// Inbox for responses
|
||||
public const string InboxResponse = "$SYS._INBOX_.{0}";
|
||||
|
||||
// JetStream advisory events
|
||||
// Go reference: jetstream_api.go advisory subjects
|
||||
public const string JsAdvisoryStreamCreated = "$JS.EVENT.ADVISORY.STREAM.CREATED.{0}";
|
||||
public const string JsAdvisoryStreamDeleted = "$JS.EVENT.ADVISORY.STREAM.DELETED.{0}";
|
||||
public const string JsAdvisoryStreamUpdated = "$JS.EVENT.ADVISORY.STREAM.UPDATED.{0}";
|
||||
public const string JsAdvisoryConsumerCreated = "$JS.EVENT.ADVISORY.CONSUMER.CREATED.{0}.{1}";
|
||||
public const string JsAdvisoryConsumerDeleted = "$JS.EVENT.ADVISORY.CONSUMER.DELETED.{0}.{1}";
|
||||
public const string JsAdvisoryStreamSnapshotCreated = "$JS.EVENT.ADVISORY.STREAM.SNAPSHOT_CREATE.{0}";
|
||||
public const string JsAdvisoryStreamSnapshotCompleted = "$JS.EVENT.ADVISORY.STREAM.SNAPSHOT_COMPLETE.{0}";
|
||||
public const string JsAdvisoryStreamRestoreCreated = "$JS.EVENT.ADVISORY.STREAM.RESTORE_CREATE.{0}";
|
||||
public const string JsAdvisoryStreamRestoreCompleted = "$JS.EVENT.ADVISORY.STREAM.RESTORE_COMPLETE.{0}";
|
||||
public const string JsAdvisoryStreamLeaderElected = "$JS.EVENT.ADVISORY.STREAM.LEADER_ELECTED.{0}";
|
||||
public const string JsAdvisoryStreamQuorumLost = "$JS.EVENT.ADVISORY.STREAM.QUORUM_LOST.{0}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
130
src/NATS.Server/JetStream/Api/AdvisoryPublisher.cs
Normal file
130
src/NATS.Server/JetStream/Api/AdvisoryPublisher.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
namespace NATS.Server.JetStream.Api;
|
||||
|
||||
/// <summary>
|
||||
/// Publishes JetStream advisory events to $JS.EVENT.ADVISORY.* subjects.
|
||||
/// Designed to be lightweight and testable; accepts a publish action delegate
|
||||
/// rather than depending directly on InternalEventSystem.
|
||||
/// Go reference: jetstream_api.go advisory publication.
|
||||
/// </summary>
|
||||
public sealed class AdvisoryPublisher
|
||||
{
|
||||
private readonly Action<string, object> _publishAction;
|
||||
private long _publishCount;
|
||||
|
||||
public AdvisoryPublisher(Action<string, object> publishAction)
|
||||
{
|
||||
_publishAction = publishAction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Total number of advisory events published since creation.
|
||||
/// </summary>
|
||||
public long PublishCount => Interlocked.Read(ref _publishCount);
|
||||
|
||||
/// <summary>
|
||||
/// Publishes a stream created advisory.
|
||||
/// Go reference: jetstream_api.go — advisory on stream creation.
|
||||
/// </summary>
|
||||
public void StreamCreated(string streamName, object? detail = null)
|
||||
{
|
||||
var subject = string.Format(Events.EventSubjects.JsAdvisoryStreamCreated, streamName);
|
||||
Publish(subject, new AdvisoryEvent
|
||||
{
|
||||
Type = "io.nats.jetstream.advisory.stream_created",
|
||||
Stream = streamName,
|
||||
TimeStamp = DateTime.UtcNow,
|
||||
Detail = detail,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Publishes a stream deleted advisory.
|
||||
/// Go reference: jetstream_api.go — advisory on stream deletion.
|
||||
/// </summary>
|
||||
public void StreamDeleted(string streamName)
|
||||
{
|
||||
var subject = string.Format(Events.EventSubjects.JsAdvisoryStreamDeleted, streamName);
|
||||
Publish(subject, new AdvisoryEvent
|
||||
{
|
||||
Type = "io.nats.jetstream.advisory.stream_deleted",
|
||||
Stream = streamName,
|
||||
TimeStamp = DateTime.UtcNow,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Publishes a stream updated advisory.
|
||||
/// Go reference: jetstream_api.go — advisory on stream config update.
|
||||
/// </summary>
|
||||
public void StreamUpdated(string streamName, object? detail = null)
|
||||
{
|
||||
var subject = string.Format(Events.EventSubjects.JsAdvisoryStreamUpdated, streamName);
|
||||
Publish(subject, new AdvisoryEvent
|
||||
{
|
||||
Type = "io.nats.jetstream.advisory.stream_updated",
|
||||
Stream = streamName,
|
||||
TimeStamp = DateTime.UtcNow,
|
||||
Detail = detail,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Publishes a consumer created advisory.
|
||||
/// Go reference: jetstream_api.go — advisory on consumer creation.
|
||||
/// </summary>
|
||||
public void ConsumerCreated(string streamName, string consumerName)
|
||||
{
|
||||
var subject = string.Format(Events.EventSubjects.JsAdvisoryConsumerCreated, streamName, consumerName);
|
||||
Publish(subject, new AdvisoryEvent
|
||||
{
|
||||
Type = "io.nats.jetstream.advisory.consumer_created",
|
||||
Stream = streamName,
|
||||
Consumer = consumerName,
|
||||
TimeStamp = DateTime.UtcNow,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Publishes a consumer deleted advisory.
|
||||
/// Go reference: jetstream_api.go — advisory on consumer deletion.
|
||||
/// </summary>
|
||||
public void ConsumerDeleted(string streamName, string consumerName)
|
||||
{
|
||||
var subject = string.Format(Events.EventSubjects.JsAdvisoryConsumerDeleted, streamName, consumerName);
|
||||
Publish(subject, new AdvisoryEvent
|
||||
{
|
||||
Type = "io.nats.jetstream.advisory.consumer_deleted",
|
||||
Stream = streamName,
|
||||
Consumer = consumerName,
|
||||
TimeStamp = DateTime.UtcNow,
|
||||
});
|
||||
}
|
||||
|
||||
private void Publish(string subject, AdvisoryEvent evt)
|
||||
{
|
||||
Interlocked.Increment(ref _publishCount);
|
||||
_publishAction(subject, evt);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Advisory event payload describing a JetStream lifecycle event.
|
||||
/// Go reference: jetstream_api.go advisory event types.
|
||||
/// </summary>
|
||||
public sealed class AdvisoryEvent
|
||||
{
|
||||
/// <summary>Reverse-DNS event type identifier (e.g., "io.nats.jetstream.advisory.stream_created").</summary>
|
||||
public string Type { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>Name of the stream involved in the event, if applicable.</summary>
|
||||
public string? Stream { get; init; }
|
||||
|
||||
/// <summary>Name of the consumer involved in the event, if applicable.</summary>
|
||||
public string? Consumer { get; init; }
|
||||
|
||||
/// <summary>UTC timestamp when the advisory was generated.</summary>
|
||||
public DateTime TimeStamp { get; init; }
|
||||
|
||||
/// <summary>Optional additional detail payload (arbitrary object).</summary>
|
||||
public object? Detail { get; init; }
|
||||
}
|
||||
Reference in New Issue
Block a user