diff --git a/README.md b/README.md index 8dc8ae3..396472c 100644 --- a/README.md +++ b/README.md @@ -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) } }() diff --git a/docs/architecture.md b/docs/architecture.md index 7855936..e8964f2 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -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: ...`). diff --git a/docs/index.md b/docs/index.md index 9cf9c0d..ee40a73 100644 --- a/docs/index.md +++ b/docs/index.md @@ -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() } diff --git a/events.go b/events.go index 7613b42..0350c06 100644 --- a/events.go +++ b/events.go @@ -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() { diff --git a/scope.go b/scope.go index 68f2ff7..ac6083d 100644 --- a/scope.go +++ b/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)) } diff --git a/store.go b/store.go index 1b8b2d2..af43666 100644 --- a/store.go +++ b/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