backbone-core 3.0.2

Backbone Framework Core - Foundation for generic CRUD system
Documentation
# Backbone Core

**Status:** โœ… FULLY IMPLEMENTED
**Last Updated:** 2026-06-06

๐Ÿฆด **Foundation for generic CRUD system with 11 standard endpoints**

Backbone Core is a foundational library that provides generic CRUD (Create, Read, Update, Delete) operations for any entity in Backbone Framework. It implements **11 standard Backbone endpoints** consistently across both HTTP REST and gRPC protocols.

## ๐Ÿ“‹ Table of Contents

- [Overview]#overview
- [Features]#features
- [Usage]#usage
- [Technical Details]#technical-details
- [Examples]#examples
- [Testing]#testing
- [Contributing]#contributing

## ๐Ÿ“– Documentation

In-depth guides live in [`docs/`](docs/README.md):

- [architecture.md]docs/architecture.md โ€” how the crate is structured
- [usage.md]docs/usage.md โ€” wire up CRUD for your entity, with examples
- [api-reference.md]docs/api-reference.md โ€” every endpoint, query param, and response shape
- [configuration.md]docs/configuration.md โ€” feature flags and runtime limits
- [openapi.md]docs/openapi.md โ€” generate & serve an OpenAPI/Swagger spec (feature `openapi`)

## ๐ŸŽฏ Overview

Backbone Core is a **protocol-agnostic, generic CRUD foundation** that enables any service to automatically get:

- โœ… **11 Standard Backbone endpoints** for every entity
- โœ… **Both HTTP REST and gRPC** protocol support
- โœ… **Pagination, filtering, and sorting**
- โœ… **Bulk operations and soft delete**
- โœ… **Type-safe generic implementations**
- โœ… **JSON or form-encoded request bodies** (auto-detected by `Content-Type`)
- โœ… **Production-ready error handling**
- โœ… **Optional OpenAPI/Swagger schema generation** (feature `openapi`, default-off)

### The 11 Backbone Endpoints

| # | HTTP Method | HTTP Endpoint | gRPC Method | Purpose |
|---|-------------|---------------|-------------|---------|
| 1 | `GET` | `/api/v1/{collection}` | `list()` | List with pagination, filtering, sorting |
| 2 | `POST` | `/api/v1/{collection}` | `create()` | Create new entity |
| 3 | `GET` | `/api/v1/{collection}/:id` | `get_by_id()` | Get entity by ID |
| 4 | `PUT` | `/api/v1/{collection}/:id` | `update()` | Full entity update |
| 5 | `PATCH` | `/api/v1/{collection}/:id` | `partial_update()` | Partial update (selected fields) |
| 6 | `DELETE` | `/api/v1/{collection}/:id` | `soft_delete()` | Soft delete (mark as deleted) |
| 7 | `POST` | `/api/v1/{collection}/bulk` | `bulk_create()` | Create multiple entities |
| 8 | `POST` | `/api/v1/{collection}/upsert` | `upsert()` | Update or insert if not exists |
| 9 | `GET` | `/api/v1/{collection}/trash` | `list_deleted()` | List deleted entities |
| 10 | `POST` | `/api/v1/{collection}/:id/restore` | `restore()` | Restore soft-deleted entity |
| 11 | `DELETE` | `/api/v1/{collection}/empty` | `empty_trash()` | Permanently delete all trash |

### Atomic batch endpoints

All-or-nothing operations applied inside a single transaction: ids are validated
up-front, and if any id is missing (or already in the target state) the whole
batch is rolled back with `400 Bad Request` โ€” no partial writes. Batches are
capped at `MAX_BATCH_SIZE` (1,000) items; larger payloads are rejected with
`400`. Lifecycle hooks and CRUD events fire per affected entity.

| HTTP Method | HTTP Endpoint | Service method | Purpose |
|-------------|---------------|----------------|---------|
| `PUT` | `/api/v1/{collection}/bulk` | `bulk_update()` | Full-update many entities (`[{ "id", ...fields }]`) |
| `PATCH` | `/api/v1/{collection}/bulk` | `bulk_partial_update()` | Partial-update many โ€” shared `{ ids, patch }` or per-id `{ items: [{ id, patch }] }` |
| `POST` | `/api/v1/{collection}/delete/bulk` | `bulk_soft_delete()` | Soft-delete many by id (`{ "ids": [...] }`) |
| `POST` | `/api/v1/{collection}/restore/bulk` | `bulk_restore()` | Restore many soft-deleted by id |
| `POST` | `/api/v1/{collection}/restore/all` | `restore_all()` | Restore every soft-deleted entity |
| `DELETE` | `/api/v1/{collection}/trash/bulk` | `bulk_permanent_delete()` | Permanently delete many trashed by id |

## ๐Ÿš€ Features

### ๐Ÿ”„ Protocol Agnostic
- **HTTP REST**: Standard REST endpoints with JSON responses
- **gRPC**: High-performance RPC with Protocol Buffers
- **Same interface**: Both protocols provide identical functionality

### ๐Ÿ“Š Advanced CRUD Operations
- **Pagination**: Automatic pagination with metadata
- **Depth-capped pagination**: list/query endpoints return `400 Bad Request` once
  a request would page beyond `MAX_PAGINATION_OFFSET` (10,000 rows โ€” ~100 pages
  at the `MAX_PER_PAGE` clamp of 100), avoiding expensive deep `OFFSET` scans and
  prompting clients to add filters instead.
- **Filtering**: HashMap-based field filtering
- **Sorting**: Multi-field sorting with configurable order
- **Sparse fieldsets**: read endpoints (`list`, `get_by_id`, `list_deleted`,
  `get_deleted_by_id`) accept `?fields=a,b,c` to trim each response object to the
  requested top-level keys plus the always-on `id`. Comma-separated, whitespace-
  trimmed; unknown keys are ignored and an empty/absent value returns every field.
  `fields`, `include`, and `with` are reserved query keys โ€” stripped before
  filters reach the repository, so they never leak into the `WHERE` clause.
- **Relation expansion (`?include=`)**: `list` and `get_by_id` accept
  `?include=<rel>` (alias `?with=`) to hydrate declared to-one relations,
  injecting each related row as a sibling object keyed by the relation name.
  Comma-separated; only relations the entity declares are honored, unknown names
  are ignored. Batched โ€” one `WHERE id = ANY(...)` per relation across the whole
  page (no N+1). Opt in per entity via the `EntityRepoMeta::relations()` hook
  (default none). Runs **after** field security and **before** sparse projection.
  *v1 limitation:* the expanded object is the raw related row (keys camelCased),
  not run through the target's response DTO or its `@private` security โ€” don't
  enable it for a target with private fields. See
  [docs/api-reference.md]docs/api-reference.md#relation-expansion-include.
- **Field-level security (`@private` / `@owner`)**: read endpoints strip an
  entity's `@private` response fields unless the caller may see them. An
  injectable `AccessScope` (`Platform` | `Tenant(id)`, read from an axum
  `Extension` set by your auth middleware) decides visibility: `Platform` sees
  all, `Tenant(id)` sees private fields only when the row's `@owner` field equals
  `id`, and an absent scope **fails closed**. Opt in per entity via the
  `EntityRepoMeta::private_fields()` / `owner_field()` hooks (default no-op).
  Enforced **before** sparse projection, so it always beats a `?fields=` request.
  See [docs/api-reference.md]docs/api-reference.md#field-level-security-private--owner.
- **Client-error aware**: list/query endpoints return `400 Bad Request` (not
  `500`) when a bad filter or sort key produces a Postgres
  `column "..." does not exist` (SQLSTATE 42703) or `invalid input syntax`
  error โ€” e.g. a typo or a stray camelCase param like `sortOrder`.
- **Bulk Operations**: Efficient bulk create and upsert
- **Atomic batch operations**: transactional, all-or-nothing bulk update / partial-update /
  soft-delete / restore / permanent-delete โ€” a missing id rolls back the whole batch
  (`400`), and batches are capped at `MAX_BATCH_SIZE` (1,000)
- **Soft Delete**: Trash management with restore functionality

### ๐Ÿ“จ Flexible Request Bodies
- **JSON or form**: `BackboneCrudHandler` decodes create/update/upsert/bulk bodies
  via the [`JsonOrForm`]src/extractors.rs extractor, accepting both
  `application/json` and `application/x-www-form-urlencoded`.
- **Lenient default**: falls back to JSON when the `Content-Type` is missing or
  unrecognized.
- **Drop-in**: use `JsonOrForm(x): JsonOrForm<T>` anywhere you would write
  `Json(x): Json<T>` in your own handlers.

### ๐Ÿ›ก๏ธ Type Safety
- **Generic Types**: Works with any entity implementing `Entity` trait
- **Compile-time Safety**: Catch errors at compile time, not runtime
- **Proper Error Handling**: Comprehensive `anyhow::Error` support

## ๐Ÿ“– Usage

### 1. Add Dependency

```toml
[dependencies]
backbone-core = "2.0.0"
```

### 2. Define Your Entity

```rust
use backbone_core::entity::*;
use serde::{Serialize, Deserialize};
use uuid::Uuid;
use chrono::{DateTime, Utc};

#[derive(Debug, Clone, Serialize, Deserialize)]
struct User {
    id: Uuid,
    name: String,
    email: String,
    created_at: DateTime<Utc>,
    updated_at: DateTime<Utc>,
    deleted_at: Option<DateTime<Utc>>,
}

impl Entity for User {
    fn id(&self) -> &Uuid { &self.id }
    fn created_at(&self) -> DateTime<Utc> { self.created_at }
    fn updated_at(&self) -> DateTime<Utc> { self.updated_at }
    fn deleted_at(&self) -> Option<DateTime<Utc>> { self.deleted_at }
}
```

### 3. Implement HTTP Handler

```rust
use backbone_core::http::*;
use backbone_core::entity::*;

struct UserService;

impl BackboneHttpHandler<User> for UserService {
    fn list(&self, request: ListRequest) -> Result<ApiResponse<Vec<User>>> {
        let users = vec![]; // Fetch from database
        Ok(ApiResponse::success(users))
    }

    fn create(&self, request: User) -> Result<ApiResponse<User>> {
        Ok(ApiResponse::success(request))
    }
}
```

### 4. Implement gRPC Service

```rust
use backbone_core::grpc::*;

struct UserGrpcService;

impl BackboneGrpcService<User> for UserGrpcService {
    fn list(&self, request: GrpcListRequest) -> Result<GrpcResponse<GrpcListResponse<User>>> {
        let response = GrpcListResponse {
            items: users,
            total: users.len() as u64,
        };
        Ok(GrpcResponse::success(response))
    }
}
```

## ๐Ÿ”ง Technical Details

### Core Traits

#### Entity Trait
```rust
pub trait Entity: Serialize + for<'de> Deserialize<'de> {
    fn id(&self) -> &Uuid;
    fn created_at(&self) -> DateTime<Utc>;
    fn updated_at(&self) -> DateTime<Utc>;
    fn deleted_at(&self) -> Option<DateTime<Utc>>;
}
```

#### Repository Traits
- **Repository<T>** - Basic CRUD operations
- **SearchableRepository<T>** - Search and filtering
- **SoftDeletableRepository<T>** - Soft delete operations
- **PaginatedRepository<T>** - Pagination support
- **BulkRepository<T>** - Bulk operations

## ๐Ÿ“š Examples

### Pagination with Filtering

```rust
let list_request = ListRequest {
    page: Some(1),
    limit: Some(20),
    sort_by: Some("name".to_string()),
    filters: Some(HashMap::from([
        ("status".to_string(), "active".to_string()),
    ("role".to_string(), "admin".to_string()),
    ])),
};
```

## ๐Ÿงช Testing

### Run Tests

```bash
# Run all tests
cargo test

# Run integration tests (requires database)
cargo test --test '*integration_tests*'
```

## ๐Ÿ”— Dependencies

```toml
[dependencies]
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
uuid = { version = "1.0", features = ["v4", "serde"] }
chrono = { version = "0.4", features = ["serde"] }
anyhow = "1.0"
thiserror = "1.0"

# gRPC
tonic = "0.12"
prost = "0.13"

# Web Framework
axum = "0.7"
tower = "0.4"
```

### Feature flags

All optional capabilities are **off by default** (this crate is plumbing โ€” pull only what
you need). See [docs/configuration.md](docs/configuration.md) for the full matrix.

| Feature | Enables |
|---------|---------|
| `postgres` / `database` | `sqlx`-backed `PostgresRepository` |
| `prost` | Protobuf well-known types for gRPC |
| `openapi` | `utoipa::ToSchema` on the HTTP types + `openapi::BackboneComponents` |
| `full` | `postgres` + `prost` |

## ๐Ÿค Contributing

1. Follow existing code style
2. Write tests for new features
3. Update documentation

## ๐Ÿ“„ License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

---

**๐Ÿฆด Backbone Core - Foundation for generic CRUD operations**