Skip to content
5 changes: 4 additions & 1 deletion packages/drizzle/src/schema/build.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { FlattenedField, SanitizedCompoundIndex } from 'payload'
import type { Block, FlattenedField, SanitizedCompoundIndex } from 'payload'

import { InvalidConfiguration } from 'payload'
import toSnakeCase from 'to-snake-case'
Expand Down Expand Up @@ -48,6 +48,7 @@ type Args = {
rootTableName?: string
rootUniqueRelationships?: Set<string>
setColumnID: SetColumnID
sharedBlocks?: Set<Block>
tableName: string
timestamps?: boolean
versions: boolean
Expand Down Expand Up @@ -85,6 +86,7 @@ export const buildTable = ({
rootTableName: incomingRootTableName,
rootUniqueRelationships,
setColumnID,
sharedBlocks,
tableName,
timestamps,
versions,
Expand Down Expand Up @@ -141,6 +143,7 @@ export const buildTable = ({
rootTableIDColType: rootTableIDColType || idColType,
rootTableName,
setColumnID,
sharedBlocks,
uniqueRelationships,
versions,
withinLocalizedArrayOrBlock,
Expand Down
7 changes: 7 additions & 0 deletions packages/drizzle/src/schema/buildRawSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { DrizzleAdapter, RawIndex, SetColumnID } from '../types.js'

import { createTableName } from '../createTableName.js'
import { buildIndexName } from '../utilities/buildIndexName.js'
import { findSharedBlocks } from '../utilities/findSharedBlocks.js'
import { buildTable } from './build.js'

/**
Expand All @@ -24,6 +25,8 @@ export const buildRawSchema = ({
adapter.indexes = new Set()
adapter.foreignKeys = new Set()

const sharedBlocks = findSharedBlocks(adapter.payload);

adapter.payload.config.collections.forEach((collection) => {
createTableName({
adapter,
Expand Down Expand Up @@ -65,6 +68,7 @@ export const buildRawSchema = ({
fields: collection.flattenedFields,
parentIsLocalized: false,
setColumnID,
sharedBlocks,
tableName,
timestamps: collection.timestamps,
versions: false,
Expand All @@ -85,6 +89,7 @@ export const buildRawSchema = ({
fields: versionFields,
parentIsLocalized: false,
setColumnID,
sharedBlocks,
tableName: versionsTableName,
timestamps: true,
versions: true,
Expand All @@ -106,6 +111,7 @@ export const buildRawSchema = ({
fields: global.flattenedFields,
parentIsLocalized: false,
setColumnID,
sharedBlocks,
tableName,
timestamps: false,
versions: false,
Expand All @@ -129,6 +135,7 @@ export const buildRawSchema = ({
fields: versionFields,
parentIsLocalized: false,
setColumnID,
sharedBlocks,
tableName: versionsTableName,
timestamps: true,
versions: true,
Expand Down
15 changes: 11 additions & 4 deletions packages/drizzle/src/schema/traverseFields.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { FlattenedField } from 'payload'
import type { Block, FlattenedField } from 'payload'

import { InvalidConfiguration } from 'payload'
import {
Expand Down Expand Up @@ -56,6 +56,7 @@ type Args = {
rootTableIDColType: IDType
rootTableName: string
setColumnID: SetColumnID
sharedBlocks?: Set<Block>
uniqueRelationships: Set<string>
versions: boolean
/**
Expand Down Expand Up @@ -97,6 +98,7 @@ export const traverseFields = ({
rootTableIDColType,
rootTableName,
setColumnID,
sharedBlocks,
uniqueRelationships,
versions,
withinLocalizedArrayOrBlock,
Expand Down Expand Up @@ -450,8 +452,13 @@ export const traverseFields = ({
},
}

const baseForeignKeys: Record<string, RawForeignKey> = {
_parentIdFk: {
const isSharedBlock = sharedBlocks && sharedBlocks.has(block.originalRef)
const baseForeignKeys: Record<string, RawForeignKey> = {}

const skipForeignKey = isSharedBlock && block.dbName

if (!skipForeignKey) {
baseForeignKeys._parentIdFk = {
name: `${blockTableName}_parent_id_fk`,
columns: ['_parentID'],
foreignColumns: [
Expand All @@ -461,7 +468,7 @@ export const traverseFields = ({
},
],
onDelete: 'cascade',
},
}
}

const isLocalized =
Expand Down
62 changes: 62 additions & 0 deletions packages/drizzle/src/utilities/findSharedBlocks.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Block, Payload, sanitizeConfig } from 'payload'
import { findSharedBlocks } from './findSharedBlocks'
const block_1: Block = {
slug: 'test-block',
fields: [],
}

const block_2: Block = {
slug: 'test-block-non-shared',
fields: [],
}

const getPayload = async () => {
return {
config: await sanitizeConfig({
collections: [
{
slug: 'collection-1',
fields: [
{
type: 'blocks',
name: 'blocks',
blocks: [block_1],
},
],
},
{
slug: 'collection-2',
fields: [
{
type: 'group',
name: 'group',
fields: [
{
type: 'blocks',
name: 'blocks',
blocks: [block_1, block_2],
},
],
},
],
},
],
} as any),
} as Payload
}

describe('findSharedBlocks', () => {
it('should find block_1 as shared', async () => {
const payload = await getPayload()
const map = findSharedBlocks(payload)
expect(map.size).toBe(1)
expect(map.has(block_1)).toBe(true)
})

it('should not find block_2 as shared', async () => {
const payload = await getPayload()
const map = findSharedBlocks(payload)
expect(map.size).toBe(1)
expect(map.has(block_2)).toBe(false)
})
})
63 changes: 63 additions & 0 deletions packages/drizzle/src/utilities/findSharedBlocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { Block, Field, FlattenedField, Payload } from 'payload'

/**
* Helper function to find all shared blocks across collections and globals.
* A block is considered "shared" if the same JS reference appears in multiple collections/globals.
*
* @param payload - The Payload instance
* @returns A Set of block references that are shared across multiple collections/globals
*/
export const findSharedBlocks = (payload: Payload): Set<Block> => {
const blockReferencesMap = new Map<Block, Set<string>>() // Map<blockReference, Set<collectionSlug>>

/**
* Recursively traverse fields to find block usage
* @param fields - Array of field configurations
* @param parentSlug - The slug of the parent collection/global
*/
const traverseFieldsForBlocks = (fields: FlattenedField[], parentSlug: string): void => {
fields.forEach((field) => {
if (field.type === 'blocks') {
const blockRefs = field.blockReferences ?? field.blocks
if (blockRefs) {
blockRefs.forEach((blockRef) => {
const block = typeof blockRef === 'string' ? payload.blocks[blockRef] : blockRef

if (!blockReferencesMap.has(block.originalRef)) {
blockReferencesMap.set(block.originalRef, new Set<string>())
}
const blockSet = blockReferencesMap.get(block.originalRef)
if (blockSet) {
blockSet.add(parentSlug)
}
})
}
}

// Recursively check nested fields
if ('flattenedFields' in field) {
traverseFieldsForBlocks(field.flattenedFields, parentSlug)
}
})
}

// Check all collections
payload.config.collections.forEach((collection) => {
traverseFieldsForBlocks(collection.flattenedFields, collection.slug)
})

// Check all globals
payload.config.globals.forEach((global) => {
traverseFieldsForBlocks(global.flattenedFields, global.slug)
})

// Return only blocks that are used in multiple collections/globals
const sharedBlocks = new Set<Block>()
blockReferencesMap.forEach((collections, blockRef) => {
if (collections.size > 1) {
sharedBlocks.add(blockRef)
}
})

return sharedBlocks
}
1 change: 1 addition & 0 deletions packages/payload/src/fields/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1713,6 +1713,7 @@ export type JoinFieldClient = {

export type FlattenedBlock = {
flattenedFields: FlattenedField[]
originalRef?: Block
} & Block

export type FlattenedBlocksField = {
Expand Down
1 change: 1 addition & 0 deletions packages/payload/src/utilities/flattenAllFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const flattenBlock = ({ block }: { block: Block }): FlattenedBlock => {
return {
...block,
flattenedFields: flattenAllFields({ fields: block.fields }),
originalRef: block,
}
}

Expand Down
Loading