@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
@@ -0,0 +1,278 @@
1
+ import { applyFieldHooks } from './applyFieldHooks.js';
2
+ import { flattenObject } from './flattenObject.js';
3
+ import { getExportFieldFunctions } from './getExportFieldFunctions.js';
4
+ import { getImportFieldFunctions } from './getImportFieldFunctions.js';
5
+ import { unflattenObject } from './unflattenObject.js';
6
+ import { describe, expect, it, vi } from 'vitest';
7
+ const mockReq = {
8
+ payload: {
9
+ logger: {
10
+ error: vi.fn()
11
+ }
12
+ }
13
+ };
14
+ describe('beforeExport / beforeImport siblingDoc arg', ()=>{
15
+ describe('CSV export (flattenObject)', ()=>{
16
+ it('passes the source doc at the current nesting level as siblingDoc', ()=>{
17
+ const received = [];
18
+ const fields = [
19
+ {
20
+ name: 'title',
21
+ type: 'text'
22
+ },
23
+ {
24
+ name: 'group',
25
+ type: 'group',
26
+ flattenedFields: [
27
+ {
28
+ name: 'inner',
29
+ type: 'text',
30
+ custom: {
31
+ 'plugin-import-export': {
32
+ hooks: {
33
+ beforeExport: ({ columnName, siblingData, siblingDoc, value })=>{
34
+ received.push({
35
+ columnName,
36
+ isSiblingDocSource: siblingDoc && typeof siblingDoc === 'object' && siblingDoc.inner === value,
37
+ isSiblingDocRow: siblingDoc === siblingData
38
+ });
39
+ return value;
40
+ }
41
+ }
42
+ }
43
+ }
44
+ }
45
+ ]
46
+ }
47
+ ];
48
+ const exportFieldHooks = getExportFieldFunctions({
49
+ fields
50
+ });
51
+ flattenObject({
52
+ data: {
53
+ group: {
54
+ inner: 'deep'
55
+ },
56
+ title: 'Top'
57
+ },
58
+ exportFieldHooks,
59
+ format: 'csv',
60
+ req: mockReq
61
+ });
62
+ expect(received).toEqual([
63
+ {
64
+ columnName: 'group_inner',
65
+ isSiblingDocSource: true,
66
+ isSiblingDocRow: false
67
+ }
68
+ ]);
69
+ });
70
+ it('lets a hook read another sibling by reading siblingDoc', ()=>{
71
+ const fields = [
72
+ {
73
+ name: 'firstName',
74
+ type: 'text'
75
+ },
76
+ {
77
+ name: 'fullName',
78
+ type: 'text',
79
+ custom: {
80
+ 'plugin-import-export': {
81
+ hooks: {
82
+ beforeExport: ({ siblingDoc, value })=>{
83
+ const last = siblingDoc.lastName;
84
+ const first = siblingDoc.firstName;
85
+ if (first && last) {
86
+ return `${first} ${last}`;
87
+ }
88
+ return value;
89
+ }
90
+ }
91
+ }
92
+ }
93
+ },
94
+ {
95
+ name: 'lastName',
96
+ type: 'text'
97
+ }
98
+ ];
99
+ const exportFieldHooks = getExportFieldFunctions({
100
+ fields
101
+ });
102
+ const result = flattenObject({
103
+ data: {
104
+ firstName: 'Ada',
105
+ fullName: '(computed)',
106
+ lastName: 'Lovelace'
107
+ },
108
+ exportFieldHooks,
109
+ format: 'csv',
110
+ req: mockReq
111
+ });
112
+ expect(result.fullName).toBe('Ada Lovelace');
113
+ });
114
+ });
115
+ describe('JSON export (applyFieldHooks)', ()=>{
116
+ it('passes the untouched source sibling as siblingDoc and the mutable output as siblingData', ()=>{
117
+ let capturedSiblingDoc = null;
118
+ let capturedSiblingData = null;
119
+ const fields = [
120
+ {
121
+ name: 'outer',
122
+ type: 'group',
123
+ flattenedFields: [
124
+ {
125
+ name: 'tracked',
126
+ type: 'text',
127
+ custom: {
128
+ 'plugin-import-export': {
129
+ hooks: {
130
+ beforeExport: ({ siblingData, siblingDoc, value })=>{
131
+ capturedSiblingDoc = siblingDoc;
132
+ capturedSiblingData = siblingData;
133
+ siblingData.injected = 'written-into-output';
134
+ return String(value) + '_changed';
135
+ }
136
+ }
137
+ }
138
+ }
139
+ }
140
+ ]
141
+ }
142
+ ];
143
+ const exportFieldHooks = getExportFieldFunctions({
144
+ fields
145
+ });
146
+ const source = {
147
+ outer: {
148
+ tracked: 'original'
149
+ }
150
+ };
151
+ applyFieldHooks({
152
+ data: source,
153
+ fieldHooks: exportFieldHooks,
154
+ fields,
155
+ format: 'json',
156
+ operation: 'export',
157
+ req: mockReq,
158
+ type: 'beforeExport'
159
+ });
160
+ expect(capturedSiblingDoc).toEqual({
161
+ tracked: 'original'
162
+ });
163
+ // The hook's mutation lives on siblingData, not siblingDoc
164
+ expect(capturedSiblingData?.injected).toBe('written-into-output');
165
+ expect(capturedSiblingDoc?.injected).toBeUndefined();
166
+ // And the source object itself is not mutated
167
+ expect(source).toEqual({
168
+ outer: {
169
+ tracked: 'original'
170
+ }
171
+ });
172
+ });
173
+ });
174
+ describe('CSV import (unflattenObject)', ()=>{
175
+ it('passes the full flat row as siblingDoc (equal to data)', ()=>{
176
+ const received = [];
177
+ const fields = [
178
+ {
179
+ name: 'title',
180
+ type: 'text'
181
+ },
182
+ {
183
+ name: 'note',
184
+ type: 'text',
185
+ custom: {
186
+ 'plugin-import-export': {
187
+ hooks: {
188
+ beforeImport: ({ data, siblingDoc, value })=>{
189
+ received.push({
190
+ siblingDocIsData: siblingDoc === data
191
+ });
192
+ return value;
193
+ }
194
+ }
195
+ }
196
+ }
197
+ }
198
+ ];
199
+ const importFieldHooks = getImportFieldFunctions({
200
+ fields
201
+ });
202
+ unflattenObject({
203
+ data: {
204
+ note: 'hello',
205
+ title: 'Top'
206
+ },
207
+ fields,
208
+ format: 'csv',
209
+ importFieldHooks,
210
+ req: mockReq
211
+ });
212
+ expect(received).toEqual([
213
+ {
214
+ siblingDocIsData: true
215
+ }
216
+ ]);
217
+ });
218
+ });
219
+ describe('JSON import (applyFieldHooks)', ()=>{
220
+ it('passes the untouched source sibling as siblingDoc and the mutable output as siblingData', ()=>{
221
+ let capturedSiblingDoc = null;
222
+ let capturedSiblingData = null;
223
+ const fields = [
224
+ {
225
+ name: 'outer',
226
+ type: 'group',
227
+ flattenedFields: [
228
+ {
229
+ name: 'tracked',
230
+ type: 'text',
231
+ custom: {
232
+ 'plugin-import-export': {
233
+ hooks: {
234
+ beforeImport: ({ siblingData, siblingDoc, value })=>{
235
+ capturedSiblingDoc = siblingDoc;
236
+ capturedSiblingData = siblingData;
237
+ siblingData.injected = 'written-into-output';
238
+ return value;
239
+ }
240
+ }
241
+ }
242
+ }
243
+ }
244
+ ]
245
+ }
246
+ ];
247
+ const importFieldHooks = getImportFieldFunctions({
248
+ fields
249
+ });
250
+ const source = {
251
+ outer: {
252
+ tracked: 'original'
253
+ }
254
+ };
255
+ applyFieldHooks({
256
+ data: source,
257
+ fieldHooks: importFieldHooks,
258
+ fields,
259
+ format: 'json',
260
+ operation: 'import',
261
+ req: mockReq,
262
+ type: 'beforeImport'
263
+ });
264
+ expect(capturedSiblingDoc).toEqual({
265
+ tracked: 'original'
266
+ });
267
+ expect(capturedSiblingData?.injected).toBe('written-into-output');
268
+ expect(capturedSiblingDoc?.injected).toBeUndefined();
269
+ expect(source).toEqual({
270
+ outer: {
271
+ tracked: 'original'
272
+ }
273
+ });
274
+ });
275
+ });
276
+ });
277
+
278
+ //# sourceMappingURL=siblingDoc.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/siblingDoc.spec.ts"],"sourcesContent":["import { FlattenedField, PayloadRequest } from 'payload'\n\nimport type { FieldBeforeExportHook, FieldBeforeImportHook } from '../types.js'\n\nimport { applyFieldHooks } from './applyFieldHooks.js'\nimport { flattenObject } from './flattenObject.js'\nimport { getExportFieldFunctions } from './getExportFieldFunctions.js'\nimport { getImportFieldFunctions } from './getImportFieldFunctions.js'\nimport { unflattenObject } from './unflattenObject.js'\n\nimport { describe, expect, it, vi } from 'vitest'\n\nconst mockReq = {\n payload: {\n logger: {\n error: vi.fn(),\n },\n },\n} as unknown as PayloadRequest\n\ndescribe('beforeExport / beforeImport siblingDoc arg', () => {\n describe('CSV export (flattenObject)', () => {\n it('passes the source doc at the current nesting level as siblingDoc', () => {\n const received: Array<{\n columnName: string\n isSiblingDocSource: boolean\n isSiblingDocRow: boolean\n }> = []\n\n const fields: FlattenedField[] = [\n { name: 'title', type: 'text' } as FlattenedField,\n {\n name: 'group',\n type: 'group',\n flattenedFields: [\n {\n name: 'inner',\n type: 'text',\n custom: {\n 'plugin-import-export': {\n hooks: {\n beforeExport: (({ columnName, siblingData, siblingDoc, value }) => {\n received.push({\n columnName,\n isSiblingDocSource:\n siblingDoc &&\n typeof siblingDoc === 'object' &&\n siblingDoc.inner === value,\n isSiblingDocRow: siblingDoc === siblingData,\n })\n return value\n }) satisfies FieldBeforeExportHook,\n },\n },\n },\n },\n ],\n } as unknown as FlattenedField,\n ]\n\n const exportFieldHooks = getExportFieldFunctions({ fields })\n\n flattenObject({\n data: { group: { inner: 'deep' }, title: 'Top' },\n exportFieldHooks,\n format: 'csv',\n req: mockReq,\n })\n\n expect(received).toEqual([\n {\n columnName: 'group_inner',\n isSiblingDocSource: true,\n isSiblingDocRow: false,\n },\n ])\n })\n\n it('lets a hook read another sibling by reading siblingDoc', () => {\n const fields: FlattenedField[] = [\n { name: 'firstName', type: 'text' } as FlattenedField,\n {\n name: 'fullName',\n type: 'text',\n custom: {\n 'plugin-import-export': {\n hooks: {\n beforeExport: (({ siblingDoc, value }) => {\n const last = siblingDoc.lastName as string | undefined\n const first = siblingDoc.firstName as string | undefined\n if (first && last) {\n return `${first} ${last}`\n }\n return value\n }) satisfies FieldBeforeExportHook,\n },\n },\n },\n } as FlattenedField,\n { name: 'lastName', type: 'text' } as FlattenedField,\n ]\n\n const exportFieldHooks = getExportFieldFunctions({ fields })\n\n const result = flattenObject({\n data: { firstName: 'Ada', fullName: '(computed)', lastName: 'Lovelace' },\n exportFieldHooks,\n format: 'csv',\n req: mockReq,\n })\n\n expect(result.fullName).toBe('Ada Lovelace')\n })\n })\n\n describe('JSON export (applyFieldHooks)', () => {\n it('passes the untouched source sibling as siblingDoc and the mutable output as siblingData', () => {\n let capturedSiblingDoc: Record<string, unknown> | null = null\n let capturedSiblingData: Record<string, unknown> | null = null\n\n const fields: FlattenedField[] = [\n {\n name: 'outer',\n type: 'group',\n flattenedFields: [\n {\n name: 'tracked',\n type: 'text',\n custom: {\n 'plugin-import-export': {\n hooks: {\n beforeExport: (({ siblingData, siblingDoc, value }) => {\n capturedSiblingDoc = siblingDoc\n capturedSiblingData = siblingData\n siblingData.injected = 'written-into-output'\n return String(value) + '_changed'\n }) satisfies FieldBeforeExportHook,\n },\n },\n },\n },\n ],\n } as unknown as FlattenedField,\n ]\n\n const exportFieldHooks = getExportFieldFunctions({ fields })\n\n const source = { outer: { tracked: 'original' } }\n\n applyFieldHooks({\n data: source,\n fieldHooks: exportFieldHooks,\n fields,\n format: 'json',\n operation: 'export',\n req: mockReq,\n type: 'beforeExport',\n })\n\n expect(capturedSiblingDoc).toEqual({ tracked: 'original' })\n // The hook's mutation lives on siblingData, not siblingDoc\n expect((capturedSiblingData as unknown as Record<string, unknown>)?.injected).toBe(\n 'written-into-output',\n )\n expect((capturedSiblingDoc as unknown as Record<string, unknown>)?.injected).toBeUndefined()\n // And the source object itself is not mutated\n expect(source).toEqual({ outer: { tracked: 'original' } })\n })\n })\n\n describe('CSV import (unflattenObject)', () => {\n it('passes the full flat row as siblingDoc (equal to data)', () => {\n const received: Array<{ siblingDocIsData: boolean }> = []\n\n const fields: FlattenedField[] = [\n { name: 'title', type: 'text' } as FlattenedField,\n {\n name: 'note',\n type: 'text',\n custom: {\n 'plugin-import-export': {\n hooks: {\n beforeImport: (({ data, siblingDoc, value }) => {\n received.push({ siblingDocIsData: siblingDoc === data })\n return value\n }) satisfies FieldBeforeImportHook,\n },\n },\n },\n } as FlattenedField,\n ]\n\n const importFieldHooks = getImportFieldFunctions({ fields })\n\n unflattenObject({\n data: { note: 'hello', title: 'Top' },\n fields,\n format: 'csv',\n importFieldHooks,\n req: mockReq,\n })\n\n expect(received).toEqual([{ siblingDocIsData: true }])\n })\n })\n\n describe('JSON import (applyFieldHooks)', () => {\n it('passes the untouched source sibling as siblingDoc and the mutable output as siblingData', () => {\n let capturedSiblingDoc: Record<string, unknown> | null = null\n let capturedSiblingData: Record<string, unknown> | null = null\n\n const fields: FlattenedField[] = [\n {\n name: 'outer',\n type: 'group',\n flattenedFields: [\n {\n name: 'tracked',\n type: 'text',\n custom: {\n 'plugin-import-export': {\n hooks: {\n beforeImport: (({ siblingData, siblingDoc, value }) => {\n capturedSiblingDoc = siblingDoc\n capturedSiblingData = siblingData\n siblingData.injected = 'written-into-output'\n return value\n }) satisfies FieldBeforeImportHook,\n },\n },\n },\n },\n ],\n } as unknown as FlattenedField,\n ]\n\n const importFieldHooks = getImportFieldFunctions({ fields })\n\n const source = { outer: { tracked: 'original' } }\n\n applyFieldHooks({\n data: source,\n fieldHooks: importFieldHooks,\n fields,\n format: 'json',\n operation: 'import',\n req: mockReq,\n type: 'beforeImport',\n })\n\n expect(capturedSiblingDoc).toEqual({ tracked: 'original' })\n expect((capturedSiblingData as unknown as Record<string, unknown>)?.injected).toBe(\n 'written-into-output',\n )\n expect((capturedSiblingDoc as unknown as Record<string, unknown>)?.injected).toBeUndefined()\n expect(source).toEqual({ outer: { tracked: 'original' } })\n })\n })\n})\n"],"names":["applyFieldHooks","flattenObject","getExportFieldFunctions","getImportFieldFunctions","unflattenObject","describe","expect","it","vi","mockReq","payload","logger","error","fn","received","fields","name","type","flattenedFields","custom","hooks","beforeExport","columnName","siblingData","siblingDoc","value","push","isSiblingDocSource","inner","isSiblingDocRow","exportFieldHooks","data","group","title","format","req","toEqual","last","lastName","first","firstName","result","fullName","toBe","capturedSiblingDoc","capturedSiblingData","injected","String","source","outer","tracked","fieldHooks","operation","toBeUndefined","beforeImport","siblingDocIsData","importFieldHooks","note"],"mappings":"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"}
@@ -1,9 +1,10 @@
1
1
  import type { FlattenedField, PayloadRequest } from 'payload';
2
- import type { FromCSVFunction } from '../types.js';
2
+ import type { ImportFieldHookEntry } from '../types.js';
3
3
  type UnflattenArgs = {
4
4
  data: Record<string, unknown>;
5
5
  fields: FlattenedField[];
6
- fromCSVFunctions?: Record<string, FromCSVFunction>;
6
+ format?: 'csv' | 'json' | ({} & string);
7
+ importFieldHooks?: Record<string, ImportFieldHookEntry>;
7
8
  req: PayloadRequest;
8
9
  };
9
10
  /**
@@ -19,6 +20,6 @@ type UnflattenArgs = {
19
20
  * - Regular nested objects
20
21
  * 4. Post-processes to handle localized fields, hasMany conversions, and relationship transforms
21
22
  */
22
- export declare const unflattenObject: ({ data, fields, fromCSVFunctions, req, }: UnflattenArgs) => Record<string, unknown>;
23
+ export declare const unflattenObject: ({ data, fields, format, importFieldHooks, req, }: UnflattenArgs) => Record<string, unknown>;
23
24
  export {};
24
25
  //# sourceMappingURL=unflattenObject.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"unflattenObject.d.ts","sourceRoot":"","sources":["../../src/utilities/unflattenObject.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAIlD,KAAK,aAAa,GAAG;IACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,MAAM,EAAE,cAAc,EAAE,CAAA;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;IAClD,GAAG,EAAE,cAAc,CAAA;CACpB,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,6CAKzB,aAAa,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CA0SxC,CAAA"}
1
+ {"version":3,"file":"unflattenObject.d.ts","sourceRoot":"","sources":["../../src/utilities/unflattenObject.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAKvD,KAAK,aAAa,GAAG;IACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,MAAM,EAAE,cAAc,EAAE,CAAA;IACxB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,CAAC,EAAE,GAAG,MAAM,CAAC,CAAA;IACvC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAA;IACvD,GAAG,EAAE,cAAc,CAAA;CACpB,CAAA;AAiCD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,qDAMzB,aAAa,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAkUxC,CAAA"}
@@ -1,4 +1,30 @@
1
- import { processRichTextField } from './processRichTextField.js';
1
+ import { getNestedFlattenedFields } from './flattenedFields.js';
2
+ import { postProcessDocument } from './unflattenPostProcess.js';
3
+ const indexSegment = /^\d+$/;
4
+ const collectArrayLikeNames = (fields, into)=>{
5
+ for (const field of fields){
6
+ if (!('name' in field) || !field.name) {
7
+ continue;
8
+ }
9
+ if (field.type === 'array' || field.type === 'blocks') {
10
+ into.add(field.name);
11
+ }
12
+ const nested = getNestedFlattenedFields(field);
13
+ if (nested) {
14
+ collectArrayLikeNames(nested, into);
15
+ }
16
+ }
17
+ };
18
+ /**
19
+ * Drops numeric array-index segments from a flat key so a runtime key like
20
+ * `items_0_note` matches the static, index-free key a user hook is registered
21
+ * under. A digit-only segment is only stripped when the segment immediately
22
+ * before it names an array or blocks field, so a literal field name like
23
+ * `2024` is preserved.
24
+ */ const toLogicalKey = (flatKey, arrayLikeNames)=>{
25
+ const segments = flatKey.split('_');
26
+ return segments.filter((seg, i)=>!indexSegment.test(seg) || !arrayLikeNames.has(segments[i - 1] ?? '')).join('_');
27
+ };
2
28
  /**
3
29
  * Converts flattened CSV data back into a nested document structure.
4
30
  *
@@ -11,11 +37,13 @@ import { processRichTextField } from './processRichTextField.js';
11
37
  * - Polymorphic relationships (_relationTo and _id suffix pairs)
12
38
  * - Regular nested objects
13
39
  * 4. Post-processes to handle localized fields, hasMany conversions, and relationship transforms
14
- */ export const unflattenObject = ({ data, fields, fromCSVFunctions = {}, req })=>{
40
+ */ export const unflattenObject = ({ data, fields, format = 'csv', importFieldHooks = {}, req })=>{
15
41
  if (!data || typeof data !== 'object') {
16
42
  return {};
17
43
  }
18
44
  const result = {};
45
+ const arrayLikeNames = new Set();
46
+ collectArrayLikeNames(fields, arrayLikeNames);
19
47
  // Sort keys to ensure array indices are processed in order
20
48
  const sortedKeys = Object.keys(data).sort((a, b)=>{
21
49
  // Extract array indices from flattened keys (e.g., "field_0_subfield" -> "0")
@@ -72,13 +100,33 @@ import { processRichTextField } from './processRichTextField.js';
72
100
  }
73
101
  }
74
102
  }
75
- // Apply fromCSV function if available
76
- if (fromCSVFunctions[flatKey]) {
77
- value = fromCSVFunctions[flatKey]({
78
- columnName: flatKey,
79
- data,
80
- value
81
- });
103
+ const importHookEntry = importFieldHooks[flatKey] ?? importFieldHooks[toLogicalKey(flatKey, arrayLikeNames)];
104
+ if (importHookEntry) {
105
+ try {
106
+ if (importHookEntry.type === 'beforeImport') {
107
+ value = importHookEntry.fn({
108
+ columnName: flatKey,
109
+ data,
110
+ format,
111
+ siblingData: data,
112
+ siblingDoc: data,
113
+ value
114
+ });
115
+ } else {
116
+ value = importHookEntry.fn({
117
+ columnName: flatKey,
118
+ data,
119
+ value
120
+ });
121
+ }
122
+ } catch (error) {
123
+ req.payload.logger.error({
124
+ err: error,
125
+ msg: `[plugin-import-export] Field-level beforeImport hook for "${flatKey}" threw — falling back to original value`
126
+ });
127
+ // Keep the original value so the row is not dropped — downstream
128
+ // validation will surface any deeper issue per-row.
129
+ }
82
130
  }
83
131
  // Example: "blocks_0_content_text" -> ["blocks", "0", "content", "text"]
84
132
  const pathSegments = flatKey.split('_');
@@ -271,165 +319,5 @@ const getParentObject = (obj, segments)=>{
271
319
  }
272
320
  return current;
273
321
  };
274
- /**
275
- * Post-processes the unflattened document to handle special field types:
276
- * - Localized fields: transforms field_locale keys to nested { field: { locale: value } }
277
- * - Number hasMany: converts comma-separated strings or arrays to number arrays
278
- * - Relationship hasMany: converts comma-separated IDs to arrays
279
- * - Polymorphic relationships: transforms flat {relationTo, id} to {relationTo, value}
280
- * - Rich text fields: ensures proper data structure
281
- */ const postProcessDocument = (doc, fields)=>{
282
- const localizedFields = fields.filter((field)=>field.localized);
283
- const processedLocalizedFields = new Set();
284
- for (const field of localizedFields){
285
- if (processedLocalizedFields.has(field.name)) {
286
- continue;
287
- }
288
- // Look for all locale-specific keys for this field
289
- const localePattern = new RegExp(`^${field.name}_([a-z]{2}(?:_[A-Z]{2})?)$`);
290
- const localeData = {};
291
- const keysToDelete = [];
292
- for (const [key, value] of Object.entries(doc)){
293
- const match = key.match(localePattern);
294
- if (match && match[1]) {
295
- const locale = match[1];
296
- localeData[locale] = value;
297
- keysToDelete.push(key);
298
- }
299
- }
300
- // If we found locale-specific data, restructure it as Payload expects
301
- if (Object.keys(localeData).length > 0) {
302
- // Payload stores localized fields as nested objects: { field: { en: 'value', es: 'value' } }
303
- doc[field.name] = localeData;
304
- keysToDelete.forEach((key)=>delete doc[key]);
305
- processedLocalizedFields.add(field.name);
306
- }
307
- }
308
- // Handle number fields with hasMany - convert string arrays to number arrays
309
- const numberFields = fields.filter((field)=>field.type === 'number' && field.hasMany);
310
- for (const field of numberFields){
311
- const value = doc[field.name];
312
- // Skip if field doesn't exist in document
313
- if (!(field.name in doc)) {
314
- continue;
315
- }
316
- // Handle comma-separated string (e.g., "1,2,3,4,5")
317
- if (typeof value === 'string' && value.includes(',')) {
318
- doc[field.name] = value.split(',').map((v)=>v.trim()).filter((v)=>v !== '').map((v)=>{
319
- const num = parseFloat(v);
320
- return isNaN(num) ? 0 : num;
321
- });
322
- } else if (Array.isArray(value)) {
323
- // Filter out null, undefined, and empty string values, then convert to numbers
324
- doc[field.name] = value.filter((v)=>v !== null && v !== undefined && v !== '').map((v)=>{
325
- if (typeof v === 'string') {
326
- const num = parseFloat(v);
327
- return isNaN(num) ? 0 : num;
328
- }
329
- return v;
330
- });
331
- } else if (value !== null && value !== undefined && value !== '') {
332
- const num = typeof value === 'string' ? parseFloat(value) : value;
333
- doc[field.name] = isNaN(num) ? [] : [
334
- num
335
- ];
336
- } else {
337
- doc[field.name] = [];
338
- }
339
- }
340
- // Handle relationship fields with hasMany - convert comma-separated IDs to arrays
341
- const relationshipFields = fields.filter((field)=>(field.type === 'relationship' || field.type === 'upload') && field.hasMany === true && !Array.isArray(field.relationTo));
342
- for (const field of relationshipFields){
343
- const value = doc[field.name];
344
- // Handle comma-separated string of IDs (e.g., "id1,id2,id3")
345
- if (typeof value === 'string' && value.includes(',')) {
346
- doc[field.name] = value.split(',').map((v)=>v.trim()).filter((v)=>v !== '');
347
- } else if (Array.isArray(value)) {
348
- doc[field.name] = value.filter((v)=>v !== null && v !== undefined && v !== '');
349
- } else if (value !== null && value !== undefined && value !== '') {
350
- doc[field.name] = [
351
- value
352
- ];
353
- }
354
- }
355
- // Handle polymorphic relationships - transform from flat structure to proper format
356
- for (const [key, value] of Object.entries(doc)){
357
- // Handle arrays of polymorphic relationships
358
- if (Array.isArray(value)) {
359
- // Check if this array contains polymorphic relationship objects
360
- const hasPolymorphicItems = value.some((item)=>typeof item === 'object' && item !== null && 'relationTo' in item);
361
- if (hasPolymorphicItems) {
362
- // Filter out null/invalid polymorphic items and transform valid ones
363
- const processedArray = [];
364
- for(let i = 0; i < value.length; i++){
365
- const item = value[i];
366
- if (typeof item === 'object' && item !== null && 'relationTo' in item) {
367
- const typedItem = item;
368
- // Skip if both relationTo and value/id are null/empty
369
- if (!typedItem.relationTo || !typedItem.id && !typedItem.value) {
370
- continue;
371
- }
372
- // Transform from {relationTo: 'collection', id: '123'} to {relationTo: 'collection', value: '123'}
373
- if ('id' in typedItem) {
374
- typedItem.value = typedItem.id;
375
- delete typedItem.id;
376
- }
377
- processedArray.push(typedItem);
378
- } else if (item !== null && item !== undefined) {
379
- processedArray.push(item);
380
- }
381
- }
382
- // Update the array with filtered results
383
- if (value.length !== processedArray.length) {
384
- doc[key] = processedArray.length > 0 ? processedArray : [];
385
- }
386
- }
387
- // For non-polymorphic arrays, preserve null placeholders for sparse arrays
388
- } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
389
- // Check if this is a single polymorphic relationship
390
- if ('relationTo' in value && ('id' in value || 'value' in value)) {
391
- const typedValue = value;
392
- // If both relationTo and value are null/empty, set the whole field to null
393
- if (!typedValue.relationTo || !typedValue.id && !typedValue.value) {
394
- doc[key] = null;
395
- } else {
396
- // If it has 'id', transform to 'value'
397
- if ('id' in typedValue && !('value' in typedValue)) {
398
- typedValue.value = typedValue.id;
399
- delete typedValue.id;
400
- }
401
- }
402
- } else {
403
- // Recursively process nested objects
404
- postProcessDocument(value, fields);
405
- }
406
- }
407
- }
408
- // Process rich text fields to ensure proper data types
409
- const richTextFields = fields.filter((field)=>field.type === 'richText');
410
- for (const field of richTextFields){
411
- if (field.name in doc && doc[field.name]) {
412
- doc[field.name] = processRichTextField(doc[field.name]);
413
- }
414
- }
415
- // Also process rich text fields in blocks
416
- const blockFields = fields.filter((field)=>field.type === 'blocks');
417
- for (const field of blockFields){
418
- if (field.name in doc && Array.isArray(doc[field.name])) {
419
- const blocks = doc[field.name];
420
- for (const block of blocks){
421
- if (!block || typeof block !== 'object') {
422
- continue;
423
- }
424
- // Look for richText fields directly in the block
425
- for (const [key, value] of Object.entries(block)){
426
- if (key === 'richText' || typeof key === 'string' && key.includes('richText')) {
427
- block[key] = processRichTextField(value);
428
- }
429
- }
430
- }
431
- }
432
- }
433
- };
434
322
 
435
323
  //# sourceMappingURL=unflattenObject.js.map