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
3 changes: 3 additions & 0 deletions .github/workflows/sync-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ on:
branches: ["main", "master"]
paths:
- "docs/**"
pull_request:
paths:
- "docs/**"
workflow_dispatch:

jobs:
Expand Down
13 changes: 13 additions & 0 deletions docs/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"tabWidth": 4,
"useTabs": false,
"printWidth": 100,
"semi": true,
"singleQuote": false,
"quoteProps": "as-needed",
"trailingComma": "all",
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "always",
"endOfLine": "lf"
}
11 changes: 6 additions & 5 deletions docs/Count-Records.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ title: Count Records
description: Count the number of records matching a set of predicates.
category: CRUD
tags: [count, count records]
order: 5
categoryOrder: 2
order: 6
---

# Count Records
Expand Down Expand Up @@ -32,9 +33,9 @@ usersCount, err := db.User.

### Supported Builder Methods

| Method | Description |
| --- | --- |
| [`Take`](/docs/Take) | Limit the number of records included in the count. Mirrors SQL's `LIMIT`. |
| Method | Description |
| -------------------- | -------------------------------------------------------------------------- |
| [`Take`](/docs/Take) | Limit the number of records included in the count. Mirrors SQL's `LIMIT`. |
| [`Skip`](/docs/Skip) | Skip a number of matching records before counting. Mirrors SQL's `OFFSET`. |

> **Note:** `Take` and `Skip` affect the result of the count. For example, if 100 records match a predicate and you call `Take(10)`, the returned count will be `10`, not `100`.
> **Note:** `Take` and `Skip` affect the result of the count. For example, if 100 records match a predicate and you call `Take(10)`, the returned count will be `10`, not `100`.
24 changes: 12 additions & 12 deletions docs/Create-Records.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ created, err := db.User.Create().

### Supported Builder Methods

| Method | Description |
| --- | --- |
| [`Select`](/docs/Select) | Select specific fields and relations. Mutually exclusive with `Omit`. |
| [`Omit`](/docs/Omit) | Omit specific scalar fields. Mutually exclusive with `Select`. |
| Method | Description |
| ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| [`Select`](/docs/Select) | Select specific fields and relations. Mutually exclusive with `Omit`. |
| [`Omit`](/docs/Omit) | Omit specific scalar fields. Mutually exclusive with `Select`. |
| [`OnConflict`](/docs/Upsert-Records#onconflict) | Configure behavior when a unique constraint is violated. Supports `.Ignore()`, `.UpdateNewValues()`, and `.Update()`. |

---
Expand Down Expand Up @@ -105,10 +105,10 @@ createdCount, err := db.User.CreateMany(usersToCreate...).Exec(ctx)

### Supported Builder Methods

| Method | Description |
| --- | --- |
| Method | Description |
| ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| [`OnConflict`](/docs/Upsert-Records#onconflict) | Configure behavior when a unique constraint is violated. Supports `.Ignore()`, `.UpdateNewValues()`, and `.Update()`. |
| [`SkipDuplicates`](/docs/SkipDuplicates) | Shorthand for `.OnConflict().Ignore()`. |
| [`SkipDuplicates`](/docs/SkipDuplicates) | Shorthand for `.OnConflict().Ignore()`. |

---

Expand Down Expand Up @@ -151,9 +151,9 @@ createdUsers, err := db.User.CreateManyAndReturn(usersToCreate...).Exec(ctx)

### Supported Builder Methods

| Method | Description |
| --- | --- |
| [`Select`](/docs/Select) | Select specific fields and relations. Mutually exclusive with `Omit`. |
| [`Omit`](/docs/Omit) | Omit specific scalar fields. Mutually exclusive with `Select`. |
| Method | Description |
| ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| [`Select`](/docs/Select) | Select specific fields and relations. Mutually exclusive with `Omit`. |
| [`Omit`](/docs/Omit) | Omit specific scalar fields. Mutually exclusive with `Select`. |
| [`OnConflict`](/docs/Upsert-Records#onconflict) | Configure behavior when a unique constraint is violated. Supports `.Ignore()`, `.UpdateNewValues()`, and `.Update()`. |
| [`SkipDuplicates`](/docs/SkipDuplicates) | Shorthand for `.OnConflict().Ignore()`. |
| [`SkipDuplicates`](/docs/SkipDuplicates) | Shorthand for `.OnConflict().Ignore()`. |
71 changes: 71 additions & 0 deletions docs/Cursor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
title: Cursor
description: Fetch pages of results relative to a known record, identified by a unique predicate.
category: Pagination & Sort
tags: [cursor, pagination, keyset]
categoryOrder: 5
order: 4
---

# Cursor

`Cursor` implements cursor-based (keyset) pagination. Instead of skipping an arbitrary number of rows like [`Skip`](/docs/Skip), you position the query _after_ a specific record, identified by a unique predicate such as `user.Email.EQ(...)` or `user.Id.EQ(...)`.

This approach stays stable under concurrent inserts and updates and is far more efficient on large datasets than offset pagination.

`Cursor` can be used with `FindFirst`, `FindMany`, and nested relation query builders. It is typically combined with [`OrderBy`](/docs/OrderBy) and [`Take`](/docs/Take).

## Fetching the Next Page

Combine a cursor with a positive [`Take`](/docs/Take) to fetch the page following a known record:

```go
nextPage, err := db.User.
FindMany(user.Email.Contains("@example.com")).
OrderBy(user.Email.Asc()).
Cursor(user.Email.EQ(lastEmail)).
Take(20).
Exec(ctx)
```

This returns the next 20 records after `lastEmail`.

## Fetching the Previous Page

Combine a cursor with a negative [`Take`](/docs/Take) to fetch the records that precede it:

```go
prevPage, err := db.User.
FindMany(user.Email.Contains("@example.com")).
OrderBy(user.Email.Asc()).
Cursor(user.Email.EQ(firstEmailOfCurrentPage)).
Take(-20).
Exec(ctx)
```

> **Note:** The cursor value must reference a unique field, such as the record's `Id` or another unique column.

## Usage Tips

- Always provide a stable [`OrderBy`](/docs/OrderBy); it must be consistent from page to page.
- Pass the last record of the current page as the cursor for the next page, and the first record for the previous page.
- Use [`Skip`](/docs/Skip) together with a cursor when you want to jump a fixed number of rows past the cursor.

## Supported By

- [`FindMany`](/docs/Read-Records#findmany)
- [`FindFirst`](/docs/Read-Records#findfirst)
- Nested relation queries via [`Select`](/docs/Select)

## Resulting SQL

A cursor query compiles into a key-based comparison on the cursor's column, combined with the ordering and limit:

```sql
SELECT * FROM "users"
WHERE "email" > 'last@example.com'
ORDER BY "email" ASC
LIMIT 20;
```

A negative [`Take`](/docs/Take) swaps the comparison direction to fetch preceding rows.
130 changes: 130 additions & 0 deletions docs/Delete-Records.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
---
title: Delete Records
description: Delete one or more records using type-safe delete builders.
category: CRUD
tags: [delete, deleteMany]
categoryOrder: 2
order: 4
---

# Delete Records

Phi provides two methods for deleting records:

- `Delete`
- `DeleteMany`

> **Note:** `Delete` always returns the deleted record, regardless of the underlying database. On databases that support `DELETE ... RETURNING`, Phi performs the deletion in a single query. On databases that don't, Phi transparently executes the operation inside a transaction by fetching the record before deleting it, ensuring consistent behavior across all supported SQL dialects.

---

## Delete

Deletes a **single record**.

The first predicate **must** uniquely identify a record (for example, `Id.EQ()` or another unique field). Additional predicates may be supplied to further constrain the deletion.

By default, all scalar fields are returned. Use `Select` or `Omit` to customize the returned data.

### Basic

```go
user, err := db.User.Delete(
user.Email.EQ("x@y.com"),
).Exec(ctx)
```

### With Additional Predicates

```go
user, err := db.User.Delete(
user.Email.EQ("x@y.com"),
user.Bio.Contains("golang"),
).Exec(ctx)
```

### Returning Selected Fields

```go
user, err := db.User.Delete(
user.Id.EQ(id),
).
Select(user.Select{
Id: true,
Username: true,
}).
Exec(ctx)
```

### Omitting Fields

```go
user, err := db.User.Delete(
user.Id.EQ(id),
).
Omit(user.Omit{
Password: true,
}).
Exec(ctx)
```

### Supported Builder Methods

| Method | Description |
| ------------------------ | ------------------------------------------------- |
| [`Select`](/docs/Select) | Return only the selected fields and relations. |
| [`Omit`](/docs/Omit) | Return all scalar fields except the omitted ones. |

---

## DeleteMany

Deletes **all records** matching the supplied predicates.

Unlike `Delete`, no unique predicate is required.

`DeleteMany` returns the number of rows deleted.

### Basic

```go
deleted, err := db.User.DeleteMany(
user.Bio.Contains("inactive"),
).Exec(ctx)
```

### Multiple Predicates

```go
deleted, err := db.User.DeleteMany(
user.Email.HasSuffix("@example.com"),
user.LoginCount.LT(5),
).Exec(ctx)
```

### Using Logical Predicates

```go
deleted, err := db.User.DeleteMany(
user.Or(
user.Bio.Contains("spam"),
user.PhoneNum.HasPrefix("+999"),
),
).Exec(ctx)
```

### Return Value

```go
deleted, err := db.User.DeleteMany(
user.Bio.Contains("inactive"),
).Exec(ctx)

fmt.Printf("Deleted %d users\n", deleted)
```

### Supported Builder Methods

`DeleteMany` exposes no additional builder methods beyond its predicates.

> **Note:** `DeleteMany` only returns the number of deleted rows. If you need the deleted records themselves, query them before deleting.
44 changes: 44 additions & 0 deletions docs/Omit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
title: Omit
description: Omit specific scalar fields from a query result.
category: Select & Omit
tags: [omit, omit fields, scalar selection]
categoryOrder: 3
order: 2
---

# Omit

`Omit` specifies which scalar fields should be excluded from a query result.

Unlike `Select`, `Omit` only applies to scalar fields. Relations are never loaded unless explicitly selected with `Select`.

## Omitting Scalar Fields

Omit scalar fields by setting their corresponding field to `true`. Set a field to `false` (or leave it unset) to include it in the result.

```go
users, err := db.User.
FindMany(user.Email.EQ("x@y.com")).
Omit(user.Omit{
Id: true,
Email: true,
Bio: true,
}).
Exec(ctx)
```

> **Note:** `Select` and `Omit` are mutually exclusive. Attempting to use both on the same query will result in an error.

## Supported By

`Omit` is supported by:

- [`Create`](/docs/Create-Records#create)
- [`CreateManyAndReturn`](/docs/Create-Records#createmanyandreturn)
- [`FindUnique`](/docs/Read-Records#findunique)
- [`FindFirst`](/docs/Read-Records#findfirst)
- [`FindMany`](/docs/Read-Records#findmany)
- [`Update`](/docs/Update-Records#update)
- [`UpdateManyAndReturn`](/docs/Update-Records#updatemanyandreturn)
- [`Delete`](/docs/Delete-Records#delete)
Loading
Loading