diff --git a/.dockerignore b/.dockerignore index dc3e6bf..9a98d9a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,6 +4,6 @@ .vscode .vagrant dist -docs +website vendor helm diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 114f8c4..a96b313 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -13,18 +13,12 @@ /pkg/ @skyoo2003 /api/ @skyoo2003 -# Root library -/kvs.go @skyoo2003 - # Documentation -/content/ @skyoo2003 -/docs/ @skyoo2003 +/website/ @skyoo2003 # CI/CD and GitHub config /.github/ @skyoo2003 # Build and release -/.goreleaser.yml @skyoo2003 +/build/ @skyoo2003 /Makefile @skyoo2003 -/Dockerfile @skyoo2003 -/Dockerfile.goreleaser @skyoo2003 diff --git a/.github/labeler.yml b/.github/labeler.yml index 1b7e010..08f5e57 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -5,8 +5,7 @@ documentation: - changed-files: - any-glob-to-any-file: - "*.md" - - "docs/**" - - "content/**" + - "website/**" dependencies: - changed-files: @@ -28,4 +27,4 @@ github_actions: docker: - changed-files: - any-glob-to-any-file: - - "Dockerfile*" + - "build/package/Dockerfile*" diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 088e8da..02eb283 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -7,10 +7,10 @@ on: - "**/*.go" - "go.mod" - "go.sum" - - "Dockerfile" - - "Dockerfile.goreleaser" + - "build/package/Dockerfile" + - "build/package/Dockerfile.goreleaser" - ".dockerignore" - - ".goreleaser.yml" + - "build/package/goreleaser.yaml" - ".github/workflows/cd.yml" pull_request: branches: [main] @@ -18,10 +18,10 @@ on: - "**/*.go" - "go.mod" - "go.sum" - - "Dockerfile" - - "Dockerfile.goreleaser" + - "build/package/Dockerfile" + - "build/package/Dockerfile.goreleaser" - ".dockerignore" - - ".goreleaser.yml" + - "build/package/goreleaser.yaml" - ".github/workflows/cd.yml" concurrency: @@ -52,7 +52,7 @@ jobs: - uses: docker/build-push-action@v6 with: context: . - file: Dockerfile + file: build/package/Dockerfile push: ${{ github.event_name == 'push' }} load: ${{ github.event_name == 'pull_request' }} tags: | @@ -90,4 +90,4 @@ jobs: uses: goreleaser/goreleaser-action@v7.2.3 with: version: "~> v2" - args: release --snapshot --clean + args: release --snapshot --clean --config build/package/goreleaser.yaml diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index d5b6528..91d54e9 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -6,10 +6,7 @@ on: - main paths: - ".github/workflows/docs.yaml" - - "hugo.toml" - - "content/**" - - "layouts/**" - - "static/**" + - "website/**" - "README.md" - "CONTRIBUTING.md" pull_request: @@ -17,10 +14,7 @@ on: - main paths: - ".github/workflows/docs.yaml" - - "hugo.toml" - - "content/**" - - "layouts/**" - - "static/**" + - "website/**" - "README.md" - "CONTRIBUTING.md" workflow_dispatch: @@ -52,12 +46,12 @@ jobs: hugo-version: '0.148.2' - name: Build documentation site - run: hugo build --gc --minify + run: hugo --source website build --gc --minify - name: Upload documentation artifact uses: actions/upload-pages-artifact@v5 with: - path: public + path: website/public deploy: if: github.event_name == 'push' && github.ref == 'refs/heads/main' diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f354422..89458b9 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -53,7 +53,7 @@ jobs: uses: goreleaser/goreleaser-action@v7.2.3 with: version: "~> v2" - args: release --clean --release-notes changes/${{ github.ref_name }}.md + args: release --clean --config build/package/goreleaser.yaml --release-notes changes/${{ github.ref_name }}.md env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} HOMEBREW_TAP_TOKEN: ${{ steps.tap_token.outputs.token }} diff --git a/.gitignore b/.gitignore index cc181d8..c48daad 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ Thumbs.db *.swp .vscode/ .idea/ +.serena/ # Go vendor/ @@ -25,6 +26,6 @@ coverage.html dist/ # Hugo -/public/ -/resources/ -/.hugo_build.lock +/website/public/ +/website/resources/ +/website/.hugo_build.lock diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bb8d103..38a0e9c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,18 +12,41 @@ Thanks for your interest in contributing! ``` . -├── kvs.go # Store (Get, Put, Delete), in memory unless Open gives it a log -├── log.go # Append log the keyspace survives a restart through -├── replication.go # The store methods a cluster drives the keyspace through -├── cmd/kvs/ # CLI entrypoint (Cobra) +├── cmd/kvs/ # Thin CLI entrypoint +├── pkg/kvs/ # Public Store library and append log +├── internal/app/kvs/ # Cobra/Viper CLI application ├── internal/server/ # HTTP, gRPC, and RESP server implementations ├── internal/cluster/ # Raft membership, kept out of the library API ├── api/kvsv1/ # Generated protobuf/gRPC code -├── pkg/resp/ # RESP2 wire protocol codec -├── content/ # Hugo documentation site +├── internal/resp/ # Private RESP2 wire protocol codec +├── website/ # Hugo documentation site └── changes/ # Changelog fragments (Changie) ``` +### Project Layout Guidelines + +Follow [golang-standards/project-layout](https://github.com/golang-standards/project-layout) +where it fits this repository. Create directories only when they have a clear purpose; do not +add empty placeholders or a top-level `src/` directory. + +- `cmd//` contains a minimal `main` package that wires arguments, standard streams, and + build metadata into an internal application package. +- `internal/app//` owns CLI composition. `internal/` owns code that external Go modules + must not import, including server, cluster, persistence, and protocol implementation. +- `pkg//` contains libraries intentionally supported for external import. Public KVS + code belongs in `pkg/kvs`; do not add non-public packages under `pkg/`. +- `api/` contains source protocol contracts and generated API bindings. +- `build/package/` contains Docker and release-packaging configuration. Keep tool-required + workflow files and repository-root configuration at their required locations. +- `website/` contains the Hugo project; do not commit its generated `public/` and `resources/` + directories. +- Keep unit tests next to their packages and package-specific Go test fixtures in that package's + `testdata/` directory. + +When relocating code, preserve public import paths and behavior unless a change explicitly +authorizes a breaking API migration. Update imports, build/release automation, ownership rules, +and documentation in the same change. + ## Getting Started 1. **Fork** the repository diff --git a/README.md b/README.md index 5b61283..fe4285e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A key-value store you can run as a server or import as a Go module. -[![CI](https://github.com/skyoo2003/kvs/actions/workflows/ci.yaml/badge.svg)](https://github.com/skyoo2003/kvs/actions/workflows/ci.yaml) [![Go Reference](https://pkg.go.dev/badge/github.com/skyoo2003/kvs.svg)](https://pkg.go.dev/github.com/skyoo2003/kvs) [![Go Report Card](https://goreportcard.com/badge/github.com/skyoo2003/kvs)](https://goreportcard.com/report/github.com/skyoo2003/kvs) +[![CI](https://github.com/skyoo2003/kvs/actions/workflows/ci.yaml/badge.svg)](https://github.com/skyoo2003/kvs/actions/workflows/ci.yaml) [![Go Reference](https://pkg.go.dev/badge/github.com/skyoo2003/kvs/pkg/kvs.svg)](https://pkg.go.dev/github.com/skyoo2003/kvs/pkg/kvs) [![Go Report Card](https://goreportcard.com/badge/github.com/skyoo2003/kvs)](https://goreportcard.com/report/github.com/skyoo2003/kvs) ## Features @@ -13,7 +13,7 @@ A key-value store you can run as a server or import as a Go module. - **Durability** — `--data-dir` appends every change to a log and replays it at startup, so the keyspace survives a restart - **Clustering** — `--raft-addr` joins a Raft cluster, so losing the leader costs an election rather than a person - **Cobra/Viper CLI** — `serve` command with configurable listen addresses -- **Library** — import `github.com/skyoo2003/kvs` directly in Go programs +- **Library** — import `github.com/skyoo2003/kvs/pkg/kvs` in Go programs - **Docker** — container images published to `ghcr.io/skyoo2003/kvs` ## Installation @@ -55,7 +55,7 @@ package main import ( "fmt" - "github.com/skyoo2003/kvs" + "github.com/skyoo2003/kvs/pkg/kvs" ) func main() { diff --git a/Dockerfile b/build/package/Dockerfile similarity index 100% rename from Dockerfile rename to build/package/Dockerfile diff --git a/Dockerfile.goreleaser b/build/package/Dockerfile.goreleaser similarity index 100% rename from Dockerfile.goreleaser rename to build/package/Dockerfile.goreleaser diff --git a/.goreleaser.yml b/build/package/goreleaser.yaml similarity index 98% rename from .goreleaser.yml rename to build/package/goreleaser.yaml index fe3b226..fbc0dcd 100644 --- a/.goreleaser.yml +++ b/build/package/goreleaser.yaml @@ -86,7 +86,7 @@ homebrew_casks: dockers_v2: - images: - ghcr.io/skyoo2003/kvs - dockerfile: Dockerfile.goreleaser + dockerfile: build/package/Dockerfile.goreleaser ids: - kvs platforms: diff --git a/cmd/kvs/main.go b/cmd/kvs/main.go index d7ee5ad..afa92ea 100644 --- a/cmd/kvs/main.go +++ b/cmd/kvs/main.go @@ -1,88 +1,17 @@ -// Package main implements to execute the program. package main import ( "fmt" - "io" "os" - "github.com/spf13/cobra" - "github.com/spf13/viper" + app "github.com/skyoo2003/kvs/internal/app/kvs" ) var version = "dev" func main() { - if err := execute(os.Args[1:], os.Stdout, os.Stderr); err != nil { + if err := app.Execute(os.Args[1:], os.Stdout, os.Stderr, version); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } - -func execute(args []string, stdout, stderr io.Writer) error { - cmd := newRootCmd(stdout, stderr) - cmd.SetArgs(args) - - return cmd.Execute() -} - -func newRootCmd(stdout, stderr io.Writer) *cobra.Command { - var cfgFile string - var showVersion bool - - rootCmd := &cobra.Command{ - Use: "kvs", - Short: "A simple key-value store CLI", - Args: cobra.NoArgs, - SilenceErrors: true, - SilenceUsage: true, - PersistentPreRunE: func(_ *cobra.Command, _ []string) error { - return initConfig(cfgFile) - }, - RunE: func(cmd *cobra.Command, _ []string) error { - if showVersion { - _, err := fmt.Fprintln(cmd.OutOrStdout(), version) - return err - } - - return cmd.Help() - }, - } - - rootCmd.SetOut(stdout) - rootCmd.SetErr(stderr) - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file path") - rootCmd.Flags().BoolVarP(&showVersion, "version", "v", false, "print version") - rootCmd.AddCommand(newVersionCmd()) - rootCmd.AddCommand(newServeCmd()) - - return rootCmd -} - -func newVersionCmd() *cobra.Command { - return &cobra.Command{ - Use: "version", - Short: "Print the CLI version", - SilenceErrors: true, - SilenceUsage: true, - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, _ []string) error { - _, err := fmt.Fprintln(cmd.OutOrStdout(), version) - return err - }, - } -} - -func initConfig(cfgFile string) error { - viper.Reset() - viper.SetEnvPrefix("KVS") - viper.AutomaticEnv() - - if cfgFile == "" { - return nil - } - - viper.SetConfigFile(cfgFile) - - return viper.ReadInConfig() -} diff --git a/internal/app/kvs/main.go b/internal/app/kvs/main.go new file mode 100644 index 0000000..85cc392 --- /dev/null +++ b/internal/app/kvs/main.go @@ -0,0 +1,79 @@ +// Package app implements the KVS command-line application. +package app + +import ( + "fmt" + "io" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// Execute runs the CLI with the supplied program arguments and build version. +func Execute(args []string, stdout, stderr io.Writer, version string) error { + cmd := newRootCmd(stdout, stderr, version) + cmd.SetArgs(args) + + return cmd.Execute() +} + +func newRootCmd(stdout, stderr io.Writer, version string) *cobra.Command { + var cfgFile string + var showVersion bool + + rootCmd := &cobra.Command{ + Use: "kvs", + Short: "A simple key-value store CLI", + Args: cobra.NoArgs, + SilenceErrors: true, + SilenceUsage: true, + PersistentPreRunE: func(_ *cobra.Command, _ []string) error { + return initConfig(cfgFile) + }, + RunE: func(cmd *cobra.Command, _ []string) error { + if showVersion { + _, err := fmt.Fprintln(cmd.OutOrStdout(), version) + return err + } + + return cmd.Help() + }, + } + + rootCmd.SetOut(stdout) + rootCmd.SetErr(stderr) + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file path") + rootCmd.Flags().BoolVarP(&showVersion, "version", "v", false, "print version") + rootCmd.AddCommand(newVersionCmd(version)) + rootCmd.AddCommand(newServeCmd()) + + return rootCmd +} + +func newVersionCmd(version string) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "Print the CLI version", + SilenceErrors: true, + SilenceUsage: true, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + _, err := fmt.Fprintln(cmd.OutOrStdout(), version) + return err + }, + } +} + +func initConfig(cfgFile string) error { + viper.Reset() + viper.SetEnvPrefix("KVS") + viper.AutomaticEnv() + + if cfgFile == "" { + return nil + } + + viper.SetConfigFile(cfgFile) + + return viper.ReadInConfig() +} diff --git a/cmd/kvs/main_test.go b/internal/app/kvs/main_test.go similarity index 97% rename from cmd/kvs/main_test.go rename to internal/app/kvs/main_test.go index 192dfe4..7755f31 100644 --- a/cmd/kvs/main_test.go +++ b/internal/app/kvs/main_test.go @@ -1,4 +1,4 @@ -package main +package app import ( "bytes" @@ -12,6 +12,8 @@ import ( const testVersionOutput = "1.2.3\n" +var version = "dev" + func TestExecuteShowsHelpByDefault(t *testing.T) { out, errOut, err := runCLI(t) if err != nil { @@ -96,7 +98,7 @@ func runCLI(t *testing.T, args ...string) (stdout, stderr string, err error) { var stdoutBuf bytes.Buffer var stderrBuf bytes.Buffer - err = execute(args, &stdoutBuf, &stderrBuf) + err = Execute(args, &stdoutBuf, &stderrBuf, version) return stdoutBuf.String(), stderrBuf.String(), err } diff --git a/cmd/kvs/serve.go b/internal/app/kvs/serve.go similarity index 99% rename from cmd/kvs/serve.go rename to internal/app/kvs/serve.go index 934c23e..5601c82 100644 --- a/cmd/kvs/serve.go +++ b/internal/app/kvs/serve.go @@ -1,4 +1,4 @@ -package main +package app import ( "os" diff --git a/cmd/kvs/serve_test.go b/internal/app/kvs/serve_test.go similarity index 99% rename from cmd/kvs/serve_test.go rename to internal/app/kvs/serve_test.go index d32256d..5718c0e 100644 --- a/cmd/kvs/serve_test.go +++ b/internal/app/kvs/serve_test.go @@ -1,4 +1,4 @@ -package main +package app import ( "path/filepath" diff --git a/internal/cluster/cluster.go b/internal/cluster/cluster.go index 95ee35d..d900083 100644 --- a/internal/cluster/cluster.go +++ b/internal/cluster/cluster.go @@ -21,8 +21,8 @@ import ( "github.com/hashicorp/raft" raftboltdb "github.com/hashicorp/raft-boltdb/v2" - "github.com/skyoo2003/kvs" "github.com/skyoo2003/kvs/internal/datadir" + "github.com/skyoo2003/kvs/pkg/kvs" ) const ( diff --git a/internal/cluster/cluster_test.go b/internal/cluster/cluster_test.go index 38f72a9..0d1dc0e 100644 --- a/internal/cluster/cluster_test.go +++ b/internal/cluster/cluster_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/skyoo2003/kvs" + "github.com/skyoo2003/kvs/pkg/kvs" ) // The milestone in one test: the leader goes away, nobody does anything, and writes come back. diff --git a/internal/cluster/soak_test.go b/internal/cluster/soak_test.go index 7a52a33..92e6806 100644 --- a/internal/cluster/soak_test.go +++ b/internal/cluster/soak_test.go @@ -10,8 +10,8 @@ import ( "testing" "time" - "github.com/skyoo2003/kvs" "github.com/skyoo2003/kvs/internal/datadir" + "github.com/skyoo2003/kvs/pkg/kvs" ) // soakFor turns the long run on and says how long it lasts. Zero, the default, skips it, so diff --git a/pkg/resp/reader.go b/internal/resp/reader.go similarity index 98% rename from pkg/resp/reader.go rename to internal/resp/reader.go index 1b99414..e1b9b65 100644 --- a/pkg/resp/reader.go +++ b/internal/resp/reader.go @@ -2,7 +2,7 @@ // // It exists for the server in this module to speak that protocol, not as a RESP library for // other programs, and is outside the v1 compatibility promise: see -// content/docs/compatibility.md. The protocol kvs answers on the wire is promised; this Go +// website/content/docs/compatibility.md. The protocol kvs answers on the wire is promised; this Go // package is not. package resp diff --git a/pkg/resp/reader_test.go b/internal/resp/reader_test.go similarity index 100% rename from pkg/resp/reader_test.go rename to internal/resp/reader_test.go diff --git a/pkg/resp/writer.go b/internal/resp/writer.go similarity index 100% rename from pkg/resp/writer.go rename to internal/resp/writer.go diff --git a/pkg/resp/writer_test.go b/internal/resp/writer_test.go similarity index 100% rename from pkg/resp/writer_test.go rename to internal/resp/writer_test.go diff --git a/internal/server/grpc.go b/internal/server/grpc.go index 9eb1f06..fea7477 100644 --- a/internal/server/grpc.go +++ b/internal/server/grpc.go @@ -10,8 +10,8 @@ import ( grpchealthv1 "google.golang.org/grpc/health/grpc_health_v1" "google.golang.org/grpc/status" - "github.com/skyoo2003/kvs" "github.com/skyoo2003/kvs/api/kvsv1" + "github.com/skyoo2003/kvs/pkg/kvs" ) type kvStoreServer struct { diff --git a/internal/server/grpc_test.go b/internal/server/grpc_test.go index a8f7007..2c49d98 100644 --- a/internal/server/grpc_test.go +++ b/internal/server/grpc_test.go @@ -12,8 +12,8 @@ import ( "google.golang.org/grpc/status" "google.golang.org/grpc/test/bufconn" - "github.com/skyoo2003/kvs" "github.com/skyoo2003/kvs/api/kvsv1" + "github.com/skyoo2003/kvs/pkg/kvs" ) func TestGRPCServerPutGetDelete(t *testing.T) { diff --git a/internal/server/http.go b/internal/server/http.go index 8e7576c..df2e416 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -7,7 +7,7 @@ import ( "net/http" "strings" - "github.com/skyoo2003/kvs" + "github.com/skyoo2003/kvs/pkg/kvs" ) type httpHandler struct { diff --git a/internal/server/http_test.go b/internal/server/http_test.go index c514b34..91ef29f 100644 --- a/internal/server/http_test.go +++ b/internal/server/http_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - "github.com/skyoo2003/kvs" + "github.com/skyoo2003/kvs/pkg/kvs" ) func newTestRequest(t *testing.T, method, target string, body io.Reader) *http.Request { diff --git a/internal/server/integration_test.go b/internal/server/integration_test.go index 08c44dd..02bc7aa 100644 --- a/internal/server/integration_test.go +++ b/internal/server/integration_test.go @@ -14,8 +14,8 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" - "github.com/skyoo2003/kvs" "github.com/skyoo2003/kvs/api/kvsv1" + "github.com/skyoo2003/kvs/pkg/kvs" ) func TestRunListenersSharesStoreAcrossHTTPAndGRPC(t *testing.T) { diff --git a/internal/server/resp.go b/internal/server/resp.go index 1b76683..f760139 100644 --- a/internal/server/resp.go +++ b/internal/server/resp.go @@ -14,8 +14,8 @@ import ( "sync/atomic" "time" - "github.com/skyoo2003/kvs" - "github.com/skyoo2003/kvs/pkg/resp" + "github.com/skyoo2003/kvs/internal/resp" + "github.com/skyoo2003/kvs/pkg/kvs" ) const ( diff --git a/internal/server/resp_client_test.go b/internal/server/resp_client_test.go index 1070dca..c1fa739 100644 --- a/internal/server/resp_client_test.go +++ b/internal/server/resp_client_test.go @@ -8,7 +8,7 @@ import ( "github.com/redis/go-redis/v9" - "github.com/skyoo2003/kvs" + "github.com/skyoo2003/kvs/pkg/kvs" ) // newGoRedisClient starts a RESP server and connects a real client library to it. Hand diff --git a/internal/server/resp_cluster.go b/internal/server/resp_cluster.go index cb85209..53b7259 100644 --- a/internal/server/resp_cluster.go +++ b/internal/server/resp_cluster.go @@ -9,7 +9,7 @@ import ( "strconv" "time" - "github.com/skyoo2003/kvs" + "github.com/skyoo2003/kvs/pkg/kvs" ) const ( diff --git a/internal/server/resp_cluster_test.go b/internal/server/resp_cluster_test.go index d1524fa..d09e8ac 100644 --- a/internal/server/resp_cluster_test.go +++ b/internal/server/resp_cluster_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/skyoo2003/kvs" + "github.com/skyoo2003/kvs/pkg/kvs" ) // fakeCluster stands in for a real membership so that what a node reports about itself can be diff --git a/internal/server/resp_codec.go b/internal/server/resp_codec.go index 18a8967..8b41e3d 100644 --- a/internal/server/resp_codec.go +++ b/internal/server/resp_codec.go @@ -6,7 +6,7 @@ import ( "maps" "slices" - "github.com/skyoo2003/kvs" + "github.com/skyoo2003/kvs/pkg/kvs" ) // respCodec persists the value types the RESP commands store. It lives here rather than beside diff --git a/internal/server/resp_codec_test.go b/internal/server/resp_codec_test.go index 2ed4af1..903aa2b 100644 --- a/internal/server/resp_codec_test.go +++ b/internal/server/resp_codec_test.go @@ -11,7 +11,7 @@ import ( "github.com/redis/go-redis/v9" - "github.com/skyoo2003/kvs" + "github.com/skyoo2003/kvs/pkg/kvs" ) func TestRESPCodecRoundTrip(t *testing.T) { diff --git a/internal/server/resp_collections_test.go b/internal/server/resp_collections_test.go index ccab1d1..5f1165c 100644 --- a/internal/server/resp_collections_test.go +++ b/internal/server/resp_collections_test.go @@ -5,7 +5,7 @@ import ( "slices" "testing" - "github.com/skyoo2003/kvs" + "github.com/skyoo2003/kvs/pkg/kvs" ) func TestRESPHashCommands(t *testing.T) { diff --git a/internal/server/resp_commands.go b/internal/server/resp_commands.go index c3e02cc..104bc83 100644 --- a/internal/server/resp_commands.go +++ b/internal/server/resp_commands.go @@ -8,8 +8,8 @@ import ( "strings" "sync" - "github.com/skyoo2003/kvs" - "github.com/skyoo2003/kvs/pkg/resp" + "github.com/skyoo2003/kvs/internal/resp" + "github.com/skyoo2003/kvs/pkg/kvs" ) const ( diff --git a/internal/server/resp_deque_test.go b/internal/server/resp_deque_test.go index a72d0f1..d449f8b 100644 --- a/internal/server/resp_deque_test.go +++ b/internal/server/resp_deque_test.go @@ -6,7 +6,7 @@ import ( "sync" "testing" - "github.com/skyoo2003/kvs" + "github.com/skyoo2003/kvs/pkg/kvs" ) func TestRespListBothEnds(t *testing.T) { diff --git a/internal/server/resp_hash.go b/internal/server/resp_hash.go index bb5a991..8abf556 100644 --- a/internal/server/resp_hash.go +++ b/internal/server/resp_hash.go @@ -5,7 +5,7 @@ import ( "slices" "strconv" - "github.com/skyoo2003/kvs" + "github.com/skyoo2003/kvs/pkg/kvs" ) // respWriteHash loads the hash at key for a mutating command, returning an empty hash when diff --git a/internal/server/resp_keyspace.go b/internal/server/resp_keyspace.go index 3fb036d..ebc1086 100644 --- a/internal/server/resp_keyspace.go +++ b/internal/server/resp_keyspace.go @@ -8,7 +8,7 @@ import ( "sync" "time" - "github.com/skyoo2003/kvs" + "github.com/skyoo2003/kvs/pkg/kvs" ) const ( diff --git a/internal/server/resp_keyspace_test.go b/internal/server/resp_keyspace_test.go index 298d354..00187ad 100644 --- a/internal/server/resp_keyspace_test.go +++ b/internal/server/resp_keyspace_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/skyoo2003/kvs" + "github.com/skyoo2003/kvs/pkg/kvs" ) func TestRESPStringCommands(t *testing.T) { diff --git a/internal/server/resp_list.go b/internal/server/resp_list.go index ac3acc6..dd04192 100644 --- a/internal/server/resp_list.go +++ b/internal/server/resp_list.go @@ -4,7 +4,7 @@ import ( "math" "strconv" - "github.com/skyoo2003/kvs" + "github.com/skyoo2003/kvs/pkg/kvs" ) // cmdPush handles LPUSH, RPUSH, LPUSHX, and RPUSHX. diff --git a/internal/server/resp_multi.go b/internal/server/resp_multi.go index 165f876..5336d92 100644 --- a/internal/server/resp_multi.go +++ b/internal/server/resp_multi.go @@ -3,8 +3,8 @@ package server import ( "bytes" - "github.com/skyoo2003/kvs" - "github.com/skyoo2003/kvs/pkg/resp" + "github.com/skyoo2003/kvs/internal/resp" + "github.com/skyoo2003/kvs/pkg/kvs" ) // respPreAuthCommands may run before AUTH succeeds, so that a client can authenticate and diff --git a/internal/server/resp_multi_test.go b/internal/server/resp_multi_test.go index bcf9477..6d1f569 100644 --- a/internal/server/resp_multi_test.go +++ b/internal/server/resp_multi_test.go @@ -5,7 +5,7 @@ import ( "sync" "testing" - "github.com/skyoo2003/kvs" + "github.com/skyoo2003/kvs/pkg/kvs" ) func TestRESPTransactionQueuesAndExecutes(t *testing.T) { diff --git a/internal/server/resp_peer.go b/internal/server/resp_peer.go index 8647e91..06b1b22 100644 --- a/internal/server/resp_peer.go +++ b/internal/server/resp_peer.go @@ -7,13 +7,13 @@ import ( "strings" "time" - "github.com/skyoo2003/kvs/pkg/resp" + "github.com/skyoo2003/kvs/internal/resp" ) // respPeerTimeout bounds a conversation with another node. Nothing here waits on a person. const respPeerTimeout = 10 * time.Second -// peerConn talks to another kvs node over RESP. pkg/resp reads client commands, which is the +// peerConn talks to another kvs node over RESP. internal/resp reads client commands, which is the // other half of the conversation, so a client reading replies needs this much of its own. type peerConn struct { conn net.Conn diff --git a/internal/server/resp_pubsub_test.go b/internal/server/resp_pubsub_test.go index a12bd42..ba1c547 100644 --- a/internal/server/resp_pubsub_test.go +++ b/internal/server/resp_pubsub_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/skyoo2003/kvs" + "github.com/skyoo2003/kvs/pkg/kvs" ) // newRESPClients opens count clients against one server, which is what pub/sub needs. diff --git a/internal/server/resp_script.go b/internal/server/resp_script.go index 0231959..89432c3 100644 --- a/internal/server/resp_script.go +++ b/internal/server/resp_script.go @@ -15,8 +15,8 @@ import ( lua "github.com/yuin/gopher-lua" - "github.com/skyoo2003/kvs" - "github.com/skyoo2003/kvs/pkg/resp" + "github.com/skyoo2003/kvs/internal/resp" + "github.com/skyoo2003/kvs/pkg/kvs" ) const ( diff --git a/internal/server/resp_script_test.go b/internal/server/resp_script_test.go index 65c622c..18ded18 100644 --- a/internal/server/resp_script_test.go +++ b/internal/server/resp_script_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/skyoo2003/kvs" + "github.com/skyoo2003/kvs/pkg/kvs" ) func TestRESPEvalConvertsReturnValues(t *testing.T) { diff --git a/internal/server/resp_set.go b/internal/server/resp_set.go index c5d7e60..2531f5a 100644 --- a/internal/server/resp_set.go +++ b/internal/server/resp_set.go @@ -6,7 +6,7 @@ import ( "slices" "strconv" - "github.com/skyoo2003/kvs" + "github.com/skyoo2003/kvs/pkg/kvs" ) func (c *respConn) cmdSAdd(args [][]byte) error { diff --git a/internal/server/resp_string.go b/internal/server/resp_string.go index 1b3d53a..882963a 100644 --- a/internal/server/resp_string.go +++ b/internal/server/resp_string.go @@ -7,8 +7,8 @@ import ( "strings" "time" - "github.com/skyoo2003/kvs" - "github.com/skyoo2003/kvs/pkg/resp" + "github.com/skyoo2003/kvs/internal/resp" + "github.com/skyoo2003/kvs/pkg/kvs" ) const ( diff --git a/internal/server/resp_test.go b/internal/server/resp_test.go index 125878d..e9c60a3 100644 --- a/internal/server/resp_test.go +++ b/internal/server/resp_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "github.com/skyoo2003/kvs" + "github.com/skyoo2003/kvs/pkg/kvs" ) // respClient is a raw protocol client, so that the tests assert the bytes on the wire diff --git a/internal/server/resp_value.go b/internal/server/resp_value.go index 3c4c8b6..53b71cf 100644 --- a/internal/server/resp_value.go +++ b/internal/server/resp_value.go @@ -3,7 +3,7 @@ package server import ( "maps" - "github.com/skyoo2003/kvs" + "github.com/skyoo2003/kvs/pkg/kvs" ) // Redis type names as TYPE reports them. The dynamic type of a stored value is what gives a diff --git a/internal/server/resp_zset.go b/internal/server/resp_zset.go index fd96eeb..103040b 100644 --- a/internal/server/resp_zset.go +++ b/internal/server/resp_zset.go @@ -7,7 +7,7 @@ import ( "strconv" "strings" - "github.com/skyoo2003/kvs" + "github.com/skyoo2003/kvs/pkg/kvs" ) const respOptWithScores = "WITHSCORES" diff --git a/internal/server/run.go b/internal/server/run.go index 55724a7..bb7bcf4 100644 --- a/internal/server/run.go +++ b/internal/server/run.go @@ -9,8 +9,8 @@ import ( "net/http" "time" - "github.com/skyoo2003/kvs" "github.com/skyoo2003/kvs/internal/cluster" + "github.com/skyoo2003/kvs/pkg/kvs" ) // Listeners carries the bound listeners RunListeners serves. A nil field disables that diff --git a/api_surface_test.go b/pkg/kvs/api_surface_test.go similarity index 94% rename from api_surface_test.go rename to pkg/kvs/api_surface_test.go index e6e3418..03eee1a 100644 --- a/api_surface_test.go +++ b/pkg/kvs/api_surface_test.go @@ -14,14 +14,14 @@ import ( ) // goldenPath holds the exported surface of this package. It is the machine-readable half of -// content/docs/compatibility.md: the page says what is promised, this file says what is there. +// website/content/docs/compatibility.md: the page says what is promised, this file says what is there. const goldenPath = "testdata/api-surface.txt" // headerSep ends the human-facing preamble of the golden file. Everything after it is the // surface itself, so the warning can be reworded without touching the comparison. const headerSep = "# ---\n" -const surfaceHeader = `# Exported API surface of this package - see content/docs/compatibility.md +const surfaceHeader = `# Exported API surface of this package - see website/content/docs/compatibility.md # for how much of it v1 promises, and for the cluster plumbing it exempts by name. # A line changed or removed below is a breaking change and needs a major version, unless the # page exempts it. A line added is a new promise: it cannot be taken back within v1. @@ -64,8 +64,8 @@ func TestPublicAPISurface(t *testing.T) { if got != want { t.Errorf("exported API surface changed.\n%s\n\n"+ "A changed or removed line is a breaking change unless compatibility.md exempts it;\n"+ - "an added line is a new promise. See content/docs/compatibility.md. If deliberate:\n"+ - " go test -run TestPublicAPISurface . -update", + "an added line is a new promise. See website/content/docs/compatibility.md. If deliberate:\n"+ + " go test ./pkg/kvs -run TestPublicAPISurface -update", firstDifference(want, got)) } } diff --git a/kvs.go b/pkg/kvs/kvs.go similarity index 100% rename from kvs.go rename to pkg/kvs/kvs.go diff --git a/kvs_export_test.go b/pkg/kvs/kvs_export_test.go similarity index 100% rename from kvs_export_test.go rename to pkg/kvs/kvs_export_test.go diff --git a/kvs_test.go b/pkg/kvs/kvs_test.go similarity index 100% rename from kvs_test.go rename to pkg/kvs/kvs_test.go diff --git a/log.go b/pkg/kvs/log.go similarity index 100% rename from log.go rename to pkg/kvs/log.go diff --git a/log_test.go b/pkg/kvs/log_test.go similarity index 100% rename from log_test.go rename to pkg/kvs/log_test.go diff --git a/replication.go b/pkg/kvs/replication.go similarity index 93% rename from replication.go rename to pkg/kvs/replication.go index 9ece8af..00518f3 100644 --- a/replication.go +++ b/pkg/kvs/replication.go @@ -35,7 +35,7 @@ func (e *NotLeaderError) Is(target error) bool { // understanding that it is wired up once at startup. // // Exported for internal/cluster to reach across the package boundary, and outside the v1 -// compatibility promise: see content/docs/compatibility.md. +// compatibility promise: see website/content/docs/compatibility.md. func (s *Store) SetReplicator(replicate func(fn func(tx *Tx) error) error) { s.mu.Lock() defer s.mu.Unlock() @@ -65,7 +65,7 @@ func (s *Store) SetCodec(codec Codec) { // node held before is worth keeping only until it arrives. // // Exported for internal/cluster to reach across the package boundary, and outside the v1 -// compatibility promise: see content/docs/compatibility.md. +// compatibility promise: see website/content/docs/compatibility.md. func (s *Store) ReplaceWith(snapshot [][]byte) error { s.mu.Lock() defer s.mu.Unlock() @@ -82,7 +82,7 @@ func (s *Store) ReplaceWith(snapshot [][]byte) error { // atomic on every node. // // Exported for internal/cluster to reach across the package boundary, and outside the v1 -// compatibility promise: see content/docs/compatibility.md. +// compatibility promise: see website/content/docs/compatibility.md. func (s *Store) ApplyReplicated(lines [][]byte) error { s.mu.Lock() defer s.mu.Unlock() diff --git a/replication_test.go b/pkg/kvs/replication_test.go similarity index 100% rename from replication_test.go rename to pkg/kvs/replication_test.go diff --git a/soak_test.go b/pkg/kvs/soak_test.go similarity index 100% rename from soak_test.go rename to pkg/kvs/soak_test.go diff --git a/speculate_test.go b/pkg/kvs/speculate_test.go similarity index 100% rename from speculate_test.go rename to pkg/kvs/speculate_test.go diff --git a/testdata/api-surface.txt b/pkg/kvs/testdata/api-surface.txt similarity index 94% rename from testdata/api-surface.txt rename to pkg/kvs/testdata/api-surface.txt index ffc008b..1d5b793 100644 --- a/testdata/api-surface.txt +++ b/pkg/kvs/testdata/api-surface.txt @@ -1,8 +1,8 @@ -# Exported API surface of this package - see content/docs/compatibility.md +# Exported API surface of this package - see website/content/docs/compatibility.md # for how much of it v1 promises, and for the cluster plumbing it exempts by name. # A line changed or removed below is a breaking change and needs a major version, unless the # page exempts it. A line added is a new promise: it cannot be taken back within v1. -# Regenerate deliberately: go test -run TestPublicAPISurface . -update +# Regenerate deliberately: go test ./pkg/kvs -run TestPublicAPISurface -update # --- ==== kvs.go ==== package kvs diff --git a/docs/.gitignore b/website/.gitignore similarity index 100% rename from docs/.gitignore rename to website/.gitignore diff --git a/content/_index.md b/website/content/_index.md similarity index 100% rename from content/_index.md rename to website/content/_index.md diff --git a/content/docs/_index.md b/website/content/docs/_index.md similarity index 100% rename from content/docs/_index.md rename to website/content/docs/_index.md diff --git a/content/docs/cli.md b/website/content/docs/cli.md similarity index 99% rename from content/docs/cli.md rename to website/content/docs/cli.md index 417dddb..15fb8d2 100644 --- a/content/docs/cli.md +++ b/website/content/docs/cli.md @@ -84,4 +84,4 @@ $ kvs -v - [HTTP API](../http-api/) — REST endpoint details - [Redis API](../redis-api/) — supported RESP commands and behaviour notes - [Durability and Clustering](../clustering/) — what `--data-dir` and `--raft-addr` promise -- [Go package reference](https://pkg.go.dev/github.com/skyoo2003/kvs) +- [Go package reference](https://pkg.go.dev/github.com/skyoo2003/kvs/pkg/kvs) diff --git a/content/docs/clustering.md b/website/content/docs/clustering.md similarity index 100% rename from content/docs/clustering.md rename to website/content/docs/clustering.md diff --git a/content/docs/compatibility.md b/website/content/docs/compatibility.md similarity index 91% rename from content/docs/compatibility.md rename to website/content/docs/compatibility.md index 903e101..fb07827 100644 --- a/content/docs/compatibility.md +++ b/website/content/docs/compatibility.md @@ -46,7 +46,9 @@ New flags may be added. Existing ones will not change under you. ### The Go library -Importing `github.com/skyoo2003/kvs` gets you: +Importing `github.com/skyoo2003/kvs/pkg/kvs` gets you: + +The former module-root library path is no longer provided; update imports to this package path. - `Store` and its constructors `NewStore` and `Open`, with `Get`, `Put`, `Delete`, `Read`, `Write`, `Snapshot`, `Speculate`, `Watch`, `SetCodec`, and `Close` @@ -55,11 +57,11 @@ Importing `github.com/skyoo2003/kvs` gets you: - The sentinel errors `ErrKeyNotFound`, `ErrNoCodec`, `ErrUnsupportedValue`, and `ErrNotLeader` with `NotLeaderError` -The exact signatures live in [`testdata/api-surface.txt`][surface], which is generated from +The exact signatures live in [`pkg/kvs/testdata/api-surface.txt`][surface], which is generated from the source and compared against it by a test on every run. Nothing can join or leave that file without the change showing up in review. -[surface]: https://github.com/skyoo2003/kvs/blob/main/testdata/api-surface.txt +[surface]: https://github.com/skyoo2003/kvs/blob/main/pkg/kvs/testdata/api-surface.txt ## What v1 does not cover @@ -67,9 +69,9 @@ without the change showing up in review. exported so `internal/cluster` can reach them across the package boundary, not for callers importing this package. They may change or disappear in a minor release. -**`github.com/skyoo2003/kvs/pkg/resp`.** It exists so the server can speak RESP2, not as a -RESP library for other programs. The protocol kvs answers on the wire is promised; this Go -package is not. +**The RESP codec.** It is private under `internal/resp` because it exists so the server can +speak RESP2, not as a RESP library for other programs. The protocol kvs answers on the wire is +promised; its codec implementation is not. **Anything under `internal/`.** The Go toolchain already stops you importing it; this is the same statement in words. diff --git a/content/docs/contributing.md b/website/content/docs/contributing.md similarity index 100% rename from content/docs/contributing.md rename to website/content/docs/contributing.md diff --git a/content/docs/http-api.md b/website/content/docs/http-api.md similarity index 100% rename from content/docs/http-api.md rename to website/content/docs/http-api.md diff --git a/content/docs/overview.md b/website/content/docs/overview.md similarity index 92% rename from content/docs/overview.md rename to website/content/docs/overview.md index dc9a575..69ae61c 100644 --- a/content/docs/overview.md +++ b/website/content/docs/overview.md @@ -14,7 +14,7 @@ KVS is a key-value store written in Go. It can be used as a library, via its CLI - **Durability** — `--data-dir` appends every change to a log and replays it at startup, so the keyspace survives a restart - **Clustering** — `--raft-addr` joins a Raft cluster, so losing the leader costs an election rather than a person - **Cobra/Viper CLI** — `serve` command with configurable listen addresses -- **Library** — import `github.com/skyoo2003/kvs` directly in Go programs +- **Library** — import `github.com/skyoo2003/kvs/pkg/kvs` in Go programs - **Docker** — container images published to `ghcr.io/skyoo2003/kvs` ## Installation @@ -50,14 +50,14 @@ Every flag, and how to set it from a config file or the environment, is on the ### Library -Import `github.com/skyoo2003/kvs` to use KVS as a library: +Import `github.com/skyoo2003/kvs/pkg/kvs` to use KVS as a library: ```go package main import ( "fmt" - "github.com/skyoo2003/kvs" + "github.com/skyoo2003/kvs/pkg/kvs" ) func main() { diff --git a/content/docs/redis-api.md b/website/content/docs/redis-api.md similarity index 100% rename from content/docs/redis-api.md rename to website/content/docs/redis-api.md diff --git a/content/docs/release.md b/website/content/docs/release.md similarity index 96% rename from content/docs/release.md rename to website/content/docs/release.md index 85d7a07..0afdf52 100644 --- a/content/docs/release.md +++ b/website/content/docs/release.md @@ -51,7 +51,7 @@ git tag v1.2.3 && git push origin v1.2.3 |---|---| | `changes/v1.2.3.md` exists on `main` | The workflow fails in two seconds without it, after the tag is already pushed | | `make all` passes | Nothing downstream runs the tests | -| `go test -run TestPublicAPISurface .` passes | An unintended change to the exported Go surface is a broken promise — see [Compatibility](../compatibility/) | +| `go test ./pkg/kvs -run TestPublicAPISurface` passes | An unintended change to the exported Go surface is a broken promise — see [Compatibility](../compatibility/) | | `goreleaser check` passes | Catches deprecated configuration before a floating GoReleaser version turns it into a failed release | | `gh release list` has no stale draft | Release Drafter keeps a rolling draft; it does not collide with a real tag, but it lingers next to the release just cut | | The tap still holds its formula | Deleting it before the cask exists leaves `brew install skyoo2003/tap/kvs` with nothing to resolve. The swap belongs after the release job, not before the tag — see below | @@ -154,8 +154,8 @@ above. ## Documentation site The Hugo site at [skyoo2003.github.io/kvs](https://skyoo2003.github.io/kvs/) is published by -`.github/workflows/docs.yaml` on every push to `main` that touches `hugo.toml`, `content/`, -`layouts/`, `static/`, `README.md`, or `CONTRIBUTING.md`. Hugo builds into `public/`, which +`.github/workflows/docs.yaml` on every push to `main` that touches `website/`, `README.md`, or +`CONTRIBUTING.md`. Hugo builds into `website/public/`, which `actions/upload-pages-artifact` and `actions/deploy-pages` publish. It needs the repository's Pages setting to deploy from GitHub Actions. diff --git a/hugo.toml b/website/hugo.toml similarity index 100% rename from hugo.toml rename to website/hugo.toml diff --git a/layouts/_default/baseof.html b/website/layouts/_default/baseof.html similarity index 100% rename from layouts/_default/baseof.html rename to website/layouts/_default/baseof.html diff --git a/layouts/_default/list.html b/website/layouts/_default/list.html similarity index 100% rename from layouts/_default/list.html rename to website/layouts/_default/list.html diff --git a/layouts/_default/single.html b/website/layouts/_default/single.html similarity index 100% rename from layouts/_default/single.html rename to website/layouts/_default/single.html diff --git a/layouts/index.html b/website/layouts/index.html similarity index 100% rename from layouts/index.html rename to website/layouts/index.html diff --git a/static/css/style.css b/website/static/css/style.css similarity index 100% rename from static/css/style.css rename to website/static/css/style.css