Skip to content
Merged
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
121 changes: 85 additions & 36 deletions store/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ go install gofr.dev/cli/gofr@latest

1. **Initialize a store configuration:**
```bash
gofr store init
gofr store init -name=user
```
This creates a `store.yaml` file with example configuration.
This creates:
- `stores/store.yaml` — central configuration template
- `stores/all.go` — store registry factory (auto-generated)
- `stores/user/interface.go` — initial interface stub
- `stores/user/user.go` — initial implementation stub (editable)

2. **Edit `store.yaml`** with your models and queries (see Configuration Reference below).
2. **Edit `stores/store.yaml`** with your models and queries (see Configuration Reference below).

3. **Generate the store code:**
```bash
Expand All @@ -30,8 +34,8 @@ go install gofr.dev/cli/gofr@latest
4. **Use in your application:**
```go
import "your-project/stores/user"
userStore := user.NewUser()

userStore := user.NewUserStore()
result, err := userStore.GetUserByID(ctx, 123)
```

Expand All @@ -40,24 +44,41 @@ go install gofr.dev/cli/gofr@latest
### Commands

```bash
# Initialize a new store.yaml configuration file
gofr store init
# Initialize a new store directory and store.yaml configuration file
# The -name flag is required
gofr store init -name=<store-name>

# Generate store code from store.yaml
# Generate store code from a store.yaml
# Default config path: stores/store.yaml
gofr store generate

# Generate store code from a specific config file
gofr store generate -config=custom-store.yaml
```

### Project Structure

After generation, your project will have:
**After `gofr store init -name=user`:**

```
stores/
├── all.go # Store registry (auto-generated)
├── user/
│ ├── interface.go # UserStore interface
│ ├── store.go # UserStore implementation
│ └── user.go # User model (if generated)
├── store.yaml # Central configuration template (edit this)
├── all.go # Store registry factory (auto-generated, DO NOT EDIT)
└── user/
├── interface.go # UserStore interface stub (DO NOT EDIT — regenerated by generate)
└── user.go # userStore implementation stub (editable — add your SQL logic here)
```

**After `gofr store generate`:**

```
stores/
├── store.yaml # Central configuration (source of truth)
├── all.go # Store registry factory (DO NOT EDIT)
└── user/
├── interface.go # UserStore interface (DO NOT EDIT — regenerated each time)
├── userStore.go # userStore implementation boilerplate (add SQL logic here)
└── user.go # User model (if defined in YAML, DO NOT EDIT)
```

### Using Generated Stores
Expand All @@ -66,19 +87,24 @@ stores/
```go
import "your-project/stores/user"

userStore := user.NewUser()
userStore := user.NewUserStore()
result, err := userStore.GetUserByID(ctx, id)
```

**Option 2: Using the registry**
```go
import "your-project/stores"
import (
"your-project/stores"
"your-project/stores/user"
)

allStores := stores.All()
userStore := stores.GetStore("user").(user.User)
// GetStore returns a factory-created instance; cast to the correct interface
userStore := stores.GetStore("user").(user.UserStore)
result, err := userStore.GetUserByID(ctx, id)
```

> **💡 Note:** `stores.All()` returns a `map[string]func() any` — a map of **factory functions**, not active instances. Use `stores.GetStore(name)` for convenient access.

### Integration Example

```go
Expand All @@ -91,7 +117,7 @@ import (

func main() {
app := gofr.New()
userStore := user.NewUser()
userStore := user.NewUserStore()

app.GET("/users/{id}", func(ctx *gofr.Context) (interface{}, error) {
id, _ := strconv.ParseInt(ctx.PathParam("id"), 10, 64)
Expand All @@ -113,7 +139,7 @@ stores:
- name: "user"
package: "user"
output_dir: "stores/user"
interface: "User"
interface: "UserStore"
implementation: "userStore"
queries:
- name: "GetUserByID"
Expand Down Expand Up @@ -143,12 +169,14 @@ models:

| Field | Description | Required |
|-------|-------------|----------|
| `name` | Store identifier (used in registry) | Yes |
| `package` | Go package name | Yes |
| `output_dir` | Directory for generated files | Yes |
| `interface` | Interface name (e.g., "User") | Yes |
| `implementation` | Implementation struct name | Yes |
| `queries` | Array of database queries | Yes |
| `name` | Store identifier (used in registry) | **Yes** |
| `package` | Go package name | **Yes** |
| `output_dir` | Directory for generated files | Optional (defaults to `stores/<name>`) |
| `interface` | Interface name — **recommended: `<Name>Store`** (e.g., `UserStore`) | Optional (defaults to `<Name>Store`) |
| `implementation` | Implementation struct name (e.g., `userStore`) | Optional (defaults to `<name>Store`) |
| `queries` | Array of database queries | Optional |

> **⚠️ Naming Convention:** The registry (`stores/all.go`) automatically appends `"Store"` when building constructor calls. To avoid compilation errors, always name your interface as `<Name>Store` (e.g., `UserStore`) and the generated constructor will be `New<Name>Store()`.

### Models

Expand Down Expand Up @@ -185,10 +213,10 @@ models:
- `delete` - DELETE queries

**Return Types:**
- `single` - Returns a single model instance
- `multiple` - Returns a slice of models
- `count` - Returns `int64` count
- `custom` - Returns `interface{}`
- `single` - Returns `(Model, error)`
- `multiple` - Returns `([]Model, error)`
- `count` - Returns `(int64, error)`
- `custom` - Returns `(any, error)`

**Example Query:**
```yaml
Expand All @@ -206,21 +234,23 @@ queries:

### Multiple Stores

You can define multiple stores in a single YAML file. Each store gets its own directory and the registry (`stores/all.go`) tracks all of them.

```yaml
version: "1.0"

stores:
- name: "user"
package: "user"
output_dir: "stores/user"
interface: "User"
interface: "UserStore"
implementation: "userStore"
queries: [...]

- name: "product"
package: "product"
output_dir: "stores/product"
interface: "Product"
interface: "ProductStore"
implementation: "productStore"
queries: [...]

Expand All @@ -231,6 +261,12 @@ models:
fields: [...]
```

**Accessing multiple stores from the registry:**
```go
userStore := stores.GetStore("user").(user.UserStore)
productStore := stores.GetStore("product").(product.ProductStore)
```

## Generated Code Examples

### Interface
Expand All @@ -240,7 +276,7 @@ package user

import "gofr.dev/pkg/gofr"

type User interface {
type UserStore interface {
GetUserByID(ctx *gofr.Context, id int64) (User, error)
GetAllUsers(ctx *gofr.Context) ([]User, error)
}
Expand All @@ -253,16 +289,20 @@ package user

type userStore struct{}

func NewUser() User {
func NewUserStore() UserStore {
return &userStore{}
}

func (s *userStore) GetUserByID(ctx *gofr.Context, id int64) (User, error) {
// TODO: Implement query using ctx.SQL()
return User{}, nil
var result User
// err := ctx.SQL().QueryRowContext(ctx, sql, id).Scan(&result.ID, ...)
return result, nil
}
```

> **💡 Note:** The generator creates method **signatures and boilerplate only**. You must implement the actual SQL execution in the `// TODO` sections using `ctx.SQL()` methods.

### Model
```go
// Code generated by gofr.dev/cli/gofr. DO NOT EDIT.
Expand All @@ -277,4 +317,13 @@ type User struct {
func (User) TableName() string {
return "user"
}
```
```

## Best Practices

1. **Know Which Files Are Auto-Generated**: Only `interface.go` and `all.go` are marked `DO NOT EDIT` and are overwritten on every `gofr store generate`. The implementation stub (`<name>.go` or `<name>Store.go`) created by `gofr store init` is editable — this is where you add your SQL logic.
2. **Use `<Name>Store` Interface Names**: This ensures the registry and constructor align correctly.
3. **Commit your YAML**: Treat `store.yaml` as source of truth. Re-run `gofr store generate` after every change.
4. **Reference Existing Models**: If you already have model structs, use the `path` + `package` fields to avoid duplication.

For a complete working example, see [`store/example.yaml`](./example.yaml).
Loading