@mvriu5/payload-ai 1.6.2 → 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.
- package/dist/components/action-toast/ActionToast.d.ts +1 -14
- package/dist/components/action-toast/ActionToast.js +1 -1
- package/dist/components/ai-input/AIInput.js +1 -1
- package/dist/components/audit-log-list/AuditLogList.d.ts +2 -3
- package/dist/components/diff-dialog/DiffDialog.d.ts +2 -6
- package/dist/components/diff-dialog/DiffDialog.js +1 -1
- package/dist/components/generate-field/GenerateField.js +2 -0
- package/dist/components/hooks/useAIChatStream.d.ts +1 -1
- package/dist/components/hooks/useAISettings.d.ts +1 -1
- package/dist/components/hooks/useAISettings.js +1 -1
- package/dist/components/hooks/useDocumentMentionSuggestions.js +1 -1
- package/dist/components/hooks/useMentions.js +1 -1
- package/dist/components/hooks/usePluginConfig.d.ts +6 -6
- package/dist/components/hooks/usePluginConfig.js +1 -1
- package/dist/{payload → features}/collectionPermissions.js +1 -1
- package/dist/{payload/textFieldGeneration.d.ts → features/content/fieldGeneration.d.ts} +4 -0
- package/dist/{payload/textFieldGeneration.js → features/content/fieldGeneration.js} +19 -3
- package/dist/{ai → features}/promptCaching.d.ts +2 -2
- package/dist/features/proposals/containerNormalization.d.ts +8 -0
- package/dist/features/proposals/containerNormalization.js +112 -0
- package/dist/{payload/proposalData.d.ts → features/proposals/data.d.ts} +5 -10
- package/dist/features/proposals/data.js +113 -0
- package/dist/features/proposals/fieldNormalization.d.ts +2 -0
- package/dist/features/proposals/fieldNormalization.js +69 -0
- package/dist/features/proposals/normalizationTypes.d.ts +31 -0
- package/dist/features/proposals/normalizationTypes.js +1 -0
- package/dist/features/proposals/optionInference.d.ts +13 -0
- package/dist/features/proposals/optionInference.js +46 -0
- package/dist/features/proposals/recordNormalization.d.ts +6 -0
- package/dist/features/proposals/recordNormalization.js +78 -0
- package/dist/features/proposals/relationshipNormalization.d.ts +8 -0
- package/dist/features/proposals/relationshipNormalization.js +71 -0
- package/dist/{ai/proposalRepair.d.ts → features/proposals/repair.d.ts} +1 -1
- package/dist/{ai/proposalSigning.d.ts → features/proposals/signing.d.ts} +2 -5
- package/dist/{ai/proposalSigning.js → features/proposals/signing.js} +1 -1
- package/dist/features/proposals/target.d.ts +52 -0
- package/dist/features/proposals/target.js +125 -0
- package/dist/features/proposals/types.d.ts +49 -0
- package/dist/features/proposals/types.js +1 -0
- package/dist/features/proposals/validation.d.ts +12 -0
- package/dist/features/proposals/validation.js +26 -0
- package/dist/features/providers/requestContext.d.ts +53 -0
- package/dist/features/providers/requestContext.js +90 -0
- package/dist/{ai/providerRuntime.d.ts → features/providers/runtime.d.ts} +1 -1
- package/dist/{ai/providerRuntime.js → features/providers/runtime.js} +1 -1
- package/dist/features/schema/context.d.ts +7 -0
- package/dist/features/schema/context.js +11 -0
- package/dist/{payload/schemaContext.d.ts → features/schema/description.d.ts} +6 -33
- package/dist/{payload/schemaContext.js → features/schema/description.js} +3 -207
- package/dist/{payload/toolFieldSelection.d.ts → features/schema/fieldSelection.d.ts} +1 -1
- package/dist/{payload/toolFieldSelection.js → features/schema/fieldSelection.js} +2 -2
- package/dist/features/schema/mentionDocuments.d.ts +15 -0
- package/dist/features/schema/mentionDocuments.js +50 -0
- package/dist/features/schema/mentions.d.ts +18 -0
- package/dist/features/schema/mentions.js +100 -0
- package/dist/{payload/normalizeData.js → features/schema/normalize.js} +1 -1
- package/dist/features/schema/registry.d.ts +30 -0
- package/dist/features/schema/registry.js +73 -0
- package/dist/{payload → features/schema}/toolSchemas.d.ts +1 -1
- package/dist/{payload → features/schema}/toolSchemas.js +1 -1
- package/dist/{ai → features}/sensitiveData.js +1 -1
- package/dist/{ai → features}/tokenUsage.d.ts +1 -2
- package/dist/handlers/apply/audit.d.ts +42 -0
- package/dist/handlers/apply/audit.js +150 -0
- package/dist/handlers/apply/operations.d.ts +18 -0
- package/dist/handlers/apply/operations.js +377 -0
- package/dist/handlers/apply/types.d.ts +33 -0
- package/dist/handlers/apply/types.js +1 -0
- package/dist/handlers/apply/validation.d.ts +45 -0
- package/dist/handlers/apply/validation.js +125 -0
- package/dist/handlers/applyActionHandler.d.ts +1 -6
- package/dist/handlers/applyActionHandler.js +13 -757
- package/dist/handlers/auditLogHandler.js +4 -8
- package/dist/handlers/chat/intent.d.ts +32 -0
- package/dist/handlers/chat/intent.js +155 -0
- package/dist/handlers/chat/prompt.d.ts +20 -0
- package/dist/handlers/chat/prompt.js +55 -0
- package/dist/handlers/chat/proposalTools.d.ts +96 -0
- package/dist/handlers/chat/proposalTools.js +451 -0
- package/dist/handlers/chat/streaming.d.ts +38 -0
- package/dist/handlers/chat/streaming.js +136 -0
- package/dist/handlers/chat/toolFactory.d.ts +79 -0
- package/dist/handlers/chat/toolFactory.js +156 -0
- package/dist/handlers/chat/types.d.ts +57 -0
- package/dist/handlers/chat/types.js +1 -0
- package/dist/handlers/chatHandler.d.ts +1 -44
- package/dist/handlers/chatHandler.js +186 -1162
- package/dist/handlers/generateFieldHandler.d.ts +3 -8
- package/dist/handlers/generateFieldHandler.js +71 -99
- package/dist/handlers/http.d.ts +17 -0
- package/dist/handlers/http.js +21 -0
- package/dist/handlers/mediaUploadHandler.js +12 -34
- package/dist/handlers/mentionSuggestionHandler.d.ts +1 -1
- package/dist/handlers/mentionSuggestionHandler.js +6 -10
- package/dist/handlers/proposalDiffHandler.d.ts +1 -1
- package/dist/handlers/proposalDiffHandler.js +72 -195
- package/dist/handlers/translateDocumentHandler.d.ts +3 -8
- package/dist/handlers/translateDocumentHandler.js +26 -82
- package/dist/index.d.ts +4 -30
- package/dist/index.js +31 -421
- package/dist/plugin/admin.d.ts +30 -0
- package/dist/plugin/admin.js +155 -0
- package/dist/plugin/collections.d.ts +3 -0
- package/dist/plugin/collections.js +169 -0
- package/dist/plugin/endpoints.d.ts +23 -0
- package/dist/plugin/endpoints.js +84 -0
- package/dist/plugin/options.d.ts +4 -0
- package/dist/plugin/options.js +13 -0
- package/dist/plugin/types.d.ts +28 -0
- package/dist/plugin/types.js +1 -0
- package/dist/{payload/shared.d.ts → utils/data.d.ts} +2 -12
- package/package.json +1 -1
- package/dist/payload/proposalData.js +0 -578
- /package/dist/{payload → features}/collectionPermissions.d.ts +0 -0
- /package/dist/{payload → features/content}/documentTranslation.d.ts +0 -0
- /package/dist/{payload → features/content}/documentTranslation.js +0 -0
- /package/dist/{ai → features}/promptCaching.js +0 -0
- /package/dist/{ai/proposalRepair.js → features/proposals/repair.js} +0 -0
- /package/dist/{ai/providerOptions.d.ts → features/providers/options.d.ts} +0 -0
- /package/dist/{ai/providerOptions.js → features/providers/options.js} +0 -0
- /package/dist/{payload/normalizeData.d.ts → features/schema/normalize.d.ts} +0 -0
- /package/dist/{ai → features}/sensitiveData.d.ts +0 -0
- /package/dist/{ai → features}/tokenUsage.js +0 -0
- /package/dist/{payload/shared.js → utils/data.js} +0 -0
- /package/dist/{payload → utils}/logging.d.ts +0 -0
- /package/dist/{payload → utils}/logging.js +0 -0
|
@@ -1,17 +1,4 @@
|
|
|
1
|
-
|
|
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 "../../
|
|
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 "../../
|
|
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 "
|
|
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:
|
|
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 {
|
|
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:
|
|
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 "../../
|
|
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)=>{
|
|
@@ -55,6 +55,7 @@ const GenerateField = ({ field, generationFieldKey, generationFieldType, path, r
|
|
|
55
55
|
scope.type,
|
|
56
56
|
scope.slug,
|
|
57
57
|
generationFieldKey,
|
|
58
|
+
path,
|
|
58
59
|
locale.code,
|
|
59
60
|
context
|
|
60
61
|
]);
|
|
@@ -63,6 +64,7 @@ const GenerateField = ({ field, generationFieldKey, generationFieldType, path, r
|
|
|
63
64
|
body: {
|
|
64
65
|
context,
|
|
65
66
|
fieldKey: generationFieldKey,
|
|
67
|
+
fieldPath: path,
|
|
66
68
|
locale: locale.code,
|
|
67
69
|
scope
|
|
68
70
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RefObject } from "react";
|
|
2
|
-
import type { ActionProposal } from "
|
|
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,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 "../../
|
|
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 "../../
|
|
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 "../../
|
|
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 "../../
|
|
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("../../
|
|
26
|
-
google: import("../../
|
|
27
|
-
mistral: import("../../
|
|
28
|
-
openai: import("../../
|
|
29
|
-
openrouter: import("../../
|
|
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 "../../
|
|
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), [
|
|
@@ -3,6 +3,10 @@ type GeneratableField = Extract<Field, {
|
|
|
3
3
|
type: "json" | "richText" | "text" | "textarea";
|
|
4
4
|
}>;
|
|
5
5
|
export type TextGenerationFieldContext = {
|
|
6
|
+
block?: {
|
|
7
|
+
label: string;
|
|
8
|
+
slug: string;
|
|
9
|
+
};
|
|
6
10
|
description?: string;
|
|
7
11
|
fieldType: GeneratableField["type"];
|
|
8
12
|
hasMany: boolean;
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
const generateFieldComponent = "@mvriu5/payload-ai/client#GenerateField";
|
|
2
|
+
const getTranslatedLabel = (label, fallback)=>{
|
|
3
|
+
if (typeof label === "string") return label;
|
|
4
|
+
if (label && typeof label === "object") {
|
|
5
|
+
const translatedLabel = Object.values(label).find((value)=>typeof value === "string");
|
|
6
|
+
if (typeof translatedLabel === "string") return translatedLabel;
|
|
7
|
+
}
|
|
8
|
+
return fallback;
|
|
9
|
+
};
|
|
2
10
|
const getLabel = (field)=>{
|
|
3
|
-
if (typeof field.label === "string") return field.label;
|
|
4
11
|
if (field.label && typeof field.label === "object") {
|
|
5
12
|
const label = Object.values(field.label).find((value)=>typeof value === "string");
|
|
6
13
|
if (typeof label === "string") return label;
|
|
7
14
|
}
|
|
8
|
-
return field.name;
|
|
15
|
+
return getTranslatedLabel(field.label, field.name);
|
|
9
16
|
};
|
|
10
17
|
const getDescription = (field)=>{
|
|
11
18
|
const description = field.admin?.description;
|
|
@@ -31,11 +38,12 @@ const addGenerateComponent = (field, key)=>{
|
|
|
31
38
|
}
|
|
32
39
|
];
|
|
33
40
|
};
|
|
34
|
-
const visitFields = ({ fields, parentKey, result })=>{
|
|
41
|
+
const visitFields = ({ block, fields, parentKey, result })=>{
|
|
35
42
|
for (const field of fields){
|
|
36
43
|
if (field.type === "tabs") {
|
|
37
44
|
for (const tab of field.tabs){
|
|
38
45
|
visitFields({
|
|
46
|
+
block,
|
|
39
47
|
fields: tab.fields,
|
|
40
48
|
parentKey: "name" in tab ? `${parentKey}.${tab.name}` : parentKey,
|
|
41
49
|
result
|
|
@@ -47,6 +55,10 @@ const visitFields = ({ fields, parentKey, result })=>{
|
|
|
47
55
|
for (const block of field.blocks){
|
|
48
56
|
if (typeof block !== "object") continue;
|
|
49
57
|
visitFields({
|
|
58
|
+
block: {
|
|
59
|
+
label: getTranslatedLabel(block.labels?.singular, block.slug),
|
|
60
|
+
slug: block.slug
|
|
61
|
+
},
|
|
50
62
|
fields: block.fields,
|
|
51
63
|
parentKey: `${parentKey}.${field.name}.${block.slug}`,
|
|
52
64
|
result
|
|
@@ -57,6 +69,7 @@ const visitFields = ({ fields, parentKey, result })=>{
|
|
|
57
69
|
if ("fields" in field && Array.isArray(field.fields)) {
|
|
58
70
|
const nextParent = "name" in field && field.name ? `${parentKey}.${field.name}` : parentKey;
|
|
59
71
|
visitFields({
|
|
72
|
+
block,
|
|
60
73
|
fields: field.fields,
|
|
61
74
|
parentKey: nextParent,
|
|
62
75
|
result
|
|
@@ -73,6 +86,9 @@ const visitFields = ({ fields, parentKey, result })=>{
|
|
|
73
86
|
if (generatableField.admin?.hidden) continue;
|
|
74
87
|
addGenerateComponent(generatableField, key);
|
|
75
88
|
result.push({
|
|
89
|
+
...block ? {
|
|
90
|
+
block
|
|
91
|
+
} : {},
|
|
76
92
|
description: getDescription(generatableField),
|
|
77
93
|
fieldType: generatableField.type,
|
|
78
94
|
hasMany: "hasMany" in generatableField ? Boolean(generatableField.hasMany) : false,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { SystemModelMessage } from "ai";
|
|
2
|
-
import type { AIProvider } from "./
|
|
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 {
|
|
2
|
-
import { type CollectionConfig, type FieldConfig } from "
|
|
3
|
-
type ProposalMode
|
|
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,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 { };
|