refactor: tighten store AX documentation
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-04 21:29:27 +00:00
parent cdf3124a40
commit 72eff0d164
6 changed files with 40 additions and 51 deletions

View file

@ -12,12 +12,10 @@ import (
var defaultArchiveOutputDirectory = ".core/archive/"
// CompactOptions archives completed journal rows before a cutoff time to a
// compressed JSONL file.
//
// Usage example: `options := store.CompactOptions{Before: time.Date(2026, 3, 30, 0, 0, 0, 0, time.UTC), Output: "/tmp/archive", Format: "gzip"}`
// The default output directory is `.core/archive/`; the default format is
// `gzip`, and `zstd` is also supported.
// Usage example: `result := storeInstance.Compact(store.CompactOptions{Before: time.Now().Add(-90 * 24 * time.Hour)})`
// Leave `Output` empty to write gzip JSONL archives under `.core/archive/`, or
// set `Format` to `zstd` when downstream tooling expects `.jsonl.zst`.
type CompactOptions struct {
// Usage example: `options := store.CompactOptions{Before: time.Now().Add(-90 * 24 * time.Hour)}`
Before time.Time
@ -32,7 +30,7 @@ func (compactOptions CompactOptions) Normalised() CompactOptions {
if compactOptions.Output == "" {
compactOptions.Output = defaultArchiveOutputDirectory
}
compactOptions.Format = lowerText(core.Trim(compactOptions.Format))
compactOptions.Format = lowercaseText(core.Trim(compactOptions.Format))
if compactOptions.Format == "" {
compactOptions.Format = "gzip"
}
@ -48,7 +46,7 @@ func (compactOptions CompactOptions) Validate() error {
nil,
)
}
switch lowerText(core.Trim(compactOptions.Format)) {
switch lowercaseText(core.Trim(compactOptions.Format)) {
case "", "gzip", "zstd":
return nil
default:
@ -60,7 +58,7 @@ func (compactOptions CompactOptions) Validate() error {
}
}
func lowerText(text string) string {
func lowercaseText(text string) string {
builder := core.NewBuilder()
for _, r := range text {
builder.WriteRune(unicode.ToLower(r))

View file

@ -323,7 +323,7 @@ func parseFluxTime(value string) (time.Time, error) {
if value == "" {
return time.Time{}, core.E("store.parseFluxTime", "range value is empty", nil)
}
value = firstOrEmptyString(core.Split(value, ","))
value = firstStringOrEmpty(core.Split(value, ","))
value = core.Trim(value)
if core.HasPrefix(value, "time(v:") && core.HasSuffix(value, ")") {
value = core.Trim(core.TrimSuffix(core.TrimPrefix(value, "time(v:"), ")"))

View file

@ -60,10 +60,9 @@ func (scopedConfig ScopedStoreConfig) Validate() error {
return nil
}
// ScopedStore prefixes group names with namespace + ":" before delegating to Store.
// Usage example: `scopedStore, err := store.NewScoped(storeInstance, "tenant-a"); if err != nil { return }; if err := scopedStore.Set("colour", "blue"); err != nil { return }`
//
// Usage example: `scopedStore, err := store.NewScoped(storeInstance, "tenant-a"); if err != nil { return }; if err := scopedStore.SetIn("config", "colour", "blue"); err != nil { return }`
// Usage example: `scopedStore, err := store.NewScoped(storeInstance, "tenant-a"); if err != nil { return }`
// Usage example: `if err := scopedStore.Set("colour", "blue"); err != nil { return } // writes tenant-a:default/colour`
// Usage example: `if err := scopedStore.SetIn("config", "colour", "blue"); err != nil { return } // writes tenant-a:config/colour`
type ScopedStore struct {
store *Store
namespace string
@ -82,10 +81,9 @@ type scopedWatcherBridge struct {
done chan struct{}
}
// NewScoped validates a namespace and prefixes groups with namespace + ":".
// Usage example: `scopedStore, err := store.NewScoped(storeInstance, "tenant-a"); if err != nil { return }`
// Prefer `NewScopedConfigured` when the namespace and quota are already known
// as a struct literal.
// Prefer `NewScopedConfigured(storeInstance, store.ScopedStoreConfig{Namespace: "tenant-a"})`
// when the namespace and quota are already known at the call site.
func NewScoped(storeInstance *Store, namespace string) (*ScopedStore, error) {
if storeInstance == nil {
return nil, core.E("store.NewScoped", "store instance is nil", nil)
@ -101,8 +99,9 @@ func NewScoped(storeInstance *Store, namespace string) (*ScopedStore, error) {
return scopedStore, nil
}
// NewScopedConfigured validates the namespace and optional quota settings before constructing a ScopedStore.
// Usage example: `scopedStore, err := store.NewScopedConfigured(storeInstance, store.ScopedStoreConfig{Namespace: "tenant-a", Quota: store.QuotaConfig{MaxKeys: 100, MaxGroups: 10}}); if err != nil { return }`
// This keeps the namespace and quota in one declarative literal instead of an
// option chain.
func NewScopedConfigured(storeInstance *Store, scopedConfig ScopedStoreConfig) (*ScopedStore, error) {
if storeInstance == nil {
return nil, core.E("store.NewScopedConfigured", "store instance is nil", nil)
@ -291,7 +290,7 @@ func (scopedStore *ScopedStore) CountAll(groupPrefix ...string) (int, error) {
if err := scopedStore.ensureReady("store.CountAll"); err != nil {
return 0, err
}
return scopedStore.store.CountAll(scopedStore.namespacedGroup(firstOrEmptyString(groupPrefix)))
return scopedStore.store.CountAll(scopedStore.namespacedGroup(firstStringOrEmpty(groupPrefix)))
}
// Usage example: `groupNames, err := scopedStore.Groups("config")`
@ -300,7 +299,7 @@ func (scopedStore *ScopedStore) Groups(groupPrefix ...string) ([]string, error)
if err := scopedStore.ensureReady("store.Groups"); err != nil {
return nil, err
}
groupNames, err := scopedStore.store.Groups(scopedStore.namespacedGroup(firstOrEmptyString(groupPrefix)))
groupNames, err := scopedStore.store.Groups(scopedStore.namespacedGroup(firstStringOrEmpty(groupPrefix)))
if err != nil {
return nil, err
}
@ -319,7 +318,7 @@ func (scopedStore *ScopedStore) GroupsSeq(groupPrefix ...string) iter.Seq2[strin
return
}
namespacePrefix := scopedStore.namespacePrefix()
for groupName, err := range scopedStore.store.GroupsSeq(scopedStore.namespacedGroup(firstOrEmptyString(groupPrefix))) {
for groupName, err := range scopedStore.store.GroupsSeq(scopedStore.namespacedGroup(firstStringOrEmpty(groupPrefix))) {
if err != nil {
if !yield("", err) {
return
@ -372,8 +371,8 @@ func (scopedStore *ScopedStore) PurgeExpired() (int64, error) {
// Usage example: `events := scopedStore.Watch("config")`
// Usage example: `events := scopedStore.Watch("*")`
// The returned events always use namespace-local group names, so a write to
// `tenant-a:config` is delivered as `config`.
// A write to `tenant-a:config` is delivered back to this scoped watcher as
// `config`, so callers never have to strip the namespace themselves.
func (scopedStore *ScopedStore) Watch(group string) <-chan Event {
if scopedStore == nil || scopedStore.store == nil {
return closedEventChannel()
@ -479,8 +478,8 @@ func (scopedStore *ScopedStore) localiseWatchedEvent(event Event) (Event, bool)
}
// 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`.
// A callback registered on `tenant-a` receives `config` rather than
// `tenant-a:config`.
func (scopedStore *ScopedStore) OnChange(callback func(Event)) func() {
if scopedStore == nil || callback == nil {
return func() {}
@ -668,7 +667,7 @@ func (scopedStoreTransaction *ScopedStoreTransaction) CountAll(groupPrefix ...st
if err := scopedStoreTransaction.ensureReady("store.ScopedStoreTransaction.CountAll"); err != nil {
return 0, err
}
return scopedStoreTransaction.storeTransaction.CountAll(scopedStoreTransaction.scopedStore.namespacedGroup(firstOrEmptyString(groupPrefix)))
return scopedStoreTransaction.storeTransaction.CountAll(scopedStoreTransaction.scopedStore.namespacedGroup(firstStringOrEmpty(groupPrefix)))
}
// Usage example: `groupNames, err := scopedStoreTransaction.Groups("config")`
@ -678,7 +677,7 @@ func (scopedStoreTransaction *ScopedStoreTransaction) Groups(groupPrefix ...stri
return nil, err
}
groupNames, err := scopedStoreTransaction.storeTransaction.Groups(scopedStoreTransaction.scopedStore.namespacedGroup(firstOrEmptyString(groupPrefix)))
groupNames, err := scopedStoreTransaction.storeTransaction.Groups(scopedStoreTransaction.scopedStore.namespacedGroup(firstStringOrEmpty(groupPrefix)))
if err != nil {
return nil, err
}
@ -698,7 +697,7 @@ func (scopedStoreTransaction *ScopedStoreTransaction) GroupsSeq(groupPrefix ...s
}
namespacePrefix := scopedStoreTransaction.scopedStore.namespacePrefix()
for groupName, err := range scopedStoreTransaction.storeTransaction.GroupsSeq(scopedStoreTransaction.scopedStore.namespacedGroup(firstOrEmptyString(groupPrefix))) {
for groupName, err := range scopedStoreTransaction.storeTransaction.GroupsSeq(scopedStoreTransaction.scopedStore.namespacedGroup(firstStringOrEmpty(groupPrefix))) {
if err != nil {
if !yield("", err) {
return

View file

@ -763,7 +763,7 @@ func (storeInstance *Store) Groups(groupPrefix ...string) ([]string, error) {
// Usage example: `for tenantGroupName, err := range storeInstance.GroupsSeq("tenant-a:") { if err != nil { break }; fmt.Println(tenantGroupName) }`
// Usage example: `for groupName, err := range storeInstance.GroupsSeq() { if err != nil { break }; fmt.Println(groupName) }`
func (storeInstance *Store) GroupsSeq(groupPrefix ...string) iter.Seq2[string, error] {
actualGroupPrefix := firstOrEmptyString(groupPrefix)
actualGroupPrefix := firstStringOrEmpty(groupPrefix)
return func(yield func(string, error) bool) {
if err := storeInstance.ensureReady("store.GroupsSeq"); err != nil {
yield("", err)
@ -808,7 +808,7 @@ func (storeInstance *Store) GroupsSeq(groupPrefix ...string) iter.Seq2[string, e
}
}
func firstOrEmptyString(values []string) string {
func firstStringOrEmpty(values []string) string {
if len(values) == 0 {
return ""
}

View file

@ -386,7 +386,7 @@ func (storeTransaction *StoreTransaction) Groups(groupPrefix ...string) ([]strin
// Usage example: `for groupName, err := range transaction.GroupsSeq("tenant-a:") { if err != nil { break }; fmt.Println(groupName) }`
// Usage example: `for groupName, err := range transaction.GroupsSeq() { if err != nil { break }; fmt.Println(groupName) }`
func (storeTransaction *StoreTransaction) GroupsSeq(groupPrefix ...string) iter.Seq2[string, error] {
actualGroupPrefix := firstOrEmptyString(groupPrefix)
actualGroupPrefix := firstStringOrEmpty(groupPrefix)
return func(yield func(string, error) bool) {
if err := storeTransaction.ensureReady("store.Transaction.GroupsSeq"); err != nil {
yield("", err)

View file

@ -33,12 +33,11 @@ FROM workspace_entries`
var defaultWorkspaceStateDirectory = ".core/state/"
// Workspace keeps mutable work-in-progress in a SQLite file such as
// `.core/state/scroll-session.duckdb` until Commit() or Discard() removes it.
//
// Usage example: `workspace, err := storeInstance.NewWorkspace("scroll-session"); if err != nil { return }; defer workspace.Discard()`
//
// Usage example: `workspace, err := storeInstance.NewWorkspace("scroll-session-2026-03-30"); if err != nil { return }; defer workspace.Discard(); _ = workspace.Put("like", map[string]any{"user": "@alice"})`
// Each workspace keeps mutable work-in-progress in a SQLite file such as
// `.core/state/scroll-session.duckdb` until `Commit()` or `Discard()` removes
// it.
type Workspace struct {
name string
store *Store
@ -67,11 +66,10 @@ func (workspace *Workspace) DatabasePath() string {
return workspace.databasePath
}
// Close keeps the workspace file on disk so `RecoverOrphans(".core/state/")`
// can reopen it later.
//
// Usage example: `if err := workspace.Close(); err != nil { return }`
// Usage example: `if err := workspace.Close(); err != nil { return }; orphans := storeInstance.RecoverOrphans(".core/state"); _ = orphans`
// `Close()` keeps the `.duckdb` file on disk so `RecoverOrphans(".core/state")`
// can reopen it after a crash or interrupted agent run.
func (workspace *Workspace) Close() error {
return workspace.closeWithoutRemovingFiles()
}
@ -103,11 +101,9 @@ func (workspace *Workspace) ensureReady(operation string) error {
return nil
}
// NewWorkspace opens a SQLite workspace file such as
// `.core/state/scroll-session-2026-03-30.duckdb` and removes it when the
// workspace is committed or discarded.
//
// Usage example: `workspace, err := storeInstance.NewWorkspace("scroll-session-2026-03-30"); if err != nil { return }; defer workspace.Discard()`
// This creates `.core/state/scroll-session-2026-03-30.duckdb` by default and
// removes it when the workspace is committed or discarded.
func (storeInstance *Store) NewWorkspace(name string) (*Workspace, error) {
if err := storeInstance.ensureReady("store.NewWorkspace"); err != nil {
return nil, err
@ -218,11 +214,9 @@ func workspaceNameFromPath(stateDirectory, databasePath string) string {
return core.TrimSuffix(relativePath, ".duckdb")
}
// RecoverOrphans(".core/state") returns orphaned workspaces such as
// `scroll-session-2026-03-30.duckdb` so callers can inspect Aggregate() and
// choose Commit() or Discard().
//
// Usage example: `orphans := storeInstance.RecoverOrphans(".core/state"); for _, orphanWorkspace := range orphans { fmt.Println(orphanWorkspace.Name(), orphanWorkspace.Aggregate()) }`
// This reopens leftover `.duckdb` files such as `scroll-session-2026-03-30`
// so callers can inspect `Aggregate()` and choose `Commit()` or `Discard()`.
func (storeInstance *Store) RecoverOrphans(stateDirectory string) []*Workspace {
if storeInstance == nil {
return nil
@ -291,10 +285,9 @@ func (workspace *Workspace) Aggregate() map[string]any {
return fields
}
// Commit writes one completed workspace row to the journal and upserts the
// summary entry in `workspace:NAME`.
//
// Usage example: `result := workspace.Commit(); if !result.OK { return }; fmt.Println(result.Value)`
// `Commit()` writes one completed workspace row to the journal, upserts the
// `workspace:NAME/summary` entry, and removes the workspace file.
func (workspace *Workspace) Commit() core.Result {
if err := workspace.ensureReady("store.Workspace.Commit"); err != nil {
return core.Result{Value: err, OK: false}
@ -321,10 +314,9 @@ func (workspace *Workspace) Discard() {
_ = workspace.closeAndRemoveFiles()
}
// Query runs SQL against the workspace buffer and returns rows as
// `[]map[string]any` for ad-hoc inspection.
//
// Usage example: `result := workspace.Query("SELECT entry_kind, COUNT(*) AS count FROM workspace_entries GROUP BY entry_kind")`
// `result.Value` contains `[]map[string]any`, which lets an agent inspect the
// current buffer state without defining extra result types.
func (workspace *Workspace) Query(query string) core.Result {
if err := workspace.ensureReady("store.Workspace.Query"); err != nil {
return core.Result{Value: err, OK: false}