@payloadcms/plugin-import-export 3.84.1 → 3.85.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/dist/export/batchProcessor.d.ts +9 -1
  2. package/dist/export/batchProcessor.d.ts.map +1 -1
  3. package/dist/export/batchProcessor.js +57 -15
  4. package/dist/export/batchProcessor.js.map +1 -1
  5. package/dist/export/createExport.d.ts.map +1 -1
  6. package/dist/export/createExport.js +98 -20
  7. package/dist/export/createExport.js.map +1 -1
  8. package/dist/export/handlePreview.d.ts.map +1 -1
  9. package/dist/export/handlePreview.js +38 -13
  10. package/dist/export/handlePreview.js.map +1 -1
  11. package/dist/exports/types.d.ts +1 -1
  12. package/dist/exports/types.d.ts.map +1 -1
  13. package/dist/exports/types.js.map +1 -1
  14. package/dist/import/batchProcessor.d.ts +14 -2
  15. package/dist/import/batchProcessor.d.ts.map +1 -1
  16. package/dist/import/batchProcessor.js +49 -27
  17. package/dist/import/batchProcessor.js.map +1 -1
  18. package/dist/import/createImport.d.ts +1 -10
  19. package/dist/import/createImport.d.ts.map +1 -1
  20. package/dist/import/createImport.js +33 -52
  21. package/dist/import/createImport.js.map +1 -1
  22. package/dist/import/handlePreview.d.ts.map +1 -1
  23. package/dist/import/handlePreview.js +32 -6
  24. package/dist/import/handlePreview.js.map +1 -1
  25. package/dist/index.d.ts +58 -3
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +18 -1
  28. package/dist/index.js.map +1 -1
  29. package/dist/types.d.ts +218 -39
  30. package/dist/types.d.ts.map +1 -1
  31. package/dist/types.js.map +1 -1
  32. package/dist/utilities/applyFieldHooks.d.ts +23 -0
  33. package/dist/utilities/applyFieldHooks.d.ts.map +1 -0
  34. package/dist/utilities/applyFieldHooks.js +118 -0
  35. package/dist/utilities/applyFieldHooks.js.map +1 -0
  36. package/dist/utilities/applyFieldHooks.spec.js +205 -0
  37. package/dist/utilities/applyFieldHooks.spec.js.map +1 -0
  38. package/dist/utilities/collectDisabledFieldPaths.d.ts.map +1 -1
  39. package/dist/utilities/collectDisabledFieldPaths.js +1 -1
  40. package/dist/utilities/collectDisabledFieldPaths.js.map +1 -1
  41. package/dist/utilities/flattenObject.d.ts +8 -6
  42. package/dist/utilities/flattenObject.d.ts.map +1 -1
  43. package/dist/utilities/flattenObject.js +95 -75
  44. package/dist/utilities/flattenObject.js.map +1 -1
  45. package/dist/utilities/flattenObject.spec.js +158 -0
  46. package/dist/utilities/flattenObject.spec.js.map +1 -0
  47. package/dist/utilities/flattenedFields.d.ts +21 -0
  48. package/dist/utilities/flattenedFields.d.ts.map +1 -0
  49. package/dist/utilities/flattenedFields.js +34 -0
  50. package/dist/utilities/flattenedFields.js.map +1 -0
  51. package/dist/utilities/getExportFieldFunctions.d.ts +5 -5
  52. package/dist/utilities/getExportFieldFunctions.d.ts.map +1 -1
  53. package/dist/utilities/getExportFieldFunctions.js +92 -98
  54. package/dist/utilities/getExportFieldFunctions.js.map +1 -1
  55. package/dist/utilities/getExportFieldFunctions.spec.js +50 -0
  56. package/dist/utilities/getExportFieldFunctions.spec.js.map +1 -0
  57. package/dist/utilities/getImportFieldFunctions.d.ts +5 -5
  58. package/dist/utilities/getImportFieldFunctions.d.ts.map +1 -1
  59. package/dist/utilities/getImportFieldFunctions.js +103 -103
  60. package/dist/utilities/getImportFieldFunctions.js.map +1 -1
  61. package/dist/utilities/getImportFieldFunctions.spec.js +167 -0
  62. package/dist/utilities/getImportFieldFunctions.spec.js.map +1 -0
  63. package/dist/utilities/isPlainObject.d.ts +2 -0
  64. package/dist/utilities/isPlainObject.d.ts.map +1 -0
  65. package/dist/utilities/isPlainObject.js +3 -0
  66. package/dist/utilities/isPlainObject.js.map +1 -0
  67. package/dist/utilities/legacyHookDispatch.spec.js +227 -0
  68. package/dist/utilities/legacyHookDispatch.spec.js.map +1 -0
  69. package/dist/utilities/polymorphicRel.d.ts +14 -0
  70. package/dist/utilities/polymorphicRel.d.ts.map +1 -0
  71. package/dist/utilities/polymorphicRel.js +17 -0
  72. package/dist/utilities/polymorphicRel.js.map +1 -0
  73. package/dist/utilities/processRichTextField.js.map +1 -1
  74. package/dist/utilities/removeDisabledFields.js.map +1 -1
  75. package/dist/utilities/setNestedValue.d.ts.map +1 -1
  76. package/dist/utilities/setNestedValue.js +10 -8
  77. package/dist/utilities/setNestedValue.js.map +1 -1
  78. package/dist/utilities/siblingDoc.spec.js +278 -0
  79. package/dist/utilities/siblingDoc.spec.js.map +1 -0
  80. package/dist/utilities/unflattenObject.d.ts +4 -3
  81. package/dist/utilities/unflattenObject.d.ts.map +1 -1
  82. package/dist/utilities/unflattenObject.js +57 -169
  83. package/dist/utilities/unflattenObject.js.map +1 -1
  84. package/dist/utilities/unflattenObject.spec.js +33 -0
  85. package/dist/utilities/unflattenObject.spec.js.map +1 -1
  86. package/dist/utilities/unflattenPostProcess.d.ts +11 -0
  87. package/dist/utilities/unflattenPostProcess.d.ts.map +1 -0
  88. package/dist/utilities/unflattenPostProcess.js +148 -0
  89. package/dist/utilities/unflattenPostProcess.js.map +1 -0
  90. package/package.json +7 -7
@@ -1,119 +1,139 @@
1
1
  import { fieldToRegex } from './fieldToRegex.js';
2
- export const flattenObject = ({ doc, fields, prefix, toCSVFunctions })=>{
2
+ import { isPlainObject } from './isPlainObject.js';
3
+ import { getPolymorphicRelId, isPolymorphicRelValue } from './polymorphicRel.js';
4
+ export const flattenObject = ({ data, exportFieldHooks, fields, format, path, req })=>{
3
5
  const row = {};
4
- // Helper to get toCSV function by full path or base field name
5
- // This allows functions registered for field names (e.g., 'richText') to work
6
- // even when the field is nested in arrays/blocks (e.g., 'blocks_0_content_richText')
7
- const getToCSVFunction = (fullPath, baseFieldName)=>{
8
- return toCSVFunctions?.[fullPath] ?? toCSVFunctions?.[baseFieldName];
6
+ const invokeHook = (entry, columnName, value, siblingSource)=>{
7
+ if (entry.type === 'beforeExport') {
8
+ return entry.fn({
9
+ columnName,
10
+ data,
11
+ format,
12
+ siblingData: row,
13
+ siblingDoc: siblingSource,
14
+ value
15
+ });
16
+ }
17
+ return entry.fn({
18
+ columnName,
19
+ data: row,
20
+ doc: data,
21
+ row,
22
+ siblingDoc: siblingSource,
23
+ value
24
+ });
9
25
  };
10
- // When fields are selected, build a set of top-level document keys to process.
11
- // This prevents sibling fields with similar prefixes from being included
12
- // (e.g. selecting 'dateWithTimezone' won't pull in 'dateWithTimezone_tz')
13
26
  const selectedTopLevelKeys = Array.isArray(fields) && fields.length > 0 ? new Set(fields.map((f)=>f.split('.')[0])) : undefined;
14
- const flattenWithFilter = (siblingDoc, currentPrefix)=>{
15
- Object.entries(siblingDoc).forEach(([key, value])=>{
16
- // At the document root, skip keys that don't match any selected field
17
- if (!currentPrefix && selectedTopLevelKeys && !selectedTopLevelKeys.has(key)) {
27
+ const flattenWithFilter = (siblingSource, currentPath, currentSchemaPath)=>{
28
+ Object.entries(siblingSource).forEach(([key, value])=>{
29
+ if (!currentPath && selectedTopLevelKeys && !selectedTopLevelKeys.has(key)) {
18
30
  return;
19
31
  }
20
- const newKey = currentPrefix ? `${currentPrefix}_${key}` : key;
21
- const toCSVFn = getToCSVFunction(newKey, key);
32
+ const fieldPath = currentPath ? `${currentPath}_${key}` : key;
33
+ const fieldSchemaPath = currentSchemaPath ? `${currentSchemaPath}_${key}` : key;
34
+ const hookEntry = exportFieldHooks?.[fieldSchemaPath];
35
+ const flattenArray = (items, arrayPath, arraySchemaPath)=>{
36
+ items.forEach((item, index)=>{
37
+ if (!isPlainObject(item)) {
38
+ row[`${arrayPath}_${index}`] = item;
39
+ return;
40
+ }
41
+ const blockType = typeof item.blockType === 'string' ? item.blockType : undefined;
42
+ const itemPath = blockType ? `${arrayPath}_${index}_${blockType}` : `${arrayPath}_${index}`;
43
+ const itemSchemaPath = blockType ? `${arraySchemaPath}_${blockType}` : arraySchemaPath;
44
+ if (isPolymorphicRelValue(item) && isPlainObject(item.value)) {
45
+ const id = getPolymorphicRelId(item);
46
+ if (id !== undefined) {
47
+ row[`${itemPath}_relationTo`] = item.relationTo;
48
+ row[`${itemPath}_id`] = id;
49
+ return;
50
+ }
51
+ }
52
+ flattenWithFilter(item, itemPath, itemSchemaPath);
53
+ });
54
+ };
22
55
  if (Array.isArray(value)) {
23
- if (toCSVFn) {
56
+ if (hookEntry) {
24
57
  try {
25
- const result = toCSVFn({
26
- columnName: newKey,
27
- data: row,
28
- doc,
29
- row,
30
- siblingDoc,
31
- value
32
- });
33
- if (typeof result !== 'undefined') {
34
- row[newKey] = result;
58
+ const result = invokeHook(hookEntry, fieldPath, value, siblingSource);
59
+ if (result === null) {
35
60
  return;
36
61
  }
37
- for(const k in row){
38
- if (k === newKey || k.startsWith(`${newKey}_`)) {
39
- return;
40
- }
62
+ if (Array.isArray(result)) {
63
+ flattenArray(result, fieldPath, fieldSchemaPath);
64
+ return;
41
65
  }
42
- } catch (error) {
43
- throw new Error(`Error in toCSVFunction for array "${newKey}": ${JSON.stringify(value)}\n${error.message}`);
44
- }
45
- }
46
- value.forEach((item, index)=>{
47
- if (typeof item === 'object' && item !== null) {
48
- const blockType = typeof item.blockType === 'string' ? item.blockType : undefined;
49
- const itemPrefix = blockType ? `${newKey}_${index}_${blockType}` : `${newKey}_${index}`;
50
- if ('relationTo' in item && 'value' in item && typeof item.value === 'object' && item.value !== null) {
51
- row[`${itemPrefix}_relationTo`] = item.relationTo;
52
- row[`${itemPrefix}_id`] = item.value.id;
66
+ if (typeof result !== 'undefined') {
67
+ row[fieldPath] = result;
53
68
  return;
54
69
  }
55
- flattenWithFilter(item, itemPrefix);
56
- } else {
57
- row[`${newKey}_${index}`] = item;
70
+ } catch (error) {
71
+ req.payload.logger.error({
72
+ err: error,
73
+ msg: `[plugin-import-export] Field-level beforeExport hook for "${fieldPath}" threw — falling back to default flattening`
74
+ });
58
75
  }
59
- });
76
+ }
77
+ flattenArray(value, fieldPath, fieldSchemaPath);
60
78
  } else if (typeof value === 'object' && value !== null) {
61
- if (!toCSVFn) {
62
- flattenWithFilter(value, newKey);
79
+ if (!hookEntry) {
80
+ flattenWithFilter(value, fieldPath, fieldSchemaPath);
63
81
  } else {
82
+ const keysBeforeHook = new Set(Object.keys(row));
64
83
  try {
65
- const result = toCSVFn({
66
- columnName: newKey,
67
- data: row,
68
- doc,
69
- row,
70
- siblingDoc,
71
- value
72
- });
73
- if (typeof result !== 'undefined') {
74
- row[newKey] = result;
84
+ const result = invokeHook(hookEntry, fieldPath, value, siblingSource);
85
+ if (result === null) {
86
+ return;
87
+ }
88
+ if (typeof result === 'undefined') {
89
+ const hookWroteForField = Object.keys(row).some((k)=>!keysBeforeHook.has(k) && (k === fieldPath || k.startsWith(`${fieldPath}_`)));
90
+ if (hookWroteForField) {
91
+ return;
92
+ }
93
+ flattenWithFilter(value, fieldPath, fieldSchemaPath);
94
+ } else if (typeof result === 'object' && !Array.isArray(result)) {
95
+ flattenWithFilter(result, fieldPath, fieldSchemaPath);
96
+ } else {
97
+ row[fieldPath] = result;
75
98
  }
76
99
  } catch (error) {
77
- throw new Error(`Error in toCSVFunction for nested object "${newKey}": ${JSON.stringify(value)}\n${error.message}`);
100
+ req.payload.logger.error({
101
+ err: error,
102
+ msg: `[plugin-import-export] Field-level beforeExport hook for "${fieldPath}" threw — falling back to default flattening`
103
+ });
104
+ flattenWithFilter(value, fieldPath, fieldSchemaPath);
78
105
  }
79
106
  }
80
107
  } else {
81
- if (toCSVFn) {
108
+ if (hookEntry) {
82
109
  try {
83
- const result = toCSVFn({
84
- columnName: newKey,
85
- data: row,
86
- doc,
87
- row,
88
- siblingDoc,
89
- value
90
- });
110
+ const result = invokeHook(hookEntry, fieldPath, value, siblingSource);
91
111
  if (typeof result !== 'undefined') {
92
- row[newKey] = result;
112
+ row[fieldPath] = result;
93
113
  }
94
114
  } catch (error) {
95
- throw new Error(`Error in toCSVFunction for field "${newKey}": ${JSON.stringify(value)}\n${error.message}`);
115
+ req.payload.logger.error({
116
+ err: error,
117
+ msg: `[plugin-import-export] Field-level beforeExport hook for "${fieldPath}" threw — falling back to original value`
118
+ });
119
+ row[fieldPath] = value;
96
120
  }
97
121
  } else {
98
- row[newKey] = value;
122
+ row[fieldPath] = value;
99
123
  }
100
124
  }
101
125
  });
102
126
  };
103
- flattenWithFilter(doc, prefix);
127
+ flattenWithFilter(data, path, path);
104
128
  if (Array.isArray(fields) && fields.length > 0) {
105
129
  const orderedResult = {};
106
- // Build all field regexes once
107
130
  const fieldPatterns = fields.map((field)=>({
108
131
  field,
109
132
  regex: fieldToRegex(field)
110
133
  }));
111
- // Single pass through row keys - O(keys * fields) regex tests but only one iteration
112
134
  const rowKeys = Object.keys(row);
113
- // Process in field order to maintain user's specified ordering
114
135
  for (const { regex } of fieldPatterns){
115
136
  for (const key of rowKeys){
116
- // Skip if already added (a key might match multiple field patterns)
117
137
  if (key in orderedResult) {
118
138
  continue;
119
139
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utilities/flattenObject.ts"],"sourcesContent":["import type { Document } from 'payload'\n\nimport type { ToCSVFunction } from '../types.js'\n\nimport { fieldToRegex } from './fieldToRegex.js'\n\ntype Args = {\n doc: Document\n fields?: string[]\n prefix?: string\n toCSVFunctions: Record<string, ToCSVFunction>\n}\n\nexport const flattenObject = ({\n doc,\n fields,\n prefix,\n toCSVFunctions,\n}: Args): Record<string, unknown> => {\n const row: Record<string, unknown> = {}\n\n // Helper to get toCSV function by full path or base field name\n // This allows functions registered for field names (e.g., 'richText') to work\n // even when the field is nested in arrays/blocks (e.g., 'blocks_0_content_richText')\n const getToCSVFunction = (fullPath: string, baseFieldName: string): ToCSVFunction | undefined => {\n return toCSVFunctions?.[fullPath] ?? toCSVFunctions?.[baseFieldName]\n }\n\n // When fields are selected, build a set of top-level document keys to process.\n // This prevents sibling fields with similar prefixes from being included\n // (e.g. selecting 'dateWithTimezone' won't pull in 'dateWithTimezone_tz')\n const selectedTopLevelKeys =\n Array.isArray(fields) && fields.length > 0\n ? new Set(fields.map((f) => f.split('.')[0]))\n : undefined\n\n const flattenWithFilter = (siblingDoc: Document, currentPrefix?: string) => {\n Object.entries(siblingDoc).forEach(([key, value]) => {\n // At the document root, skip keys that don't match any selected field\n if (!currentPrefix && selectedTopLevelKeys && !selectedTopLevelKeys.has(key)) {\n return\n }\n\n const newKey = currentPrefix ? `${currentPrefix}_${key}` : key\n const toCSVFn = getToCSVFunction(newKey, key)\n\n if (Array.isArray(value)) {\n if (toCSVFn) {\n try {\n const result = toCSVFn({\n columnName: newKey,\n data: row,\n doc,\n row,\n siblingDoc,\n value,\n })\n\n if (typeof result !== 'undefined') {\n row[newKey] = result\n return\n }\n\n for (const k in row) {\n if (k === newKey || k.startsWith(`${newKey}_`)) {\n return\n }\n }\n } catch (error) {\n throw new Error(\n `Error in toCSVFunction for array \"${newKey}\": ${JSON.stringify(value)}\\n${\n (error as Error).message\n }`,\n )\n }\n }\n\n value.forEach((item, index) => {\n if (typeof item === 'object' && item !== null) {\n const blockType = typeof item.blockType === 'string' ? item.blockType : undefined\n const itemPrefix = blockType ? `${newKey}_${index}_${blockType}` : `${newKey}_${index}`\n\n if (\n 'relationTo' in item &&\n 'value' in item &&\n typeof item.value === 'object' &&\n item.value !== null\n ) {\n row[`${itemPrefix}_relationTo`] = item.relationTo\n row[`${itemPrefix}_id`] = item.value.id\n return\n }\n\n flattenWithFilter(item, itemPrefix)\n } else {\n row[`${newKey}_${index}`] = item\n }\n })\n } else if (typeof value === 'object' && value !== null) {\n if (!toCSVFn) {\n flattenWithFilter(value, newKey)\n } else {\n try {\n const result = toCSVFn({\n columnName: newKey,\n data: row,\n doc,\n row,\n siblingDoc,\n value,\n })\n if (typeof result !== 'undefined') {\n row[newKey] = result\n }\n } catch (error) {\n throw new Error(\n `Error in toCSVFunction for nested object \"${newKey}\": ${JSON.stringify(value)}\\n${\n (error as Error).message\n }`,\n )\n }\n }\n } else {\n if (toCSVFn) {\n try {\n const result = toCSVFn({\n columnName: newKey,\n data: row,\n doc,\n row,\n siblingDoc,\n value,\n })\n if (typeof result !== 'undefined') {\n row[newKey] = result\n }\n } catch (error) {\n throw new Error(\n `Error in toCSVFunction for field \"${newKey}\": ${JSON.stringify(value)}\\n${\n (error as Error).message\n }`,\n )\n }\n } else {\n row[newKey] = value\n }\n }\n })\n }\n\n flattenWithFilter(doc, prefix)\n\n if (Array.isArray(fields) && fields.length > 0) {\n const orderedResult: Record<string, unknown> = {}\n\n // Build all field regexes once\n const fieldPatterns = fields.map((field) => ({\n field,\n regex: fieldToRegex(field),\n }))\n\n // Single pass through row keys - O(keys * fields) regex tests but only one iteration\n const rowKeys = Object.keys(row)\n\n // Process in field order to maintain user's specified ordering\n for (const { regex } of fieldPatterns) {\n for (const key of rowKeys) {\n // Skip if already added (a key might match multiple field patterns)\n if (key in orderedResult) {\n continue\n }\n\n if (regex.test(key)) {\n orderedResult[key] = row[key]\n }\n }\n }\n\n return orderedResult\n }\n\n return row\n}\n"],"names":["fieldToRegex","flattenObject","doc","fields","prefix","toCSVFunctions","row","getToCSVFunction","fullPath","baseFieldName","selectedTopLevelKeys","Array","isArray","length","Set","map","f","split","undefined","flattenWithFilter","siblingDoc","currentPrefix","Object","entries","forEach","key","value","has","newKey","toCSVFn","result","columnName","data","k","startsWith","error","Error","JSON","stringify","message","item","index","blockType","itemPrefix","relationTo","id","orderedResult","fieldPatterns","field","regex","rowKeys","keys","test"],"mappings":"AAIA,SAASA,YAAY,QAAQ,oBAAmB;AAShD,OAAO,MAAMC,gBAAgB,CAAC,EAC5BC,GAAG,EACHC,MAAM,EACNC,MAAM,EACNC,cAAc,EACT;IACL,MAAMC,MAA+B,CAAC;IAEtC,+DAA+D;IAC/D,8EAA8E;IAC9E,qFAAqF;IACrF,MAAMC,mBAAmB,CAACC,UAAkBC;QAC1C,OAAOJ,gBAAgB,CAACG,SAAS,IAAIH,gBAAgB,CAACI,cAAc;IACtE;IAEA,+EAA+E;IAC/E,yEAAyE;IACzE,0EAA0E;IAC1E,MAAMC,uBACJC,MAAMC,OAAO,CAACT,WAAWA,OAAOU,MAAM,GAAG,IACrC,IAAIC,IAAIX,OAAOY,GAAG,CAAC,CAACC,IAAMA,EAAEC,KAAK,CAAC,IAAI,CAAC,EAAE,KACzCC;IAEN,MAAMC,oBAAoB,CAACC,YAAsBC;QAC/CC,OAAOC,OAAO,CAACH,YAAYI,OAAO,CAAC,CAAC,CAACC,KAAKC,MAAM;YAC9C,sEAAsE;YACtE,IAAI,CAACL,iBAAiBX,wBAAwB,CAACA,qBAAqBiB,GAAG,CAACF,MAAM;gBAC5E;YACF;YAEA,MAAMG,SAASP,gBAAgB,GAAGA,cAAc,CAAC,EAAEI,KAAK,GAAGA;YAC3D,MAAMI,UAAUtB,iBAAiBqB,QAAQH;YAEzC,IAAId,MAAMC,OAAO,CAACc,QAAQ;gBACxB,IAAIG,SAAS;oBACX,IAAI;wBACF,MAAMC,SAASD,QAAQ;4BACrBE,YAAYH;4BACZI,MAAM1B;4BACNJ;4BACAI;4BACAc;4BACAM;wBACF;wBAEA,IAAI,OAAOI,WAAW,aAAa;4BACjCxB,GAAG,CAACsB,OAAO,GAAGE;4BACd;wBACF;wBAEA,IAAK,MAAMG,KAAK3B,IAAK;4BACnB,IAAI2B,MAAML,UAAUK,EAAEC,UAAU,CAAC,GAAGN,OAAO,CAAC,CAAC,GAAG;gCAC9C;4BACF;wBACF;oBACF,EAAE,OAAOO,OAAO;wBACd,MAAM,IAAIC,MACR,CAAC,kCAAkC,EAAER,OAAO,GAAG,EAAES,KAAKC,SAAS,CAACZ,OAAO,EAAE,EACvE,AAACS,MAAgBI,OAAO,EACxB;oBAEN;gBACF;gBAEAb,MAAMF,OAAO,CAAC,CAACgB,MAAMC;oBACnB,IAAI,OAAOD,SAAS,YAAYA,SAAS,MAAM;wBAC7C,MAAME,YAAY,OAAOF,KAAKE,SAAS,KAAK,WAAWF,KAAKE,SAAS,GAAGxB;wBACxE,MAAMyB,aAAaD,YAAY,GAAGd,OAAO,CAAC,EAAEa,MAAM,CAAC,EAAEC,WAAW,GAAG,GAAGd,OAAO,CAAC,EAAEa,OAAO;wBAEvF,IACE,gBAAgBD,QAChB,WAAWA,QACX,OAAOA,KAAKd,KAAK,KAAK,YACtBc,KAAKd,KAAK,KAAK,MACf;4BACApB,GAAG,CAAC,GAAGqC,WAAW,WAAW,CAAC,CAAC,GAAGH,KAAKI,UAAU;4BACjDtC,GAAG,CAAC,GAAGqC,WAAW,GAAG,CAAC,CAAC,GAAGH,KAAKd,KAAK,CAACmB,EAAE;4BACvC;wBACF;wBAEA1B,kBAAkBqB,MAAMG;oBAC1B,OAAO;wBACLrC,GAAG,CAAC,GAAGsB,OAAO,CAAC,EAAEa,OAAO,CAAC,GAAGD;oBAC9B;gBACF;YACF,OAAO,IAAI,OAAOd,UAAU,YAAYA,UAAU,MAAM;gBACtD,IAAI,CAACG,SAAS;oBACZV,kBAAkBO,OAAOE;gBAC3B,OAAO;oBACL,IAAI;wBACF,MAAME,SAASD,QAAQ;4BACrBE,YAAYH;4BACZI,MAAM1B;4BACNJ;4BACAI;4BACAc;4BACAM;wBACF;wBACA,IAAI,OAAOI,WAAW,aAAa;4BACjCxB,GAAG,CAACsB,OAAO,GAAGE;wBAChB;oBACF,EAAE,OAAOK,OAAO;wBACd,MAAM,IAAIC,MACR,CAAC,0CAA0C,EAAER,OAAO,GAAG,EAAES,KAAKC,SAAS,CAACZ,OAAO,EAAE,EAC/E,AAACS,MAAgBI,OAAO,EACxB;oBAEN;gBACF;YACF,OAAO;gBACL,IAAIV,SAAS;oBACX,IAAI;wBACF,MAAMC,SAASD,QAAQ;4BACrBE,YAAYH;4BACZI,MAAM1B;4BACNJ;4BACAI;4BACAc;4BACAM;wBACF;wBACA,IAAI,OAAOI,WAAW,aAAa;4BACjCxB,GAAG,CAACsB,OAAO,GAAGE;wBAChB;oBACF,EAAE,OAAOK,OAAO;wBACd,MAAM,IAAIC,MACR,CAAC,kCAAkC,EAAER,OAAO,GAAG,EAAES,KAAKC,SAAS,CAACZ,OAAO,EAAE,EACvE,AAACS,MAAgBI,OAAO,EACxB;oBAEN;gBACF,OAAO;oBACLjC,GAAG,CAACsB,OAAO,GAAGF;gBAChB;YACF;QACF;IACF;IAEAP,kBAAkBjB,KAAKE;IAEvB,IAAIO,MAAMC,OAAO,CAACT,WAAWA,OAAOU,MAAM,GAAG,GAAG;QAC9C,MAAMiC,gBAAyC,CAAC;QAEhD,+BAA+B;QAC/B,MAAMC,gBAAgB5C,OAAOY,GAAG,CAAC,CAACiC,QAAW,CAAA;gBAC3CA;gBACAC,OAAOjD,aAAagD;YACtB,CAAA;QAEA,qFAAqF;QACrF,MAAME,UAAU5B,OAAO6B,IAAI,CAAC7C;QAE5B,+DAA+D;QAC/D,KAAK,MAAM,EAAE2C,KAAK,EAAE,IAAIF,cAAe;YACrC,KAAK,MAAMtB,OAAOyB,QAAS;gBACzB,oEAAoE;gBACpE,IAAIzB,OAAOqB,eAAe;oBACxB;gBACF;gBAEA,IAAIG,MAAMG,IAAI,CAAC3B,MAAM;oBACnBqB,aAAa,CAACrB,IAAI,GAAGnB,GAAG,CAACmB,IAAI;gBAC/B;YACF;QACF;QAEA,OAAOqB;IACT;IAEA,OAAOxC;AACT,EAAC"}
1
+ {"version":3,"sources":["../../src/utilities/flattenObject.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\nimport type { ExportFieldHookEntry } from '../types.js'\n\nimport { fieldToRegex } from './fieldToRegex.js'\nimport { isPlainObject } from './isPlainObject.js'\nimport { getPolymorphicRelId, isPolymorphicRelValue } from './polymorphicRel.js'\n\ntype Args = {\n data: Record<string, unknown>\n exportFieldHooks: Record<string, ExportFieldHookEntry>\n fields?: string[]\n format: 'csv' | 'json' | ({} & string)\n path?: string\n req: PayloadRequest\n}\n\nexport const flattenObject = ({\n data,\n exportFieldHooks,\n fields,\n format,\n path,\n req,\n}: Args): Record<string, unknown> => {\n const row: Record<string, unknown> = {}\n\n const invokeHook = (\n entry: ExportFieldHookEntry,\n columnName: string,\n value: unknown,\n siblingSource: Record<string, unknown>,\n ): unknown => {\n if (entry.type === 'beforeExport') {\n return entry.fn({\n columnName,\n data,\n format,\n siblingData: row,\n siblingDoc: siblingSource,\n value,\n })\n }\n return entry.fn({\n columnName,\n data: row,\n doc: data,\n row,\n siblingDoc: siblingSource,\n value,\n })\n }\n\n const selectedTopLevelKeys =\n Array.isArray(fields) && fields.length > 0\n ? new Set(fields.map((f) => f.split('.')[0]))\n : undefined\n\n const flattenWithFilter = (\n siblingSource: Record<string, unknown>,\n currentPath: string | undefined,\n currentSchemaPath: string | undefined,\n ) => {\n Object.entries(siblingSource).forEach(([key, value]) => {\n if (!currentPath && selectedTopLevelKeys && !selectedTopLevelKeys.has(key)) {\n return\n }\n\n const fieldPath = currentPath ? `${currentPath}_${key}` : key\n const fieldSchemaPath = currentSchemaPath ? `${currentSchemaPath}_${key}` : key\n const hookEntry = exportFieldHooks?.[fieldSchemaPath]\n\n const flattenArray = (items: unknown[], arrayPath: string, arraySchemaPath: string): void => {\n items.forEach((item, index) => {\n if (!isPlainObject(item)) {\n row[`${arrayPath}_${index}`] = item\n return\n }\n\n const blockType = typeof item.blockType === 'string' ? item.blockType : undefined\n const itemPath = blockType\n ? `${arrayPath}_${index}_${blockType}`\n : `${arrayPath}_${index}`\n const itemSchemaPath = blockType ? `${arraySchemaPath}_${blockType}` : arraySchemaPath\n\n if (isPolymorphicRelValue(item) && isPlainObject(item.value)) {\n const id = getPolymorphicRelId(item)\n if (id !== undefined) {\n row[`${itemPath}_relationTo`] = item.relationTo\n row[`${itemPath}_id`] = id\n return\n }\n }\n\n flattenWithFilter(item, itemPath, itemSchemaPath)\n })\n }\n\n if (Array.isArray(value)) {\n if (hookEntry) {\n try {\n const result = invokeHook(hookEntry, fieldPath, value, siblingSource)\n\n if (result === null) {\n return\n }\n\n if (Array.isArray(result)) {\n flattenArray(result, fieldPath, fieldSchemaPath)\n return\n }\n\n if (typeof result !== 'undefined') {\n row[fieldPath] = result\n return\n }\n } catch (error) {\n req.payload.logger.error({\n err: error,\n msg: `[plugin-import-export] Field-level beforeExport hook for \"${fieldPath}\" threw — falling back to default flattening`,\n })\n }\n }\n\n flattenArray(value, fieldPath, fieldSchemaPath)\n } else if (typeof value === 'object' && value !== null) {\n if (!hookEntry) {\n flattenWithFilter(value as Record<string, unknown>, fieldPath, fieldSchemaPath)\n } else {\n const keysBeforeHook = new Set(Object.keys(row))\n try {\n const result = invokeHook(hookEntry, fieldPath, value, siblingSource)\n if (result === null) {\n return\n }\n if (typeof result === 'undefined') {\n const hookWroteForField = Object.keys(row).some(\n (k) => !keysBeforeHook.has(k) && (k === fieldPath || k.startsWith(`${fieldPath}_`)),\n )\n if (hookWroteForField) {\n return\n }\n flattenWithFilter(value as Record<string, unknown>, fieldPath, fieldSchemaPath)\n } else if (typeof result === 'object' && !Array.isArray(result)) {\n flattenWithFilter(result as Record<string, unknown>, fieldPath, fieldSchemaPath)\n } else {\n row[fieldPath] = result\n }\n } catch (error) {\n req.payload.logger.error({\n err: error,\n msg: `[plugin-import-export] Field-level beforeExport hook for \"${fieldPath}\" threw — falling back to default flattening`,\n })\n flattenWithFilter(value as Record<string, unknown>, fieldPath, fieldSchemaPath)\n }\n }\n } else {\n if (hookEntry) {\n try {\n const result = invokeHook(hookEntry, fieldPath, value, siblingSource)\n if (typeof result !== 'undefined') {\n row[fieldPath] = result\n }\n } catch (error) {\n req.payload.logger.error({\n err: error,\n msg: `[plugin-import-export] Field-level beforeExport hook for \"${fieldPath}\" threw — falling back to original value`,\n })\n row[fieldPath] = value\n }\n } else {\n row[fieldPath] = value\n }\n }\n })\n }\n\n flattenWithFilter(data, path, path)\n\n if (Array.isArray(fields) && fields.length > 0) {\n const orderedResult: Record<string, unknown> = {}\n\n const fieldPatterns = fields.map((field) => ({\n field,\n regex: fieldToRegex(field),\n }))\n\n const rowKeys = Object.keys(row)\n\n for (const { regex } of fieldPatterns) {\n for (const key of rowKeys) {\n if (key in orderedResult) {\n continue\n }\n\n if (regex.test(key)) {\n orderedResult[key] = row[key]\n }\n }\n }\n\n return orderedResult\n }\n\n return row\n}\n"],"names":["fieldToRegex","isPlainObject","getPolymorphicRelId","isPolymorphicRelValue","flattenObject","data","exportFieldHooks","fields","format","path","req","row","invokeHook","entry","columnName","value","siblingSource","type","fn","siblingData","siblingDoc","doc","selectedTopLevelKeys","Array","isArray","length","Set","map","f","split","undefined","flattenWithFilter","currentPath","currentSchemaPath","Object","entries","forEach","key","has","fieldPath","fieldSchemaPath","hookEntry","flattenArray","items","arrayPath","arraySchemaPath","item","index","blockType","itemPath","itemSchemaPath","id","relationTo","result","error","payload","logger","err","msg","keysBeforeHook","keys","hookWroteForField","some","k","startsWith","orderedResult","fieldPatterns","field","regex","rowKeys","test"],"mappings":"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"}
@@ -0,0 +1,158 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { flattenObject } from './flattenObject.js';
3
+ const mockReq = {
4
+ payload: {
5
+ logger: {
6
+ error: vi.fn()
7
+ }
8
+ }
9
+ };
10
+ describe('flattenObject parent + child traversal', ()=>{
11
+ it('should run a parent group hook and still apply child field hooks against the transformed value', ()=>{
12
+ const exportFieldHooks = {
13
+ meta: {
14
+ type: 'beforeExport',
15
+ fn: ({ value })=>{
16
+ const obj = value ?? {};
17
+ return {
18
+ ...obj,
19
+ slug: `${obj.slug ?? ''}-from-parent`
20
+ };
21
+ }
22
+ },
23
+ meta_slug: {
24
+ type: 'beforeExport',
25
+ fn: ({ value })=>`${value}-from-child`
26
+ }
27
+ };
28
+ const row = flattenObject({
29
+ data: {
30
+ meta: {
31
+ slug: 'raw'
32
+ }
33
+ },
34
+ exportFieldHooks,
35
+ format: 'csv',
36
+ req: mockReq
37
+ });
38
+ expect(row.meta_slug).toBe('raw-from-parent-from-child');
39
+ });
40
+ it('should still recurse into children when the parent group hook returns undefined', ()=>{
41
+ const childCalls = [];
42
+ const exportFieldHooks = {
43
+ meta: {
44
+ type: 'beforeExport',
45
+ fn: ()=>undefined
46
+ },
47
+ meta_slug: {
48
+ type: 'beforeExport',
49
+ fn: ({ value })=>{
50
+ childCalls.push(value);
51
+ return `${value}-from-child`;
52
+ }
53
+ }
54
+ };
55
+ const row = flattenObject({
56
+ data: {
57
+ meta: {
58
+ slug: 'raw'
59
+ }
60
+ },
61
+ exportFieldHooks,
62
+ format: 'csv',
63
+ req: mockReq
64
+ });
65
+ expect(childCalls).toEqual([
66
+ 'raw'
67
+ ]);
68
+ expect(row.meta_slug).toBe('raw-from-child');
69
+ });
70
+ it('should not recurse into children when the parent hook returns a primitive', ()=>{
71
+ const childFn = vi.fn(({ value })=>`${value}-from-child`);
72
+ const exportFieldHooks = {
73
+ meta: {
74
+ type: 'beforeExport',
75
+ fn: ()=>'serialized'
76
+ },
77
+ meta_slug: {
78
+ type: 'beforeExport',
79
+ fn: childFn
80
+ }
81
+ };
82
+ const row = flattenObject({
83
+ data: {
84
+ meta: {
85
+ slug: 'raw'
86
+ }
87
+ },
88
+ exportFieldHooks,
89
+ format: 'csv',
90
+ req: mockReq
91
+ });
92
+ expect(row.meta).toBe('serialized');
93
+ expect(row.meta_slug).toBeUndefined();
94
+ expect(childFn).not.toHaveBeenCalled();
95
+ });
96
+ it('should fall back to default array flattening when an undefined-returning hook did not write any keys, even if a sibling key with a matching prefix exists', ()=>{
97
+ const exportFieldHooks = {
98
+ tag: {
99
+ type: 'beforeExport',
100
+ fn: ()=>undefined
101
+ }
102
+ };
103
+ const row = flattenObject({
104
+ data: {
105
+ tag_meta: 'sibling-set-first',
106
+ tag: [
107
+ 'a',
108
+ 'b'
109
+ ]
110
+ },
111
+ exportFieldHooks,
112
+ format: 'csv',
113
+ req: mockReq
114
+ });
115
+ expect(row.tag_meta).toBe('sibling-set-first');
116
+ expect(row.tag_0).toBe('a');
117
+ expect(row.tag_1).toBe('b');
118
+ });
119
+ it('should run a parent array hook then child hooks for each item', ()=>{
120
+ const exportFieldHooks = {
121
+ items: {
122
+ type: 'beforeExport',
123
+ fn: ({ value })=>{
124
+ if (!Array.isArray(value)) {
125
+ return value;
126
+ }
127
+ return value.map((item)=>({
128
+ ...item,
129
+ note: `${item.note ?? ''}-parent`
130
+ }));
131
+ }
132
+ },
133
+ items_note: {
134
+ type: 'beforeExport',
135
+ fn: ({ value })=>`${value}-child`
136
+ }
137
+ };
138
+ const row = flattenObject({
139
+ data: {
140
+ items: [
141
+ {
142
+ note: 'a'
143
+ },
144
+ {
145
+ note: 'b'
146
+ }
147
+ ]
148
+ },
149
+ exportFieldHooks,
150
+ format: 'csv',
151
+ req: mockReq
152
+ });
153
+ expect(row.items_0_note).toBe('a-parent-child');
154
+ expect(row.items_1_note).toBe('b-parent-child');
155
+ });
156
+ });
157
+
158
+ //# sourceMappingURL=flattenObject.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/flattenObject.spec.ts"],"sourcesContent":["import { PayloadRequest } from 'payload'\n\nimport type { ExportFieldHookEntry } from '../types.js'\n\nimport { describe, expect, it, vi } from 'vitest'\n\nimport { flattenObject } from './flattenObject.js'\n\nconst mockReq = {\n payload: {\n logger: {\n error: vi.fn(),\n },\n },\n} as unknown as PayloadRequest\n\ndescribe('flattenObject parent + child traversal', () => {\n it('should run a parent group hook and still apply child field hooks against the transformed value', () => {\n const exportFieldHooks: Record<string, ExportFieldHookEntry> = {\n meta: {\n type: 'beforeExport',\n fn: ({ value }) => {\n const obj = (value ?? {}) as Record<string, unknown>\n return { ...obj, slug: `${obj.slug ?? ''}-from-parent` }\n },\n },\n meta_slug: {\n type: 'beforeExport',\n fn: ({ value }) => `${value}-from-child`,\n },\n }\n\n const row = flattenObject({\n data: { meta: { slug: 'raw' } },\n exportFieldHooks,\n format: 'csv',\n req: mockReq,\n })\n\n expect(row.meta_slug).toBe('raw-from-parent-from-child')\n })\n\n it('should still recurse into children when the parent group hook returns undefined', () => {\n const childCalls: unknown[] = []\n const exportFieldHooks: Record<string, ExportFieldHookEntry> = {\n meta: {\n type: 'beforeExport',\n fn: () => undefined,\n },\n meta_slug: {\n type: 'beforeExport',\n fn: ({ value }) => {\n childCalls.push(value)\n return `${value}-from-child`\n },\n },\n }\n\n const row = flattenObject({\n data: { meta: { slug: 'raw' } },\n exportFieldHooks,\n format: 'csv',\n req: mockReq,\n })\n\n expect(childCalls).toEqual(['raw'])\n expect(row.meta_slug).toBe('raw-from-child')\n })\n\n it('should not recurse into children when the parent hook returns a primitive', () => {\n const childFn = vi.fn(({ value }: { value: unknown }) => `${value}-from-child`)\n const exportFieldHooks: Record<string, ExportFieldHookEntry> = {\n meta: {\n type: 'beforeExport',\n fn: () => 'serialized',\n },\n meta_slug: {\n type: 'beforeExport',\n fn: childFn,\n },\n }\n\n const row = flattenObject({\n data: { meta: { slug: 'raw' } },\n exportFieldHooks,\n format: 'csv',\n req: mockReq,\n })\n\n expect(row.meta).toBe('serialized')\n expect(row.meta_slug).toBeUndefined()\n expect(childFn).not.toHaveBeenCalled()\n })\n\n it('should fall back to default array flattening when an undefined-returning hook did not write any keys, even if a sibling key with a matching prefix exists', () => {\n const exportFieldHooks: Record<string, ExportFieldHookEntry> = {\n tag: {\n type: 'beforeExport',\n fn: () => undefined,\n },\n }\n\n const row = flattenObject({\n data: { tag_meta: 'sibling-set-first', tag: ['a', 'b'] },\n exportFieldHooks,\n format: 'csv',\n req: mockReq,\n })\n\n expect(row.tag_meta).toBe('sibling-set-first')\n expect(row.tag_0).toBe('a')\n expect(row.tag_1).toBe('b')\n })\n\n it('should run a parent array hook then child hooks for each item', () => {\n const exportFieldHooks: Record<string, ExportFieldHookEntry> = {\n items: {\n type: 'beforeExport',\n fn: ({ value }) => {\n if (!Array.isArray(value)) {\n return value\n }\n return value.map((item) => ({\n ...(item as Record<string, unknown>),\n note: `${(item as Record<string, unknown>).note ?? ''}-parent`,\n }))\n },\n },\n items_note: {\n type: 'beforeExport',\n fn: ({ value }) => `${value}-child`,\n },\n }\n\n const row = flattenObject({\n data: { items: [{ note: 'a' }, { note: 'b' }] },\n exportFieldHooks,\n format: 'csv',\n req: mockReq,\n })\n\n expect(row.items_0_note).toBe('a-parent-child')\n expect(row.items_1_note).toBe('b-parent-child')\n })\n})\n"],"names":["describe","expect","it","vi","flattenObject","mockReq","payload","logger","error","fn","exportFieldHooks","meta","type","value","obj","slug","meta_slug","row","data","format","req","toBe","childCalls","undefined","push","toEqual","childFn","toBeUndefined","not","toHaveBeenCalled","tag","tag_meta","tag_0","tag_1","items","Array","isArray","map","item","note","items_note","items_0_note","items_1_note"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { FlattenedField } from 'payload';
2
+ /**
3
+ * Returns the `flattenedFields` of a group/tab/array field, or undefined for
4
+ * field types that don't carry nested fields. Concentrates the cast for the
5
+ * Payload core typing oversight (`FlattenedField` doesn't expose
6
+ * `flattenedFields` on every variant).
7
+ */
8
+ export declare const getNestedFlattenedFields: (field: FlattenedField) => FlattenedField[] | undefined;
9
+ /**
10
+ * Returns the `flattenedFields` of a `BlocksField` block (always an array).
11
+ */
12
+ export declare const getBlockFlattenedFields: (block: {
13
+ flattenedFields?: FlattenedField[];
14
+ }) => FlattenedField[];
15
+ /**
16
+ * Traverses a flattened field schema and calls `registerHandler` for each
17
+ * named field. Handles blocks (keyed by block slug), groups, tabs, and arrays.
18
+ * Shared by `getExportFieldFunctions` and `getImportFieldFunctions`.
19
+ */
20
+ export declare const registerFieldHooks: <TEntry>(fields: FlattenedField[], parentPath: string, result: Record<string, TEntry>, registerHandler: (field: FlattenedField, fullKey: string, result: Record<string, TEntry>) => void) => void;
21
+ //# sourceMappingURL=flattenedFields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flattenedFields.d.ts","sourceRoot":"","sources":["../../src/utilities/flattenedFields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7C;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,UAAW,cAAc,KAAG,cAAc,EAAE,GAAG,SACjB,CAAA;AAEnE;;GAEG;AACH,eAAO,MAAM,uBAAuB,UAAW;IAC7C,eAAe,CAAC,EAAE,cAAc,EAAE,CAAA;CACnC,KAAG,cAAc,EAAiC,CAAA;AAEnD;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,UAC/B,cAAc,EAAE,cACZ,MAAM,UACV,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,mBACb,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,KAChG,IA0BF,CAAA"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Returns the `flattenedFields` of a group/tab/array field, or undefined for
3
+ * field types that don't carry nested fields. Concentrates the cast for the
4
+ * Payload core typing oversight (`FlattenedField` doesn't expose
5
+ * `flattenedFields` on every variant).
6
+ */ export const getNestedFlattenedFields = (field)=>field.flattenedFields;
7
+ /**
8
+ * Returns the `flattenedFields` of a `BlocksField` block (always an array).
9
+ */ export const getBlockFlattenedFields = (block)=>block.flattenedFields ?? [];
10
+ /**
11
+ * Traverses a flattened field schema and calls `registerHandler` for each
12
+ * named field. Handles blocks (keyed by block slug), groups, tabs, and arrays.
13
+ * Shared by `getExportFieldFunctions` and `getImportFieldFunctions`.
14
+ */ export const registerFieldHooks = (fields, parentPath, result, registerHandler)=>{
15
+ for (const field of fields){
16
+ if (!('name' in field) || !field.name) {
17
+ continue;
18
+ }
19
+ if (field.type === 'blocks') {
20
+ const base = parentPath ? `${parentPath}_${field.name}` : field.name;
21
+ for (const block of field.blocks ?? []){
22
+ registerFieldHooks(getBlockFlattenedFields(block), `${base}_${block.slug}`, result, registerHandler);
23
+ }
24
+ continue;
25
+ }
26
+ const fullKey = parentPath ? `${parentPath}_${field.name}` : field.name;
27
+ registerHandler(field, fullKey, result);
28
+ if (field.type === 'group' || field.type === 'tab' || field.type === 'array') {
29
+ registerFieldHooks(getNestedFlattenedFields(field) ?? [], fullKey, result, registerHandler);
30
+ }
31
+ }
32
+ };
33
+
34
+ //# sourceMappingURL=flattenedFields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/flattenedFields.ts"],"sourcesContent":["import type { FlattenedField } from 'payload'\n\n/**\n * Returns the `flattenedFields` of a group/tab/array field, or undefined for\n * field types that don't carry nested fields. Concentrates the cast for the\n * Payload core typing oversight (`FlattenedField` doesn't expose\n * `flattenedFields` on every variant).\n */\nexport const getNestedFlattenedFields = (field: FlattenedField): FlattenedField[] | undefined =>\n (field as { flattenedFields?: FlattenedField[] }).flattenedFields\n\n/**\n * Returns the `flattenedFields` of a `BlocksField` block (always an array).\n */\nexport const getBlockFlattenedFields = (block: {\n flattenedFields?: FlattenedField[]\n}): FlattenedField[] => block.flattenedFields ?? []\n\n/**\n * Traverses a flattened field schema and calls `registerHandler` for each\n * named field. Handles blocks (keyed by block slug), groups, tabs, and arrays.\n * Shared by `getExportFieldFunctions` and `getImportFieldFunctions`.\n */\nexport const registerFieldHooks = <TEntry>(\n fields: FlattenedField[],\n parentPath: string,\n result: Record<string, TEntry>,\n registerHandler: (field: FlattenedField, fullKey: string, result: Record<string, TEntry>) => void,\n): void => {\n for (const field of fields) {\n if (!('name' in field) || !field.name) {\n continue\n }\n\n if (field.type === 'blocks') {\n const base = parentPath ? `${parentPath}_${field.name}` : field.name\n for (const block of field.blocks ?? []) {\n registerFieldHooks(\n getBlockFlattenedFields(block),\n `${base}_${block.slug}`,\n result,\n registerHandler,\n )\n }\n continue\n }\n\n const fullKey = parentPath ? `${parentPath}_${field.name}` : field.name\n registerHandler(field, fullKey, result)\n\n if (field.type === 'group' || field.type === 'tab' || field.type === 'array') {\n registerFieldHooks(getNestedFlattenedFields(field) ?? [], fullKey, result, registerHandler)\n }\n }\n}\n"],"names":["getNestedFlattenedFields","field","flattenedFields","getBlockFlattenedFields","block","registerFieldHooks","fields","parentPath","result","registerHandler","name","type","base","blocks","slug","fullKey"],"mappings":"AAEA;;;;;CAKC,GACD,OAAO,MAAMA,2BAA2B,CAACC,QACvC,AAACA,MAAiDC,eAAe,CAAA;AAEnE;;CAEC,GACD,OAAO,MAAMC,0BAA0B,CAACC,QAEhBA,MAAMF,eAAe,IAAI,EAAE,CAAA;AAEnD;;;;CAIC,GACD,OAAO,MAAMG,qBAAqB,CAChCC,QACAC,YACAC,QACAC;IAEA,KAAK,MAAMR,SAASK,OAAQ;QAC1B,IAAI,CAAE,CAAA,UAAUL,KAAI,KAAM,CAACA,MAAMS,IAAI,EAAE;YACrC;QACF;QAEA,IAAIT,MAAMU,IAAI,KAAK,UAAU;YAC3B,MAAMC,OAAOL,aAAa,GAAGA,WAAW,CAAC,EAAEN,MAAMS,IAAI,EAAE,GAAGT,MAAMS,IAAI;YACpE,KAAK,MAAMN,SAASH,MAAMY,MAAM,IAAI,EAAE,CAAE;gBACtCR,mBACEF,wBAAwBC,QACxB,GAAGQ,KAAK,CAAC,EAAER,MAAMU,IAAI,EAAE,EACvBN,QACAC;YAEJ;YACA;QACF;QAEA,MAAMM,UAAUR,aAAa,GAAGA,WAAW,CAAC,EAAEN,MAAMS,IAAI,EAAE,GAAGT,MAAMS,IAAI;QACvED,gBAAgBR,OAAOc,SAASP;QAEhC,IAAIP,MAAMU,IAAI,KAAK,WAAWV,MAAMU,IAAI,KAAK,SAASV,MAAMU,IAAI,KAAK,SAAS;YAC5EN,mBAAmBL,yBAAyBC,UAAU,EAAE,EAAEc,SAASP,QAAQC;QAC7E;IACF;AACF,EAAC"}
@@ -1,12 +1,12 @@
1
- import { type FlattenedField } from 'payload';
2
- import type { ToCSVFunction } from '../types.js';
1
+ import type { FlattenedField } from 'payload';
2
+ import type { ExportFieldHookEntry } from '../types.js';
3
3
  type Args = {
4
4
  fields: FlattenedField[];
5
5
  };
6
6
  /**
7
- * Gets custom toCSV field functions for export.
8
- * These functions transform field values when flattening documents for CSV export.
7
+ * Builds a map from logical field path (e.g. `content_textBlock_body`) to
8
+ * the export hook entry. Paths include block slugs but never array indices.
9
9
  */
10
- export declare const getExportFieldFunctions: ({ fields }: Args) => Record<string, ToCSVFunction>;
10
+ export declare const getExportFieldFunctions: ({ fields }: Args) => Record<string, ExportFieldHookEntry>;
11
11
  export {};
12
12
  //# sourceMappingURL=getExportFieldFunctions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"getExportFieldFunctions.d.ts","sourceRoot":"","sources":["../../src/utilities/getExportFieldFunctions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAA+C,MAAM,SAAS,CAAA;AAE1F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEhD,KAAK,IAAI,GAAG;IACV,MAAM,EAAE,cAAc,EAAE,CAAA;CACzB,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,uBAAuB,eAAgB,IAAI,KAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAqHtF,CAAA"}
1
+ {"version":3,"file":"getExportFieldFunctions.d.ts","sourceRoot":"","sources":["../../src/utilities/getExportFieldFunctions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7C,OAAO,KAAK,EAAE,oBAAoB,EAAyB,MAAM,aAAa,CAAA;AAK9E,KAAK,IAAI,GAAG;IACV,MAAM,EAAE,cAAc,EAAE,CAAA;CACzB,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,uBAAuB,eAAgB,IAAI,KAAG,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAI7F,CAAA"}