From 656f50a3ad25e48e2ee2537aab0cb6bb8b39779c Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 2 Apr 2026 18:27:12 -0400 Subject: [PATCH 1/3] rename activation field to always --- assert-struct/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assert-struct/SKILL.md b/assert-struct/SKILL.md index 115e79b..cd064fd 100644 --- a/assert-struct/SKILL.md +++ b/assert-struct/SKILL.md @@ -2,7 +2,7 @@ name: assert-struct-guidance description: Always use this skill before writing any test code in the Toasty repository advice-for: assert-struct -activation: default +activation: always --- The `assert-struct` crate helps to write concise assertions for the values of struct fields. From 47c5999de066b45458ea6668127bca913e1f095a Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 3 Apr 2026 11:10:06 -0400 Subject: [PATCH 2/3] rename advice-for to crates --- assert-struct/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assert-struct/SKILL.md b/assert-struct/SKILL.md index cd064fd..faa97f0 100644 --- a/assert-struct/SKILL.md +++ b/assert-struct/SKILL.md @@ -1,7 +1,7 @@ --- name: assert-struct-guidance description: Always use this skill before writing any test code in the Toasty repository -advice-for: assert-struct +crates: assert-struct activation: always --- From 3070668da18a8e3fb964684429180ac2f3e06d29 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 3 Apr 2026 11:10:12 -0400 Subject: [PATCH 3/3] add toasty skill --- toasty/SKILL.md | 221 ++++++++++++++++++++++++++++++ toasty/resources/LLM.txt | 283 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 504 insertions(+) create mode 100644 toasty/SKILL.md create mode 100644 toasty/resources/LLM.txt diff --git a/toasty/SKILL.md b/toasty/SKILL.md new file mode 100644 index 0000000..0a99c49 --- /dev/null +++ b/toasty/SKILL.md @@ -0,0 +1,221 @@ +--- +name: toasty-guidance +description: Guidance for using the Toasty async ORM crate — schema definition, CRUD, relations, queries, and transactions +crates: toasty +activation: always +--- + +Toasty is an async ORM for Rust supporting SQL (SQLite, PostgreSQL, MySQL) and NoSQL (DynamoDB). It prioritizes type safety and leans into each database's capabilities rather than hiding them. + +# Schema definition + +Define models with `#[derive(toasty::Model)]`. Mark the primary key with `#[key]` and auto-generated fields with `#[auto]` (auto-increment for integers, UUID v7 for `uuid::Uuid`). + +```rust +#[derive(Debug, toasty::Model)] +struct User { + #[key] + #[auto] + id: i64, + + #[unique] + email: String, + + name: String, + + #[has_many] + todos: toasty::HasMany, +} + +#[derive(Debug, toasty::Model)] +struct Todo { + #[key] + #[auto] + id: i64, + + #[index] + user_id: i64, + + #[belongs_to(key = user_id, references = id)] + user: toasty::BelongsTo, + + title: String, +} +``` + +Use `#[derive(toasty::Embed)]` for value types that flatten into the parent table: + +```rust +#[derive(toasty::Embed)] +struct Address { + street: String, + city: String, +} +``` + +# Database setup + +Register all models with the builder and connect: + +```rust +let db = toasty::Db::builder() + .register::() + .register::() + .connect("sqlite://memory") + .await?; +``` + +Connection strings: `sqlite://memory`, `sqlite:///path/to/db`, `postgresql://user:pass@host/db`, `mysql://user:pass@host/db`, `dynamodb://region`. + +# CRUD operations + +## Create + +```rust +let user = User::create() + .name("Alice") + .email("alice@example.com") + .todo(Todo::create().title("Task 1")) // nested create + .exec(&mut db) + .await?; +``` + +## Read + +```rust +// By primary key (generated finder) +let user = User::get_by_id(&mut db, &id).await?; + +// All records +let users = User::all().exec(&mut db).await?; + +// Filter +let users = User::all() + .and(User::fields().name().eq("Alice")) + .exec(&mut db) + .await?; + +// Single result +let user = User::all() + .and(User::fields().email().eq("a@b.com")) + .one() + .exec(&mut db) + .await?; + +// Optional result +let user = User::all() + .and(User::fields().email().eq("a@b.com")) + .first() + .exec(&mut db) + .await?; + +// Count +let n = User::all().count().exec(&mut db).await?; +``` + +## Update + +```rust +user.update() + .name("Bob") + .exec(&mut db) + .await?; +``` + +## Delete + +```rust +User::all() + .and(User::fields().active().eq(false)) + .delete() + .exec(&mut db) + .await?; +``` + +# Query expressions + +Build type-safe filters using generated field paths: + +```rust +User::fields().age().gt(18) +User::fields().age().ge(21) +User::fields().name().ne("admin") +User::fields().id().is_in([1, 2, 3]) +User::fields().phone().is_some() +User::fields().phone().is_none() + +// Combine with boolean logic +User::fields().age().gt(18).and(User::fields().active().eq(true)) +``` + +The `query!` macro offers a shorthand: + +```rust +toasty::query!(User filter .name == "Alice" && .age > 18) +``` + +# Relations + +Three relation types: `HasMany`, `HasOne`, `BelongsTo`. + +**Lazy loading** (default) -- access a relation to load it on demand: + +```rust +let todos = user.todos().exec(&mut db).await?; +let owner = todo.user().exec(&mut db).await?; +``` + +**Eager loading** -- preload with `.include()`: + +```rust +let users = User::all() + .include(User::fields().todos()) + .exec(&mut db) + .await?; +``` + +# Pagination + +Cursor-based pagination: + +```rust +let page = User::all().paginate(10).exec(&mut db).await?; +for u in &page.items { /* ... */ } +if page.has_next() { + let next = page.next(&mut db).await?; +} +``` + +# Transactions + +```rust +let mut tx = db.transaction().await?; +User::create().name("Alice").exec(&mut tx as &mut dyn toasty::Executor).await?; +tx.commit().await?; +// Drop without commit = automatic rollback +``` + +Nested transactions (savepoints) are supported via `tx.transaction().await?`. + +# Batching + +Execute independent statements together: + +```rust +use toasty::batch; + +let (users, posts) = batch(( + User::all(), + Post::all(), +)).exec(&mut db).await?; +``` + +# Key design points + +- The `Executor` trait is implemented by both `Db` and `Transaction` -- pass `&mut db` or `&mut tx` interchangeably. +- `Db` owns a connection pool (deadpool) and is cheap to clone. +- For a dedicated connection, use `db.connection().await?`. +- Relations are **unloaded by default**; calling `.get()` before loading panics. Use `.include()` for eager loading or call the relation method to load lazily. +- Statement type parameter `T` is the **returning type**, not the model (e.g., `Query>` returns `Vec`). + +See the `LLM.txt` resource within this skill for the full API reference. diff --git a/toasty/resources/LLM.txt b/toasty/resources/LLM.txt new file mode 100644 index 0000000..1bd35cb --- /dev/null +++ b/toasty/resources/LLM.txt @@ -0,0 +1,283 @@ +Toasty v0.2.0 — Async ORM for Rust (SQL + NoSQL) +================================================== + +1 MODEL DEFINITION +-------------------- + +Derive macro: #[derive(toasty::Model)] +Embed macro: #[derive(toasty::Embed)] + +Field attributes: + #[key] — primary key + #[auto] — auto-generated (auto-increment for integers, UUID v7 for uuid::Uuid) + #[unique] — unique constraint + #[index] — database index + #[has_many] — one-to-many relation, field type: toasty::HasMany + #[has_one] — one-to-one relation, field type: toasty::HasOne or toasty::HasOne> + #[belongs_to(key = FK, references = PK)] — many-to-one, field type: toasty::BelongsTo + #[serialize(json)] — JSON serialization (requires "serde" feature) + +Embedded types flatten fields into the parent table. Embedded enums store as discriminants. + +Supported field types: + Primitives — i8..i64, u8..u64, isize, usize, bool, String, Vec + Optional — Option + Smart ptrs — Box, Arc, Cow<'_, T> + UUID — uuid::Uuid (feature: uuid) + Decimal — rust_decimal::Decimal, bigdecimal::BigDecimal (features) + DateTime — jiff types (feature: jiff) + JSON — serde_json::Value (feature: serde) + + +2 DATABASE SETUP +------------------ + +let db = toasty::Db::builder() + .register::() + .register::() + .connect(URL) // or .build(driver) + .await?; + +Connection strings: + sqlite://memory + sqlite:///path/to/file.db + postgresql://user:pass@host/dbname + mysql://user:pass@host/dbname + dynamodb://region + +Direct driver construction: + toasty_driver_sqlite::Sqlite::in_memory() + +Pool: Db owns a deadpool connection pool. Clone is cheap (Arc-based). +Dedicated connection: db.connection().await? — returns Connection. + + +3 EXECUTOR TRAIT +------------------ + +pub trait Executor: Send + Sync { + async fn transaction(&mut self) -> Result>; + async fn exec_untyped(&mut self, stmt: Statement) -> Result; + fn schema(&mut self) -> &Arc; +} + +Implementors: Db, Transaction, Connection. + +Call pattern: stmt.exec(&mut executor).await? + + +4 CREATE (INSERT) +------------------- + +Builder pattern (generated per model): + + ModelName::create() + .field_name(value) + .relation_name(RelatedModel::create().field(val)) // nested + .exec(&mut db) + .await?; + +create! macro: + + toasty::create!(User { + .name("Alice"), + .email("a@b.com"), + in user.todos { + { .title("Task 1") }, + { .title("Task 2") }, + } + }) + +Manual insert: + + use toasty::stmt::Insert; + let mut insert = Insert::::blank_single(); + insert.set(field_index, value); + insert.into_statement().exec(&mut db).await?; + + +5 READ (QUERY) +---------------- + +Generated finders: + Model::get_by_id(&mut db, &id).await? + Model::filter_by_FIELD(value).exec(&mut db).await? + Model::all() — returns Query> + +Query methods: + .and(expr) — add filter + .or(expr) — alternative filter + .limit(n) — limit results + .offset(n) — skip results + .order_by(path.asc()) — ascending order + .order_by(path.desc()) — descending order + .include(path) — eager-load relation + .one() — expect exactly one result → Query + .first() — expect zero or one → Query> + .count() — count matching rows → i64 + .delete() — delete matching rows + .paginate(page_size) — cursor-based pagination + +Returning types: + Query> → Vec + Query → M (error if missing) + Query> → Option + +query! macro: + toasty::query!(Model) + toasty::query!(Model filter .field == value && .other > 10) + + +6 EXPRESSIONS & PATHS +----------------------- + +Access via: Model::fields().field_name() + +Comparison operators: + .eq(val) .ne(val) + .gt(val) .ge(val) + .lt(val) .le(val) + .is_in(iter) + +Option field operators: + .is_some() .is_none() + +Embedded enum matching: + .matches(EnumVariant) + +Boolean combinators: + expr.and(expr) + expr.or(expr) + expr.not() + Expr::and_all(vec![...]) + +List expression: + Expr::>::list([...]) + + +7 UPDATE +---------- + +Instance update builder (generated): + record.update() + .field_name(new_value) + .exec(&mut db) + .await?; + +Bulk update: + use toasty::stmt::Update; + Update::>::new(query) + .set(Model::fields().field(), value) + .exec(&mut db) + .await?; + + +8 DELETE +---------- + + use toasty::stmt::Delete; + + Delete::from(query).exec(&mut db).await?; + + // Or via query chain: + Model::all().and(filter).delete().exec(&mut db).await?; + + +9 RELATIONS +------------- + +Types: + toasty::HasMany — one-to-many + toasty::HasOne — one-to-one (required) + toasty::HasOne> — one-to-one (optional) + toasty::BelongsTo — many-to-one + +Loading: + Lazy: record.relation_name().exec(&mut db).await? + Eager: query.include(Model::fields().relation_name()) + +State: + relation.is_unloaded() — true before loading + relation.get() — panics if unloaded + +Nested create: + Parent::create().child(Child::create().field(val)).exec(&mut db).await? + + +10 PAGINATION +--------------- + + let page = Model::all().paginate(page_size).exec(&mut db).await?; + + page.items — Vec + page.has_next() — bool + page.has_prev() — bool + page.next(&mut db).await? — next page + page.prev(&mut db).await? — previous page + +Supports composite keys. Cursor-based (not offset-based). + + +11 TRANSACTIONS +----------------- + + let mut tx = db.transaction().await?; + // ... operations using &mut tx ... + tx.commit().await?; + // Drop without commit = automatic rollback + +Configurable: + TransactionBuilder::new() + .isolation(IsolationLevel::Serializable) + .read_only(false) + .begin_on_db(&mut db) + .await?; + +Nested (savepoints): + let mut inner = tx.transaction().await?; + inner.commit().await?; + + +12 BATCHING +------------- + + use toasty::batch; + + let (a, b) = batch((query_a, query_b)).exec(&mut db).await?; + +Works with queries, creates, updates, and deletes. + + +13 DATABASE-SPECIFIC NOTES +----------------------------- + +SQL (SQLite, PostgreSQL, MySQL): + - Arbitrary WHERE clauses, JOINs for relations + - Transaction isolation levels + +DynamoDB: + - Query methods generated based on primary/sort key + - Constraints limited to sort-key conditions + - Composite key pagination + - Conditional updates for has-one relations + + +14 FEATURE FLAGS +------------------ + + uuid — uuid::Uuid field support + serde — JSON serialization (#[serialize(json)]) + jiff — date/time types from jiff crate + decimal — rust_decimal::Decimal + bigdecimal — bigdecimal::BigDecimal + + +15 KEY DEPENDENCIES +--------------------- + + tokio (async runtime) + deadpool (connection pooling) + tracing (logging) + toasty-core (core types) + toasty-macros (proc macros) + Driver crates: toasty-driver-sqlite, toasty-driver-pg, toasty-driver-mysql, toasty-driver-dynamodb