Files
klz-cables.com/.pnpm-store/v10/files/2b/281cc5262a46d902f7911f4f85dd1ecb68c318043b5f217abcf3fc54bd9018961b8c56a09ae93a0da43676288c55eb22acb6ed36e007243015fc2bb4896c55
Marc Mintel 5397309103
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Failing after 34s
Build & Deploy / 🏗️ Build (push) Has started running
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
fix(products): fix breadcrumbs and product filtering (backport from main)
2026-02-24 16:04:21 +01:00

659 lines
30 KiB
Plaintext

import { sql } from 'drizzle-orm';
import { APIError } from 'payload';
import { fieldIsVirtual, fieldShouldBeLocalized } from 'payload/shared';
import toSnakeCase from 'to-snake-case';
import { isArrayOfRows } from '../../utilities/isArrayOfRows.js';
import { resolveBlockTableName } from '../../utilities/validateExistingBlockIsIdentical.js';
import { transformArray } from './array.js';
import { transformBlocks } from './blocks.js';
import { transformNumbers } from './numbers.js';
import { transformRelationship } from './relationships.js';
import { transformSelects } from './selects.js';
import { transformTexts } from './texts.js';
export const traverseFields = ({ adapter, arrays, arraysToPush, baseTableName, blocks, blocksToDelete, columnPrefix, data, enableAtomicWrites, existingLocales, fieldPrefix, fields, forcedLocale, insideArrayOrBlock = false, locales, numbers, numbersToDelete, parentIsLocalized, parentTableName, path, relationships, relationshipsToAppend, relationshipsToDelete, row, selects, texts, textsToDelete, withinArrayOrBlockLocale })=>{
let fieldsMatched = false;
if (row._uuid) {
data._uuid = row._uuid;
}
fields.forEach((field)=>{
let columnName = '';
let fieldName = '';
let fieldData;
if (fieldIsVirtual(field)) {
return;
}
// Mark that we found a matching field
if (data[field.name] !== undefined) {
fieldsMatched = true;
}
columnName = `${columnPrefix || ''}${toSnakeCase(field.name)}`;
fieldName = `${fieldPrefix || ''}${field.name}`;
fieldData = data[field.name];
const isLocalized = fieldShouldBeLocalized({
field,
parentIsLocalized
});
if (field.type === 'array') {
const arrayTableName = adapter.tableNameMap.get(`${parentTableName}_${columnName}`);
if (isLocalized) {
const value = data[field.name];
if (typeof value === 'object' && value !== null) {
Object.entries(value).forEach(([localeKey, localeValue])=>{
let localeData = localeValue;
let push = false;
if (localeValue && typeof localeValue === 'object' && '$push' in localeValue) {
localeData = localeValue.$push;
push = true;
if (!Array.isArray(localeData)) {
localeData = [
localeData
];
}
}
if (Array.isArray(localeData)) {
const newRows = transformArray({
adapter,
arrayTableName,
baseTableName,
blocks,
blocksToDelete,
data: localeData,
field,
locale: localeKey,
numbers,
numbersToDelete,
parentIsLocalized: parentIsLocalized || field.localized,
path,
relationships,
relationshipsToDelete,
selects,
texts,
textsToDelete,
withinArrayOrBlockLocale: localeKey
});
if (push) {
if (!arraysToPush[arrayTableName]) {
arraysToPush[arrayTableName] = [];
}
arraysToPush[arrayTableName] = arraysToPush[arrayTableName].concat(newRows);
} else {
if (!arrays[arrayTableName]) {
arrays[arrayTableName] = [];
}
arrays[arrayTableName] = arrays[arrayTableName].concat(newRows);
}
}
});
}
} else {
let value = data[field.name];
let push = false;
if (typeof value === 'object' && '$push' in value) {
value = Array.isArray(value.$push) ? value.$push : [
value.$push
];
push = true;
}
const newRows = transformArray({
adapter,
arrayTableName,
baseTableName,
blocks,
blocksToDelete,
data: value,
field,
numbers,
numbersToDelete,
parentIsLocalized: parentIsLocalized || field.localized,
path,
relationships,
relationshipsToDelete,
selects,
texts,
textsToDelete,
withinArrayOrBlockLocale
});
if (push) {
if (!arraysToPush[arrayTableName]) {
arraysToPush[arrayTableName] = [];
}
arraysToPush[arrayTableName] = arraysToPush[arrayTableName].concat(newRows);
} else {
if (!arrays[arrayTableName]) {
arrays[arrayTableName] = [];
}
arrays[arrayTableName] = arrays[arrayTableName].concat(newRows);
}
}
return;
}
if (field.type === 'blocks' && !adapter.blocksAsJSON) {
;
(field.blockReferences ?? field.blocks).forEach((block)=>{
const matchedBlock = typeof block === 'string' ? adapter.payload.config.blocks.find((each)=>each.slug === block) : block;
blocksToDelete.add(resolveBlockTableName(matchedBlock, adapter.tableNameMap.get(`${baseTableName}_blocks_${toSnakeCase(matchedBlock.slug)}`)));
});
if (isLocalized) {
if (typeof data[field.name] === 'object' && data[field.name] !== null) {
Object.entries(data[field.name]).forEach(([localeKey, localeData])=>{
if (Array.isArray(localeData)) {
transformBlocks({
adapter,
baseTableName,
blocks,
blocksToDelete,
data: localeData,
field,
locale: localeKey,
numbers,
numbersToDelete,
parentIsLocalized: parentIsLocalized || field.localized,
path,
relationships,
relationshipsToDelete,
selects,
texts,
textsToDelete,
withinArrayOrBlockLocale: localeKey
});
}
});
}
} else if (isArrayOfRows(fieldData)) {
transformBlocks({
adapter,
baseTableName,
blocks,
blocksToDelete,
data: fieldData,
field,
numbers,
numbersToDelete,
parentIsLocalized: parentIsLocalized || field.localized,
path,
relationships,
relationshipsToDelete,
selects,
texts,
textsToDelete,
withinArrayOrBlockLocale
});
}
return;
}
if (field.type === 'group' || field.type === 'tab') {
if (typeof data[field.name] === 'object' && data[field.name] !== null) {
if (isLocalized) {
Object.entries(data[field.name]).forEach(([localeKey, localeData])=>{
// preserve array ID if there is
localeData._uuid = data.id || data._uuid;
traverseFields({
adapter,
arrays,
arraysToPush,
baseTableName,
blocks,
blocksToDelete,
columnPrefix: `${columnName}_`,
data: localeData,
enableAtomicWrites,
existingLocales,
fieldPrefix: `${fieldName}_`,
fields: field.flattenedFields,
forcedLocale: localeKey,
insideArrayOrBlock,
locales,
numbers,
numbersToDelete,
parentIsLocalized: parentIsLocalized || field.localized,
parentTableName,
path: `${path || ''}${field.name}.`,
relationships,
relationshipsToAppend,
relationshipsToDelete,
row,
selects,
texts,
textsToDelete,
withinArrayOrBlockLocale: localeKey
});
});
} else {
// preserve array ID if there is
const groupData = data[field.name];
groupData._uuid = data.id || data._uuid;
traverseFields({
adapter,
arrays,
arraysToPush,
baseTableName,
blocks,
blocksToDelete,
columnPrefix: `${columnName}_`,
data: groupData,
existingLocales,
fieldPrefix: `${fieldName}_`,
fields: field.flattenedFields,
insideArrayOrBlock,
locales,
numbers,
numbersToDelete,
parentIsLocalized: parentIsLocalized || field.localized,
parentTableName,
path: `${path || ''}${field.name}.`,
relationships,
relationshipsToAppend,
relationshipsToDelete,
row,
selects,
texts,
textsToDelete,
withinArrayOrBlockLocale
});
}
}
return;
}
if (field.type === 'relationship' || field.type === 'upload') {
const relationshipPath = `${path || ''}${field.name}`;
// Handle $push operation for relationship fields
if (fieldData && typeof fieldData === 'object' && 'hasMany' in field && field.hasMany && ('$push' in fieldData || field.localized && Object.values(fieldData).some((localeValue)=>localeValue && typeof localeValue === 'object' && '$push' in localeValue))) {
let itemsToAppend;
if (field.localized) {
let hasLocaleOperations = false;
Object.entries(fieldData).forEach(([localeKey, localeValue])=>{
if (localeValue && typeof localeValue === 'object' && '$push' in localeValue) {
hasLocaleOperations = true;
const push = localeValue.$push;
const localeItems = Array.isArray(push) ? push : [
push
];
localeItems.forEach((item)=>{
const relationshipToAppend = {
locale: localeKey,
path: relationshipPath,
value: item
};
// Handle polymorphic relationships
if (Array.isArray(field.relationTo) && item && typeof item === 'object' && 'relationTo' in item) {
relationshipToAppend.relationTo = item.relationTo;
relationshipToAppend.value = item.value;
} else if (typeof field.relationTo === 'string') {
// Simple relationship
relationshipToAppend.relationTo = field.relationTo;
relationshipToAppend.value = item;
}
relationshipsToAppend.push(relationshipToAppend);
});
}
});
if (hasLocaleOperations) {
return;
}
} else {
// Handle non-localized fields: { field: { $push: data } }
itemsToAppend = Array.isArray(fieldData.$push) ? fieldData.$push : [
fieldData.$push
];
itemsToAppend.forEach((item)=>{
const relationshipToAppend = {
locale: isLocalized ? withinArrayOrBlockLocale : undefined,
path: relationshipPath,
value: item
};
// Handle polymorphic relationships
if (Array.isArray(field.relationTo) && item && typeof item === 'object' && 'relationTo' in item && 'value' in item) {
relationshipToAppend.relationTo = item.relationTo;
relationshipToAppend.value = item.value;
} else if (typeof field.relationTo === 'string') {
// Simple relationship
relationshipToAppend.relationTo = field.relationTo;
relationshipToAppend.value = item;
}
relationshipsToAppend.push(relationshipToAppend);
});
}
return;
}
// Handle $remove operation for relationship fields
if (fieldData && typeof fieldData === 'object' && 'hasMany' in field && field.hasMany && ('$remove' in fieldData || field.localized && Object.values(fieldData).some((localeValue)=>localeValue && typeof localeValue === 'object' && '$remove' in localeValue))) {
// Check for new locale-first syntax: { field: { locale: { $remove: data } } }
if (field.localized) {
let hasLocaleOperations = false;
Object.entries(fieldData).forEach(([localeKey, localeValue])=>{
if (localeValue && typeof localeValue === 'object' && '$remove' in localeValue) {
hasLocaleOperations = true;
const remove = localeValue.$remove;
const localeItems = Array.isArray(remove) ? remove : [
remove
];
localeItems.forEach((item)=>{
const relationshipToDelete = {
itemToRemove: item,
locale: localeKey,
path: relationshipPath
};
// Store relationTo for simple relationships
if (typeof field.relationTo === 'string') {
relationshipToDelete.relationTo = field.relationTo;
}
relationshipsToDelete.push(relationshipToDelete);
});
}
});
if (hasLocaleOperations) {
return;
}
} else {
// Handle non-localized fields: { field: { $remove: data } }
const itemsToRemove = Array.isArray(fieldData.$remove) ? fieldData.$remove : [
fieldData.$remove
];
itemsToRemove.forEach((item)=>{
const relationshipToDelete = {
itemToRemove: item,
locale: isLocalized ? withinArrayOrBlockLocale : undefined,
path: relationshipPath
};
// Store relationTo for simple relationships
if (typeof field.relationTo === 'string') {
relationshipToDelete.relationTo = field.relationTo;
}
relationshipsToDelete.push(relationshipToDelete);
});
}
return;
}
if (isLocalized && (Array.isArray(field.relationTo) || 'hasMany' in field && field.hasMany)) {
if (typeof fieldData === 'object') {
Object.entries(fieldData).forEach(([localeKey, localeData])=>{
if (localeData === null) {
relationshipsToDelete.push({
locale: localeKey,
path: relationshipPath
});
return;
}
transformRelationship({
baseRow: {
locale: localeKey,
path: relationshipPath
},
data: localeData,
field,
relationships
});
});
}
return;
} else if (Array.isArray(field.relationTo) || 'hasMany' in field && field.hasMany) {
if (fieldData === null || Array.isArray(fieldData) && fieldData.length === 0) {
relationshipsToDelete.push({
path: relationshipPath
});
return;
}
transformRelationship({
baseRow: {
locale: withinArrayOrBlockLocale,
path: relationshipPath
},
data: fieldData,
field,
relationships
});
return;
} else {
if (!isLocalized && fieldData && typeof fieldData === 'object' && 'id' in fieldData && fieldData?.id) {
fieldData = fieldData.id;
} else if (isLocalized) {
if (typeof fieldData === 'object') {
Object.entries(fieldData).forEach(([localeKey, localeData])=>{
if (typeof localeData === 'object') {
if (localeData && 'id' in localeData && localeData?.id) {
fieldData[localeKey] = localeData.id;
}
} else {
fieldData[localeKey] = localeData;
}
});
}
}
}
}
if (field.type === 'text' && field.hasMany) {
const textPath = `${path || ''}${field.name}`;
if (isLocalized) {
if (typeof fieldData === 'object') {
Object.entries(fieldData).forEach(([localeKey, localeData])=>{
if (Array.isArray(localeData)) {
if (!localeData.length) {
textsToDelete.push({
locale: localeKey,
path: textPath
});
return;
}
transformTexts({
baseRow: {
locale: localeKey,
path: textPath
},
data: localeData,
texts
});
}
});
}
} else if (Array.isArray(fieldData)) {
if (!fieldData.length) {
textsToDelete.push({
locale: withinArrayOrBlockLocale,
path: textPath
});
return;
}
transformTexts({
baseRow: {
locale: withinArrayOrBlockLocale,
path: textPath
},
data: fieldData,
texts
});
}
return;
}
if (field.type === 'number' && field.hasMany) {
const numberPath = `${path || ''}${field.name}`;
if (isLocalized) {
if (typeof fieldData === 'object') {
Object.entries(fieldData).forEach(([localeKey, localeData])=>{
if (Array.isArray(localeData)) {
if (!localeData.length) {
numbersToDelete.push({
locale: localeKey,
path: numberPath
});
return;
}
transformNumbers({
baseRow: {
locale: localeKey,
path: numberPath
},
data: localeData,
numbers
});
}
});
}
} else if (Array.isArray(fieldData)) {
if (!fieldData.length) {
numbersToDelete.push({
locale: withinArrayOrBlockLocale,
path: numberPath
});
return;
}
transformNumbers({
baseRow: {
locale: withinArrayOrBlockLocale,
path: numberPath
},
data: fieldData,
numbers
});
}
return;
}
if (field.type === 'select' && field.hasMany) {
const selectTableName = adapter.tableNameMap.get(`${parentTableName}_${columnName}`);
if (!selects[selectTableName]) {
selects[selectTableName] = [];
}
if (isLocalized) {
if (typeof data[field.name] === 'object' && data[field.name] !== null) {
Object.entries(data[field.name]).forEach(([localeKey, localeData])=>{
if (Array.isArray(localeData)) {
const newRows = transformSelects({
id: insideArrayOrBlock ? data._uuid || data.id : undefined,
data: localeData,
locale: localeKey
});
selects[selectTableName] = selects[selectTableName].concat(newRows);
}
});
}
} else if (Array.isArray(data[field.name])) {
const newRows = transformSelects({
id: insideArrayOrBlock ? data._uuid || data.id : undefined,
data: data[field.name],
locale: withinArrayOrBlockLocale
});
selects[selectTableName] = selects[selectTableName].concat(newRows);
}
return;
}
const valuesToTransform = [];
if (isLocalized) {
if (typeof fieldData === 'object' && fieldData !== null) {
Object.entries(fieldData).forEach(([localeKey, localeData])=>{
if (!locales[localeKey]) {
locales[localeKey] = {};
}
valuesToTransform.push({
localeKey,
ref: locales,
value: localeData
});
});
}
} else {
let ref = row;
if (forcedLocale) {
if (!locales[forcedLocale]) {
locales[forcedLocale] = {};
}
ref = locales[forcedLocale];
}
valuesToTransform.push({
ref,
value: fieldData
});
}
valuesToTransform.forEach(({ localeKey, ref, value })=>{
let formattedValue = value;
if (field.type === 'date') {
if (fieldName === 'updatedAt' && formattedValue === null) {
// If updatedAt is explicitly set to null, skip it entirely - don't add to row
// This prevents the timestamp from being updated for session-only operations
return; // Skip this iteration
} else if (fieldName === 'updatedAt' && typeof formattedValue === 'undefined') {
// If updatedAt is undefined, set it to current time (normal behavior)
formattedValue = new Date().toISOString();
} else {
if (typeof value === 'number' && !Number.isNaN(value)) {
formattedValue = new Date(value).toISOString();
} else if (value instanceof Date) {
formattedValue = value.toISOString();
}
}
}
if (typeof value !== 'undefined') {
if (value && field.type === 'point' && adapter.name !== 'sqlite') {
formattedValue = sql`ST_GeomFromGeoJSON(${JSON.stringify(value)})`;
}
if (field.type === 'text' && value && typeof value !== 'string') {
formattedValue = JSON.stringify(value);
}
if (field.type === 'number' && value && typeof value === 'object' && '$inc' in value && typeof value.$inc === 'number') {
if (!enableAtomicWrites) {
throw new APIError('The passed data must not contain any nested fields for atomic writes');
}
formattedValue = sql.raw(`${columnName} + ${value.$inc}`);
}
}
if (typeof formattedValue !== 'undefined') {
if (localeKey) {
ref[localeKey][fieldName] = formattedValue;
} else {
ref[fieldName] = formattedValue;
}
}
});
});
// Handle dot-notation paths when no fields matched
if (!fieldsMatched) {
Object.keys(data).forEach((key)=>{
if (key.includes('.')) {
// Split on first dot only
const firstDotIndex = key.indexOf('.');
const fieldName = key.substring(0, firstDotIndex);
const remainingPath = key.substring(firstDotIndex + 1);
// Create nested structure for this field
if (!data[fieldName]) {
data[fieldName] = {};
}
const nestedData = data[fieldName];
// Move the value to the nested structure
nestedData[remainingPath] = data[key];
delete data[key];
// Recursively process the newly created nested structure
// The field traversal will naturally handle it if the field exists in the schema
traverseFields({
adapter,
arrays,
arraysToPush,
baseTableName,
blocks,
blocksToDelete,
columnPrefix,
data,
enableAtomicWrites,
existingLocales,
fieldPrefix,
fields,
forcedLocale,
insideArrayOrBlock,
locales,
numbers,
numbersToDelete,
parentIsLocalized,
parentTableName,
path,
relationships,
relationshipsToAppend,
relationshipsToDelete,
row,
selects,
texts,
textsToDelete,
withinArrayOrBlockLocale
});
}
});
}
};
//# sourceMappingURL=traverseFields.js.map