@abinnovision/payloadcms-mcpx 2.0.0-beta.8 → 2.0.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/CHANGELOG.md +43 -0
- package/README.md +56 -36
- package/dist/admin/capability-matrix.d.mts +17 -7
- package/dist/admin/capability-matrix.mjs +374 -272
- package/dist/admin/capability-toggles.mjs +79 -42
- package/dist/admin/confirmations-panel.mjs +232 -0
- package/dist/admin/confirmations.d.mts +15 -0
- package/dist/admin/confirmations.mjs +71 -0
- package/dist/admin/endpoint-url.mjs +7 -0
- package/dist/admin/index.d.mts +2 -1
- package/dist/admin/index.mjs +2 -1
- package/dist/admin/setup-guide.d.mts +6 -4
- package/dist/admin/setup-guide.mjs +51 -33
- package/dist/api-keys/capability-matrix.d.mts +12 -10
- package/dist/api-keys/capability-matrix.mjs +17 -37
- package/dist/api-keys/collection.mjs +18 -19
- package/dist/api-keys/confirmation-view.mjs +7 -0
- package/dist/api-keys/fields.mjs +67 -53
- package/dist/auth/resolve.mjs +79 -30
- package/dist/capabilities.d.mts +2 -0
- package/dist/capabilities.mjs +69 -15
- package/dist/define-tool.d.mts +10 -6
- package/dist/define-tool.mjs +8 -1
- package/dist/endpoint/confirmations.mjs +124 -0
- package/dist/endpoint/handler.mjs +25 -27
- package/dist/endpoint/http.mjs +27 -0
- package/dist/endpoint/server.mjs +52 -27
- package/dist/endpoint/upload.mjs +104 -0
- package/dist/grants/confirmation.mjs +75 -0
- package/dist/grants/grant.mjs +137 -0
- package/dist/guards.mjs +11 -2
- package/dist/i18n.mjs +3 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/options.mjs +73 -62
- package/dist/plugin.d.mts +3 -2
- package/dist/plugin.mjs +19 -4
- 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 +21 -15
- package/dist/schema/outline.mjs +4 -5
- package/dist/schema/path.mjs +13 -4
- package/dist/schema/pointer.mjs +11 -17
- package/dist/schema/shape.mjs +39 -51
- package/dist/schema/walk.mjs +68 -21
- package/dist/tools/builtin.mjs +7 -1
- package/dist/tools/create-document.mjs +38 -26
- package/dist/tools/delete-document.mjs +154 -0
- package/dist/tools/describe-schema.mjs +7 -5
- package/dist/tools/document.mjs +147 -21
- package/dist/tools/duplicate-document.mjs +50 -0
- package/dist/tools/find-documents.mjs +15 -20
- package/dist/tools/find-versions.mjs +11 -15
- package/dist/tools/get-document.mjs +31 -15
- package/dist/tools/links.mjs +53 -0
- package/dist/tools/list-capabilities.mjs +17 -7
- package/dist/tools/patch-document.mjs +60 -54
- package/dist/tools/publish-document.mjs +23 -30
- package/dist/tools/query-paths.mjs +4 -2
- package/dist/tools/run-confirmed.mjs +114 -0
- package/dist/tools/shared.mjs +66 -50
- package/dist/tools/validate-document.mjs +10 -11
- package/dist/tools/versions.mjs +24 -10
- package/dist/types.d.mts +228 -60
- package/dist/upload/file.mjs +177 -0
- package/dist/version.mjs +4 -2
- package/dist/write/draft-guard.mjs +46 -37
- package/dist/write/patch.mjs +4 -4
- package/dist/write/publish-intent.mjs +25 -11
- package/dist/write/row-ids.mjs +6 -2
- package/dist/write/write-data.mjs +18 -31
- package/package.json +15 -14
- package/dist/builtin-tool-names.mjs +0 -16
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { isPlainObject } from "../guards.mjs";
|
|
2
|
-
import {
|
|
1
|
+
import { isPlainObject, propOf } from "../guards.mjs";
|
|
2
|
+
import { SchemaError } from "./errors.mjs";
|
|
3
|
+
import { editorRoot, lexicalSubSchema, subSchemaNodeTypes } from "./lexical.mjs";
|
|
3
4
|
import { isIndexSegment, joinPath } from "./path.mjs";
|
|
4
5
|
import { blockOf, blockSlugsOf } from "./walk.mjs";
|
|
5
|
-
import { SchemaError } from "./errors.mjs";
|
|
6
6
|
//#region src/schema/lexical-pointer.ts
|
|
7
7
|
const stepIntoFields = (at) => {
|
|
8
8
|
const { addedValue, config, field, node, nodeType, rest } = at;
|
|
@@ -15,8 +15,7 @@ const stepIntoFields = (at) => {
|
|
|
15
15
|
kind: "fields",
|
|
16
16
|
rest
|
|
17
17
|
};
|
|
18
|
-
const
|
|
19
|
-
const slug = (isPlainObject(data) ? data["blockType"] : void 0) ?? (isPlainObject(added) ? added["blockType"] : void 0);
|
|
18
|
+
const slug = propOf(data, "blockType") ?? propOf(propOf(addedValue, "fields"), "blockType");
|
|
20
19
|
if (slug === void 0) throw new SchemaError(`Cannot tell which block a "${nodeType}" node holds. Supply a "blockType" on the value, one of: ${blockSlugsOf(sub.blocksField).join(", ")}`);
|
|
21
20
|
if (typeof slug !== "string") throw new SchemaError(`"blockType" must be a string naming a block in a "${nodeType}" node here. Allowed: ${blockSlugsOf(sub.blocksField).join(", ")}`);
|
|
22
21
|
const block = blockOf(config, sub.blocksField, slug);
|
|
@@ -41,10 +40,11 @@ const stepIntoFields = (at) => {
|
|
|
41
40
|
descriptor,
|
|
42
41
|
field
|
|
43
42
|
};
|
|
44
|
-
|
|
43
|
+
const root = editorRoot(state);
|
|
44
|
+
if (!root) throw new SchemaError(`"${descriptor.path}" holds no editor state yet. Write the whole field once, then address positions inside it.`);
|
|
45
45
|
const [entry, ...rest] = at.segments;
|
|
46
46
|
if (entry !== "root") throw new SchemaError(`"${String(entry)}" is not a position in a rich text field. An editor state is entered at "root", e.g. "${descriptor.path}/root/children/0". getDocument with "outline" lists every position this field holds.`);
|
|
47
|
-
let node =
|
|
47
|
+
let node = root;
|
|
48
48
|
let nodeType = "root";
|
|
49
49
|
let segments = rest;
|
|
50
50
|
let walked = ["root"];
|
|
@@ -73,7 +73,7 @@ const stepIntoFields = (at) => {
|
|
|
73
73
|
if (!isIndexSegment(index)) throw new SchemaError(`"${descriptor.path}${joinPath([...walked, "children"])}" is a list; "${index}" is not an index. getDocument with "outline" reports the pointer of each node in it.`);
|
|
74
74
|
const children = node?.["children"];
|
|
75
75
|
const child = Array.isArray(children) && index !== "-" ? children[Number(index)] : void 0;
|
|
76
|
-
const type = isPlainObject(child) ? child["type"] : addedValue
|
|
76
|
+
const type = isPlainObject(child) ? child["type"] : propOf(addedValue, "type");
|
|
77
77
|
if (typeof type !== "string") {
|
|
78
78
|
if (beyond.length === 0) return {
|
|
79
79
|
kind: "position",
|
package/dist/schema/lexical.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isPlainObject, ownValue } from "../guards.mjs";
|
|
1
|
+
import { isPlainObject, ownValue, propOf } from "../guards.mjs";
|
|
2
2
|
import { flattenAllFields } from "payload";
|
|
3
3
|
//#region src/schema/lexical.ts
|
|
4
4
|
const LEXICAL_CORE_NODES = [
|
|
@@ -11,7 +11,9 @@ const LEXICAL_CORE_NODES = [
|
|
|
11
11
|
const OPAQUE_NODE_TYPES = /* @__PURE__ */ new Set(["upload"]);
|
|
12
12
|
const subSchemaCache = /* @__PURE__ */ new WeakMap();
|
|
13
13
|
const featuresOf = (field) => field.editor?.editorConfig?.features;
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Editors other than Lexical report only the core nodes.
|
|
16
|
+
*/ const allowedNodeTypes = (field) => {
|
|
15
17
|
const registered = (featuresOf(field)?.nodes ?? []).flatMap((entry) => {
|
|
16
18
|
const type = entry.node?.getType?.();
|
|
17
19
|
return type ? [type] : [];
|
|
@@ -41,7 +43,9 @@ const lexicalSubSchema = (field, nodeType) => {
|
|
|
41
43
|
if (!cached.has(nodeType)) cached.set(nodeType, resolveSubSchema(field, nodeType));
|
|
42
44
|
return cached.get(nodeType) ?? void 0;
|
|
43
45
|
};
|
|
44
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* In the order their features registered them.
|
|
48
|
+
*/ const subSchemaNodeTypes = (field) => [...featuresOf(field)?.getSubFields?.keys() ?? []].filter((nodeType) => lexicalSubSchema(field, nodeType) !== void 0);
|
|
45
49
|
const KINDS = {
|
|
46
50
|
array: {
|
|
47
51
|
accepts: (value) => Array.isArray(value),
|
|
@@ -144,6 +148,16 @@ const UNIVERSAL_PROPERTIES = { version: "number" };
|
|
|
144
148
|
}
|
|
145
149
|
};
|
|
146
150
|
/**
|
|
151
|
+
* The `root` node of an editor state, if it has one.
|
|
152
|
+
*/ const editorRoot = (state) => {
|
|
153
|
+
const root = propOf(state, "root");
|
|
154
|
+
return isPlainObject(root) ? root : void 0;
|
|
155
|
+
};
|
|
156
|
+
const constraintsFor = (type) => ({
|
|
157
|
+
...UNIVERSAL_PROPERTIES,
|
|
158
|
+
...ownValue(REQUIRED_NODE_PROPERTIES, type)
|
|
159
|
+
});
|
|
160
|
+
/**
|
|
147
161
|
* Whether the table already says what a node type's `fields` must be, so the
|
|
148
162
|
* sub-field walk does not report the same problem twice.
|
|
149
163
|
*/ const constrainsFields = (type) => "fields" in (ownValue(REQUIRED_NODE_PROPERTIES, type) ?? {});
|
|
@@ -155,8 +169,7 @@ const describeConstraints = (constraints) => Object.fromEntries(Object.entries(c
|
|
|
155
169
|
* {@link UNIVERSAL_PROPERTIES} omits it: the validator finds the entry by
|
|
156
170
|
* `type` and so never reports it missing, but a client must still write one.
|
|
157
171
|
*/ const nodePropertiesFor = (types) => Object.fromEntries([...new Set(types)].sort().map((type) => [type, describeConstraints(type === "root" ? ROOT_PROPERTIES : {
|
|
158
|
-
...
|
|
159
|
-
...UNIVERSAL_PROPERTIES,
|
|
172
|
+
...constraintsFor(type),
|
|
160
173
|
type: "string"
|
|
161
174
|
})]));
|
|
162
175
|
const check = (node, constraints) => {
|
|
@@ -173,10 +186,7 @@ const check = (node, constraints) => {
|
|
|
173
186
|
problems.rejected.sort((left, right) => left.property.localeCompare(right.property));
|
|
174
187
|
return problems;
|
|
175
188
|
};
|
|
176
|
-
const nodeProblems = (node) => check(node,
|
|
177
|
-
...UNIVERSAL_PROPERTIES,
|
|
178
|
-
...ownValue(REQUIRED_NODE_PROPERTIES, node["type"])
|
|
179
|
-
});
|
|
189
|
+
const nodeProblems = (node) => check(node, constraintsFor(node["type"]));
|
|
180
190
|
/**
|
|
181
191
|
* The root is the one node Payload describes itself, down to refusing an
|
|
182
192
|
* unknown property, so it is checked against that description instead of the
|
|
@@ -190,11 +200,7 @@ const nodeProblems = (node) => check(node, {
|
|
|
190
200
|
* instead of a whole node. Absent where the table says nothing, matching the
|
|
191
201
|
* node walk: a project's own node may carry any property.
|
|
192
202
|
*/ const propertyProblem = (nodeType, property, value) => {
|
|
193
|
-
const
|
|
194
|
-
...UNIVERSAL_PROPERTIES,
|
|
195
|
-
...ownValue(REQUIRED_NODE_PROPERTIES, nodeType)
|
|
196
|
-
};
|
|
197
|
-
const constraint = ownValue(constraints, property);
|
|
203
|
+
const constraint = ownValue(nodeType === "root" ? ROOT_PROPERTIES : constraintsFor(nodeType), property);
|
|
198
204
|
return constraint === void 0 || accepts(constraint, value) ? void 0 : { needs: needs(constraint) };
|
|
199
205
|
};
|
|
200
206
|
const NODE_OPTION_SOURCES = [{
|
|
@@ -229,4 +235,4 @@ const stringList = (value) => Array.isArray(value) && value.every((entry) => typ
|
|
|
229
235
|
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
|
|
230
236
|
};
|
|
231
237
|
//#endregion
|
|
232
|
-
export { REQUIRED_NODE_PROPERTIES, ROOT_PROPERTIES, allowedNodeTypes, constrainsFields, lexicalSubSchema, nodeOptions, nodeProblems, nodePropertiesFor, propertyProblem, rootProblems, subSchemaNodeTypes };
|
|
238
|
+
export { REQUIRED_NODE_PROPERTIES, ROOT_PROPERTIES, allowedNodeTypes, constrainsFields, editorRoot, lexicalSubSchema, nodeOptions, nodeProblems, nodePropertiesFor, propertyProblem, rootProblems, subSchemaNodeTypes };
|
package/dist/schema/outline.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isPlainObject, ownValue } from "../guards.mjs";
|
|
2
|
-
import { allowedNodeTypes, nodeOptions } from "./lexical.mjs";
|
|
2
|
+
import { allowedNodeTypes, editorRoot, nodeOptions } from "./lexical.mjs";
|
|
3
3
|
//#region src/schema/outline.ts
|
|
4
4
|
const TEXT_PREVIEW_LENGTH = 80;
|
|
5
5
|
const collectText = (node) => {
|
|
@@ -43,12 +43,11 @@ const walk = (node, pointer, options, entries) => {
|
|
|
43
43
|
* entry's `pointer` extends it with "/root" and the node's real indices, which
|
|
44
44
|
* makes it directly usable in a patch operation.
|
|
45
45
|
*/ const lexicalOutline = (state, basePointer, field) => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (!isPlainObject(root) || !Array.isArray(root["children"])) return [];
|
|
46
|
+
const rootChildren = editorRoot(state)?.["children"];
|
|
47
|
+
if (!Array.isArray(rootChildren)) return [];
|
|
49
48
|
const options = nodeOptions(field, allowedNodeTypes(field));
|
|
50
49
|
const entries = [];
|
|
51
|
-
|
|
50
|
+
rootChildren.forEach((child, index) => {
|
|
52
51
|
if (isPlainObject(child)) walk(child, `${basePointer}/root/children/${String(index)}`, options, entries);
|
|
53
52
|
});
|
|
54
53
|
return entries;
|
package/dist/schema/path.mjs
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
//#region src/schema/path.ts
|
|
2
2
|
const JSON_POINTER_PATTERN = /^(\/([^~/]|~[01])*)*$/;
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* No segments is the root pointer, `""`.
|
|
5
|
+
*/ const joinPath = (parts) => parts.map((part) => `/${part.replace(/~/g, "~0").replace(/\//g, "~1")}`).join("");
|
|
6
|
+
/**
|
|
7
|
+
* Unescapes `~1` and `~0`. The root pointer yields no segments.
|
|
8
|
+
*/ const splitPath = (path) => path.split("/").slice(1).map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
5
9
|
const PROTOTYPE_SEGMENTS = /* @__PURE__ */ new Set([
|
|
6
10
|
"__proto__",
|
|
7
11
|
"constructor",
|
|
@@ -12,11 +16,16 @@ const PROTOTYPE_SEGMENTS = /* @__PURE__ */ new Set([
|
|
|
12
16
|
* intermediate tokens but writes them as a final key, so a pointer containing
|
|
13
17
|
* one is refused before anything resolves it.
|
|
14
18
|
*/ const prototypeSegmentProblem = (pointer) => splitPath(pointer).some((segment) => PROTOTYPE_SEGMENTS.has(segment)) ? `"${pointer}" contains a segment named __proto__, constructor or prototype, which no field or node property uses.` : void 0;
|
|
15
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* `-` included, since RFC 6901 reads it as the position after the last.
|
|
21
|
+
*/ const isIndexSegment = (segment) => segment === "-" || /^\d+$/.test(segment);
|
|
16
22
|
/**
|
|
17
23
|
* A path Payload reports on a validation error (`layout.0.title`) as a JSON
|
|
18
24
|
* Pointer, so everything handed back addresses documents the same way. The
|
|
19
25
|
* path already carries real indices, so it maps directly.
|
|
20
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));
|
|
21
30
|
//#endregion
|
|
22
|
-
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,50 +175,40 @@ 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) => {
|
|
224
212
|
const problems = [];
|
|
225
213
|
const scope = {
|
|
226
214
|
config,
|
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";
|
|
@@ -10,12 +12,17 @@ const RESERVED_FIELD_NAMES = /* @__PURE__ */ new Set([
|
|
|
10
12
|
"id",
|
|
11
13
|
"updatedAt"
|
|
12
14
|
]);
|
|
13
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* The keys a row carries beside its fields.
|
|
17
|
+
*/ const ROW_KEYS = /* @__PURE__ */ new Set([
|
|
14
18
|
"blockName",
|
|
15
19
|
"blockType",
|
|
16
20
|
"id"
|
|
17
21
|
]);
|
|
18
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* On a flattened field, whichever of `blockReferences` and `blocks` was
|
|
24
|
+
* declared.
|
|
25
|
+
*/ const blockSlugsOf = (field) => [...new Set((field.blockReferences ?? field.blocks).map((block) => typeof block === "string" ? block : block.slug))];
|
|
19
26
|
/**
|
|
20
27
|
* An inlined definition wins over the registry (`config.blocks`). A block's own
|
|
21
28
|
* fields are identical wherever it appears, but the blocks its children accept
|
|
@@ -29,9 +36,17 @@ const RESERVED_FIELD_NAMES = /* @__PURE__ */ new Set([
|
|
|
29
36
|
/**
|
|
30
37
|
* Payload's `fieldIsHiddenOrDisabled` reads `hidden` and `admin.disabled`, not
|
|
31
38
|
* `admin.hidden`, which its own upload base fields carry.
|
|
32
|
-
*/ const
|
|
33
|
-
const
|
|
34
|
-
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);
|
|
35
50
|
const describeBase = (field, { path, readOnly, translate }) => {
|
|
36
51
|
const description = translate("admin" in field ? field.admin.description : void 0);
|
|
37
52
|
return {
|
|
@@ -66,17 +81,11 @@ const withRows = (descriptor, field) => ({
|
|
|
66
81
|
});
|
|
67
82
|
const isContainer = (descriptor) => descriptor.type === "array" || descriptor.type === "group" || descriptor.type === "tab";
|
|
68
83
|
const isInformative = (descriptor) => descriptor.description !== void 0 || descriptor.required === true || descriptor.localized === true;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
* themselves only when they declare something of their own: an array always,
|
|
72
|
-
* since its row counts live nowhere else. The walk stops at every blocks field
|
|
73
|
-
* and names the slugs, so a node's size follows the number of blocks it
|
|
74
|
-
* allows, not their definitions. Omitting `translate` costs language
|
|
75
|
-
* selection, never the description itself.
|
|
76
|
-
*/ const describeFields = (fields, translate = translateAny) => {
|
|
84
|
+
const describedByFields = /* @__PURE__ */ new WeakMap();
|
|
85
|
+
const describeWith = (fields, translate) => {
|
|
77
86
|
const walk = (current, prefix, parentReadOnly) => current.flatMap((field) => {
|
|
78
87
|
if (isSkipped(field)) return [];
|
|
79
|
-
const readOnly = parentReadOnly ||
|
|
88
|
+
const readOnly = parentReadOnly || adminFlag(field, "readOnly");
|
|
80
89
|
const path = [...prefix, field.name];
|
|
81
90
|
const at = {
|
|
82
91
|
path: joinPath(path),
|
|
@@ -97,11 +106,30 @@ const isInformative = (descriptor) => descriptor.description !== void 0 || descr
|
|
|
97
106
|
return walk(fields, [], false);
|
|
98
107
|
};
|
|
99
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
|
+
/**
|
|
100
125
|
* The descriptors that address a value, which every walk resolving a path
|
|
101
126
|
* against a document needs. Containers are left out, since only
|
|
102
127
|
* {@link nodeDescriber} reports one.
|
|
103
128
|
*/ const describeAddressableFields = (fields) => describeFields(fields).filter((descriptor) => !isContainer(descriptor));
|
|
104
|
-
/**
|
|
129
|
+
/**
|
|
130
|
+
* The addressable descriptors under `prefix`, with their paths split relative
|
|
131
|
+
* to it.
|
|
132
|
+
*/ const descriptorsUnder = (fields, prefix) => describeAddressableFields(fields).flatMap((descriptor) => {
|
|
105
133
|
const parts = splitPath(descriptor.path);
|
|
106
134
|
return prefix.every((part, offset) => part === parts[offset]) ? [{
|
|
107
135
|
descriptor,
|
|
@@ -116,11 +144,30 @@ const findFieldAt = (fields, path, type) => {
|
|
|
116
144
|
if (field.type === "array" && path[1] === "*") return findFieldAt(field.flattenedFields, path.slice(2), type);
|
|
117
145
|
}
|
|
118
146
|
};
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
+
};
|
|
124
171
|
/**
|
|
125
172
|
* Looks up the sanitized config for a collection or global as an
|
|
126
173
|
* {@link EntitySchema}. Throws on an unknown slug instead of returning
|
|
@@ -132,4 +179,4 @@ const findBlocksField = (fields, path) => findFieldAt(fields, path, "blocks");
|
|
|
132
179
|
return found;
|
|
133
180
|
};
|
|
134
181
|
//#endregion
|
|
135
|
-
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 };
|
package/dist/tools/builtin.mjs
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { createDocument } from "./create-document.mjs";
|
|
2
|
+
import { deleteDocument } from "./delete-document.mjs";
|
|
2
3
|
import { describeSchema } from "./describe-schema.mjs";
|
|
4
|
+
import { duplicateDocument } from "./duplicate-document.mjs";
|
|
3
5
|
import { findDocuments } from "./find-documents.mjs";
|
|
4
6
|
import { findVersions } from "./find-versions.mjs";
|
|
5
7
|
import { getDocument } from "./get-document.mjs";
|
|
6
8
|
import { listCapabilities } from "./list-capabilities.mjs";
|
|
7
9
|
import { patchDocument } from "./patch-document.mjs";
|
|
8
10
|
import { publishDocument } from "./publish-document.mjs";
|
|
11
|
+
import { createRunConfirmed } from "./run-confirmed.mjs";
|
|
9
12
|
import { validateDocument } from "./validate-document.mjs";
|
|
10
13
|
//#region src/tools/builtin.ts
|
|
11
14
|
/**
|
|
@@ -21,8 +24,11 @@ import { validateDocument } from "./validate-document.mjs";
|
|
|
21
24
|
findVersions,
|
|
22
25
|
patchDocument,
|
|
23
26
|
createDocument,
|
|
27
|
+
duplicateDocument,
|
|
24
28
|
validateDocument,
|
|
25
|
-
publishDocument
|
|
29
|
+
publishDocument,
|
|
30
|
+
deleteDocument,
|
|
31
|
+
createRunConfirmed(() => BUILTIN_TOOLS)
|
|
26
32
|
];
|
|
27
33
|
//#endregion
|
|
28
34
|
export { BUILTIN_TOOLS };
|