From 4c6f2d60477597c0930303b647c6f4ff42728337 Mon Sep 17 00:00:00 2001 From: Virgil Date: Sat, 4 Apr 2026 19:24:47 +0000 Subject: [PATCH] feat(scope): add scoped on-change helper Co-Authored-By: Virgil --- scope.go | 21 +++++++++++++++++++++ scope_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/scope.go b/scope.go index f28f997..d7e28aa 100644 --- a/scope.go +++ b/scope.go @@ -280,6 +280,27 @@ func (scopedStore *ScopedStore) PurgeExpired() (int64, error) { return removedRows, nil } +// Usage example: `unregister := scopedStore.OnChange(func(event store.Event) { fmt.Println(event.Group, event.Key, event.Value) })` +// The callback receives the namespace-local group name, so a write to +// `tenant-a:config` is reported as `config`. +func (scopedStore *ScopedStore) OnChange(callback func(Event)) func() { + if scopedStore == nil || callback == nil { + return func() {} + } + if scopedStore.storeInstance == nil { + return func() {} + } + + namespacePrefix := scopedStore.namespacePrefix() + return scopedStore.storeInstance.OnChange(func(event Event) { + if !core.HasPrefix(event.Group, namespacePrefix) { + return + } + event.Group = core.TrimPrefix(event.Group, namespacePrefix) + callback(event) + }) +} + // ScopedStoreTransaction exposes namespace-local transaction helpers so callers // can work inside a scoped namespace without manually prefixing group names. // diff --git a/scope_test.go b/scope_test.go index f75b835..e732b0c 100644 --- a/scope_test.go +++ b/scope_test.go @@ -273,6 +273,32 @@ func TestScope_ScopedStore_Good_DeletePrefix(t *testing.T) { assert.Equal(t, "keep", otherValue) } +func TestScope_ScopedStore_Good_OnChange_NamespaceLocal(t *testing.T) { + storeInstance, _ := New(":memory:") + defer storeInstance.Close() + + scopedStore, _ := NewScoped(storeInstance, "tenant-a") + otherScopedStore, _ := NewScoped(storeInstance, "tenant-b") + + var events []Event + unregister := scopedStore.OnChange(func(event Event) { + events = append(events, event) + }) + defer unregister() + + require.NoError(t, scopedStore.SetIn("config", "colour", "blue")) + require.NoError(t, otherScopedStore.SetIn("config", "colour", "red")) + require.NoError(t, scopedStore.Delete("config", "colour")) + + require.Len(t, events, 2) + assert.Equal(t, "config", events[0].Group) + assert.Equal(t, "colour", events[0].Key) + assert.Equal(t, "blue", events[0].Value) + assert.Equal(t, "config", events[1].Group) + assert.Equal(t, "colour", events[1].Key) + assert.Equal(t, "", events[1].Value) +} + func TestScope_ScopedStore_Good_GetAll(t *testing.T) { storeInstance, _ := New(":memory:") defer storeInstance.Close() -- 2.45.3