Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
24 changes: 23 additions & 1 deletion docs/content/2.models/2.retrieving.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,34 @@ The `paginate` method returns an object with the following properties:

## Aggregates

The query builder also provides a variety of aggregate methods such as `count`, `max`, and `sum`.
The query builder also provides a variety of aggregate methods: `count`, `max`, `min`, `avg`, and `sum`. Each of these executes the query immediately and can be combined with any other query constraints.

```ts
const count = await User.count();

const max = await User.max("price");

const min = await User.min("price");

const avg = await User.avg("price");

const sum = await Order.where("status", "=", "paid").sum("total");
```

`count` and `sum` return `0` when no records match, while `max`, `min`, and `avg` return `null`.

### Determining if Records Exist

Instead of counting records, you can use the `exists` and `doesntExist` methods to efficiently check whether any records match the query's constraints:

```ts
if (await Order.where("status", "=", "pending").exists()) {
// ...
}

if (await Order.where("user_id", "=", 1).doesntExist()) {
// ...
}
```

## Selecting Columns
Expand Down
27 changes: 27 additions & 0 deletions docs/content/2.models/5.relationships.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,33 @@ You can even combine multiple eager loads with and without constraints in the sa
const people = await Person.with("vehicles", { pets: (query) => query.where("type", "dog") }).get();
```

### Counting Related Records

If you only need the number of related records, use `withCount` instead of loading the full relationship. Each result gets a `{relation}Count` attribute containing the count:

```ts
// Get people with a count of their pets
const people = await Person.withCount("pets").get();

people[0].petsCount; // number
```

The count is computed in the same query as the main results using a subquery, so no extra queries are run.

You can count multiple relationships at once, and add constraints to the counting query by passing an object mapping the relationship name to a callback:

```ts
// Count all visits, plus only the visits to vet #1
const pets = await Pet.withCount("vets", {
vetVisits: (query) => query.where("vet_id", 1),
}).get();

pets[0].vetsCount; // number
pets[0].vetVisitsCount; // number of visits matching the constraint
```

Counts are included when serializing the model with `toJSON()`.

## Many-to-Many Relationships

You can define many-to-many relationships using the `belongsToMany` method. This requires a pivot table (or join table) to link the two models together.
Expand Down
119 changes: 119 additions & 0 deletions docs/content/2.models/8.transactions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
---
title: Transactions
description: Wrapping model activity in database transactions with Vasta.
navigation:
icon: i-lucide-git-commit-horizontal
seo:
description: Learn how to wrap model activity in database transactions with Vasta.
title: Transactions
---

## Running a Transaction

Start a transaction with Kysely's `db.transaction().execute()` (or the [`Model.transaction`](#modeltransaction) shorthand) and pass the transaction object to the model operations that should run inside it. The transaction commits when the callback resolves and rolls back if it throws.

More documentation on Kysely transactions is available in the [Kysely docs](https://kysely.dev/docs/examples/transactions/simple-transaction).

```ts
import db from "./database/db";

await db.transaction().execute(async (trx) => {
const person = await Person.create(
{ name: "Aang", birthday: new Date("1993-01-01") },
trx,
);

const pet = new Pet({ name: "Appa", type: "bison", person_id: person.id });
await pet.save(trx);
});
```

## Queries

Pass the transaction to `query()` to run a query on it. Everything chained from the builder runs on the transaction.

```ts
await db.transaction().execute(async (trx) => {
const pets = await Pet.query(trx).where("type", "cat").get();
const owner = await Person.query(trx).find(1);
});
```

You can also bind a connection to an existing builder chain with `useConnection`.

```ts
const query = Pet.query().where("type", "cat");
const pets = await query.useConnection(trx).get();
```

## Models Remember Their Connection

Models loaded through a transaction-bound query keep that connection. Calling `save`, `delete`, or accessing relations on them stays inside the transaction. You don't need to pass the transaction again for subsequent operations.

```ts
await db.transaction().execute(async (trx) => {
const pet = await Pet.query(trx).firstOrFail();

pet.name = "Momo";
await pet.save(); // runs on trx

const owner = await pet.owner; // loaded through trx
});
```

The same applies to models returned by `create(attributes, trx)` and to eager-loaded relations (`Pet.query(trx).with("owner")`).

For a model instance that isn't already bound to a transaction, either pass the transaction to `save`/`delete` directly or bind it with `useConnection`:

```ts
const pet = new Pet({ name: "Hawky", type: "hawk" });

await pet.save(trx);
// or
await pet.useConnection(trx).save();
```

## Creating

`create` and `createMany` accept a connection as their second argument.

```ts
await db.transaction().execute(async (trx) => {
await Pet.create({ name: "Momo", type: "lemur" }, trx);

await Pet.create(
[
{ name: "Appa", type: "bison" },
{ name: "Hawky", type: "hawk" },
],
trx,
);
});
```

## Model.transaction

As a shorthand, you can start a transaction directly from any model without importing the database instance. The transaction runs on that model's database and can be used with any model on the same database.

```ts
await Person.transaction(async (trx) => {
const person = await Person.create({ name: "Katara" }, trx);
await Pet.create({ name: "Momo", type: "lemur", person_id: person.id }, trx);
});
```

## Raw Kysely Queries

The transaction is a regular Kysely `Transaction`, so raw queries can run on it alongside model activity.

```ts
await db.transaction().execute(async (trx) => {
await Person.create({ name: "Toph" }, trx);

await trx
.updateTable("people")
.set({ favorite_color: "green" })
.where("name", "=", "Toph")
.execute();
});
```
Loading
Loading