@abinnovision/payloadcms-mcpx 1.1.0-beta.6 → 2.0.0-beta.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +144 -470
  2. package/dist/admin/capability-matrix.d.mts +33 -0
  3. package/dist/admin/capability-matrix.mjs +354 -0
  4. package/dist/admin/capability-toggles.mjs +73 -0
  5. package/dist/{client → admin}/setup-guide.d.mts +4 -2
  6. package/dist/{client → admin}/setup-guide.mjs +3 -10
  7. package/dist/api-keys/capability-matrix.d.mts +12 -8
  8. package/dist/api-keys/capability-matrix.mjs +14 -86
  9. package/dist/api-keys/collection.mjs +2 -1
  10. package/dist/api-keys/fields.mjs +36 -24
  11. package/dist/auth/resolve.mjs +71 -23
  12. package/dist/{tools/names.mjs → builtin-tool-names.mjs} +1 -1
  13. package/dist/capabilities.mjs +44 -46
  14. package/dist/define-tool.d.mts +23 -0
  15. package/dist/{types.mjs → define-tool.mjs} +1 -1
  16. package/dist/endpoint/errors.mjs +5 -6
  17. package/dist/endpoint/handler.mjs +73 -29
  18. package/dist/endpoint/server.mjs +85 -22
  19. package/dist/endpoint/upload.mjs +135 -0
  20. package/dist/entity.d.mts +9 -0
  21. package/dist/guards.mjs +7 -0
  22. package/dist/i18n.mjs +10 -8
  23. package/dist/index.d.mts +5 -2
  24. package/dist/index.mjs +3 -2
  25. package/dist/options.mjs +54 -47
  26. package/dist/plugin.d.mts +2 -2
  27. package/dist/plugin.mjs +13 -3
  28. package/dist/request.d.mts +2 -3
  29. package/dist/request.mjs +2 -3
  30. package/dist/result.d.mts +2 -3
  31. package/dist/result.mjs +3 -3
  32. package/dist/schema/admin-hidden.mjs +91 -0
  33. package/dist/schema/describe.mjs +17 -33
  34. package/dist/schema/errors.mjs +8 -0
  35. package/dist/schema/index.mjs +5 -2
  36. package/dist/schema/lexical-pointer.mjs +18 -20
  37. package/dist/schema/lexical.mjs +44 -93
  38. package/dist/schema/outline.mjs +5 -15
  39. package/dist/schema/path.mjs +28 -0
  40. package/dist/schema/pointer.mjs +18 -25
  41. package/dist/schema/shape.mjs +25 -71
  42. package/dist/schema/walk.mjs +43 -45
  43. package/dist/tools/builtin.mjs +4 -4
  44. package/dist/tools/create-document.mjs +50 -24
  45. package/dist/tools/describe-schema.mjs +38 -34
  46. package/dist/tools/document.mjs +124 -0
  47. package/dist/tools/find-documents.mjs +32 -19
  48. package/dist/tools/find-versions.mjs +22 -18
  49. package/dist/tools/get-document.mjs +60 -33
  50. package/dist/tools/list-capabilities.mjs +35 -19
  51. package/dist/tools/patch-document.mjs +122 -66
  52. package/dist/tools/publish-document.mjs +26 -32
  53. package/dist/tools/query-paths.mjs +57 -0
  54. package/dist/tools/read-request.d.mts +10 -0
  55. package/dist/tools/read-request.mjs +43 -0
  56. package/dist/tools/shared.mjs +74 -105
  57. package/dist/tools/validate-document.mjs +18 -30
  58. package/dist/tools/versions.mjs +37 -20
  59. package/dist/types.d.mts +135 -75
  60. package/dist/upload/file.mjs +166 -0
  61. package/dist/upload/grant.mjs +96 -0
  62. package/dist/version.mjs +3 -1
  63. package/dist/write/draft-guard.mjs +23 -41
  64. package/dist/write/patch.mjs +27 -183
  65. package/dist/write/publish-blockers.mjs +7 -14
  66. package/dist/write/publish-intent.mjs +6 -4
  67. package/dist/write/row-ids.mjs +46 -0
  68. package/dist/write/transaction.mjs +5 -8
  69. package/dist/write/write-data.mjs +64 -0
  70. package/package.json +11 -10
  71. package/dist/client/capability-matrix.d.mts +0 -26
  72. package/dist/client/capability-matrix.mjs +0 -349
  73. package/dist/endpoint/index.mjs +0 -4
  74. package/dist/tools/target.mjs +0 -45
  75. /package/dist/{client → admin}/index.d.mts +0 -0
  76. /package/dist/{client → admin}/index.mjs +0 -0
@@ -0,0 +1,33 @@
1
+ import { CapabilityMatrix } from "../api-keys/capability-matrix.mjs";
2
+ import React from "react";
3
+ //#region src/admin/capability-matrix.d.ts
4
+ interface McpxCapabilityMatrixProps {
5
+ /**
6
+ * Built from the plugin options at config time.
7
+ */
8
+ matrix: CapabilityMatrix;
9
+ /**
10
+ * The group's own config, supplied by Payload. Carries the description.
11
+ */
12
+ field?: {
13
+ admin?: {
14
+ description?: unknown;
15
+ };
16
+ };
17
+ /**
18
+ * The `capabilities` group's own path, supplied by Payload.
19
+ */
20
+ path?: string;
21
+ readOnly?: boolean;
22
+ /**
23
+ * Whether the key form is split into tabs, from the plugin options.
24
+ */
25
+ withinTab?: boolean;
26
+ }
27
+ /**
28
+ * What an API key may do, as one table per namespace: a row per entity, a
29
+ * column per operation and a bulk toggle in each column header. The nested
30
+ * checkbox fields back every cell, so the stored document is unaffected.
31
+ */
32
+ export declare const McpxCapabilityMatrix: React.FC<McpxCapabilityMatrixProps>;
33
+ //#endregion
@@ -0,0 +1,354 @@
1
+ "use client";
2
+ import { CAPABILITIES_FIELD } from "../capabilities.mjs";
3
+ import { CAPABILITY_OPERATIONS, capabilityPaths, cellPath, toolPath } from "../api-keys/capability-matrix.mjs";
4
+ import { buildToggleActions, columnState, rowState, toolsState } from "./capability-toggles.mjs";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { CheckboxInput, FieldDescription, FieldLabel, useForm, useFormFields } from "@payloadcms/ui";
7
+ import { useCallback, useMemo } from "react";
8
+ //#region src/admin/capability-matrix.tsx
9
+ const NOT_EXPOSED = "The plugin config does not expose this.";
10
+ const BASE_CLASS = "mcpx-capabilities";
11
+ const SHEET = `
12
+ .${BASE_CLASS} tbody tr:hover { background: var(--theme-elevation-50); }
13
+ .${BASE_CLASS}__toggle:hover { text-decoration: underline; }
14
+ .${BASE_CLASS} section:last-of-type { margin-bottom: 0; }
15
+ `;
16
+ const CELL_PADDING = "calc(var(--base) * 0.6)";
17
+ const EDGE_PADDING = "calc(var(--base) * 0.8)";
18
+ const styles = {
19
+ section: { marginBottom: "var(--base)" },
20
+ table: {
21
+ tableLayout: "fixed",
22
+ width: "auto",
23
+ borderCollapse: "separate",
24
+ borderSpacing: 0,
25
+ textAlign: "left"
26
+ },
27
+ head: { background: "var(--theme-elevation-50)" },
28
+ headTitle: {
29
+ padding: `${CELL_PADDING} ${CELL_PADDING} ${CELL_PADDING} ${EDGE_PADDING}`,
30
+ verticalAlign: "middle",
31
+ fontSize: "0.8em",
32
+ letterSpacing: "0.08em",
33
+ textTransform: "uppercase",
34
+ color: "var(--theme-elevation-400)",
35
+ fontWeight: "normal"
36
+ },
37
+ headOperation: {
38
+ padding: CELL_PADDING,
39
+ verticalAlign: "middle",
40
+ color: "var(--theme-elevation-400)",
41
+ fontWeight: "normal",
42
+ textAlign: "center",
43
+ whiteSpace: "nowrap"
44
+ },
45
+ headLabel: {
46
+ padding: `${CELL_PADDING} ${CELL_PADDING} ${CELL_PADDING} ${EDGE_PADDING}`,
47
+ verticalAlign: "middle",
48
+ color: "var(--theme-elevation-500)",
49
+ fontWeight: "normal"
50
+ },
51
+ rule: { borderBottom: "1px solid var(--theme-border-color)" },
52
+ topStart: { borderStartStartRadius: "var(--style-radius-s)" },
53
+ topEnd: { borderStartEndRadius: "var(--style-radius-s)" },
54
+ rowHeader: {
55
+ padding: `${CELL_PADDING} ${CELL_PADDING} ${CELL_PADDING} ${EDGE_PADDING}`,
56
+ verticalAlign: "middle",
57
+ fontWeight: "normal",
58
+ wordBreak: "break-word"
59
+ },
60
+ rowButton: {
61
+ padding: 0,
62
+ border: 0,
63
+ background: "none",
64
+ color: "inherit",
65
+ font: "inherit",
66
+ cursor: "pointer",
67
+ textAlign: "start",
68
+ textDecoration: "none"
69
+ },
70
+ hint: {
71
+ display: "block",
72
+ color: "var(--theme-elevation-400)"
73
+ },
74
+ cell: {
75
+ padding: CELL_PADDING,
76
+ verticalAlign: "middle",
77
+ textAlign: "center"
78
+ },
79
+ lastCell: { paddingInlineEnd: EDGE_PADDING },
80
+ dash: {
81
+ color: "var(--theme-elevation-400)",
82
+ cursor: "help"
83
+ }
84
+ };
85
+ const Box = ({ label, onChange, readOnly, state }) => /*#__PURE__*/ jsx(CheckboxInput, {
86
+ checked: state === "on",
87
+ name: label,
88
+ onToggle: onChange,
89
+ partialChecked: state === "mixed",
90
+ readOnly
91
+ });
92
+ const LABEL_WIDTH = "calc(var(--base) * 9)";
93
+ const OPERATION_WIDTH = "calc(var(--base) * 3.5)";
94
+ const Columns = ({ count }) => /*#__PURE__*/ jsxs("colgroup", { children: [/*#__PURE__*/ jsx("col", { style: { width: LABEL_WIDTH } }), Array.from({ length: count }, (_, index) => /*#__PURE__*/ jsx("col", { style: { width: OPERATION_WIDTH } }, index))] });
95
+ const cellStyle = (isLast) => isLast ? {
96
+ ...styles.cell,
97
+ ...styles.lastCell
98
+ } : styles.cell;
99
+ const Dash = () => /*#__PURE__*/ jsx("span", {
100
+ "aria-label": NOT_EXPOSED,
101
+ style: styles.dash,
102
+ title: NOT_EXPOSED,
103
+ children: "—"
104
+ });
105
+ const NamespaceTable = ({ matrix, namespace, path, readOnly, toggle, values }) => {
106
+ const rows = matrix[namespace.id];
107
+ const columns = CAPABILITY_OPERATIONS.filter((operation) => rows.some((row) => row[operation.id]));
108
+ return /*#__PURE__*/ jsx("section", {
109
+ style: styles.section,
110
+ children: /*#__PURE__*/ jsxs("table", {
111
+ style: styles.table,
112
+ children: [
113
+ /*#__PURE__*/ jsx(Columns, { count: columns.length }),
114
+ /*#__PURE__*/ jsxs("thead", {
115
+ style: styles.head,
116
+ children: [/*#__PURE__*/ jsxs("tr", { children: [/*#__PURE__*/ jsx("th", {
117
+ scope: "col",
118
+ style: {
119
+ ...styles.headTitle,
120
+ ...styles.topStart
121
+ },
122
+ children: namespace.title
123
+ }), columns.map((operation, column) => /*#__PURE__*/ jsx("th", {
124
+ scope: "col",
125
+ style: column === columns.length - 1 ? {
126
+ ...styles.headOperation,
127
+ ...styles.topEnd
128
+ } : styles.headOperation,
129
+ title: operation.description,
130
+ children: operation.label
131
+ }, operation.id))] }), /*#__PURE__*/ jsxs("tr", { children: [/*#__PURE__*/ jsx("th", {
132
+ scope: "row",
133
+ style: {
134
+ ...styles.headLabel,
135
+ ...styles.rule
136
+ },
137
+ children: "All"
138
+ }), columns.map((operation, column) => {
139
+ const state = columnState(matrix, path, namespace.id, operation.id, values);
140
+ return /*#__PURE__*/ jsx("td", {
141
+ style: {
142
+ ...cellStyle(column === columns.length - 1),
143
+ ...styles.rule
144
+ },
145
+ children: /*#__PURE__*/ jsx(Box, {
146
+ label: `${operation.label} every ${namespace.title.toLowerCase()} entry`,
147
+ onChange: () => {
148
+ toggle({
149
+ kind: "column",
150
+ namespace: namespace.id,
151
+ operation: operation.id,
152
+ value: state !== "on"
153
+ });
154
+ },
155
+ readOnly,
156
+ state
157
+ })
158
+ }, operation.id);
159
+ })] })]
160
+ }),
161
+ /*#__PURE__*/ jsx("tbody", { children: rows.map((row) => {
162
+ const state = rowState(path, namespace.id, row, values);
163
+ return /*#__PURE__*/ jsxs("tr", { children: [/*#__PURE__*/ jsxs("th", {
164
+ scope: "row",
165
+ style: styles.rowHeader,
166
+ children: [/*#__PURE__*/ jsx("button", {
167
+ className: `${BASE_CLASS}__toggle`,
168
+ disabled: readOnly,
169
+ onClick: () => {
170
+ toggle({
171
+ kind: "row",
172
+ namespace: namespace.id,
173
+ fieldName: row.fieldName,
174
+ value: state !== "on"
175
+ });
176
+ },
177
+ style: styles.rowButton,
178
+ title: `Toggle every capability for ${row.label}`,
179
+ type: "button",
180
+ children: row.label
181
+ }), row.hint ? /*#__PURE__*/ jsx("span", {
182
+ style: styles.hint,
183
+ children: row.hint
184
+ }) : null]
185
+ }), columns.map((operation, column) => /*#__PURE__*/ jsx("td", {
186
+ style: cellStyle(column === columns.length - 1),
187
+ children: row[operation.id] ? /*#__PURE__*/ jsx(Box, {
188
+ label: `${operation.label} ${row.label}`,
189
+ onChange: () => {
190
+ toggle({
191
+ kind: "cell",
192
+ namespace: namespace.id,
193
+ fieldName: row.fieldName,
194
+ operation: operation.id,
195
+ value: values[cellPath(path, namespace.id, row.fieldName, operation.id)] !== true
196
+ });
197
+ },
198
+ readOnly,
199
+ state: values[cellPath(path, namespace.id, row.fieldName, operation.id)] === true ? "on" : "off"
200
+ }) : /*#__PURE__*/ jsx(Dash, {})
201
+ }, operation.id))] }, row.fieldName);
202
+ }) })
203
+ ]
204
+ })
205
+ });
206
+ };
207
+ const ToolsTable = ({ matrix, path, readOnly, toggle, values }) => {
208
+ const toolState = toolsState(matrix, path, values);
209
+ return /*#__PURE__*/ jsx("section", {
210
+ style: styles.section,
211
+ children: /*#__PURE__*/ jsxs("table", {
212
+ style: styles.table,
213
+ children: [
214
+ /*#__PURE__*/ jsx(Columns, { count: 1 }),
215
+ /*#__PURE__*/ jsxs("thead", {
216
+ style: styles.head,
217
+ children: [/*#__PURE__*/ jsxs("tr", { children: [/*#__PURE__*/ jsx("th", {
218
+ scope: "col",
219
+ style: {
220
+ ...styles.headTitle,
221
+ ...styles.topStart
222
+ },
223
+ children: "Tools"
224
+ }), /*#__PURE__*/ jsx("th", {
225
+ scope: "col",
226
+ style: {
227
+ ...styles.headOperation,
228
+ ...styles.topEnd
229
+ },
230
+ children: "Enabled"
231
+ })] }), /*#__PURE__*/ jsxs("tr", { children: [/*#__PURE__*/ jsx("th", {
232
+ scope: "row",
233
+ style: {
234
+ ...styles.headLabel,
235
+ ...styles.rule
236
+ },
237
+ children: "All"
238
+ }), /*#__PURE__*/ jsx("td", {
239
+ style: {
240
+ ...cellStyle(true),
241
+ ...styles.rule
242
+ },
243
+ children: /*#__PURE__*/ jsx(Box, {
244
+ label: "Enable every tool",
245
+ onChange: () => {
246
+ toggle({
247
+ kind: "tools",
248
+ value: toolState !== "on"
249
+ });
250
+ },
251
+ readOnly,
252
+ state: toolState
253
+ })
254
+ })] })]
255
+ }),
256
+ /*#__PURE__*/ jsx("tbody", { children: matrix.tools.map((tool) => /*#__PURE__*/ jsxs("tr", { children: [/*#__PURE__*/ jsxs("th", {
257
+ scope: "row",
258
+ style: styles.rowHeader,
259
+ children: [tool.name, /*#__PURE__*/ jsx("span", {
260
+ style: styles.hint,
261
+ children: tool.description
262
+ })]
263
+ }), /*#__PURE__*/ jsx("td", {
264
+ style: cellStyle(true),
265
+ children: /*#__PURE__*/ jsx(Box, {
266
+ label: `Enable ${tool.name}`,
267
+ onChange: () => {
268
+ toggle({
269
+ kind: "tool",
270
+ name: tool.name,
271
+ value: values[toolPath(path, tool.name)] !== true
272
+ });
273
+ },
274
+ readOnly,
275
+ state: values[toolPath(path, tool.name)] === true ? "on" : "off"
276
+ })
277
+ })] }, tool.name)) })
278
+ ]
279
+ })
280
+ });
281
+ };
282
+ /**
283
+ * What an API key may do, as one table per namespace: a row per entity, a
284
+ * column per operation and a bulk toggle in each column header. The nested
285
+ * checkbox fields back every cell, so the stored document is unaffected.
286
+ */ const McpxCapabilityMatrix = ({ field, matrix, path = CAPABILITIES_FIELD, readOnly = false, withinTab = false }) => {
287
+ const { dispatchFields, setModified } = useForm();
288
+ const paths = useMemo(() => capabilityPaths(matrix, path), [matrix, path]);
289
+ const encoded = useFormFields(([fields]) => paths.map((cell) => fields[cell]?.value === true ? "1" : "0").join(""));
290
+ const values = useMemo(() => Object.fromEntries(paths.map((cell, index) => [cell, encoded[index] === "1"])), [paths, encoded]);
291
+ const toggle = useCallback((intent) => {
292
+ const actions = buildToggleActions(matrix, path, values, intent);
293
+ if (actions.length === 0) return;
294
+ for (const action of actions) dispatchFields(action);
295
+ setModified(true);
296
+ }, [
297
+ matrix,
298
+ path,
299
+ values,
300
+ dispatchFields,
301
+ setModified
302
+ ]);
303
+ const namespaces = [{
304
+ id: "collections",
305
+ title: "Collections"
306
+ }, {
307
+ id: "globals",
308
+ title: "Globals"
309
+ }].filter((namespace) => matrix[namespace.id].length > 0);
310
+ return /*#__PURE__*/ jsxs("div", {
311
+ className: [
312
+ "field-type",
313
+ "group-field",
314
+ "group-field--top-level",
315
+ withinTab && "group-field--within-tab",
316
+ BASE_CLASS
317
+ ].filter(Boolean).join(" "),
318
+ children: [/*#__PURE__*/ jsx("style", { children: SHEET }), /*#__PURE__*/ jsxs("div", {
319
+ className: "group-field__wrap",
320
+ children: [
321
+ /*#__PURE__*/ jsx("div", {
322
+ className: "group-field__header",
323
+ children: /*#__PURE__*/ jsxs("header", { children: [/*#__PURE__*/ jsx("h3", {
324
+ className: "group-field__title",
325
+ children: /*#__PURE__*/ jsx(FieldLabel, {
326
+ as: "span",
327
+ label: "Capabilities"
328
+ })
329
+ }), typeof field?.admin?.description === "string" ? /*#__PURE__*/ jsx(FieldDescription, {
330
+ description: field.admin.description,
331
+ path
332
+ }) : null] })
333
+ }),
334
+ namespaces.map((namespace) => /*#__PURE__*/ jsx(NamespaceTable, {
335
+ matrix,
336
+ namespace,
337
+ path,
338
+ readOnly,
339
+ toggle,
340
+ values
341
+ }, namespace.id)),
342
+ matrix.tools.length > 0 ? /*#__PURE__*/ jsx(ToolsTable, {
343
+ matrix,
344
+ path,
345
+ readOnly,
346
+ toggle,
347
+ values
348
+ }) : null
349
+ ]
350
+ })]
351
+ });
352
+ };
353
+ //#endregion
354
+ export { McpxCapabilityMatrix };
@@ -0,0 +1,73 @@
1
+ import { CAPABILITY_OPERATIONS, cellPath, toolPath } from "../api-keys/capability-matrix.mjs";
2
+ //#region src/admin/capability-toggles.ts
3
+ const stateOf = (paths, values) => {
4
+ if (paths.length === 0) return "off";
5
+ const granted = paths.filter((path) => values[path] === true).length;
6
+ if (granted === 0) return "off";
7
+ return granted === paths.length ? "on" : "mixed";
8
+ };
9
+ /**
10
+ * Whether a column's bulk toggle reads as on, off or indeterminate. Cells the
11
+ * config does not expose are ignored: two granted cells and one dash read as on.
12
+ */ const columnState = (matrix, basePath, namespace, operation, values) => stateOf(matrix[namespace].filter((row) => row[operation]).map((row) => cellPath(basePath, namespace, row.fieldName, operation)), values);
13
+ /**
14
+ * Whether an entity's row toggle reads as on, off or indeterminate, over the
15
+ * operations the config exposes for that row.
16
+ */ const rowState = (basePath, namespace, row, values) => stateOf(CAPABILITY_OPERATIONS.filter((operation) => row[operation.id]).map((operation) => cellPath(basePath, namespace, row.fieldName, operation.id)), values);
17
+ const toolsState = (matrix, basePath, values) => stateOf(matrix.tools.map((tool) => toolPath(basePath, tool.name)), values);
18
+ const reconcile = (next, touched) => {
19
+ if (touched.has("publish") && next.publish) return {
20
+ ...next,
21
+ write: true
22
+ };
23
+ if (touched.has("write") && !next.write) return {
24
+ ...next,
25
+ publish: false
26
+ };
27
+ return next;
28
+ };
29
+ const rowActions = (basePath, namespace, row, values, changes) => {
30
+ const current = {
31
+ read: values[cellPath(basePath, namespace, row.fieldName, "read")] === true,
32
+ write: values[cellPath(basePath, namespace, row.fieldName, "write")] === true,
33
+ publish: values[cellPath(basePath, namespace, row.fieldName, "publish")] === true
34
+ };
35
+ const touched = /* @__PURE__ */ new Set();
36
+ const next = { ...current };
37
+ for (const operation of CAPABILITY_OPERATIONS) {
38
+ const change = changes[operation.id];
39
+ if (change !== void 0 && row[operation.id]) {
40
+ next[operation.id] = change;
41
+ touched.add(operation.id);
42
+ }
43
+ }
44
+ const reconciled = reconcile(next, touched);
45
+ return CAPABILITY_OPERATIONS.filter((operation) => row[operation.id] && reconciled[operation.id] !== current[operation.id]).map((operation) => ({
46
+ type: "UPDATE",
47
+ path: cellPath(basePath, namespace, row.fieldName, operation.id),
48
+ value: reconciled[operation.id]
49
+ }));
50
+ };
51
+ /**
52
+ * The form-state updates one click implies, as literal actions. Every toggle
53
+ * (cell, row, column or tool) comes through here, so the publish rule applies
54
+ * to bulk grants too.
55
+ *
56
+ * Only cells whose value changes get an action, so a bulk toggle that changes
57
+ * nothing does not mark the form modified.
58
+ */ const buildToggleActions = (matrix, basePath, values, intent) => {
59
+ if (intent.kind === "tool" || intent.kind === "tools") return matrix.tools.filter((tool) => intent.kind === "tools" || tool.name === intent.name).filter((tool) => values[toolPath(basePath, tool.name)] === true !== intent.value).map((tool) => ({
60
+ type: "UPDATE",
61
+ path: toolPath(basePath, tool.name),
62
+ value: intent.value
63
+ }));
64
+ const rows = matrix[intent.namespace].filter((row) => intent.kind === "column" || row.fieldName === intent.fieldName);
65
+ const changes = intent.kind === "row" ? {
66
+ read: intent.value,
67
+ write: intent.value,
68
+ publish: intent.value
69
+ } : { [intent.operation]: intent.value };
70
+ return rows.flatMap((row) => rowActions(basePath, intent.namespace, row, values, changes));
71
+ };
72
+ //#endregion
73
+ export { buildToggleActions, columnState, rowState, toolsState };
@@ -1,7 +1,9 @@
1
1
  import React from "react";
2
- //#region src/client/setup-guide.d.ts
2
+ //#region src/admin/setup-guide.d.ts
3
3
  interface McpxSetupGuideProps {
4
- /** Endpoint path below the API route, from the plugin options. */
4
+ /**
5
+ * Endpoint path below the API route, from the plugin options.
6
+ */
5
7
  endpointPath: string;
6
8
  }
7
9
  /**
@@ -3,23 +3,16 @@ import { buildSetupGuide } from "../api-keys/setup-guide.mjs";
3
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
4
  import { CopyToClipboard, useConfig, useDocumentInfo, useFormFields } from "@payloadcms/ui";
5
5
  import { useEffect, useState } from "react";
6
- //#region src/client/setup-guide.tsx
6
+ //#region src/admin/setup-guide.tsx
7
7
  const asString = (value) => typeof value === "string" ? value : void 0;
8
- /**
9
- * Reads `serverURL` when the config sets one and falls back to the browser's
10
- * origin. The fallback has to wait for mount: this component is server-rendered
11
- * first, where `window` does not exist.
12
- */ const useOrigin = (serverUrl) => {
8
+ const useOrigin = (serverUrl) => {
13
9
  const [origin, setOrigin] = useState(serverUrl);
14
10
  useEffect(() => {
15
11
  if (serverUrl === "") setOrigin(window.location.origin);
16
12
  }, [serverUrl]);
17
13
  return origin;
18
14
  };
19
- /**
20
- * Payload's own theme variables, so the panel follows the admin's light and
21
- * dark themes without shipping a stylesheet consumers would have to transpile.
22
- */ const styles = {
15
+ const styles = {
23
16
  lead: { marginBottom: "calc(var(--base) * 0.75)" },
24
17
  section: { marginBottom: "calc(var(--base) * 0.75)" },
25
18
  sectionHeader: {
@@ -1,28 +1,32 @@
1
1
  import "../options.mjs";
2
2
  //#region src/api-keys/capability-matrix.d.ts
3
3
  /**
4
- * One entity's row. The three booleans say what the plugin config exposes, not
5
- * what the key has been granted: a `false` renders as a dash rather than an
6
- * empty box, so absence reads as a refusal by config rather than a gap.
4
+ * One entity's row. The booleans say what the plugin config exposes, not what
5
+ * the key was granted. A `false` renders as a dash, so absence reads as a
6
+ * refusal by config rather than a gap.
7
7
  */
8
8
  export interface CapabilityRow {
9
9
  fieldName: string;
10
- /** The slug, which is what MCP clients send and what refusals name. */
10
+ /**
11
+ * The slug, which is what MCP clients send and what refusals name.
12
+ */
11
13
  label: string;
12
14
  read: boolean;
13
15
  write: boolean;
14
16
  publish: boolean;
15
- /** Said only where a row departs from what its column header promises. */
17
+ /**
18
+ * Said only where a row departs from what its column header promises.
19
+ */
16
20
  hint?: string;
17
21
  }
18
- export interface CapabilityTool {
22
+ interface CapabilityTool {
19
23
  name: string;
20
24
  description: string;
21
25
  }
22
26
  /**
23
27
  * Everything the admin component needs to draw the matrix, derived from the
24
- * plugin options at config time. Crosses the server/client boundary as a client
25
- * prop, so every leaf has to stay JSON-serializable.
28
+ * plugin options at config time. Passed as a client prop, so every leaf must be
29
+ * JSON-serializable.
26
30
  */
27
31
  export interface CapabilityMatrix {
28
32
  collections: CapabilityRow[];
@@ -1,14 +1,13 @@
1
- import { CAPABILITIES_FIELD, canCreate, canPublish, canWrite } from "../capabilities.mjs";
1
+ import { CAPABILITIES_FIELD, canPublish, canWrite, isLiveWrite } from "../capabilities.mjs";
2
2
  //#region src/api-keys/capability-matrix.ts
3
3
  /**
4
- * The group's `admin.description`, which the matrix renders in Payload's own
5
- * description slot under the heading. It is the one place the two markings in
6
- * the table are explained, so both belong in it.
4
+ * The group's `admin.description`, rendered by the matrix in Payload's
5
+ * description slot. It explains both markings in the table: an unticked box
6
+ * and a dash.
7
7
  */ const CAPABILITIES_DESCRIPTION = "What this key may do. An unticked box is a refusal, and a dash means the plugin config does not expose that operation at all.";
8
8
  /**
9
- * The three operations a collection or global can expose, described once. The
10
- * generated checkboxes and the matrix column headers both read these, so the
11
- * wording a key's owner sees cannot drift from what the field config carries.
9
+ * The operations a collection or global can expose. The generated checkboxes
10
+ * and the matrix column headers share this wording.
12
11
  */ const CAPABILITY_OPERATIONS = [
13
12
  {
14
13
  id: "read",
@@ -26,19 +25,20 @@ import { CAPABILITIES_FIELD, canCreate, canPublish, canWrite } from "../capabili
26
25
  description: "Promote the current draft to what the public sees."
27
26
  }
28
27
  ];
29
- const UPLOAD_HINT = "Files are uploaded in the admin panel.";
28
+ const LIVE_HINT = "Writes go live immediately.";
29
+ const hintFor = (entity) => isLiveWrite(entity) ? { hint: LIVE_HINT } : {};
30
30
  const toRow = (entity) => ({
31
31
  fieldName: entity.fieldName,
32
32
  label: entity.slug,
33
33
  read: entity.read,
34
34
  write: canWrite(entity),
35
35
  publish: canPublish(entity),
36
- ...canWrite(entity) && !canCreate(entity) ? { hint: UPLOAD_HINT } : {}
36
+ ...hintFor(entity)
37
37
  });
38
38
  /**
39
39
  * The single description of what the config exposes. `createCapabilityFields`
40
- * generates its checkboxes from this, and the admin component draws from it, so
41
- * a cell can never appear without a field behind it.
40
+ * and the admin component both draw from it, so a cell cannot appear without a
41
+ * field behind it.
42
42
  */ const createCapabilityMatrix = (options) => ({
43
43
  collections: options.collections.map(toRow),
44
44
  globals: options.globals.map(toRow),
@@ -49,80 +49,8 @@ const toRow = (entity) => ({
49
49
  });
50
50
  const cellPath = (basePath, namespace, fieldName, operation) => `${basePath}.${namespace}.${fieldName}.${operation}`;
51
51
  const toolPath = (basePath, name) => `${basePath}.tools.${name}`;
52
- /** Every path the matrix can address, in a stable order. */ const capabilityPaths = (matrix, basePath = CAPABILITIES_FIELD) => [...["collections", "globals"].flatMap((namespace) => matrix[namespace].flatMap((row) => CAPABILITY_OPERATIONS.filter((operation) => row[operation.id]).map((operation) => cellPath(basePath, namespace, row.fieldName, operation.id)))), ...matrix.tools.map((tool) => toolPath(basePath, tool.name))];
53
- const stateOf = (paths, values) => {
54
- if (paths.length === 0) return "off";
55
- const granted = paths.filter((path) => values[path] === true).length;
56
- if (granted === 0) return "off";
57
- return granted === paths.length ? "on" : "mixed";
58
- };
59
52
  /**
60
- * Whether a column's bulk toggle reads as on, off or indeterminate. Non-exposed
61
- * cells are ignored: a column of two granted cells and one dash is fully on.
62
- */ const columnState = (matrix, basePath, namespace, operation, values) => stateOf(matrix[namespace].filter((row) => row[operation]).map((row) => cellPath(basePath, namespace, row.fieldName, operation)), values);
63
- /**
64
- * Whether an entity's row toggle reads as on, off or indeterminate, over the
65
- * operations the config exposes for that row.
66
- */ const rowState = (basePath, namespace, row, values) => stateOf(CAPABILITY_OPERATIONS.filter((operation) => row[operation.id]).map((operation) => cellPath(basePath, namespace, row.fieldName, operation.id)), values);
67
- const toolsState = (matrix, basePath, values) => stateOf(matrix.tools.map((tool) => toolPath(basePath, tool.name)), values);
68
- /**
69
- * Publishing is an extension of writing, and `publishFlag` in `capabilities.ts`
70
- * discards a publish without a write. Ticking publish therefore ticks write,
71
- * and clearing write clears publish, so the form cannot save a combination the
72
- * server ignores.
73
- */ const reconcile = (next, touched) => {
74
- if (touched.has("publish") && next.publish) return {
75
- ...next,
76
- write: true
77
- };
78
- if (touched.has("write") && !next.write) return {
79
- ...next,
80
- publish: false
81
- };
82
- return next;
83
- };
84
- const rowActions = (basePath, namespace, row, values, changes) => {
85
- const current = {
86
- read: values[cellPath(basePath, namespace, row.fieldName, "read")] === true,
87
- write: values[cellPath(basePath, namespace, row.fieldName, "write")] === true,
88
- publish: values[cellPath(basePath, namespace, row.fieldName, "publish")] === true
89
- };
90
- const touched = /* @__PURE__ */ new Set();
91
- const next = { ...current };
92
- for (const operation of CAPABILITY_OPERATIONS) {
93
- const change = changes[operation.id];
94
- if (change !== void 0 && row[operation.id]) {
95
- next[operation.id] = change;
96
- touched.add(operation.id);
97
- }
98
- }
99
- const reconciled = reconcile(next, touched);
100
- return CAPABILITY_OPERATIONS.filter((operation) => row[operation.id] && reconciled[operation.id] !== current[operation.id]).map((operation) => ({
101
- type: "UPDATE",
102
- path: cellPath(basePath, namespace, row.fieldName, operation.id),
103
- value: reconciled[operation.id]
104
- }));
105
- };
106
- /**
107
- * The form-state updates one click implies, as literal actions. Every toggle in
108
- * the matrix — cell, row, column or tool — comes through here, so the publish
109
- * rule holds for bulk grants as much as for a single box.
110
- *
111
- * Only cells whose value actually changes get an action, which keeps a bulk
112
- * toggle from marking the form modified when it changes nothing.
113
- */ const buildToggleActions = (matrix, basePath, values, intent) => {
114
- if (intent.kind === "tool" || intent.kind === "tools") return matrix.tools.filter((tool) => intent.kind === "tools" || tool.name === intent.name).filter((tool) => values[toolPath(basePath, tool.name)] === true !== intent.value).map((tool) => ({
115
- type: "UPDATE",
116
- path: toolPath(basePath, tool.name),
117
- value: intent.value
118
- }));
119
- const rows = matrix[intent.namespace].filter((row) => intent.kind === "column" || row.fieldName === intent.fieldName);
120
- const changes = intent.kind === "row" ? {
121
- read: intent.value,
122
- write: intent.value,
123
- publish: intent.value
124
- } : { [intent.operation]: intent.value };
125
- return rows.flatMap((row) => rowActions(basePath, intent.namespace, row, values, changes));
126
- };
53
+ * Every path the matrix can address, in a stable order.
54
+ */ const capabilityPaths = (matrix, basePath = CAPABILITIES_FIELD) => [...["collections", "globals"].flatMap((namespace) => matrix[namespace].flatMap((row) => CAPABILITY_OPERATIONS.filter((operation) => row[operation.id]).map((operation) => cellPath(basePath, namespace, row.fieldName, operation.id)))), ...matrix.tools.map((tool) => toolPath(basePath, tool.name))];
127
55
  //#endregion
128
- export { CAPABILITIES_DESCRIPTION, CAPABILITY_OPERATIONS, buildToggleActions, capabilityPaths, cellPath, columnState, createCapabilityMatrix, rowState, toolPath, toolsState };
56
+ export { CAPABILITIES_DESCRIPTION, CAPABILITY_OPERATIONS, capabilityPaths, cellPath, createCapabilityMatrix, toolPath };