Commit Graph

4613 Commits

Author SHA1 Message Date
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
eff75f9613 fix: deep querying, handle getLocalizedPaths for blocks (#10187)
Fixes https://github.com/payloadcms/payload/issues/10126

Properly handles the `getLocalizedPaths` function for blocks.

Previously, using collection with this config:
```
{
  slug: 'deep-nested',
  fields: [
    {
      type: 'tabs',
      tabs: [
        {
          name: 'content',
          fields: [
            {
              type: 'blocks',
              name: 'blocks',
              blocks: [
                {
                  slug: 'testBlock',
                  fields: [
                    {
                      type: 'tabs',
                      tabs: [
                        {
                          name: 'meta',
                          fields: [
                            {
                              type: 'relationship',
                              relationTo: 'movies',
                              name: 'movie',
                            },
                          ],
                        },
                      ],
                    },
                  ],
                },
              ],
            },
          ],
        },
      ],
    },
  ],
},

```
The following query didn't work in MongoDB:
```ts
const result = await payload.find({
  collection: 'deep-nested',
  where: {
    'content.blocks.meta.movie': {
      equals: movie.id,
    },
  },
})
```
2024-12-27 09:46:09 -05:00
Said Akhrarov
ebf3cee803 fix(ui): prevent many upload fields overwriting bulkUpload onSuccess (#10189)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Fixes #

-->
### What?
This PR fixes an issue where multiple `upload` fields would sequentially
overwrite the `BulkUpload` internal `onSuccess` function causing new
uploads to populate the incorrect field from which the interaction
started.

### Why?
Sequential `upload` fields use a `useEffect` to set the success function
of the `BulkUpload` provider component, however this did not take into
account many `upload` fields in a single document. This PR prevents many
`upload` fields from overriding their sibling's `onSuccess` function in
order to populate those fields correctly.

### How?
By changing the way the bulk upload component handles success functions
from a singular function to a map of functions based on a string path of
the field, or if necessary, using a collection slug in the case of a
bulk upload on an `upload` collection list view.

Fixes #10177

Before (One hasMany, one single):

[Editing-hasmany-single--Post-before--Payload.webm](https://github.com/user-attachments/assets/01aeaa64-a065-4e66-8ab4-6bb9d4fa8556)

Before (Many hasMany):

[Editing-hasmany-two--Post-before--Payload.webm](https://github.com/user-attachments/assets/a65c58aa-9a15-4cca-b2c4-17484c020ddc)

After (One hasMany, one single):

[Editing-hasmany-single--Post-after--Payload.webm](https://github.com/user-attachments/assets/7206f94e-4ce2-41b3-8b45-625f4974d28d)

After (Many hasMany):

[Editing-hasmany-two--Post-after--Payload.webm](https://github.com/user-attachments/assets/72dbbdee-d4a5-4488-8ef0-3dd3918115a9)
2024-12-27 09:41:19 -05:00
Sasha
d8a62b7022 feat: plugin template (#10150)
Updates the plugin template and adds it to the monorepo

Includes:
* Integration testing setup 
* Adding custom client / server components via a plugin
* The same building setup that we use for our plugins in the monorepo
* `create-payload-app` dynamically configures the project based on the
name:`dev/tsconfig.json`, `src/index.ts`, `dev/payload.config.ts`
For example, from project name: `payload-plugin-cool`
`src/index.ts`:
```ts
export type PayloadPluginCoolConfig = {
  /**
   * List of collections to add a custom field
   */
  collections?: Partial<Record<CollectionSlug, true>>
  disabled?: boolean
}

export const payloadPluginCool =
  (pluginOptions: PayloadPluginCoolConfig) =>
/// ...
```
`dev/tsconfig.json`:
```json
{
  "extends": "../tsconfig.json",
  "exclude": [],
  "include": [
    "**/*.ts",
    "**/*.tsx",
    "../src/**/*.ts",
    "../src/**/*.tsx",
    "next.config.mjs",
    ".next/types/**/*.ts"
  ],
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@payload-config": [
        "./payload.config.ts"
      ],
      "payload-plugin-cool": [
        "../src/index.ts"
      ],
      "payload-plugin-cool/client": [
        "../src/exports/client.ts"
      ],
      "payload-plugin-cool/rsc": [
        "../src/exports/rsc.ts"
      ]
    },
    "noEmit": true
  }
}

```

`./dev/payload.config.ts`
```
import { payloadPluginCool } from 'payload-plugin-cool'
///
 plugins: [
    payloadPluginCool({
      collections: {
        posts: true,
      },
    }),
  ],
```

Example of published plugin
https://www.npmjs.com/package/payload-plugin-cool
2024-12-27 14:25:08 +00:00
Dan Ribbens
326b72072c chore: update rimraf and fix windows compatible clean script (#10152)
Previously we had been downgrading rimraf to v3 simply to handle clean
with glob patterns across platforms. In v4 and newer of rimraf you can
add `-g` to use glob patterns.

This change updates rimraf and adds the flag to handle globs in our
package scripts to be windows compatible.
2024-12-27 09:16:17 -05:00
Alessio Gravili
1dc4b2c5e1 chore(richtext-lexical): export $createServerBlockNode, $isServerBlockNode and ServerBlockNode for the server (#10192)
This is useful for working within custom markdown transformers on the
server, using a headless lexical editor
2024-12-27 07:17:51 +00:00
Jacob Fletcher
f3aebe3263 fix(ui): public users unable to log out (#10188)
Fixes #10180. When logged in as an unauthorized user who cannot access
the admin panel, the user is unable to log out through the prompted
`/admin/logout` page. This was because that page was using an incorrect
API endpoint, reading from `admin.user` instead of `user.collection`
when formatting the route. This page was also able to get stuck in an
infinite loading state when attempting to log out without any user at
all. Now, public users can properly log out and then back in with
another user who might have access. The messaging around this was also
misleading. Instead of displaying the "Unauthorized, you must be logged
in to make this request" message, we now display a new "Unauthorized,
this user does not have access to the admin panel" message for added
clarity.
2024-12-26 22:52:00 -05:00
Jacob Fletcher
5d3b816cd8 chore(ui): exports parseSearchParams (#10185)
As pointed out in #10164, parsing a `where` query from search params is
not exactly straightforward. Internally we rely on the `qs` module for
this, but it comes with a couple small nuances that are undocumented,
like the need to stringify them and specify depth. To standardize this,
we use a `parseSearchParams` utility internally that accepts the
`URLSearchParams` object that the `useSearchParams()` hook returns from
`next/navigation`. This PR exports that function for reuse and adds
JSDocs accordingly. Usage looks something like this:

```tsx
'use client'
import { useSearchParams } from 'next/navigation'
import { parseSearchParams } from '@payloadcms/ui'

function MyComponent() {
  const searchParams = useSearchParams()
  const parsedSearchParams = parseSearchParams(searchParams)
}
```
2024-12-26 16:02:53 -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
Kethan Vegunta
6b45b2d7e9 chore: UnauthorizedError file misspelled as UnathorizedError (#10157)
Fixes misspelled file.

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2024-12-26 18:45:21 +00:00
Jacob Fletcher
b33f4b0143 fix(ui): infinite loading states when adding blocks or array rows (#10175)
Fixes #10070. Adding new blocks or array rows can randomly get stuck
within an infinite loading state. This was because the abort controllers
responsible for disregarding duplicate `onChange` and `onSave` events
was not properly resetting its refs across invocations. This caused
subsequent event handlers to incorrectly abort themselves, leading to
unresolved requests and a `null` form state. Similarly, the cleanup
effects responsible for aborting these requests on component unmount
were also referencing its `current` property directly off the refs,
which can possible be stale if not first set as a variable outside the
return function.

This PR also carries over some missing `onSave` logic from the default
edit view into the live preview view. In the future the logic between
these two views should be standardized, as they're nearly identical but
often become out of sync. This can likely be done through the use of
reusable hooks, such as `useOnSave`, `useOnChange`, etc. Same with the
document locking functionality which is complex and deeply integrated
into each of these views.
2024-12-26 12:17:06 -05:00
Sasha
8debb68db2 fix: generate types when no en language is defined in i18n (#10181)
Previously, if you had for example only `de` in `i18n` -
`generate:types` would fail
Fixes https://github.com/payloadcms/payload/issues/10145
2024-12-26 15:35:58 +00:00
Jacob Fletcher
466f109152 chore(live-preview): strongly types message events (#10148)
Live Preview message events were typed with the generic `MessageEvent`
interface without passing any of the Live Preview specific properties,
leading to unknown types upon use. To fix this, there is a new
`LivePreviewMessageEvent` which properly extends the underlying
`MessageEvent` interface, providing much needed type safety to these
functions. In the same vein, the `UpdatedDocument` type was not being
properly shared across packages, leading to multiple independent
definitions of this type. This type is now exported from `payload`
itself and renamed to `DocumentEvent` for improved semantics. Same with
the `FieldSchemaJSON` type. This PR also adjusts where globally scoped
variables are set, putting them within the shared `_payloadLivePreview`
namespace instead of setting them individually at the top-level.
2024-12-23 18:19:52 +00:00
Germán Jabloñski
154ad9d132 fix(richtext-lexical): restore selection (#10129)
Fix #9964

Now we make sure that the node for the previous selection exists before
restoring it to avoid a runtime error.

I also optimized the performance of a function in the client feature.

In the future, we should centralize the insertion of all decorator
blocks in one place. There are several things to improve. For example,
currently an additional paragraph is inserted (in addition to the one
for the selection we delete).
2024-12-22 15:18:23 +00:00
Said Akhrarov
68b5f61641 fix: ensure tab label and description are serializable (#10115)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Fixes #

-->
### What?
This PR fixes an issue where assigning a label or description function
to a tab would cause a runtime error due to passing a function to a
client component.

### Why?
To prevent runtime errors when using non-static designations.

### How?
By properly evaluating label and description functions prior to
assignment to their `clientTab` counterpart.

Fixes #10114

Before:

![image](https://github.com/user-attachments/assets/e003780a-2f24-4988-96ce-27c22fd61854)

After:

![image](https://github.com/user-attachments/assets/0bd185a0-415d-4414-bead-48647e244ba7)
2024-12-22 00:33:40 +00:00
Elliot DeNolf
e9a330d4b9 chore(release): v3.11.0 [skip ci] 2024-12-21 07:56:14 -05:00
Sasha
b08ff88fbd fix(db-mongodb): mongodb optimizations (#10120)
There are few issues introduced in #9594 that we need to look into with
these changes.
2024-12-21 07:42:44 -05:00
Said Akhrarov
a58b9fc230 fix(ui): join table row still shows after deletion (#9783)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Fixes #

-->
### What?
This PR fixes an issue where deleting an entry in a `Join` via the
`Drawer` accessed through the `DrawerLink` would not update the table
until the page was refreshed.

### Why?
For a better, more reactive, deletion experience for end-users. Ideally,
the deletion is reflected in the table right away.

### How?
By passing an `onDrawerDelete` function to the `DrawerLink` which simply
filters out the existing doc according to an id.

Fixes #9580

Before:

[Editing---Post--before-Payload.webm](https://github.com/user-attachments/assets/3dd4df78-bb63-46b1-bf5f-7643935e15ad)

After:

[Editing---Post--after-Payload.webm](https://github.com/user-attachments/assets/97bb604f-41df-4cc9-8c46-9a59a19c72b7)
2024-12-20 21:47:09 -05:00
Said Akhrarov
ec853c4336 fix(ui): ensure unpublish confirmation is reachable when opened in drawers (#10109)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Fixes #

-->
### What?
This PR fixes an issue where the unpublish modal was unreachable due to
the high `z-index` on `Drawer` components. This makes unpublishing
documents from a drawer impossible. For example, when editting a
document from the drawer opened in a `RelationshipTable`.

### Why?
To allow editors to be able to unpublish docs regardless of drawer depth
and context.

### How?
By rendering the unpublish modal at a sufficiently high z-index, while
taking into account edit depth.

Fixes #10108

Before:

[Dashboard-unpublish-before--Payload.webm](https://github.com/user-attachments/assets/7acf1002-138e-48bd-81ec-76f5eabfb2d4)

After:

[Dashboard-unpublish-after--Payload.webm](https://github.com/user-attachments/assets/ff109ee9-5b63-43d0-931f-500ded8f6d3a)
2024-12-20 23:52:05 +00:00
Jacob Fletcher
957867f6e2 fix: ensures generated IDs persist on create (#10089)
IDs that are supplied directly through the API, such as client-side
generated IDs when adding new blocks and array rows, are overwritten on
create. This is because when adding blocks or array rows on the client,
their IDs are generated first before being sent to the server for
processing. Then when the server receives this data, it incorrectly
overrides them to ensure they are unique when using relational DBs. But
this only needs to happen when no ID was supplied on create, or
specifically when duplicating documents via the `beforeDuplicate` hook.
2024-12-20 15:14:23 -05: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
Elliot DeNolf
4d50046104 chore(release): v3.10.0 [skip ci] 2024-12-20 14:42:13 -05:00
Sasha
a7109ed048 refactor: consistent caps for acronyms in variable names (#10101)
Improves consistency for variable names like `docID`, `insertedID`,
`parentID`
For example: `docId` -> `docID`
2024-12-20 18:10:34 +00:00
James Mikrut
a501e604d6 chore(richtext-slate): exports useElementButton, useLeafButton (#10047)
Exports `useElementButton` and `useLeafButton` from the Slate package
for custom Slate components.
2024-12-20 09:48:49 -07:00
Dan Ribbens
6bba7bec4e feat(ui): export RelationshipTable component (#10084)
Fixes https://github.com/payloadcms/payload/discussions/9049
2024-12-20 11:47:22 -05:00
Elliot DeNolf
2a6bac97f5 chore(release): eslint/3.9.0 2024-12-20 11:35:52 -05:00
Sasha
59fc9d094e fix(ui): close copy locale modal after locale is changed (#10096)
Ensures we close the modal _only_ after locale is changed. This caused
our localization e2e's to flake:

![image](https://github.com/user-attachments/assets/41205afe-0e45-499b-9aa6-07734a7f26fc)
2024-12-20 08:29:53 -05: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
Alessio Gravili
b3308736c4 feat: jsdocs for generated types, by using admin.description (#9917)
This makes use of admin.description to generate JSDocs for field,
collection and global generated types.


![image](https://github.com/user-attachments/assets/980d825f-49a2-426d-933a-2ff3d205ea24)


![image](https://github.com/user-attachments/assets/d0b1f288-1ea1-4d80-8c05-003d59a4e41a)

For the future, we should add a dedicated property to override these
JSDocs.

You can view the effect of this PR on our test suite generated types
here:
05f552bbbc
2024-12-19 22:22:43 -05: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
e468292039 perf(db-mongodb): improve performance of all operations, up to 50% faster (#9594)
This PR improves speed and memory efficiency across all operations with
the Mongoose adapter.

### How?

- Removes Mongoose layer from all database calls, instead uses MongoDB
directly. (this doesn't remove building mongoose schema since it's still
needed for indexes + users in theory can use it)
- Replaces deep copying of read results using
`JSON.parse(JSON.stringify(data))` with the `transform` `operation:
'read'` function which converts Date's, ObjectID's in relationships /
joins to strings. As before, it also handles transformations for write
operations.
- Faster `hasNearConstraint` for potentially large `where`'s
- `traverseFields` now can accept `flattenedFields` which we use in
`transform`. Less recursive calls with tabs/rows/collapsible

Additional fixes
- Uses current transaction for querying nested relationships properties
in `buildQuery`, previously it wasn't used which could've led to wrong
results
- Allows to clear not required point fields with passing `null` from the
Local API. Previously it didn't work in both, MongoDB and Postgres

Benchmarks using this file
https://github.com/payloadcms/payload/blob/chore/db-benchmark/test/_community/int.spec.ts

### Small Dataset Performance

| Metric | Before Optimization | After Optimization | Improvement (%) |

|---------------------------|---------------------|--------------------|-----------------|
| Average FULL (ms) | 1170 | 844 | 27.86% |
| `payload.db.create` (ms) | 1413 | 691 | 51.12% |
| `payload.db.find` (ms) | 2856 | 2204 | 22.83% |
| `payload.db.deleteMany` (ms) | 15206 | 8439 | 44.53% |
| `payload.db.updateOne` (ms) | 21444 | 12162 | 43.30% |
| `payload.db.findOne` (ms) | 159 | 112 | 29.56% |
| `payload.db.deleteOne` (ms) | 3729 | 2578 | 30.89% |
| DB small FULL (ms) | 64473 | 46451 | 27.93% |

---

### Medium Dataset Performance

| Metric | Before Optimization | After Optimization | Improvement (%) |

|---------------------------|---------------------|--------------------|-----------------|
| Average FULL (ms) | 9407 | 6210 | 33.99% |
| `payload.db.create` (ms) | 10270 | 4321 | 57.93% |
| `payload.db.find` (ms) | 20814 | 16036 | 22.93% |
| `payload.db.deleteMany` (ms) | 126351 | 61789 | 51.11% |
| `payload.db.updateOne` (ms) | 201782 | 99943 | 50.49% |
| `payload.db.findOne` (ms) | 1081 | 817 | 24.43% |
| `payload.db.deleteOne` (ms) | 28534 | 23363 | 18.12% |
| DB medium FULL (ms) | 519518 | 342194 | 34.13% |

---

### Large Dataset Performance

| Metric | Before Optimization | After Optimization | Improvement (%) |

|---------------------------|---------------------|--------------------|-----------------|
| Average FULL (ms) | 26575 | 17509 | 34.14% |
| `payload.db.create` (ms) | 29085 | 12196 | 58.08% |
| `payload.db.find` (ms) | 58497 | 43838 | 25.04% |
| `payload.db.deleteMany` (ms) | 372195 | 173218 | 53.47% |
| `payload.db.updateOne` (ms) | 544089 | 288350 | 47.00% |
| `payload.db.findOne` (ms) | 3058 | 2197 | 28.14% |
| `payload.db.deleteOne` (ms) | 82444 | 64730 | 21.49% |
| DB large FULL (ms) | 1461097 | 969714 | 33.62% |
2024-12-19 13:20:39 -05:00
Andrzej Kłapeć
6a8aecadf8 fix(richtext-lexical): incorrect string interpolation in the upload converter (#10069)
This fixes the incorrect `<source>` `media` attribute value generation
within the upload converter.

<img width="919" alt="Zrzut ekranu 2024-12-19 o 12 21 10"
src="https://github.com/user-attachments/assets/6f26de7e-26e0-446a-83c5-6e5a776fac1e"
/>
2024-12-19 11:03:51 -07: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
5753efb0a4 fix(db-mongodb): querying by localized polymorphic relationships using objects (#10037)
Previously, queries like this didn't work:
```ts
const res = await payload.find({
  collection: 'polymorphic-relationships',
  where: {
    polymorphicLocalized: {
      equals: {
        relationTo: 'movies',
        value: movie.id,
      },
    },
  },
})
```

This was due to the incorrectly passed path to MongoDB without
`.{locale}` suffix.
Additionally, to MongoDB now we send:
```

{
  $or: [
    {
      polymorphic: {
        $eq: {
          relationTo: formattedValue.relationTo,
          value: formattedValue.value,
        },
      },
    },
    {
      polymorphic: {
        $eq: {
          relationTo: 'movies',
          value: 'some-id',
        },
      },
    },
  ],
},
```

Instead of:
```
{
  $and: [
    {
      'polymorphic.relationTo': {
        $eq: 'movies ',
      },
    },
    {
      'polymorphic.value': {
        $eq: 'some-id ',
      },
    },
  ],
}
```

To match the _exact_ value. This is essential when we do querying by
relationships with `hasMany: true` and custom IDs that can be repeated.
`$or` is needed if for some reason keys are stored in the DB in a
different order
2024-12-19 10:42:15 -05:00
Germán Jabloñski
12dad35cf9 fix(richtext-lexical): ui bug when zooming in Safari (#10072)
Fix #10043
2024-12-19 15:00:31 +00: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
1d46b6d738 fix(ui): join field "add new" calculate initial drawer data with relationship inside blocks (#10057)
We merged https://github.com/payloadcms/payload/pull/9773 that adds
support for join field with relationships inside arrays, it just happens
that now it's also true for relationships inside blocks, added a test
case with blocks.
This just corrects initial data drawer calculation with the "add new"
button if we encounter a blocks field in join's `on`.
2024-12-19 08:45:22 +02: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
Paul
ce74f1b238 fix(storage-vercel-blob): fixes issue where files with spaces in their name would not be retrieved correctly (#10062)
URI encodes filenames so that they're retrieved correctly from Vercel's
blob storage. Sometimes spaces would cause problems only in certain file
names
2024-12-19 00:32:59 +00:00
Sasha
97c120ab28 fix(richtext-*): use correct "for" attribute for label (#10036)
Fixes https://github.com/payloadcms/payload/issues/10034
2024-12-18 16:58:59 -07:00
Paul
439dd04ce9 fix: commit transaction if a user isnt found in forgotPassword operation (#10055)
The forgotPassword operation exits silently if no user is found, but
because we don't throw an error the transaction never gets committed
leading to a timeout.
2024-12-18 15:53:53 -06:00
Elliot DeNolf
d0d7b51ed5 chore(release): v3.9.0 [skip ci] 2024-12-18 15:58:11 -05:00
Paul
ef90ebb395 feat(storage-*): add support for browser-based caching via etags (#10014)
This PR makes changes to every storage adapter in order to add
browser-based caching by returning etags, then checking for them into
incoming requests and responding a status code of `304` so the data
doesn't have to be returned again.

Performance improvements for cached subsequent requests:

![image](https://github.com/user-attachments/assets/e51b812c-63a0-4bdb-a396-0f172982cb07)


This respects `disableCache` in the dev tools.



Also fixes a bug with getting the latest image when using the Vercel
Blob Storage adapter.
2024-12-18 15:54:36 -05:00
Alessio Gravili
194a8c189a feat: add shouldRestore config to job queue tasks (#10059)
By default, if a task has passed previously and a workflow is re-run,
the task will not be re-run. Instead, the output from the previous task
run will be returned. This is to prevent unnecessary re-runs of tasks
that have already passed.

This PR allows you to configure this behavior through the
`retries.shouldRestore` property. This property accepts a boolean or a
function for more complex restore behaviors.
2024-12-18 13:16:13 -07:00
Patrik
1446fe4694 fix: encodes upload filename urls (#10048)
### What?

Previously, upload files urls were not being encoded.

### Why?

As a result, this could lead to discrepancies where upload filenames
with spaces - the spaces would not be encoded as %20 in the URL.

### How?

To address this issue, we simply need to encode the filename of the
upload media.

Fixes #9698
2024-12-18 14:37:29 -05:00
Elliot DeNolf
f29e6335f6 fix(payload-cloud): improve not found logging (#10058)
In Payload Cloud, an unhelpful message would be surfaced if attempting
to retrieve a non-existent file. This improves the log message and
response to be more helpful.
2024-12-18 14:34:43 -05:00