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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,6 @@ site/

# Reference repos
typeql-ref/
type-bridge-core/target/
type-bridge-core/**/target/
*.profraw
*.profdata
110 changes: 90 additions & 20 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,124 @@

All notable changes to TypeBridge will be documented in this file.

## [Unreleased]

## [1.4.1] - 2026-03-03

### New Features

#### Lifecycle Hook System (PR #118, closes #116)

Three-layer hook system for reacting to CRUD lifecycle events (audit logging, validation, cache invalidation, async notifications).

##### Python ORM — `type_bridge.crud.hooks`
- **`CrudEvent` enum** — `PRE_INSERT`, `POST_INSERT`, `PRE_UPDATE`, `POST_UPDATE`, `PRE_DELETE`, `POST_DELETE`, `PRE_PUT`, `POST_PUT`
- **`LifecycleHook` protocol** — implement only the methods you need (`pre_insert`, `post_delete`, etc.)
- **`HookCancelled` exception** — raise in a pre-hook to abort the operation
- **Per-manager registration** — `manager.add_hook(hook)` / `manager.remove_hook(hook)`, chainable
- **`should_run(event, sender)`** filtering by event type or model class
- Pre-hooks run in registration order; post-hooks run in reverse order (middleware unwinding)
- Zero overhead when no hooks are registered

##### Rust ORM — `type-bridge-orm`
- **`LifecycleHook` trait** with `HookContext`, `PreHookResult` (Continue/Reject), and `HookRunner`
- Integrated into `EntityManager` and `RelationManager` via `add_hook()`
- Same semantics as the Python layer (registration-order pre-hooks, reverse-order post-hooks)

##### Rust Server — `type-bridge-server`
- **`CrudInfo`** on `RequestContext` — operation, type_name, type_kind, attribute_names, iid
- **`CrudInterceptor` trait** with `on_crud_request` / `on_crud_response` and `should_intercept`
- **`CrudInterceptorAdapter`** bridges `CrudInterceptor` into the existing `Interceptor` chain

#### Put (Upsert) Clause — `type-bridge-core-lib`
- **Added `Clause::Put(Vec<Statement>)` variant** for idempotent insert (upsert) operations
- Parser: `parse_put_clause` with keyword lookahead in both `parse_patterns` and `parse_statements`
- Compiler: Generates `put\n<statements>;` TypeQL output
- Validation: Reuses Insert validation rules (`Clause::Insert | Clause::Put`)
- Full roundtrip support (parse → AST → compile → parse)

### Refactoring

#### Server API Simplification — `type-bridge-server`
- **Removed standalone CRUD module** (`/entities/*`, `/relations/*` endpoints, `CrudQueryBuilder`, raw query support) — superseded by the interceptor-based design
- **Removed `crud_builder` benchmark** and `criterion` dev-dependency
- **Server now has 4 endpoints**: `POST /query`, `POST /query/validate`, `GET /health`, `GET /schema`

## [1.4.0] - 2026-02-20

### Highlights

- 5 new Rust crates: `core-lib`, `orm`, `orm-derive`, `server`, and `python` (PyO3 bindings)
- Up to **40x faster validation** and **2.5x faster query compilation** via the Rust backend
- Async Rust ORM with derive macros, chainable queries, and batch operations
- Query-intercepting proxy server with REST endpoints
- MkDocs Material documentation site

### New Features

#### Rust Core Integration (PRs #95, #101–#107)
#### Rust Core — `type-bridge-core-lib` (PRs #95, #101-#108)
- **TypeQL schema parser** with inheritance resolution and PyO3 bindings
- **TypeQL query parser** with bidirectional AST roundtrip
- **Schema-aware query validation** with PyO3 bindings
- **Schema-aware query validation** with statement and pattern validators
- **Rust-backed value coercion** and `format_value`
- **Custom validation rules** with portable JSON DSL
- **Wired Rust core into Python** compiler and validation pipeline

#### Rust ORM — `type-bridge-orm` (PR #114)
- **Async Rust ORM** with entity CRUD and mock-testable session layer
- **Derive macros** — `TypeBridgeEntity`, `TypeBridgeAttribute`, `TypeBridgeRelation`
- **Relation support** with update/put operations
#### Async Rust ORM — `type-bridge-orm` (PR #114)
- **Async ORM** with entity CRUD and mock-testable session layer
- **Derive macros** — `#[derive(TypeBridgeEntity)]`, `#[derive(TypeBridgeRelation)]`, `#[derive(TypeBridgeAttribute)]`
- **Chainable query builders** with expression filtering and aggregation
- **Schema management** — registration, generation, diff, and sync
- **Abstract types, inheritance, and code generator**
- **Abstract types** with inheritance and code generation
- **Batch operations** — `insert_many`, `delete_many`, `update_many`
- **`FieldRef<A>`** for type-safe query field references
- **`include_schema!` proc-macro** for compile-time TQL codegen
- **`include_schema!`** proc-macro for compile-time TQL codegen
- **Schema introspection** from live TypeDB database
- **Group-by queries** with `GroupByResult`
- **Role player field access** for relation query filtering
- **Expression helpers** — `in_range`, `startswith`, `endswith`
- **Connection pooling** with `Database::into_shared`
- **Expression helpers** — `in_range()`, `startswith()`, `endswith()`
- **Connection pooling** with `Database::into_shared()`
- **Serde support** on all ORM model types
- **Structured tracing spans** on all public methods
- **Structured tracing** spans on all public methods

#### Query Intercept Proxy Server — `type-bridge-server` (PR #109)
#### Query Intercept Proxy — `type-bridge-server` (PR #109)
- **REST CRUD endpoints** with schema-aware query building
- **`CrudQueryBuilder` PyO3 class** for TypeQL generation
- **Extensible library/framework architecture**
- **207 tests** with 100% MC/DC coverage and CI codecov integration
- **`CrudQueryBuilder` PyO3 class** for TypeQL generation from Python
- **Extensible library/framework** — pluggable `QueryExecutor`, `Interceptor`, `SchemaSource`
- **207 tests** with 100% MC/DC coverage

### Improvements
### Performance: Python vs Rust

#### Validation

| Operation | Python | Rust | Speedup |
|-----------|--------|------|---------|
| Single type name | 1.89 us | 354.75 ns | **5.3x** |
| Long name (100+ chars) | 24.90 us | 620.52 ns | **40.1x** |
| Batch 1,000 names | 5.32 ms | 266.80 us | **19.9x** |
| Batch 5,000 names | 28.02 ms | 1.33 ms | **21.1x** |

#### Query Compilation (via serde bridge)

| Operation | Python | Rust | Speedup |
|-----------|--------|------|---------|
| Standalone update | 93.28 us | 37.82 us | **2.5x** |
| Large batch (200 clauses) | 2.26 ms | 1.07 ms | **2.1x** |
| Heavy insert (100x6) | 1.71 ms | 896.19 us | **1.9x** |

### Documentation & CI

#### Documentation & CI
- **MkDocs + Material** documentation site with auto-generated API reference (PR #98)
- **Rust crate CI** and multi-platform wheel builds (PR #95)
- **Comprehensive benchmark suite** with TOML storage and diff support (PR #103)
- Full documentation and metadata polish for Rust core
- **Rust crate CI** with multi-platform wheel builds — Linux (x86_64, aarch64), macOS (x86_64, aarch64), Windows (x86_64) (PR #95)
- **Comprehensive benchmark suite** with TOML storage, comparison reports, and markdown generation (PR #103)
- **Codecov integration** for Rust coverage tracking (PR #109)

### Bug Fixes

- Resolve Rust 1.93.0 clippy lint errors
- Pin Python 3.13 for Rust CI jobs and fix coverage script
- Add version specifiers to inter-crate path dependencies for crates.io publishing
- Make release workflow idempotent (skip already-published packages)

## [1.3.0] - 2026-02-09

Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ type_bridge/
│ ├── base.py # Type variables (E, R)
│ ├── utils.py # Shared utilities (format_value, is_multi_value_attribute)
│ ├── exceptions.py # CRUD exceptions
│ ├── hooks.py # Lifecycle hooks (CrudEvent, HookCancelled, CrudHook, HookRunner)
│ ├── entity/ # Entity CRUD operations
│ │ ├── __init__.py # Entity module exports
│ │ ├── manager.py # EntityManager class
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ A modern, Pythonic ORM for [TypeDB](https://github.com/typedb/typedb) with an At
- **Data Validation**: Automatic type checking and coercion via Pydantic, including keyword validation
- **JSON Support**: Seamless JSON serialization/deserialization
- **CRUD Operations**: Full CRUD with fetching API (get, filter, all, update) for entities and relations
- **Lifecycle Hooks**: Pre/post-operation hooks for audit logging, validation, cache invalidation, and async notifications
- **Chainable Operations**: Filter, delete, and bulk update with method chaining and lambda functions
- **Query Builder**: Pythonic interface for building TypeQL queries
- **Multi-player Roles**: A single role can accept multiple entity types via `Role.multi(...)`
Expand Down
107 changes: 107 additions & 0 deletions docs/guide/crud.md
Original file line number Diff line number Diff line change
Expand Up @@ -1532,6 +1532,113 @@ def test_database_operations():
mock_driver.databases.contains.assert_called_with("test_db")
```

## Lifecycle Hooks

Hooks let you react to CRUD events for cross-cutting concerns — audit logging, input validation, cache invalidation, auto-populating fields, and more.

### Quick Example

```python
from type_bridge import CrudEvent, HookCancelled

class AuditHook:
"""Log every write operation."""

def post_insert(self, sender, instance):
print(f"[insert] {sender.__name__} iid={instance.iid}")

def post_update(self, sender, instance):
print(f"[update] {sender.__name__} iid={instance.iid}")

def post_delete(self, sender, instance):
print(f"[delete] {sender.__name__} iid={instance.iid}")


manager = Person.manager(db)
manager.add_hook(AuditHook()) # chainable — returns self
```

### Events

The `CrudEvent` enum covers all eight lifecycle points:

| Event | When it fires |
|-------|---------------|
| `PRE_INSERT` | Before inserting an entity/relation |
| `POST_INSERT` | After a successful insert |
| `PRE_UPDATE` | Before updating |
| `POST_UPDATE` | After a successful update |
| `PRE_DELETE` | Before deleting |
| `POST_DELETE` | After a successful delete |
| `PRE_PUT` | Before an idempotent put (upsert) |
| `POST_PUT` | After a successful put |

### Writing a Hook

Hooks are **duck-typed** — implement only the methods you need. No base class required.

```python
class TimestampHook:
"""Auto-populate created_at on insert."""

def pre_insert(self, sender, instance):
if hasattr(instance, "created_at") and instance.created_at is None:
instance.created_at = CreatedAt(datetime.now(timezone.utc))
```

### Cancelling Operations

Raise `HookCancelled` in any pre-hook to abort the operation:

```python
class EmailDomainValidator:
def __init__(self, domain: str):
self.domain = domain

def pre_insert(self, sender, instance):
if hasattr(instance, "email"):
if not instance.email.value.endswith(f"@{self.domain}"):
raise HookCancelled(f"Email must end with @{self.domain}")

def pre_update(self, sender, instance):
self.pre_insert(sender, instance) # same logic
```

### Filtering with `should_run`

Implement `should_run(event, sender)` to restrict when a hook fires:

```python
class PersonOnlyHook:
def should_run(self, event, sender):
return sender.__name__ == "Person"

def post_insert(self, sender, instance):
print(f"New person: {instance}")
```

Without `should_run`, hooks run for every event on every model.

### Registration and Composition

```python
manager = (
Person.manager(db)
.add_hook(TimestampHook())
.add_hook(EmailDomainValidator("company.com"))
.add_hook(AuditHook())
)

# Remove a hook later
manager.remove_hook(audit_hook)
```

### Execution Order

- **Pre-hooks** run in registration order. If any raises `HookCancelled`, the operation is aborted.
- **Post-hooks** run in **reverse** registration order (middleware unwinding). Post-hook errors are logged but do not propagate.
- **Zero overhead** when no hooks are registered — the runner short-circuits on an empty hook list.

## See Also

- [Entities](entities.md) - Entity definition
Expand Down
3 changes: 3 additions & 0 deletions docs/guide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ alice = Person(name=Name("Alice"), age=Age(30))
person_manager = Person.manager(db)
person_manager.insert(alice)
persons = person_manager.all()

# 5. Add lifecycle hooks (optional)
person_manager.add_hook(my_audit_hook) # chainable
```

## Key Principles
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "type-bridge"
version = "1.4.0"
version = "1.4.1"
description = "A modern, Pythonic ORM for TypeDB with an Attribute-based API"
readme = "README.md"
requires-python = ">=3.13"
Expand All @@ -29,7 +29,7 @@ dependencies = [
"lark>=1.1.9",
"jinja2>=3.1.0",
"typer>=0.15.0",
"type-bridge-core>=1.4.0",
"type-bridge-core>=1.4.1",
]

[project.urls]
Expand Down
Loading
Loading