Commit Graph

114 Commits

Author SHA1 Message Date
Paul
b014416584 feat: add support for not_like operation (#11326)
This PR adds support for `not_like` as a query operation. Functions just
as a negative to `like`, uses `ilike` in postgres and `like` in sqlite
2025-02-22 00:32:37 +00:00
Sasha
6d36a28cdc feat: join field across many collections (#10919)
This feature allows you to specify `collection` for the join field as
array.
This can be useful for example to describe relationship linking like
this:
```ts
{
  slug: 'folders',
  fields: [
    {
      type: 'join',
      on: 'folder',
      collection: ['files', 'documents', 'folders'],
      name: 'children',
    },
    {
      type: 'relationship',
      relationTo: 'folders',
      name: 'folder',
    },
  ],
},
{
  slug: 'files',
  upload: true,
  fields: [
    {
      type: 'relationship',
      relationTo: 'folders',
      name: 'folder',
    },
  ],
},
{
  slug: 'documents',
  fields: [
    {
      type: 'relationship',
      relationTo: 'folders',
      name: 'folder',
    },
  ],
},
```

Documents and files can be placed to folders and folders themselves can
be nested to other folders (root folders just have `folder` as `null`).

Output type of `Folder`:
```ts
export interface Folder {
  id: string;
  children?: {
    docs?:
      | (
          | {
              relationTo?: 'files';
              value: string | File;
            }
          | {
              relationTo?: 'documents';
              value: string | Document;
            }
          | {
              relationTo?: 'folders';
              value: string | Folder;
            }
        )[]
      | null;
    hasNextPage?: boolean | null;
  } | null;
  folder?: (string | null) | Folder;
  updatedAt: string;
  createdAt: string;
}
```

While you could instead have many join fields (for example
`childrenFolders`, `childrenFiles`) etc - this doesn't allow you to
sort/filter and paginate things across many collections, which isn't
trivial. With SQL we use `UNION ALL` query to achieve that.

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-02-18 21:53:45 +02:00
Sasha
88548fcbe6 fix(db-postgres): querying other collections via relationships inside blocks (#11255)
### What?
Previously, in postgres query like:
```ts
const result = await payload.find({
  collection: 'blocks',
  where: { 'blocks.director.name': { equals: 'Test Director' } },
})
```
where `blocks` is a blocks field, `director` is a relationship field and
`name` is a text field inside `directors`, failed with:

![image](https://github.com/user-attachments/assets/f4b62b69-bd17-4ef0-9f0c-08057e9f2d57)

### Why?
The generated query before was a bit wrong.
Before:
```sql
select distinct
  "blocks"."id",
  "blocks"."created_at",
  "blocks"."created_at"
from
  "blocks"
  left join "directors" "a5ad426a_eda4_4067_af7e_5b294d7f0968" on "a5ad426a_eda4_4067_af7e_5b294d7f0968"."id" = "blocks_blocks_some"."director_id"
   left join "blocks_blocks_some" on "blocks"."id" = "blocks_blocks_some"."_parent_id"
where
  "a5ad426a_eda4_4067_af7e_5b294d7f0968"."name" = 'Test Director'
order by
  "blocks"."created_at" desc
limit
	10
```
Notice `left join directors` _before_ join of `blocks_blocks_some`.
`blocks_blocks_some` doesn't exist yet, this PR changes so now we
generate
```sql
select distinct
  "blocks"."id",
  "blocks"."created_at",
  "blocks"."created_at"
from
  "blocks"
  left join "blocks_blocks_some" on "blocks"."id" = "blocks_blocks_some"."_parent_id"
  left join "directors" "a5ad426a_eda4_4067_af7e_5b294d7f0968" on "a5ad426a_eda4_4067_af7e_5b294d7f0968"."id" = "blocks_blocks_some"."director_id"
where
  "a5ad426a_eda4_4067_af7e_5b294d7f0968"."name" = 'Test Director'
order by
  "blocks"."created_at" desc
limit
	10
```
2025-02-18 14:51:17 -05:00
Sasha
847d8d824f feat: add page query parameter for joins (#10998)
Currently, the join field outputs to its result `hasNextPage: boolean`
and have the `limit` query parameter but lacks `page` which can be
useful. This PR adds it.
2025-02-18 16:47:09 +02:00
Alessio Gravili
e6fea1d132 fix: localized fields within block references were not handled properly if any parent is localized (#11207)
The `localized` properly was not stripped out of referenced block fields, if any parent was localized. For normal fields, this is done in sanitizeConfig. As the same referenced block config can be used in both a localized and non-localized config, we are not able to strip it out inside sanitizeConfig by modifying the block config.

Instead, this PR had to bring back tedious logic to handle it everywhere the `field.localized` property is accessed. For backwards-compatibility, we need to keep the existing sanitizeConfig logic. In 4.0, we should remove it to benefit from better test coverage of runtime field.localized handling - for now, this is done for our test suite using the `PAYLOAD_DO_NOT_SANITIZE_LOCALIZED_PROPERTY` flag.
2025-02-17 19:50:32 +00:00
Sasha
513ba636af fix(db-postgres): ensure countDistinct works correctly and achieve better performance when the query has table joins (#11208)
The fix, added in https://github.com/payloadcms/payload/pull/11096
wasn't sufficient enough. It did handle the case when the same query
path / table was joined twice and caused incorrect `totalDocs`, but it
didn't handle the case when `JOIN` returns more than 1 rows, which 2
added new assertions here check.

Now, we use `COUNT(*)` only if we don't have any joined tables. If we
do, instead of using `SELECT (COUNT DISTINCT id)` which as described in
the previous PR is _very slow_ for large tables, we use the following
query:

```sql
SELECT COUNT(1) OVER() as count -- window function, executes for each row only once
FROM users
LEFT JOIN -- ... here additional rows are added
WHERE -- ...
GROUP BY users.id -- this ensures we're counting only users without additional rows from joins. 
LIMIT 1 -- Since COUNT(1) OVER() executes and resolves before doing LIMIT, we can safely apply LIMIT 1.
```
2025-02-16 14:08:08 +02:00
Alessio Gravili
4c8cafd6a6 perf: deduplicate blocks used in multiple places using new config.blocks property (#10905)
If you have multiple blocks that are used in multiple places, this can quickly blow up the size of your Payload Config. This will incur a performance hit, as more data is
1.  sent to the client (=> bloated `ClientConfig` and large initial html) and
2. processed on the server (permissions are calculated every single time you navigate to a page - this iterates through all blocks you have defined, even if they're duplicative)

This can be optimized by defining your block **once** in your Payload Config, and just referencing the block slug whenever it's used, instead of passing the entire block config. To do this, the block can be defined in the `blocks` array of the Payload Config. The slug can then be passed to the `blockReferences` array in the Blocks Field - the `blocks` array has to be empty for compatibility reasons.

```ts
import { buildConfig } from 'payload'
import { lexicalEditor, BlocksFeature } from '@payloadcms/richtext-lexical'

// Payload Config
const config = buildConfig({
  // Define the block once
  blocks: [
    {
      slug: 'TextBlock',
      fields: [
        {
          name: 'text',
          type: 'text',
        },
      ],
    },
  ],
  collections: [
    {
      slug: 'collection1',
      fields: [
        {
          name: 'content',
          type: 'blocks',
          // Reference the block by slug
          blockReferences: ['TextBlock'],
          blocks: [], // Required to be empty, for compatibility reasons
        },
      ],
    },
     {
      slug: 'collection2',
      fields: [
        {
          name: 'editor',
          type: 'richText',
          editor: lexicalEditor({
            BlocksFeature({
              // Same reference can be reused anywhere, even in the lexical editor, without incurred performance hit
              blocks: ['TextBlock'],
            })
          })
        },
      ],
    },
  ],
})
```

## v4.0 Plans

In 4.0, we will remove the `blockReferences` property, and allow string block references to be passed directly to the blocks `property`. Essentially, we'd remove the `blocks` property and rename `blockReferences` to `blocks`.

The reason we opted to a new property in this PR is to avoid breaking changes. Allowing strings to be passed to the `blocks` property will prevent plugins that iterate through fields / blocks from compiling.

## PR Changes

- Testing: This PR introduces a plugin that automatically converts blocks to block references. This is done in the fields__blocks test suite, to run our existing test suite using block references.

- Block References support: Most changes are similar. Everywhere we iterate through blocks, we have to now do the following:
1. Check if `field.blockReferences` is provided. If so, only iterate through that.
2. Check if the block is an object (= actual block), or string
3. If it's a string, pull the actual block from the Payload Config or from `payload.blocks`.

The exception is config sanitization and block type generations. This PR optimizes them so that each block is only handled once, instead of every time the block is referenced.

## Benchmarks

60 Block fields, each block field having the same 600 Blocks.

### Before:
**Initial HTML:** 195 kB
**Generated types:** takes 11 minutes, 461,209 lines

https://github.com/user-attachments/assets/11d49a4e-5414-4579-8050-e6346e552f56

### After:
**Initial HTML:** 73.6 kB
**Generated types:** takes 2 seconds, 35,810 lines

https://github.com/user-attachments/assets/3eab1a99-6c29-489d-add5-698df67780a3

### After Permissions Optimization (follow-up PR)
Initial HTML: 73.6 kB

https://github.com/user-attachments/assets/a909202e-45a8-4bf6-9a38-8c85813f1312


## Future Plans

1. This PR does not yet deduplicate block references during permissions calculation. We'll optimize that in a separate PR, as this one is already large enough
2. The same optimization can be done to deduplicate fields. One common use-case would be link field groups that may be referenced in multiple entities, outside of blocks. We might explore adding a new `fieldReferences` property, that allows you to reference those same `config.blocks`.
2025-02-14 00:08:20 +00:00
Sasha
98fec35368 fix(db-postgres): incorrect pagination results when querying hasMany relationships multiple times (#11096)
Fixes https://github.com/payloadcms/payload/issues/10810

This was caused by using `COUNT(*)` aggregation instead of
`COUNT(DISTINCT table.id)`. However, we want to use `COUNT(*)` because
`COUNT(DISTINCT table.id)` is slow on large tables. Now we fallback to
`COUNT(DISTINCT table.id)` only when `COUNT(*)` cannot work properly.

Example of a query that leads to incorrect `totalDocs`:
```ts
const res = await payload.find({
  collection: 'directors',
  limit: 10,
  where: {
    or: [
      {
        movies: {
          equals: movie2.id,
        },
      },
      {
        movies: {
          equals: movie1.id,
        },
      },
      {
        movies: {
          equals: movie1.id,
        },
      },
    ],
  },
})
```
2025-02-11 01:16:18 +02:00
Jarrod Flesch
d2fe9b0807 fix(db-mongodb): ensures same level operators are respected (#11087)
### What?
If you had multiple operator constraints on a single field, the last one
defined would be the only one used.

Example:
```ts
where: {
  id: {
    in: [doc2.id],
    not_in: [], // <-- only respected this operator constraint
  },
}
```

and
```ts
where: {
  id: {
    not_in: [],
    in: [doc2.id], // <-- only respected this operator constraint
  },
}
```

They would yield different results.

### Why?
The results were not merged into an `$and` query inside parseParams.

### How?
Merges the results within an `$and` constraint.

Fixes https://github.com/payloadcms/payload/issues/10944

Supersedes https://github.com/payloadcms/payload/pull/11011
2025-02-10 16:29:08 -05:00
Sasha
57143b37d0 fix(db-postgres): ensure globals have createdAt, updatedAt and globalType fields (#10938)
Previously, data for globals was inconsistent across database adapters.
In Postgres, globals didn't store correct `createdAt`, `updatedAt`
fields and the `updateGlobal` lacked the `globalType` field. This PR
solves that without introducing schema changes.
2025-02-06 23:48:59 +02:00
Sasha
3ad56cd86f fix(db-postgres): select hasMany: true with autosave doesn't work properly (#11012)
Previously, select fields with `hasMany: true` didn't save properly in
Postgres on autosave.
2025-02-06 23:47:53 +02:00
Sasha
68a7de2610 fix(db-postgres): select hasMany inside arrays and blocks with versions (#10829)
Fixes https://github.com/payloadcms/payload/issues/10780

Previously, with enabled versions, nested select `hasMany: true` fields
weren't working with SQL database adapters. This was due to wrongly
passed `parent` to select rows data because we store arrays and blocks
in versions a bit differently, using both, `id` and `_uuid` (which
contains the normal Object ID) columns. And unlike with non versions
`_uuid` column isn't actually applicable here as it's not unique, thus
we need to save blocks/arrays first and then map their ObjectIDs to
generated by the database IDs and use them for select fields `parent`
data
2025-01-31 18:26:04 +02:00
Sasha
e1dcb9594c fix(db-postgres): write operations on polymorphic joined collections throw error (#10854)
Fixes https://github.com/payloadcms/payload/issues/10845
See
https://github.com/payloadcms/payload/issues/10845#issuecomment-2620201486
2025-01-31 18:25:47 +02:00
Sasha
d601300034 fix(db-mongodb): querying polymorphic relationships with the all operator (#10704)
Fixes https://github.com/payloadcms/payload/issues/10678
2025-01-23 18:23:50 +02:00
Sasha
46c1b375b8 fix: properly handle nullable minDistance and maxDistance in near query (#10622)
Fixes https://github.com/payloadcms/payload/issues/10521
2025-01-21 02:18:13 +02:00
Alessio Gravili
42382b6f6f perf: operations performance optimizations (#10609)
- reduces unnecessary shallow copying within operations by removing
unnecessary spreads or .map()'s
- removes unnecessary `deleteMany` call in `deleteUserPreferences` for
auth-enabled collections
- replaces all instances of `validOperators.includes` with
`validOperatorMap[]`. O(n) => O(1)
- optimizes the `sanitizeInternalFields` function. Previously, it was
doing a **lot** of shallow copying
2025-01-16 17:07:35 +00:00
Sasha
1525cc6e3a fix(db-postgres): handle undefined fallback for adapter.schemaName in relationships migration (#10384)
Fixes from https://github.com/payloadcms/payload/pull/10322
2025-01-06 15:19:21 +00:00
Nacho Martin
556b8ed393 fix(db-postgres): added missing quotes and schema name to sql statement in v2-v3 migration (#10322)
### What?

* Added missing schema name to the SQL statement
* Added missing quotes to the `parent_id`'s in the SQL statement

### Why?

* Migrations script isn't working for databases that don't use the
`public` schema
* They were missing, causing the database to throw an error.

### Fixes:

- [Discord
Discussion](https://discord.com/channels/967097582721572934/1319313926885736448)
- [Discord
Discussion](https://discord.com/channels/967097582721572934/1324352204915609661)
- [Discord
Discussion](https://discord.com/channels/967097582721572934/1323445259606429716)

---------

Co-authored-by: Sasha <64744993+r1tsuu@users.noreply.github.com>
2025-01-06 16:40:32 +02:00
Sasha
98666eb016 perf(db-postgres): do not push database schema if not changed (#10155)
Based on https://github.com/payloadcms/payload/pull/10154

If the actual database schema is not changed (no new columns, enums,
indexes, tables) - skip calling Drizzle push. This, potentially can
significantly reduce overhead on reloads in development mode especially
when using remote databases.

If for whatever reason you need to preserve the current behavior you can
use `PAYLOAD_FORCE_DRIZZLE_PUSH=true` env flag.
2024-12-27 10:12:01 -05:00
Sasha
374b79d218 fix(db-postgres): prevent indexes from changing name on HMR (#10154)
As we didn't reset our `adapter.indexes` state, on every HMR reload we
incremented every single index name with the `buildIndexName`:

466f109152/packages/drizzle/src/utilities/buildIndexName.ts (L3-L24)

I found this while debugging our internal SQL schema:

Before reload:

```ts
 "payload_preferences": {
    "name": "payload_preferences",
    "columns": {
      "id": {
        "name": "id",
        "type": "serial",
        "primaryKey": true
      },
      "key": {
        "name": "key",
        "type": "varchar"
      },
      "value": {
        "name": "value",
        "type": "jsonb"
      },
      "updatedAt": {
        "name": "updated_at",
        "type": "timestamp",
        "defaultNow": true,
        "mode": "string",
        "notNull": true,
        "precision": 3,
        "withTimezone": true
      },
      "createdAt": {
        "name": "created_at",
        "type": "timestamp",
        "defaultNow": true,
        "mode": "string",
        "notNull": true,
        "precision": 3,
        "withTimezone": true
      }
    },
    "foreignKeys": {},
    "indexes": {
      "payload_preferences_key_idx": {
        "name": "payload_preferences_key_idx",
        "on": "key"
      },
      "payload_preferences_updated_at_idx": {
        "name": "payload_preferences_updated_at_idx",
        "on": "updatedAt"
      },
      "payload_preferences_created_at_idx": {
        "name": "payload_preferences_created_at_idx",
        "on": "createdAt"
      }
    }
  },
```

After:
```ts

"payload_preferences": {
    "name": "payload_preferences",
    "columns": {
      "id": {
        "name": "id",
        "type": "serial",
        "primaryKey": true
      },
      "key": {
        "name": "key",
        "type": "varchar"
      },
      "value": {
        "name": "value",
        "type": "jsonb"
      },
      "updatedAt": {
        "name": "updated_at",
        "type": "timestamp",
        "defaultNow": true,
        "mode": "string",
        "notNull": true,
        "precision": 3,
        "withTimezone": true
      },
      "createdAt": {
        "name": "created_at",
        "type": "timestamp",
        "defaultNow": true,
        "mode": "string",
        "notNull": true,
        "precision": 3,
        "withTimezone": true
      }
    },
    "foreignKeys": {},
    "indexes": {
      "payload_preferences_key_1_idx": {
        "name": "payload_preferences_key_1_idx",
        "on": "key"
      },
      "payload_preferences_updated_at_1_idx": {
        "name": "payload_preferences_updated_at_1_idx",
        "on": "updatedAt"
      },
      "payload_preferences_created_at_1_idx": {
        "name": "payload_preferences_created_at_1_idx",
        "on": "createdAt"
      }
    }
  },
```
Which isn't really great for dev performance and can potentially cause
errors
2024-12-27 10:04:48 -05:00
Sasha
a0d8131649 fix(db-postgres): joins to self collection (#10182)
### What?
With Postgres, before join to self like:
```ts
import type { CollectionConfig } from 'payload'

export const SelfJoins: CollectionConfig = {
  slug: 'self-joins',
  fields: [
    {
      name: 'rel',
      type: 'relationship',
      relationTo: 'self-joins',
    },
    {
      name: 'joins',
      type: 'join',
      on: 'rel',
      collection: 'self-joins',
    },
  ],
}
```
wasn't possible, even though it's a valid usage.

### How?
Now, to differentiate parent `self_joins` and children `self_joins` we
do additional alias for the nested select -
`"4d3cf2b6_1adf_46a8_b6d2_3e1c3809d737"`:
```sql
select 
  "id", 
  "rel_id", 
  "updated_at", 
  "created_at", 
  (
    select 
      coalesce(
        json_agg(
          json_build_object('id', "joins_alias".id)
        ), 
        '[]' :: json
      ) 
    from 
      (
        select 
          "created_at", 
          "rel_id", 
          "id" 
        from 
          "self_joins" "4d3cf2b6_1adf_46a8_b6d2_3e1c3809d737" 
        where 
          "4d3cf2b6_1adf_46a8_b6d2_3e1c3809d737"."rel_id" = "self_joins"."id" 
        order by 
          "4d3cf2b6_1adf_46a8_b6d2_3e1c3809d737"."created_at" desc 
        limit 
          $1
      ) "joins_alias"
  ) as "joins_alias" 
from 
  "self_joins" 
where 
  "self_joins"."id" = $2 
order by 
  "self_joins"."created_at" desc 
limit 
  $3

```

Fixes https://github.com/payloadcms/payload/issues/10144

-->
2024-12-26 20:47:49 +02:00
Sasha
4e953530df feat(db-sqlite): add autoIncrement option (#9427)
### What?
Exposes ability to enable
[AUTOINCREMENT](https://www.sqlite.org/autoinc.html) for Primary Keys
which ensures that the same ID cannot be reused from previously deleted
rows.

```ts
sqliteAdapter({
  autoIncrement: true
})
```

### Why?
This may be essential for some systems. Enabled `autoIncrement: true`
also for the SQLite Adapter in our tests, which can be useful when
testing whether the doc was deleted or not when you also have other
create operations.

### How?
Uses Drizzle's `autoIncrement` option.

WARNING:
This cannot be enabled in an existing project without a custom
migration, as it completely changes how primary keys are stored in the
database.
2024-12-20 20:13:28 +00:00
Sasha
7c4ea5b86e refactor: optimize database schema generation bin script (#10086)
* Avoids additional file system writes (1 for `await writeFile` and then
`npx prettier --write`) instead prettier now formats the javascript
string directly. Went from 650 MS to 250 MS for the prettify block.
* Disables database connection, since the `db.generateSchema` doesn't
need connection, this also disables Drizzle schema push.
* Properly exits the bin script process.
2024-12-20 14:43:11 +02:00
Dan Ribbens
d03658de01 feat: join field with polymorphic relationships (#9990)
### What?
The join field had a limitation imposed that prevents it from targeting
polymorphic relationship fields. With this change we can support any
relationship fields.

### Why?
Improves the functionality of join field.

### How?
Extended the database adapters and removed the config sanitization that
would throw an error when polymorphic relationships were used.

Fixes #
2024-12-19 22:34:52 +00:00
Sasha
07be617963 fix(db-postgres): relationships v2-v3 migration errors when migrating from v2 to stable v3 (#10080)
When migrating from stable v2 to v3, the provided relationships
migration fails because of not having `payload_locked_documents` and
`payload_locked_documents_rels` tables in the migration generated in v2.


![image](https://github.com/user-attachments/assets/0dc57e6a-5e6e-4b74-bcab-70e660f4e939)
2024-12-19 19:09:07 +00:00
Sasha
23f1ed4a48 feat(db-postgres, db-sqlite): drizzle schema generation (#9953)
This PR allows to have full type safety on `payload.drizzle` with a
single command
```sh
pnpm payload generate:db-schema
```
Which generates TypeScript code with Drizzle declarations based on the
current database schema.

Example of generated file with the website template: 
https://gist.github.com/r1tsuu/b8687f211b51d9a3a7e78ba41e8fbf03

Video that shows the power:


https://github.com/user-attachments/assets/3ced958b-ec1d-49f5-9f51-d859d5fae236


We also now proxy drizzle package the same way we do for Lexical so you
don't have to install it (and you shouldn't because you may have version
mismatch).
Instead, you can import from Drizzle like this:
```ts
import {
  pgTable,
  index,
  foreignKey,
  integer,
  text,
  varchar,
  jsonb,
  boolean,
  numeric,
  serial,
  timestamp,
  uniqueIndex,
  pgEnum,
} from '@payloadcms/db-postgres/drizzle/pg-core'
import { sql } from '@payloadcms/db-postgres/drizzle'
import { relations } from '@payloadcms/db-postgres/drizzle/relations'
```


Fixes https://github.com/payloadcms/payload/discussions/4318

In the future we can also support types generation for mongoose / raw
mongodb results.
2024-12-19 11:08:17 -05:00
Sasha
0c57eef621 fix: unique error message regression (#10064)
Regression from https://github.com/payloadcms/payload/pull/9935, we need
to call `req.t` with `''error:valueMustBeUnique''` instead of just
`''error:valueMustBeUnique''`
2024-12-19 08:13:44 +00:00
Sasha
03ff77544e feat(db-sqlite): add idType: 'uuid' support (#10016)
Adds `idType: 'uuid'` to the SQLite adapter support:
```ts
sqliteAdapter({
  idType: 'uuid',
})
```

Achieved through Drizzle's `$defaultFn()`
https://orm.drizzle.team/docs/latest-releases/drizzle-orm-v0283#-added-defaultfn--default-methods-to-column-builders
as SQLite doesn't have native UUID support. Added `sqlite-uuid` to CI.
2024-12-18 22:44:04 -05:00
Sasha
0e5bda9a74 feat: make req partial and optional in DB / Local API operations (#9935)
### What?
Previously, the `req` argument:
In database operations (e.g `payload.db`) was required and you needed to
pass the whole `req` with all the properties. This is confusing because
in database operations we never use its properties outside of
`req.transactionID` and `req.t`, both of which should be optional as
well.

Now, you don't have to do that cast:
```ts
payload.db.findOne({
  collection: 'posts',
  req: {} as PayloadRequest,
  where: {
    id: {
      equals: 1,
    },
  },
})
```
Becomes:
```ts
payload.db.findOne({
  collection: 'posts',
  where: {
    id: {
      equals: 1,
    },
  },
})
```

If you need to use transactions, you're not required to do the `as` cast
as well now, as the `req` not only optional but also partial -
`Partial<PayloadRequest>`.
`initTransaction`, `commitTransaction`, `killTransaction` utilities are
typed better now as well. They do not require to you pass all the
properties of `req`, but only `payload` -
`MarkRequired<Partial<PayloadRequest>, 'payload'>`
```ts
const req = { payload }
await initTransaction(req)
await payload.db.create({
  collection: "posts",
  data: {},
  req
})
await commitTransaction(req)
```

The same for the Local API. Internal operations (for example
`packages/payload/src/collections/operations/find.ts`) still accept the
whole `req`, but local ones
(`packages/payload/src/collections/operations/local/find.ts`) which are
used through `payload.` now accept `Partial<PayloadRequest>`, as they
pass it through to internal operations with `createLocalReq`.

So now, this is also valid, while previously you had to do `as` cast for
`req`.
```ts
const req = { payload }
await initTransaction(req)
await payload.create({
  collection: "posts",
  data: {},
  req
})
await commitTransaction(req)
```


Marked as deprecated `PayloadRequest['transactionIDPromise']` to remove
in the next major version. It was never used anywhere.
Refactored `withSession` that returns an object to `getSession` that
returns just `ClientSession`. Better type safety for arguments
Deduplicated in all drizzle operations to `getTransaction(this, req)`
utility:
```ts
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
```
Added fallback for throwing unique validation errors in database
operations when `req.t` is not available.

In migration `up` and `down` functions our `req` is not partial, while
we used to passed `req` with only 2 properties - `payload` and
`transactionID`. This is misleading and you can't access for example
`req.t`.
Now, to achieve "real" full `req` - we generate it with `createLocalReq`
in all migration functions.

This all is backwards compatible. In all public API places where you
expect the full `req` (like hooks) you still have it.

### Why?
Better DX, more expected types, less errors because of types casting.
2024-12-18 22:43:37 -05:00
Sasha
eee6432715 fix(db-postgres): query has many relationships nested in row fields (#9944) (#9944)
### What?
Querying by nested to rows fields in has many relationships like this:
```ts
const result = await payload.find({
  collection: 'relationship-fields',
  where: {
    'relationToRowMany.title': { equals: 'some-title' },
  },
})
```
Where the related collection:
```ts
const RowFields: CollectionConfig = {
  slug: rowFieldsSlug,
  fields: [
    {
      type: 'row',
      fields: [
        {
          name: 'title',
          label: 'Title within a row',
          type: 'text',
          required: true,
        },
      ],
    },
  ],
}
```

was broken

### Why?
We migrated to use `flattenedFields`, but not in this specific case.
This error would be caught earlier we used `noImplictAny` typescript
rule. https://www.typescriptlang.org/tsconfig/#noImplicitAny which
wouldn't allow us to create variable like this:
```ts
let relationshipFields // relationshipFields is any here
```
Instead, we should write:
```ts
let relationshipFields: FlattenedField[]
```
We should migrate to it and `strictNullChecks` as well.

Fixes https://github.com/payloadcms/payload/issues/9534
2024-12-18 22:26:08 -05:00
Sasha
2ee3e30b50 fix(db-postgres): select query on upload fields with hasMany: true (#10029)
Previously, if you selected only upload `hasMany: true` field, you would
receive an empty arrays always, because the `_rels` table wasn't joined
in this case. Fixes the condition to count `field.type === 'upload'` .
2024-12-18 06:33:12 +02:00
Dan Ribbens
b0b2fc6c47 feat: join field support relationships inside arrays (#9773)
### What?

Allow the join field to have a configuration `on` relationships inside
of an array, ie `on: 'myArray.myRelationship'`.

### Why?

This is a more powerful and expressive way to use the join field and not
be limited by usage of array data. For example, if you have a roles
array for multinant sites, you could add a join field on the sites to
show who the admins are.

### How?

This fixes the traverseFields function to allow the configuration to
pass sanitization. In addition, the function for querying the drizzle
tables needed to be ehanced.

Additional changes from https://github.com/payloadcms/payload/pull/9995:

- Significantly improves traverseFields and the 'join' case with a raw
query injection pattern, right now it's internal but we could expose it
at some point, for example for querying vectors.
- Fixes potential issues with not passed locale to traverseFields (it
was undefined always)
- Adds an empty array fallback for joins with localized relationships

Fixes #
https://github.com/payloadcms/payload/discussions/9643

---------

Co-authored-by: Because789 <thomas@because789.ch>
Co-authored-by: Sasha <64744993+r1tsuu@users.noreply.github.com>
2024-12-17 13:14:43 -05:00
Sasha
727fba7b1c refactor: deduplicate and abstract SQL schema building (#9987)
### What?
Abstracts SQL schema building, significantly reducing code duplication
for SQLite / Postgres

db-sqlite lines count From:
```sh
 wc -l **/*.ts
      62 src/connect.ts
      32 src/countDistinct.ts
       9 src/createJSONQuery/convertPathToJSONTraversal.ts
      86 src/createJSONQuery/index.ts
      15 src/defaultSnapshot.ts
       6 src/deleteWhere.ts
      21 src/dropDatabase.ts
      15 src/execute.ts
     178 src/index.ts
     139 src/init.ts
      19 src/insert.ts
      19 src/requireDrizzleKit.ts
     544 src/schema/build.ts
      27 src/schema/createIndex.ts
      38 src/schema/getIDColumn.ts
      13 src/schema/idToUUID.ts
      28 src/schema/setColumnID.ts
     787 src/schema/traverseFields.ts
      18 src/schema/withDefault.ts
     248 src/types.ts
    2304 total
```

To:
```sh
wc -l **/*.ts
      62 src/connect.ts
      32 src/countDistinct.ts
       9 src/createJSONQuery/convertPathToJSONTraversal.ts
      86 src/createJSONQuery/index.ts
      15 src/defaultSnapshot.ts
       6 src/deleteWhere.ts
      21 src/dropDatabase.ts
      15 src/execute.ts
     180 src/index.ts
      39 src/init.ts
      19 src/insert.ts
      19 src/requireDrizzleKit.ts
     149 src/schema/buildDrizzleTable.ts
      32 src/schema/setColumnID.ts
     258 src/types.ts
     942 total
```

Builds abstract schema in shared drizzle package that later gets
converted by SQLite/ Postgres specific implementation into drizzle
schema.
This, apparently can also help here
https://github.com/payloadcms/payload/pull/9953. It should be very
trivial to implement a MySQL adapter with this as well.
2024-12-16 17:17:18 +02:00
Sasha
00909ec5c4 fix(db-sqlite): working point field CRUD and default value (#9989)
Previously, the point field with SQLite was incorrectly built to the
schema and not parsed to the result.
Now, it works with SQLite, just doesn't support queries (`near` etc.).
Fixes
https://github.com/payloadcms/payload/pull/9987#discussion_r1885674299
2024-12-16 07:37:47 +02:00
Sasha
5e3963482e fix(db-postgres): payload.db.upsert inserts new rows instead of updating existing ones (#9916)
### What?
`payload.db.updateOne` (and so `payload.db.upsert`) with drizzle
adapters used incoming `where` incorrectly and worked properly only
either if you passed `id` or some where query path required table joins
(like `where: { 'array.title'`) which is also the reason why `upsert`
_worked_ with user preferences specifically, because we need to join the
`preferences_rels` table to query by `user.relationTo` and `user.value`

Fixes https://github.com/payloadcms/payload/issues/9915

This was found here - https://github.com/payloadcms/payload/pull/9913,
the database KV adapter uses `upsert` with `where` by unique fields.
2024-12-12 03:53:57 +02:00
Sasha
b73fc586b8 feat: expose session, db in migrations to use the active transaction with the database directly (#9849)
This PR adds a feature which fixes another issue with migrations in
Postgres and does few refactors that significantly reduce code
duplication.

Previously, if you needed to use the underlying database directly in
migrations with the active transaction (for example to execute raw SQL),
created from `payload create:migration`, as `req` doesn't work there you
had to do something like this:
```ts
// Postgres
export async function up({ payload, req }: MigrateUpArgs): Promise<void> {
  const db = payload.db.sessions?.[await req.transactionID!].db ?? payload.db
  const { rows: posts } = await db.execute(sql`SELECT * from posts`)
}

// MongoDB
export async function up({ payload, req }: MigrateUpArgs): Promise<void> {
  const session = payload.db.sessions?.[await req.transactionID!]
  const posts = await payload.db.collections.posts.collection.find({ session }).toArray()
}
```

Which was:
1. Awkward to write
2. Not documented anywhere

Now, we expose `session` and `db` to `up` and `down` functions for you:

#### MongoDB:
```ts
import { type MigrateUpArgs } from '@payloadcms/db-mongodb'

export async function up({ session, payload, req }: MigrateUpArgs): Promise<void> {
  const posts = await payload.db.collections.posts.collection.find({ session }).toArray()
}
```
#### Postgres:
```ts
import { type MigrateUpArgs, sql } from '@payloadcms/db-postgres'

export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
  const { rows: posts } = await db.execute(sql`SELECT * from posts`)
}
```

#### SQLite:
```ts
import { type MigrateUpArgs, sql } from '@payloadcms/db-sqlite'

export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
  const { rows: posts } = await db.run(sql`SELECT * from posts`)
}
```
This actually was a thing with Postgres migrations, we already were
passing `db`, but:
1. Only for `up` and when running `payload migrate`, not for example
with `payload migrate:fresh`
2. Not documented neither in TypeScript or docs.

By ensuring we use `db`, this also fixes an issue that affects all
Postgres/SQLite migrations:

Currently, if we run `payload migration:create` with the postgres
adapter we get a file like this:

```ts
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'

export async function up({ payload, req }: MigrateUpArgs): Promise<void> {
  await payload.db.drizzle.execute(sql`
   CREATE TABLE IF NOT EXISTS "users" (
  	"id" serial PRIMARY KEY NOT NULL,
  );
```
Looks good?
Not exactly!
`payload.db.drizzle.execute()` doesn't really use the current
transaction which can lead to some problems.
Instead, it should use the `db` from `payload.db.sessions?.[await
req.transactionID!].db` because that's where we store our Drizzle
instance with the transaction.

But now, if we generate `payload migrate:create` we get:
```ts
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'

export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
  await db.execute(sql`
   CREATE TABLE IF NOT EXISTS "users" (
  	"id" serial PRIMARY KEY NOT NULL,
  );
```

Which is what we want, as the `db` is passed correctly here:

76428373e4/packages/drizzle/src/migrate.ts (L88-L90)

```ts
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
  const dbWithTransaction = payload.db.sessions?.[await req.transactionID!].db
  payload.logger.info({ one: db === dbWithTransaction })
  payload.logger.info({ two: db === payload.db.drizzle })
```
<img width="336" alt="image"
src="https://github.com/user-attachments/assets/f9fab5a9-44c2-44a9-95dd-8e5cf267f027">

Additionally, this PR refactors:
* `createMigration` with Drizzle - now we have sharable
`buildCreateMigration` in `@payloadcms/drizzle` to reduce copy-pasting
of the same logic.
* the `v2-v3` relationships migration for Postgres is now shared between
`db-postgres` and `db-vercel-postgres`, again to reduce copy-paste.
2024-12-11 12:23:12 -05:00
Sasha
dff71eedaa fix(db-postgres): handle select query on select fields (#9607)
Fixes https://github.com/payloadcms/payload/issues/9606

With Postgres / SQLite, select fields (non `hasMany: true`) weren't
properly handled in the `traverseFields.ts` function for `select` query.
2024-12-04 11:27:03 -05:00
Dan Ribbens
631edd4c17 fix: latest: true version disappear on parallel writes (#9032)
What?
Fixes issue when on parallel writes in result you can have 0 latest:
true versions.

Why?
There must be always a version with latest: true

How?
Ensures that we always have a version with latest: true by adding a
filter on createdAt < createdVersion.createdAt.
Instead, this ponentially can lead to a situation where we have 2
versions with latest: true, if they were created at the exact same time,
but this shouldn't happen in a real world scenario and it's much less
problematic than not having a version with latest: true.

Fixes https://github.com/payloadcms/payload/issues/5895

Changes from #8986

---------

Co-authored-by: Sasha <64744993+r1tsuu@users.noreply.github.com>
2024-12-02 14:34:45 -05:00
Sasha
e5cc9153aa fix(db-postgres): allow to clear select fields with hasMany: true (#9464)
### What?
Previously, using Postgres, select fields with `hasMany: true` weren't
clearable.
Meaning, you couldn't pass an empty array:
```ts
const updatedDoc = await payload.update({
  id,
  collection: 'select-fields',
  data: {
    selectHasMany: [],
  },
})
```

### Why?
To achieve the same behavior with MongoDB.

### How?
Modifies logic in `packages/drizzle/src/upsertRow/index.ts` to include
empty arrays.
2024-11-25 11:15:19 -05:00
Sasha
cae300e8e3 perf: flattenedFields collection/global property, remove deep copying in validateQueryPaths (#9299)
### What?
Improves querying performance of the Local API, reduces copying overhead

### How?

Adds `flattenedFields` property for sanitized collection/global config
which contains fields in database schema structure.
For example, It removes rows / collapsible / unnamed tabs and merges
them to the parent `fields`, ignores UI fields, named tabs are added as
`type: 'tab'`.
This simplifies code in places like Drizzle `transform/traverseFields`.
Also, now we can avoid calling `flattenTopLevelFields` which adds some
overhead and use `collection.flattenedFields` / `field.flattenedFields`.

By refactoring `configToJSONSchema.ts` with `flattenedFields` it also
1. Fixes https://github.com/payloadcms/payload/issues/9467
2. Fixes types for UI fields were generated for `select`



Removes this deep copying for each `where` query constraint
58ac784425/packages/payload/src/database/queryValidation/validateQueryPaths.ts (L69-L73)
which potentially can add overhead if you have a large collection/global
config

UPD:
The overhead is even much more than in the benchmark below if you have
Lexical with blocks.

Benchmark in `relationships/int.spec.ts`:
```ts
const now = Date.now()
for (let i = 0; i < 10; i++) {
  const now = Date.now()
  for (let i = 0; i < 300; i++) {
    const query = await payload.find({
      collection: 'chained',
      where: {
        'relation.relation.name': {
          equals: 'third',
        },
        and: [
          {
            'relation.relation.name': {
              equals: 'third',
            },
          },
          {
            'relation.relation.name': {
              equals: 'third',
            },
          },
          {
            'relation.relation.name': {
              equals: 'third',
            },
          },
          {
            'relation.relation.name': {
              equals: 'third',
            },
          },
          {
            'relation.relation.name': {
              equals: 'third',
            },
          },
          {
            'relation.relation.name': {
              equals: 'third',
            },
          },
          {
            'relation.relation.name': {
              equals: 'third',
            },
          },
          {
            'relation.relation.name': {
              equals: 'third',
            },
          },
          {
            'relation.relation.name': {
              equals: 'third',
            },
          },
        ],
      },
    })
  }

  payload.logger.info(`#${i + 1} ${Date.now() - now}`)
}
payload.logger.info(`Total ${Date.now() - now}`)
```

Before:
```
[02:11:48] INFO: #1 3682
[02:11:50] INFO: #2 2199
[02:11:54] INFO: #3 3483
[02:11:56] INFO: #4 2516
[02:11:59] INFO: #5 2467
[02:12:01] INFO: #6 1987
[02:12:03] INFO: #7 1986
[02:12:05] INFO: #8 2375
[02:12:07] INFO: #9 2040
[02:12:09] INFO: #10 1920
    [PASS] Relationships > Querying > Nested Querying > should allow querying two levels deep (24667ms)
[02:12:09] INFO: Total 24657
```

After:
```
[02:12:36] INFO: #1 2113
[02:12:38] INFO: #2 1854
[02:12:40] INFO: #3 1700
[02:12:42] INFO: #4 1797
[02:12:44] INFO: #5 2121
[02:12:46] INFO: #6 1774
[02:12:47] INFO: #7 1670
[02:12:49] INFO: #8 1610
[02:12:50] INFO: #9 1596
[02:12:52] INFO: #10 1576
    [PASS] Relationships > Querying > Nested Querying > should allow querying two levels deep (17828ms)
[02:12:52] INFO: Total 17818
```
2024-11-25 10:28:07 -05:00
Jessica Chowdhury
fb3242df0a feat: add skip and force accept flags to migration commands (#8843)
1. Adds flag `--skip-empty` to `migrate:create` to bypass the empty
migration file prompt.
    - Blank migration file will not be created if this flag is passed.
3. Adds flag `--force-accept-warning` to `migrate:fresh` to bypass the
drop database prompt
2024-11-22 10:37:54 -05:00
Sasha
d499de1e0f fix(db-postgres): joins with versions and hasMany relationship (#9370)
Fixes errors when having joins with versions +drafts on `hasMany: true`
relationships.
Removes `joinQuery` overhead if we don't need it for the current
operation. Right now, in all adapters we support joins only for `find`,
`findOne`, and `queryDrafts`.


Fixes https://github.com/payloadcms/payload/issues/9369
2024-11-21 10:42:05 -05:00
Sasha
5b9cee67c0 fix(db-postgres): create relationship-v2-v3 migration (#9178)
### What?
This command from here:
https://github.com/payloadcms/payload/pull/6339
```sh
payload migrate:create --file @payloadcms/db-postgres/relationships-v2-v3
```
stopped working after db-postgers and drizzle packages were separated 

### How?
Passes correct `dirname` to `getPredefinedMigration`

Additionally, adds support for `.js` files in `getPredefinedMigration`
2024-11-13 19:02:17 +00:00
Alessio Gravili
03291472d6 chore: bump all eslint dependencies, run lint and prettier (#9128)
This fixes a peer dependency error in our monorepo, as
eslint-plugin-jsx-a11y finally supports eslint v9.

Additionally, this officially adds TypeScript 5.6 support for
typescript-eslint.
2024-11-12 10:18:22 -05:00
Sasha
b878daf27a feat(db-postgres): deep querying on json and rich text fields (#9102)
### What?
Allows to query on JSON / Rich Text fields in Postgres the same way as
in Mongodb with any nesting level.

Example:
Data:
```js
{
  json: {
    array: [
      {
        text: 'some-text', // nested to array + object
        object: {
          text: 'deep-text', // nested to array + 2x object
          array: [10], // number is nested to array + 2x object + array
        },
      },
    ],
  }
}
```
Query:
```ts
payload.find({
  collection: 'json-fields',
  where: {
    and: [
      {
        'json.array.text': {
          equals: 'some-text',
        },
      },
      {
        'json.array.object.text': {
          equals: 'deep-text',
        },
      },
      {
        'json.array.object.array': {
          in: [10, 20],
        },
      },
      {
        'json.array.object.array': {
          exists: true,
        },
      },
      {
        'json.array.object.notexists': {
          exists: false,
        },
      },
    ],
  },
})
```

### How?
Utilizes [the `jsonb_path_exists` postgres
function](https://www.postgresql.org/docs/current/functions-json.html)
2024-11-12 09:26:04 +02:00
Jacob Fletcher
c96fa613bc feat!: on demand rsc (#8364)
Currently, Payload renders all custom components on initial compile of
the admin panel. This is problematic for two key reasons:
1. Custom components do not receive contextual data, i.e. fields do not
receive their field data, edit views do not receive their document data,
etc.
2. Components are unnecessarily rendered before they are used

This was initially required to support React Server Components within
the Payload Admin Panel for two key reasons:
1. Fields can be dynamically rendered within arrays, blocks, etc.
2. Documents can be recursively rendered within a "drawer" UI, i.e.
relationship fields
3. Payload supports server/client component composition 

In order to achieve this, components need to be rendered on the server
and passed as "slots" to the client. Currently, the pattern for this is
to render custom server components in the "client config". Then when a
view or field is needed to be rendered, we first check the client config
for a "pre-rendered" component, otherwise render our client-side
fallback component.

But for the reasons listed above, this pattern doesn't exactly make
custom server components very useful within the Payload Admin Panel,
which is where this PR comes in. Now, instead of pre-rendering all
components on initial compile, we're able to render custom components
_on demand_, only as they are needed.

To achieve this, we've established [this
pattern](https://github.com/payloadcms/payload/pull/8481) of React
Server Functions in the Payload Admin Panel. With Server Functions, we
can iterate the Payload Config and return JSX through React's
`text/x-component` content-type. This means we're able to pass
contextual props to custom components, such as data for fields and
views.

## Breaking Changes

1. Add the following to your root layout file, typically located at
`(app)/(payload)/layout.tsx`:

    ```diff
    /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
    /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
    + import type { ServerFunctionClient } from 'payload'

    import config from '@payload-config'
    import { RootLayout } from '@payloadcms/next/layouts'
    import { handleServerFunctions } from '@payloadcms/next/utilities'
    import React from 'react'

    import { importMap } from './admin/importMap.js'
    import './custom.scss'

    type Args = {
      children: React.ReactNode
    }

+ const serverFunctions: ServerFunctionClient = async function (args) {
    +  'use server'
    +  return handleServerFunctions({
    +    ...args,
    +    config,
    +    importMap,
    +  })
    + }

    const Layout = ({ children }: Args) => (
      <RootLayout
        config={config}
        importMap={importMap}
    +  serverFunctions={serverFunctions}
      >
        {children}
      </RootLayout>
    )

    export default Layout
    ```

2. If you were previously posting to the `/api/form-state` endpoint, it
no longer exists. Instead, you'll need to invoke the `form-state` Server
Function, which can be done through the _new_ `getFormState` utility:

    ```diff
    - import { getFormState } from '@payloadcms/ui'
    - const { state } = await getFormState({
    -   apiRoute: '',
    -   body: {
    -     // ...
    -   },
    -   serverURL: ''
    - })

    + const { getFormState } = useServerFunctions()
    +
    + const { state } = await getFormState({
    +   // ...
    + })
    ```

## Breaking Changes

```diff
- useFieldProps()
- useCellProps()
```

More details coming soon.

---------

Co-authored-by: Alessio Gravili <alessio@gravili.de>
Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
Co-authored-by: James <james@trbl.design>
2024-11-11 13:59:05 -05:00
Sasha
0a15388edb feat(db-postgres): add point field support (#9078)
### What?
Adds full support for the point field to Postgres and Vercel Postgres
adapters through the Postgis extension. Fully the same API as with
MongoDB, including support for `near`, `within` and `intersects`
operators.

Additionally, exposes to adapter args:
*
`tablesFilter`https://orm.drizzle.team/docs/drizzle-kit-push#including-tables-schemas-and-extensions.
* `extensions` list of extensions to create, for example `['vector',
'pg_search']`, `postgis` is created automatically if there's any point
field

### Why?
It's essential to support that field type, especially if the postgres
adapter should be out of beta on 3.0 stable.

### How?
* Bumps `drizzle-orm` to `0.36.1` and `drizzle-kit` to `0.28.0` as we
need this change https://github.com/drizzle-team/drizzle-orm/pull/3141
* Uses its functions to achieve querying functionality, for example the
`near` operator works through `ST_DWithin` or `intersects` through
`ST_Intersects`.
* Removes MongoDB condition from all point field tests, but keeps for
SQLite

Resolves these discussions:
https://github.com/payloadcms/payload/discussions/8996
https://github.com/payloadcms/payload/discussions/8644
2024-11-11 09:31:47 -05:00
Sasha
147d28e62c fix(db-postgres): handle special characters in createDatabase (#9022)
### What?
Handles database name with special characters. For example: `-` -
`my-awesome-app`.

### Why?
Previously, `my-awesome-app` led to this error:
```
Error: failed to create database my-awesome-app.
Details: syntax error at or near "-"
```
This can reproduced for example with `create-payload-app`, as the
generated db name is based on project's name.

### How?
Wraps the query variable to quotes, `create database "my-awesome-app"`
instead of `create database my-awesome-app`.
2024-11-06 13:29:57 -05:00
Dan Ribbens
93a55d1075 feat: add join field config where property (#8973)
### What?

Makes it possible to filter join documents using a `where` added
directly in the config.


### Why?

It makes the join field more powerful for adding contextual meaning to
the documents being returned. For example, maybe you have a
`requiresAction` field that you set and you can have a join that
automatically filters the documents to those that need attention.

### How?

In the database adapter, we merge the requested `where` to the `where`
defined on the field.
On the frontend the results are filtered using the `filterOptions`
property in the component.

Fixes
https://github.com/payloadcms/payload/discussions/8936
https://github.com/payloadcms/payload/discussions/8937

---------

Co-authored-by: Sasha <64744993+r1tsuu@users.noreply.github.com>
2024-11-06 10:06:25 -05:00
Sasha
3175541c80 fix: select with unnamed tabs (#8966)
### What?
Fixes `select` handling for properties inside of unnamed tabs using the
mongodb adapter.
Additionally, refactors `traverseFields` in drizzle to reuse logic from
groups / collapsible or rows if unnamed.

### Why?
`select` must work for any fields.

### How?
Fixes the `'tab'` case in `buildProjectionFromSelect` to handle when the
field is an unnamed tab.
Adds extra tests for named tabs / unnamed.
2024-10-31 12:06:05 -04:00