@abinnovision/payloadcms-mcpx 2.0.0-beta.19 → 2.0.0-beta.20
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/admin/capability-matrix.mjs +213 -316
- package/dist/admin/capability-toggles.mjs +24 -31
- package/dist/admin/confirmations-panel.mjs +61 -71
- package/dist/admin/confirmations.mjs +2 -1
- package/dist/admin/endpoint-url.mjs +7 -0
- package/dist/admin/setup-guide.mjs +2 -1
- package/dist/api-keys/capability-matrix.d.mts +3 -6
- package/dist/api-keys/capability-matrix.mjs +9 -48
- package/dist/api-keys/collection.mjs +2 -5
- package/dist/api-keys/confirmation-view.mjs +2 -1
- package/dist/api-keys/fields.mjs +33 -55
- package/dist/auth/resolve.mjs +29 -10
- package/dist/capabilities.d.mts +2 -0
- package/dist/capabilities.mjs +62 -19
- package/dist/endpoint/confirmations.mjs +14 -14
- package/dist/endpoint/handler.mjs +6 -22
- package/dist/endpoint/http.mjs +27 -0
- package/dist/endpoint/upload.mjs +11 -43
- package/dist/grants/confirmation.mjs +11 -42
- package/dist/grants/grant.mjs +58 -40
- package/dist/guards.mjs +8 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/options.mjs +34 -54
- package/dist/plugin.mjs +2 -1
- package/dist/request.mjs +5 -1
- package/dist/result.mjs +11 -1
- package/dist/schema/admin-hidden.mjs +4 -4
- package/dist/schema/describe.mjs +12 -18
- package/dist/schema/index.mjs +5 -6
- package/dist/schema/lexical-pointer.mjs +8 -8
- package/dist/schema/lexical.mjs +15 -13
- package/dist/schema/outline.mjs +4 -5
- package/dist/schema/path.mjs +4 -1
- package/dist/schema/pointer.mjs +11 -17
- package/dist/schema/shape.mjs +38 -50
- package/dist/schema/walk.mjs +57 -18
- package/dist/tools/create-document.mjs +16 -44
- package/dist/tools/delete-document.mjs +14 -17
- package/dist/tools/describe-schema.mjs +7 -5
- package/dist/tools/document.mjs +130 -44
- package/dist/tools/duplicate-document.mjs +11 -10
- package/dist/tools/find-documents.mjs +15 -20
- package/dist/tools/find-versions.mjs +10 -14
- package/dist/tools/get-document.mjs +8 -12
- package/dist/tools/links.mjs +2 -1
- package/dist/tools/list-capabilities.mjs +2 -1
- package/dist/tools/patch-document.mjs +12 -44
- package/dist/tools/publish-document.mjs +9 -27
- package/dist/tools/query-paths.mjs +1 -1
- package/dist/tools/run-confirmed.mjs +38 -50
- package/dist/tools/shared.mjs +43 -41
- package/dist/tools/validate-document.mjs +4 -6
- package/dist/tools/versions.mjs +11 -6
- package/dist/types.d.mts +12 -26
- package/dist/upload/file.mjs +10 -18
- package/dist/write/draft-guard.mjs +20 -20
- package/dist/write/patch.mjs +4 -4
- package/dist/write/publish-intent.mjs +22 -28
- package/dist/write/row-ids.mjs +3 -1
- package/dist/write/write-data.mjs +18 -31
- package/package.json +5 -5
- package/dist/builtin-tool-names.mjs +0 -19
package/dist/schema/path.mjs
CHANGED
|
@@ -24,5 +24,8 @@ const PROTOTYPE_SEGMENTS = /* @__PURE__ */ new Set([
|
|
|
24
24
|
* Pointer, so everything handed back addresses documents the same way. The
|
|
25
25
|
* path already carries real indices, so it maps directly.
|
|
26
26
|
*/ const pointerFromPayloadPath = (path) => path ? joinPath(path.split(".")) : "";
|
|
27
|
+
/**
|
|
28
|
+
* The pointer one segment up. The root pointer stays the root.
|
|
29
|
+
*/ const parentPointer = (pointer) => joinPath(splitPath(pointer).slice(0, -1));
|
|
27
30
|
//#endregion
|
|
28
|
-
export { JSON_POINTER_PATTERN, isIndexSegment, joinPath, pointerFromPayloadPath, prototypeSegmentProblem, splitPath };
|
|
31
|
+
export { JSON_POINTER_PATTERN, isIndexSegment, joinPath, parentPointer, pointerFromPayloadPath, prototypeSegmentProblem, splitPath };
|
package/dist/schema/pointer.mjs
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { blockOf, blockSlugsOf, describeAddressableFields, findBlocksField, findRichTextField, schemaOf } from "./walk.mjs";
|
|
1
|
+
import { propOf } from "../guards.mjs";
|
|
3
2
|
import { SchemaError } from "./errors.mjs";
|
|
3
|
+
import { isIndexSegment, joinPath, splitPath } from "./path.mjs";
|
|
4
|
+
import { blockSlugsOf, describeAddressableFields, findFieldAt, longestMatch, requireBlock, schemaOf } from "./walk.mjs";
|
|
4
5
|
import { resolveLexicalPointer } from "./lexical-pointer.mjs";
|
|
5
6
|
//#region src/schema/pointer.ts
|
|
6
7
|
const partMatches = (part, segment) => segment !== void 0 && (part === "*" ? isIndexSegment(segment) : part === segment);
|
|
7
|
-
const matchPointerSegments = (descriptors, segments) => descriptors.map((descriptor) => ({
|
|
8
|
-
consumed: splitPath(descriptor.path).length,
|
|
9
|
-
descriptor,
|
|
10
|
-
parts: splitPath(descriptor.path)
|
|
11
|
-
})).filter(({ parts }) => parts.every((part, offset) => partMatches(part, segments[offset]))).sort((left, right) => right.consumed - left.consumed)[0];
|
|
12
8
|
const isSubtreePrefix = (descriptors, segments) => descriptors.some((descriptor) => {
|
|
13
9
|
const parts = splitPath(descriptor.path);
|
|
14
10
|
return parts.length > segments.length && segments.every((segment, offset) => {
|
|
@@ -21,17 +17,15 @@ const stepIntoBlock = (at) => {
|
|
|
21
17
|
const { addedValue, config, descriptor, rows } = at;
|
|
22
18
|
const [index, ...remaining] = at.rest;
|
|
23
19
|
if (!isIndexSegment(index)) throw new SchemaError(`"${descriptor.path}" is an array; "${index}" is not an index.`);
|
|
24
|
-
const field =
|
|
20
|
+
const field = findFieldAt(at.fields, splitPath(descriptor.path), "blocks");
|
|
25
21
|
const existing = Array.isArray(rows) && index !== "-" ? rows[Number(index)] : void 0;
|
|
26
|
-
const slug = existing
|
|
22
|
+
const slug = propOf(existing, "blockType") ?? propOf(addedValue, "blockType");
|
|
27
23
|
if (!field || slug === void 0) throw new SchemaError(`Cannot tell which block "${descriptor.path}/${index}" is. Supply a "blockType" on the value, one of: ${field ? blockSlugsOf(field).join(", ") : ""}`);
|
|
28
24
|
if (typeof slug !== "string") throw new SchemaError(`"blockType" must be a string naming a block at "${descriptor.path}". Allowed: ${blockSlugsOf(field).join(", ")}`);
|
|
29
|
-
const block = blockOf(config, field, slug);
|
|
30
|
-
if (!block) throw new SchemaError(`"${slug}" is not allowed at "${descriptor.path}". Allowed: ${blockSlugsOf(field).join(", ")}`);
|
|
31
25
|
return {
|
|
32
26
|
blockType: slug,
|
|
33
27
|
data: existing,
|
|
34
|
-
fields:
|
|
28
|
+
fields: requireBlock(config, field, slug, descriptor.path).flattenedFields,
|
|
35
29
|
rest: remaining
|
|
36
30
|
};
|
|
37
31
|
};
|
|
@@ -48,7 +42,7 @@ const stepIntoBlock = (at) => {
|
|
|
48
42
|
let inLexical;
|
|
49
43
|
while (segments.length > 0) {
|
|
50
44
|
const descriptors = describeAddressableFields(fields);
|
|
51
|
-
const match =
|
|
45
|
+
const match = longestMatch(descriptors, segments, partMatches);
|
|
52
46
|
if (!match) {
|
|
53
47
|
if (isSubtreePrefix(descriptors, segments)) return {
|
|
54
48
|
...blockType === void 0 ? {} : { blockType },
|
|
@@ -57,7 +51,7 @@ const stepIntoBlock = (at) => {
|
|
|
57
51
|
};
|
|
58
52
|
throw new SchemaError(`"${joinPath(segments)}" is not a field here. Available: ${descriptors.map((descriptor) => descriptor.path).join(", ")}`);
|
|
59
53
|
}
|
|
60
|
-
const rest = segments.slice(match.
|
|
54
|
+
const rest = segments.slice(match.parts.length);
|
|
61
55
|
if (rest.length === 0) return {
|
|
62
56
|
...blockType === void 0 ? {} : { blockType },
|
|
63
57
|
descriptor: match.descriptor,
|
|
@@ -67,7 +61,7 @@ const stepIntoBlock = (at) => {
|
|
|
67
61
|
...readOnly === void 0 ? {} : { readOnly }
|
|
68
62
|
};
|
|
69
63
|
if (match.descriptor.type === "richText") {
|
|
70
|
-
const field =
|
|
64
|
+
const field = findFieldAt(fields, splitPath(match.descriptor.path), "richText");
|
|
71
65
|
if (!field) throw new Error(`"${match.descriptor.path}" could not be resolved.`);
|
|
72
66
|
const step = resolveLexicalPointer({
|
|
73
67
|
...target.addedValue === void 0 ? {} : { addedValue: target.addedValue },
|
|
@@ -75,7 +69,7 @@ const stepIntoBlock = (at) => {
|
|
|
75
69
|
descriptor: match.descriptor,
|
|
76
70
|
field,
|
|
77
71
|
segments: rest,
|
|
78
|
-
state: valueAtSegments(data, segments.slice(0, match.
|
|
72
|
+
state: valueAtSegments(data, segments.slice(0, match.parts.length))
|
|
79
73
|
});
|
|
80
74
|
if (step.kind === "position") return {
|
|
81
75
|
...blockType === void 0 ? {} : { blockType },
|
|
@@ -99,7 +93,7 @@ const stepIntoBlock = (at) => {
|
|
|
99
93
|
descriptor: match.descriptor,
|
|
100
94
|
fields,
|
|
101
95
|
rest,
|
|
102
|
-
rows: valueAtSegments(data, segments.slice(0, match.
|
|
96
|
+
rows: valueAtSegments(data, segments.slice(0, match.parts.length)),
|
|
103
97
|
config
|
|
104
98
|
});
|
|
105
99
|
readOnly = match.descriptor.readOnly ?? readOnly;
|
package/dist/schema/shape.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { isPlainObject, ownValue } from "../guards.mjs";
|
|
2
|
-
import { ROOT_PROPERTIES, constrainsFields, lexicalSubSchema, nodeProblems, propertyProblem, rootProblems } from "./lexical.mjs";
|
|
1
|
+
import { isPlainObject, ownValue, propOf } from "../guards.mjs";
|
|
2
|
+
import { ROOT_PROPERTIES, constrainsFields, editorRoot, lexicalSubSchema, nodeProblems, propertyProblem, rootProblems } from "./lexical.mjs";
|
|
3
3
|
import { splitPath } from "./path.mjs";
|
|
4
|
-
import { ROW_KEYS,
|
|
4
|
+
import { ROW_KEYS, blockForRow, blockSlugsOf, classifyKey, descriptorsUnder, findFieldAt } from "./walk.mjs";
|
|
5
5
|
//#region src/schema/shape.ts
|
|
6
6
|
/**
|
|
7
7
|
* Lexical throws when it hydrates a state whose root holds nothing. An empty
|
|
@@ -33,10 +33,9 @@ const checkNodeFields = (scope, field, node) => {
|
|
|
33
33
|
}, data);
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
|
-
const
|
|
37
|
-
const block = typeof slug === "string" ? blockOf(scope.config, sub.blocksField, slug) : void 0;
|
|
36
|
+
const block = blockForRow(scope.config, sub.blocksField, data);
|
|
38
37
|
if (!block) {
|
|
39
|
-
scope.problems.push(`${scope.pointer}/fields: ${refusedSlug(
|
|
38
|
+
scope.problems.push(`${scope.pointer}/fields: ${refusedSlug(data["blockType"])}. Allowed: ${blockSlugsOf(sub.blocksField).join(", ")}`);
|
|
40
39
|
return;
|
|
41
40
|
}
|
|
42
41
|
checkValue({
|
|
@@ -81,11 +80,11 @@ const checkNodes = (scope, editor, nodes, pointer) => {
|
|
|
81
80
|
});
|
|
82
81
|
};
|
|
83
82
|
const checkRichText = (scope, editor, value) => {
|
|
84
|
-
|
|
83
|
+
const root = editorRoot(value);
|
|
84
|
+
if (!root) {
|
|
85
85
|
scope.problems.push(`${scope.pointer}: expected a Lexical editor state with a "root".`);
|
|
86
86
|
return;
|
|
87
87
|
}
|
|
88
|
-
const root = value["root"];
|
|
89
88
|
const { missing, rejected, unexpected } = rootProblems(root);
|
|
90
89
|
if (missing.length > 0) scope.problems.push(`${scope.pointer}/root: the root node is missing ${quoted(missing)}. Write nodes as Lexical serializes them.`);
|
|
91
90
|
for (const problem of rejected) scope.problems.push(`${scope.pointer}/root/${problem.property}: the root node needs ${problem.needs} here.`);
|
|
@@ -148,7 +147,7 @@ const checkLeafValue = (scope, descriptor, value) => {
|
|
|
148
147
|
if (descriptor.type === "richText") {
|
|
149
148
|
checkRichText(scope, {
|
|
150
149
|
allowed: descriptor.nodes ?? [],
|
|
151
|
-
field:
|
|
150
|
+
field: findFieldAt(scope.fields, splitPath(descriptor.path), "richText"),
|
|
152
151
|
nodeOptions: descriptor.nodeOptions
|
|
153
152
|
}, value);
|
|
154
153
|
return;
|
|
@@ -158,13 +157,12 @@ const checkLeafValue = (scope, descriptor, value) => {
|
|
|
158
157
|
scope.problems.push(`${scope.pointer}: expected an array of blocks.`);
|
|
159
158
|
return;
|
|
160
159
|
}
|
|
161
|
-
const field =
|
|
162
|
-
if (!field) return;
|
|
160
|
+
const field = findFieldAt(scope.fields, splitPath(descriptor.path), "blocks");
|
|
163
161
|
value.forEach((row, index) => {
|
|
164
|
-
const
|
|
165
|
-
|
|
162
|
+
const block = field && blockForRow(scope.config, field, row);
|
|
163
|
+
if (!field) return;
|
|
166
164
|
if (!block) {
|
|
167
|
-
scope.problems.push(`${scope.pointer}/${String(index)}: ${refusedSlug(
|
|
165
|
+
scope.problems.push(`${scope.pointer}/${String(index)}: ${refusedSlug(propOf(row, "blockType"))}. Allowed: ${blockSlugsOf(field).join(", ")}`);
|
|
168
166
|
return;
|
|
169
167
|
}
|
|
170
168
|
checkValue({
|
|
@@ -177,47 +175,37 @@ const checkLeafValue = (scope, descriptor, value) => {
|
|
|
177
175
|
};
|
|
178
176
|
const checkValue = (scope, value) => {
|
|
179
177
|
if (!isPlainObject(value)) return;
|
|
180
|
-
const
|
|
181
|
-
const relative = descriptorsUnder(scope.fields, prefixParts);
|
|
178
|
+
const relative = descriptorsUnder(scope.fields, scope.prefix);
|
|
182
179
|
for (const [key, entry] of Object.entries(value)) {
|
|
183
180
|
if (ROW_KEYS.has(key)) continue;
|
|
184
|
-
const candidates = relative.filter(({ parts }) => parts[0] === key);
|
|
185
181
|
const pointer = `${scope.pointer}/${key}`;
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
...scope,
|
|
194
|
-
pointer
|
|
195
|
-
}, exact.descriptor, entry);
|
|
196
|
-
continue;
|
|
197
|
-
}
|
|
198
|
-
if (candidates.some(({ parts }) => parts[1] === "*")) {
|
|
199
|
-
if (!Array.isArray(entry)) {
|
|
200
|
-
scope.problems.push(`${pointer}: expected an array.`);
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
entry.forEach((row, index) => {
|
|
204
|
-
checkValue({
|
|
182
|
+
const found = classifyKey(relative, scope.prefix, key);
|
|
183
|
+
switch (found.kind) {
|
|
184
|
+
case "none":
|
|
185
|
+
scope.problems.push(`${pointer}: no such field. Available: ${[...new Set(relative.map(({ parts }) => parts[0]))].join(", ")}`);
|
|
186
|
+
break;
|
|
187
|
+
case "leaf":
|
|
188
|
+
checkLeafValue({
|
|
205
189
|
...scope,
|
|
206
|
-
pointer
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
190
|
+
pointer
|
|
191
|
+
}, found.descriptor, entry);
|
|
192
|
+
break;
|
|
193
|
+
case "rows":
|
|
194
|
+
if (Array.isArray(entry)) entry.forEach((row, index) => {
|
|
195
|
+
checkValue({
|
|
196
|
+
...scope,
|
|
197
|
+
pointer: `${pointer}/${String(index)}`,
|
|
198
|
+
prefix: found.prefix
|
|
199
|
+
}, row);
|
|
200
|
+
});
|
|
201
|
+
else scope.problems.push(`${pointer}: expected an array.`);
|
|
202
|
+
break;
|
|
203
|
+
case "group": checkValue({
|
|
204
|
+
...scope,
|
|
205
|
+
pointer,
|
|
206
|
+
prefix: found.prefix
|
|
207
|
+
}, entry);
|
|
215
208
|
}
|
|
216
|
-
checkValue({
|
|
217
|
-
...scope,
|
|
218
|
-
pointer,
|
|
219
|
-
prefix: [...prefixParts, key]
|
|
220
|
-
}, entry);
|
|
221
209
|
}
|
|
222
210
|
};
|
|
223
211
|
const validateWriteValue = (config, target, value) => {
|
package/dist/schema/walk.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { propOf } from "../guards.mjs";
|
|
2
|
+
import { SchemaError } from "./errors.mjs";
|
|
1
3
|
import { allowedNodeTypes, nodeOptions } from "./lexical.mjs";
|
|
2
4
|
import { joinPath, splitPath } from "./path.mjs";
|
|
3
5
|
import { translateAny } from "../i18n.mjs";
|
|
@@ -34,9 +36,17 @@ const RESERVED_FIELD_NAMES = /* @__PURE__ */ new Set([
|
|
|
34
36
|
/**
|
|
35
37
|
* Payload's `fieldIsHiddenOrDisabled` reads `hidden` and `admin.disabled`, not
|
|
36
38
|
* `admin.hidden`, which its own upload base fields carry.
|
|
37
|
-
*/ const
|
|
38
|
-
const
|
|
39
|
-
const
|
|
39
|
+
*/ const adminFlag = (field, key) => "admin" in field && field.admin[key] === true;
|
|
40
|
+
const blockForRow = (config, field, row) => {
|
|
41
|
+
const slug = propOf(row, "blockType");
|
|
42
|
+
return typeof slug === "string" ? blockOf(config, field, slug) : void 0;
|
|
43
|
+
};
|
|
44
|
+
const requireBlock = (config, field, slug, at) => {
|
|
45
|
+
const block = blockOf(config, field, slug);
|
|
46
|
+
if (!block) throw new SchemaError(`"${slug}" is not allowed at "${at}". Allowed: ${blockSlugsOf(field).join(", ")}`);
|
|
47
|
+
return block;
|
|
48
|
+
};
|
|
49
|
+
const isSkipped = (field) => !("name" in field) || field.type === "join" || RESERVED_FIELD_NAMES.has(field.name) || fieldIsVirtual(field) || adminFlag(field, "hidden") || fieldIsHiddenOrDisabled(field);
|
|
40
50
|
const describeBase = (field, { path, readOnly, translate }) => {
|
|
41
51
|
const description = translate("admin" in field ? field.admin.description : void 0);
|
|
42
52
|
return {
|
|
@@ -71,17 +81,11 @@ const withRows = (descriptor, field) => ({
|
|
|
71
81
|
});
|
|
72
82
|
const isContainer = (descriptor) => descriptor.type === "array" || descriptor.type === "group" || descriptor.type === "tab";
|
|
73
83
|
const isInformative = (descriptor) => descriptor.description !== void 0 || descriptor.required === true || descriptor.localized === true;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
* themselves only when they declare something of their own: an array always,
|
|
77
|
-
* since its row counts live nowhere else. The walk stops at every blocks field
|
|
78
|
-
* and names the slugs, so a node's size follows the number of blocks it
|
|
79
|
-
* allows, not their definitions. Omitting `translate` costs language
|
|
80
|
-
* selection, never the description itself.
|
|
81
|
-
*/ const describeFields = (fields, translate = translateAny) => {
|
|
84
|
+
const describedByFields = /* @__PURE__ */ new WeakMap();
|
|
85
|
+
const describeWith = (fields, translate) => {
|
|
82
86
|
const walk = (current, prefix, parentReadOnly) => current.flatMap((field) => {
|
|
83
87
|
if (isSkipped(field)) return [];
|
|
84
|
-
const readOnly = parentReadOnly ||
|
|
88
|
+
const readOnly = parentReadOnly || adminFlag(field, "readOnly");
|
|
85
89
|
const path = [...prefix, field.name];
|
|
86
90
|
const at = {
|
|
87
91
|
path: joinPath(path),
|
|
@@ -102,6 +106,22 @@ const isInformative = (descriptor) => descriptor.description !== void 0 || descr
|
|
|
102
106
|
return walk(fields, [], false);
|
|
103
107
|
};
|
|
104
108
|
/**
|
|
109
|
+
* Named tabs, groups and arrays add a path segment and are described
|
|
110
|
+
* themselves only when they declare something of their own: an array always,
|
|
111
|
+
* since its row counts live nowhere else. The walk stops at every blocks field
|
|
112
|
+
* and names the slugs, so a node's size follows the number of blocks it
|
|
113
|
+
* allows, not their definitions. Omitting `translate` costs language
|
|
114
|
+
* selection, never the description itself.
|
|
115
|
+
*/ const describeFields = (fields, translate = translateAny) => {
|
|
116
|
+
if (translate !== translateAny) return describeWith(fields, translate);
|
|
117
|
+
let described = describedByFields.get(fields);
|
|
118
|
+
if (!described) {
|
|
119
|
+
described = describeWith(fields, translate);
|
|
120
|
+
describedByFields.set(fields, described);
|
|
121
|
+
}
|
|
122
|
+
return described;
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
105
125
|
* The descriptors that address a value, which every walk resolving a path
|
|
106
126
|
* against a document needs. Containers are left out, since only
|
|
107
127
|
* {@link nodeDescriber} reports one.
|
|
@@ -124,11 +144,30 @@ const findFieldAt = (fields, path, type) => {
|
|
|
124
144
|
if (field.type === "array" && path[1] === "*") return findFieldAt(field.flattenedFields, path.slice(2), type);
|
|
125
145
|
}
|
|
126
146
|
};
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
147
|
+
const longestMatch = (descriptors, segments, eq) => descriptors.map((descriptor) => ({
|
|
148
|
+
descriptor,
|
|
149
|
+
parts: splitPath(descriptor.path)
|
|
150
|
+
})).filter(({ parts }) => parts.every((part, offset) => eq(part, segments[offset]))).sort((left, right) => right.parts.length - left.parts.length)[0];
|
|
151
|
+
const classifyKey = (relative, prefix, key) => {
|
|
152
|
+
const candidates = relative.filter(({ parts }) => parts[0] === key);
|
|
153
|
+
const exact = candidates.find(({ parts }) => parts.length === 1);
|
|
154
|
+
if (exact) return {
|
|
155
|
+
descriptor: exact.descriptor,
|
|
156
|
+
kind: "leaf"
|
|
157
|
+
};
|
|
158
|
+
if (candidates.length === 0) return { kind: "none" };
|
|
159
|
+
return candidates.some(({ parts }) => parts[1] === "*") ? {
|
|
160
|
+
kind: "rows",
|
|
161
|
+
prefix: [
|
|
162
|
+
...prefix,
|
|
163
|
+
key,
|
|
164
|
+
"*"
|
|
165
|
+
]
|
|
166
|
+
} : {
|
|
167
|
+
kind: "group",
|
|
168
|
+
prefix: [...prefix, key]
|
|
169
|
+
};
|
|
170
|
+
};
|
|
132
171
|
/**
|
|
133
172
|
* Looks up the sanitized config for a collection or global as an
|
|
134
173
|
* {@link EntitySchema}. Throws on an unknown slug instead of returning
|
|
@@ -140,4 +179,4 @@ const findBlocksField = (fields, path) => findFieldAt(fields, path, "blocks");
|
|
|
140
179
|
return found;
|
|
141
180
|
};
|
|
142
181
|
//#endregion
|
|
143
|
-
export { RESERVED_FIELD_NAMES, ROW_KEYS, blockOf, blockSlugsOf, describeAddressableFields, describeFields, descriptorsUnder,
|
|
182
|
+
export { RESERVED_FIELD_NAMES, ROW_KEYS, adminFlag, blockForRow, blockOf, blockSlugsOf, classifyKey, describeAddressableFields, describeFields, descriptorsUnder, findFieldAt, longestMatch, requireBlock, schemaOf };
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { defineMcpxTool } from "../define-tool.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { definedProps } from "../guards.mjs";
|
|
3
|
+
import { errorResult } from "../result.mjs";
|
|
3
4
|
import { validateWriteValue } from "../schema/shape.mjs";
|
|
4
5
|
import "../schema/index.mjs";
|
|
5
|
-
import { collectPublishBlockers } from "../write/publish-blockers.mjs";
|
|
6
6
|
import { assertAcceptsFile, fileShape, fileWriteRefusal, uploadHandoff, uploadedFile } from "../upload/file.mjs";
|
|
7
|
-
import { liveWriteSentence, localeOf, localeShape, patchOnlySlugs,
|
|
8
|
-
import {
|
|
9
|
-
import { documentLinks } from "./links.mjs";
|
|
7
|
+
import { liveWriteSentence, localeOf, localeShape, patchOnlySlugs, slugsFor, stringEnum } from "./shared.mjs";
|
|
8
|
+
import { resolveCollection, writtenResult } from "./document.mjs";
|
|
10
9
|
import { stripRowIds } from "../write/row-ids.mjs";
|
|
11
10
|
import { z } from "zod";
|
|
12
11
|
import { APIError } from "payload";
|
|
@@ -19,36 +18,6 @@ const DESCRIPTION = (scope) => `Creates a document in a collection. Use it only
|
|
|
19
18
|
|
|
20
19
|
${liveWriteSentence(scope, "create")}${uploadSentence(scope)}`;
|
|
21
20
|
/**
|
|
22
|
-
* What createDocument and duplicateDocument return: the new document, re-read
|
|
23
|
-
* privileged in `locale`, with its links and publish blockers.
|
|
24
|
-
*/ const createdResult = async (scope, args) => {
|
|
25
|
-
const { target, locale } = args;
|
|
26
|
-
const saved = await readDraft(scope, {
|
|
27
|
-
target: {
|
|
28
|
-
...target,
|
|
29
|
-
id: args.id
|
|
30
|
-
},
|
|
31
|
-
locale,
|
|
32
|
-
privileged: true
|
|
33
|
-
});
|
|
34
|
-
const validation = await collectPublishBlockers(scope.req, {
|
|
35
|
-
doc: saved,
|
|
36
|
-
entity: target
|
|
37
|
-
});
|
|
38
|
-
return jsonResult({
|
|
39
|
-
id: saved["id"],
|
|
40
|
-
status: saved["_status"],
|
|
41
|
-
updatedAt: saved["updatedAt"],
|
|
42
|
-
...await documentLinks(scope.req, {
|
|
43
|
-
target,
|
|
44
|
-
doc: saved,
|
|
45
|
-
locale
|
|
46
|
-
}),
|
|
47
|
-
...validation.blockers.length > 0 ? { publishBlockers: validation.blockers } : {},
|
|
48
|
-
...validation.unavailable ? { publishBlockersUnavailable: true } : {}
|
|
49
|
-
});
|
|
50
|
-
};
|
|
51
|
-
/**
|
|
52
21
|
* Collection-only, because a global always exists. A create in an upload
|
|
53
22
|
* collection needs `file`: the call then returns an upload grant, and the PUT
|
|
54
23
|
* to it runs the call again with the bytes.
|
|
@@ -69,7 +38,7 @@ ${liveWriteSentence(scope, "create")}${uploadSentence(scope)}`;
|
|
|
69
38
|
},
|
|
70
39
|
isEnabled: (scope) => slugsFor(scope, "create").collections.length > 0,
|
|
71
40
|
inputSchema: (scope) => ({
|
|
72
|
-
collection:
|
|
41
|
+
collection: stringEnum(slugsFor(scope, "create").collections),
|
|
73
42
|
...localeShape(scope, {
|
|
74
43
|
required: true,
|
|
75
44
|
description: "Locale of the localized fields in \"data\"."
|
|
@@ -78,8 +47,7 @@ ${liveWriteSentence(scope, "create")}${uploadSentence(scope)}`;
|
|
|
78
47
|
...fileShape(scope, (slugs) => `Required in ${slugs}. The call returns an "upload" for the bytes instead of writing.`)
|
|
79
48
|
}),
|
|
80
49
|
handler: async ({ args, scope }) => {
|
|
81
|
-
const target =
|
|
82
|
-
if (target.kind === "global") throw new Error("createDocument resolved a global.");
|
|
50
|
+
const target = resolveCollection(scope, args.collection, "create");
|
|
83
51
|
const { payload } = scope.req;
|
|
84
52
|
const locale = localeOf(scope, args.locale);
|
|
85
53
|
const { file } = args;
|
|
@@ -116,15 +84,19 @@ ${liveWriteSentence(scope, "create")}${uploadSentence(scope)}`;
|
|
|
116
84
|
draft: true,
|
|
117
85
|
overrideAccess: false,
|
|
118
86
|
req: scope.req,
|
|
119
|
-
...
|
|
120
|
-
|
|
87
|
+
...definedProps({
|
|
88
|
+
locale,
|
|
89
|
+
file: bytes
|
|
90
|
+
})
|
|
121
91
|
});
|
|
122
|
-
return await
|
|
123
|
-
target
|
|
124
|
-
|
|
92
|
+
return await writtenResult(scope, {
|
|
93
|
+
target: {
|
|
94
|
+
...target,
|
|
95
|
+
id: created["id"]
|
|
96
|
+
},
|
|
125
97
|
locale
|
|
126
98
|
});
|
|
127
99
|
}
|
|
128
100
|
});
|
|
129
101
|
//#endregion
|
|
130
|
-
export { createDocument
|
|
102
|
+
export { createDocument };
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { defineMcpxTool } from "../define-tool.mjs";
|
|
2
2
|
import { errorResult, jsonResult } from "../result.mjs";
|
|
3
3
|
import { translateStatic } from "../i18n.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { expectedUpdatedAtShape, idSchema, slugsFor, stringEnum } from "./shared.mjs";
|
|
5
|
+
import { readDraft, resolveCollection, staleReadResult } from "./document.mjs";
|
|
6
6
|
import { withTrashIntent } from "../write/publish-intent.mjs";
|
|
7
7
|
import { withTransaction } from "../write/transaction.mjs";
|
|
8
8
|
import { z } from "zod";
|
|
9
|
-
import { Forbidden,
|
|
9
|
+
import { Forbidden, combineQueries, executeAccess } from "payload";
|
|
10
10
|
import { formatAdminURL } from "payload/shared";
|
|
11
11
|
//#region src/tools/delete-document.ts
|
|
12
12
|
const DESCRIPTION = `Asks to delete one document. This call deletes nothing: it returns a "confirmation" with an "id" and a "url". Give the url to the user, who approves or rejects in the admin panel, then pass the id to runConfirmed, which deletes. Refused when the document is unknown, already in trash, changed since "expectedUpdatedAt" or not deletable by the user.`;
|
|
@@ -58,9 +58,9 @@ const mayDelete = async (req, target) => {
|
|
|
58
58
|
},
|
|
59
59
|
isEnabled: (scope) => scope.uploads && scope.collections.deletable.length > 0,
|
|
60
60
|
inputSchema: (scope) => ({
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
collection: stringEnum(scope.collections.deletable),
|
|
62
|
+
id: idSchema,
|
|
63
|
+
...expectedUpdatedAtShape,
|
|
64
64
|
reason: z.string().max(500).optional().describe("Why, shown to the user who approves.")
|
|
65
65
|
}),
|
|
66
66
|
confirm: {
|
|
@@ -103,22 +103,19 @@ const mayDelete = async (req, target) => {
|
|
|
103
103
|
}
|
|
104
104
|
},
|
|
105
105
|
handler: async ({ args, scope, confirmation }) => {
|
|
106
|
-
const target =
|
|
106
|
+
const target = {
|
|
107
|
+
...resolveCollection(scope, args.collection, "delete"),
|
|
108
|
+
id: args.id
|
|
109
|
+
};
|
|
107
110
|
const { req } = scope;
|
|
108
111
|
const { payload } = req;
|
|
109
112
|
const trash = target.config.trash ?? false;
|
|
110
113
|
return await withTransaction(req, async () => {
|
|
111
|
-
const doc = await
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
draft: true,
|
|
116
|
-
trash: true,
|
|
117
|
-
overrideAccess: false,
|
|
118
|
-
disableErrors: true,
|
|
119
|
-
req
|
|
114
|
+
const doc = await readDraft(scope, {
|
|
115
|
+
target,
|
|
116
|
+
locale: void 0,
|
|
117
|
+
trash: true
|
|
120
118
|
});
|
|
121
|
-
if (!doc) throw new NotFound(req.t);
|
|
122
119
|
if (doc["deletedAt"]) return errorResult("The document is already in the trash. Emptying the trash is not available.");
|
|
123
120
|
const stale = staleReadResult(doc, args.expectedUpdatedAt, "The document changed since you read it. Read it again before deleting.");
|
|
124
121
|
if (stale) return stale;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { defineMcpxTool } from "../define-tool.mjs";
|
|
2
2
|
import { jsonResult } from "../result.mjs";
|
|
3
|
+
import { SchemaError } from "../schema/errors.mjs";
|
|
3
4
|
import { nodePropertiesFor } from "../schema/lexical.mjs";
|
|
4
5
|
import { translatorFor } from "../i18n.mjs";
|
|
5
|
-
import { SchemaError } from "../schema/errors.mjs";
|
|
6
6
|
import { nodeDescriber, reachableSchemaPaths } from "../schema/describe.mjs";
|
|
7
7
|
import "../schema/index.mjs";
|
|
8
|
-
import { entityShape } from "./shared.mjs";
|
|
8
|
+
import { entityShape, reaches } from "./shared.mjs";
|
|
9
9
|
import { resolveEntity } from "./document.mjs";
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
//#region src/tools/describe-schema.ts
|
|
@@ -30,7 +30,7 @@ const PATHS_LIMIT = 400;
|
|
|
30
30
|
readOnlyHint: true,
|
|
31
31
|
openWorldHint: false
|
|
32
32
|
},
|
|
33
|
-
isEnabled: (scope) => scope
|
|
33
|
+
isEnabled: (scope) => reaches(scope, "read"),
|
|
34
34
|
inputSchema: (scope) => ({
|
|
35
35
|
...entityShape(scope, "read", "Instead of \"collection\"."),
|
|
36
36
|
paths: z.array(z.string()).max(PATHS_LIMIT).optional().describe("Schema paths from \"next\". Omit for the top-level node."),
|
|
@@ -43,9 +43,12 @@ const PATHS_LIMIT = 400;
|
|
|
43
43
|
const expanded = args.expand === true ? reachableSchemaPaths(config, ref) : void 0;
|
|
44
44
|
const requested = expanded?.paths ?? (args.paths && args.paths.length > 0 ? args.paths : [""]);
|
|
45
45
|
const failures = [];
|
|
46
|
+
const nodeTypes = [];
|
|
46
47
|
const nodes = requested.map((schemaPath) => {
|
|
47
48
|
try {
|
|
48
|
-
|
|
49
|
+
const node = describeNode(config, ref, schemaPath);
|
|
50
|
+
nodeTypes.push(...node.fields.flatMap((field) => field.nodes ?? []));
|
|
51
|
+
return node;
|
|
49
52
|
} catch (error) {
|
|
50
53
|
if (error instanceof SchemaError) return {
|
|
51
54
|
error: error.message,
|
|
@@ -62,7 +65,6 @@ const PATHS_LIMIT = 400;
|
|
|
62
65
|
err: failures[0],
|
|
63
66
|
msg: `[payloadcms-mcpx] Describing ${String(failures.length)} schema paths failed.`
|
|
64
67
|
});
|
|
65
|
-
const nodeTypes = nodes.flatMap((node) => (node.fields ?? []).flatMap((field) => field.nodes ?? []));
|
|
66
68
|
if (nodeTypes.length > 0) nodes.push({ nodeProperties: nodePropertiesFor(nodeTypes) });
|
|
67
69
|
if (expanded?.truncated) nodes.push({ error: `Result truncated after ${String(400)} nodes. Request explicit paths instead.` });
|
|
68
70
|
return jsonResult(nodes);
|