diff --git a/src/AzLocal.State/JsonSnapshotStateStore.cs b/src/AzLocal.State/JsonSnapshotStateStore.cs
index 71dfcce..dd2f758 100644
--- a/src/AzLocal.State/JsonSnapshotStateStore.cs
+++ b/src/AzLocal.State/JsonSnapshotStateStore.cs
@@ -1,5 +1,92 @@
-namespace AzLocal.State;
+using AzLocal.Core.Interfaces;
+using Microsoft.Extensions.Configuration;
+using System.Collections.Concurrent;
+using System.Text.Json;
-public class JsonSnapshotStateStore
+namespace AzLocal.State;
+
+///
+/// Same in-memory dictionary as , but every write is also
+/// persisted to a JSON file on disk. State survives process restarts.
+/// Snapshot file defaults to %TEMP%/azlocal/state.json unless overridden via
+/// AzLocal:SnapshotPath in configuration.
+///
+public class JsonSnapshotStateStore : IStateStore
{
+ private readonly string _filePath;
+
+ // Limits concurrent file writes to one at a time so the snapshot is never half-written.
+ private readonly SemaphoreSlim _lock = new(1, 1);
+ private readonly ConcurrentDictionary _store;
+
+ public JsonSnapshotStateStore(IConfiguration config)
+ {
+ var dir = config["AzLocal:SnapshotPath"]
+ ?? Path.Combine(Path.GetTempPath(), "azlocal");
+ Directory.CreateDirectory(dir);
+ _filePath = Path.Combine(dir, "state.json");
+ _store = Load();
+ }
+
+ /// Returns the entry deserialized as , or null if the key does not exist.
+ public Task GetAsync(string key)
+ {
+ if (_store.TryGetValue(key, out var json))
+ return Task.FromResult(JsonSerializer.Deserialize(json));
+ return Task.FromResult(default);
+ }
+
+ /// Serializes to JSON, stores it in memory, and flushes the snapshot to disk.
+ public async Task SetAsync(string key, T value)
+ {
+ _store[key] = JsonSerializer.Serialize(value);
+ await SaveAsync();
+ }
+
+ /// Removes the entry with the given key and flushes the snapshot to disk. No-ops if the key does not exist.
+ public async Task DeleteAsync(string key)
+ {
+ _store.TryRemove(key, out _);
+ await SaveAsync();
+ }
+
+ /// Returns all entries whose keys start with , deserialized as .
+ public Task> ListAsync(string prefix)
+ {
+ var results = _store
+ .Where(kv => kv.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
+ .Select(kv => JsonSerializer.Deserialize(kv.Value)!)
+ .ToList();
+ return Task.FromResult>(results);
+ }
+
+ /// Returns true if an entry with the given key exists.
+ public Task ExistsAsync(string key) =>
+ Task.FromResult(_store.ContainsKey(key));
+
+ #region private helpers
+
+ // Reads the snapshot file on startup. Returns an empty dictionary if the file does not exist yet.
+ private ConcurrentDictionary Load()
+ {
+ if (!File.Exists(_filePath))
+ return new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase);
+ var json = File.ReadAllText(_filePath);
+ var dict = JsonSerializer.Deserialize>(json) ?? new();
+ return new ConcurrentDictionary(dict, StringComparer.OrdinalIgnoreCase);
+ }
+
+ // Writes the full dictionary to disk under the semaphore so concurrent writes don't race.
+ private async Task SaveAsync()
+ {
+ await _lock.WaitAsync();
+ try
+ {
+ await File.WriteAllTextAsync(_filePath,
+ JsonSerializer.Serialize(new Dictionary(_store)));
+ }
+ finally { _lock.Release(); }
+ }
+
+ #endregion
}