# internal/jsonstore

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)
