@next-buildr/payload 0.0.0-stage → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +4 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/adapter/adapter.test.d.ts +2 -0
- package/dist/adapter/adapter.test.d.ts.map +1 -0
- package/dist/adapter/adapter.test.js +282 -0
- package/dist/adapter/canvas-data-source.d.ts +9 -0
- package/dist/adapter/canvas-data-source.d.ts.map +1 -0
- package/dist/adapter/canvas-data-source.js +24 -0
- package/dist/adapter/document-adapter.d.ts +20 -0
- package/dist/adapter/document-adapter.d.ts.map +1 -0
- package/dist/adapter/document-adapter.js +183 -0
- package/dist/adapter/http.d.ts +34 -0
- package/dist/adapter/http.d.ts.map +1 -0
- package/dist/adapter/http.js +72 -0
- package/dist/adapter/index.d.ts +4 -0
- package/dist/adapter/index.d.ts.map +1 -0
- package/dist/adapter/index.js +5 -0
- package/dist/admin/index.d.ts +4 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +2 -0
- package/dist/admin/layout-field-connected.d.ts +6 -0
- package/dist/admin/layout-field-connected.d.ts.map +1 -0
- package/dist/admin/layout-field-connected.js +16 -0
- package/dist/admin/layout-field-view.d.ts +16 -0
- package/dist/admin/layout-field-view.d.ts.map +1 -0
- package/dist/admin/layout-field-view.js +14 -0
- package/dist/admin/layout-field.d.ts +6 -0
- package/dist/admin/layout-field.d.ts.map +1 -0
- package/dist/admin/layout-field.js +13 -0
- package/dist/admin/layout-field.test.d.ts +2 -0
- package/dist/admin/layout-field.test.d.ts.map +1 -0
- package/dist/admin/layout-field.test.js +41 -0
- package/dist/contract.d.ts +289 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +176 -0
- package/dist/data/build-context.d.ts +20 -0
- package/dist/data/build-context.d.ts.map +1 -0
- package/dist/data/build-context.js +25 -0
- package/dist/data/data.test.d.ts +2 -0
- package/dist/data/data.test.d.ts.map +1 -0
- package/dist/data/data.test.js +261 -0
- package/dist/data/index.d.ts +10 -0
- package/dist/data/index.d.ts.map +1 -0
- package/dist/data/index.js +5 -0
- package/dist/data/normalize.d.ts +14 -0
- package/dist/data/normalize.d.ts.map +1 -0
- package/dist/data/normalize.js +128 -0
- package/dist/data/payload-data-source.d.ts +42 -0
- package/dist/data/payload-data-source.d.ts.map +1 -0
- package/dist/data/payload-data-source.js +179 -0
- package/dist/data/payload-data-source.test.d.ts +2 -0
- package/dist/data/payload-data-source.test.d.ts.map +1 -0
- package/dist/data/payload-data-source.test.js +160 -0
- package/dist/data/resolve-layout.d.ts +45 -0
- package/dist/data/resolve-layout.d.ts.map +1 -0
- package/dist/data/resolve-layout.js +111 -0
- package/dist/data/schema-from-fields.d.ts +63 -0
- package/dist/data/schema-from-fields.d.ts.map +1 -0
- package/dist/data/schema-from-fields.js +178 -0
- package/dist/data/where.d.ts +53 -0
- package/dist/data/where.d.ts.map +1 -0
- package/dist/data/where.js +210 -0
- package/dist/mcp/http-backend.d.ts +35 -0
- package/dist/mcp/http-backend.d.ts.map +1 -0
- package/dist/mcp/http-backend.js +337 -0
- package/dist/mcp/http-backend.test.d.ts +2 -0
- package/dist/mcp/http-backend.test.d.ts.map +1 -0
- package/dist/mcp/http-backend.test.js +178 -0
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +2 -0
- package/dist/mcp/route.d.ts +63 -0
- package/dist/mcp/route.d.ts.map +1 -0
- package/dist/mcp/route.js +230 -0
- package/dist/next/get-document.d.ts +64 -0
- package/dist/next/get-document.d.ts.map +1 -0
- package/dist/next/get-document.js +208 -0
- package/dist/next/get-document.test.d.ts +2 -0
- package/dist/next/get-document.test.d.ts.map +1 -0
- package/dist/next/get-document.test.js +155 -0
- package/dist/next/index.d.ts +9 -0
- package/dist/next/index.d.ts.map +1 -0
- package/dist/next/index.js +4 -0
- package/dist/next/next-helpers.test.d.ts +2 -0
- package/dist/next/next-helpers.test.d.ts.map +1 -0
- package/dist/next/next-helpers.test.js +122 -0
- package/dist/next/revalidate.d.ts +44 -0
- package/dist/next/revalidate.d.ts.map +1 -0
- package/dist/next/revalidate.js +45 -0
- package/dist/next/seo.d.ts +43 -0
- package/dist/next/seo.d.ts.map +1 -0
- package/dist/next/seo.js +53 -0
- package/dist/next/tags.d.ts +23 -0
- package/dist/next/tags.d.ts.map +1 -0
- package/dist/next/tags.js +31 -0
- package/dist/plugin/access.d.ts +19 -0
- package/dist/plugin/access.d.ts.map +1 -0
- package/dist/plugin/access.js +32 -0
- package/dist/plugin/collections/form-submissions.d.ts +10 -0
- package/dist/plugin/collections/form-submissions.d.ts.map +1 -0
- package/dist/plugin/collections/form-submissions.js +37 -0
- package/dist/plugin/collections/templates.d.ts +13 -0
- package/dist/plugin/collections/templates.d.ts.map +1 -0
- package/dist/plugin/collections/templates.js +118 -0
- package/dist/plugin/endpoints/access.test.d.ts +2 -0
- package/dist/plugin/endpoints/access.test.d.ts.map +1 -0
- package/dist/plugin/endpoints/access.test.js +91 -0
- package/dist/plugin/endpoints/adapter-contract.test.d.ts +2 -0
- package/dist/plugin/endpoints/adapter-contract.test.d.ts.map +1 -0
- package/dist/plugin/endpoints/adapter-contract.test.js +45 -0
- package/dist/plugin/endpoints/context.d.ts +65 -0
- package/dist/plugin/endpoints/context.d.ts.map +1 -0
- package/dist/plugin/endpoints/context.js +113 -0
- package/dist/plugin/endpoints/data-context.d.ts +8 -0
- package/dist/plugin/endpoints/data-context.d.ts.map +1 -0
- package/dist/plugin/endpoints/data-context.js +63 -0
- package/dist/plugin/endpoints/data-endpoints.test.d.ts +2 -0
- package/dist/plugin/endpoints/data-endpoints.test.d.ts.map +1 -0
- package/dist/plugin/endpoints/data-endpoints.test.js +125 -0
- package/dist/plugin/endpoints/data-query.test.d.ts +2 -0
- package/dist/plugin/endpoints/data-query.test.d.ts.map +1 -0
- package/dist/plugin/endpoints/data-query.test.js +96 -0
- package/dist/plugin/endpoints/data-schema.d.ts +5 -0
- package/dist/plugin/endpoints/data-schema.d.ts.map +1 -0
- package/dist/plugin/endpoints/data-schema.js +21 -0
- package/dist/plugin/endpoints/data.d.ts +10 -0
- package/dist/plugin/endpoints/data.d.ts.map +1 -0
- package/dist/plugin/endpoints/data.js +96 -0
- package/dist/plugin/endpoints/document.d.ts +11 -0
- package/dist/plugin/endpoints/document.d.ts.map +1 -0
- package/dist/plugin/endpoints/document.js +119 -0
- package/dist/plugin/endpoints/documents.d.ts +10 -0
- package/dist/plugin/endpoints/documents.d.ts.map +1 -0
- package/dist/plugin/endpoints/documents.js +145 -0
- package/dist/plugin/endpoints/endpoints.test-kit.d.ts +42 -0
- package/dist/plugin/endpoints/endpoints.test-kit.d.ts.map +1 -0
- package/dist/plugin/endpoints/endpoints.test-kit.js +141 -0
- package/dist/plugin/endpoints/endpoints.test.d.ts +2 -0
- package/dist/plugin/endpoints/endpoints.test.d.ts.map +1 -0
- package/dist/plugin/endpoints/endpoints.test.js +209 -0
- package/dist/plugin/endpoints/forms.d.ts +16 -0
- package/dist/plugin/endpoints/forms.d.ts.map +1 -0
- package/dist/plugin/endpoints/forms.js +199 -0
- package/dist/plugin/endpoints/forms.test.d.ts +2 -0
- package/dist/plugin/endpoints/forms.test.d.ts.map +1 -0
- package/dist/plugin/endpoints/forms.test.js +346 -0
- package/dist/plugin/endpoints/guards.d.ts +9 -0
- package/dist/plugin/endpoints/guards.d.ts.map +1 -0
- package/dist/plugin/endpoints/guards.js +35 -0
- package/dist/plugin/endpoints/index.d.ts +6 -0
- package/dist/plugin/endpoints/index.d.ts.map +1 -0
- package/dist/plugin/endpoints/index.js +32 -0
- package/dist/plugin/endpoints/locales-no-fallback.test.d.ts +2 -0
- package/dist/plugin/endpoints/locales-no-fallback.test.d.ts.map +1 -0
- package/dist/plugin/endpoints/locales-no-fallback.test.js +30 -0
- package/dist/plugin/endpoints/locales.test.d.ts +2 -0
- package/dist/plugin/endpoints/locales.test.d.ts.map +1 -0
- package/dist/plugin/endpoints/locales.test.js +128 -0
- package/dist/plugin/endpoints/manifest.d.ts +5 -0
- package/dist/plugin/endpoints/manifest.d.ts.map +1 -0
- package/dist/plugin/endpoints/manifest.js +18 -0
- package/dist/plugin/endpoints/mcp-http-backend.test.d.ts +2 -0
- package/dist/plugin/endpoints/mcp-http-backend.test.d.ts.map +1 -0
- package/dist/plugin/endpoints/mcp-http-backend.test.js +84 -0
- package/dist/plugin/endpoints/mcp-off.test.d.ts +2 -0
- package/dist/plugin/endpoints/mcp-off.test.d.ts.map +1 -0
- package/dist/plugin/endpoints/mcp-off.test.js +33 -0
- package/dist/plugin/endpoints/mcp-publish.test.d.ts +2 -0
- package/dist/plugin/endpoints/mcp-publish.test.d.ts.map +1 -0
- package/dist/plugin/endpoints/mcp-publish.test.js +42 -0
- package/dist/plugin/endpoints/mcp-route.test.d.ts +2 -0
- package/dist/plugin/endpoints/mcp-route.test.d.ts.map +1 -0
- package/dist/plugin/endpoints/mcp-route.test.js +132 -0
- package/dist/plugin/endpoints/mcp.test.d.ts +2 -0
- package/dist/plugin/endpoints/mcp.test.d.ts.map +1 -0
- package/dist/plugin/endpoints/mcp.test.js +231 -0
- package/dist/plugin/endpoints/media.d.ts +7 -0
- package/dist/plugin/endpoints/media.d.ts.map +1 -0
- package/dist/plugin/endpoints/media.js +115 -0
- package/dist/plugin/endpoints/media.test.d.ts +2 -0
- package/dist/plugin/endpoints/media.test.d.ts.map +1 -0
- package/dist/plugin/endpoints/media.test.js +95 -0
- package/dist/plugin/endpoints/publish-a11y.test.d.ts +2 -0
- package/dist/plugin/endpoints/publish-a11y.test.d.ts.map +1 -0
- package/dist/plugin/endpoints/publish-a11y.test.js +27 -0
- package/dist/plugin/endpoints/publish.d.ts +9 -0
- package/dist/plugin/endpoints/publish.d.ts.map +1 -0
- package/dist/plugin/endpoints/publish.js +81 -0
- package/dist/plugin/endpoints/rename.d.ts +9 -0
- package/dist/plugin/endpoints/rename.d.ts.map +1 -0
- package/dist/plugin/endpoints/rename.js +47 -0
- package/dist/plugin/endpoints/respond.d.ts +9 -0
- package/dist/plugin/endpoints/respond.d.ts.map +1 -0
- package/dist/plugin/endpoints/respond.js +10 -0
- package/dist/plugin/endpoints/samples.d.ts +5 -0
- package/dist/plugin/endpoints/samples.d.ts.map +1 -0
- package/dist/plugin/endpoints/samples.js +42 -0
- package/dist/plugin/endpoints/save.d.ts +9 -0
- package/dist/plugin/endpoints/save.d.ts.map +1 -0
- package/dist/plugin/endpoints/save.js +58 -0
- package/dist/plugin/endpoints/session.d.ts +5 -0
- package/dist/plugin/endpoints/session.d.ts.map +1 -0
- package/dist/plugin/endpoints/session.js +30 -0
- package/dist/plugin/endpoints/templates.test.d.ts +2 -0
- package/dist/plugin/endpoints/templates.test.d.ts.map +1 -0
- package/dist/plugin/endpoints/templates.test.js +191 -0
- package/dist/plugin/fields.d.ts +28 -0
- package/dist/plugin/fields.d.ts.map +1 -0
- package/dist/plugin/fields.js +62 -0
- package/dist/plugin/forms/forms.test.d.ts +2 -0
- package/dist/plugin/forms/forms.test.d.ts.map +1 -0
- package/dist/plugin/forms/forms.test.js +88 -0
- package/dist/plugin/forms/notify.d.ts +6 -0
- package/dist/plugin/forms/notify.d.ts.map +1 -0
- package/dist/plugin/forms/notify.js +13 -0
- package/dist/plugin/forms/rate-limit.d.ts +24 -0
- package/dist/plugin/forms/rate-limit.d.ts.map +1 -0
- package/dist/plugin/forms/rate-limit.js +36 -0
- package/dist/plugin/forms/validate.d.ts +22 -0
- package/dist/plugin/forms/validate.d.ts.map +1 -0
- package/dist/plugin/forms/validate.js +79 -0
- package/dist/plugin/hooks/layout-hook.d.ts +9 -0
- package/dist/plugin/hooks/layout-hook.d.ts.map +1 -0
- package/dist/plugin/hooks/layout-hook.js +33 -0
- package/dist/plugin/hooks/layout-hook.test.d.ts +2 -0
- package/dist/plugin/hooks/layout-hook.test.d.ts.map +1 -0
- package/dist/plugin/hooks/layout-hook.test.js +191 -0
- package/dist/plugin/hooks/process-layout.d.ts +32 -0
- package/dist/plugin/hooks/process-layout.d.ts.map +1 -0
- package/dist/plugin/hooks/process-layout.js +68 -0
- package/dist/plugin/hooks/read-layout.d.ts +17 -0
- package/dist/plugin/hooks/read-layout.d.ts.map +1 -0
- package/dist/plugin/hooks/read-layout.js +39 -0
- package/dist/plugin/index.d.ts +18 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/index.js +105 -0
- package/dist/plugin/locales.d.ts +32 -0
- package/dist/plugin/locales.d.ts.map +1 -0
- package/dist/plugin/locales.js +56 -0
- package/dist/plugin/options.d.ts +77 -0
- package/dist/plugin/options.d.ts.map +1 -0
- package/dist/plugin/options.js +123 -0
- package/dist/plugin/plugin.test.d.ts +2 -0
- package/dist/plugin/plugin.test.d.ts.map +1 -0
- package/dist/plugin/plugin.test.js +186 -0
- package/dist/plugin/write-guard.d.ts +14 -0
- package/dist/plugin/write-guard.d.ts.map +1 -0
- package/dist/plugin/write-guard.js +18 -0
- package/package.json +108 -3
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { dataSchemaResponseSchema } from '../../contract.js';
|
|
2
|
+
import { schemaFromCollection } from '../../data/index.js';
|
|
3
|
+
import { allowed, collectionOf, schemaEnv } from './context.js';
|
|
4
|
+
import { fail, json } from './respond.js';
|
|
5
|
+
/** `GET /api/buildr/data-schema/:collection`: what the bindings of this collection's documents can reach. */
|
|
6
|
+
export const dataSchemaEndpoint = (env) => ({
|
|
7
|
+
path: '/buildr/data-schema/:collection',
|
|
8
|
+
method: 'get',
|
|
9
|
+
handler: async (req) => {
|
|
10
|
+
const collection = collectionOf(env, req);
|
|
11
|
+
if (!collection.ok)
|
|
12
|
+
return collection.response;
|
|
13
|
+
if (!(await allowed(env, 'edit', req)))
|
|
14
|
+
return fail(403, 'You may not edit with the builder.');
|
|
15
|
+
const { source, options } = schemaEnv(env, req);
|
|
16
|
+
const schema = schemaFromCollection(source, collection.value, options);
|
|
17
|
+
if (schema === undefined)
|
|
18
|
+
return fail(404, `The collection "${collection.value}" has no data.`);
|
|
19
|
+
return json(dataSchemaResponseSchema.parse(schema));
|
|
20
|
+
},
|
|
21
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Endpoint } from 'payload';
|
|
2
|
+
import { type EndpointEnv } from './context.ts';
|
|
3
|
+
/**
|
|
4
|
+
* `POST /api/buildr/data/query {spec, locale?}`: what a `Query` binding resolves to in the canvas.
|
|
5
|
+
* Only allowlisted collections, fields and sort keys (`options.queryable`) can be asked for.
|
|
6
|
+
*/
|
|
7
|
+
export declare const dataQueryEndpoint: (env: EndpointEnv) => Endpoint;
|
|
8
|
+
/** `POST /api/buildr/data/media {ids, locale?}`: media assets by id, as `{ [id]: MediaAsset }`. */
|
|
9
|
+
export declare const dataMediaEndpoint: (env: EndpointEnv) => Endpoint;
|
|
10
|
+
//# sourceMappingURL=data.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../../src/plugin/endpoints/data.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAkB,MAAM,SAAS,CAAC;AASxD,OAAO,EAAmB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AA4BjE;;;GAGG;AACH,eAAO,MAAM,iBAAiB,QAAS,WAAW,KAAG,QAuBnD,CAAC;AAEH,mGAAmG;AACnG,eAAO,MAAM,iBAAiB,QAAS,WAAW,KAAG,QAuBnD,CAAC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { dataMediaRequestSchema, dataMediaResponseSchema, dataQueryRequestSchema, dataQueryResponseSchema, } from '../../contract.js';
|
|
2
|
+
import { createPayloadDataSource, DataQueryError } from '../../data/index.js';
|
|
3
|
+
import { chooseLocale } from '../locales.js';
|
|
4
|
+
import { allowed, bodyOf } from './context.js';
|
|
5
|
+
import { mutationGuard } from './guards.js';
|
|
6
|
+
import { fail, json, unauthorized } from './respond.js';
|
|
7
|
+
/** The canvas source: reads with the access of the editing user, drafts included. */
|
|
8
|
+
function sourceOf(env, req) {
|
|
9
|
+
const contextNames = {};
|
|
10
|
+
for (const [slug, own] of Object.entries(env.options.collections)) {
|
|
11
|
+
contextNames[slug] = own.context;
|
|
12
|
+
}
|
|
13
|
+
return createPayloadDataSource({
|
|
14
|
+
payload: req.payload,
|
|
15
|
+
req,
|
|
16
|
+
queryable: env.options.queryable,
|
|
17
|
+
mediaCollection: env.options.media?.collection,
|
|
18
|
+
contextNames,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/** The checks both endpoints share; `undefined` when the request may go on. */
|
|
22
|
+
async function guard(env, req) {
|
|
23
|
+
if (req.user === null || req.user === undefined)
|
|
24
|
+
return unauthorized();
|
|
25
|
+
const rejected = mutationGuard(req);
|
|
26
|
+
if (rejected !== undefined)
|
|
27
|
+
return rejected;
|
|
28
|
+
if (!(await allowed(env, 'edit', req)))
|
|
29
|
+
return fail(403, 'You may not edit with the builder.');
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* `POST /api/buildr/data/query {spec, locale?}`: what a `Query` binding resolves to in the canvas.
|
|
34
|
+
* Only allowlisted collections, fields and sort keys (`options.queryable`) can be asked for.
|
|
35
|
+
*/
|
|
36
|
+
export const dataQueryEndpoint = (env) => ({
|
|
37
|
+
path: '/buildr/data/query',
|
|
38
|
+
method: 'post',
|
|
39
|
+
handler: async (req) => {
|
|
40
|
+
const rejected = await guard(env, req);
|
|
41
|
+
if (rejected !== undefined)
|
|
42
|
+
return rejected;
|
|
43
|
+
const body = await bodyOf(req);
|
|
44
|
+
if (!body.ok)
|
|
45
|
+
return body.response;
|
|
46
|
+
const parsed = dataQueryRequestSchema.safeParse(body.value);
|
|
47
|
+
if (!parsed.success)
|
|
48
|
+
return fail(400, 'The request does not match the contract.');
|
|
49
|
+
const locale = chooseLocale(req, parsed.data.locale);
|
|
50
|
+
if (!locale.ok)
|
|
51
|
+
return locale.response;
|
|
52
|
+
try {
|
|
53
|
+
const result = await sourceOf(env, req).query(parsed.data.spec, {
|
|
54
|
+
locale: locale.locale ?? '',
|
|
55
|
+
mode: 'canvas',
|
|
56
|
+
});
|
|
57
|
+
return json(dataQueryResponseSchema.parse(result));
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (error instanceof DataQueryError)
|
|
61
|
+
return fail(422, error.message);
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
/** `POST /api/buildr/data/media {ids, locale?}`: media assets by id, as `{ [id]: MediaAsset }`. */
|
|
67
|
+
export const dataMediaEndpoint = (env) => ({
|
|
68
|
+
path: '/buildr/data/media',
|
|
69
|
+
method: 'post',
|
|
70
|
+
handler: async (req) => {
|
|
71
|
+
const rejected = await guard(env, req);
|
|
72
|
+
if (rejected !== undefined)
|
|
73
|
+
return rejected;
|
|
74
|
+
const body = await bodyOf(req);
|
|
75
|
+
if (!body.ok)
|
|
76
|
+
return body.response;
|
|
77
|
+
const parsed = dataMediaRequestSchema.safeParse(body.value);
|
|
78
|
+
if (!parsed.success)
|
|
79
|
+
return fail(400, 'The request does not match the contract.');
|
|
80
|
+
const locale = chooseLocale(req, parsed.data.locale);
|
|
81
|
+
if (!locale.ok)
|
|
82
|
+
return locale.response;
|
|
83
|
+
try {
|
|
84
|
+
const found = await sourceOf(env, req).getMedia(parsed.data.ids, {
|
|
85
|
+
locale: locale.locale ?? '',
|
|
86
|
+
mode: 'canvas',
|
|
87
|
+
});
|
|
88
|
+
return json(dataMediaResponseSchema.parse(found));
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (error instanceof DataQueryError)
|
|
92
|
+
return fail(422, error.message);
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Endpoint, PayloadRequest } from 'payload';
|
|
2
|
+
import { type DocumentTarget, type EndpointEnv } from './context.ts';
|
|
3
|
+
export declare const titleOf: (req: PayloadRequest, target: DocumentTarget, doc: Record<string, unknown>) => string;
|
|
4
|
+
/** `GET /api/buildr/documents/:collection/:id`: the document the editor opens, migrated in memory. */
|
|
5
|
+
export declare const getDocumentEndpoint: (env: EndpointEnv) => Endpoint;
|
|
6
|
+
/**
|
|
7
|
+
* `GET /api/buildr/documents/:collection/:id/revision`: only the revision and who saved it, so
|
|
8
|
+
* the editor can poll for a save made elsewhere (PB-143) without loading the document.
|
|
9
|
+
*/
|
|
10
|
+
export declare const revisionEndpoint: (env: EndpointEnv) => Endpoint;
|
|
11
|
+
//# sourceMappingURL=document.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../../../src/plugin/endpoints/document.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAWxD,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,WAAW,EAIjB,MAAM,cAAc,CAAC;AAGtB,eAAO,MAAM,OAAO,QACb,cAAc,UACX,cAAc,OACjB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WAK7B,CAAC;AAEF,sGAAsG;AACtG,eAAO,MAAM,mBAAmB,QAAS,WAAW,KAAG,QAgDrD,CAAC;AA0BH;;;GAGG;AACH,eAAO,MAAM,gBAAgB,QAAS,WAAW,KAAG,QAsBlD,CAAC"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { documentQuerySchema, documentResponseSchema, revisionResponseSchema, } from '../../contract.js';
|
|
2
|
+
import { resolveLayout } from '../../data/index.js';
|
|
3
|
+
import { readLayout } from '../hooks/read-layout.js';
|
|
4
|
+
import { chooseLocale } from '../locales.js';
|
|
5
|
+
import { allowed, latestOf, revisionOf, targetOf, } from './context.js';
|
|
6
|
+
import { fail, invalid, json } from './respond.js';
|
|
7
|
+
export const titleOf = (req, target, doc) => {
|
|
8
|
+
const field = req.payload.collections[target.collection]?.config.admin?.useAsTitle ?? 'title';
|
|
9
|
+
const value = doc[field];
|
|
10
|
+
return typeof value === 'string' && value !== '' ? value : target.id;
|
|
11
|
+
};
|
|
12
|
+
/** `GET /api/buildr/documents/:collection/:id`: the document the editor opens, migrated in memory. */
|
|
13
|
+
export const getDocumentEndpoint = (env) => ({
|
|
14
|
+
path: '/buildr/documents/:collection/:id',
|
|
15
|
+
method: 'get',
|
|
16
|
+
handler: async (req) => {
|
|
17
|
+
const target = targetOf(env, req);
|
|
18
|
+
if (!target.ok)
|
|
19
|
+
return target.response;
|
|
20
|
+
if (!(await allowed(env, 'edit', req)))
|
|
21
|
+
return fail(403, 'You may not edit with the builder.');
|
|
22
|
+
const query = documentQuerySchema.safeParse(Object.fromEntries(req.searchParams ?? []));
|
|
23
|
+
if (!query.success)
|
|
24
|
+
return fail(400, 'The query is not valid.');
|
|
25
|
+
const locale = chooseLocale(req, query.data.locale);
|
|
26
|
+
if (!locale.ok)
|
|
27
|
+
return locale.response;
|
|
28
|
+
const found = await latestOf(req, target.value, { localeArgs: locale.args });
|
|
29
|
+
if (!found.ok)
|
|
30
|
+
return found.response;
|
|
31
|
+
const doc = found.value;
|
|
32
|
+
const own = env.options.collections[target.value.collection];
|
|
33
|
+
const resolved = await resolveLayout({
|
|
34
|
+
payload: req.payload,
|
|
35
|
+
req,
|
|
36
|
+
collection: target.value.collection,
|
|
37
|
+
doc,
|
|
38
|
+
contextName: own?.context ?? target.value.collection,
|
|
39
|
+
draft: true,
|
|
40
|
+
});
|
|
41
|
+
// A blank canvas for a document nobody gave a layout: the built-in one is only a render fallback.
|
|
42
|
+
const layout = readLayout(resolved.source === 'builtin' ? undefined : resolved.layout, env.options);
|
|
43
|
+
if (!layout.ok)
|
|
44
|
+
return invalid(layout.diagnostics);
|
|
45
|
+
const path = own?.path;
|
|
46
|
+
const body = {
|
|
47
|
+
ref: target.value,
|
|
48
|
+
title: titleOf(req, target.value, doc),
|
|
49
|
+
slug: typeof doc['slug'] === 'string' ? doc['slug'] : null,
|
|
50
|
+
status: doc['_status'] === 'published' ? 'published' : 'draft',
|
|
51
|
+
updatedAt: String(doc['updatedAt'] ?? ''),
|
|
52
|
+
revision: revisionOf(doc),
|
|
53
|
+
document: layout.doc,
|
|
54
|
+
contextRef: `${target.value.collection}:${target.value.id}`,
|
|
55
|
+
layoutSource: resolved.source,
|
|
56
|
+
layoutRef: resolved.layoutRef,
|
|
57
|
+
previewPath: path === undefined ? null : path(doc),
|
|
58
|
+
...(layout.readOnly ? { readOnly: true } : {}),
|
|
59
|
+
};
|
|
60
|
+
return json(documentResponseSchema.parse(body));
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
/** The label of the user a write was attributed to; empty when it cannot be told. */
|
|
64
|
+
async function updaterLabel(req, ref) {
|
|
65
|
+
const at = ref;
|
|
66
|
+
if (typeof at?.relationTo !== 'string')
|
|
67
|
+
return undefined;
|
|
68
|
+
const value = at.value;
|
|
69
|
+
const id = typeof value === 'object' && value !== null ? value.id : value;
|
|
70
|
+
if (typeof id !== 'string' && typeof id !== 'number')
|
|
71
|
+
return undefined;
|
|
72
|
+
try {
|
|
73
|
+
// Only the name is read, on the server's authority: an editor may see who saved, not more.
|
|
74
|
+
const user = (await req.payload.findByID({
|
|
75
|
+
collection: at.relationTo,
|
|
76
|
+
id,
|
|
77
|
+
depth: 0,
|
|
78
|
+
overrideAccess: true,
|
|
79
|
+
}));
|
|
80
|
+
const name = user['name'];
|
|
81
|
+
const email = user['email'];
|
|
82
|
+
if (typeof name === 'string' && name !== '')
|
|
83
|
+
return name;
|
|
84
|
+
return typeof email === 'string' && email !== '' ? email : undefined;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* `GET /api/buildr/documents/:collection/:id/revision`: only the revision and who saved it, so
|
|
92
|
+
* the editor can poll for a save made elsewhere (PB-143) without loading the document.
|
|
93
|
+
*/
|
|
94
|
+
export const revisionEndpoint = (env) => ({
|
|
95
|
+
path: '/buildr/documents/:collection/:id/revision',
|
|
96
|
+
method: 'get',
|
|
97
|
+
handler: async (req) => {
|
|
98
|
+
const target = targetOf(env, req);
|
|
99
|
+
if (!target.ok)
|
|
100
|
+
return target.response;
|
|
101
|
+
if (!(await allowed(env, 'edit', req)))
|
|
102
|
+
return fail(403, 'You may not edit with the builder.');
|
|
103
|
+
const found = await latestOf(req, target.value, {
|
|
104
|
+
select: { buildrRevision: true, updatedAt: true, buildrUpdatedBy: true },
|
|
105
|
+
});
|
|
106
|
+
if (!found.ok)
|
|
107
|
+
return found.response;
|
|
108
|
+
const doc = found.value;
|
|
109
|
+
const by = env.options.mcp.enabled
|
|
110
|
+
? await updaterLabel(req, doc['buildrUpdatedBy'])
|
|
111
|
+
: undefined;
|
|
112
|
+
const body = {
|
|
113
|
+
revision: revisionOf(doc),
|
|
114
|
+
updatedAt: String(doc['updatedAt'] ?? ''),
|
|
115
|
+
...(by === undefined ? {} : { updatedBy: by }),
|
|
116
|
+
};
|
|
117
|
+
return json(revisionResponseSchema.parse(body));
|
|
118
|
+
},
|
|
119
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Endpoint } from 'payload';
|
|
2
|
+
import { type EndpointEnv } from './context.ts';
|
|
3
|
+
/** `GET /api/buildr/documents?collection&search&page`: the builder documents the user may read. */
|
|
4
|
+
export declare const listDocumentsEndpoint: (env: EndpointEnv) => Endpoint;
|
|
5
|
+
/**
|
|
6
|
+
* `POST /api/buildr/documents`: creates a draft in a builder collection (title, optional slug and
|
|
7
|
+
* template). Collection access decides; the document is never published, whoever asks.
|
|
8
|
+
*/
|
|
9
|
+
export declare const createDocumentEndpoint: (env: EndpointEnv) => Endpoint;
|
|
10
|
+
//# sourceMappingURL=documents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../../src/plugin/endpoints/documents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAyB,MAAM,SAAS,CAAC;AAW/D,OAAO,EAGL,KAAK,WAAW,EAMjB,MAAM,cAAc,CAAC;AAqDtB,mGAAmG;AACnG,eAAO,MAAM,qBAAqB,QAAS,WAAW,KAAG,QAwCvD,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,sBAAsB,QAAS,WAAW,KAAG,QAoDxD,CAAC"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { createDocumentRequestSchema, createDocumentResponseSchema, DOCUMENT_LIST_PAGE_SIZE, documentListQuerySchema, documentListResponseSchema, } from '../../contract.js';
|
|
2
|
+
import { resolveLayout } from '../../data/index.js';
|
|
3
|
+
import { isApiKeyRequest } from '../access.js';
|
|
4
|
+
import { allowed, bodyOf, mcpMayUse, revisionOf, updatedBy, writeContext, writeLimit, } from './context.js';
|
|
5
|
+
import { titleOf } from './document.js';
|
|
6
|
+
import { mutationGuard } from './guards.js';
|
|
7
|
+
import { fail, json, unauthorized } from './respond.js';
|
|
8
|
+
const hasField = (req, collection, name) => req.payload.collections[collection]?.config.flattenedFields.some((field) => field.name === name) === true;
|
|
9
|
+
async function summaryOf(env, req, collection, doc) {
|
|
10
|
+
const own = env.options.collections[collection];
|
|
11
|
+
const resolved = await resolveLayout({
|
|
12
|
+
payload: req.payload,
|
|
13
|
+
req,
|
|
14
|
+
collection,
|
|
15
|
+
doc,
|
|
16
|
+
contextName: own?.context ?? collection,
|
|
17
|
+
draft: true,
|
|
18
|
+
});
|
|
19
|
+
const id = doc['id'];
|
|
20
|
+
return {
|
|
21
|
+
ref: { collection, id },
|
|
22
|
+
title: titleOf(req, { collection, id: String(id) }, doc),
|
|
23
|
+
slug: typeof doc['slug'] === 'string' ? doc['slug'] : null,
|
|
24
|
+
status: doc['_status'] === 'published' ? 'published' : 'draft',
|
|
25
|
+
updatedAt: String(doc['updatedAt'] ?? ''),
|
|
26
|
+
revision: revisionOf(doc),
|
|
27
|
+
layoutSource: resolved.source,
|
|
28
|
+
previewPath: own?.path === undefined ? null : own.path(doc),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** The response to send when the collection is not a builder collection or an agent may not use it. */
|
|
32
|
+
function refuseCollection(env, req, collection) {
|
|
33
|
+
if (env.options.collections[collection] === undefined) {
|
|
34
|
+
return fail(404, `The collection "${collection}" was not found.`);
|
|
35
|
+
}
|
|
36
|
+
if (isApiKeyRequest(req) && !mcpMayUse(env, collection)) {
|
|
37
|
+
return fail(403, `Agents may not use the collection "${collection}".`);
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
/** `GET /api/buildr/documents?collection&search&page`: the builder documents the user may read. */
|
|
42
|
+
export const listDocumentsEndpoint = (env) => ({
|
|
43
|
+
path: '/buildr/documents',
|
|
44
|
+
method: 'get',
|
|
45
|
+
handler: async (req) => {
|
|
46
|
+
if (req.user === null || req.user === undefined)
|
|
47
|
+
return unauthorized();
|
|
48
|
+
if (!(await allowed(env, 'edit', req)))
|
|
49
|
+
return fail(403, 'You may not edit with the builder.');
|
|
50
|
+
const query = documentListQuerySchema.safeParse(Object.fromEntries(req.searchParams ?? []));
|
|
51
|
+
if (!query.success)
|
|
52
|
+
return fail(400, 'The query is not valid.');
|
|
53
|
+
const { collection, search, page } = query.data;
|
|
54
|
+
const refused = refuseCollection(env, req, collection);
|
|
55
|
+
if (refused !== undefined)
|
|
56
|
+
return refused;
|
|
57
|
+
const titleField = req.payload.collections[collection]?.config.admin?.useAsTitle ?? 'title';
|
|
58
|
+
const fields = [titleField, ...(hasField(req, collection, 'slug') ? ['slug'] : [])];
|
|
59
|
+
const where = search === undefined || search === ''
|
|
60
|
+
? undefined
|
|
61
|
+
: { or: fields.map((name) => ({ [name]: { like: search } })) };
|
|
62
|
+
const result = await req.payload.find({
|
|
63
|
+
collection,
|
|
64
|
+
draft: true,
|
|
65
|
+
depth: 0,
|
|
66
|
+
page,
|
|
67
|
+
limit: DOCUMENT_LIST_PAGE_SIZE,
|
|
68
|
+
sort: '-updatedAt',
|
|
69
|
+
req,
|
|
70
|
+
overrideAccess: false,
|
|
71
|
+
...(where === undefined ? {} : { where }),
|
|
72
|
+
});
|
|
73
|
+
const items = await Promise.all(result.docs.map((doc) => summaryOf(env, req, collection, doc)));
|
|
74
|
+
return json(documentListResponseSchema.parse({
|
|
75
|
+
items,
|
|
76
|
+
page: result.page ?? page,
|
|
77
|
+
totalPages: result.totalPages,
|
|
78
|
+
}));
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
/**
|
|
82
|
+
* `POST /api/buildr/documents`: creates a draft in a builder collection (title, optional slug and
|
|
83
|
+
* template). Collection access decides; the document is never published, whoever asks.
|
|
84
|
+
*/
|
|
85
|
+
export const createDocumentEndpoint = (env) => ({
|
|
86
|
+
path: '/buildr/documents',
|
|
87
|
+
method: 'post',
|
|
88
|
+
handler: async (req) => {
|
|
89
|
+
if (req.user === null || req.user === undefined)
|
|
90
|
+
return unauthorized();
|
|
91
|
+
const rejected = mutationGuard(req);
|
|
92
|
+
if (rejected !== undefined)
|
|
93
|
+
return rejected;
|
|
94
|
+
if (!(await allowed(env, 'edit', req)))
|
|
95
|
+
return fail(403, 'You may not edit with the builder.');
|
|
96
|
+
const limited = await writeLimit(env, req);
|
|
97
|
+
if (limited !== undefined)
|
|
98
|
+
return limited;
|
|
99
|
+
const body = await bodyOf(req);
|
|
100
|
+
if (!body.ok)
|
|
101
|
+
return body.response;
|
|
102
|
+
const parsed = createDocumentRequestSchema.safeParse(body.value);
|
|
103
|
+
if (!parsed.success)
|
|
104
|
+
return fail(400, 'The request does not match the contract.');
|
|
105
|
+
const { collection, title, slug, template } = parsed.data;
|
|
106
|
+
const refused = refuseCollection(env, req, collection);
|
|
107
|
+
if (refused !== undefined)
|
|
108
|
+
return refused;
|
|
109
|
+
if (template !== undefined && env.options.collections[collection]?.templates !== true) {
|
|
110
|
+
return fail(400, `The collection "${collection}" does not take templates.`);
|
|
111
|
+
}
|
|
112
|
+
if (slug !== undefined && !hasField(req, collection, 'slug')) {
|
|
113
|
+
return fail(400, `The collection "${collection}" has no slug.`);
|
|
114
|
+
}
|
|
115
|
+
const titleField = req.payload.collections[collection]?.config.admin?.useAsTitle ?? 'title';
|
|
116
|
+
try {
|
|
117
|
+
const created = await req.payload.create({
|
|
118
|
+
collection,
|
|
119
|
+
data: {
|
|
120
|
+
[titleField]: title,
|
|
121
|
+
...(slug === undefined ? {} : { slug }),
|
|
122
|
+
...(template === undefined ? {} : { template }),
|
|
123
|
+
buildrRevision: 0,
|
|
124
|
+
...updatedBy(env, req),
|
|
125
|
+
},
|
|
126
|
+
draft: true,
|
|
127
|
+
depth: 0,
|
|
128
|
+
req,
|
|
129
|
+
overrideAccess: false,
|
|
130
|
+
context: writeContext(req),
|
|
131
|
+
});
|
|
132
|
+
const summary = await summaryOf(env, req, collection, created);
|
|
133
|
+
return json(createDocumentResponseSchema.parse(summary), 201);
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
const status = error.status;
|
|
137
|
+
if (status === 403)
|
|
138
|
+
return fail(403, 'You may not create documents in this collection.');
|
|
139
|
+
if (status === 400) {
|
|
140
|
+
return fail(400, error instanceof Error ? error.message : 'The document is not valid.');
|
|
141
|
+
}
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type BuilderDocument } from '@next-buildr/core';
|
|
2
|
+
import { type CollectionConfig, type Config, type EmailAdapter, type Field, type GlobalConfig, type Payload } from 'payload';
|
|
3
|
+
import type { BuildrPluginOptions } from '../options.ts';
|
|
4
|
+
export declare const meta: import("@next-buildr/core").RegistryMeta;
|
|
5
|
+
/** The widget renamed `label` to `title` in version 2. */
|
|
6
|
+
export declare const migrations: never;
|
|
7
|
+
/** A page with one child (a widget unless `type` says otherwise) carrying `props`. */
|
|
8
|
+
export declare const withChild: (props: Record<string, unknown>, options?: {
|
|
9
|
+
version?: number;
|
|
10
|
+
type?: string;
|
|
11
|
+
}) => BuilderDocument;
|
|
12
|
+
export declare const title: (text: string) => {
|
|
13
|
+
title: import("@next-buildr/core").StaticValue<string>;
|
|
14
|
+
};
|
|
15
|
+
export interface Harness {
|
|
16
|
+
readonly payload: Payload;
|
|
17
|
+
readonly token: string;
|
|
18
|
+
/** A request to `/api{path}` as the logged-in user (or, with `token: null`, anonymously). */
|
|
19
|
+
readonly call: (method: string, path: string, options?: {
|
|
20
|
+
body?: unknown;
|
|
21
|
+
token?: string | null;
|
|
22
|
+
headers?: Record<string, string>;
|
|
23
|
+
}) => Promise<{
|
|
24
|
+
status: number;
|
|
25
|
+
body: any;
|
|
26
|
+
}>;
|
|
27
|
+
/** Creates a user and returns their token. */
|
|
28
|
+
readonly login: (email: string, role?: string) => Promise<string>;
|
|
29
|
+
readonly close: () => void;
|
|
30
|
+
}
|
|
31
|
+
/** One live Payload per test file: its schema push shares process-wide state. */
|
|
32
|
+
export declare function boot(key: string, options?: Partial<BuildrPluginOptions>, extra?: {
|
|
33
|
+
readonly pageFields?: Field[];
|
|
34
|
+
/** Access control of the pages collection (default: Payload default, authenticated only). */
|
|
35
|
+
readonly pageAccess?: CollectionConfig['access'];
|
|
36
|
+
readonly collections?: CollectionConfig[];
|
|
37
|
+
readonly globals?: GlobalConfig[];
|
|
38
|
+
readonly email?: EmailAdapter;
|
|
39
|
+
/** Payload localization; the `title` of the pages is then localized. */
|
|
40
|
+
readonly localization?: Config['localization'];
|
|
41
|
+
}): Promise<Harness>;
|
|
42
|
+
//# sourceMappingURL=endpoints.test-kit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoints.test-kit.d.ts","sourceRoot":"","sources":["../../../src/plugin/endpoints/endpoints.test-kit.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,eAAe,EAKrB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,MAAM,EACX,KAAK,YAAY,EACjB,KAAK,KAAK,EACV,KAAK,YAAY,EAGjB,KAAK,OAAO,EACb,MAAM,SAAS,CAAC;AAEjB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AA8BzD,eAAO,MAAM,IAAI,0CAA0D,CAAC;AAC5E,0DAA0D;AAC1D,eAAO,MAAM,UAAU,EAgBlB,KAAK,CAAC;AAEX,sFAAsF;AACtF,eAAO,MAAM,SAAS,UACb,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YACrB;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,KAC3C,eAWF,CAAC;AACF,eAAO,MAAM,KAAK,SAAU,MAAM;IAAQ,KAAK;CAAY,CAAC;AAE5D,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,6FAA6F;IAC7F,QAAQ,CAAC,IAAI,EAAE,CACb,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,KAClF,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;IAC5C,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAClE,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED,iFAAiF;AACjF,wBAAsB,IAAI,CACxB,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,OAAO,CAAC,mBAAmB,CAAM,EAC1C,KAAK,GAAE;IACL,QAAQ,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC;IAC9B,6FAA6F;IAC7F,QAAQ,CAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACjD,QAAQ,CAAC,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC1C,QAAQ,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC;IAC9B,wEAAwE;IACxE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC,CAAC;CAC3C,GACL,OAAO,CAAC,OAAO,CAAC,CAuElB"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { createRegistryMeta, p, s, } from '@next-buildr/core';
|
|
5
|
+
import { sqliteAdapter } from '@payloadcms/db-sqlite';
|
|
6
|
+
import { buildConfig, getPayload, handleEndpoints, } from 'payload';
|
|
7
|
+
import { buildrPlugin } from '../index.js';
|
|
8
|
+
const base = {
|
|
9
|
+
type: 'buildr/widget',
|
|
10
|
+
version: 2,
|
|
11
|
+
label: 'Widget',
|
|
12
|
+
category: 'content',
|
|
13
|
+
props: { title: p.text({ label: 'Title', default: 'Hello' }) },
|
|
14
|
+
contentCategories: ['flow'],
|
|
15
|
+
styles: { groups: [] },
|
|
16
|
+
runtime: 'shared',
|
|
17
|
+
slots: {},
|
|
18
|
+
};
|
|
19
|
+
const page = {
|
|
20
|
+
...base,
|
|
21
|
+
type: 'buildr/page',
|
|
22
|
+
version: 1,
|
|
23
|
+
label: 'Page',
|
|
24
|
+
props: {},
|
|
25
|
+
capabilities: { root: true },
|
|
26
|
+
slots: { default: {} },
|
|
27
|
+
};
|
|
28
|
+
const image = {
|
|
29
|
+
...base,
|
|
30
|
+
type: 'buildr/image',
|
|
31
|
+
version: 1,
|
|
32
|
+
label: 'Image',
|
|
33
|
+
props: { alt: p.text({ label: 'Alt', default: '' }) },
|
|
34
|
+
};
|
|
35
|
+
export const meta = createRegistryMeta({ components: [page, base, image] });
|
|
36
|
+
/** The widget renamed `label` to `title` in version 2. */
|
|
37
|
+
export const migrations = {
|
|
38
|
+
'buildr/page': { currentVersion: 1, steps: [] },
|
|
39
|
+
'buildr/image': { currentVersion: 1, steps: [] },
|
|
40
|
+
'buildr/widget': {
|
|
41
|
+
currentVersion: 2,
|
|
42
|
+
steps: [
|
|
43
|
+
{
|
|
44
|
+
from: 1,
|
|
45
|
+
to: 2,
|
|
46
|
+
migrate: (props) => {
|
|
47
|
+
const { label, ...rest } = props ?? {};
|
|
48
|
+
return { ...rest, title: label };
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
/** A page with one child (a widget unless `type` says otherwise) carrying `props`. */
|
|
55
|
+
export const withChild = (props, options = {}) => {
|
|
56
|
+
const type = options.type ?? 'buildr/widget';
|
|
57
|
+
return {
|
|
58
|
+
schemaVersion: 1,
|
|
59
|
+
root: 'root',
|
|
60
|
+
nodes: {
|
|
61
|
+
root: { id: 'root', type: 'buildr/page', slots: { default: ['child00001'] } },
|
|
62
|
+
child00001: { id: 'child00001', type, props },
|
|
63
|
+
},
|
|
64
|
+
components: { 'buildr/page': 1, [type]: options.version ?? (type === 'buildr/widget' ? 2 : 1) },
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
export const title = (text) => ({ title: s(text) });
|
|
68
|
+
/** One live Payload per test file: its schema push shares process-wide state. */
|
|
69
|
+
export async function boot(key, options = {}, extra = {}) {
|
|
70
|
+
const dir = mkdtempSync(join(tmpdir(), `buildr-payload-${key}-`));
|
|
71
|
+
const config = buildConfig({
|
|
72
|
+
secret: 'test-secret',
|
|
73
|
+
collections: [
|
|
74
|
+
{
|
|
75
|
+
slug: 'pages',
|
|
76
|
+
admin: { useAsTitle: 'title' },
|
|
77
|
+
fields: [
|
|
78
|
+
{ name: 'title', type: 'text', localized: extra.localization !== undefined },
|
|
79
|
+
{ name: 'slug', type: 'text' },
|
|
80
|
+
...(extra.pageFields ?? []),
|
|
81
|
+
],
|
|
82
|
+
...(extra.pageAccess === undefined ? {} : { access: extra.pageAccess }),
|
|
83
|
+
versions: { drafts: { autosave: true }, maxPerDoc: 50 },
|
|
84
|
+
},
|
|
85
|
+
...(extra.collections ?? []),
|
|
86
|
+
],
|
|
87
|
+
...(extra.globals === undefined ? {} : { globals: extra.globals }),
|
|
88
|
+
...(extra.email === undefined ? {} : { email: extra.email }),
|
|
89
|
+
...(extra.localization === undefined ? {} : { localization: extra.localization }),
|
|
90
|
+
db: sqliteAdapter({ client: { url: `file:${join(dir, 'db.sqlite')}` } }),
|
|
91
|
+
plugins: [
|
|
92
|
+
buildrPlugin({
|
|
93
|
+
collections: { pages: { context: 'page', path: (doc) => `/${String(doc['slug'])}` } },
|
|
94
|
+
registry: { meta, migrations },
|
|
95
|
+
...options,
|
|
96
|
+
}),
|
|
97
|
+
],
|
|
98
|
+
typescript: { autoGenerate: false },
|
|
99
|
+
});
|
|
100
|
+
const payload = await getPayload({ config, key });
|
|
101
|
+
const login = async (email) => {
|
|
102
|
+
await payload.create({ collection: 'users', data: { email, password: 'secret-password' } });
|
|
103
|
+
const result = await payload.login({
|
|
104
|
+
collection: 'users',
|
|
105
|
+
data: { email, password: 'secret-password' },
|
|
106
|
+
});
|
|
107
|
+
return result.token;
|
|
108
|
+
};
|
|
109
|
+
const token = await login('editor@example.com');
|
|
110
|
+
const call = async (method, path, init = {}) => {
|
|
111
|
+
const authToken = init.token === undefined ? token : init.token;
|
|
112
|
+
const response = await handleEndpoints({
|
|
113
|
+
config: payload.config,
|
|
114
|
+
request: new Request(`http://localhost/api${path}`, {
|
|
115
|
+
method,
|
|
116
|
+
headers: {
|
|
117
|
+
...(init.body === undefined ? {} : { 'content-type': 'application/json' }),
|
|
118
|
+
...(authToken === null ? {} : { authorization: `JWT ${authToken}` }),
|
|
119
|
+
...init.headers,
|
|
120
|
+
},
|
|
121
|
+
...(init.body === undefined ? {} : { body: JSON.stringify(init.body) }),
|
|
122
|
+
}),
|
|
123
|
+
});
|
|
124
|
+
const text = await response.text();
|
|
125
|
+
return { status: response.status, body: text === '' ? undefined : JSON.parse(text) };
|
|
126
|
+
};
|
|
127
|
+
return {
|
|
128
|
+
payload,
|
|
129
|
+
token,
|
|
130
|
+
call,
|
|
131
|
+
login,
|
|
132
|
+
close: () => {
|
|
133
|
+
try {
|
|
134
|
+
rmSync(dir, { recursive: true, force: true });
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// Windows keeps the database file open a moment longer.
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoints.test.d.ts","sourceRoot":"","sources":["../../../src/plugin/endpoints/endpoints.test.ts"],"names":[],"mappings":""}
|