Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .github/workflows/natsstore.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: natsstore
on:
push:
paths:
- 'natsstore/**'

pull_request:
paths:
- 'natsstore/**'


jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 'stable'

- name: Install deps
working-directory: natsstore
shell: bash --noprofile --norc -x -eo pipefail {0}
run: |
go get -t ./...
go install honnef.co/go/tools/cmd/staticcheck@latest
go install github.com/client9/misspell/cmd/misspell@latest

- name: Run linters
working-directory: natsstore
shell: bash --noprofile --norc -x -eo pipefail {0}
run: |
$(exit $(go fmt ./... | wc -l))
go vet ./...
go vet ./test/...
staticcheck ./...
staticcheck ./test/...
find . -type f -name "*.go" | xargs misspell -error -locale US

test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 'stable'

- name: Run tests
working-directory: natsstore
shell: bash --noprofile --norc -x -eo pipefail {0}
run: |
go test -v -count=1 ./test/...
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,18 @@ You can use the library as a whole, or pick just what you need.

# Utilities

| Module | Description | Docs | Version |
|----------------------|----------------------------------------------|--------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|
| Core NATS Extensions | Core NATS extensions | [README.md](natsext/README.md) | [![Go Reference][natsext-image]][natsext-url] |
| `natscontext` | Allow connecting to NATS using NATS Contexts | [README.md](natscontext/README.md) | [![Go Reference][natscontext-image]][natscontext-url] |
| NATS System Client | NATS client for NATS monitoring APIs | [README.md](natssysclient/README.md) | [![Go Reference][natssysclient-image]][natssysclient-url] |
| Module | Description | Docs | Version |
|-----------------------|-------------------------------------------------|--------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|
| Core NATS Extensions | Core NATS extensions | [README.md](natsext/README.md) | [![Go Reference][natsext-image]][natsext-url] |
| `natscontext` | Allow connecting to NATS using NATS Contexts | [README.md](natscontext/README.md) | [![Go Reference][natscontext-image]][natscontext-url] |
| `natsstore` | A usability wrapper around JetStream KV buckets | [README.md](natsstore/README.md) | [![Go Reference][natsnatsstore-image]][natsnatsstore-url] |
| NATS System Client | NATS client for NATS monitoring APIs | [README.md](natssysclient/README.md) | [![Go Reference][natssysclient-image]][natssysclient-url] |

[natsext-url]: https://pkg.go.dev/github.com/synadia-io/orbit.go/natsext
[natsext-image]: https://pkg.go.dev/badge/github.com/synadia-io/orbit.go/natsext.svg
[natscontext-url]: https://pkg.go.dev/github.com/synadia-io/orbit.go/natscontext
[natscontext-image]: https://pkg.go.dev/badge/github.com/synadia-io/orbit.go/natscontext.svg
[natsstore-url]: https://pkg.go.dev/github.com/synadia-io/orbit.go/natsstore
[natsstore-image]: https://pkg.go.dev/badge/github.com/synadia-io/orbit.go/natsstore.svg
[natssysclient-url]: https://pkg.go.dev/github.com/synadia-io/orbit.go/natssysclient
[natssysclient-image]: https://pkg.go.dev/badge/github.com/synadia-io/orbit.go/natssysclient.svg
2 changes: 2 additions & 0 deletions natscontext/go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
10 changes: 2 additions & 8 deletions natsext/go.work.sum
Original file line number Diff line number Diff line change
@@ -1,32 +1,26 @@
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/nats-io/nats.go v1.36.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
github.com/nats-io/nats.go v1.38.0 h1:A7P+g7Wjp4/NWqDOOP/K6hfhr54DvdDQUznt5JFg9XA=
github.com/nats-io/nats.go v1.38.0/go.mod h1:IGUM++TwokGnXPs82/wCuiHS02/aKrdYUQkU8If6yjw=
github.com/nats-io/nkeys v0.4.9 h1:qe9Faq2Gxwi6RZnZMXfmGMZkg3afLLOtrU+gDZJ35b0=
github.com/nats-io/nkeys v0.4.9/go.mod h1:jcMqs+FLG+W5YO36OX6wFIFcmpdAns+w1Wm6D3I/evE=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
90 changes: 90 additions & 0 deletions natsstore/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# NATS JetStream Store

[License-Url]: https://www.apache.org/licenses/LICENSE-2.0
[License-Image]: https://img.shields.io/badge/License-Apache2-blue.svg
[ReportCard-Url]: https://goreportcard.com/report/github.com/synadia-io/orbit.go/natsstore
[ReportCard-Image]: https://goreportcard.com/badge/github.com/synadia-io/orbit.go/natsstore
[Build-Status-Url]: https://github.com/synadia-io/orbit.go/actions/workflows/natsstore.yaml
[Build-Status-Image]: https://github.com/synadia-io/orbit.go/actions/workflows/natsstore.yaml/badge.svg?branch=main
[GoDoc-Url]: https://pkg.go.dev/github.com/synadia-io/orbit.go/natsstore
[GoDoc-Image]: https://pkg.go.dev/badge/github.com/synadia-io/orbit.go/natsstore.svg

[![License][License-Image]][License-Url]
[![Go Reference][GoDoc-Image]][GoDoc-Url]
[![Build Status][Build-Status-Image]][Build-Status-Url]
[![Go Report Card][ReportCard-Image]][ReportCard-Url]

Nats JetStream Store library provides a client to make it easier to perform common operations on a JetStream KV bucket.

## Installation

```bash
go get github.com/synadia-io/orbit.go/natsstore
```

## Usage

The client serves as a wrapper around a KV bucket. However, it also requires access to the underlying
JetStream stream to support low-level purging. Therefor, the client will look for the bucket and underlying stream.

```go
// connect to nats
nc, err := nats.Connect(nats.DefaultURL)
if err != nil {
// handle error
}
defer nc.Close()

// get the jetstream client
js, err := jetstream.New(nc)
if err != nil {
// handle error
}

// get a store wrapping an existing JetStream KV bucket, using a json
// codec to persist entries
store, err = natsstore.NewKeyValueStore(js, "myBucket", natsstore.NewJsonCodec())
if err != nil {
// handle error
}

// add a new entry to the store but fail if it already exists
_, err := store.Apply(context.Background(), "entries.entry_one", func(entry natsstore.Entry) ([]byte, error) {
if !entry.IsNew() {
return nil, fmt.Errorf("entry already exists")
}

return entry.Encode("Hello World")
})
if err != nil {
// handle error
}

// update an entry in the store, but fail if it does not exist
_, err := store.Apply(context.Background(), "entries.entry_one", func(entry natsstore.Entry) ([]byte, error) {
if entry.IsNew() {
return nil, fmt.Errorf("entry does not exist")
}

return entry.Encode("Goodbye, cruel world")
})
if err != nil {
// handle error
}

// list the entries in the store
fmt.Println("Entries:")
err := store.List(context.Background(), "entries.>", func(entry natsstore.Entry, hasMore bool) error {
fmt.Printf(" - %s\n", entry.Key())
return nil
})
if err != nil {
// handle error
}
```

### Codecs
The store entries are stored as byte slices, but the store can be configured with a codec to encode and decode the
entries. The library provides a `Json` codec which can optionally be wrapped by a `Secured` codec. The latter will
encrypt/decrypt all entry data written/read to/from the store. You can also implement your own codec by implementing
the `Codec` interface and passing it when constructing the store.
7 changes: 7 additions & 0 deletions natsstore/codec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package natsstore

// Codec provides a way to encode and decode data
type Codec interface {
Encode(any) ([]byte, error)
Decode([]byte, any) error
}
17 changes: 17 additions & 0 deletions natsstore/codec_json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package natsstore

import "encoding/json"

func NewJsonCodec() Codec {
return &jsonCodec{}
}

type jsonCodec struct{}

func (c *jsonCodec) Encode(a any) ([]byte, error) {
return json.Marshal(a)
}

func (c *jsonCodec) Decode(data []byte, a any) error {
return json.Unmarshal(data, a)
}
36 changes: 36 additions & 0 deletions natsstore/codec_secure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package natsstore

import "github.com/nats-io/nkeys"

func Secured(codec Codec, xkey nkeys.KeyPair) Codec {
pk, _ := xkey.PublicKey()
return &secureCodec{
codec: codec,
xkey: xkey,
pk: pk,
}
}

type secureCodec struct {
codec Codec
xkey nkeys.KeyPair
pk string
}

func (s *secureCodec) Encode(a any) ([]byte, error) {
b, err := s.codec.Encode(a)
if err != nil {
return nil, err
}

return s.xkey.Seal(b, s.pk)
}

func (s *secureCodec) Decode(bytes []byte, a any) error {
b, err := s.xkey.Open(bytes, s.pk)
if err != nil {
return err
}

return s.codec.Decode(b, a)
}
30 changes: 30 additions & 0 deletions natsstore/entry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package natsstore

func newEntry(codec Codec) Entry {
return Entry{
Revision: 0,
codec: codec,
}
}

type Entry struct {
Key string
Data []byte
Revision uint64
codec Codec
}

// IsNew returns true if the entry isn't stored
func (e Entry) IsNew() bool {
return e.Revision == 0 && len(e.Data) == 0
}

// Decode uses the codec to decode the data in the entry yet
func (e Entry) Decode(a any) error {
return e.codec.Decode(e.Data, a)
}

// Encode uses the codec to encode the data in the entry
func (e Entry) Encode(a any) ([]byte, error) {
return e.codec.Encode(a)
}
14 changes: 14 additions & 0 deletions natsstore/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module github.com/synadia-io/orbit.go/natsstore

go 1.24.1

require github.com/nats-io/nats.go v1.40.1

require (
github.com/klauspost/compress v1.18.0 // indirect
github.com/nats-io/nkeys v0.4.10 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
)
14 changes: 14 additions & 0 deletions natsstore/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/nats-io/nats.go v1.40.1 h1:MLjDkdsbGUeCMKFyCFoLnNn/HDTqcgVa3EQm+pMNDPk=
github.com/nats-io/nats.go v1.40.1/go.mod h1:wV73x0FSI/orHPSYoyMeJB+KajMDoWyXmFaRrrYaaTo=
github.com/nats-io/nkeys v0.4.10 h1:glmRrpCmYLHByYcePvnTBEAwawwapjCPMjy2huw20wc=
github.com/nats-io/nkeys v0.4.10/go.mod h1:OjRrnIKnWBFl+s4YK5ChQfvHP2fxqZexrKJoVVyWB3U=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
6 changes: 6 additions & 0 deletions natsstore/go.work
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
go 1.24.1

use (
.
./test
)
Loading