refactor(store): sharpen AX examples and comments
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
ead99906de
commit
cdc4d5a11d
6 changed files with 27 additions and 57 deletions
|
|
@ -31,15 +31,15 @@ func main() {
|
|||
|
||||
storeInstance.Set("config", "theme", "dark")
|
||||
storeInstance.SetWithTTL("session", "token", "abc123", 24*time.Hour)
|
||||
value, err := storeInstance.Get("config", "theme")
|
||||
fmt.Println(value, err)
|
||||
themeValue, err := storeInstance.Get("config", "theme")
|
||||
fmt.Println(themeValue, err)
|
||||
|
||||
// Watch for mutations
|
||||
watcher := storeInstance.Watch("config", "*")
|
||||
defer storeInstance.Unwatch(watcher)
|
||||
go func() {
|
||||
for event := range watcher.Events {
|
||||
fmt.Println(event.Type, event.Key)
|
||||
fmt.Println(event.Type, event.Group, event.Key, event.Value)
|
||||
}
|
||||
}()
|
||||
|
||||
|
|
|
|||
|
|
@ -103,8 +103,8 @@ Both return `NotFoundError` if the key does not exist or has expired.
|
|||
```go
|
||||
storeInstance.Set("miner", "pool", "pool.lthn.io:3333")
|
||||
storeInstance.Set("miner", "wallet", "iz...")
|
||||
out, _ := storeInstance.Render(`{"pool":"{{ .pool }}","wallet":"{{ .wallet }}"}`, "miner")
|
||||
// out: {"pool":"pool.lthn.io:3333","wallet":"iz..."}
|
||||
renderedTemplate, _ := storeInstance.Render(`{"pool":"{{ .pool }}","wallet":"{{ .wallet }}"}`, "miner")
|
||||
// renderedTemplate: {"pool":"pool.lthn.io:3333","wallet":"iz..."}
|
||||
```
|
||||
|
||||
Template parse errors and execution errors are both returned as wrapped errors with context (e.g., `store.Render: parse: ...` and `store.Render: exec: ...`).
|
||||
|
|
|
|||
|
|
@ -35,21 +35,21 @@ func main() {
|
|||
|
||||
// Basic CRUD
|
||||
storeInstance.Set("config", "theme", "dark")
|
||||
value, _ := storeInstance.Get("config", "theme")
|
||||
core.Println(value) // "dark"
|
||||
themeValue, _ := storeInstance.Get("config", "theme")
|
||||
core.Println(themeValue) // "dark"
|
||||
|
||||
// TTL expiry -- key disappears after the duration elapses
|
||||
storeInstance.SetWithTTL("session", "token", "abc123", 24*time.Hour)
|
||||
|
||||
// Fetch all keys in a group
|
||||
all, _ := storeInstance.GetAll("config")
|
||||
core.Println(all) // map[theme:dark]
|
||||
configEntries, _ := storeInstance.GetAll("config")
|
||||
core.Println(configEntries) // map[theme:dark]
|
||||
|
||||
// Template rendering from stored values
|
||||
storeInstance.Set("mail", "host", "smtp.example.com")
|
||||
storeInstance.Set("mail", "port", "587")
|
||||
out, _ := storeInstance.Render(`{{ .host }}:{{ .port }}`, "mail")
|
||||
core.Println(out) // "smtp.example.com:587"
|
||||
renderedTemplate, _ := storeInstance.Render(`{{ .host }}:{{ .port }}`, "mail")
|
||||
core.Println(renderedTemplate) // "smtp.example.com:587"
|
||||
|
||||
// Namespace isolation for multi-tenant use
|
||||
scopedStore, _ := store.NewScoped(storeInstance, "tenant-42")
|
||||
|
|
@ -66,13 +66,13 @@ func main() {
|
|||
defer storeInstance.Unwatch(watcher)
|
||||
go func() {
|
||||
for event := range watcher.Events {
|
||||
core.Println("event", event.Type, event.Group, event.Key)
|
||||
core.Println("event", event.Type, event.Group, event.Key, event.Value)
|
||||
}
|
||||
}()
|
||||
|
||||
// Or register a synchronous callback
|
||||
unregister := storeInstance.OnChange(func(e store.Event) {
|
||||
core.Println("changed", e.Key)
|
||||
unregister := storeInstance.OnChange(func(event store.Event) {
|
||||
core.Println("changed", event.Group, event.Key, event.Value)
|
||||
})
|
||||
defer unregister()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// EventType labels the mutation stored in Event.Type.
|
||||
// Usage example: `if event.Type == store.EventSet { return }`
|
||||
type EventType int
|
||||
|
||||
|
|
@ -37,7 +36,6 @@ func (t EventType) String() string {
|
|||
}
|
||||
}
|
||||
|
||||
// Event records mutation details sent to watchers and callbacks.
|
||||
// Usage example: `event := store.Event{Type: store.EventSet, Group: "config", Key: "theme", Value: "dark"}`
|
||||
// Usage example: `event := store.Event{Type: store.EventDeleteGroup, Group: "config"}`
|
||||
// EventDeleteGroup leaves Key and Value empty.
|
||||
|
|
@ -49,7 +47,6 @@ type Event struct {
|
|||
Timestamp time.Time
|
||||
}
|
||||
|
||||
// Watcher exposes the buffered event channel for a group/key filter.
|
||||
// Usage example: `watcher := storeInstance.Watch("config", "*"); defer storeInstance.Unwatch(watcher); for event := range watcher.Events { _ = event }`
|
||||
type Watcher struct {
|
||||
// Usage example: `for event := range watcher.Events { _ = event }`
|
||||
|
|
@ -72,7 +69,6 @@ type changeCallbackRegistration struct {
|
|||
// watcherEventBufferCapacity is the capacity of each watcher's buffered channel.
|
||||
const watcherEventBufferCapacity = 16
|
||||
|
||||
// Watch creates a buffered subscription for a group/key filter.
|
||||
// Usage example: `watcher := storeInstance.Watch("config", "*")`
|
||||
// `("*", "*")` matches every mutation and the watcher buffer holds 16 events.
|
||||
func (storeInstance *Store) Watch(group, key string) *Watcher {
|
||||
|
|
@ -92,7 +88,6 @@ func (storeInstance *Store) Watch(group, key string) *Watcher {
|
|||
return watcher
|
||||
}
|
||||
|
||||
// Unwatch removes a watcher and closes its channel.
|
||||
// Usage example: `storeInstance.Unwatch(watcher)`
|
||||
// Safe to call multiple times; subsequent calls are no-ops.
|
||||
func (storeInstance *Store) Unwatch(watcher *Watcher) {
|
||||
|
|
@ -112,7 +107,6 @@ func (storeInstance *Store) Unwatch(watcher *Watcher) {
|
|||
})
|
||||
}
|
||||
|
||||
// OnChange registers a synchronous callback for every mutation.
|
||||
// Usage example: `unregister := storeInstance.OnChange(func(event store.Event) { hub.SendToChannel("store-events", event) }); defer unregister()`
|
||||
// Callbacks run synchronously in the writer goroutine, so keep heavy work out of the handler.
|
||||
func (storeInstance *Store) OnChange(callback func(Event)) func() {
|
||||
|
|
|
|||
25
scope.go
25
scope.go
|
|
@ -52,20 +52,17 @@ func (scopedStore *ScopedStore) namespacedGroup(group string) string {
|
|||
return scopedStore.namespace + ":" + group
|
||||
}
|
||||
|
||||
// Returns the namespace string for this scoped store.
|
||||
// Usage example: `scopedStore, _ := store.NewScoped(storeInstance, "tenant-a"); namespace := scopedStore.Namespace()`
|
||||
func (scopedStore *ScopedStore) Namespace() string {
|
||||
return scopedStore.namespace
|
||||
}
|
||||
|
||||
// Get retrieves a value by group and key within the namespace.
|
||||
// Usage example: `value, err := scopedStore.Get("config", "theme")`
|
||||
// Usage example: `themeValue, err := scopedStore.Get("config", "theme")`
|
||||
func (scopedStore *ScopedStore) Get(group, key string) (string, error) {
|
||||
return scopedStore.storeInstance.Get(scopedStore.namespacedGroup(group), key)
|
||||
}
|
||||
|
||||
// Set stores a value by group and key within the namespace. If quotas are
|
||||
// configured, they are checked before inserting new keys or groups.
|
||||
// Quota checks happen before inserting new keys or groups.
|
||||
// Usage example: `err := scopedStore.Set("config", "theme", "dark")`
|
||||
func (scopedStore *ScopedStore) Set(group, key, value string) error {
|
||||
if err := scopedStore.checkQuota(group, key); err != nil {
|
||||
|
|
@ -74,8 +71,7 @@ func (scopedStore *ScopedStore) Set(group, key, value string) error {
|
|||
return scopedStore.storeInstance.Set(scopedStore.namespacedGroup(group), key, value)
|
||||
}
|
||||
|
||||
// SetWithTTL stores a value with a time-to-live within the namespace. Quota
|
||||
// checks are applied for new keys and groups.
|
||||
// Quota checks happen before inserting new keys or groups, even when the value expires later.
|
||||
// Usage example: `err := scopedStore.SetWithTTL("sessions", "token", "abc", time.Hour)`
|
||||
func (scopedStore *ScopedStore) SetWithTTL(group, key, value string, ttl time.Duration) error {
|
||||
if err := scopedStore.checkQuota(group, key); err != nil {
|
||||
|
|
@ -84,41 +80,32 @@ func (scopedStore *ScopedStore) SetWithTTL(group, key, value string, ttl time.Du
|
|||
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.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.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")`
|
||||
// Usage example: `configEntries, err := scopedStore.GetAll("config")`
|
||||
func (scopedStore *ScopedStore) GetAll(group string) (map[string]string, error) {
|
||||
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") { if err != nil { break }; _ = entry }`
|
||||
func (scopedStore *ScopedStore) All(group string) iter.Seq2[KeyValue, error] {
|
||||
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")`
|
||||
// Usage example: `keyCount, err := scopedStore.Count("config")`
|
||||
func (scopedStore *ScopedStore) Count(group string) (int, error) {
|
||||
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")`
|
||||
// Usage example: `renderedTemplate, err := scopedStore.Render("Hello {{ .name }}", "user")`
|
||||
func (scopedStore *ScopedStore) Render(templateSource, group string) (string, error) {
|
||||
return scopedStore.storeInstance.Render(templateSource, scopedStore.namespacedGroup(group))
|
||||
}
|
||||
|
|
|
|||
25
store.go
25
store.go
|
|
@ -85,7 +85,7 @@ func (storeInstance *Store) Close() error {
|
|||
}
|
||||
|
||||
// Expired keys are lazily removed and treated as not found.
|
||||
// Usage example: `value, err := storeInstance.Get("config", "theme")`
|
||||
// Usage example: `themeValue, err := storeInstance.Get("config", "theme")`
|
||||
func (storeInstance *Store) Get(group, key string) (string, error) {
|
||||
var value string
|
||||
var expiresAt sql.NullInt64
|
||||
|
|
@ -106,7 +106,6 @@ func (storeInstance *Store) Get(group, key string) (string, error) {
|
|||
return value, nil
|
||||
}
|
||||
|
||||
// Overwrites any existing row and clears its expiry.
|
||||
// Usage example: `err := storeInstance.Set("config", "theme", "dark")`
|
||||
func (storeInstance *Store) Set(group, key, value string) error {
|
||||
_, err := storeInstance.database.Exec(
|
||||
|
|
@ -137,7 +136,6 @@ func (storeInstance *Store) SetWithTTL(group, key, value string, ttl time.Durati
|
|||
return nil
|
||||
}
|
||||
|
||||
// Removes a single key from a group.
|
||||
// Usage example: `err := storeInstance.Delete("config", "theme")`
|
||||
func (storeInstance *Store) Delete(group, key string) error {
|
||||
_, err := storeInstance.database.Exec("DELETE FROM "+entriesTableName+" WHERE "+entryGroupColumn+" = ? AND "+entryKeyColumn+" = ?", group, key)
|
||||
|
|
@ -148,8 +146,7 @@ func (storeInstance *Store) Delete(group, key string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Counts live keys only.
|
||||
// Usage example: `count, err := storeInstance.Count("config")`
|
||||
// Usage example: `keyCount, err := storeInstance.Count("config")`
|
||||
func (storeInstance *Store) Count(group string) (int, error) {
|
||||
var count int
|
||||
err := storeInstance.database.QueryRow(
|
||||
|
|
@ -162,7 +159,6 @@ func (storeInstance *Store) Count(group string) (int, error) {
|
|||
return count, nil
|
||||
}
|
||||
|
||||
// Removes all keys from a group.
|
||||
// Usage example: `err := storeInstance.DeleteGroup("cache")`
|
||||
func (storeInstance *Store) DeleteGroup(group string) error {
|
||||
_, err := storeInstance.database.Exec("DELETE FROM "+entriesTableName+" WHERE "+entryGroupColumn+" = ?", group)
|
||||
|
|
@ -173,14 +169,12 @@ func (storeInstance *Store) DeleteGroup(group string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// KeyValue represents a key-value pair.
|
||||
// Usage example: `for entry, err := range storeInstance.All("config") { if err != nil { break }; _ = entry }`
|
||||
type KeyValue struct {
|
||||
Key, Value string
|
||||
}
|
||||
|
||||
// Returns live key-value pairs only.
|
||||
// Usage example: `entries, err := storeInstance.GetAll("config")`
|
||||
// Usage example: `configEntries, err := storeInstance.GetAll("config")`
|
||||
func (storeInstance *Store) GetAll(group string) (map[string]string, error) {
|
||||
entriesByKey := make(map[string]string)
|
||||
for entry, err := range storeInstance.All(group) {
|
||||
|
|
@ -192,7 +186,6 @@ func (storeInstance *Store) GetAll(group string) (map[string]string, error) {
|
|||
return entriesByKey, nil
|
||||
}
|
||||
|
||||
// Returns live key-value pairs only.
|
||||
// Usage example: `for entry, err := range storeInstance.All("config") { if err != nil { break }; _ = entry }`
|
||||
func (storeInstance *Store) All(group string) iter.Seq2[KeyValue, error] {
|
||||
return func(yield func(KeyValue, error) bool) {
|
||||
|
|
@ -244,8 +237,7 @@ func (storeInstance *Store) GetFields(group, key string) (iter.Seq[string], erro
|
|||
return fieldsSeq(value), nil
|
||||
}
|
||||
|
||||
// Renders a Go template from live key-value pairs.
|
||||
// Usage example: `out, err := storeInstance.Render("Hello {{ .name }}", "user")`
|
||||
// Usage example: `renderedTemplate, err := storeInstance.Render("Hello {{ .name }}", "user")`
|
||||
func (storeInstance *Store) Render(templateSource, group string) (string, error) {
|
||||
templateData := make(map[string]string)
|
||||
for entry, err := range storeInstance.All(group) {
|
||||
|
|
@ -266,8 +258,7 @@ func (storeInstance *Store) Render(templateSource, group string) (string, error)
|
|||
return builder.String(), nil
|
||||
}
|
||||
|
||||
// Counts live keys across groups with the given prefix.
|
||||
// Usage example: `count, err := storeInstance.CountAll("tenant-a:")`
|
||||
// Usage example: `tenantKeyCount, err := storeInstance.CountAll("tenant-a:")`
|
||||
func (storeInstance *Store) CountAll(groupPrefix string) (int, error) {
|
||||
var count int
|
||||
var err error
|
||||
|
|
@ -288,8 +279,7 @@ func (storeInstance *Store) CountAll(groupPrefix string) (int, error) {
|
|||
return count, nil
|
||||
}
|
||||
|
||||
// Returns distinct live group names with the given prefix.
|
||||
// Usage example: `groupNames, err := storeInstance.Groups("tenant-a:")`
|
||||
// Usage example: `tenantGroupNames, err := storeInstance.Groups("tenant-a:")`
|
||||
func (storeInstance *Store) Groups(groupPrefix string) ([]string, error) {
|
||||
var groupNames []string
|
||||
for groupName, err := range storeInstance.GroupsSeq(groupPrefix) {
|
||||
|
|
@ -301,8 +291,7 @@ func (storeInstance *Store) Groups(groupPrefix string) ([]string, error) {
|
|||
return groupNames, nil
|
||||
}
|
||||
|
||||
// Returns distinct live group names with the given prefix.
|
||||
// Usage example: `for groupName, err := range storeInstance.GroupsSeq("tenant-a:") { if err != nil { break }; _ = groupName }`
|
||||
// Usage example: `for tenantGroupName, err := range storeInstance.GroupsSeq("tenant-a:") { if err != nil { break }; _ = tenantGroupName }`
|
||||
func (storeInstance *Store) GroupsSeq(groupPrefix string) iter.Seq2[string, error] {
|
||||
return func(yield func(string, error) bool) {
|
||||
var rows *sql.Rows
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue