From 407b0963dc2b68d0657c9f58afa8bd7f769945f7 Mon Sep 17 00:00:00 2001 From: Snider Date: Tue, 17 Mar 2026 14:05:30 +0000 Subject: [PATCH 1/4] ci: add Core ecosystem CI workflow with CodeRabbit auto-fix Uses dAppCore/build actions for test, auto-fix on CodeRabbit changes, and auto-merge on CodeRabbit approval. Co-Authored-By: Virgil --- .github/workflows/ci.yml | 54 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4963a87 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,54 @@ +name: CI + +on: + push: + branches: [main, dev] + pull_request: + branches: [main] + pull_request_review: + types: [submitted] + +jobs: + test: + if: github.event_name != 'pull_request_review' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dAppCore/build/actions/build/core@dev + with: + go-version: "1.26" + run-vet: "true" + + auto-fix: + if: > + github.event_name == 'pull_request_review' && + github.event.review.user.login == 'coderabbitai' && + github.event.review.state == 'changes_requested' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.ref }} + fetch-depth: 0 + - uses: dAppCore/build/actions/fix@dev + with: + go-version: "1.26" + + auto-merge: + if: > + github.event_name == 'pull_request_review' && + github.event.review.user.login == 'coderabbitai' && + github.event.review.state == 'approved' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v4 + - name: Merge PR + run: gh pr merge ${{ github.event.pull_request.number }} --merge --delete-branch + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} -- 2.45.3 From 3cd761dc032a77c85aeac2fc580b9494484614d8 Mon Sep 17 00:00:00 2001 From: Snider Date: Tue, 17 Mar 2026 17:48:09 +0000 Subject: [PATCH 2/4] chore: sync dependencies for v0.1.9 Co-Authored-By: Virgil --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 990e9ba..4c0715a 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( forge.lthn.ai/core/go-log v0.0.4 github.com/stretchr/testify v1.11.1 - modernc.org/sqlite v1.46.2 + modernc.org/sqlite v1.47.0 ) require ( diff --git a/go.sum b/go.sum index 9209e57..a7fa33e 100644 --- a/go.sum +++ b/go.sum @@ -63,8 +63,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.46.2 h1:gkXQ6R0+AjxFC/fTDaeIVLbNLNrRoOK7YYVz5BKhTcE= -modernc.org/sqlite v1.46.2/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig= +modernc.org/sqlite v1.47.0 h1:R1XyaNpoW4Et9yly+I2EeX7pBza/w+pmYee/0HJDyKk= +modernc.org/sqlite v1.47.0/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= -- 2.45.3 From 5225c99cd7d8426e99a2696fe2883c1857837a96 Mon Sep 17 00:00:00 2001 From: Snider Date: Sun, 22 Mar 2026 01:27:35 +0000 Subject: [PATCH 3/4] refactor(module): migrate module paths from forge.lthn.ai to dappco.re Update module path to dappco.re/go/core/store, require dappco.re/go/core/log v0.1.0, and update all Go import paths and documentation references. Co-Authored-By: Virgil --- CLAUDE.md | 2 +- README.md | 6 +++--- docs/index.md | 4 ++-- go.mod | 4 ++-- go.sum | 2 -- scope.go | 2 +- store.go | 2 +- 7 files changed, 10 insertions(+), 12 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 004b238..1c7eb40 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## What This Is -SQLite key-value store with TTL, namespace isolation, and reactive events. Pure Go (no CGO). Module: `forge.lthn.ai/core/go-store` +SQLite key-value store with TTL, namespace isolation, and reactive events. Pure Go (no CGO). Module: `dappco.re/go/core/store` ## Getting Started diff --git a/README.md b/README.md index 70d1379..05399b2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Go Reference](https://pkg.go.dev/badge/forge.lthn.ai/core/go-store.svg)](https://pkg.go.dev/forge.lthn.ai/core/go-store) +[![Go Reference](https://pkg.go.dev/badge/dappco.re/go/core/store.svg)](https://pkg.go.dev/dappco.re/go/core/store) [![License: EUPL-1.2](https://img.shields.io/badge/License-EUPL--1.2-blue.svg)](LICENSE.md) [![Go Version](https://img.shields.io/badge/Go-1.26-00ADD8?style=flat&logo=go)](go.mod) @@ -6,14 +6,14 @@ Group-namespaced SQLite key-value store with TTL expiry, namespace isolation, quota enforcement, and a reactive event system. Backed by a pure-Go SQLite driver (no CGO), uses WAL mode for concurrent reads, and enforces a single connection to ensure pragma consistency. Supports scoped stores for multi-tenant use, Watch/Unwatch subscriptions, and OnChange callbacks — the designed integration point for go-ws real-time streaming. -**Module**: `forge.lthn.ai/core/go-store` +**Module**: `dappco.re/go/core/store` **Licence**: EUPL-1.2 **Language**: Go 1.25 ## Quick Start ```go -import "forge.lthn.ai/core/go-store" +import "dappco.re/go/core/store" st, err := store.New("/path/to/store.db") // or store.New(":memory:") defer st.Close() diff --git a/docs/index.md b/docs/index.md index 5d9f6da..7365623 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,7 +9,7 @@ description: Group-namespaced SQLite key-value store with TTL expiry, namespace The package has a single runtime dependency -- a pure-Go SQLite driver (`modernc.org/sqlite`). No CGO is required. It compiles and runs on all platforms that Go supports. -**Module path:** `forge.lthn.ai/core/go-store` +**Module path:** `dappco.re/go/core/store` **Go version:** 1.26+ **Licence:** EUPL-1.2 @@ -22,7 +22,7 @@ import ( "fmt" "time" - "forge.lthn.ai/core/go-store" + "dappco.re/go/core/store" ) func main() { diff --git a/go.mod b/go.mod index 4c0715a..853eac3 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ -module forge.lthn.ai/core/go-store +module dappco.re/go/core/store go 1.26.0 require ( - forge.lthn.ai/core/go-log v0.0.4 + dappco.re/go/core/log v0.1.0 github.com/stretchr/testify v1.11.1 modernc.org/sqlite v1.47.0 ) diff --git a/go.sum b/go.sum index a7fa33e..1cf12e5 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -forge.lthn.ai/core/go-log v0.0.4 h1:KTuCEPgFmuM8KJfnyQ8vPOU1Jg654W74h8IJvfQMfv0= -forge.lthn.ai/core/go-log v0.0.4/go.mod h1:r14MXKOD3LF/sI8XUJQhRk/SZHBE7jAFVuCfgkXoZPw= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/scope.go b/scope.go index f53ebd0..3c529a9 100644 --- a/scope.go +++ b/scope.go @@ -7,7 +7,7 @@ import ( "regexp" "time" - coreerr "forge.lthn.ai/core/go-log" + coreerr "dappco.re/go/core/log" ) // validNamespace matches alphanumeric characters and hyphens (non-empty). diff --git a/store.go b/store.go index d2e0abf..c1f977f 100644 --- a/store.go +++ b/store.go @@ -9,7 +9,7 @@ import ( "text/template" "time" - coreerr "forge.lthn.ai/core/go-log" + coreerr "dappco.re/go/core/log" _ "modernc.org/sqlite" ) -- 2.45.3 From 95bf2700d1ccf559c8ee6eff3fafc41f0d9e4282 Mon Sep 17 00:00:00 2001 From: Virgil Date: Mon, 23 Mar 2026 15:17:03 +0000 Subject: [PATCH 4/4] docs(api): add extracted contract table Co-Authored-By: Virgil --- docs/api-contract.md | 51 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 docs/api-contract.md diff --git a/docs/api-contract.md b/docs/api-contract.md new file mode 100644 index 0000000..e1360d5 --- /dev/null +++ b/docs/api-contract.md @@ -0,0 +1,51 @@ +--- +title: API Contract +description: Exported public API for go-store with signatures, descriptions, and current test coverage status. +--- + +# API Contract + +Coverage is marked `yes` only when the current test suite explicitly exercises the exported item's public contract. Indirect internal use inside another exported method is not counted. + +| Kind | Name | Signature | Description | Test Coverage | +|------|------|-----------|-------------|---------------| +| Type | `Store` | `type Store struct` | Group-namespaced key-value store backed by SQLite. | yes | +| Function | `New` | `func New(dbPath string) (*Store, error)` | Creates a `Store` at the given SQLite path; `":memory:"` is intended for tests. | yes | +| Method | `(*Store).Close` | `func (s *Store) Close() error` | Stops the background purge goroutine and closes the underlying database. | yes | +| Method | `(*Store).Get` | `func (s *Store) Get(group, key string) (string, error)` | Retrieves a value by group and key; expired keys are lazily deleted and treated as not found. | yes | +| Method | `(*Store).Set` | `func (s *Store) Set(group, key, value string) error` | Stores a value by group and key, overwriting any existing value and clearing any expiry. | yes | +| Method | `(*Store).SetWithTTL` | `func (s *Store) SetWithTTL(group, key, value string, ttl time.Duration) error` | Stores a value with a time-to-live; expired keys are later removed lazily and by background purge. | yes | +| Method | `(*Store).Delete` | `func (s *Store) Delete(group, key string) error` | Removes a single key from a group. | yes | +| Method | `(*Store).Count` | `func (s *Store) Count(group string) (int, error)` | Returns the number of non-expired keys in a group. | yes | +| Method | `(*Store).DeleteGroup` | `func (s *Store) DeleteGroup(group string) error` | Removes all keys in a group. | yes | +| Type | `KV` | `type KV struct` | Represents a key-value pair. | no | +| Method | `(*Store).GetAll` | `func (s *Store) GetAll(group string) (map[string]string, error)` | Returns all non-expired key-value pairs in a group. | yes | +| Method | `(*Store).All` | `func (s *Store) All(group string) iter.Seq2[KV, error]` | Returns an iterator over all non-expired key-value pairs in a group. | no | +| Method | `(*Store).GetSplit` | `func (s *Store) GetSplit(group, key, sep string) (iter.Seq[string], error)` | Retrieves a value and returns an iterator over parts split by the supplied separator. | no | +| Method | `(*Store).GetFields` | `func (s *Store) GetFields(group, key string) (iter.Seq[string], error)` | Retrieves a value and returns an iterator over whitespace-separated parts. | no | +| Method | `(*Store).Render` | `func (s *Store) Render(tmplStr, group string) (string, error)` | Loads all non-expired key-value pairs from a group and renders a Go template. | yes | +| Method | `(*Store).CountAll` | `func (s *Store) CountAll(prefix string) (int, error)` | Returns the total number of non-expired keys across groups whose names start with the given prefix. | yes | +| Method | `(*Store).Groups` | `func (s *Store) Groups(prefix string) ([]string, error)` | Returns the distinct group names of all non-expired keys, optionally filtered by prefix. | yes | +| Method | `(*Store).GroupsSeq` | `func (s *Store) GroupsSeq(prefix string) iter.Seq2[string, error]` | Returns an iterator over the distinct group names of all non-expired keys. | no | +| Method | `(*Store).PurgeExpired` | `func (s *Store) PurgeExpired() (int64, error)` | Deletes all expired keys across all groups and returns the number of rows removed. | yes | +| Type | `QuotaConfig` | `type QuotaConfig struct` | Defines optional namespace limits for a `ScopedStore`; zero values mean unlimited. | yes | +| Type | `ScopedStore` | `type ScopedStore struct` | Wraps a `*Store` and auto-prefixes group names with a namespace. | yes | +| Function | `NewScoped` | `func NewScoped(store *Store, namespace string) (*ScopedStore, error)` | Creates a `ScopedStore` with a validated namespace prefix. | yes | +| Function | `NewScopedWithQuota` | `func NewScopedWithQuota(store *Store, namespace string, quota QuotaConfig) (*ScopedStore, error)` | Creates a `ScopedStore` with quota enforcement for new keys and groups. | yes | +| Method | `(*ScopedStore).Namespace` | `func (s *ScopedStore) Namespace() string` | Returns the namespace string for the scoped store. | yes | +| Method | `(*ScopedStore).Get` | `func (s *ScopedStore) Get(group, key string) (string, error)` | Retrieves a value by group and key within the namespace. | yes | +| Method | `(*ScopedStore).Set` | `func (s *ScopedStore) Set(group, key, value string) error` | Stores a value by group and key within the namespace, enforcing quotas for new keys and groups. | yes | +| Method | `(*ScopedStore).SetWithTTL` | `func (s *ScopedStore) SetWithTTL(group, key, value string, ttl time.Duration) error` | Stores a value with a time-to-live within the namespace, enforcing quotas for new keys and groups. | yes | +| Method | `(*ScopedStore).Delete` | `func (s *ScopedStore) Delete(group, key string) error` | Removes a single key from a group within the namespace. | yes | +| Method | `(*ScopedStore).DeleteGroup` | `func (s *ScopedStore) DeleteGroup(group string) error` | Removes all keys in a group within the namespace. | yes | +| Method | `(*ScopedStore).GetAll` | `func (s *ScopedStore) GetAll(group string) (map[string]string, error)` | Returns all non-expired key-value pairs in a group within the namespace. | yes | +| Method | `(*ScopedStore).All` | `func (s *ScopedStore) All(group string) iter.Seq2[KV, error]` | Returns an iterator over all non-expired key-value pairs in a group within the namespace. | no | +| Method | `(*ScopedStore).Count` | `func (s *ScopedStore) Count(group string) (int, error)` | Returns the number of non-expired keys in a group within the namespace. | yes | +| Method | `(*ScopedStore).Render` | `func (s *ScopedStore) Render(tmplStr, group string) (string, error)` | Loads all non-expired key-value pairs from a namespaced group and renders a Go template. | yes | +| Type | `EventType` | `type EventType int` | Describes the kind of store mutation that occurred. | yes | +| Method | `EventType.String` | `func (t EventType) String() string` | Returns a human-readable label for the event type. | yes | +| Type | `Event` | `type Event struct` | Describes a single store mutation; `Key` is empty for group deletes and `Value` is only set for writes. | yes | +| Type | `Watcher` | `type Watcher struct` | Receives events matching a group/key filter. | yes | +| Method | `(*Store).Watch` | `func (s *Store) Watch(group, key string) *Watcher` | Creates a watcher for matching mutations; `"*"` wildcards are supported and events are dropped if the buffer fills. | yes | +| Method | `(*Store).Unwatch` | `func (s *Store) Unwatch(w *Watcher)` | Removes a watcher, closes its channel, and tolerates repeated calls. | yes | +| Method | `(*Store).OnChange` | `func (s *Store) OnChange(fn func(Event)) func()` | Registers a synchronous callback for every mutation and returns an unregister function. | yes | -- 2.45.3