refactor(store): tighten scoped AX names
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
36a8d89677
commit
2c55d220fa
2 changed files with 21 additions and 21 deletions
|
|
@ -188,6 +188,6 @@ These are design notes, not committed work:
|
|||
|
||||
- **Pagination for `GetAll`.** A `GetPage(group string, offset, limit int)` method would support large groups without full in-memory materialisation.
|
||||
- **Indexed prefix keys.** An additional index on `(grp, key)` prefix would accelerate prefix scans without a full-table scan.
|
||||
- **TTL background purge interval as constructor option.** Currently only settable by mutating `s.purgeInterval` directly in tests. A `WithPurgeInterval(d time.Duration)` functional option would make this part of the public API.
|
||||
- **TTL background purge interval as constructor configuration.** Currently only settable by mutating `storeInstance.purgeInterval` directly in tests. A constructor-level `PurgeInterval` field or config entry would make this part of the public API.
|
||||
- **Cross-group atomic operations.** Exposing a `Transaction(func(tx *StoreTx) error)` API would allow callers to compose atomic multi-group operations.
|
||||
- **`DeletePrefix(prefix string)` method.** Would enable efficient cleanup of an entire namespace without first listing groups.
|
||||
|
|
|
|||
40
scope.go
40
scope.go
|
|
@ -23,20 +23,20 @@ type QuotaConfig struct {
|
|||
// namespace to prevent key collisions across tenants.
|
||||
// Usage example: `scopedStore, _ := store.NewScoped(storeInstance, "tenant-a")`
|
||||
type ScopedStore struct {
|
||||
store *Store
|
||||
namespace string
|
||||
quota QuotaConfig
|
||||
storeInstance *Store
|
||||
namespace string
|
||||
quota QuotaConfig
|
||||
}
|
||||
|
||||
// NewScoped creates a ScopedStore that prefixes all groups with the given
|
||||
// namespace. The namespace must be non-empty and contain only alphanumeric
|
||||
// characters and hyphens.
|
||||
// Usage example: `scopedStore, _ := store.NewScoped(storeInstance, "tenant-a")`
|
||||
func NewScoped(store *Store, namespace string) (*ScopedStore, error) {
|
||||
func NewScoped(storeInstance *Store, namespace string) (*ScopedStore, error) {
|
||||
if !validNamespace.MatchString(namespace) {
|
||||
return nil, core.E("store.NewScoped", core.Sprintf("namespace %q is invalid (must be non-empty, alphanumeric + hyphens)", namespace), nil)
|
||||
}
|
||||
scopedStore := &ScopedStore{store: store, namespace: namespace}
|
||||
scopedStore := &ScopedStore{storeInstance: storeInstance, namespace: namespace}
|
||||
return scopedStore, nil
|
||||
}
|
||||
|
||||
|
|
@ -44,8 +44,8 @@ func NewScoped(store *Store, namespace string) (*ScopedStore, error) {
|
|||
// checked on Set and SetWithTTL before inserting new keys or creating new
|
||||
// groups.
|
||||
// Usage example: `scopedStore, _ := store.NewScopedWithQuota(storeInstance, "tenant-a", quota)`
|
||||
func NewScopedWithQuota(store *Store, namespace string, quota QuotaConfig) (*ScopedStore, error) {
|
||||
scopedStore, err := NewScoped(store, namespace)
|
||||
func NewScopedWithQuota(storeInstance *Store, namespace string, quota QuotaConfig) (*ScopedStore, error) {
|
||||
scopedStore, err := NewScoped(storeInstance, namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -67,7 +67,7 @@ func (scopedStore *ScopedStore) Namespace() string {
|
|||
// Get retrieves a value by group and key within the namespace.
|
||||
// Usage example: `value, err := scopedStore.Get("config", "theme")`
|
||||
func (scopedStore *ScopedStore) Get(group, key string) (string, error) {
|
||||
return scopedStore.store.Get(scopedStore.namespacedGroup(group), key)
|
||||
return scopedStore.storeInstance.Get(scopedStore.namespacedGroup(group), key)
|
||||
}
|
||||
|
||||
// Set stores a value by group and key within the namespace. If quotas are
|
||||
|
|
@ -77,7 +77,7 @@ func (scopedStore *ScopedStore) Set(group, key, value string) error {
|
|||
if err := scopedStore.checkQuota(group, key); err != nil {
|
||||
return err
|
||||
}
|
||||
return scopedStore.store.Set(scopedStore.namespacedGroup(group), key, value)
|
||||
return scopedStore.storeInstance.Set(scopedStore.namespacedGroup(group), key, value)
|
||||
}
|
||||
|
||||
// SetWithTTL stores a value with a time-to-live within the namespace. Quota
|
||||
|
|
@ -87,46 +87,46 @@ func (scopedStore *ScopedStore) SetWithTTL(group, key, value string, ttl time.Du
|
|||
if err := scopedStore.checkQuota(group, key); err != nil {
|
||||
return err
|
||||
}
|
||||
return scopedStore.store.SetWithTTL(scopedStore.namespacedGroup(group), key, value, ttl)
|
||||
return scopedStore.storeInstance.SetWithTTL(scopedStore.namespacedGroup(group), key, value, ttl)
|
||||
}
|
||||
|
||||
// Delete removes a single key from a group within the namespace.
|
||||
// Usage example: `err := scopedStore.Delete("config", "theme")`
|
||||
func (scopedStore *ScopedStore) Delete(group, key string) error {
|
||||
return scopedStore.store.Delete(scopedStore.namespacedGroup(group), key)
|
||||
return scopedStore.storeInstance.Delete(scopedStore.namespacedGroup(group), key)
|
||||
}
|
||||
|
||||
// DeleteGroup removes all keys in a group within the namespace.
|
||||
// Usage example: `err := scopedStore.DeleteGroup("cache")`
|
||||
func (scopedStore *ScopedStore) DeleteGroup(group string) error {
|
||||
return scopedStore.store.DeleteGroup(scopedStore.namespacedGroup(group))
|
||||
return scopedStore.storeInstance.DeleteGroup(scopedStore.namespacedGroup(group))
|
||||
}
|
||||
|
||||
// GetAll returns all non-expired key-value pairs in a group within the
|
||||
// namespace.
|
||||
// Usage example: `entries, err := scopedStore.GetAll("config")`
|
||||
func (scopedStore *ScopedStore) GetAll(group string) (map[string]string, error) {
|
||||
return scopedStore.store.GetAll(scopedStore.namespacedGroup(group))
|
||||
return scopedStore.storeInstance.GetAll(scopedStore.namespacedGroup(group))
|
||||
}
|
||||
|
||||
// All returns an iterator over all non-expired key-value pairs in a group
|
||||
// within the namespace.
|
||||
// Usage example: `for entry, err := range scopedStore.All("config") { _ = entry; _ = err }`
|
||||
func (scopedStore *ScopedStore) All(group string) iter.Seq2[KeyValue, error] {
|
||||
return scopedStore.store.All(scopedStore.namespacedGroup(group))
|
||||
return scopedStore.storeInstance.All(scopedStore.namespacedGroup(group))
|
||||
}
|
||||
|
||||
// Count returns the number of non-expired keys in a group within the namespace.
|
||||
// Usage example: `count, err := scopedStore.Count("config")`
|
||||
func (scopedStore *ScopedStore) Count(group string) (int, error) {
|
||||
return scopedStore.store.Count(scopedStore.namespacedGroup(group))
|
||||
return scopedStore.storeInstance.Count(scopedStore.namespacedGroup(group))
|
||||
}
|
||||
|
||||
// Render loads all non-expired key-value pairs from a namespaced group and
|
||||
// renders a Go template.
|
||||
// Usage example: `output, err := scopedStore.Render("Hello {{ .name }}", "user")`
|
||||
func (scopedStore *ScopedStore) Render(templateSource, group string) (string, error) {
|
||||
return scopedStore.store.Render(templateSource, scopedStore.namespacedGroup(group))
|
||||
return scopedStore.storeInstance.Render(templateSource, scopedStore.namespacedGroup(group))
|
||||
}
|
||||
|
||||
// checkQuota verifies that inserting key into group would not exceed the
|
||||
|
|
@ -141,7 +141,7 @@ func (scopedStore *ScopedStore) checkQuota(group, key string) error {
|
|||
namespacePrefix := scopedStore.namespace + ":"
|
||||
|
||||
// Check if this is an upsert (key already exists) — upserts never exceed quota.
|
||||
_, err := scopedStore.store.Get(namespacedGroup, key)
|
||||
_, err := scopedStore.storeInstance.Get(namespacedGroup, key)
|
||||
if err == nil {
|
||||
// Key exists — this is an upsert, no quota check needed.
|
||||
return nil
|
||||
|
|
@ -153,7 +153,7 @@ func (scopedStore *ScopedStore) checkQuota(group, key string) error {
|
|||
|
||||
// Check MaxKeys quota.
|
||||
if scopedStore.quota.MaxKeys > 0 {
|
||||
keyCount, err := scopedStore.store.CountAll(namespacePrefix)
|
||||
keyCount, err := scopedStore.storeInstance.CountAll(namespacePrefix)
|
||||
if err != nil {
|
||||
return core.E("store.ScopedStore", "quota check", err)
|
||||
}
|
||||
|
|
@ -164,14 +164,14 @@ func (scopedStore *ScopedStore) checkQuota(group, key string) error {
|
|||
|
||||
// Check MaxGroups quota — only if this would create a new group.
|
||||
if scopedStore.quota.MaxGroups > 0 {
|
||||
existingGroupCount, err := scopedStore.store.Count(namespacedGroup)
|
||||
existingGroupCount, err := scopedStore.storeInstance.Count(namespacedGroup)
|
||||
if err != nil {
|
||||
return core.E("store.ScopedStore", "quota check", err)
|
||||
}
|
||||
if existingGroupCount == 0 {
|
||||
// This group is new — check if adding it would exceed the group limit.
|
||||
knownGroupCount := 0
|
||||
for _, iterationErr := range scopedStore.store.GroupsSeq(namespacePrefix) {
|
||||
for _, iterationErr := range scopedStore.storeInstance.GroupsSeq(namespacePrefix) {
|
||||
if iterationErr != nil {
|
||||
return core.E("store.ScopedStore", "quota check", iterationErr)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue