Skip to content

Commit f9e5573

Browse files
authored
feat: adds keepAfterRead to plugin-relationship-objectid (#7388)
## Description Duplicate of payloadcms/plugin-relationship-object-ids#6 for 3.x
1 parent e823051 commit f9e5573

File tree

3 files changed

+140
-7
lines changed

3 files changed

+140
-7
lines changed

packages/plugin-relationship-object-ids/README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Minimum required version of Payload: `1.9.5`
88

99
It injects a `beforeChange` field hook into each `relationship` and `upload` field, which converts string-based IDs to `ObjectID`s immediately prior to storage.
1010

11+
By default, it also injects an `afterRead` field hook into the above fields, which ensures that the values are re-formatted back to strings after having been read from the database.
12+
1113
#### Usage
1214

1315
Simply import and install the plugin to make it work:
@@ -20,7 +22,11 @@ export default buildConfig({
2022
// your config here
2123
plugins: [
2224
// Call the plugin within your `plugins` array
23-
relationshipsAsObjectID(),
25+
relationshipsAsObjectID({
26+
// Optionally keep relationship values as ObjectID
27+
// when they are retrieved from the database.
28+
keepAfterRead: true,
29+
}),
2430
],
2531
})
2632
```
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import type { CollectionConfig, Config, FieldHook, RelationshipField, UploadField } from 'payload'
2+
3+
import mongoose from 'mongoose'
4+
import { fieldAffectsData } from 'payload/shared'
5+
6+
const convertValue = ({
7+
relatedCollection,
8+
value,
9+
}: {
10+
relatedCollection: CollectionConfig
11+
value: number | string
12+
}): mongoose.Types.ObjectId | number | string => {
13+
const customIDField = relatedCollection.fields.find(
14+
(field) => fieldAffectsData(field) && field.name === 'id',
15+
)
16+
17+
if (!customIDField && mongoose.Types.ObjectId.isValid(value)) {
18+
return value.toString()
19+
}
20+
21+
return value
22+
}
23+
24+
interface RelationObject {
25+
relationTo: string
26+
value: number | string
27+
}
28+
29+
function isValidRelationObject(value: unknown): value is RelationObject {
30+
return typeof value === 'object' && value !== null && 'relationTo' in value && 'value' in value
31+
}
32+
33+
interface Args {
34+
config: Config
35+
field: RelationshipField | UploadField
36+
}
37+
38+
export const getAfterReadHook =
39+
({ config, field }: Args): FieldHook =>
40+
({ value }) => {
41+
let relatedCollection: CollectionConfig | undefined
42+
43+
const hasManyRelations = typeof field.relationTo !== 'string'
44+
45+
if (!hasManyRelations) {
46+
relatedCollection = config.collections?.find(({ slug }) => slug === field.relationTo)
47+
}
48+
49+
if (Array.isArray(value)) {
50+
return value.map((val) => {
51+
// Handle has many
52+
if (relatedCollection && val) {
53+
return convertValue({
54+
relatedCollection,
55+
value: val,
56+
})
57+
}
58+
59+
// Handle has many - polymorphic
60+
if (isValidRelationObject(val)) {
61+
const relatedCollectionForSingleValue = config.collections?.find(
62+
({ slug }) => slug === val.relationTo,
63+
)
64+
65+
if (relatedCollectionForSingleValue) {
66+
return {
67+
relationTo: val.relationTo,
68+
value: convertValue({
69+
relatedCollection: relatedCollectionForSingleValue,
70+
value: val.value,
71+
}),
72+
}
73+
}
74+
}
75+
76+
return val
77+
})
78+
}
79+
80+
// Handle has one - polymorphic
81+
if (isValidRelationObject(value)) {
82+
relatedCollection = config.collections?.find(({ slug }) => slug === value.relationTo)
83+
84+
if (relatedCollection) {
85+
return {
86+
relationTo: value.relationTo,
87+
value: convertValue({ relatedCollection, value: value.value }),
88+
}
89+
}
90+
}
91+
92+
// Handle has one
93+
if (relatedCollection && value) {
94+
return convertValue({
95+
relatedCollection,
96+
value,
97+
})
98+
}
99+
100+
return value
101+
}

packages/plugin-relationship-object-ids/src/index.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,28 @@
1-
import type { Config, Field } from 'payload'
1+
import type { Config, Field, FieldHook } from 'payload'
22

3+
import { getAfterReadHook } from './hooks/afterRead.js'
34
import { getBeforeChangeHook } from './hooks/beforeChange.js'
45

5-
const traverseFields = ({ config, fields }: { config: Config; fields: Field[] }): Field[] => {
6+
interface TraverseFieldsArgs {
7+
config: Config
8+
fields: Field[]
9+
keepAfterRead: boolean
10+
}
11+
12+
const traverseFields = ({ config, fields, keepAfterRead }: TraverseFieldsArgs): Field[] => {
613
return fields.map((field) => {
714
if (field.type === 'relationship' || field.type === 'upload') {
15+
const afterRead: FieldHook[] = [...(field.hooks?.afterRead || [])]
16+
17+
if (!keepAfterRead) {
18+
afterRead.unshift(getAfterReadHook({ config, field }))
19+
}
20+
821
return {
922
...field,
1023
hooks: {
1124
...(field.hooks || {}),
25+
afterRead,
1226
beforeChange: [
1327
...(field.hooks?.beforeChange || []),
1428
getBeforeChangeHook({ config, field }),
@@ -20,7 +34,7 @@ const traverseFields = ({ config, fields }: { config: Config; fields: Field[] })
2034
if ('fields' in field) {
2135
return {
2236
...field,
23-
fields: traverseFields({ config, fields: field.fields }),
37+
fields: traverseFields({ config, fields: field.fields, keepAfterRead }),
2438
}
2539
}
2640

@@ -30,7 +44,7 @@ const traverseFields = ({ config, fields }: { config: Config; fields: Field[] })
3044
tabs: field.tabs.map((tab) => {
3145
return {
3246
...tab,
33-
fields: traverseFields({ config, fields: tab.fields }),
47+
fields: traverseFields({ config, fields: tab.fields, keepAfterRead }),
3448
}
3549
}),
3650
}
@@ -42,7 +56,7 @@ const traverseFields = ({ config, fields }: { config: Config; fields: Field[] })
4256
blocks: field.blocks.map((block) => {
4357
return {
4458
...block,
45-
fields: traverseFields({ config, fields: block.fields }),
59+
fields: traverseFields({ config, fields: block.fields, keepAfterRead }),
4660
}
4761
}),
4862
}
@@ -52,9 +66,19 @@ const traverseFields = ({ config, fields }: { config: Config; fields: Field[] })
5266
})
5367
}
5468

69+
interface Args {
70+
/*
71+
If you want to keep ObjectIDs as ObjectIDs after read, you can enable this flag.
72+
By default, all relationship ObjectIDs are stringified within the AfterRead hook.
73+
*/
74+
keepAfterRead?: boolean
75+
}
76+
5577
export const relationshipsAsObjectID =
56-
(/** Possible args in the future */) =>
78+
(args?: Args) =>
5779
(config: Config): Config => {
80+
const keepAfterRead = typeof args?.keepAfterRead === 'boolean' ? args.keepAfterRead : false
81+
5882
return {
5983
...config,
6084
collections: (config.collections || []).map((collection) => {
@@ -63,6 +87,7 @@ export const relationshipsAsObjectID =
6387
fields: traverseFields({
6488
config,
6589
fields: collection.fields,
90+
keepAfterRead,
6691
}),
6792
}
6893
}),
@@ -72,6 +97,7 @@ export const relationshipsAsObjectID =
7297
fields: traverseFields({
7398
config,
7499
fields: global.fields,
100+
keepAfterRead,
75101
}),
76102
}
77103
}),

0 commit comments

Comments
 (0)