| Dimension | Shared | Only in A | Only in B | Overlap |
|---|---|---|---|---|
| Sections | 0 | 7 | 8 | 0% |
| Commands | 0 | 6 | 0 | 0% |
| Section tags | 1 | 7 | 1 | 11% |
What each file covers
Sections
0 shared · 7 only in A · 8 only in B- − Repository Guidelines
- − Project Structure & Module Organization
- − Build, Test, and Development Commands
- − Coding Style & Naming Conventions
- − Testing Guidelines
- − Commit & Pull Request Guidelines
- − Environment & Configuration Tips
- + Rule Name: Thing ORM Usage Guide
- + Description:
- + Quick reference for using the Thing ORM (github.com/burugo/thing).
- + Core Concept
- + Configuration
- + Basic Usage
- + Key Features
- + Focus
Commands
0 shared · 6 only in A · 0 only in B- − docker-compose.yaml
- − go test ./...
- − npm run lint
- − npm run test:coverage
- − git log
- − npm run test
Section tags
1 shared · 7 only in A · 1 only in B- − setup
- − build
- − test
- − lint-format
- − architecture
- − types
- − git-pr
- + database
- code-style
Line diff
burugo/one-mcp · AGENTS.md
@@ −1 @@
1# Repository Guidelines
2
3## Project Structure & Module Organization
4The Go backend lives under `backend/` with request handlers in `api/handler`, shared utilities in `common/`, data access in `data/`, and business logic in `service/`. The React frontend sits in `frontend/src` with translations in `frontend/public/locales` and build artifacts generated into `frontend/dist`. Persistent assets such as the SQLite database and uploads are stored in `data/` and `upload/`, while deployment aides live in `deploy/`, `Dockerfile`, and `docker-compose.yaml`.
5
6## Build, Test, and Development Commands
7- `./run.sh` — launches the backend on `:3000` and the Vite dev server on `:5173` with hot reload.
8- `PORT=8080 ./build.sh` — produces a production binary and bundles the frontend.
9- `go test ./...` — executes the Go unit tests across the backend.
10- `cd frontend && npm run build` — type-checks, lints, and compiles the React app.
11
12## Coding Style & Naming Conventions
13Go code must pass `gofmt` (tabs for indentation) and follow idiomatic package naming (`lower_case` for directories, `CamelCase` for exported types). Keep API handlers in `backend/api/handler` named `*_handler.go` and tests as `*_test.go`. TypeScript and JSX files use two-space indentation, TypeScript strict mode, and Tailwind utility ordering as emitted by `shadcn`. Run `npm run lint` before pushing to ensure ESLint (flat config) passes.
14
15## Testing Guidelines
16Backend features require table-driven tests under matching `*_test.go` files; ensure new logic is covered by `go test ./...` and update `coverage.out` when reporting coverage. Frontend components should use Vitest with Testing Library—add specs under `frontend/src/**/__tests__/` or alongside components with `.test.tsx` suffix. For UI flows, prefer `npm run test:coverage` to exercise V8 coverage and attach results in PRs touching critical paths.
17
18## Commit & Pull Request Guidelines
19Follow the Conventional Commits style visible in `git log` (e.g., `feat(proxy): add SSE support`). Keep messages scoped, written in the imperative, and limited to 72 characters in the subject. Pull requests should link related issues, summarize behavior changes, note migrations or env needs, and include before/after screenshots for UI updates. Confirm both `go test ./...` and `npm run test` results in the PR description before requesting review.
20
21## Environment & Configuration Tips
22Copy `.env_example` to `.env` and override only the keys you touch; avoid committing secrets. SQLite state is persisted in `data/one-mcp.db`, so remove it if you need a clean slate. When integrating external services, prefer storing credentials in `.env` and referencing them via `config/` structs rather than hardcoding values.
23
burugo/one-mcp · .cursor/rules/thing.mdc
@@ +1 @@
1---
2description:
3globs: *.go
4alwaysApply: false
5---
6---
7description:
8globs: *.go
9alwaysApply: false
10---
11# Rule Name: Thing ORM Usage Guide
12
13# Description:
14# Quick reference for using the Thing ORM (github.com/burugo/thing).
15
16## Core Concept
17- High-performance Go ORM focusing on CRUD and list operations.
18- Built-in caching (Redis or in-memory) automatically handles single-entity and list query caching & invalidation.
19- Uses Go generics for type safety and a cleaner API.
20- Supports MySQL, PostgreSQL, SQLite.
21
22## Configuration
231. **Create DB Adapter:**
24 ```go
25 import "github.com/burugo/thing/drivers/db/sqlite" // Or mysql, postgres
26 dbAdapter, err := sqlite.NewSQLiteAdapter(":memory:")
27 ```
282. **Create Cache Client (Optional, defaults to in-memory):**
29 ```go
30 import (
31 "github.com/redis/go-redis/v9"
32 redisCache "github.com/burugo/thing/drivers/cache/redis"
33 "github.com/burugo/thing"
34 )
35 // Use nil for default in-memory:
36 // var cacheClient thing.CacheClient = nil
37 // Or Redis:
38 // rdb := redis.NewClient(...)
39 // cacheClient = redisCache.NewClient(rdb)
40 ```
413. **Get ORM Instance per Model:**
42 ```go
43 import "github.com/burugo/thing"
44 // import your models package
45
46 // For a specific model typels.User)
47 userThing, err := thing.New[*models.User](mdc:db)
48 ```
49 *Alternative Global Config (less flexible):* `thing.Configure(dbAdapter, cacheClient)` then `thing.Use[*Model]()`.
50
51## Basic Usage
521. **Model Definition:** Embed `thing.BaseModel` and use `db` tags for columns.
53 ```go
54 type User struct {
55 thing.BaseModel // Includes ID, CreatedAt, UpdatedAt, DeletedAt
56 Name string `db:"name"` // Explicitly maps Name field to 'name' column
57 UserAge int // db tag omitted, defaults to 'user_age' column
58 }
59 // Optional: Define TableName() method if different from snake_case plural struct name.
60 // func (u *User) TableName() string { return "custom_users_table" }
61 ```
62 **Note:** While using the `db` tag is recommended for clarity, it's technically optional. If omitted, Thing ORM defaults to converting the Go field name (CamelCase, e.g., `UserAge`) to its snake_case equivalent (`user_age`) as the database column name. Use `db:"-"` to explicitly ignore a field.
63
64 **Example:**
65 ```go
66 // Example: All index types and relationships
67 type User struct {
68 thing.BaseModel
69 Name string `db:"name,index"` // Single-field index
70 Email string `db:"email,unique"` // Single-field unique index
71 ColA string `db:"col_a,index:idx_ab"` // Composite index (idx_ab)
72 ColB int `db:"col_b,index:idx_ab"` // Composite index (idx_ab)
73 KeyA string `db:"key_a,unique:uq_ab"` // Composite unique index (uq_ab)
74 KeyB string `db:"key_b,unique:uq_ab"` // Composite unique index (uq_ab)
75 // Relationship example:
76 Books []*Book `thing:"hasMany;fk:user_id;model:Book" db:"-"`
77 }
78 ```
79
80 **Indexes are always declared in the db tag (index, unique, index:..., unique:...).**
81 **The thing tag is only used for relationship declarations (hasMany, belongsTo, model, fk, etc.).**
82 **Do not use thing tag for index declaration.**
832. **CRUD Operations and Querying (using `userThing` from config step):**
84 - **Create/Update:** `err := userThing.Save(&userInstance)` (creates if ID is zero, updates otherwise).
85 - **Read by ID:** `foundUser, err := userThing.ByID(id)` (uses cache).
86 - **Read Multiple by IDs:** `userMap, err := userThing.ByIDs([]int64{1, 2, 3}, "OptionalPreloadField")` (uses cache, returns `map[int64]YourModelType`. Preloading is optional).
87 - **Delete (Hard):** `err := userThing.Delete(&userInstance)`.
88 - **Soft Delete:** `err := userThing.SoftDelete(&userInstance)` (sets `deleted=true` and `updated_at`).
89
90 - **Querying Records:**
91 The `thing` ORM provides a flexible way to build and execute queries.
92
93 - **Initiating a Query:**
94 You can start a query using `Query()` with `thing.QueryParams` or by chaining methods like `Where()`, `Order()`, and `Preload()`. All query methods return a `*CachedResult[T]` instance, allowing for lazy execution.
95 ```go
96 // Option 1: Using QueryParams
97 params := thing.QueryParams{
98 Where: "age > ? AND status = ?", Args: []interface{}{25, "active"},
99 Order: "name ASC",
100 Preloads: []string{"Books"}, // For preloading relationships
101 }
102 result := userThing.Query(params)
103
104 // Option 2: Chainable methods (can be started from userThing or chained on a CachedResult)
105 // Starting from userThing:
106 result = userThing.Where("age > ?", 25).Order("name ASC").Preload("Books")
107 // Chaining on an existing result:
108 // result = existingResult.Where("status = ?", "active")
109 ```
110
111 - **Fetching Results from `CachedResult[T]`:**
112 Once you have a `CachedResult[T]`, you can fetch data in various ways:
113 ```go
114 // Fetch a paginated list (e.g., offset 0, limit 10)
115 users, err := result.Fetch(0, 10) // Uses cache
116
117 // Fetch the first record matching the query
118 // Returns common.ErrNotFound if no record matches
119 firstUser, err := result.First()
120 if err != nil {
121 if errors.Is(err, common.ErrNotFound) {
122 // Handle case where no user was found
123 } else {
124 // Handle other errors
125 }
126 }
127
128 // Fetch all records matching the query
129 allMatchingUsers, err := result.All()
130 ```
131
132 - **Counting Records:**
133 To get the total number of records matching the query conditions:
134 ```go
135 count, err := result.Count() // Uses cache
136 ```
137
138 - **Including Soft-Deleted Records:**
139 By default, queries exclude soft-deleted records. To include them, use `WithDeleted()` on a `CachedResult[T]`:
140 ```go
141 resultIncludingDeleted := result.WithDeleted()
142 // Now, Fetch(), First(), All(), Count() on resultIncludingDeleted will include soft-deleted items.
143 // Example:
144 // usersIncludingDeleted, err := resultIncludingDeleted.Fetch(0, 10)
145 ```
146
147 - **Fetching All Records of a Model (Simplified):**
148 To get all records for a model type without any specific query conditions:
149 ```go
150 allUsersOfType, err := userThing.All()
151 // This is a convenient alias for userThing.Query(thing.QueryParams{}).All()
152 ```
153
154## Key Features
155- **Caching:** Mostly automatic for `ByID` and `Query`. Monitor via `cacheClient.GetCacheStats(ctx)`.
156- **Relationships:** Define using `thing` struct tags (`hasMany`, `belongsTo`, `manyToMany`) and preload using `QueryParams.Preloads`. Always use `db:"-"` on relation fields in your struct, as these fields are populated by the ORM and do not correspond directly to a column in the model's own table.
157
158 - **`hasMany`**: One-to-many relationship.
159 ```go
160 // User has many Books.
161 // Assumes 'books' table has a 'user_id' foreign key.
162 type User struct {
163 thing.BaseModel
164 // ... other fields
165 Books []*Book `thing:"hasMany;fk:user_id;model:Book" db:"-"`
166 }
167
168 type Book struct {
169 thing.BaseModel
170 UserID uint `db:"user_id"` // Foreign key
171 Title string `db:"title"`
172 // ... other fields
173 }
174 ```
175
176 - **`belongsTo`**: Inverse of one-to-many.
177 ```go
178 // Book belongs to a User.
179 // Assumes 'books' table has a 'user_id' foreign key.
180 type Book struct {
181 thing.BaseModel
182 UserID uint `db:"user_id"` // Foreign key
183 Title string `db:"title"`
184 User *User `thing:"belongsTo;fk:user_id;model:User" db:"-"` // 'model' is optional here if type matches
185 // ... other fields
186 }
187
188 type User struct {
189 thing.BaseModel
190 Name string `db:"name"`
191 // ... other fields
192 }
193 ```
194
195 - **`manyToMany`**: Many-to-many relationship.
196 Requires a join table.
197 ```go
198 // User has and belongs to many Roles.
199 // Roles has and belongs to many Users.
200 // Join table: 'user_roles' with 'user_id' (links to users.id) and 'role_id' (links to roles.id).
201 type User struct {
202 thing.BaseModel
203 Name string `db:"name"`
204 Roles []*Role `thing:"manyToMany;model:Role;joinTable:user_roles;joinLocalKey:user_id;joinRelatedKey:role_id" db:"-"`
205 // ... other fields
206 }
207
208 type Role struct {
209 thing.BaseModel
210 Name string `db:"name"`
211 Users []*User `thing:"manyToMany;model:User;joinTable:user_roles;joinLocalKey:role_id;joinRelatedKey:user_id" db:"-"`
212 // ... other fields
213 }
214
215 // Join table model (optional to define as a struct, but table must exist)
216 // type UserRole struct {
217 // UserID uint `db:"user_id"`
218 // RoleID uint `db:"role_id"`
219 // }
220 // func (ur *UserRole) TableName() string { return "user_roles" }
221 ```
222 Preloading relationships:
223 ```go
224 // Preload Books for a User
225 // users, err := userThing.Query(thing.QueryParams{Preloads: []string{"Books"}}).Fetch(0,10)
226
227 // Preload Roles for a User (manyToMany)
228 // users, err := userThing.Query(thing.QueryParams{Preloads: []string{"Roles"}}).Fetch(0,10)
229 ```
230- **Hooks:** Register listeners for events (e.g., `BeforeSave`, `AfterCreate`) using `thing.RegisterListener`.
231- **Auto Migration:** `thing.AutoMigrate(&User{}, &Book{})` creates/updates tables based on model struct tags (`db`).
232- **JSON Serialization:** `thing.ToJSON(model, options...)`. Use `thing.WithFields("field1,nested{subfield},-excluded")` for flexible control, including method-based virtual properties (e.g., `FullName() string` -> `full_name` in DSL).
233- **Raw SQL:** Access via `dbAdapter := userThing.DBAdapter()`, then use `dbAdapter.Exec`, `dbAdapter.Get`, `dbAdapter.Select`.
234
235## Focus
236- Optimized for common CRUD and list retrieval patterns.
237- Complex JOINs or database-specific features might require Raw SQL.
@@ −1 +1 @@
1−# Repository Guidelines
1+---
2+description:
3+globs: *.go
4+alwaysApply: false
5+---
6+---
7+description:
8+globs: *.go
9+alwaysApply: false
10+---
11+# Rule Name: Thing ORM Usage Guide
212
3−## Project Structure & Module Organization
4−The Go backend lives under `backend/` with request handlers in `api/handler`, shared utilities in `common/`, data access in `data/`, and business logic in `service/`. The React frontend sits in `frontend/src` with translations in `frontend/public/locales` and build artifacts generated into `frontend/dist`. Persistent assets such as the SQLite database and uploads are stored in `data/` and `upload/`, while deployment aides live in `deploy/`, `Dockerfile`, and `docker-compose.yaml`.
13+# Description:
14+# Quick reference for using the Thing ORM (github.com/burugo/thing).
515
6−## Build, Test, and Development Commands
7−- `./run.sh` — launches the backend on `:3000` and the Vite dev server on `:5173` with hot reload.
8−- `PORT=8080 ./build.sh` — produces a production binary and bundles the frontend.
9−- `go test ./...` — executes the Go unit tests across the backend.
10−- `cd frontend && npm run build` — type-checks, lints, and compiles the React app.
16+## Core Concept
17+- High-performance Go ORM focusing on CRUD and list operations.
18+- Built-in caching (Redis or in-memory) automatically handles single-entity and list query caching & invalidation.
19+- Uses Go generics for type safety and a cleaner API.
20+- Supports MySQL, PostgreSQL, SQLite.
1121
12−## Coding Style & Naming Conventions
13−Go code must pass `gofmt` (tabs for indentation) and follow idiomatic package naming (`lower_case` for directories, `CamelCase` for exported types). Keep API handlers in `backend/api/handler` named `*_handler.go` and tests as `*_test.go`. TypeScript and JSX files use two-space indentation, TypeScript strict mode, and Tailwind utility ordering as emitted by `shadcn`. Run `npm run lint` before pushing to ensure ESLint (flat config) passes.
22+## Configuration
23+1. **Create DB Adapter:**
24+ ```go
25+ import "github.com/burugo/thing/drivers/db/sqlite" // Or mysql, postgres
26+ dbAdapter, err := sqlite.NewSQLiteAdapter(":memory:")
27+ ```
28+2. **Create Cache Client (Optional, defaults to in-memory):**
29+ ```go
30+ import (
31+ "github.com/redis/go-redis/v9"
32+ redisCache "github.com/burugo/thing/drivers/cache/redis"
33+ "github.com/burugo/thing"
34+ )
35+ // Use nil for default in-memory:
36+ // var cacheClient thing.CacheClient = nil
37+ // Or Redis:
38+ // rdb := redis.NewClient(...)
39+ // cacheClient = redisCache.NewClient(rdb)
40+ ```
41+3. **Get ORM Instance per Model:**
42+ ```go
43+ import "github.com/burugo/thing"
44+ // import your models package
1445
15−## Testing Guidelines
16−Backend features require table-driven tests under matching `*_test.go` files; ensure new logic is covered by `go test ./...` and update `coverage.out` when reporting coverage. Frontend components should use Vitest with Testing Library—add specs under `frontend/src/**/__tests__/` or alongside components with `.test.tsx` suffix. For UI flows, prefer `npm run test:coverage` to exercise V8 coverage and attach results in PRs touching critical paths.
46+ // For a specific model typels.User)
47+ userThing, err := thing.New[*models.User](mdc:db)
48+ ```
49+ *Alternative Global Config (less flexible):* `thing.Configure(dbAdapter, cacheClient)` then `thing.Use[*Model]()`.
1750
18−## Commit & Pull Request Guidelines
19−Follow the Conventional Commits style visible in `git log` (e.g., `feat(proxy): add SSE support`). Keep messages scoped, written in the imperative, and limited to 72 characters in the subject. Pull requests should link related issues, summarize behavior changes, note migrations or env needs, and include before/after screenshots for UI updates. Confirm both `go test ./...` and `npm run test` results in the PR description before requesting review.
51+## Basic Usage
52+1. **Model Definition:** Embed `thing.BaseModel` and use `db` tags for columns.
53+ ```go
54+ type User struct {
55+ thing.BaseModel // Includes ID, CreatedAt, UpdatedAt, DeletedAt
56+ Name string `db:"name"` // Explicitly maps Name field to 'name' column
57+ UserAge int // db tag omitted, defaults to 'user_age' column
58+ }
59+ // Optional: Define TableName() method if different from snake_case plural struct name.
60+ // func (u *User) TableName() string { return "custom_users_table" }
61+ ```
62+ **Note:** While using the `db` tag is recommended for clarity, it's technically optional. If omitted, Thing ORM defaults to converting the Go field name (CamelCase, e.g., `UserAge`) to its snake_case equivalent (`user_age`) as the database column name. Use `db:"-"` to explicitly ignore a field.
2063
21−## Environment & Configuration Tips
22−Copy `.env_example` to `.env` and override only the keys you touch; avoid committing secrets. SQLite state is persisted in `data/one-mcp.db`, so remove it if you need a clean slate. When integrating external services, prefer storing credentials in `.env` and referencing them via `config/` structs rather than hardcoding values.
64+ **Example:**
65+ ```go
66+ // Example: All index types and relationships
67+ type User struct {
68+ thing.BaseModel
69+ Name string `db:"name,index"` // Single-field index
70+ Email string `db:"email,unique"` // Single-field unique index
71+ ColA string `db:"col_a,index:idx_ab"` // Composite index (idx_ab)
72+ ColB int `db:"col_b,index:idx_ab"` // Composite index (idx_ab)
73+ KeyA string `db:"key_a,unique:uq_ab"` // Composite unique index (uq_ab)
74+ KeyB string `db:"key_b,unique:uq_ab"` // Composite unique index (uq_ab)
75+ // Relationship example:
76+ Books []*Book `thing:"hasMany;fk:user_id;model:Book" db:"-"`
77+ }
78+ ```
2379
80+ **Indexes are always declared in the db tag (index, unique, index:..., unique:...).**
81+ **The thing tag is only used for relationship declarations (hasMany, belongsTo, model, fk, etc.).**
82+ **Do not use thing tag for index declaration.**
83+2. **CRUD Operations and Querying (using `userThing` from config step):**
84+ - **Create/Update:** `err := userThing.Save(&userInstance)` (creates if ID is zero, updates otherwise).
85+ - **Read by ID:** `foundUser, err := userThing.ByID(id)` (uses cache).
86+ - **Read Multiple by IDs:** `userMap, err := userThing.ByIDs([]int64{1, 2, 3}, "OptionalPreloadField")` (uses cache, returns `map[int64]YourModelType`. Preloading is optional).
87+ - **Delete (Hard):** `err := userThing.Delete(&userInstance)`.
88+ - **Soft Delete:** `err := userThing.SoftDelete(&userInstance)` (sets `deleted=true` and `updated_at`).
89+
90+ - **Querying Records:**
91+ The `thing` ORM provides a flexible way to build and execute queries.
92+
93+ - **Initiating a Query:**
94+ You can start a query using `Query()` with `thing.QueryParams` or by chaining methods like `Where()`, `Order()`, and `Preload()`. All query methods return a `*CachedResult[T]` instance, allowing for lazy execution.
95+ ```go
96+ // Option 1: Using QueryParams
97+ params := thing.QueryParams{
98+ Where: "age > ? AND status = ?", Args: []interface{}{25, "active"},
99+ Order: "name ASC",
100+ Preloads: []string{"Books"}, // For preloading relationships
101+ }
102+ result := userThing.Query(params)
103+
104+ // Option 2: Chainable methods (can be started from userThing or chained on a CachedResult)
105+ // Starting from userThing:
106+ result = userThing.Where("age > ?", 25).Order("name ASC").Preload("Books")
107+ // Chaining on an existing result:
108+ // result = existingResult.Where("status = ?", "active")
109+ ```
110+
111+ - **Fetching Results from `CachedResult[T]`:**
112+ Once you have a `CachedResult[T]`, you can fetch data in various ways:
113+ ```go
114+ // Fetch a paginated list (e.g., offset 0, limit 10)
115+ users, err := result.Fetch(0, 10) // Uses cache
116+
117+ // Fetch the first record matching the query
118+ // Returns common.ErrNotFound if no record matches
119+ firstUser, err := result.First()
120+ if err != nil {
121+ if errors.Is(err, common.ErrNotFound) {
122+ // Handle case where no user was found
123+ } else {
124+ // Handle other errors
125+ }
126+ }
127+
128+ // Fetch all records matching the query
129+ allMatchingUsers, err := result.All()
130+ ```
131+
132+ - **Counting Records:**
133+ To get the total number of records matching the query conditions:
134+ ```go
135+ count, err := result.Count() // Uses cache
136+ ```
137+
138+ - **Including Soft-Deleted Records:**
139+ By default, queries exclude soft-deleted records. To include them, use `WithDeleted()` on a `CachedResult[T]`:
140+ ```go
141+ resultIncludingDeleted := result.WithDeleted()
142+ // Now, Fetch(), First(), All(), Count() on resultIncludingDeleted will include soft-deleted items.
143+ // Example:
144+ // usersIncludingDeleted, err := resultIncludingDeleted.Fetch(0, 10)
145+ ```
146+
147+ - **Fetching All Records of a Model (Simplified):**
148+ To get all records for a model type without any specific query conditions:
149+ ```go
150+ allUsersOfType, err := userThing.All()
151+ // This is a convenient alias for userThing.Query(thing.QueryParams{}).All()
152+ ```
153+
154+## Key Features
155+- **Caching:** Mostly automatic for `ByID` and `Query`. Monitor via `cacheClient.GetCacheStats(ctx)`.
156+- **Relationships:** Define using `thing` struct tags (`hasMany`, `belongsTo`, `manyToMany`) and preload using `QueryParams.Preloads`. Always use `db:"-"` on relation fields in your struct, as these fields are populated by the ORM and do not correspond directly to a column in the model's own table.
157+
158+ - **`hasMany`**: One-to-many relationship.
159+ ```go
160+ // User has many Books.
161+ // Assumes 'books' table has a 'user_id' foreign key.
162+ type User struct {
163+ thing.BaseModel
164+ // ... other fields
165+ Books []*Book `thing:"hasMany;fk:user_id;model:Book" db:"-"`
166+ }
167+
168+ type Book struct {
169+ thing.BaseModel
170+ UserID uint `db:"user_id"` // Foreign key
171+ Title string `db:"title"`
172+ // ... other fields
173+ }
174+ ```
175+
176+ - **`belongsTo`**: Inverse of one-to-many.
177+ ```go
178+ // Book belongs to a User.
179+ // Assumes 'books' table has a 'user_id' foreign key.
180+ type Book struct {
181+ thing.BaseModel
182+ UserID uint `db:"user_id"` // Foreign key
183+ Title string `db:"title"`
184+ User *User `thing:"belongsTo;fk:user_id;model:User" db:"-"` // 'model' is optional here if type matches
185+ // ... other fields
186+ }
187+
188+ type User struct {
189+ thing.BaseModel
190+ Name string `db:"name"`
191+ // ... other fields
192+ }
193+ ```
194+
195+ - **`manyToMany`**: Many-to-many relationship.
196+ Requires a join table.
197+ ```go
198+ // User has and belongs to many Roles.
199+ // Roles has and belongs to many Users.
200+ // Join table: 'user_roles' with 'user_id' (links to users.id) and 'role_id' (links to roles.id).
201+ type User struct {
202+ thing.BaseModel
203+ Name string `db:"name"`
204+ Roles []*Role `thing:"manyToMany;model:Role;joinTable:user_roles;joinLocalKey:user_id;joinRelatedKey:role_id" db:"-"`
205+ // ... other fields
206+ }
207+
208+ type Role struct {
209+ thing.BaseModel
210+ Name string `db:"name"`
211+ Users []*User `thing:"manyToMany;model:User;joinTable:user_roles;joinLocalKey:role_id;joinRelatedKey:user_id" db:"-"`
212+ // ... other fields
213+ }
214+
215+ // Join table model (optional to define as a struct, but table must exist)
216+ // type UserRole struct {
217+ // UserID uint `db:"user_id"`
218+ // RoleID uint `db:"role_id"`
219+ // }
220+ // func (ur *UserRole) TableName() string { return "user_roles" }
221+ ```
222+ Preloading relationships:
223+ ```go
224+ // Preload Books for a User
225+ // users, err := userThing.Query(thing.QueryParams{Preloads: []string{"Books"}}).Fetch(0,10)
226+
227+ // Preload Roles for a User (manyToMany)
228+ // users, err := userThing.Query(thing.QueryParams{Preloads: []string{"Roles"}}).Fetch(0,10)
229+ ```
230+- **Hooks:** Register listeners for events (e.g., `BeforeSave`, `AfterCreate`) using `thing.RegisterListener`.
231+- **Auto Migration:** `thing.AutoMigrate(&User{}, &Book{})` creates/updates tables based on model struct tags (`db`).
232+- **JSON Serialization:** `thing.ToJSON(model, options...)`. Use `thing.WithFields("field1,nested{subfield},-excluded")` for flexible control, including method-based virtual properties (e.g., `FullName() string` -> `full_name` in DSL).
233+- **Raw SQL:** Access via `dbAdapter := userThing.DBAdapter()`, then use `dbAdapter.Exec`, `dbAdapter.Get`, `dbAdapter.Select`.
234+
235+## Focus
236+- Optimized for common CRUD and list retrieval patterns.
237+- Complex JOINs or database-specific features might require Raw SQL.
