@@ -10,6 +10,7 @@ import (
1010 "time"
1111
1212 ds "github.com/codeGROOVE-dev/ds9/pkg/datastore"
13+ "github.com/codeGROOVE-dev/sfcache/pkg/store/compress"
1314)
1415
1516const (
@@ -19,28 +20,30 @@ const (
1920
2021// Store implements persistence using Google Cloud Datastore.
2122type Store [K comparable , V any ] struct {
22- client * ds.Client
23- kind string
23+ client * ds.Client
24+ kind string
25+ compressor compress.Compressor
26+ ext string
2427}
2528
2629// ValidateKey checks if a key is valid for Datastore persistence.
2730// Datastore has stricter key length limits than files.
2831func (* Store [K , V ]) ValidateKey (key K ) error {
29- s := fmt .Sprintf ("%v" , key )
30- if len (s ) > maxDatastoreKeyLen {
31- return fmt .Errorf ("key too long: %d bytes (max %d for datastore)" , len (s ), maxDatastoreKeyLen )
32- }
33- if s == "" {
32+ k := fmt .Sprintf ("%v" , key )
33+ if k == "" {
3434 return errors .New ("key cannot be empty" )
3535 }
36+ if len (k ) > maxDatastoreKeyLen {
37+ return fmt .Errorf ("key too long: %d bytes (max %d for datastore)" , len (k ), maxDatastoreKeyLen )
38+ }
3639 return nil
3740}
3841
3942// Location returns the Datastore key path for a given cache key.
4043// Implements the Store interface Location() method.
4144// Format: "kind/key" (e.g., "CacheEntry/mykey").
4245func (s * Store [K , V ]) Location (key K ) string {
43- return fmt .Sprintf ("%s/%v" , s .kind , key )
46+ return fmt .Sprintf ("%s/%v%s " , s .kind , key , s . ext )
4447}
4548
4649// entry represents a cache entry in Datastore.
@@ -54,27 +57,30 @@ type entry struct {
5457
5558// New creates a new Datastore-based persistence layer.
5659// The cacheID is used as the Datastore database name.
57- // An empty projectID will be auto-detected from the environment.
58- func New [K comparable , V any ](ctx context.Context , cacheID string ) (* Store [K , V ], error ) {
59- // Empty project ID lets ds9 auto-detect
60+ // Optional compressor enables compression (default: no compression).
61+ func New [K comparable , V any ](ctx context.Context , cacheID string , c ... compress.Compressor ) (* Store [K , V ], error ) {
62+ comp := compress .None ()
63+ if len (c ) > 0 && c [0 ] != nil {
64+ comp = c [0 ]
65+ }
66+
6067 client , err := ds .NewClientWithDatabase (ctx , "" , cacheID )
6168 if err != nil {
6269 return nil , fmt .Errorf ("create datastore client: %w" , err )
6370 }
6471
65- // Verify connectivity (assert readiness)
66- // Note: ds9 doesn't expose Ping, but client creation validates connectivity
67-
6872 return & Store [K , V ]{
69- client : client ,
70- kind : datastoreKind ,
73+ client : client ,
74+ kind : datastoreKind ,
75+ compressor : comp ,
76+ ext : comp .Extension (),
7177 }, nil
7278}
7379
7480// makeKey creates a Datastore key from a cache key.
75- // We use the string representation directly as the key name.
81+ // We use the string representation directly as the key name, with extension suffix .
7682func (s * Store [K , V ]) makeKey (key K ) * ds.Key {
77- return ds .NameKey (s .kind , fmt .Sprintf ("%v" , key ), nil )
83+ return ds .NameKey (s .kind , fmt .Sprintf ("%v%s " , key , s . ext ), nil )
7884}
7985
8086// Get retrieves a value from Datastore.
@@ -98,14 +104,17 @@ func (s *Store[K, V]) Get(ctx context.Context, key K) (value V, expiry time.Time
98104 return zero , time.Time {}, false , nil
99105 }
100106
101- // Decode from base64
102107 b , err := base64 .StdEncoding .DecodeString (e .Value )
103108 if err != nil {
104109 return zero , time.Time {}, false , fmt .Errorf ("decode base64: %w" , err )
105110 }
106111
107- // Decode value from JSON
108- if err := json .Unmarshal (b , & value ); err != nil {
112+ jsonData , err := s .compressor .Decode (b )
113+ if err != nil {
114+ return zero , time.Time {}, false , fmt .Errorf ("decompress: %w" , err )
115+ }
116+
117+ if err := json .Unmarshal (jsonData , & value ); err != nil {
109118 return zero , time.Time {}, false , fmt .Errorf ("unmarshal value: %w" , err )
110119 }
111120
@@ -114,21 +123,23 @@ func (s *Store[K, V]) Get(ctx context.Context, key K) (value V, expiry time.Time
114123
115124// Set saves a value to Datastore.
116125func (s * Store [K , V ]) Set (ctx context.Context , key K , value V , expiry time.Time ) error {
117- k := s .makeKey (key )
118-
119- // Encode value as JSON then base64
120- b , err := json .Marshal (value )
126+ jsonData , err := json .Marshal (value )
121127 if err != nil {
122128 return fmt .Errorf ("marshal value: %w" , err )
123129 }
124130
131+ data , err := s .compressor .Encode (jsonData )
132+ if err != nil {
133+ return fmt .Errorf ("compress: %w" , err )
134+ }
135+
125136 e := entry {
126- Value : base64 .StdEncoding .EncodeToString (b ),
137+ Value : base64 .StdEncoding .EncodeToString (data ),
127138 Expiry : expiry ,
128139 UpdatedAt : time .Now (),
129140 }
130141
131- if _ , err := s .client .Put (ctx , k , & e ); err != nil {
142+ if _ , err := s .client .Put (ctx , s . makeKey ( key ) , & e ); err != nil {
132143 return fmt .Errorf ("datastore put: %w" , err )
133144 }
134145
0 commit comments