@mvriu5/payload-ai 1.2.0 → 1.3.2

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 (51) hide show
  1. package/README.md +42 -0
  2. package/dist/components/Icons.d.ts +0 -8
  3. package/dist/components/Icons.js +4 -216
  4. package/dist/components/{ActionToast.d.ts → action-toast/ActionToast.d.ts} +1 -2
  5. package/dist/components/{ActionToast.js → action-toast/ActionToast.js} +25 -34
  6. package/dist/components/{ActionToast.module.css → action-toast/ActionToast.module.css} +0 -79
  7. package/dist/components/ai-input/AIInput.js +433 -0
  8. package/dist/components/{AIInput.module.css → ai-input/AIInput.module.css} +111 -43
  9. package/dist/components/ai-input/badge.d.ts +14 -0
  10. package/dist/components/ai-input/badge.js +85 -0
  11. package/dist/components/{AuditLogList.d.ts → audit-log-list/AuditLogList.d.ts} +4 -8
  12. package/dist/components/{AuditLogList.js → audit-log-list/AuditLogList.js} +49 -21
  13. package/dist/components/{AuditLogList.module.css → audit-log-list/AuditLogList.module.css} +11 -65
  14. package/dist/components/dashboard/Dashboard.d.ts +2 -0
  15. package/dist/components/dashboard/Dashboard.js +15 -0
  16. package/dist/components/dashboard/Dashboard.module.css +13 -0
  17. package/dist/components/{DiffDialog.d.ts → diff-dialog/DiffDialog.d.ts} +2 -2
  18. package/dist/components/{DiffDialog.js → diff-dialog/DiffDialog.js} +200 -189
  19. package/dist/components/{DiffDialog.module.css → diff-dialog/DiffDialog.module.css} +17 -35
  20. package/dist/components/hooks/useAIChatStream.d.ts +39 -0
  21. package/dist/components/hooks/useAIChatStream.js +217 -0
  22. package/dist/components/hooks/useAISettings.js +26 -25
  23. package/dist/components/hooks/useAuditLog.d.ts +11 -0
  24. package/dist/components/hooks/useAuditLog.js +41 -0
  25. package/dist/components/hooks/useDocumentMentionSuggestions.d.ts +1 -1
  26. package/dist/components/hooks/useDocumentMentionSuggestions.js +31 -27
  27. package/dist/components/hooks/useMentions.d.ts +58 -0
  28. package/dist/components/hooks/useMentions.js +276 -0
  29. package/dist/components/hooks/usePluginConfig.d.ts +52 -0
  30. package/dist/components/hooks/usePluginConfig.js +20 -0
  31. package/dist/components/{MentionPopover.d.ts → mention-popover/MentionPopover.d.ts} +2 -4
  32. package/dist/components/{MentionPopover.js → mention-popover/MentionPopover.js} +1 -8
  33. package/dist/components/{MentionPopover.module.css → mention-popover/MentionPopover.module.css} +1 -1
  34. package/dist/exports/client.d.ts +2 -2
  35. package/dist/exports/client.js +2 -2
  36. package/dist/handlers/applyActionHandler.js +27 -7
  37. package/dist/handlers/chatHandler.js +302 -58
  38. package/dist/handlers/mediaUploadHandler.d.ts +7 -0
  39. package/dist/handlers/mediaUploadHandler.js +99 -0
  40. package/dist/handlers/mentionSuggestionHandler.js +16 -14
  41. package/dist/handlers/proposalDiffHandler.js +41 -29
  42. package/dist/index.d.ts +6 -0
  43. package/dist/index.js +39 -7
  44. package/dist/payload/collectionPermissions.js +3 -1
  45. package/dist/payload/normalizeData.d.ts +0 -6
  46. package/dist/payload/normalizeData.js +25 -13
  47. package/dist/payload/proposalData.js +4 -1
  48. package/dist/payload/schemaContext.js +98 -43
  49. package/package.json +6 -6
  50. package/dist/components/AIInput.js +0 -896
  51. /package/dist/components/{AIInput.d.ts → ai-input/AIInput.d.ts} +0 -0
@@ -0,0 +1,99 @@
1
+ import { addDataAndFileToRequest } from "payload";
2
+ import { getNumber, getString, isRecord } from "../payload/shared.js";
3
+ const mimeTypeMatches = (mimeType, acceptedMimeType)=>{
4
+ if (acceptedMimeType === mimeType) return true;
5
+ if (!acceptedMimeType.endsWith("/*")) return false;
6
+ return mimeType.startsWith(`${acceptedMimeType.slice(0, -2)}/`);
7
+ };
8
+ const isAcceptedMimeType = (mimeType, acceptedMimeTypes)=>{
9
+ if (!acceptedMimeTypes || acceptedMimeTypes.length === 0) return true;
10
+ return acceptedMimeTypes.some((acceptedMimeType)=>mimeTypeMatches(mimeType, acceptedMimeType));
11
+ };
12
+ const getUploadData = (data)=>{
13
+ if (!isRecord(data)) return {};
14
+ const { collection: _collection, file: _file, ...uploadData } = data;
15
+ return uploadData;
16
+ };
17
+ const getMediaAttachment = ({ collectionSlug, doc, file })=>{
18
+ const record = isRecord(doc) ? doc : {};
19
+ const id = record.id;
20
+ return {
21
+ collection: collectionSlug,
22
+ filename: getString(record.filename) || file.name || "",
23
+ filesize: getNumber(record.filesize) || file.size || 0,
24
+ id: typeof id === "string" || typeof id === "number" ? String(id) : "",
25
+ mimeType: getString(record.mimeType) || file.mimetype || "",
26
+ type: "media",
27
+ url: getString(record.url)
28
+ };
29
+ };
30
+ export const createMediaUploadHandler = ({ acceptedMimeTypes, collectionSlug, maxFileSize })=>async (req)=>{
31
+ const uploadReq = req;
32
+ try {
33
+ await addDataAndFileToRequest(uploadReq);
34
+ const collectionConfig = uploadReq.payload.config.collections.find((collection)=>collection.slug === collectionSlug);
35
+ if (!collectionConfig) {
36
+ return Response.json({
37
+ error: `Upload collection not found: ${collectionSlug}`
38
+ }, {
39
+ status: 400
40
+ });
41
+ }
42
+ if (!collectionConfig.upload) {
43
+ return Response.json({
44
+ error: `Collection is not upload-enabled: ${collectionSlug}`
45
+ }, {
46
+ status: 400
47
+ });
48
+ }
49
+ const file = uploadReq.file;
50
+ if (!file) {
51
+ return Response.json({
52
+ error: "File is required"
53
+ }, {
54
+ status: 400
55
+ });
56
+ }
57
+ const fileSize = typeof file.size === "number" && Number.isFinite(file.size) ? file.size : 0;
58
+ if (maxFileSize && fileSize > maxFileSize) {
59
+ return Response.json({
60
+ error: `File exceeds max size of ${maxFileSize} bytes`
61
+ }, {
62
+ status: 413
63
+ });
64
+ }
65
+ const mimeType = file.mimetype || "";
66
+ if (mimeType && !isAcceptedMimeType(mimeType, acceptedMimeTypes)) {
67
+ return Response.json({
68
+ error: `File type is not accepted: ${mimeType}`
69
+ }, {
70
+ status: 415
71
+ });
72
+ }
73
+ const doc = await uploadReq.payload.create({
74
+ collection: collectionSlug,
75
+ data: getUploadData(uploadReq.data),
76
+ file: file,
77
+ overrideAccess: false,
78
+ req: uploadReq
79
+ });
80
+ return Response.json({
81
+ attachment: getMediaAttachment({
82
+ collectionSlug,
83
+ doc,
84
+ file
85
+ }),
86
+ doc
87
+ });
88
+ } catch (err) {
89
+ uploadReq.payload.logger.error({
90
+ err,
91
+ msg: "AI media upload failed"
92
+ });
93
+ return Response.json({
94
+ error: "Media upload failed."
95
+ }, {
96
+ status: 500
97
+ });
98
+ }
99
+ };
@@ -61,16 +61,13 @@ export const createMentionSuggestionHandler = (options = {})=>async (req)=>{
61
61
  type: "doc"
62
62
  });
63
63
  };
64
- for (const collection of collections){
65
- if (suggestions.length >= 5) break;
64
+ const searchResults = await Promise.all(collections.flatMap((collection)=>{
66
65
  const collectionFields = collection.fields;
67
66
  const localeCodes = query && hasLocalizedFields(collectionFields) ? getLocaleCodes(req) : [];
68
67
  const localesToSearch = localeCodes.length > 0 ? localeCodes : [
69
68
  null
70
69
  ];
71
- for (const locale of localesToSearch){
72
- if (suggestions.length >= 5) break;
73
- const result = await req.payload.find({
70
+ return localesToSearch.map((locale)=>req.payload.find({
74
71
  collection: collection.slug,
75
72
  depth: 0,
76
73
  limit: query ? 100 : 10,
@@ -79,16 +76,21 @@ export const createMentionSuggestionHandler = (options = {})=>async (req)=>{
79
76
  } : {},
80
77
  overrideAccess: false,
81
78
  req
79
+ }).then((result)=>({
80
+ collection,
81
+ result
82
+ })));
83
+ }));
84
+ for (const { collection, result } of searchResults){
85
+ if (suggestions.length >= 5) break;
86
+ for (const doc of result.docs){
87
+ if (suggestions.length >= 5) break;
88
+ addSuggestion({
89
+ collectionSlug: collection.slug,
90
+ doc,
91
+ requireLabelMatch: Boolean(normalizedQuery),
92
+ useAsTitle: collection.admin?.useAsTitle
82
93
  });
83
- for (const doc of result.docs){
84
- if (suggestions.length >= 5) break;
85
- addSuggestion({
86
- collectionSlug: collection.slug,
87
- doc,
88
- requireLabelMatch: Boolean(normalizedQuery),
89
- useAsTitle: collection.admin?.useAsTitle
90
- });
91
- }
92
94
  }
93
95
  }
94
96
  return Response.json({
@@ -69,7 +69,7 @@ export const createProposalDiffHandler = (options = {})=>async (req)=>{
69
69
  req,
70
70
  slug: proposal.slug
71
71
  }) : null;
72
- for (const [locale, localeData] of Object.entries(preparedData.localizedData)){
72
+ const localizedResults = await Promise.all(Object.entries(preparedData.localizedData).map(async ([locale, localeData])=>{
73
73
  const doc = await req.payload.findGlobal({
74
74
  depth: 2,
75
75
  fallbackLocale: false,
@@ -83,9 +83,16 @@ export const createProposalDiffHandler = (options = {})=>async (req)=>{
83
83
  fields: globalFields,
84
84
  preparedData: localeData
85
85
  });
86
+ return {
87
+ completedData,
88
+ doc,
89
+ locale
90
+ };
91
+ }));
92
+ localizedResults.forEach(({ completedData, doc, locale })=>{
86
93
  beforeByLocale[locale] = redactSensitiveData(doc);
87
94
  afterByLocale[locale] = redactSensitiveData(mergeData(doc, completedData));
88
- }
95
+ });
89
96
  return Response.json({
90
97
  after: afterByLocale,
91
98
  before: beforeByLocale
@@ -177,38 +184,43 @@ export const createProposalDiffHandler = (options = {})=>async (req)=>{
177
184
  req
178
185
  }) : null;
179
186
  let createFallbackSource = null;
180
- for (const [locale, localeData] of Object.entries(preparedData.localizedData)){
181
- const fallbackSource = proposal.action === "create" ? createFallbackSource || {} : locale === defaultLocale ? await req.payload.findByID({
182
- collection: proposal.collection,
183
- depth: 2,
184
- fallbackLocale: false,
185
- id: proposal.id,
186
- locale,
187
- overrideAccess: false,
188
- req
189
- }) : defaultLocaleDoc || {};
190
- const completedData = applyLocalizedRequiredFallbackToPreparedData({
191
- fallbackSource,
192
- fields: collectionFields,
193
- preparedData: localeData
194
- });
195
- if (proposal.action === "create") {
187
+ if (proposal.action === "create") {
188
+ for (const [locale, localeData] of Object.entries(preparedData.localizedData)){
189
+ const completedData = applyLocalizedRequiredFallbackToPreparedData({
190
+ fallbackSource: createFallbackSource || {},
191
+ fields: collectionFields,
192
+ preparedData: localeData
193
+ });
196
194
  beforeByLocale[locale] = {};
197
195
  afterByLocale[locale] = redactSensitiveData(completedData);
198
196
  createFallbackSource = mergeData(createFallbackSource || {}, completedData);
199
- continue;
200
197
  }
201
- const doc = await req.payload.findByID({
202
- collection: proposal.collection,
203
- depth: 2,
204
- fallbackLocale: false,
205
- id: proposal.id,
206
- locale,
207
- overrideAccess: false,
208
- req
198
+ } else {
199
+ const localizedResults = await Promise.all(Object.entries(preparedData.localizedData).map(async ([locale, localeData])=>{
200
+ const doc = await req.payload.findByID({
201
+ collection: proposal.collection,
202
+ depth: 2,
203
+ fallbackLocale: false,
204
+ id: proposal.id,
205
+ locale,
206
+ overrideAccess: false,
207
+ req
208
+ });
209
+ const completedData = applyLocalizedRequiredFallbackToPreparedData({
210
+ fallbackSource: locale === defaultLocale ? doc : defaultLocaleDoc || {},
211
+ fields: collectionFields,
212
+ preparedData: localeData
213
+ });
214
+ return {
215
+ completedData,
216
+ doc,
217
+ locale
218
+ };
219
+ }));
220
+ localizedResults.forEach(({ completedData, doc, locale })=>{
221
+ beforeByLocale[locale] = redactSensitiveData(doc);
222
+ afterByLocale[locale] = redactSensitiveData(mergeData(doc, completedData));
209
223
  });
210
- beforeByLocale[locale] = redactSensitiveData(doc);
211
- afterByLocale[locale] = redactSensitiveData(mergeData(doc, completedData));
212
224
  }
213
225
  return Response.json({
214
226
  after: afterByLocale,
package/dist/index.d.ts CHANGED
@@ -6,6 +6,12 @@ export type PayloadAIPluginOptions = {
6
6
  collections?: CollectionPermissionMap;
7
7
  disabled?: boolean;
8
8
  maxOutputTokens?: number;
9
+ media?: {
10
+ acceptedMimeTypes?: string[];
11
+ collectionSlug?: string;
12
+ enabled?: boolean;
13
+ maxFileSize?: number;
14
+ };
9
15
  models?: AIModelConfig;
10
16
  };
11
17
  export declare const payloadAiPlugin: (pluginOptions: PayloadAIPluginOptions) => (config: Config) => Config;
package/dist/index.js CHANGED
@@ -2,13 +2,25 @@ import { aiProviders, getResolvedAIModelConfig } from "./ai/providerOptions.js";
2
2
  import { createApplyActionHandler } from "./handlers/applyActionHandler.js";
3
3
  import { createChatHandler } from "./handlers/chatHandler.js";
4
4
  import { createMentionSuggestionHandler } from "./handlers/mentionSuggestionHandler.js";
5
+ import { createMediaUploadHandler } from "./handlers/mediaUploadHandler.js";
5
6
  import { createProposalDiffHandler } from "./handlers/proposalDiffHandler.js";
6
7
  import { createAuditLogHandler } from "./handlers/auditLogHandler.js";
7
8
  import { resolveCollectionPermissions } from "./payload/collectionPermissions.js";
8
9
  import { isInternalCollection } from "./payload/shared.js";
9
- const auditLogCollectionSlug = "payload-ai-auditlog";
10
+ const resolveMediaUploadOptions = (media)=>{
11
+ if (!media || media.enabled === false) return null;
12
+ return {
13
+ ...media.acceptedMimeTypes ? {
14
+ acceptedMimeTypes: media.acceptedMimeTypes
15
+ } : {},
16
+ collectionSlug: media.collectionSlug || "media",
17
+ ...typeof media.maxFileSize === "number" && Number.isFinite(media.maxFileSize) && media.maxFileSize > 0 ? {
18
+ maxFileSize: Math.floor(media.maxFileSize)
19
+ } : {}
20
+ };
21
+ };
10
22
  const createAIChangesCollection = ()=>({
11
- slug: auditLogCollectionSlug,
23
+ slug: "payload-ai-auditlog",
12
24
  access: {
13
25
  create: ()=>false,
14
26
  delete: ()=>false,
@@ -156,9 +168,10 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
156
168
  const collectionPermissions = resolveCollectionPermissions(pluginOptions.collections);
157
169
  const allowUserApiKeys = pluginOptions.allowUserApiKeys !== false;
158
170
  const modelConfig = getResolvedAIModelConfig(pluginOptions.models);
171
+ const mediaUploadOptions = resolveMediaUploadOptions(pluginOptions.media);
159
172
  const maxOutputTokens = typeof pluginOptions.maxOutputTokens === "number" && Number.isFinite(pluginOptions.maxOutputTokens) && pluginOptions.maxOutputTokens > 0 ? Math.floor(pluginOptions.maxOutputTokens) : undefined;
160
173
  if (!config.collections) config.collections = [];
161
- if (!config.collections.some((collection)=>collection.slug === auditLogCollectionSlug)) {
174
+ if (!config.collections.some((collection)=>collection.slug === "payload-ai-auditlog")) {
162
175
  config.collections.push(createAIChangesCollection());
163
176
  }
164
177
  addAccountFields({
@@ -166,7 +179,13 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
166
179
  config
167
180
  });
168
181
  if (pluginOptions.disabled) return config;
169
- const mentionCollectionSlugs = config.collections.filter((collection)=>!isInternalCollection(collection.slug)).filter((collection)=>collectionPermissions ? Boolean(collectionPermissions[collection.slug]?.read) : true).map((collection)=>collection.slug);
182
+ const mentionCollectionSlugs = config.collections.flatMap((collection)=>{
183
+ if (isInternalCollection(collection.slug)) return [];
184
+ if (collectionPermissions && !collectionPermissions[collection.slug]?.read) return [];
185
+ return [
186
+ collection.slug
187
+ ];
188
+ });
170
189
  if (!config.endpoints) config.endpoints = [];
171
190
  if (!config.admin) config.admin = {};
172
191
  config.admin.custom = {
@@ -175,12 +194,18 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
175
194
  ...config.admin.custom?.payloadAiPlugin || {},
176
195
  collectionSlugs: mentionCollectionSlugs,
177
196
  allowUserApiKeys,
197
+ ...mediaUploadOptions ? {
198
+ media: {
199
+ ...mediaUploadOptions,
200
+ enabled: true
201
+ }
202
+ } : {},
178
203
  models: modelConfig
179
204
  }
180
205
  };
181
206
  if (!config.admin.components) config.admin.components = {};
182
207
  if (!config.admin.components.beforeDashboard) config.admin.components.beforeDashboard = [];
183
- config.admin.components.beforeDashboard.push(`@mvriu5/payload-ai/client#AIInput`);
208
+ config.admin.components.beforeDashboard.push(`@mvriu5/payload-ai/client#Dashboard`);
184
209
  config.endpoints.push({
185
210
  handler: createChatHandler({
186
211
  allowUserApiKeys,
@@ -193,7 +218,7 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
193
218
  });
194
219
  config.endpoints.push({
195
220
  handler: createApplyActionHandler({
196
- changeLogCollection: auditLogCollectionSlug,
221
+ changeLogCollection: "payload-ai-auditlog",
197
222
  collections: collectionPermissions
198
223
  }),
199
224
  method: "post",
@@ -201,7 +226,7 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
201
226
  });
202
227
  config.endpoints.push({
203
228
  handler: createAuditLogHandler({
204
- changeLogCollection: auditLogCollectionSlug
229
+ changeLogCollection: "payload-ai-auditlog"
205
230
  }),
206
231
  method: "get",
207
232
  path: "/ai-audit-log"
@@ -220,6 +245,13 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
220
245
  method: "post",
221
246
  path: "/ai-mention-suggestion"
222
247
  });
248
+ if (mediaUploadOptions) {
249
+ config.endpoints.push({
250
+ handler: createMediaUploadHandler(mediaUploadOptions),
251
+ method: "post",
252
+ path: "/ai-upload-media"
253
+ });
254
+ }
223
255
  if (incomingOnInit) {
224
256
  config.onInit = async (payload)=>{
225
257
  await incomingOnInit(payload);
@@ -6,7 +6,9 @@ const allActions = [
6
6
  "update"
7
7
  ];
8
8
  const getKnownCollectionSlugs = (req)=>{
9
- return req.payload.config.collections.map((collection)=>collection.slug).filter((slug)=>!isInternalCollection(slug));
9
+ return req.payload.config.collections.flatMap((collection)=>isInternalCollection(collection.slug) ? [] : [
10
+ collection.slug
11
+ ]);
10
12
  };
11
13
  export const resolveCollectionPermissions = (collections)=>{
12
14
  if (!collections) return undefined;
@@ -33,15 +33,9 @@ export type NormalizedData = {
33
33
  droppedFields: string[];
34
34
  };
35
35
  export declare const isAuthCollection: (collectionConfig?: CollectionConfig | null) => boolean;
36
- export declare const getCollectionFields: (collectionConfig?: CollectionConfig | null) => FieldConfig[];
37
36
  export declare const hasDrafts: (collectionConfig?: CollectionConfig | null) => boolean;
38
37
  export declare const getSchemaFields: (collectionConfig?: CollectionConfig | null) => FieldConfig[];
39
38
  export declare const createLexicalText: (value: unknown) => Record<string, unknown>;
40
39
  export declare const normalizeAuthData: (collectionConfig: CollectionConfig | undefined, normalized: NormalizedData) => NormalizedData;
41
40
  export declare const normalizeDataForFields: (fields: readonly FieldConfig[], data: Record<string, unknown>) => NormalizedData;
42
- export declare const getLocalizedRequiredFallbackData: ({ fields, source, target, }: {
43
- fields: readonly FieldConfig[];
44
- source: Record<string, unknown>;
45
- target: Record<string, unknown>;
46
- }) => Record<string, unknown>;
47
41
  export {};
@@ -6,7 +6,7 @@ const getNamedFields = (fields)=>{
6
6
  export const isAuthCollection = (collectionConfig)=>{
7
7
  return Boolean(collectionConfig?.auth);
8
8
  };
9
- export const getCollectionFields = (collectionConfig)=>{
9
+ const getCollectionFields = (collectionConfig)=>{
10
10
  const fields = [
11
11
  ...collectionConfig?.fields || []
12
12
  ];
@@ -45,7 +45,12 @@ export const getSchemaFields = (collectionConfig)=>{
45
45
  export const createLexicalText = (value)=>{
46
46
  if (isRecord(value) && isRecord(value.root)) return value;
47
47
  const text = Array.isArray(value) ? value.join("\n") : String(value || "");
48
- const lines = text.split("\n").map((line)=>line.trim()).filter(Boolean);
48
+ const lines = text.split("\n").flatMap((line)=>{
49
+ const trimmedLine = line.trim();
50
+ return trimmedLine ? [
51
+ trimmedLine
52
+ ] : [];
53
+ });
49
54
  return {
50
55
  root: {
51
56
  children: (lines.length ? lines : [
@@ -89,7 +94,12 @@ const normalizeArrayValue = (field, value)=>{
89
94
  });
90
95
  };
91
96
  const getOptionValues = (field)=>{
92
- return (field.options || []).map((option)=>typeof option === "string" ? option : option.value).filter((option)=>Boolean(option));
97
+ return (field.options || []).flatMap((option)=>{
98
+ const value = typeof option === "string" ? option : option.value;
99
+ return value ? [
100
+ value
101
+ ] : [];
102
+ });
93
103
  };
94
104
  const normalizeOptionValue = (field, value)=>{
95
105
  const optionValues = getOptionValues(field);
@@ -101,19 +111,21 @@ const normalizeOptionValue = (field, value)=>{
101
111
  };
102
112
  const normalizeBlocksValue = (field, value)=>{
103
113
  if (!Array.isArray(value)) return value;
104
- return value.map((item)=>{
105
- if (!isRecord(item)) return null;
114
+ return value.flatMap((item)=>{
115
+ if (!isRecord(item)) return [];
106
116
  const blockType = typeof item.blockType === "string" ? item.blockType : typeof item.type === "string" ? item.type : typeof item.slug === "string" ? item.slug : null;
107
- if (!blockType) return null;
117
+ if (!blockType) return [];
108
118
  const block = field.blocks?.find((candidate)=>candidate.slug === blockType);
109
- if (!block) return null;
119
+ if (!block) return [];
110
120
  const { blockType: _blockType, type: _type, slug: _slug, ...data } = item;
111
121
  const normalizedBlock = normalizeDataForFields(block.fields || [], data).data;
112
- return {
113
- ...normalizedBlock,
114
- blockType
115
- };
116
- }).filter(Boolean);
122
+ return [
123
+ {
124
+ ...normalizedBlock,
125
+ blockType
126
+ }
127
+ ];
128
+ });
117
129
  };
118
130
  const normalizeFieldValue = (field, value)=>{
119
131
  if (value === undefined) return value;
@@ -209,7 +221,7 @@ export const normalizeDataForFields = (fields, data)=>{
209
221
  droppedFields
210
222
  };
211
223
  };
212
- export const getLocalizedRequiredFallbackData = ({ fields, source, target })=>{
224
+ const getLocalizedRequiredFallbackData = ({ fields, source, target })=>{
213
225
  const fallbackData = {};
214
226
  for (const field of getNamedFields(fields)){
215
227
  const targetValue = target[field.name];
@@ -69,7 +69,10 @@ const normalizeRelationshipScalar = (value)=>{
69
69
  }
70
70
  const trimmedValue = value.trim();
71
71
  if (!trimmedValue || /\s/.test(trimmedValue)) return undefined;
72
- if (/^\d+$/.test(trimmedValue) && Number(trimmedValue) <= 0) return undefined;
72
+ if (/^\d+$/.test(trimmedValue)) {
73
+ const numericValue = Number(trimmedValue);
74
+ return numericValue > 0 ? numericValue : undefined;
75
+ }
73
76
  return trimmedValue;
74
77
  };
75
78
  const normalizeRelationshipValue = ({ field, issues, path, value })=>{