feat: add forceSelect collection / global config property (#11627)
### What?
Adds a new property to collection / global config `forceSelect` which
can be used to ensure that some fields are always selected, regardless
of the `select` query.
### Why?
This can be beneficial for hooks and access control, for example imagine
you need the value of `data.slug` in your hook.
With the following query it would be `undefined`:
`?select[title]=true`
Now, to solve this you can specify
```
forceSelect: {
slug: true
}
```
### How?
Every operation now merges the incoming `select` with
`collectionConfig.forceSelect`.
This commit is contained in:
@@ -83,7 +83,7 @@ export interface Config {
|
||||
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
||||
};
|
||||
db: {
|
||||
defaultIDType: string;
|
||||
defaultIDType: number;
|
||||
};
|
||||
globals: {
|
||||
menu: Menu;
|
||||
@@ -123,7 +123,7 @@ export interface UserAuthOperations {
|
||||
* via the `definition` "posts".
|
||||
*/
|
||||
export interface Post {
|
||||
id: string;
|
||||
id: number;
|
||||
title?: string | null;
|
||||
content?: {
|
||||
root: {
|
||||
@@ -148,7 +148,7 @@ export interface Post {
|
||||
* via the `definition` "media".
|
||||
*/
|
||||
export interface Media {
|
||||
id: string;
|
||||
id: number;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string | null;
|
||||
@@ -192,7 +192,7 @@ export interface Media {
|
||||
* via the `definition` "users".
|
||||
*/
|
||||
export interface User {
|
||||
id: string;
|
||||
id: number;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
email: string;
|
||||
@@ -209,24 +209,24 @@ export interface User {
|
||||
* via the `definition` "payload-locked-documents".
|
||||
*/
|
||||
export interface PayloadLockedDocument {
|
||||
id: string;
|
||||
id: number;
|
||||
document?:
|
||||
| ({
|
||||
relationTo: 'posts';
|
||||
value: string | Post;
|
||||
value: number | Post;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'media';
|
||||
value: string | Media;
|
||||
value: number | Media;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
value: number | User;
|
||||
} | null);
|
||||
globalSlug?: string | null;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
value: number | User;
|
||||
};
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
@@ -236,10 +236,10 @@ export interface PayloadLockedDocument {
|
||||
* via the `definition` "payload-preferences".
|
||||
*/
|
||||
export interface PayloadPreference {
|
||||
id: string;
|
||||
id: number;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
value: number | User;
|
||||
};
|
||||
key?: string | null;
|
||||
value?:
|
||||
@@ -259,7 +259,7 @@ export interface PayloadPreference {
|
||||
* via the `definition` "payload-migrations".
|
||||
*/
|
||||
export interface PayloadMigration {
|
||||
id: string;
|
||||
id: number;
|
||||
name?: string | null;
|
||||
batch?: number | null;
|
||||
updatedAt: string;
|
||||
@@ -378,7 +378,7 @@ export interface PayloadMigrationsSelect<T extends boolean = true> {
|
||||
* via the `definition` "menu".
|
||||
*/
|
||||
export interface Menu {
|
||||
id: string;
|
||||
id: number;
|
||||
globalText?: string | null;
|
||||
updatedAt?: string | null;
|
||||
createdAt?: string | null;
|
||||
|
||||
26
test/select/collections/ForceSelect/index.tsx
Normal file
26
test/select/collections/ForceSelect/index.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
export const ForceSelect: CollectionConfig<'force-select'> = {
|
||||
slug: 'force-select',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'forceSelected',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'array',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'forceSelected',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
forceSelect: { array: { forceSelected: true }, forceSelected: true },
|
||||
}
|
||||
@@ -1,11 +1,16 @@
|
||||
import type { GlobalConfig } from 'payload'
|
||||
|
||||
import { lexicalEditor } from '@payloadcms/richtext-lexical'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import path from 'path'
|
||||
|
||||
import type { Post } from './payload-types.js'
|
||||
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||
import { devUser } from '../credentials.js'
|
||||
import { CustomID } from './collections/CustomID/index.js'
|
||||
import { DeepPostsCollection } from './collections/DeepPosts/index.js'
|
||||
import { ForceSelect } from './collections/ForceSelect/index.js'
|
||||
import { LocalizedPostsCollection } from './collections/LocalizedPosts/index.js'
|
||||
import { Pages } from './collections/Pages/index.js'
|
||||
import { Points } from './collections/Points/index.js'
|
||||
@@ -24,6 +29,7 @@ export default buildConfigWithDefaults({
|
||||
DeepPostsCollection,
|
||||
Pages,
|
||||
Points,
|
||||
ForceSelect,
|
||||
{
|
||||
slug: 'upload',
|
||||
fields: [],
|
||||
@@ -51,6 +57,30 @@ export default buildConfigWithDefaults({
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'force-select-global',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'forceSelected',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'array',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'forceSelected',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
forceSelect: { array: { forceSelected: true }, forceSelected: true },
|
||||
} satisfies GlobalConfig<'force-select-global'>,
|
||||
],
|
||||
admin: {
|
||||
importMap: {
|
||||
|
||||
@@ -2336,6 +2336,53 @@ describe('Select', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should force collection select fields with forceSelect', async () => {
|
||||
const { id, text, array, forceSelected } = await payload.create({
|
||||
collection: 'force-select',
|
||||
data: {
|
||||
array: [{ forceSelected: 'text' }],
|
||||
text: 'some-text',
|
||||
forceSelected: 'force-selected',
|
||||
},
|
||||
})
|
||||
|
||||
const response = await payload.findByID({
|
||||
collection: 'force-select',
|
||||
id,
|
||||
select: { text: true },
|
||||
})
|
||||
|
||||
expect(response).toStrictEqual({
|
||||
id,
|
||||
forceSelected,
|
||||
text,
|
||||
array,
|
||||
})
|
||||
})
|
||||
|
||||
it('should force global select fields with forceSelect', async () => {
|
||||
const { forceSelected, id, array, text } = await payload.updateGlobal({
|
||||
slug: 'force-select-global',
|
||||
data: {
|
||||
array: [{ forceSelected: 'text' }],
|
||||
text: 'some-text',
|
||||
forceSelected: 'force-selected',
|
||||
},
|
||||
})
|
||||
|
||||
const response = await payload.findGlobal({
|
||||
slug: 'force-select-global',
|
||||
select: { text: true },
|
||||
})
|
||||
|
||||
expect(response).toStrictEqual({
|
||||
id,
|
||||
forceSelected,
|
||||
text,
|
||||
array,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
async function createPost() {
|
||||
|
||||
@@ -72,6 +72,7 @@ export interface Config {
|
||||
'deep-posts': DeepPost;
|
||||
pages: Page;
|
||||
points: Point;
|
||||
'force-select': ForceSelect;
|
||||
upload: Upload;
|
||||
rels: Rel;
|
||||
'custom-ids': CustomId;
|
||||
@@ -88,6 +89,7 @@ export interface Config {
|
||||
'deep-posts': DeepPostsSelect<false> | DeepPostsSelect<true>;
|
||||
pages: PagesSelect<false> | PagesSelect<true>;
|
||||
points: PointsSelect<false> | PointsSelect<true>;
|
||||
'force-select': ForceSelectSelect<false> | ForceSelectSelect<true>;
|
||||
upload: UploadSelect<false> | UploadSelect<true>;
|
||||
rels: RelsSelect<false> | RelsSelect<true>;
|
||||
'custom-ids': CustomIdsSelect<false> | CustomIdsSelect<true>;
|
||||
@@ -101,9 +103,11 @@ export interface Config {
|
||||
};
|
||||
globals: {
|
||||
'global-post': GlobalPost;
|
||||
'force-select-global': ForceSelectGlobal;
|
||||
};
|
||||
globalsSelect: {
|
||||
'global-post': GlobalPostSelect<false> | GlobalPostSelect<true>;
|
||||
'force-select-global': ForceSelectGlobalSelect<false> | ForceSelectGlobalSelect<true>;
|
||||
};
|
||||
locale: 'en' | 'de';
|
||||
user: User & {
|
||||
@@ -445,6 +449,23 @@ export interface Point {
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "force-select".
|
||||
*/
|
||||
export interface ForceSelect {
|
||||
id: string;
|
||||
text?: string | null;
|
||||
forceSelected?: string | null;
|
||||
array?:
|
||||
| {
|
||||
forceSelected?: string | null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "custom-ids".
|
||||
@@ -503,6 +524,10 @@ export interface PayloadLockedDocument {
|
||||
relationTo: 'points';
|
||||
value: string | Point;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'force-select';
|
||||
value: string | ForceSelect;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'upload';
|
||||
value: string | Upload;
|
||||
@@ -835,6 +860,22 @@ export interface PointsSelect<T extends boolean = true> {
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "force-select_select".
|
||||
*/
|
||||
export interface ForceSelectSelect<T extends boolean = true> {
|
||||
text?: T;
|
||||
forceSelected?: T;
|
||||
array?:
|
||||
| T
|
||||
| {
|
||||
forceSelected?: T;
|
||||
id?: T;
|
||||
};
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "upload_select".
|
||||
@@ -928,6 +969,23 @@ export interface GlobalPost {
|
||||
updatedAt?: string | null;
|
||||
createdAt?: string | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "force-select-global".
|
||||
*/
|
||||
export interface ForceSelectGlobal {
|
||||
id: string;
|
||||
text?: string | null;
|
||||
forceSelected?: string | null;
|
||||
array?:
|
||||
| {
|
||||
forceSelected?: string | null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
updatedAt?: string | null;
|
||||
createdAt?: string | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "global-post_select".
|
||||
@@ -939,6 +997,23 @@ export interface GlobalPostSelect<T extends boolean = true> {
|
||||
createdAt?: T;
|
||||
globalType?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "force-select-global_select".
|
||||
*/
|
||||
export interface ForceSelectGlobalSelect<T extends boolean = true> {
|
||||
text?: T;
|
||||
forceSelected?: T;
|
||||
array?:
|
||||
| T
|
||||
| {
|
||||
forceSelected?: T;
|
||||
id?: T;
|
||||
};
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
globalType?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "auth".
|
||||
|
||||
Reference in New Issue
Block a user