diff --git a/packages/drizzle/src/schema/build.ts b/packages/drizzle/src/schema/build.ts index 64dcbb2b1f7..609970ac090 100644 --- a/packages/drizzle/src/schema/build.ts +++ b/packages/drizzle/src/schema/build.ts @@ -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' @@ -48,6 +48,7 @@ type Args = { rootTableName?: string rootUniqueRelationships?: Set setColumnID: SetColumnID + sharedBlocks?: Set tableName: string timestamps?: boolean versions: boolean @@ -85,6 +86,7 @@ export const buildTable = ({ rootTableName: incomingRootTableName, rootUniqueRelationships, setColumnID, + sharedBlocks, tableName, timestamps, versions, @@ -141,6 +143,7 @@ export const buildTable = ({ rootTableIDColType: rootTableIDColType || idColType, rootTableName, setColumnID, + sharedBlocks, uniqueRelationships, versions, withinLocalizedArrayOrBlock, diff --git a/packages/drizzle/src/schema/buildRawSchema.ts b/packages/drizzle/src/schema/buildRawSchema.ts index 525349c2553..843b841a2f8 100644 --- a/packages/drizzle/src/schema/buildRawSchema.ts +++ b/packages/drizzle/src/schema/buildRawSchema.ts @@ -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' /** @@ -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, @@ -65,6 +68,7 @@ export const buildRawSchema = ({ fields: collection.flattenedFields, parentIsLocalized: false, setColumnID, + sharedBlocks, tableName, timestamps: collection.timestamps, versions: false, @@ -85,6 +89,7 @@ export const buildRawSchema = ({ fields: versionFields, parentIsLocalized: false, setColumnID, + sharedBlocks, tableName: versionsTableName, timestamps: true, versions: true, @@ -106,6 +111,7 @@ export const buildRawSchema = ({ fields: global.flattenedFields, parentIsLocalized: false, setColumnID, + sharedBlocks, tableName, timestamps: false, versions: false, @@ -129,6 +135,7 @@ export const buildRawSchema = ({ fields: versionFields, parentIsLocalized: false, setColumnID, + sharedBlocks, tableName: versionsTableName, timestamps: true, versions: true, diff --git a/packages/drizzle/src/schema/traverseFields.ts b/packages/drizzle/src/schema/traverseFields.ts index 09fd865e4e0..1a7748fddb7 100644 --- a/packages/drizzle/src/schema/traverseFields.ts +++ b/packages/drizzle/src/schema/traverseFields.ts @@ -1,4 +1,4 @@ -import type { FlattenedField } from 'payload' +import type { Block, FlattenedField } from 'payload' import { InvalidConfiguration } from 'payload' import { @@ -56,6 +56,7 @@ type Args = { rootTableIDColType: IDType rootTableName: string setColumnID: SetColumnID + sharedBlocks?: Set uniqueRelationships: Set versions: boolean /** @@ -97,6 +98,7 @@ export const traverseFields = ({ rootTableIDColType, rootTableName, setColumnID, + sharedBlocks, uniqueRelationships, versions, withinLocalizedArrayOrBlock, @@ -450,8 +452,13 @@ export const traverseFields = ({ }, } - const baseForeignKeys: Record = { - _parentIdFk: { + const isSharedBlock = sharedBlocks && sharedBlocks.has(block.originalRef) + const baseForeignKeys: Record = {} + + const skipForeignKey = isSharedBlock && block.dbName + + if (!skipForeignKey) { + baseForeignKeys._parentIdFk = { name: `${blockTableName}_parent_id_fk`, columns: ['_parentID'], foreignColumns: [ @@ -461,7 +468,7 @@ export const traverseFields = ({ }, ], onDelete: 'cascade', - }, + } } const isLocalized = diff --git a/packages/drizzle/src/utilities/findSharedBlocks.spec.ts b/packages/drizzle/src/utilities/findSharedBlocks.spec.ts new file mode 100644 index 00000000000..b7848f6f895 --- /dev/null +++ b/packages/drizzle/src/utilities/findSharedBlocks.spec.ts @@ -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) + }) +}) diff --git a/packages/drizzle/src/utilities/findSharedBlocks.ts b/packages/drizzle/src/utilities/findSharedBlocks.ts new file mode 100644 index 00000000000..04f9a13c129 --- /dev/null +++ b/packages/drizzle/src/utilities/findSharedBlocks.ts @@ -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 => { + const blockReferencesMap = new Map>() // Map> + + /** + * 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()) + } + 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() + blockReferencesMap.forEach((collections, blockRef) => { + if (collections.size > 1) { + sharedBlocks.add(blockRef) + } + }) + + return sharedBlocks +} diff --git a/packages/payload/src/fields/config/types.ts b/packages/payload/src/fields/config/types.ts index 537740a4722..d5828799e23 100644 --- a/packages/payload/src/fields/config/types.ts +++ b/packages/payload/src/fields/config/types.ts @@ -1713,6 +1713,7 @@ export type JoinFieldClient = { export type FlattenedBlock = { flattenedFields: FlattenedField[] + originalRef?: Block } & Block export type FlattenedBlocksField = { diff --git a/packages/payload/src/utilities/flattenAllFields.ts b/packages/payload/src/utilities/flattenAllFields.ts index 9173e0885a9..e16394e5c76 100644 --- a/packages/payload/src/utilities/flattenAllFields.ts +++ b/packages/payload/src/utilities/flattenAllFields.ts @@ -13,6 +13,7 @@ export const flattenBlock = ({ block }: { block: Block }): FlattenedBlock => { return { ...block, flattenedFields: flattenAllFields({ fields: block.fields }), + originalRef: block, } }