mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-17 22:19:42 +02:00
docs: add per package README for implementation details (AI generated with human review)
This commit is contained in:
364
internal/jsonstore/README.md
Normal file
364
internal/jsonstore/README.md
Normal file
@@ -0,0 +1,364 @@
|
||||
# JSON Store
|
||||
|
||||
The jsonstore package provides persistent JSON storage with namespace support, using thread-safe concurrent maps and automatic loading/saving.
|
||||
|
||||
## Overview
|
||||
|
||||
The jsonstore package implements a simple yet powerful JSON storage system for GoDoxy, supporting both key-value stores (MapStore) and single object stores (ObjectStore) with automatic persistence to JSON files.
|
||||
|
||||
### Key Features
|
||||
|
||||
- Namespace-based storage
|
||||
- Thread-safe concurrent map operations (xsync)
|
||||
- Automatic JSON loading on initialization
|
||||
- Automatic JSON saving on program exit
|
||||
- Generic type support
|
||||
- Marshal/Unmarshal integration
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[JSON Store] --> B{Namespace}
|
||||
B --> C[MapStore]
|
||||
B --> D[ObjectStore]
|
||||
|
||||
C --> E[xsync.Map]
|
||||
D --> F[Single Object]
|
||||
|
||||
G[Storage File] --> H[Load on Init]
|
||||
H --> I[Parse JSON]
|
||||
I --> J[xsync.Map or Object]
|
||||
|
||||
K[Program Exit] --> L[Save All]
|
||||
L --> M[Serialize to JSON]
|
||||
M --> N[Write Files]
|
||||
```
|
||||
|
||||
## Core Components
|
||||
|
||||
### MapStore
|
||||
|
||||
```go
|
||||
type MapStore[VT any] struct {
|
||||
*xsync.Map[string, VT]
|
||||
}
|
||||
|
||||
// Implements:
|
||||
// - Initialize() - initializes the internal map
|
||||
// - MarshalJSON() - serializes to JSON
|
||||
// - UnmarshalJSON() - deserializes from JSON
|
||||
```
|
||||
|
||||
### ObjectStore
|
||||
|
||||
```go
|
||||
type ObjectStore[Pointer Initializer] struct {
|
||||
ptr Pointer
|
||||
}
|
||||
|
||||
// Initializer interface requires:
|
||||
// - Initialize()
|
||||
```
|
||||
|
||||
### Store Interface
|
||||
|
||||
```go
|
||||
type store interface {
|
||||
Initialize()
|
||||
json.Marshaler
|
||||
json.Unmarshaler
|
||||
}
|
||||
```
|
||||
|
||||
## Public API
|
||||
|
||||
### MapStore Creation
|
||||
|
||||
```go
|
||||
// Store creates a new namespace map store.
|
||||
func Store[VT any](namespace namespace) MapStore[VT]
|
||||
```
|
||||
|
||||
### ObjectStore Creation
|
||||
|
||||
```go
|
||||
// Object creates a new namespace object store.
|
||||
func Object[Ptr Initializer](namespace namespace) Ptr
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### MapStore Example
|
||||
|
||||
```go
|
||||
// Define a namespace
|
||||
type UserID string
|
||||
|
||||
// Create a store for user sessions
|
||||
var sessions = jsonstore.Store[UserID]("sessions")
|
||||
|
||||
// Store a value
|
||||
sessions.Store("user123", "session-token-abc")
|
||||
|
||||
// Load a value
|
||||
token, ok := sessions.Load("user123")
|
||||
if ok {
|
||||
fmt.Println("Session:", token)
|
||||
}
|
||||
|
||||
// Iterate over all entries
|
||||
for id, token := range sessions.Range {
|
||||
fmt.Printf("%s: %s\n", id, token)
|
||||
}
|
||||
|
||||
// Delete a value
|
||||
sessions.Delete("user123")
|
||||
```
|
||||
|
||||
### ObjectStore Example
|
||||
|
||||
```go
|
||||
// Define a struct that implements Initialize
|
||||
type AppConfig struct {
|
||||
Name string
|
||||
Version int
|
||||
}
|
||||
|
||||
func (c *AppConfig) Initialize() {
|
||||
c.Name = "MyApp"
|
||||
c.Version = 1
|
||||
}
|
||||
|
||||
// Create an object store
|
||||
var config = jsonstore.Object[*AppConfig]("app_config")
|
||||
|
||||
// Access the object
|
||||
fmt.Printf("App: %s v%d\n", config.Name, config.Version)
|
||||
|
||||
// Modify and save (automatic on exit)
|
||||
config.Version = 2
|
||||
```
|
||||
|
||||
### Complete Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/yusing/godoxy/internal/jsonstore"
|
||||
)
|
||||
|
||||
type Settings struct {
|
||||
Theme string
|
||||
Lang string
|
||||
}
|
||||
|
||||
func (s *Settings) Initialize() {
|
||||
s.Theme = "dark"
|
||||
s.Lang = "en"
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Create namespace type
|
||||
type SettingsKey string
|
||||
|
||||
// Create stores
|
||||
var settings = jsonstore.Object[*Settings]("settings")
|
||||
var cache = jsonstore.Store[string]("cache")
|
||||
|
||||
// Use stores
|
||||
settings.Theme = "light"
|
||||
cache.Store("key1", "value1")
|
||||
|
||||
// On program exit, all stores are automatically saved
|
||||
}
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Application
|
||||
participant Store
|
||||
participant xsync.Map
|
||||
participant File
|
||||
|
||||
Application->>Store: Store(key, value)
|
||||
Store->>xsync.Map: Store(key, value)
|
||||
xsync.Map-->>Store: Done
|
||||
|
||||
Application->>Store: Load(key)
|
||||
Store->>xsync.Map: Load(key)
|
||||
xsync.Map-->>Store: value
|
||||
Store-->>Application: value
|
||||
|
||||
Application->>Store: Save()
|
||||
Store->>File: Marshal JSON
|
||||
File-->>Store: Success
|
||||
|
||||
Note over Store,File: On program exit
|
||||
Store->>File: Save all stores
|
||||
File-->>Store: Complete
|
||||
```
|
||||
|
||||
## Namespace
|
||||
|
||||
Namespaces are string identifiers for different storage areas:
|
||||
|
||||
```go
|
||||
type namespace string
|
||||
|
||||
// Create namespaces
|
||||
var (
|
||||
users = jsonstore.Store[User]("users")
|
||||
sessions = jsonstore.Store[Session]("sessions")
|
||||
config = jsonstore.Object[*Config]("config")
|
||||
metadata = jsonstore.Store[string]("metadata")
|
||||
)
|
||||
```
|
||||
|
||||
### Reserved Names
|
||||
|
||||
None
|
||||
|
||||
## File Storage
|
||||
|
||||
### File Location
|
||||
|
||||
```go
|
||||
var storesPath = common.DataDir // Typically ./data/.{namespace}.json
|
||||
```
|
||||
|
||||
### File Format
|
||||
|
||||
Stores are saved as `{namespace}.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"key1": "value1",
|
||||
"key2": "value2"
|
||||
}
|
||||
```
|
||||
|
||||
### Automatic Loading
|
||||
|
||||
On initialization, stores are loaded from disk:
|
||||
|
||||
```go
|
||||
func loadNS[T store](ns namespace) T {
|
||||
store := reflect.New(reflect.TypeFor[T]().Elem()).Interface().(T)
|
||||
store.Initialize()
|
||||
|
||||
path := filepath.Join(storesPath, string(ns)+".json")
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
log.Err(err).Msg("failed to load store")
|
||||
}
|
||||
return store
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err := sonic.ConfigDefault.NewDecoder(file).Decode(&store); err != nil {
|
||||
log.Err(err).Msg("failed to decode store")
|
||||
}
|
||||
|
||||
stores[ns] = store
|
||||
return store
|
||||
}
|
||||
```
|
||||
|
||||
### Automatic Saving
|
||||
|
||||
On program exit, all stores are saved:
|
||||
|
||||
```go
|
||||
func init() {
|
||||
task.OnProgramExit("save_stores", func() {
|
||||
if err := save(); err != nil {
|
||||
log.Error().Err(err).Msg("failed to save stores")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func save() error {
|
||||
for ns, store := range stores {
|
||||
path := filepath.Join(storesPath, string(ns)+".json")
|
||||
if err := serialization.SaveJSON(path, &store, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Thread Safety
|
||||
|
||||
The MapStore uses `xsync.Map` for thread-safe operations:
|
||||
|
||||
```go
|
||||
type MapStore[VT any] struct {
|
||||
*xsync.Map[string, VT]
|
||||
}
|
||||
|
||||
// All operations are safe:
|
||||
// - Load, Store, Delete
|
||||
// - Range iteration
|
||||
// - LoadAndDelete
|
||||
// - LoadOrCompute
|
||||
```
|
||||
|
||||
## JSON Serialization
|
||||
|
||||
### MarshalJSON
|
||||
|
||||
```go
|
||||
func (s MapStore[VT]) MarshalJSON() ([]byte, error) {
|
||||
return sonic.Marshal(xsync.ToPlainMap(s.Map))
|
||||
}
|
||||
```
|
||||
|
||||
### UnmarshalJSON
|
||||
|
||||
```go
|
||||
func (s *MapStore[VT]) UnmarshalJSON(data []byte) error {
|
||||
tmp := make(map[string]VT)
|
||||
if err := sonic.Unmarshal(data, &tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
s.Map = xsync.NewMap[string, VT](xsync.WithPresize(len(tmp)))
|
||||
for k, v := range tmp {
|
||||
s.Store(k, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Integration Points
|
||||
|
||||
The jsonstore package integrates with:
|
||||
|
||||
- **Serialization**: JSON marshaling/unmarshaling
|
||||
- **Task Management**: Program exit callbacks
|
||||
- **Common**: Data directory configuration
|
||||
|
||||
## Error Handling
|
||||
|
||||
Errors are logged but don't prevent store usage:
|
||||
|
||||
```go
|
||||
if err := sonic.Unmarshal(data, &tmp); err != nil {
|
||||
log.Err(err).
|
||||
Str("path", path).
|
||||
Msg("failed to load store")
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- Uses `xsync.Map` for lock-free reads
|
||||
- Presizes maps based on input data
|
||||
- Sonic library for fast JSON parsing
|
||||
- Background save on program exit (non-blocking)
|
||||
Reference in New Issue
Block a user