@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.
- package/README.md +144 -470
- package/dist/admin/capability-matrix.d.mts +33 -0
- package/dist/admin/capability-matrix.mjs +354 -0
- package/dist/admin/capability-toggles.mjs +73 -0
- package/dist/{client → admin}/setup-guide.d.mts +4 -2
- package/dist/{client → admin}/setup-guide.mjs +3 -10
- package/dist/api-keys/capability-matrix.d.mts +12 -8
- package/dist/api-keys/capability-matrix.mjs +14 -86
- package/dist/api-keys/collection.mjs +2 -1
- package/dist/api-keys/fields.mjs +36 -24
- package/dist/auth/resolve.mjs +71 -23
- package/dist/{tools/names.mjs → builtin-tool-names.mjs} +1 -1
- package/dist/capabilities.mjs +44 -46
- package/dist/define-tool.d.mts +23 -0
- package/dist/{types.mjs → define-tool.mjs} +1 -1
- package/dist/endpoint/errors.mjs +5 -6
- package/dist/endpoint/handler.mjs +73 -29
- package/dist/endpoint/server.mjs +85 -22
- package/dist/endpoint/upload.mjs +135 -0
- package/dist/entity.d.mts +9 -0
- package/dist/guards.mjs +7 -0
- package/dist/i18n.mjs +10 -8
- package/dist/index.d.mts +5 -2
- package/dist/index.mjs +3 -2
- package/dist/options.mjs +54 -47
- package/dist/plugin.d.mts +2 -2
- package/dist/plugin.mjs +13 -3
- package/dist/request.d.mts +2 -3
- package/dist/request.mjs +2 -3
- package/dist/result.d.mts +2 -3
- package/dist/result.mjs +3 -3
- package/dist/schema/admin-hidden.mjs +91 -0
- package/dist/schema/describe.mjs +17 -33
- package/dist/schema/errors.mjs +8 -0
- package/dist/schema/index.mjs +5 -2
- package/dist/schema/lexical-pointer.mjs +18 -20
- package/dist/schema/lexical.mjs +44 -93
- package/dist/schema/outline.mjs +5 -15
- package/dist/schema/path.mjs +28 -0
- package/dist/schema/pointer.mjs +18 -25
- package/dist/schema/shape.mjs +25 -71
- package/dist/schema/walk.mjs +43 -45
- package/dist/tools/builtin.mjs +4 -4
- package/dist/tools/create-document.mjs +50 -24
- package/dist/tools/describe-schema.mjs +38 -34
- package/dist/tools/document.mjs +124 -0
- package/dist/tools/find-documents.mjs +32 -19
- package/dist/tools/find-versions.mjs +22 -18
- package/dist/tools/get-document.mjs +60 -33
- package/dist/tools/list-capabilities.mjs +35 -19
- package/dist/tools/patch-document.mjs +122 -66
- package/dist/tools/publish-document.mjs +26 -32
- package/dist/tools/query-paths.mjs +57 -0
- package/dist/tools/read-request.d.mts +10 -0
- package/dist/tools/read-request.mjs +43 -0
- package/dist/tools/shared.mjs +74 -105
- package/dist/tools/validate-document.mjs +18 -30
- package/dist/tools/versions.mjs +37 -20
- package/dist/types.d.mts +135 -75
- package/dist/upload/file.mjs +166 -0
- package/dist/upload/grant.mjs +96 -0
- package/dist/version.mjs +3 -1
- package/dist/write/draft-guard.mjs +23 -41
- package/dist/write/patch.mjs +27 -183
- package/dist/write/publish-blockers.mjs +7 -14
- package/dist/write/publish-intent.mjs +6 -4
- package/dist/write/row-ids.mjs +46 -0
- package/dist/write/transaction.mjs +5 -8
- package/dist/write/write-data.mjs +64 -0
- package/package.json +11 -10
- package/dist/client/capability-matrix.d.mts +0 -26
- package/dist/client/capability-matrix.mjs +0 -349
- package/dist/endpoint/index.mjs +0 -4
- package/dist/tools/target.mjs +0 -45
- /package/dist/{client → admin}/index.d.mts +0 -0
- /package/dist/{client → admin}/index.mjs +0 -0
package/dist/tools/shared.mjs
CHANGED
|
@@ -1,159 +1,128 @@
|
|
|
1
|
-
import { canCreate,
|
|
2
|
-
import {
|
|
3
|
-
import { NotFound } from "payload";
|
|
1
|
+
import { canCreate, isLiveWrite } from "../capabilities.mjs";
|
|
2
|
+
import { acceptsFiles } from "../upload/file.mjs";
|
|
4
3
|
import { z } from "zod";
|
|
5
4
|
//#region src/tools/shared.ts
|
|
6
5
|
/**
|
|
7
6
|
* An out-of-scope slug fails schema validation before a handler runs, so a
|
|
8
7
|
* client only ever sees what its key may touch.
|
|
9
8
|
*/ const slugEnum = (slugs) => z.enum(slugs);
|
|
10
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Payload's id type follows the adapter, so both forms are handed on as read.
|
|
11
|
+
*/ const idSchema = z.union([z.string(), z.number()]);
|
|
12
|
+
const slugsOf = (scope, key) => ({
|
|
13
|
+
collections: scope.collections[key],
|
|
14
|
+
globals: scope.globals[key]
|
|
15
|
+
});
|
|
11
16
|
const slugsWhere = (scope, predicate, allowed) => {
|
|
12
17
|
const pick = (entities, slugs) => entities.filter((entity) => slugs.includes(entity.slug) && predicate(entity)).map((entity) => entity.slug);
|
|
13
18
|
return [...pick(scope.exposure.collections, allowed.collections), ...pick(scope.exposure.globals, allowed.globals)];
|
|
14
19
|
};
|
|
15
20
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
globals: scope.writableGlobals
|
|
22
|
-
});
|
|
23
|
-
/**
|
|
24
|
-
* Slugs this key may write but never create in, because their documents are
|
|
25
|
-
* files. Collection-only, since nothing creates a global either way.
|
|
26
|
-
*/ const patchOnlySlugs = (scope) => slugsWhere(scope, (entity) => !canCreate(entity), {
|
|
27
|
-
collections: scope.writable,
|
|
28
|
-
globals: []
|
|
29
|
-
});
|
|
30
|
-
/** Slugs this key may write and, separately, publish. */ const publishableWriteSlugs = (scope) => slugsWhere(scope, canPublish, {
|
|
31
|
-
collections: scope.publishable,
|
|
32
|
-
globals: scope.publishableGlobals
|
|
33
|
-
});
|
|
34
|
-
/**
|
|
35
|
-
* What a write actually does for this key, and what it takes to make it public.
|
|
36
|
-
* A live-write slug has no draft and no publish step; a publishable one has
|
|
37
|
-
* both. Stated per key so a client is never told its writes are drafts while
|
|
38
|
-
* they are not, nor that publishing is out of reach when it is not.
|
|
39
|
-
*/ const draftSentence = (scope) => {
|
|
40
|
-
const live = liveWriteSlugs(scope);
|
|
41
|
-
const publishable = publishableWriteSlugs(scope);
|
|
42
|
-
return `${live.length === 0 ? "Every write lands as a draft." : `Writes land as drafts, except for ${live.join(", ")}, which have no drafts: a write there changes the live document immediately.`} ${publishable.length === 0 ? "Nothing this key writes is ever published; publishing stays a human action in the admin panel." : `Publish a draft with publishDocument, which this key may do for ${publishable.join(", ")}. Publishing anything else stays a human action in the admin panel.`}`;
|
|
21
|
+
* Collections this key may write but not create in: upload collections whose
|
|
22
|
+
* files MCP does not accept. Nothing creates a global either way.
|
|
23
|
+
*/ const patchOnlySlugs = (scope) => {
|
|
24
|
+
const creatable = slugsFor(scope, "create").collections;
|
|
25
|
+
return scope.collections.writable.filter((slug) => !creatable.includes(slug));
|
|
43
26
|
};
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
|
|
27
|
+
const widen = (branch) => branch;
|
|
28
|
+
/**
|
|
29
|
+
* The one list the shape helpers and {@link resolveEntity} both read.
|
|
30
|
+
*/ const slugsFor = (scope, operation) => {
|
|
47
31
|
switch (operation) {
|
|
48
32
|
case "create": return {
|
|
49
|
-
collections: slugsWhere(scope, canCreate, {
|
|
50
|
-
collections: scope.writable,
|
|
33
|
+
collections: slugsWhere(scope, (entity) => canCreate(entity) && (!entity.isUpload || acceptsFiles(scope, entity.slug)), {
|
|
34
|
+
collections: scope.collections.writable,
|
|
51
35
|
globals: []
|
|
52
36
|
}),
|
|
53
37
|
globals: []
|
|
54
38
|
};
|
|
55
|
-
case "publish": return
|
|
56
|
-
|
|
57
|
-
globals: scope.publishableGlobals
|
|
58
|
-
};
|
|
59
|
-
case "read": return {
|
|
60
|
-
collections: scope.readable,
|
|
61
|
-
globals: scope.readableGlobals
|
|
62
|
-
};
|
|
39
|
+
case "publish": return slugsOf(scope, "publishable");
|
|
40
|
+
case "read": return slugsOf(scope, "readable");
|
|
63
41
|
case "versions": return {
|
|
64
42
|
collections: slugsWhere(scope, (entity) => entity.hasVersions, {
|
|
65
|
-
collections: scope.readable,
|
|
43
|
+
collections: scope.collections.readable,
|
|
66
44
|
globals: []
|
|
67
45
|
}),
|
|
68
46
|
globals: slugsWhere(scope, (entity) => entity.hasVersions, {
|
|
69
47
|
collections: [],
|
|
70
|
-
globals: scope.
|
|
48
|
+
globals: scope.globals.readable
|
|
71
49
|
})
|
|
72
50
|
};
|
|
73
|
-
case "write": return
|
|
74
|
-
collections: scope.writable,
|
|
75
|
-
globals: scope.writableGlobals
|
|
76
|
-
};
|
|
51
|
+
case "write": return slugsOf(scope, "writable");
|
|
77
52
|
}
|
|
78
53
|
};
|
|
79
54
|
/**
|
|
55
|
+
* The slugs of {@link slugsFor} `"versions"` that also have drafts. Only they
|
|
56
|
+
* have `_status` on their versions, so only they can be queried by status.
|
|
57
|
+
*/ const draftVersionSlugs = (scope) => {
|
|
58
|
+
const { collections, globals } = slugsFor(scope, "versions");
|
|
59
|
+
return {
|
|
60
|
+
collections: slugsWhere(scope, (entity) => entity.hasDrafts, {
|
|
61
|
+
collections,
|
|
62
|
+
globals: []
|
|
63
|
+
}),
|
|
64
|
+
globals: slugsWhere(scope, (entity) => entity.hasDrafts, {
|
|
65
|
+
collections: [],
|
|
66
|
+
globals
|
|
67
|
+
})
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Slugs reachable by `operation` whose writes go live, because they have no
|
|
72
|
+
* draft to write.
|
|
73
|
+
*/ const liveWriteSlugs = (scope, operation) => slugsWhere(scope, isLiveWrite, slugsFor(scope, operation));
|
|
74
|
+
/**
|
|
75
|
+
* Stated on the tools that write, since a client calling them must know before
|
|
76
|
+
* the call which writes are public.
|
|
77
|
+
*/ const liveWriteSentence = (scope, operation) => {
|
|
78
|
+
const live = liveWriteSlugs(scope, operation);
|
|
79
|
+
return live.length === 0 ? "Every write is saved as a draft." : `Writes to ${live.join(", ")} go live immediately. Every other write is saved as a draft.`;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
80
82
|
* With no reachable global, `global` is left out and `collection` stays
|
|
81
|
-
* required, so a deployment without globals
|
|
83
|
+
* required, so a deployment without globals gets no extra argument. Only the
|
|
82
84
|
* mixed case makes either optional, and the handler enforces exclusivity there.
|
|
83
|
-
|
|
85
|
+
* `globalRule` states that rule on `global` for a tool without an `id`.
|
|
86
|
+
*/ const entityShape = (scope, operation, globalRule) => {
|
|
84
87
|
const { collections, globals } = slugsFor(scope, operation);
|
|
85
|
-
if (globals.length === 0) return widen({ collection: slugEnum(collections)
|
|
86
|
-
if (collections.length === 0) return widen({ global: slugEnum(globals)
|
|
88
|
+
if (globals.length === 0) return widen({ collection: slugEnum(collections) });
|
|
89
|
+
if (collections.length === 0) return widen({ global: slugEnum(globals) });
|
|
90
|
+
const global = slugEnum(globals).optional();
|
|
87
91
|
return widen({
|
|
88
|
-
collection: slugEnum(collections).optional()
|
|
89
|
-
global:
|
|
92
|
+
collection: slugEnum(collections).optional(),
|
|
93
|
+
global: globalRule === void 0 ? global : global.describe(globalRule)
|
|
90
94
|
});
|
|
91
95
|
};
|
|
92
96
|
/**
|
|
93
97
|
* Only a collection document has one. Optional in the mixed case, where
|
|
94
|
-
* `
|
|
98
|
+
* `resolveDocument` enforces the dependency and the description states it.
|
|
95
99
|
*/ const idShape = (scope, operation) => {
|
|
96
100
|
const { collections, globals } = slugsFor(scope, operation);
|
|
97
101
|
if (collections.length === 0) return widen({});
|
|
98
102
|
if (globals.length === 0) return widen({ id: idSchema });
|
|
99
|
-
return widen({ id: idSchema.optional().describe("
|
|
103
|
+
return widen({ id: idSchema.optional().describe("Required with \"collection\", omitted with \"global\".") });
|
|
100
104
|
};
|
|
105
|
+
/**
|
|
106
|
+
* For the reads that fall back to the default locale, unlike the writes.
|
|
107
|
+
*/ const READ_LOCALE_DESCRIPTION = "Default: the default locale. A value missing in this locale is shown from the default locale.";
|
|
101
108
|
const localeShape = (scope, options) => {
|
|
102
|
-
if (!scope.
|
|
103
|
-
const
|
|
104
|
-
|
|
109
|
+
if (!scope.localization) return widen({});
|
|
110
|
+
const enumerated = z.enum(scope.localization.locales);
|
|
111
|
+
const locale = options.required ? enumerated : enumerated.optional();
|
|
112
|
+
return widen({ locale: options.description === void 0 ? locale : locale.describe(options.description) });
|
|
105
113
|
};
|
|
106
114
|
/**
|
|
107
115
|
* Defaults to 0 rather than Payload's own default: a client usually wants ids
|
|
108
116
|
* it can write back, and populating a relation costs a query.
|
|
109
|
-
*/ const depthShape = (scope) => ({ depth: z.number().int().min(0).max(scope.limits.maxDepth).optional().describe(
|
|
117
|
+
*/ const depthShape = (scope) => ({ depth: z.number().int().min(0).max(scope.limits.maxDepth).optional().describe("Default 0. A relationship into a collection this key cannot read stays an id.") });
|
|
110
118
|
/**
|
|
111
119
|
* The locale to operate on: the explicit argument, else the request's, else
|
|
112
120
|
* the default. `undefined` when localization is off.
|
|
113
121
|
*/ const localeOf = (scope, locale) => {
|
|
114
|
-
if (!scope.
|
|
122
|
+
if (!scope.localization) return;
|
|
123
|
+
const { locales, defaultLocale } = scope.localization;
|
|
115
124
|
const requested = locale ?? scope.req.locale;
|
|
116
|
-
return
|
|
117
|
-
};
|
|
118
|
-
/**
|
|
119
|
-
* Reads the current draft in a fixed locale with no fallback, which is the
|
|
120
|
-
* shape that may be written back or validated without mixing locales.
|
|
121
|
-
*/ const readTarget = async (scope, args) => {
|
|
122
|
-
const { payload } = scope.req;
|
|
123
|
-
const privileged = args.privileged === true;
|
|
124
|
-
const shared = {
|
|
125
|
-
depth: 0,
|
|
126
|
-
draft: true,
|
|
127
|
-
...args.locale === void 0 ? {} : {
|
|
128
|
-
locale: args.locale,
|
|
129
|
-
fallbackLocale: false
|
|
130
|
-
},
|
|
131
|
-
overrideAccess: privileged,
|
|
132
|
-
showHiddenFields: privileged,
|
|
133
|
-
req: scope.req
|
|
134
|
-
};
|
|
135
|
-
if (args.target.kind === "collection") {
|
|
136
|
-
const doc = await payload.findByID({
|
|
137
|
-
...shared,
|
|
138
|
-
collection: args.target.slug,
|
|
139
|
-
id: args.id,
|
|
140
|
-
disableErrors: true
|
|
141
|
-
});
|
|
142
|
-
if (!doc) throw new NotFound(scope.req.t);
|
|
143
|
-
return doc;
|
|
144
|
-
}
|
|
145
|
-
return await payload.findGlobal({
|
|
146
|
-
...shared,
|
|
147
|
-
slug: args.target.slug
|
|
148
|
-
});
|
|
149
|
-
};
|
|
150
|
-
const translateLabel = (scope, label, fallback) => {
|
|
151
|
-
const { i18n, t } = scope.req;
|
|
152
|
-
const resolved = typeof label === "function" ? label({
|
|
153
|
-
i18n,
|
|
154
|
-
t
|
|
155
|
-
}) : label;
|
|
156
|
-
return translateStatic(resolved, i18n) ?? fallback;
|
|
125
|
+
return requested && locales.includes(requested) ? requested : defaultLocale;
|
|
157
126
|
};
|
|
158
127
|
//#endregion
|
|
159
|
-
export { depthShape,
|
|
128
|
+
export { READ_LOCALE_DESCRIPTION, depthShape, draftVersionSlugs, entityShape, idSchema, idShape, liveWriteSentence, liveWriteSlugs, localeOf, localeShape, patchOnlySlugs, slugEnum, slugsFor, widen };
|
|
@@ -1,57 +1,45 @@
|
|
|
1
|
+
import { defineMcpxTool } from "../define-tool.mjs";
|
|
1
2
|
import { jsonResult } from "../result.mjs";
|
|
2
|
-
import { idShape, localeOf, localeShape, readTarget, targetShape } from "./shared.mjs";
|
|
3
|
-
import { requireIdFor, resolveTarget } from "./target.mjs";
|
|
4
|
-
import { defineMcpxTool } from "../types.mjs";
|
|
5
3
|
import { collectPublishBlockers } from "../write/publish-blockers.mjs";
|
|
4
|
+
import { entityShape, idShape, localeOf, localeShape } from "./shared.mjs";
|
|
5
|
+
import { collectLocaleBlockers, identityOf, readDraft, resolveDocument } from "./document.mjs";
|
|
6
6
|
/**
|
|
7
|
-
* Gated on write rather than read, because publish blockers only mean
|
|
8
|
-
*
|
|
7
|
+
* Gated on write rather than read, because publish blockers only mean something
|
|
8
|
+
* to a caller who can act on them.
|
|
9
9
|
*
|
|
10
10
|
* It reads the document twice on purpose: once under the key's own access to
|
|
11
|
-
* refuse a caller who may not see it, then privileged, so the check
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* refuse a caller who may not see it, then privileged, so the check covers every
|
|
12
|
+
* field and not only those the user can read. It has no `readOnlyHint` because
|
|
13
|
+
* the traversal fires field hooks.
|
|
14
14
|
*/ const validateDocument = defineMcpxTool({
|
|
15
15
|
name: "validateDocument",
|
|
16
|
-
description: `
|
|
17
|
-
|
|
18
|
-
Pass exactly one of "collection" and "global". "id" is required with "collection" and must be omitted with "global", because a global is a singleton.
|
|
19
|
-
|
|
20
|
-
Nothing is written, but the check runs the same field-level beforeValidate and beforeChange hooks a save would, so a hook with side effects fires. "publishBlockersUnavailable" means the check itself failed, so the empty list says nothing.`,
|
|
16
|
+
description: `Lists what still blocks publishing one document or global, without saving. Checks one locale when "locale" is given, otherwise every locale. Returns "publishBlockers", each with a pointer and a message, and a "locale" when every locale was checked. An empty list means it can be published, unless "publishBlockersUnavailable" is true: then the check itself failed. The check runs the field hooks a save runs.`,
|
|
21
17
|
annotations: { openWorldHint: false },
|
|
22
|
-
isEnabled: (scope) => scope.writable.length + scope.
|
|
18
|
+
isEnabled: (scope) => scope.collections.writable.length + scope.globals.writable.length > 0,
|
|
23
19
|
inputSchema: (scope) => ({
|
|
24
|
-
...
|
|
25
|
-
collection: "Collection holding the document.",
|
|
26
|
-
global: "Global to validate."
|
|
27
|
-
}),
|
|
20
|
+
...entityShape(scope, "write"),
|
|
28
21
|
...idShape(scope, "write"),
|
|
29
|
-
...localeShape(scope, {
|
|
30
|
-
required: true,
|
|
31
|
-
description: "Locale to validate."
|
|
32
|
-
})
|
|
22
|
+
...localeShape(scope, { required: false })
|
|
33
23
|
}),
|
|
34
24
|
handler: async ({ args, scope }) => {
|
|
35
|
-
const target =
|
|
36
|
-
const
|
|
25
|
+
const target = resolveDocument(scope, args, "write");
|
|
26
|
+
const { localization } = scope;
|
|
37
27
|
const locale = localeOf(scope, args.locale);
|
|
38
|
-
await
|
|
28
|
+
await readDraft(scope, {
|
|
39
29
|
target,
|
|
40
|
-
id,
|
|
41
30
|
locale
|
|
42
31
|
});
|
|
43
|
-
const doc = await
|
|
32
|
+
const doc = await readDraft(scope, {
|
|
44
33
|
target,
|
|
45
|
-
id,
|
|
46
34
|
locale,
|
|
47
35
|
privileged: true
|
|
48
36
|
});
|
|
49
|
-
const validation = await collectPublishBlockers(scope.req, {
|
|
37
|
+
const validation = localization && args.locale === void 0 ? await collectLocaleBlockers(scope, target, localization.locales) : await collectPublishBlockers(scope.req, {
|
|
50
38
|
doc,
|
|
51
39
|
entity: target
|
|
52
40
|
});
|
|
53
41
|
return jsonResult({
|
|
54
|
-
...target
|
|
42
|
+
...identityOf(target, doc["id"]),
|
|
55
43
|
status: doc["_status"],
|
|
56
44
|
updatedAt: doc["updatedAt"],
|
|
57
45
|
publishBlockers: validation.blockers,
|
package/dist/tools/versions.mjs
CHANGED
|
@@ -1,35 +1,44 @@
|
|
|
1
|
+
import { stripAdminHidden } from "../schema/admin-hidden.mjs";
|
|
2
|
+
import "../schema/index.mjs";
|
|
3
|
+
import { mcpxReadRequest } from "./read-request.mjs";
|
|
4
|
+
import { hasDraftsEnabled } from "payload/shared";
|
|
1
5
|
import { Pointer, createPatch } from "rfc6902";
|
|
2
6
|
//#region src/tools/versions.ts
|
|
3
|
-
|
|
7
|
+
const NOISE = [
|
|
4
8
|
"id",
|
|
5
9
|
"globalType",
|
|
6
10
|
"createdAt",
|
|
7
11
|
"updatedAt",
|
|
8
12
|
"_status"
|
|
9
13
|
];
|
|
10
|
-
|
|
11
|
-
/**
|
|
14
|
+
const NEWEST_FIRST = ["-updatedAt", "-id"];
|
|
15
|
+
/**
|
|
16
|
+
* The document or global as the key's user sees it, without `admin.hidden`
|
|
17
|
+
* fields.
|
|
18
|
+
*/ const readLive = async (scope, read, options) => {
|
|
12
19
|
const shared = {
|
|
13
20
|
depth: read.depth,
|
|
14
21
|
draft: options.draft,
|
|
15
22
|
overrideAccess: false,
|
|
16
|
-
req: scope
|
|
23
|
+
req: mcpxReadRequest(scope),
|
|
17
24
|
...options.select === void 0 ? {} : { select: options.select },
|
|
18
25
|
...read.locale === void 0 ? {} : { locale: read.locale }
|
|
19
26
|
};
|
|
20
|
-
|
|
27
|
+
const doc = await (read.target.kind === "collection" ? scope.req.payload.findByID({
|
|
21
28
|
...shared,
|
|
22
29
|
collection: read.target.slug,
|
|
23
|
-
id: read.id
|
|
30
|
+
id: read.target.id
|
|
24
31
|
}) : scope.req.payload.findGlobal({
|
|
25
32
|
...shared,
|
|
26
33
|
slug: read.target.slug
|
|
27
34
|
}));
|
|
35
|
+
return stripAdminHidden(scope.req.payload.config, read.target, doc);
|
|
28
36
|
};
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
const withoutHidden = (scope, read, stored) => ({
|
|
38
|
+
...stored,
|
|
39
|
+
version: stripAdminHidden(scope.req.payload.config, read.target, stored.version)
|
|
40
|
+
});
|
|
41
|
+
const assertReadable = async (scope, read) => {
|
|
33
42
|
await readLive(scope, {
|
|
34
43
|
...read,
|
|
35
44
|
depth: 0
|
|
@@ -38,7 +47,9 @@ import { Pointer, createPatch } from "rfc6902";
|
|
|
38
47
|
select: {}
|
|
39
48
|
});
|
|
40
49
|
};
|
|
41
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* `parent` comes back as a raw id or, populated, as the document itself.
|
|
52
|
+
*/ const isVersionOf = (version, id) => {
|
|
42
53
|
const parent = version["parent"];
|
|
43
54
|
const parentId = typeof parent === "object" && parent !== null ? parent["id"] : parent;
|
|
44
55
|
return (typeof parentId === "string" || typeof parentId === "number") && String(parentId) === String(id);
|
|
@@ -56,7 +67,7 @@ import { Pointer, createPatch } from "rfc6902";
|
|
|
56
67
|
depth: read.depth,
|
|
57
68
|
disableErrors: true,
|
|
58
69
|
overrideAccess: false,
|
|
59
|
-
req: scope
|
|
70
|
+
req: mcpxReadRequest(scope),
|
|
60
71
|
...read.locale === void 0 ? {} : { locale: read.locale }
|
|
61
72
|
};
|
|
62
73
|
const version = await (read.target.kind === "collection" ? payload.findVersionByID({
|
|
@@ -67,13 +78,15 @@ import { Pointer, createPatch } from "rfc6902";
|
|
|
67
78
|
slug: read.target.slug
|
|
68
79
|
}));
|
|
69
80
|
if (!version) return null;
|
|
70
|
-
if (read.
|
|
71
|
-
return version;
|
|
81
|
+
if (read.target.kind === "collection" && !isVersionOf(version, read.target.id)) return null;
|
|
82
|
+
return withoutHidden(scope, read, version);
|
|
72
83
|
};
|
|
73
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* One page of a document's or global's history, newest first.
|
|
86
|
+
*/ const queryVersions = async (scope, read, options) => {
|
|
74
87
|
await assertReadable(scope, read);
|
|
75
88
|
const where = {
|
|
76
|
-
...read.
|
|
89
|
+
...read.target.kind === "collection" ? { parent: { equals: read.target.id } } : {},
|
|
77
90
|
...options.status === void 0 ? {} : { "version._status": { equals: options.status } }
|
|
78
91
|
};
|
|
79
92
|
const shared = {
|
|
@@ -81,7 +94,7 @@ import { Pointer, createPatch } from "rfc6902";
|
|
|
81
94
|
limit: options.limit,
|
|
82
95
|
sort: NEWEST_FIRST,
|
|
83
96
|
overrideAccess: false,
|
|
84
|
-
req: scope
|
|
97
|
+
req: mcpxReadRequest(scope),
|
|
85
98
|
where,
|
|
86
99
|
...options.page === void 0 ? {} : { page: options.page },
|
|
87
100
|
...read.locale === void 0 ? {} : { locale: read.locale }
|
|
@@ -94,11 +107,15 @@ import { Pointer, createPatch } from "rfc6902";
|
|
|
94
107
|
slug: read.target.slug
|
|
95
108
|
}));
|
|
96
109
|
};
|
|
97
|
-
/**
|
|
98
|
-
|
|
110
|
+
/**
|
|
111
|
+
* The newest version whose status is published, or `null` if none is.
|
|
112
|
+
*/ const loadPublished = async (scope, read) => {
|
|
113
|
+
if (!hasDraftsEnabled(read.target.config)) return null;
|
|
114
|
+
const [newest] = (await queryVersions(scope, read, {
|
|
99
115
|
status: "published",
|
|
100
116
|
limit: 1
|
|
101
|
-
})).docs
|
|
117
|
+
})).docs;
|
|
118
|
+
return newest ? withoutHidden(scope, read, newest) : null;
|
|
102
119
|
};
|
|
103
120
|
const strip = (doc) => Object.fromEntries(Object.entries(doc).filter(([key]) => !NOISE.includes(key)));
|
|
104
121
|
/**
|