@mvriu5/payload-ai 1.6.3 → 1.6.4

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 (125) hide show
  1. package/dist/components/action-toast/ActionToast.d.ts +1 -14
  2. package/dist/components/action-toast/ActionToast.js +1 -1
  3. package/dist/components/ai-input/AIInput.js +1 -1
  4. package/dist/components/audit-log-list/AuditLogList.d.ts +2 -3
  5. package/dist/components/diff-dialog/DiffDialog.d.ts +2 -6
  6. package/dist/components/diff-dialog/DiffDialog.js +1 -1
  7. package/dist/components/hooks/useAIChatStream.d.ts +1 -1
  8. package/dist/components/hooks/useAISettings.d.ts +1 -1
  9. package/dist/components/hooks/useAISettings.js +1 -1
  10. package/dist/components/hooks/useDocumentMentionSuggestions.js +1 -1
  11. package/dist/components/hooks/useMentions.js +1 -1
  12. package/dist/components/hooks/usePluginConfig.d.ts +6 -6
  13. package/dist/components/hooks/usePluginConfig.js +1 -1
  14. package/dist/{payload → features}/collectionPermissions.js +1 -1
  15. package/dist/{ai → features}/promptCaching.d.ts +2 -2
  16. package/dist/features/proposals/containerNormalization.d.ts +8 -0
  17. package/dist/features/proposals/containerNormalization.js +112 -0
  18. package/dist/{payload/proposalData.d.ts → features/proposals/data.d.ts} +5 -10
  19. package/dist/features/proposals/data.js +113 -0
  20. package/dist/features/proposals/fieldNormalization.d.ts +2 -0
  21. package/dist/features/proposals/fieldNormalization.js +69 -0
  22. package/dist/features/proposals/normalizationTypes.d.ts +31 -0
  23. package/dist/features/proposals/normalizationTypes.js +1 -0
  24. package/dist/features/proposals/optionInference.d.ts +13 -0
  25. package/dist/features/proposals/optionInference.js +46 -0
  26. package/dist/features/proposals/recordNormalization.d.ts +6 -0
  27. package/dist/features/proposals/recordNormalization.js +78 -0
  28. package/dist/features/proposals/relationshipNormalization.d.ts +8 -0
  29. package/dist/features/proposals/relationshipNormalization.js +71 -0
  30. package/dist/{ai/proposalRepair.d.ts → features/proposals/repair.d.ts} +1 -1
  31. package/dist/{ai/proposalSigning.d.ts → features/proposals/signing.d.ts} +2 -5
  32. package/dist/{ai/proposalSigning.js → features/proposals/signing.js} +1 -1
  33. package/dist/features/proposals/target.d.ts +52 -0
  34. package/dist/features/proposals/target.js +125 -0
  35. package/dist/features/proposals/types.d.ts +49 -0
  36. package/dist/features/proposals/types.js +1 -0
  37. package/dist/features/proposals/validation.d.ts +12 -0
  38. package/dist/features/proposals/validation.js +26 -0
  39. package/dist/features/providers/requestContext.d.ts +53 -0
  40. package/dist/features/providers/requestContext.js +90 -0
  41. package/dist/{ai/providerRuntime.d.ts → features/providers/runtime.d.ts} +1 -1
  42. package/dist/{ai/providerRuntime.js → features/providers/runtime.js} +1 -1
  43. package/dist/features/schema/context.d.ts +7 -0
  44. package/dist/features/schema/context.js +11 -0
  45. package/dist/{payload/schemaContext.d.ts → features/schema/description.d.ts} +6 -33
  46. package/dist/{payload/schemaContext.js → features/schema/description.js} +3 -207
  47. package/dist/{payload/toolFieldSelection.d.ts → features/schema/fieldSelection.d.ts} +1 -1
  48. package/dist/{payload/toolFieldSelection.js → features/schema/fieldSelection.js} +2 -2
  49. package/dist/features/schema/mentionDocuments.d.ts +15 -0
  50. package/dist/features/schema/mentionDocuments.js +50 -0
  51. package/dist/features/schema/mentions.d.ts +18 -0
  52. package/dist/features/schema/mentions.js +100 -0
  53. package/dist/{payload/normalizeData.js → features/schema/normalize.js} +1 -1
  54. package/dist/features/schema/registry.d.ts +30 -0
  55. package/dist/features/schema/registry.js +73 -0
  56. package/dist/{payload → features/schema}/toolSchemas.d.ts +1 -1
  57. package/dist/{payload → features/schema}/toolSchemas.js +1 -1
  58. package/dist/{ai → features}/sensitiveData.js +1 -1
  59. package/dist/{ai → features}/tokenUsage.d.ts +1 -2
  60. package/dist/handlers/apply/audit.d.ts +42 -0
  61. package/dist/handlers/apply/audit.js +150 -0
  62. package/dist/handlers/apply/operations.d.ts +18 -0
  63. package/dist/handlers/apply/operations.js +377 -0
  64. package/dist/handlers/apply/types.d.ts +33 -0
  65. package/dist/handlers/apply/types.js +1 -0
  66. package/dist/handlers/apply/validation.d.ts +45 -0
  67. package/dist/handlers/apply/validation.js +125 -0
  68. package/dist/handlers/applyActionHandler.d.ts +1 -6
  69. package/dist/handlers/applyActionHandler.js +13 -757
  70. package/dist/handlers/auditLogHandler.js +4 -8
  71. package/dist/handlers/chat/intent.d.ts +32 -0
  72. package/dist/handlers/chat/intent.js +155 -0
  73. package/dist/handlers/chat/prompt.d.ts +20 -0
  74. package/dist/handlers/chat/prompt.js +55 -0
  75. package/dist/handlers/chat/proposalTools.d.ts +96 -0
  76. package/dist/handlers/chat/proposalTools.js +451 -0
  77. package/dist/handlers/chat/streaming.d.ts +38 -0
  78. package/dist/handlers/chat/streaming.js +136 -0
  79. package/dist/handlers/chat/toolFactory.d.ts +79 -0
  80. package/dist/handlers/chat/toolFactory.js +156 -0
  81. package/dist/handlers/chat/types.d.ts +57 -0
  82. package/dist/handlers/chat/types.js +1 -0
  83. package/dist/handlers/chatHandler.d.ts +1 -44
  84. package/dist/handlers/chatHandler.js +186 -1162
  85. package/dist/handlers/generateFieldHandler.d.ts +3 -8
  86. package/dist/handlers/generateFieldHandler.js +26 -97
  87. package/dist/handlers/http.d.ts +17 -0
  88. package/dist/handlers/http.js +21 -0
  89. package/dist/handlers/mediaUploadHandler.js +12 -34
  90. package/dist/handlers/mentionSuggestionHandler.d.ts +1 -1
  91. package/dist/handlers/mentionSuggestionHandler.js +6 -10
  92. package/dist/handlers/proposalDiffHandler.d.ts +1 -1
  93. package/dist/handlers/proposalDiffHandler.js +72 -195
  94. package/dist/handlers/translateDocumentHandler.d.ts +3 -8
  95. package/dist/handlers/translateDocumentHandler.js +26 -82
  96. package/dist/index.d.ts +4 -30
  97. package/dist/index.js +31 -421
  98. package/dist/plugin/admin.d.ts +30 -0
  99. package/dist/plugin/admin.js +155 -0
  100. package/dist/plugin/collections.d.ts +3 -0
  101. package/dist/plugin/collections.js +169 -0
  102. package/dist/plugin/endpoints.d.ts +23 -0
  103. package/dist/plugin/endpoints.js +84 -0
  104. package/dist/plugin/options.d.ts +4 -0
  105. package/dist/plugin/options.js +13 -0
  106. package/dist/plugin/types.d.ts +28 -0
  107. package/dist/plugin/types.js +1 -0
  108. package/dist/{payload/shared.d.ts → utils/data.d.ts} +2 -12
  109. package/package.json +1 -1
  110. package/dist/payload/proposalData.js +0 -578
  111. /package/dist/{payload → features}/collectionPermissions.d.ts +0 -0
  112. /package/dist/{payload → features/content}/documentTranslation.d.ts +0 -0
  113. /package/dist/{payload → features/content}/documentTranslation.js +0 -0
  114. /package/dist/{payload/textFieldGeneration.d.ts → features/content/fieldGeneration.d.ts} +0 -0
  115. /package/dist/{payload/textFieldGeneration.js → features/content/fieldGeneration.js} +0 -0
  116. /package/dist/{ai → features}/promptCaching.js +0 -0
  117. /package/dist/{ai/proposalRepair.js → features/proposals/repair.js} +0 -0
  118. /package/dist/{ai/providerOptions.d.ts → features/providers/options.d.ts} +0 -0
  119. /package/dist/{ai/providerOptions.js → features/providers/options.js} +0 -0
  120. /package/dist/{payload/normalizeData.d.ts → features/schema/normalize.d.ts} +0 -0
  121. /package/dist/{ai → features}/sensitiveData.d.ts +0 -0
  122. /package/dist/{ai → features}/tokenUsage.js +0 -0
  123. /package/dist/{payload/shared.js → utils/data.js} +0 -0
  124. /package/dist/{payload → utils}/logging.d.ts +0 -0
  125. /package/dist/{payload → utils}/logging.js +0 -0
@@ -1,17 +1,4 @@
1
- export type ActionProposal = {
2
- _aiSignature?: {
3
- expiresAt: string;
4
- value: string;
5
- };
6
- action: "create" | "delete" | "update" | "updateGlobal";
7
- collection?: string;
8
- data?: Record<string, unknown>;
9
- id?: string;
10
- label: string;
11
- locale?: string;
12
- localizedData?: Record<string, Record<string, unknown>>;
13
- slug?: string;
14
- };
1
+ import type { ActionProposal } from "../../features/proposals/types.js";
15
2
  type ActionToastProps = {
16
3
  apiRoute: string;
17
4
  description?: string;
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Button, CheckIcon, SwapIcon } from "@payloadcms/ui";
3
3
  import { formatAdminURL } from "payload/shared";
4
4
  import { useState } from "react";
5
- import { redactSensitiveData } from "../../ai/sensitiveData.js";
5
+ import { redactSensitiveData } from "../../features/sensitiveData.js";
6
6
  import { DiffDialog } from "../diff-dialog/DiffDialog.js";
7
7
  import { TextShimmer } from "../text-shimmer/TextShimmer.js";
8
8
  import styles from "./ActionToast.module.css";
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { Button, ChevronIcon, useAuth, useConfig, useDocumentForm, useDocumentInfo, useLocale } from "@payloadcms/ui";
4
4
  import { formatAdminURL } from "payload/shared";
5
5
  import { useEffect, useRef, useState } from "react";
6
- import { mergeData } from "../../payload/shared.js";
6
+ import { mergeData } from "../../utils/data.js";
7
7
  import { ActionToast } from "../action-toast/ActionToast.js";
8
8
  import { useAIChatStream } from "../hooks/useAIChatStream.js";
9
9
  import { useAISettings } from "../hooks/useAISettings.js";
@@ -1,5 +1,4 @@
1
- import type { ActionProposal } from "../action-toast/ActionToast.js";
2
- import { type ProposalDiff } from "../diff-dialog/DiffDialog.js";
1
+ import type { ActionProposal, ActionProposalReference, ProposalDiff } from "../../features/proposals/types.js";
3
2
  export type AppliedChange = {
4
3
  action?: ActionProposal["action"] | null;
5
4
  additions: number;
@@ -22,7 +21,7 @@ export type AppliedChange = {
22
21
  export type ActiveDiff = {
23
22
  change: AppliedChange | null;
24
23
  diff: ProposalDiff;
25
- proposal: ActionProposal;
24
+ proposal: ActionProposalReference;
26
25
  };
27
26
  declare const AuditLogList: () => import("react").JSX.Element;
28
27
  export default AuditLogList;
@@ -1,14 +1,10 @@
1
- import type { ActionProposal } from "../action-toast/ActionToast.js";
1
+ import type { ActionProposalReference, ProposalDiff } from "../../features/proposals/types.js";
2
2
  import type { AppliedChange } from "../audit-log-list/AuditLogList.js";
3
- export type ProposalDiff = {
4
- after: unknown;
5
- before: unknown;
6
- };
7
3
  type DiffDialogProps = {
8
4
  change?: AppliedChange;
9
5
  diff: ProposalDiff;
10
6
  onClose: () => void;
11
- proposal: ActionProposal;
7
+ proposal: ActionProposalReference;
12
8
  tokenUsage?: {
13
9
  inputTokens?: number;
14
10
  outputTokens?: number;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, useState } from "react";
3
- import { getJSONLineKey } from "../../payload/shared.js";
3
+ import { getJSONLineKey } from "../../utils/data.js";
4
4
  import styles from "./DiffDialog.module.css";
5
5
  import { Button } from "@payloadcms/ui";
6
6
  const formatDiffValue = (value)=>{
@@ -1,5 +1,5 @@
1
1
  import { RefObject } from "react";
2
- import type { ActionProposal } from "../action-toast/ActionToast.js";
2
+ import type { ActionProposal } from "../../features/proposals/types.js";
3
3
  import type { Mention } from "./useMentions.js";
4
4
  export type TokenUsage = {
5
5
  inputTokens?: number;
@@ -1,4 +1,4 @@
1
- import type { AIProviderProfile } from "../../ai/providerOptions.js";
1
+ import type { AIProviderProfile } from "../../features/providers/options.js";
2
2
  interface UseAISettingsOptions {
3
3
  adminUserSlug?: string;
4
4
  apiRoute: string;
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { formatAdminURL } from "payload/shared";
3
3
  import { useEffect, useState } from "react";
4
- import { isAbortError } from "../../payload/shared.js";
4
+ import { isAbortError } from "../../utils/data.js";
5
5
  const managedSelectionKey = "payload-ai:selected-managed-model";
6
6
  const getStoredModelKey = (provider)=>`payload-ai:selected-model:${provider}`;
7
7
  const getStoredModel = (provider)=>{
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { formatAdminURL } from "payload/shared";
3
3
  import { useEffect, useState } from "react";
4
- import { isAbortError } from "../../payload/shared.js";
4
+ import { isAbortError } from "../../utils/data.js";
5
5
  const fetchDocumentMentionSuggestions = async ({ apiRoute, collectionSlug, query, signal })=>{
6
6
  const res = await fetch(formatAdminURL({
7
7
  apiRoute,
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
- import { getSerializableLabel, isInternalCollection } from "../../payload/shared.js";
3
+ import { getSerializableLabel, isInternalCollection } from "../../utils/data.js";
4
4
  import { createBadgePrefix, getTextNodeAtOffset, replaceTextRangeWithBadge } from "../ai-input/badge.js";
5
5
  import { useDocumentMentionSuggestions } from "../hooks/useDocumentMentionSuggestions.js";
6
6
  const isMentionBoundary = (character)=>{
@@ -1,4 +1,4 @@
1
- import { type AIProviderProfile } from "../../ai/providerOptions.js";
1
+ import { type AIProviderProfile } from "../../features/providers/options.js";
2
2
  type MediaConfig = {
3
3
  acceptedMimeTypes?: string[];
4
4
  collectionSlug: string;
@@ -22,11 +22,11 @@ export declare const usePluginConfig: (config: {
22
22
  aiModelConfig: {
23
23
  defaults: Record<"claude" | "google" | "mistral" | "openai" | "openrouter", string>;
24
24
  providers: {
25
- claude: import("../../ai/providerOptions.js").AIProviderModelOption[];
26
- google: import("../../ai/providerOptions.js").AIProviderModelOption[];
27
- mistral: import("../../ai/providerOptions.js").AIProviderModelOption[];
28
- openai: import("../../ai/providerOptions.js").AIProviderModelOption[];
29
- openrouter: import("../../ai/providerOptions.js").AIProviderModelOption[];
25
+ claude: import("../../features/providers/options.js").AIProviderModelOption[];
26
+ google: import("../../features/providers/options.js").AIProviderModelOption[];
27
+ mistral: import("../../features/providers/options.js").AIProviderModelOption[];
28
+ openai: import("../../features/providers/options.js").AIProviderModelOption[];
29
+ openrouter: import("../../features/providers/options.js").AIProviderModelOption[];
30
30
  };
31
31
  };
32
32
  defaultLocale: string | undefined;
@@ -1,5 +1,5 @@
1
1
  import { useMemo } from "react";
2
- import { getLegacyAIProviderProfiles, getResolvedAIModelConfig } from "../../ai/providerOptions.js";
2
+ import { getLegacyAIProviderProfiles, getResolvedAIModelConfig } from "../../features/providers/options.js";
3
3
  export const usePluginConfig = (config)=>{
4
4
  const pluginConfig = config.admin?.custom?.payloadAiPlugin;
5
5
  const aiModelConfig = useMemo(()=>getResolvedAIModelConfig(pluginConfig?.models), [
@@ -1,4 +1,4 @@
1
- import { isInternalCollection } from "./shared.js";
1
+ import { isInternalCollection } from "../utils/data.js";
2
2
  const allActions = [
3
3
  "create",
4
4
  "delete",
@@ -1,10 +1,10 @@
1
1
  import type { SystemModelMessage } from "ai";
2
- import type { AIProvider } from "./providerOptions.js";
2
+ import type { AIProvider } from "./providers/options.js";
3
3
  type PromptCacheContext = {
4
4
  cacheable: Record<string, unknown>[];
5
5
  dynamic: Record<string, unknown>[];
6
6
  };
7
- type PromptCacheProviderOptions = NonNullable<SystemModelMessage["providerOptions"]>;
7
+ export type PromptCacheProviderOptions = NonNullable<SystemModelMessage["providerOptions"]>;
8
8
  export declare const splitPromptCacheContext: (mentionContext: Record<string, unknown>[]) => PromptCacheContext;
9
9
  export declare const createCachedSystemMessages: ({ cacheableContext, cacheableInstructions, dynamicInstructions, enabled, provider, staticInstructions, }: {
10
10
  cacheableContext: Record<string, unknown>[];
@@ -0,0 +1,8 @@
1
+ import type { NormalizeFieldValueArgs } from "./normalizationTypes.js";
2
+ export declare const containerNormalizers: {
3
+ array: ({ field, normalizeRecord, path, value, ...state }: NormalizeFieldValueArgs) => (Record<string, unknown> | undefined)[] | undefined;
4
+ blocks: ({ field, normalizeRecord, path, value, ...state }: NormalizeFieldValueArgs) => ({
5
+ blockType: string;
6
+ } | undefined)[] | undefined;
7
+ group: ({ field, normalizeRecord, path, value, ...state }: NormalizeFieldValueArgs) => Record<string, unknown> | undefined;
8
+ };
@@ -0,0 +1,112 @@
1
+ import { isRecord } from "../../utils/data.js";
2
+ const normalizeGroup = ({ field, normalizeRecord, path, value, ...state })=>{
3
+ if (!isRecord(value)) {
4
+ state.issues.push({
5
+ code: "invalid_container",
6
+ message: "Group fields must be objects.",
7
+ path
8
+ });
9
+ return undefined;
10
+ }
11
+ return normalizeRecord({
12
+ ...state,
13
+ allowSafeFallback: false,
14
+ enforceRequiredChildren: state.mode === "create" || state.enforceRequiredChildren,
15
+ fields: field.fields || [],
16
+ path,
17
+ value
18
+ });
19
+ };
20
+ const getArrayItemLabelField = (field)=>{
21
+ const childFields = (field.fields || []).filter((childField)=>Boolean(childField.name));
22
+ return childFields.find((childField)=>childField.name === "label") || childFields.find((childField)=>childField.name === "title") || childFields.find((childField)=>childField.name === "name") || childFields.find((childField)=>childField.name === "value") || childFields[0];
23
+ };
24
+ const normalizeArray = ({ field, normalizeRecord, path, value, ...state })=>{
25
+ if (!Array.isArray(value)) {
26
+ state.issues.push({
27
+ code: "invalid_array",
28
+ message: "Array fields must use an array value.",
29
+ path
30
+ });
31
+ return undefined;
32
+ }
33
+ const itemLabelField = getArrayItemLabelField(field);
34
+ return value.map((item, index)=>{
35
+ const itemPath = `${path}.${index}`;
36
+ if (!isRecord(item) && !itemLabelField) {
37
+ state.issues.push({
38
+ code: "invalid_array",
39
+ message: "Array items must be objects for this field.",
40
+ path: itemPath
41
+ });
42
+ return undefined;
43
+ }
44
+ return normalizeRecord({
45
+ ...state,
46
+ allowSafeFallback: false,
47
+ enforceRequiredChildren: true,
48
+ fields: field.fields || [],
49
+ path: itemPath,
50
+ value: isRecord(item) ? item : {
51
+ [itemLabelField.name]: item
52
+ }
53
+ });
54
+ });
55
+ };
56
+ const getBlockType = (item)=>typeof item.blockType === "string" ? item.blockType : typeof item.type === "string" ? item.type : typeof item.slug === "string" ? item.slug : null;
57
+ const normalizeBlocks = ({ field, normalizeRecord, path, value, ...state })=>{
58
+ if (!Array.isArray(value)) {
59
+ state.issues.push({
60
+ code: "invalid_blocks",
61
+ message: "Blocks fields must use an array value.",
62
+ path
63
+ });
64
+ return undefined;
65
+ }
66
+ return value.map((item, index)=>{
67
+ const itemPath = `${path}.${index}`;
68
+ if (!isRecord(item)) {
69
+ state.issues.push({
70
+ code: "invalid_blocks",
71
+ message: "Each block entry must be an object.",
72
+ path: itemPath
73
+ });
74
+ return undefined;
75
+ }
76
+ const blockType = getBlockType(item);
77
+ if (!blockType) {
78
+ state.issues.push({
79
+ code: "invalid_block_type",
80
+ message: "Each block entry must include blockType.",
81
+ path: itemPath
82
+ });
83
+ return undefined;
84
+ }
85
+ const block = field.blocks?.find((candidate)=>candidate.slug === blockType);
86
+ if (!block) {
87
+ state.issues.push({
88
+ code: "invalid_block_type",
89
+ message: `Unknown block type "${blockType}".`,
90
+ path: `${itemPath}.blockType`
91
+ });
92
+ return undefined;
93
+ }
94
+ const { blockType: _blockType, slug: _slug, type: _type, ...blockData } = item;
95
+ return {
96
+ ...normalizeRecord({
97
+ ...state,
98
+ allowSafeFallback: false,
99
+ enforceRequiredChildren: true,
100
+ fields: block.fields || [],
101
+ path: itemPath,
102
+ value: blockData
103
+ }),
104
+ blockType
105
+ };
106
+ });
107
+ };
108
+ export const containerNormalizers = {
109
+ array: normalizeArray,
110
+ blocks: normalizeBlocks,
111
+ group: normalizeGroup
112
+ };
@@ -1,11 +1,7 @@
1
- import { type LocalizedDataInput } from "./shared.js";
2
- import { type CollectionConfig, type FieldConfig } from "./normalizeData.js";
3
- type ProposalMode = "create" | "update";
4
- export type ProposalValidationIssue = {
5
- code: "empty_localized_data" | "invalid_array" | "invalid_block_type" | "invalid_blocks" | "invalid_checkbox" | "invalid_container" | "invalid_date" | "invalid_option" | "invalid_relationship" | "missing_required_field" | "non_localized_field_in_secondary_locale" | "unknown_field";
6
- message: string;
7
- path: string;
8
- };
1
+ import type { LocalizedDataInput } from "./types.js";
2
+ import { type CollectionConfig, type FieldConfig } from "../schema/normalize.js";
3
+ import type { ProposalMode, ProposalValidationIssue } from "./normalizationTypes.js";
4
+ export type { ProposalValidationIssue } from "./normalizationTypes.js";
9
5
  export type PreparedProposalData = {
10
6
  coercedFields: string[];
11
7
  data?: Record<string, unknown>;
@@ -20,7 +16,7 @@ type PrepareProposalDataArgs = {
20
16
  localizedData?: LocalizedDataInput;
21
17
  mode: ProposalMode;
22
18
  };
23
- export declare const prepareProposalWriteData: ({ collectionConfig, data, inferenceText, label, localizedData, mode }: PrepareProposalDataArgs) => PreparedProposalData;
19
+ export declare const prepareProposalWriteData: ({ collectionConfig, data, inferenceText, label, localizedData, mode, }: PrepareProposalDataArgs) => PreparedProposalData;
24
20
  export declare const applyLocalizedRequiredFallbackToPreparedData: ({ fallbackSource, fields, preparedData, }: {
25
21
  fallbackSource: Record<string, unknown>;
26
22
  fields: readonly FieldConfig[];
@@ -28,4 +24,3 @@ export declare const applyLocalizedRequiredFallbackToPreparedData: ({ fallbackSo
28
24
  }) => {
29
25
  [x: string]: unknown;
30
26
  };
31
- export {};
@@ -0,0 +1,113 @@
1
+ import { isRecord } from "../../utils/data.js";
2
+ import { getSchemaFields, normalizeAuthData } from "../schema/normalize.js";
3
+ import { prepareSingleLocaleData } from "./recordNormalization.js";
4
+ export const prepareProposalWriteData = ({ collectionConfig, data, inferenceText, label, localizedData, mode })=>{
5
+ const schemaFields = getSchemaFields(collectionConfig);
6
+ const titleFieldName = typeof collectionConfig?.admin?.useAsTitle === "string" ? collectionConfig.admin.useAsTitle : undefined;
7
+ const issues = [];
8
+ const coercedFields = [];
9
+ if (localizedData) {
10
+ const localeEntries = Object.entries(localizedData);
11
+ if (localeEntries.length === 0) {
12
+ issues.push({
13
+ code: "empty_localized_data",
14
+ message: "localizedData must contain at least one locale entry.",
15
+ path: "localizedData"
16
+ });
17
+ return {
18
+ coercedFields,
19
+ issues,
20
+ localizedData: {}
21
+ };
22
+ }
23
+ const normalizedLocalizedData = {};
24
+ for (const [locale, localeValue] of localeEntries){
25
+ if (!isRecord(localeValue)) {
26
+ issues.push({
27
+ code: "invalid_container",
28
+ message: "Each locale entry must be an object.",
29
+ path: `localizedData.${locale}`
30
+ });
31
+ continue;
32
+ }
33
+ normalizedLocalizedData[locale] = prepareSingleLocaleData({
34
+ coercedFields,
35
+ data: localeValue,
36
+ fields: schemaFields,
37
+ inferenceText,
38
+ issues,
39
+ label,
40
+ mode,
41
+ titleFieldName
42
+ });
43
+ }
44
+ const [firstLocale] = localeEntries[0] || [];
45
+ if (firstLocale) {
46
+ for (const field of schemaFields.filter((candidate)=>Boolean(candidate.name))){
47
+ if (field.localized) continue;
48
+ for (const [locale, localeValue] of Object.entries(normalizedLocalizedData)){
49
+ if (locale === firstLocale || localeValue[field.name] === undefined) continue;
50
+ issues.push({
51
+ code: "non_localized_field_in_secondary_locale",
52
+ message: "Non-localized fields may only be set in the first locale entry.",
53
+ path: `localizedData.${locale}.${field.name}`
54
+ });
55
+ }
56
+ }
57
+ }
58
+ return {
59
+ coercedFields,
60
+ issues,
61
+ localizedData: Object.fromEntries(Object.entries(normalizedLocalizedData).map(([locale, localeValue])=>[
62
+ locale,
63
+ normalizeAuthData(collectionConfig, {
64
+ coercedFields: [],
65
+ data: localeValue,
66
+ droppedFields: []
67
+ }).data
68
+ ]))
69
+ };
70
+ }
71
+ const normalizedData = prepareSingleLocaleData({
72
+ coercedFields,
73
+ data: data || {},
74
+ fields: schemaFields,
75
+ inferenceText,
76
+ issues,
77
+ label,
78
+ mode,
79
+ titleFieldName
80
+ });
81
+ const authNormalized = normalizeAuthData(collectionConfig, {
82
+ coercedFields: [],
83
+ data: normalizedData,
84
+ droppedFields: []
85
+ });
86
+ coercedFields.push(...authNormalized.coercedFields);
87
+ return {
88
+ coercedFields,
89
+ data: authNormalized.data,
90
+ issues
91
+ };
92
+ };
93
+ export const applyLocalizedRequiredFallbackToPreparedData = ({ fallbackSource, fields, preparedData })=>{
94
+ const mergedData = {
95
+ ...preparedData
96
+ };
97
+ for (const field of fields.filter((candidate)=>Boolean(candidate.name))){
98
+ if (field.localized && field.required && mergedData[field.name] === undefined && fallbackSource[field.name] !== undefined) {
99
+ mergedData[field.name] = fallbackSource[field.name];
100
+ continue;
101
+ }
102
+ if (field.type === "group" && field.fields?.length && isRecord(fallbackSource[field.name])) {
103
+ const currentValue = isRecord(mergedData[field.name]) ? mergedData[field.name] : {};
104
+ const nestedValue = applyLocalizedRequiredFallbackToPreparedData({
105
+ fallbackSource: fallbackSource[field.name],
106
+ fields: field.fields,
107
+ preparedData: currentValue
108
+ });
109
+ if (Object.keys(nestedValue).length > 0) mergedData[field.name] = nestedValue;
110
+ }
111
+ }
112
+ return mergedData;
113
+ };
@@ -0,0 +1,2 @@
1
+ import type { NormalizeFieldValueArgs } from "./normalizationTypes.js";
2
+ export declare const normalizeFieldValue: (args: NormalizeFieldValueArgs) => unknown;
@@ -0,0 +1,69 @@
1
+ import { createLexicalText } from "../schema/normalize.js";
2
+ import { containerNormalizers } from "./containerNormalization.js";
3
+ import { getFieldOptionValues, inferOptionValueFromText } from "./optionInference.js";
4
+ import { normalizeRelationshipValue } from "./relationshipNormalization.js";
5
+ const normalizeCheckbox = ({ issues, path, value })=>{
6
+ if (typeof value === "boolean") return value;
7
+ if (value === "true") return true;
8
+ if (value === "false") return false;
9
+ issues.push({
10
+ code: "invalid_checkbox",
11
+ message: "Checkbox fields must use true or false.",
12
+ path
13
+ });
14
+ return undefined;
15
+ };
16
+ const normalizeOption = ({ coercedFields, field, inferenceText, issues, label, path, value })=>{
17
+ const optionValues = getFieldOptionValues(field);
18
+ const stringValue = value === null ? "" : String(value);
19
+ const inferredOptionValue = inferOptionValueFromText({
20
+ field,
21
+ requireFieldMention: true,
22
+ text: inferenceText || label
23
+ });
24
+ if (inferredOptionValue && optionValues.includes(inferredOptionValue)) {
25
+ if (stringValue !== inferredOptionValue) coercedFields.push(path);
26
+ return inferredOptionValue;
27
+ }
28
+ if (stringValue && optionValues.includes(stringValue)) return stringValue;
29
+ issues.push({
30
+ code: "invalid_option",
31
+ message: `Field must use one of: ${optionValues.join(", ")}.`,
32
+ path
33
+ });
34
+ return undefined;
35
+ };
36
+ const normalizeText = ({ value })=>value === null ? value : String(value);
37
+ const normalizeDate = ({ issues, path, value })=>{
38
+ const date = new Date(String(value));
39
+ if (!Number.isNaN(date.getTime())) return date.toISOString();
40
+ issues.push({
41
+ code: "invalid_date",
42
+ message: "Date fields must use a valid date value.",
43
+ path
44
+ });
45
+ return undefined;
46
+ };
47
+ const normalizeRelationship = ({ field, issues, path, value })=>normalizeRelationshipValue({
48
+ field,
49
+ issues,
50
+ path,
51
+ value
52
+ });
53
+ const fieldNormalizers = {
54
+ ...containerNormalizers,
55
+ checkbox: normalizeCheckbox,
56
+ date: normalizeDate,
57
+ email: normalizeText,
58
+ radio: normalizeOption,
59
+ relationship: normalizeRelationship,
60
+ richText: ({ value })=>createLexicalText(value),
61
+ select: normalizeOption,
62
+ text: normalizeText,
63
+ textarea: normalizeText,
64
+ upload: normalizeRelationship
65
+ };
66
+ export const normalizeFieldValue = (args)=>{
67
+ const normalizer = args.field.type ? fieldNormalizers[args.field.type] : undefined;
68
+ return normalizer ? normalizer(args) : args.value;
69
+ };
@@ -0,0 +1,31 @@
1
+ import type { FieldConfig } from "../schema/normalize.js";
2
+ export type ProposalMode = "create" | "update";
3
+ export type ProposalValidationIssue = {
4
+ code: "empty_localized_data" | "invalid_array" | "invalid_block_type" | "invalid_blocks" | "invalid_checkbox" | "invalid_container" | "invalid_date" | "invalid_option" | "invalid_relationship" | "missing_required_field" | "non_localized_field_in_secondary_locale" | "unknown_field";
5
+ message: string;
6
+ path: string;
7
+ };
8
+ export type NormalizationState = {
9
+ coercedFields: string[];
10
+ inferenceText?: string;
11
+ issues: ProposalValidationIssue[];
12
+ label: string;
13
+ mode: ProposalMode;
14
+ titleFieldName?: string;
15
+ };
16
+ export type NormalizeRecordArgs = NormalizationState & {
17
+ allowSafeFallback: boolean;
18
+ enforceRequiredChildren: boolean;
19
+ fields: readonly FieldConfig[];
20
+ path: string;
21
+ value: Record<string, unknown>;
22
+ };
23
+ type NormalizeRecord = (args: NormalizeRecordArgs) => Record<string, unknown>;
24
+ export type NormalizeFieldValueArgs = NormalizationState & {
25
+ enforceRequiredChildren: boolean;
26
+ field: FieldConfig;
27
+ normalizeRecord: NormalizeRecord;
28
+ path: string;
29
+ value: unknown;
30
+ };
31
+ export {};
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,13 @@
1
+ import type { FieldConfig } from "../schema/normalize.js";
2
+ export declare const getFieldOptionValues: (field: FieldConfig) => string[];
3
+ export declare const inferOptionValueFromText: ({ field, requireFieldMention, text, }: {
4
+ field: FieldConfig;
5
+ requireFieldMention?: boolean;
6
+ text?: string;
7
+ }) => string | undefined;
8
+ export declare const getDefaultFieldValue: ({ field, inferenceText, label, titleFieldName, }: {
9
+ field: FieldConfig;
10
+ inferenceText?: string;
11
+ label: string;
12
+ titleFieldName?: string;
13
+ }) => {} | null | undefined;
@@ -0,0 +1,46 @@
1
+ import { getOptionValue, getSafeProposalLabel } from "../../utils/data.js";
2
+ const isTitleLikeField = (field, titleFieldName)=>{
3
+ const normalizedName = field.name?.toLowerCase();
4
+ return Boolean(field.name && (field.name === titleFieldName || normalizedName === "title" || normalizedName === "name" || normalizedName === "label" || normalizedName === "headline"));
5
+ };
6
+ export const getFieldOptionValues = (field)=>(field.options || []).map((option)=>getOptionValue(option)).filter((option)=>Boolean(option));
7
+ const escapeRegExp = (value)=>value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8
+ const getFieldNameVariants = (fieldName)=>{
9
+ if (!fieldName) return [];
10
+ const spacedName = fieldName.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").toLowerCase();
11
+ return Array.from(new Set([
12
+ fieldName.toLowerCase(),
13
+ spacedName
14
+ ])).filter(Boolean);
15
+ };
16
+ export const inferOptionValueFromText = ({ field, requireFieldMention = false, text })=>{
17
+ const normalizedText = text?.trim().toLowerCase();
18
+ if (!normalizedText) return undefined;
19
+ if (requireFieldMention && !getFieldNameVariants(field.name).some((variant)=>new RegExp(`\\b${escapeRegExp(variant)}\\b`, "i").test(normalizedText))) {
20
+ return undefined;
21
+ }
22
+ return getFieldOptionValues(field).sort((left, right)=>right.length - left.length).find((optionValue)=>new RegExp(`\\b${escapeRegExp(optionValue.toLowerCase())}\\b`, "i").test(normalizedText));
23
+ };
24
+ export const getDefaultFieldValue = ({ field, inferenceText, label, titleFieldName })=>{
25
+ if (field.name === "_status") return "draft";
26
+ if (field.type === "checkbox") return false;
27
+ if (field.type === "select" || field.type === "radio") {
28
+ const inferredOptionValue = inferOptionValueFromText({
29
+ field,
30
+ requireFieldMention: true,
31
+ text: inferenceText || label
32
+ });
33
+ if (inferredOptionValue !== undefined) return inferredOptionValue;
34
+ if (field.defaultValue !== undefined) return field.defaultValue;
35
+ return getOptionValue(field.options?.[0]);
36
+ }
37
+ if (field.defaultValue !== undefined) return field.defaultValue;
38
+ if ([
39
+ "email",
40
+ "text",
41
+ "textarea"
42
+ ].includes(field.type || "") && isTitleLikeField(field, titleFieldName)) {
43
+ return getSafeProposalLabel(label);
44
+ }
45
+ return undefined;
46
+ };
@@ -0,0 +1,6 @@
1
+ import { type FieldConfig } from "../schema/normalize.js";
2
+ import type { NormalizationState } from "./normalizationTypes.js";
3
+ export declare const prepareSingleLocaleData: ({ data, fields, ...state }: NormalizationState & {
4
+ data: Record<string, unknown>;
5
+ fields: readonly FieldConfig[];
6
+ }) => Record<string, unknown>;