docs(specs): document exported api

This commit is contained in:
Virgil 2026-03-27 20:43:54 +00:00
parent 44bb8b1a8e
commit 0609e4a7b6

131
specs/RFC.md Normal file
View file

@ -0,0 +1,131 @@
# store
**Import:** `dappco.re/go/core/store`
**Files:** 4
`store` provides a SQLite-backed key-value store with group namespaces, TTL expiry, quota-enforced scoped views, and reactive change notifications. The package also exports the sentinel errors `ErrNotFound` and `ErrQuotaExceeded`.
## Types
This package exports structs and one defined integer type. It exports no interfaces or type aliases.
### `EventType`
`type EventType int`
Describes the kind of store mutation that occurred.
Exported constants:
- `EventSet`: a key was created or updated.
- `EventDelete`: a single key was removed.
- `EventDeleteGroup`: all keys in a group were removed.
### `Event`
`type Event struct`
Describes a single store mutation. `Key` is empty for `EventDeleteGroup`. `Value` is only populated for `EventSet`.
Fields:
- `Type EventType`: the mutation kind.
- `Group string`: the group that changed.
- `Key string`: the key that changed, or `""` for group deletion.
- `Value string`: the new value for set events.
- `Timestamp time.Time`: when the event was emitted.
### `Watcher`
`type Watcher struct`
Receives events matching a group/key filter. Create one with `(*Store).Watch` and stop delivery with `(*Store).Unwatch`.
Fields:
- `Ch <-chan Event`: the public read-only event channel consumers select on.
### `KV`
`type KV struct`
Represents a key-value pair yielded by store iterators.
Fields:
- `Key string`: the stored key.
- `Value string`: the stored value.
### `QuotaConfig`
`type QuotaConfig struct`
Defines optional limits for a `ScopedStore` namespace. Zero values mean unlimited.
Fields:
- `MaxKeys int`: maximum total keys across all groups in the namespace.
- `MaxGroups int`: maximum distinct groups in the namespace.
### `ScopedStore`
`type ScopedStore struct`
Wraps a `*Store` and prefixes all group names with a namespace to prevent collisions across tenants. Quotas, when configured, are enforced on new keys and groups.
### `Store`
`type Store struct`
Group-namespaced key-value store backed by SQLite. It owns the SQLite connection, starts a background purge loop for expired entries, and fans out mutation notifications to watchers and change callbacks.
## Functions
### Package functions
| Signature | Description |
| --- | --- |
| `func New(dbPath string) (*Store, error)` | Creates a `Store` at the given SQLite path. `":memory:"` is valid for tests. The implementation opens SQLite, forces a single open connection, enables WAL mode, sets `busy_timeout=5000`, ensures the `kv` table exists, applies the `expires_at` migration if needed, and starts the background expiry purge loop. |
| `func NewScoped(store *Store, namespace string) (*ScopedStore, error)` | Creates a `ScopedStore` that prefixes every group with `namespace:`. The namespace must be non-empty and match `^[a-zA-Z0-9-]+$`. |
| `func NewScopedWithQuota(store *Store, namespace string, quota QuotaConfig) (*ScopedStore, error)` | Creates a `ScopedStore` with the same namespace rules as `NewScoped`, then attaches quota enforcement used by `Set` and `SetWithTTL` for new keys and new groups. |
### `EventType` methods
| Signature | Description |
| --- | --- |
| `func (t EventType) String() string` | Returns a human-readable label for the event type: `set`, `delete`, `delete_group`, or `unknown`. |
### `Store` methods
| Signature | Description |
| --- | --- |
| `func (s *Store) All(group string) iter.Seq2[KV, error]` | Returns an iterator over all non-expired key-value pairs in `group`. Query, scan, and row errors are yielded through the second iterator value. |
| `func (s *Store) Close() error` | Stops the background purge goroutine and closes the underlying database connection. |
| `func (s *Store) Count(group string) (int, error)` | Returns the number of non-expired keys in `group`. |
| `func (s *Store) CountAll(prefix string) (int, error)` | Returns the total number of non-expired keys across all groups whose names start with `prefix`. Passing `""` counts all non-expired keys. Prefix matching is implemented with escaped SQLite `LIKE` patterns. |
| `func (s *Store) Delete(group, key string) error` | Removes a single key from a group and emits an `EventDelete` notification after a successful write. |
| `func (s *Store) DeleteGroup(group string) error` | Removes all keys in a group and emits an `EventDeleteGroup` notification after a successful write. |
| `func (s *Store) Get(group, key string) (string, error)` | Retrieves a value by group and key. Expired entries are treated as missing, are lazily deleted on read, and return `ErrNotFound` wrapped with `store.Get` context. |
| `func (s *Store) GetAll(group string) (map[string]string, error)` | Collects all non-expired key-value pairs in `group` into a `map[string]string` by consuming `All`. |
| `func (s *Store) GetFields(group, key string) (iter.Seq[string], error)` | Retrieves a value and returns an iterator over whitespace-delimited fields. |
| `func (s *Store) GetSplit(group, key, sep string) (iter.Seq[string], error)` | Retrieves a value and returns an iterator over substrings split by `sep`. |
| `func (s *Store) Groups(prefix string) ([]string, error)` | Returns the distinct names of groups containing non-expired keys. If `prefix` is non-empty, only matching group names are returned. |
| `func (s *Store) GroupsSeq(prefix string) iter.Seq2[string, error]` | Returns an iterator over the distinct names of groups containing non-expired keys, optionally filtered by `prefix`. Query, scan, and row errors are yielded through the second iterator value. |
| `func (s *Store) OnChange(fn func(Event)) func()` | Registers a callback invoked on every store mutation. Callbacks run synchronously in the goroutine that performed the write. The returned function unregisters the callback and is idempotent. |
| `func (s *Store) PurgeExpired() (int64, error)` | Deletes all expired keys across all groups and returns the number of removed rows. |
| `func (s *Store) Render(tmplStr, group string) (string, error)` | Loads all non-expired key-value pairs from `group`, parses `tmplStr` with Go's `text/template`, and executes the template with the group's key-value map as data. |
| `func (s *Store) Set(group, key, value string) error` | Inserts or updates a key with no expiry. Existing rows are overwritten and any previous TTL is cleared. A successful write emits an `EventSet`. |
| `func (s *Store) SetWithTTL(group, key, value string, ttl time.Duration) error` | Inserts or updates a key with an expiry time of `time.Now().Add(ttl)`. A successful write emits an `EventSet`. |
| `func (s *Store) Unwatch(w *Watcher)` | Removes a watcher and closes its channel. Calling `Unwatch` more than once for the same watcher is a no-op. |
| `func (s *Store) Watch(group, key string) *Watcher` | Creates a watcher that receives events matching `group` and `key`. `*` acts as a wildcard, the returned channel is buffered to 16 events, and sends are non-blocking, so events are dropped if the consumer falls behind. |
### `ScopedStore` methods
| Signature | Description |
| --- | --- |
| `func (s *ScopedStore) All(group string) iter.Seq2[KV, error]` | Returns the same iterator as `Store.All`, but against the namespace-prefixed group. |
| `func (s *ScopedStore) Count(group string) (int, error)` | Returns the number of non-expired keys in the namespace-prefixed group. |
| `func (s *ScopedStore) Delete(group, key string) error` | Removes a single key from the namespace-prefixed group. |
| `func (s *ScopedStore) DeleteGroup(group string) error` | Removes all keys from the namespace-prefixed group. |
| `func (s *ScopedStore) Get(group, key string) (string, error)` | Retrieves a value from the namespace-prefixed group. |
| `func (s *ScopedStore) GetAll(group string) (map[string]string, error)` | Returns all non-expired key-value pairs from the namespace-prefixed group. |
| `func (s *ScopedStore) Namespace() string` | Returns the namespace string used to prefix groups. |
| `func (s *ScopedStore) Render(tmplStr, group string) (string, error)` | Renders a Go template with the key-value map loaded from the namespace-prefixed group. |
| `func (s *ScopedStore) Set(group, key, value string) error` | Stores a value in the namespace-prefixed group. When quotas are configured, new keys and new groups are checked before writing; upserts bypass the quota limit checks. |
| `func (s *ScopedStore) SetWithTTL(group, key, value string, ttl time.Duration) error` | Stores a TTL-bound value in the namespace-prefixed group with the same quota enforcement rules as `Set`. |