payload-mcp-toolkit 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +133 -0
- package/dist/__tests__/introspection.test.js +364 -0
- package/dist/__tests__/introspection.test.js.map +1 -0
- package/dist/__tests__/url-validator.test.js +326 -0
- package/dist/__tests__/url-validator.test.js.map +1 -0
- package/dist/draft-workflow.d.ts +60 -0
- package/dist/draft-workflow.js +93 -0
- package/dist/draft-workflow.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +142 -0
- package/dist/index.js.map +1 -0
- package/dist/introspection.d.ts +23 -0
- package/dist/introspection.js +238 -0
- package/dist/introspection.js.map +1 -0
- package/dist/prompts.d.ts +21 -0
- package/dist/prompts.js +215 -0
- package/dist/prompts.js.map +1 -0
- package/dist/rate-limiter.d.ts +25 -0
- package/dist/rate-limiter.js +51 -0
- package/dist/rate-limiter.js.map +1 -0
- package/dist/resources.d.ts +18 -0
- package/dist/resources.js +77 -0
- package/dist/resources.js.map +1 -0
- package/dist/tools/compose-helpers.d.ts +117 -0
- package/dist/tools/compose-helpers.js +236 -0
- package/dist/tools/compose-helpers.js.map +1 -0
- package/dist/tools/compose-layout.d.ts +139 -0
- package/dist/tools/compose-layout.js +61 -0
- package/dist/tools/compose-layout.js.map +1 -0
- package/dist/tools/patch-layout.d.ts +107 -0
- package/dist/tools/patch-layout.js +123 -0
- package/dist/tools/patch-layout.js.map +1 -0
- package/dist/tools/publish-draft.d.ts +24 -0
- package/dist/tools/publish-draft.js +69 -0
- package/dist/tools/publish-draft.js.map +1 -0
- package/dist/tools/resolve-reference.d.ts +31 -0
- package/dist/tools/resolve-reference.js +169 -0
- package/dist/tools/resolve-reference.js.map +1 -0
- package/dist/tools/safe-delete.d.ts +37 -0
- package/dist/tools/safe-delete.js +161 -0
- package/dist/tools/safe-delete.js.map +1 -0
- package/dist/tools/schedule-publish.d.ts +49 -0
- package/dist/tools/schedule-publish.js +120 -0
- package/dist/tools/schedule-publish.js.map +1 -0
- package/dist/tools/search-content.d.ts +43 -0
- package/dist/tools/search-content.js +210 -0
- package/dist/tools/search-content.js.map +1 -0
- package/dist/tools/update-document.d.ts +32 -0
- package/dist/tools/update-document.js +114 -0
- package/dist/tools/update-document.js.map +1 -0
- package/dist/tools/upload-media.d.ts +26 -0
- package/dist/tools/upload-media.js +115 -0
- package/dist/tools/upload-media.js.map +1 -0
- package/dist/tools/versions.d.ts +50 -0
- package/dist/tools/versions.js +159 -0
- package/dist/tools/versions.js.map +1 -0
- package/dist/types.d.ts +118 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/url-validator.d.ts +36 -0
- package/dist/url-validator.js +222 -0
- package/dist/url-validator.js.map +1 -0
- package/package.json +85 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** Schema for a single leaf block within a section */ export const leafBlockSchema = z.object({
|
|
3
|
+
blockType: z.string(),
|
|
4
|
+
fields: z.record(z.string(), z.unknown()).optional()
|
|
5
|
+
});
|
|
6
|
+
/** Schema for a single section in a layout */ export const sectionSchema = z.object({
|
|
7
|
+
sectionType: z.string(),
|
|
8
|
+
config: z.record(z.string(), z.unknown()).optional(),
|
|
9
|
+
/** Leaf blocks for single-content sections */ content: z.array(leafBlockSchema).optional(),
|
|
10
|
+
/** Left column leaf blocks (for two-column sections) */ leftColumn: z.array(leafBlockSchema).optional(),
|
|
11
|
+
/** Right column leaf blocks (for two-column sections) */ rightColumn: z.array(leafBlockSchema).optional(),
|
|
12
|
+
/** Flat field values for fixed sections */ fields: z.record(z.string(), z.unknown()).optional()
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* Validate and compose an array of section inputs into block JSON.
|
|
16
|
+
* Returns either composed blocks or a list of validation errors.
|
|
17
|
+
*/ export function composeSections(sections, catalog) {
|
|
18
|
+
const sectionMap = new Map(catalog.sections.map((s)=>[
|
|
19
|
+
s.slug,
|
|
20
|
+
s
|
|
21
|
+
]));
|
|
22
|
+
const allLeafSlugs = catalog.leaves.map((l)=>l.slug);
|
|
23
|
+
const blocks = [];
|
|
24
|
+
const errors = [];
|
|
25
|
+
for(let i = 0; i < sections.length; i++){
|
|
26
|
+
const result = composeSingleSection(sections[i], i, sectionMap, allLeafSlugs);
|
|
27
|
+
if (result.errors.length > 0) {
|
|
28
|
+
errors.push(...result.errors);
|
|
29
|
+
} else if (result.block) {
|
|
30
|
+
blocks.push(result.block);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
blocks,
|
|
35
|
+
errors
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** Build a hint payload describing the available section/leaf vocabulary. */ export function buildHint(catalog) {
|
|
39
|
+
return {
|
|
40
|
+
availableSections: catalog.sections.map((s)=>s.slug),
|
|
41
|
+
availableLeaves: catalog.leaves.map((l)=>l.slug),
|
|
42
|
+
sectionDetails: catalog.sections.map((s)=>({
|
|
43
|
+
slug: s.slug,
|
|
44
|
+
nestingType: s.nestingType,
|
|
45
|
+
acceptedLeaves: s.acceptedLeafSlugs,
|
|
46
|
+
maxRows: s.maxRows
|
|
47
|
+
}))
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Apply a list operation against an existing array of blocks.
|
|
52
|
+
* `full` always replaces; the rest preserve the existing array.
|
|
53
|
+
*/ export function applyOperation(composedBlocks, operation, insertIndex, existingLayout) {
|
|
54
|
+
if (operation === 'full' || !existingLayout) {
|
|
55
|
+
return composedBlocks;
|
|
56
|
+
}
|
|
57
|
+
const existing = [
|
|
58
|
+
...existingLayout
|
|
59
|
+
];
|
|
60
|
+
if (operation === 'append') {
|
|
61
|
+
return [
|
|
62
|
+
...existing,
|
|
63
|
+
...composedBlocks
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
if (operation === 'prepend') {
|
|
67
|
+
return [
|
|
68
|
+
...composedBlocks,
|
|
69
|
+
...existing
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
if (operation === 'insertAt') {
|
|
73
|
+
if (insertIndex === undefined || insertIndex < 0 || insertIndex > existing.length) {
|
|
74
|
+
return [
|
|
75
|
+
...existing,
|
|
76
|
+
...composedBlocks
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
existing.splice(insertIndex, 0, ...composedBlocks);
|
|
80
|
+
return existing;
|
|
81
|
+
}
|
|
82
|
+
if (operation === 'replaceAt') {
|
|
83
|
+
if (insertIndex === undefined || insertIndex < 0 || insertIndex >= existing.length) {
|
|
84
|
+
return existing;
|
|
85
|
+
}
|
|
86
|
+
existing.splice(insertIndex, composedBlocks.length, ...composedBlocks);
|
|
87
|
+
return existing;
|
|
88
|
+
}
|
|
89
|
+
return composedBlocks;
|
|
90
|
+
}
|
|
91
|
+
function composeSingleSection(section, index, sectionMap, allLeafSlugs) {
|
|
92
|
+
const sectionSchema = sectionMap.get(section.sectionType);
|
|
93
|
+
if (!sectionSchema) {
|
|
94
|
+
return {
|
|
95
|
+
block: null,
|
|
96
|
+
errors: [
|
|
97
|
+
{
|
|
98
|
+
sectionIndex: index,
|
|
99
|
+
message: `Unknown section type: "${section.sectionType}"`,
|
|
100
|
+
validAlternatives: [
|
|
101
|
+
...sectionMap.keys()
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (sectionSchema.nestingType === 'fixed') {
|
|
108
|
+
return composeFixedSection(section, index, sectionSchema);
|
|
109
|
+
}
|
|
110
|
+
// Detect two-column layout by leftColumn block field
|
|
111
|
+
const hasDualColumns = sectionSchema.fields.some((f)=>f.name === 'leftColumn' && f.type === 'blocks');
|
|
112
|
+
if (hasDualColumns) {
|
|
113
|
+
return composeTwoColumnSection(section, index, sectionSchema, allLeafSlugs);
|
|
114
|
+
}
|
|
115
|
+
return composeContentSection(section, index, sectionSchema, allLeafSlugs);
|
|
116
|
+
}
|
|
117
|
+
function composeFixedSection(section, index, schema) {
|
|
118
|
+
const errors = validateRequiredFields(section.fields ?? {}, schema.fields, index);
|
|
119
|
+
if (errors.length > 0) return {
|
|
120
|
+
block: null,
|
|
121
|
+
errors
|
|
122
|
+
};
|
|
123
|
+
return {
|
|
124
|
+
block: {
|
|
125
|
+
blockType: schema.slug,
|
|
126
|
+
...section.fields ?? {},
|
|
127
|
+
...section.config ?? {}
|
|
128
|
+
},
|
|
129
|
+
errors: []
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function composeContentSection(section, index, schema, allLeafSlugs) {
|
|
133
|
+
const content = section.content ?? [];
|
|
134
|
+
const errors = validateLeafBlocks(content, schema, index, 'content', allLeafSlugs);
|
|
135
|
+
if (schema.maxRows && content.length > schema.maxRows) {
|
|
136
|
+
errors.push({
|
|
137
|
+
sectionIndex: index,
|
|
138
|
+
message: `Section "${schema.slug}" allows at most ${schema.maxRows} content block(s), got ${content.length}`
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if (errors.length > 0) return {
|
|
142
|
+
block: null,
|
|
143
|
+
errors
|
|
144
|
+
};
|
|
145
|
+
const blockFieldName = findBlockFieldName(schema, 'content');
|
|
146
|
+
const composedLeaves = content.map((leaf)=>({
|
|
147
|
+
blockType: leaf.blockType,
|
|
148
|
+
...leaf.fields ?? {}
|
|
149
|
+
}));
|
|
150
|
+
return {
|
|
151
|
+
block: {
|
|
152
|
+
blockType: schema.slug,
|
|
153
|
+
[blockFieldName]: composedLeaves,
|
|
154
|
+
...section.config ?? {},
|
|
155
|
+
...section.fields ?? {}
|
|
156
|
+
},
|
|
157
|
+
errors: []
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function composeTwoColumnSection(section, index, schema, allLeafSlugs) {
|
|
161
|
+
const leftColumn = section.leftColumn ?? [];
|
|
162
|
+
const rightColumn = section.rightColumn ?? [];
|
|
163
|
+
const errors = [
|
|
164
|
+
...validateLeafBlocks(leftColumn, schema, index, 'leftColumn', allLeafSlugs),
|
|
165
|
+
...validateLeafBlocks(rightColumn, schema, index, 'rightColumn', allLeafSlugs)
|
|
166
|
+
];
|
|
167
|
+
if (errors.length > 0) return {
|
|
168
|
+
block: null,
|
|
169
|
+
errors
|
|
170
|
+
};
|
|
171
|
+
return {
|
|
172
|
+
block: {
|
|
173
|
+
blockType: schema.slug,
|
|
174
|
+
leftColumn: leftColumn.map((leaf)=>({
|
|
175
|
+
blockType: leaf.blockType,
|
|
176
|
+
...leaf.fields ?? {}
|
|
177
|
+
})),
|
|
178
|
+
rightColumn: rightColumn.map((leaf)=>({
|
|
179
|
+
blockType: leaf.blockType,
|
|
180
|
+
...leaf.fields ?? {}
|
|
181
|
+
})),
|
|
182
|
+
...section.config ?? {}
|
|
183
|
+
},
|
|
184
|
+
errors: []
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function validateLeafBlocks(leaves, sectionSchema, sectionIndex, columnName, allLeafSlugs) {
|
|
188
|
+
const errors = [];
|
|
189
|
+
for(let i = 0; i < leaves.length; i++){
|
|
190
|
+
const leaf = leaves[i];
|
|
191
|
+
if (!allLeafSlugs.includes(leaf.blockType)) {
|
|
192
|
+
errors.push({
|
|
193
|
+
sectionIndex,
|
|
194
|
+
message: `Unknown leaf block type "${leaf.blockType}" at ${columnName}[${i}]`,
|
|
195
|
+
validAlternatives: allLeafSlugs
|
|
196
|
+
});
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (!sectionSchema.acceptedLeafSlugs.includes(leaf.blockType)) {
|
|
200
|
+
errors.push({
|
|
201
|
+
sectionIndex,
|
|
202
|
+
message: `Leaf block "${leaf.blockType}" is not allowed in section "${sectionSchema.slug}" at ${columnName}[${i}]`,
|
|
203
|
+
validAlternatives: sectionSchema.acceptedLeafSlugs
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return errors;
|
|
208
|
+
}
|
|
209
|
+
function validateRequiredFields(values, fieldSchemas, sectionIndex) {
|
|
210
|
+
const errors = [];
|
|
211
|
+
for (const field of fieldSchemas){
|
|
212
|
+
if (!field.required) continue;
|
|
213
|
+
if ([
|
|
214
|
+
'id',
|
|
215
|
+
'blockName',
|
|
216
|
+
'blockType'
|
|
217
|
+
].includes(field.name)) continue;
|
|
218
|
+
const value = values[field.name];
|
|
219
|
+
if (value === undefined || value === null || value === '') {
|
|
220
|
+
errors.push({
|
|
221
|
+
sectionIndex,
|
|
222
|
+
message: `Required field "${field.name}" is missing`
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return errors;
|
|
227
|
+
}
|
|
228
|
+
function findBlockFieldName(schema, defaultName) {
|
|
229
|
+
const hasDefault = schema.fields.some((f)=>f.name === defaultName && f.type === 'blocks');
|
|
230
|
+
if (hasDefault) return defaultName;
|
|
231
|
+
const blocksField = schema.fields.find((f)=>f.type === 'blocks');
|
|
232
|
+
if (blocksField) return blocksField.name;
|
|
233
|
+
return defaultName;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
//# sourceMappingURL=compose-helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/tools/compose-helpers.ts"],"sourcesContent":["import { z } from 'zod'\nimport type { BlockCatalog, SectionBlockSchema } from '../types'\n\n/** Schema for a single leaf block within a section */\nexport const leafBlockSchema = z.object({\n blockType: z.string(),\n fields: z.record(z.string(), z.unknown()).optional(),\n})\n\n/** Schema for a single section in a layout */\nexport const sectionSchema = z.object({\n sectionType: z.string(),\n config: z.record(z.string(), z.unknown()).optional(),\n /** Leaf blocks for single-content sections */\n content: z.array(leafBlockSchema).optional(),\n /** Left column leaf blocks (for two-column sections) */\n leftColumn: z.array(leafBlockSchema).optional(),\n /** Right column leaf blocks (for two-column sections) */\n rightColumn: z.array(leafBlockSchema).optional(),\n /** Flat field values for fixed sections */\n fields: z.record(z.string(), z.unknown()).optional(),\n})\n\nexport type LeafBlockInput = z.infer<typeof leafBlockSchema>\nexport type SectionInput = z.infer<typeof sectionSchema>\n\nexport interface ValidationError {\n sectionIndex: number\n message: string\n validAlternatives?: string[]\n}\n\nexport interface ComposeResult {\n blocks: Record<string, unknown>[]\n errors: ValidationError[]\n}\n\n/**\n * Validate and compose an array of section inputs into block JSON.\n * Returns either composed blocks or a list of validation errors.\n */\nexport function composeSections(\n sections: SectionInput[],\n catalog: BlockCatalog,\n): ComposeResult {\n const sectionMap = new Map(catalog.sections.map((s) => [s.slug, s]))\n const allLeafSlugs = catalog.leaves.map((l) => l.slug)\n\n const blocks: Record<string, unknown>[] = []\n const errors: ValidationError[] = []\n\n for (let i = 0; i < sections.length; i++) {\n const result = composeSingleSection(sections[i], i, sectionMap, allLeafSlugs)\n if (result.errors.length > 0) {\n errors.push(...result.errors)\n } else if (result.block) {\n blocks.push(result.block)\n }\n }\n\n return { blocks, errors }\n}\n\n/** Build a hint payload describing the available section/leaf vocabulary. */\nexport function buildHint(catalog: BlockCatalog) {\n return {\n availableSections: catalog.sections.map((s) => s.slug),\n availableLeaves: catalog.leaves.map((l) => l.slug),\n sectionDetails: catalog.sections.map((s) => ({\n slug: s.slug,\n nestingType: s.nestingType,\n acceptedLeaves: s.acceptedLeafSlugs,\n maxRows: s.maxRows,\n })),\n }\n}\n\n/**\n * Apply a list operation against an existing array of blocks.\n * `full` always replaces; the rest preserve the existing array.\n */\nexport function applyOperation(\n composedBlocks: Record<string, unknown>[],\n operation: 'full' | 'append' | 'prepend' | 'insertAt' | 'replaceAt',\n insertIndex: number | undefined,\n existingLayout: Record<string, unknown>[] | undefined,\n): Record<string, unknown>[] {\n if (operation === 'full' || !existingLayout) {\n return composedBlocks\n }\n\n const existing = [...existingLayout]\n\n if (operation === 'append') {\n return [...existing, ...composedBlocks]\n }\n\n if (operation === 'prepend') {\n return [...composedBlocks, ...existing]\n }\n\n if (operation === 'insertAt') {\n if (insertIndex === undefined || insertIndex < 0 || insertIndex > existing.length) {\n return [...existing, ...composedBlocks]\n }\n existing.splice(insertIndex, 0, ...composedBlocks)\n return existing\n }\n\n if (operation === 'replaceAt') {\n if (insertIndex === undefined || insertIndex < 0 || insertIndex >= existing.length) {\n return existing\n }\n existing.splice(insertIndex, composedBlocks.length, ...composedBlocks)\n return existing\n }\n\n return composedBlocks\n}\n\n// ─── Internal composition logic ──────────────────────────────────\n\ninterface SingleResult {\n block: Record<string, unknown> | null\n errors: ValidationError[]\n}\n\nfunction composeSingleSection(\n section: SectionInput,\n index: number,\n sectionMap: Map<string, SectionBlockSchema>,\n allLeafSlugs: string[],\n): SingleResult {\n const sectionSchema = sectionMap.get(section.sectionType)\n\n if (!sectionSchema) {\n return {\n block: null,\n errors: [\n {\n sectionIndex: index,\n message: `Unknown section type: \"${section.sectionType}\"`,\n validAlternatives: [...sectionMap.keys()],\n },\n ],\n }\n }\n\n if (sectionSchema.nestingType === 'fixed') {\n return composeFixedSection(section, index, sectionSchema)\n }\n\n // Detect two-column layout by leftColumn block field\n const hasDualColumns = sectionSchema.fields.some(\n (f) => f.name === 'leftColumn' && f.type === 'blocks',\n )\n\n if (hasDualColumns) {\n return composeTwoColumnSection(section, index, sectionSchema, allLeafSlugs)\n }\n\n return composeContentSection(section, index, sectionSchema, allLeafSlugs)\n}\n\nfunction composeFixedSection(\n section: SectionInput,\n index: number,\n schema: SectionBlockSchema,\n): SingleResult {\n const errors = validateRequiredFields(section.fields ?? {}, schema.fields, index)\n if (errors.length > 0) return { block: null, errors }\n\n return {\n block: {\n blockType: schema.slug,\n ...(section.fields ?? {}),\n ...(section.config ?? {}),\n },\n errors: [],\n }\n}\n\nfunction composeContentSection(\n section: SectionInput,\n index: number,\n schema: SectionBlockSchema,\n allLeafSlugs: string[],\n): SingleResult {\n const content = section.content ?? []\n const errors = validateLeafBlocks(content, schema, index, 'content', allLeafSlugs)\n\n if (schema.maxRows && content.length > schema.maxRows) {\n errors.push({\n sectionIndex: index,\n message: `Section \"${schema.slug}\" allows at most ${schema.maxRows} content block(s), got ${content.length}`,\n })\n }\n\n if (errors.length > 0) return { block: null, errors }\n\n const blockFieldName = findBlockFieldName(schema, 'content')\n const composedLeaves = content.map((leaf) => ({\n blockType: leaf.blockType,\n ...(leaf.fields ?? {}),\n }))\n\n return {\n block: {\n blockType: schema.slug,\n [blockFieldName]: composedLeaves,\n ...(section.config ?? {}),\n ...(section.fields ?? {}),\n },\n errors: [],\n }\n}\n\nfunction composeTwoColumnSection(\n section: SectionInput,\n index: number,\n schema: SectionBlockSchema,\n allLeafSlugs: string[],\n): SingleResult {\n const leftColumn = section.leftColumn ?? []\n const rightColumn = section.rightColumn ?? []\n const errors: ValidationError[] = [\n ...validateLeafBlocks(leftColumn, schema, index, 'leftColumn', allLeafSlugs),\n ...validateLeafBlocks(rightColumn, schema, index, 'rightColumn', allLeafSlugs),\n ]\n\n if (errors.length > 0) return { block: null, errors }\n\n return {\n block: {\n blockType: schema.slug,\n leftColumn: leftColumn.map((leaf) => ({\n blockType: leaf.blockType,\n ...(leaf.fields ?? {}),\n })),\n rightColumn: rightColumn.map((leaf) => ({\n blockType: leaf.blockType,\n ...(leaf.fields ?? {}),\n })),\n ...(section.config ?? {}),\n },\n errors: [],\n }\n}\n\nfunction validateLeafBlocks(\n leaves: LeafBlockInput[],\n sectionSchema: SectionBlockSchema,\n sectionIndex: number,\n columnName: string,\n allLeafSlugs: string[],\n): ValidationError[] {\n const errors: ValidationError[] = []\n\n for (let i = 0; i < leaves.length; i++) {\n const leaf = leaves[i]\n\n if (!allLeafSlugs.includes(leaf.blockType)) {\n errors.push({\n sectionIndex,\n message: `Unknown leaf block type \"${leaf.blockType}\" at ${columnName}[${i}]`,\n validAlternatives: allLeafSlugs,\n })\n continue\n }\n\n if (!sectionSchema.acceptedLeafSlugs.includes(leaf.blockType)) {\n errors.push({\n sectionIndex,\n message: `Leaf block \"${leaf.blockType}\" is not allowed in section \"${sectionSchema.slug}\" at ${columnName}[${i}]`,\n validAlternatives: sectionSchema.acceptedLeafSlugs,\n })\n }\n }\n\n return errors\n}\n\nfunction validateRequiredFields(\n values: Record<string, unknown>,\n fieldSchemas: Array<{ name: string; type: string; required?: boolean }>,\n sectionIndex: number,\n): ValidationError[] {\n const errors: ValidationError[] = []\n\n for (const field of fieldSchemas) {\n if (!field.required) continue\n if (['id', 'blockName', 'blockType'].includes(field.name)) continue\n\n const value = values[field.name]\n if (value === undefined || value === null || value === '') {\n errors.push({\n sectionIndex,\n message: `Required field \"${field.name}\" is missing`,\n })\n }\n }\n\n return errors\n}\n\nfunction findBlockFieldName(\n schema: SectionBlockSchema,\n defaultName: string,\n): string {\n const hasDefault = schema.fields.some((f) => f.name === defaultName && f.type === 'blocks')\n if (hasDefault) return defaultName\n\n const blocksField = schema.fields.find((f) => f.type === 'blocks')\n if (blocksField) return blocksField.name\n\n return defaultName\n}\n"],"names":["z","leafBlockSchema","object","blockType","string","fields","record","unknown","optional","sectionSchema","sectionType","config","content","array","leftColumn","rightColumn","composeSections","sections","catalog","sectionMap","Map","map","s","slug","allLeafSlugs","leaves","l","blocks","errors","i","length","result","composeSingleSection","push","block","buildHint","availableSections","availableLeaves","sectionDetails","nestingType","acceptedLeaves","acceptedLeafSlugs","maxRows","applyOperation","composedBlocks","operation","insertIndex","existingLayout","existing","undefined","splice","section","index","get","sectionIndex","message","validAlternatives","keys","composeFixedSection","hasDualColumns","some","f","name","type","composeTwoColumnSection","composeContentSection","schema","validateRequiredFields","validateLeafBlocks","blockFieldName","findBlockFieldName","composedLeaves","leaf","columnName","includes","values","fieldSchemas","field","required","value","defaultName","hasDefault","blocksField","find"],"mappings":"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"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { BlockCatalog } from '../types';
|
|
3
|
+
import { type SectionInput } from './compose-helpers';
|
|
4
|
+
/**
|
|
5
|
+
* Create the composePageLayout MCP tool from the introspected block catalog.
|
|
6
|
+
*
|
|
7
|
+
* Validates and composes a layout array from natural section/leaf inputs.
|
|
8
|
+
* Returns the composed JSON ready to be written to a doc's `layout` field —
|
|
9
|
+
* use updateDocument or patchLayout to actually persist the result.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createComposeLayoutTool(catalog: BlockCatalog): {
|
|
12
|
+
name: string;
|
|
13
|
+
description: string;
|
|
14
|
+
parameters: z.ZodObject<{
|
|
15
|
+
sections: z.ZodArray<z.ZodObject<{
|
|
16
|
+
sectionType: z.ZodString;
|
|
17
|
+
config: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
18
|
+
content: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
19
|
+
blockType: z.ZodString;
|
|
20
|
+
fields: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
21
|
+
}, "strip", z.ZodTypeAny, {
|
|
22
|
+
blockType: string;
|
|
23
|
+
fields?: Record<string, unknown> | undefined;
|
|
24
|
+
}, {
|
|
25
|
+
blockType: string;
|
|
26
|
+
fields?: Record<string, unknown> | undefined;
|
|
27
|
+
}>, "many">>;
|
|
28
|
+
leftColumn: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
29
|
+
blockType: z.ZodString;
|
|
30
|
+
fields: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
31
|
+
}, "strip", z.ZodTypeAny, {
|
|
32
|
+
blockType: string;
|
|
33
|
+
fields?: Record<string, unknown> | undefined;
|
|
34
|
+
}, {
|
|
35
|
+
blockType: string;
|
|
36
|
+
fields?: Record<string, unknown> | undefined;
|
|
37
|
+
}>, "many">>;
|
|
38
|
+
rightColumn: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
39
|
+
blockType: z.ZodString;
|
|
40
|
+
fields: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
41
|
+
}, "strip", z.ZodTypeAny, {
|
|
42
|
+
blockType: string;
|
|
43
|
+
fields?: Record<string, unknown> | undefined;
|
|
44
|
+
}, {
|
|
45
|
+
blockType: string;
|
|
46
|
+
fields?: Record<string, unknown> | undefined;
|
|
47
|
+
}>, "many">>;
|
|
48
|
+
fields: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
49
|
+
}, "strip", z.ZodTypeAny, {
|
|
50
|
+
sectionType: string;
|
|
51
|
+
fields?: Record<string, unknown> | undefined;
|
|
52
|
+
config?: Record<string, unknown> | undefined;
|
|
53
|
+
content?: {
|
|
54
|
+
blockType: string;
|
|
55
|
+
fields?: Record<string, unknown> | undefined;
|
|
56
|
+
}[] | undefined;
|
|
57
|
+
leftColumn?: {
|
|
58
|
+
blockType: string;
|
|
59
|
+
fields?: Record<string, unknown> | undefined;
|
|
60
|
+
}[] | undefined;
|
|
61
|
+
rightColumn?: {
|
|
62
|
+
blockType: string;
|
|
63
|
+
fields?: Record<string, unknown> | undefined;
|
|
64
|
+
}[] | undefined;
|
|
65
|
+
}, {
|
|
66
|
+
sectionType: string;
|
|
67
|
+
fields?: Record<string, unknown> | undefined;
|
|
68
|
+
config?: Record<string, unknown> | undefined;
|
|
69
|
+
content?: {
|
|
70
|
+
blockType: string;
|
|
71
|
+
fields?: Record<string, unknown> | undefined;
|
|
72
|
+
}[] | undefined;
|
|
73
|
+
leftColumn?: {
|
|
74
|
+
blockType: string;
|
|
75
|
+
fields?: Record<string, unknown> | undefined;
|
|
76
|
+
}[] | undefined;
|
|
77
|
+
rightColumn?: {
|
|
78
|
+
blockType: string;
|
|
79
|
+
fields?: Record<string, unknown> | undefined;
|
|
80
|
+
}[] | undefined;
|
|
81
|
+
}>, "many">;
|
|
82
|
+
operation: z.ZodDefault<z.ZodOptional<z.ZodEnum<["full", "append", "prepend", "insertAt", "replaceAt"]>>>;
|
|
83
|
+
insertIndex: z.ZodOptional<z.ZodNumber>;
|
|
84
|
+
existingLayout: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
|
|
85
|
+
}, "strip", z.ZodTypeAny, {
|
|
86
|
+
sections: {
|
|
87
|
+
sectionType: string;
|
|
88
|
+
fields?: Record<string, unknown> | undefined;
|
|
89
|
+
config?: Record<string, unknown> | undefined;
|
|
90
|
+
content?: {
|
|
91
|
+
blockType: string;
|
|
92
|
+
fields?: Record<string, unknown> | undefined;
|
|
93
|
+
}[] | undefined;
|
|
94
|
+
leftColumn?: {
|
|
95
|
+
blockType: string;
|
|
96
|
+
fields?: Record<string, unknown> | undefined;
|
|
97
|
+
}[] | undefined;
|
|
98
|
+
rightColumn?: {
|
|
99
|
+
blockType: string;
|
|
100
|
+
fields?: Record<string, unknown> | undefined;
|
|
101
|
+
}[] | undefined;
|
|
102
|
+
}[];
|
|
103
|
+
operation: "full" | "append" | "prepend" | "insertAt" | "replaceAt";
|
|
104
|
+
insertIndex?: number | undefined;
|
|
105
|
+
existingLayout?: Record<string, unknown>[] | undefined;
|
|
106
|
+
}, {
|
|
107
|
+
sections: {
|
|
108
|
+
sectionType: string;
|
|
109
|
+
fields?: Record<string, unknown> | undefined;
|
|
110
|
+
config?: Record<string, unknown> | undefined;
|
|
111
|
+
content?: {
|
|
112
|
+
blockType: string;
|
|
113
|
+
fields?: Record<string, unknown> | undefined;
|
|
114
|
+
}[] | undefined;
|
|
115
|
+
leftColumn?: {
|
|
116
|
+
blockType: string;
|
|
117
|
+
fields?: Record<string, unknown> | undefined;
|
|
118
|
+
}[] | undefined;
|
|
119
|
+
rightColumn?: {
|
|
120
|
+
blockType: string;
|
|
121
|
+
fields?: Record<string, unknown> | undefined;
|
|
122
|
+
}[] | undefined;
|
|
123
|
+
}[];
|
|
124
|
+
operation?: "full" | "append" | "prepend" | "insertAt" | "replaceAt" | undefined;
|
|
125
|
+
insertIndex?: number | undefined;
|
|
126
|
+
existingLayout?: Record<string, unknown>[] | undefined;
|
|
127
|
+
}>;
|
|
128
|
+
handler: (args: {
|
|
129
|
+
sections: SectionInput[];
|
|
130
|
+
operation?: "full" | "append" | "prepend" | "insertAt" | "replaceAt";
|
|
131
|
+
insertIndex?: number;
|
|
132
|
+
existingLayout?: Record<string, unknown>[];
|
|
133
|
+
}) => Promise<{
|
|
134
|
+
content: {
|
|
135
|
+
type: "text";
|
|
136
|
+
text: string;
|
|
137
|
+
}[];
|
|
138
|
+
}>;
|
|
139
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { applyOperation, buildHint, composeSections, sectionSchema } from './compose-helpers';
|
|
3
|
+
/**
|
|
4
|
+
* Create the composePageLayout MCP tool from the introspected block catalog.
|
|
5
|
+
*
|
|
6
|
+
* Validates and composes a layout array from natural section/leaf inputs.
|
|
7
|
+
* Returns the composed JSON ready to be written to a doc's `layout` field —
|
|
8
|
+
* use updateDocument or patchLayout to actually persist the result.
|
|
9
|
+
*/ export function createComposeLayoutTool(catalog) {
|
|
10
|
+
const sectionSlugs = catalog.sections.map((s)=>s.slug);
|
|
11
|
+
const leafSlugs = catalog.leaves.map((l)=>l.slug);
|
|
12
|
+
return {
|
|
13
|
+
name: 'composePageLayout',
|
|
14
|
+
description: 'Compose a page layout from section and leaf blocks. Validates nesting rules and required fields. ' + 'Returns block JSON ready for the Payload page layout field. ' + `Available sections: ${sectionSlugs.join(', ')}. Available leaves: ${leafSlugs.join(', ')}.`,
|
|
15
|
+
parameters: z.object({
|
|
16
|
+
sections: z.array(sectionSchema).describe('Array of section blocks to compose into a layout'),
|
|
17
|
+
operation: z.enum([
|
|
18
|
+
'full',
|
|
19
|
+
'append',
|
|
20
|
+
'prepend',
|
|
21
|
+
'insertAt',
|
|
22
|
+
'replaceAt'
|
|
23
|
+
]).optional().default('full').describe('How to apply sections: full replace, append, prepend, insertAt, or replaceAt'),
|
|
24
|
+
insertIndex: z.number().optional().describe('Index for insertAt/replaceAt operations'),
|
|
25
|
+
existingLayout: z.array(z.record(z.string(), z.unknown())).optional().describe('Current page layout JSON for partial update operations')
|
|
26
|
+
}),
|
|
27
|
+
handler: async (args)=>{
|
|
28
|
+
const { sections, operation = 'full', insertIndex, existingLayout } = args;
|
|
29
|
+
const { blocks, errors } = composeSections(sections, catalog);
|
|
30
|
+
if (errors.length > 0) {
|
|
31
|
+
return {
|
|
32
|
+
content: [
|
|
33
|
+
{
|
|
34
|
+
type: 'text',
|
|
35
|
+
text: JSON.stringify({
|
|
36
|
+
success: false,
|
|
37
|
+
errors,
|
|
38
|
+
hint: buildHint(catalog)
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const finalLayout = applyOperation(blocks, operation, insertIndex, existingLayout);
|
|
45
|
+
return {
|
|
46
|
+
content: [
|
|
47
|
+
{
|
|
48
|
+
type: 'text',
|
|
49
|
+
text: JSON.stringify({
|
|
50
|
+
success: true,
|
|
51
|
+
layout: finalLayout,
|
|
52
|
+
blockCount: finalLayout.length
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
//# sourceMappingURL=compose-layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/tools/compose-layout.ts"],"sourcesContent":["import { z } from 'zod'\nimport type { BlockCatalog } from '../types'\nimport {\n applyOperation,\n buildHint,\n composeSections,\n sectionSchema,\n type SectionInput,\n} from './compose-helpers'\n\n/**\n * Create the composePageLayout MCP tool from the introspected block catalog.\n *\n * Validates and composes a layout array from natural section/leaf inputs.\n * Returns the composed JSON ready to be written to a doc's `layout` field —\n * use updateDocument or patchLayout to actually persist the result.\n */\nexport function createComposeLayoutTool(catalog: BlockCatalog) {\n const sectionSlugs = catalog.sections.map((s) => s.slug)\n const leafSlugs = catalog.leaves.map((l) => l.slug)\n\n return {\n name: 'composePageLayout',\n description:\n 'Compose a page layout from section and leaf blocks. Validates nesting rules and required fields. ' +\n 'Returns block JSON ready for the Payload page layout field. ' +\n `Available sections: ${sectionSlugs.join(', ')}. Available leaves: ${leafSlugs.join(', ')}.`,\n parameters: z.object({\n sections: z\n .array(sectionSchema)\n .describe('Array of section blocks to compose into a layout'),\n operation: z\n .enum(['full', 'append', 'prepend', 'insertAt', 'replaceAt'])\n .optional()\n .default('full')\n .describe('How to apply sections: full replace, append, prepend, insertAt, or replaceAt'),\n insertIndex: z\n .number()\n .optional()\n .describe('Index for insertAt/replaceAt operations'),\n existingLayout: z\n .array(z.record(z.string(), z.unknown()))\n .optional()\n .describe('Current page layout JSON for partial update operations'),\n }),\n handler: async (args: {\n sections: SectionInput[]\n operation?: 'full' | 'append' | 'prepend' | 'insertAt' | 'replaceAt'\n insertIndex?: number\n existingLayout?: Record<string, unknown>[]\n }) => {\n const { sections, operation = 'full', insertIndex, existingLayout } = args\n\n const { blocks, errors } = composeSections(sections, catalog)\n\n if (errors.length > 0) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n success: false,\n errors,\n hint: buildHint(catalog),\n }),\n },\n ],\n }\n }\n\n const finalLayout = applyOperation(blocks, operation, insertIndex, existingLayout)\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n success: true,\n layout: finalLayout,\n blockCount: finalLayout.length,\n }),\n },\n ],\n }\n },\n }\n}\n"],"names":["z","applyOperation","buildHint","composeSections","sectionSchema","createComposeLayoutTool","catalog","sectionSlugs","sections","map","s","slug","leafSlugs","leaves","l","name","description","join","parameters","object","array","describe","operation","enum","optional","default","insertIndex","number","existingLayout","record","string","unknown","handler","args","blocks","errors","length","content","type","text","JSON","stringify","success","hint","finalLayout","layout","blockCount"],"mappings":"AAAA,SAASA,CAAC,QAAQ,MAAK;AAEvB,SACEC,cAAc,EACdC,SAAS,EACTC,eAAe,EACfC,aAAa,QAER,oBAAmB;AAE1B;;;;;;CAMC,GACD,OAAO,SAASC,wBAAwBC,OAAqB;IAC3D,MAAMC,eAAeD,QAAQE,QAAQ,CAACC,GAAG,CAAC,CAACC,IAAMA,EAAEC,IAAI;IACvD,MAAMC,YAAYN,QAAQO,MAAM,CAACJ,GAAG,CAAC,CAACK,IAAMA,EAAEH,IAAI;IAElD,OAAO;QACLI,MAAM;QACNC,aACE,sGACA,iEACA,CAAC,oBAAoB,EAAET,aAAaU,IAAI,CAAC,MAAM,oBAAoB,EAAEL,UAAUK,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9FC,YAAYlB,EAAEmB,MAAM,CAAC;YACnBX,UAAUR,EACPoB,KAAK,CAAChB,eACNiB,QAAQ,CAAC;YACZC,WAAWtB,EACRuB,IAAI,CAAC;gBAAC;gBAAQ;gBAAU;gBAAW;gBAAY;aAAY,EAC3DC,QAAQ,GACRC,OAAO,CAAC,QACRJ,QAAQ,CAAC;YACZK,aAAa1B,EACV2B,MAAM,GACNH,QAAQ,GACRH,QAAQ,CAAC;YACZO,gBAAgB5B,EACboB,KAAK,CAACpB,EAAE6B,MAAM,CAAC7B,EAAE8B,MAAM,IAAI9B,EAAE+B,OAAO,KACpCP,QAAQ,GACRH,QAAQ,CAAC;QACd;QACAW,SAAS,OAAOC;YAMd,MAAM,EAAEzB,QAAQ,EAAEc,YAAY,MAAM,EAAEI,WAAW,EAAEE,cAAc,EAAE,GAAGK;YAEtE,MAAM,EAAEC,MAAM,EAAEC,MAAM,EAAE,GAAGhC,gBAAgBK,UAAUF;YAErD,IAAI6B,OAAOC,MAAM,GAAG,GAAG;gBACrB,OAAO;oBACLC,SAAS;wBACP;4BACEC,MAAM;4BACNC,MAAMC,KAAKC,SAAS,CAAC;gCACnBC,SAAS;gCACTP;gCACAQ,MAAMzC,UAAUI;4BAClB;wBACF;qBACD;gBACH;YACF;YAEA,MAAMsC,cAAc3C,eAAeiC,QAAQZ,WAAWI,aAAaE;YAEnE,OAAO;gBACLS,SAAS;oBACP;wBACEC,MAAM;wBACNC,MAAMC,KAAKC,SAAS,CAAC;4BACnBC,SAAS;4BACTG,QAAQD;4BACRE,YAAYF,YAAYR,MAAM;wBAChC;oBACF;iBACD;YACH;QACF;IACF;AACF"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PayloadRequest } from 'payload';
|
|
3
|
+
import type { BlockCatalog } from '../types';
|
|
4
|
+
import { type SectionInput } from './compose-helpers';
|
|
5
|
+
/**
|
|
6
|
+
* Creates the patchLayout MCP tool — a surgical wrapper around composePageLayout
|
|
7
|
+
* that mutates a single document's layout-style field directly, never round-tripping
|
|
8
|
+
* the entire array through updateDocument.
|
|
9
|
+
*
|
|
10
|
+
* Why this exists: prompting an LLM to "add a CTA at the bottom of the home page"
|
|
11
|
+
* via updateDocument forces it to send the entire layout array, which one bad token
|
|
12
|
+
* can wipe out. patchLayout fetches the current layout itself, applies a scoped
|
|
13
|
+
* operation, and writes back — the LLM only ever describes the delta.
|
|
14
|
+
*
|
|
15
|
+
* Defaults to layoutField "layout" but accepts any block-array field name.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createPatchLayoutTool(catalog: BlockCatalog, draftCollections: Set<string>): {
|
|
18
|
+
name: string;
|
|
19
|
+
description: string;
|
|
20
|
+
parameters: {
|
|
21
|
+
collection: z.ZodString;
|
|
22
|
+
documentId: z.ZodString;
|
|
23
|
+
layoutField: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
24
|
+
sections: z.ZodArray<z.ZodObject<{
|
|
25
|
+
sectionType: z.ZodString;
|
|
26
|
+
config: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
27
|
+
content: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
28
|
+
blockType: z.ZodString;
|
|
29
|
+
fields: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
30
|
+
}, "strip", z.ZodTypeAny, {
|
|
31
|
+
blockType: string;
|
|
32
|
+
fields?: Record<string, unknown> | undefined;
|
|
33
|
+
}, {
|
|
34
|
+
blockType: string;
|
|
35
|
+
fields?: Record<string, unknown> | undefined;
|
|
36
|
+
}>, "many">>;
|
|
37
|
+
leftColumn: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
38
|
+
blockType: z.ZodString;
|
|
39
|
+
fields: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
40
|
+
}, "strip", z.ZodTypeAny, {
|
|
41
|
+
blockType: string;
|
|
42
|
+
fields?: Record<string, unknown> | undefined;
|
|
43
|
+
}, {
|
|
44
|
+
blockType: string;
|
|
45
|
+
fields?: Record<string, unknown> | undefined;
|
|
46
|
+
}>, "many">>;
|
|
47
|
+
rightColumn: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
48
|
+
blockType: z.ZodString;
|
|
49
|
+
fields: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
50
|
+
}, "strip", z.ZodTypeAny, {
|
|
51
|
+
blockType: string;
|
|
52
|
+
fields?: Record<string, unknown> | undefined;
|
|
53
|
+
}, {
|
|
54
|
+
blockType: string;
|
|
55
|
+
fields?: Record<string, unknown> | undefined;
|
|
56
|
+
}>, "many">>;
|
|
57
|
+
fields: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
58
|
+
}, "strip", z.ZodTypeAny, {
|
|
59
|
+
sectionType: string;
|
|
60
|
+
fields?: Record<string, unknown> | undefined;
|
|
61
|
+
config?: Record<string, unknown> | undefined;
|
|
62
|
+
content?: {
|
|
63
|
+
blockType: string;
|
|
64
|
+
fields?: Record<string, unknown> | undefined;
|
|
65
|
+
}[] | undefined;
|
|
66
|
+
leftColumn?: {
|
|
67
|
+
blockType: string;
|
|
68
|
+
fields?: Record<string, unknown> | undefined;
|
|
69
|
+
}[] | undefined;
|
|
70
|
+
rightColumn?: {
|
|
71
|
+
blockType: string;
|
|
72
|
+
fields?: Record<string, unknown> | undefined;
|
|
73
|
+
}[] | undefined;
|
|
74
|
+
}, {
|
|
75
|
+
sectionType: string;
|
|
76
|
+
fields?: Record<string, unknown> | undefined;
|
|
77
|
+
config?: Record<string, unknown> | undefined;
|
|
78
|
+
content?: {
|
|
79
|
+
blockType: string;
|
|
80
|
+
fields?: Record<string, unknown> | undefined;
|
|
81
|
+
}[] | undefined;
|
|
82
|
+
leftColumn?: {
|
|
83
|
+
blockType: string;
|
|
84
|
+
fields?: Record<string, unknown> | undefined;
|
|
85
|
+
}[] | undefined;
|
|
86
|
+
rightColumn?: {
|
|
87
|
+
blockType: string;
|
|
88
|
+
fields?: Record<string, unknown> | undefined;
|
|
89
|
+
}[] | undefined;
|
|
90
|
+
}>, "many">;
|
|
91
|
+
operation: z.ZodEnum<["append", "prepend", "insertAt", "replaceAt", "full"]>;
|
|
92
|
+
insertIndex: z.ZodOptional<z.ZodNumber>;
|
|
93
|
+
};
|
|
94
|
+
handler: (args: {
|
|
95
|
+
collection: string;
|
|
96
|
+
documentId: string;
|
|
97
|
+
layoutField?: string;
|
|
98
|
+
sections: SectionInput[];
|
|
99
|
+
operation: "append" | "prepend" | "insertAt" | "replaceAt" | "full";
|
|
100
|
+
insertIndex?: number;
|
|
101
|
+
}, req: PayloadRequest, _extra: unknown) => Promise<{
|
|
102
|
+
content: {
|
|
103
|
+
type: "text";
|
|
104
|
+
text: string;
|
|
105
|
+
}[];
|
|
106
|
+
}>;
|
|
107
|
+
};
|