feat: support relationship writes using objects instead of IDs (#9253)

### What?
Previously, this code led to a validation error because `movie` is an
object and you needed to use `movie.id` instead.
```ts
const movie = await payload.create({ collection: 'movies', data: {} })
const result = await payload.create({
  collection: 'object-writes',
  data: {
    many: [movie],
    manyPoly: [{ relationTo: 'movies', value: movie }],
    one: movie,
    onePoly: {
      relationTo: 'movies',
      value: movie,
    },
  },
})
```
While it's simple to modify this example, it's more painful when you
have a data with `depth` > 0 and then you want to update that document.

### Why?
Better DX as less checks needed, and TypeScript says that we can pass an
object.

### How?
Sanitizes the field value in the root `beforeValidate` hook
This commit is contained in:
Sasha
2024-11-17 11:25:32 +02:00
committed by GitHub
parent 35917c67d7
commit d21fca9156
4 changed files with 136 additions and 5 deletions

View File

@@ -341,6 +341,33 @@ export default buildConfigWithDefaults({
},
],
},
{
slug: 'object-writes',
fields: [
{
type: 'relationship',
relationTo: 'movies',
name: 'one',
},
{
type: 'relationship',
relationTo: 'movies',
name: 'many',
hasMany: true,
},
{
type: 'relationship',
relationTo: ['movies'],
name: 'onePoly',
},
{
type: 'relationship',
relationTo: ['movies'],
name: 'manyPoly',
hasMany: true,
},
],
},
],
onInit: async (payload) => {
await payload.create({

View File

@@ -1181,7 +1181,7 @@ describe('Relationships', () => {
})
})
describe('Creating', () => {
describe('Writing', () => {
describe('With transactions', () => {
it('should be able to create filtered relations within a transaction', async () => {
const req = {} as PayloadRequest
@@ -1208,6 +1208,52 @@ describe('Relationships', () => {
expect(withRelation.filteredRelation.id).toEqual(related.id)
})
})
describe('With passing an object', () => {
it('should create with passing an object', async () => {
const movie = await payload.create({ collection: 'movies', data: {} })
const result = await payload.create({
collection: 'object-writes',
data: {
many: [movie],
manyPoly: [{ relationTo: 'movies', value: movie }],
one: movie,
onePoly: {
relationTo: 'movies',
value: movie,
},
},
})
expect(result.many[0]).toStrictEqual(movie)
expect(result.one).toStrictEqual(movie)
expect(result.manyPoly[0]).toStrictEqual({ relationTo: 'movies', value: movie })
expect(result.onePoly).toStrictEqual({ relationTo: 'movies', value: movie })
})
it('should update with passing an object', async () => {
const movie = await payload.create({ collection: 'movies', data: {} })
const { id } = await payload.create({ collection: 'object-writes', data: {} })
const result = await payload.update({
collection: 'object-writes',
id,
data: {
many: [movie],
manyPoly: [{ relationTo: 'movies', value: movie }],
one: movie,
onePoly: {
relationTo: 'movies',
value: movie,
},
},
})
expect(result.many[0]).toStrictEqual(movie)
expect(result.one).toStrictEqual(movie)
expect(result.manyPoly[0]).toStrictEqual({ relationTo: 'movies', value: movie })
expect(result.onePoly).toStrictEqual({ relationTo: 'movies', value: movie })
})
})
})
describe('Polymorphic Relationships', () => {

View File

@@ -27,6 +27,7 @@ export interface Config {
pages: Page;
'rels-to-pages': RelsToPage;
'rels-to-pages-and-custom-text-ids': RelsToPagesAndCustomTextId;
'object-writes': ObjectWrite;
users: User;
'payload-locked-documents': PayloadLockedDocument;
'payload-preferences': PayloadPreference;
@@ -50,6 +51,7 @@ export interface Config {
pages: PagesSelect<false> | PagesSelect<true>;
'rels-to-pages': RelsToPagesSelect<false> | RelsToPagesSelect<true>;
'rels-to-pages-and-custom-text-ids': RelsToPagesAndCustomTextIdsSelect<false> | RelsToPagesAndCustomTextIdsSelect<true>;
'object-writes': ObjectWritesSelect<false> | ObjectWritesSelect<true>;
users: UsersSelect<false> | UsersSelect<true>;
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
@@ -311,6 +313,27 @@ export interface RelsToPagesAndCustomTextId {
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "object-writes".
*/
export interface ObjectWrite {
id: string;
one?: (string | null) | Movie;
many?: (string | Movie)[] | null;
onePoly?: {
relationTo: 'movies';
value: string | Movie;
} | null;
manyPoly?:
| {
relationTo: 'movies';
value: string | Movie;
}[]
| null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents".
@@ -382,6 +405,10 @@ export interface PayloadLockedDocument {
relationTo: 'rels-to-pages-and-custom-text-ids';
value: string | RelsToPagesAndCustomTextId;
} | null)
| ({
relationTo: 'object-writes';
value: string | ObjectWrite;
} | null)
| ({
relationTo: 'users';
value: string | User;
@@ -609,6 +636,18 @@ export interface RelsToPagesAndCustomTextIdsSelect<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "object-writes_select".
*/
export interface ObjectWritesSelect<T extends boolean = true> {
one?: T;
many?: T;
onePoly?: T;
manyPoly?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users_select".