using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using NATS.Server.Configuration; using NATS.Server.JetStream.Api; namespace NATS.Server.JetStream; // Maps to Go's enableJetStream() in server/jetstream.go:414-523. // Orchestrates the JetStream subsystem lifecycle: validates config, creates the // store directory, registers API subjects, and tears down cleanly on dispose. public sealed class JetStreamService : IAsyncDisposable { // Full set of $JS.API.> subjects registered at startup. // Mirrors the subjects registered by setJetStreamExportSubs() in // golang/nats-server/server/jetstream.go and jsApiSubs in jetstream_api.go. private static readonly IReadOnlyList AllApiSubjects = [ "$JS.API.>", JetStreamApiSubjects.Info, JetStreamApiSubjects.StreamCreate + "*", JetStreamApiSubjects.StreamUpdate + "*", JetStreamApiSubjects.StreamDelete + "*", JetStreamApiSubjects.StreamInfo + "*", JetStreamApiSubjects.StreamNames, JetStreamApiSubjects.StreamList, JetStreamApiSubjects.StreamPurge + "*", JetStreamApiSubjects.StreamMessageGet + "*", JetStreamApiSubjects.StreamMessageDelete + "*", JetStreamApiSubjects.StreamSnapshot + "*", JetStreamApiSubjects.StreamRestore + "*", JetStreamApiSubjects.StreamLeaderStepdown + "*", JetStreamApiSubjects.ConsumerCreate + "*", JetStreamApiSubjects.ConsumerDelete + "*.*", JetStreamApiSubjects.ConsumerInfo + "*.*", JetStreamApiSubjects.ConsumerNames + "*", JetStreamApiSubjects.ConsumerList + "*", JetStreamApiSubjects.ConsumerPause + "*.*", JetStreamApiSubjects.ConsumerNext + "*.*", JetStreamApiSubjects.DirectGet + "*", JetStreamApiSubjects.MetaLeaderStepdown, ]; private readonly JetStreamOptions _options; private readonly ILogger _logger; private List _registeredApiSubjects = []; public InternalClient? InternalClient { get; } public bool IsRunning { get; private set; } /// /// The API subjects registered with the server after a successful StartAsync. /// Empty before start or after dispose. /// public IReadOnlyList RegisteredApiSubjects => _registeredApiSubjects; /// /// Maximum streams limit from configuration. 0 means unlimited. /// Maps to Go's JetStreamAccountLimits.MaxStreams. /// public int MaxStreams => _options.MaxStreams; /// /// Maximum consumers limit from configuration. 0 means unlimited. /// Maps to Go's JetStreamAccountLimits.MaxConsumers. /// public int MaxConsumers => _options.MaxConsumers; /// /// Maximum memory store bytes from configuration. 0 means unlimited. /// Maps to Go's JetStreamConfig.MaxMemory. /// public long MaxMemory => _options.MaxMemoryStore; /// /// Maximum file store bytes from configuration. 0 means unlimited. /// Maps to Go's JetStreamConfig.MaxStore. /// public long MaxStore => _options.MaxFileStore; public JetStreamService(JetStreamOptions options, InternalClient? internalClient = null) : this(options, internalClient, NullLoggerFactory.Instance) { } public JetStreamService(JetStreamOptions options, InternalClient? internalClient, ILoggerFactory loggerFactory) { _options = options; InternalClient = internalClient; _logger = loggerFactory.CreateLogger(); } // Maps to Go's enableJetStream() in server/jetstream.go:414-523. // Validates the store directory, creates it if absent, then registers all // $JS.API.> subjects so inbound API messages can be routed. public Task StartAsync(CancellationToken ct) { if (IsRunning) { _logger.LogDebug("JetStream is already running; ignoring duplicate StartAsync"); return Task.CompletedTask; } // Validate and create store directory when specified. // Go: os.MkdirAll(cfg.StoreDir, defaultDirPerms) — jetstream.go:430-444. if (!string.IsNullOrEmpty(_options.StoreDir)) { if (Directory.Exists(_options.StoreDir)) { _logger.LogDebug("JetStream store directory already exists: {StoreDir}", _options.StoreDir); } else { Directory.CreateDirectory(_options.StoreDir); _logger.LogInformation("JetStream store directory created: {StoreDir}", _options.StoreDir); } } else { _logger.LogInformation("JetStream running in memory-only mode (no StoreDir configured)"); } // Register all $JS.API.> subjects. // Go: setJetStreamExportSubs() — jetstream.go:489-494. _registeredApiSubjects = [.. AllApiSubjects]; IsRunning = true; _logger.LogInformation( "JetStream started. MaxMemory={MaxMemory}, MaxStore={MaxStore}, MaxStreams={MaxStreams}, MaxConsumers={MaxConsumers}, RegisteredSubjects={Count}", _options.MaxMemoryStore, _options.MaxFileStore, _options.MaxStreams, _options.MaxConsumers, _registeredApiSubjects.Count); return Task.CompletedTask; } // Maps to Go's shutdown path in jetstream.go. // Clears registered subjects and marks the service as not running. public ValueTask DisposeAsync() { _registeredApiSubjects = []; IsRunning = false; _logger.LogInformation("JetStream stopped"); return ValueTask.CompletedTask; } }