@next-buildr/payload 0.0.0-stage → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/LICENSE +21 -0
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/adapter/adapter.test.d.ts +2 -0
  4. package/dist/adapter/adapter.test.d.ts.map +1 -0
  5. package/dist/adapter/adapter.test.js +282 -0
  6. package/dist/adapter/canvas-data-source.d.ts +9 -0
  7. package/dist/adapter/canvas-data-source.d.ts.map +1 -0
  8. package/dist/adapter/canvas-data-source.js +24 -0
  9. package/dist/adapter/document-adapter.d.ts +20 -0
  10. package/dist/adapter/document-adapter.d.ts.map +1 -0
  11. package/dist/adapter/document-adapter.js +183 -0
  12. package/dist/adapter/http.d.ts +34 -0
  13. package/dist/adapter/http.d.ts.map +1 -0
  14. package/dist/adapter/http.js +72 -0
  15. package/dist/adapter/index.d.ts +4 -0
  16. package/dist/adapter/index.d.ts.map +1 -0
  17. package/dist/adapter/index.js +5 -0
  18. package/dist/admin/index.d.ts +4 -0
  19. package/dist/admin/index.d.ts.map +1 -0
  20. package/dist/admin/index.js +2 -0
  21. package/dist/admin/layout-field-connected.d.ts +6 -0
  22. package/dist/admin/layout-field-connected.d.ts.map +1 -0
  23. package/dist/admin/layout-field-connected.js +16 -0
  24. package/dist/admin/layout-field-view.d.ts +16 -0
  25. package/dist/admin/layout-field-view.d.ts.map +1 -0
  26. package/dist/admin/layout-field-view.js +14 -0
  27. package/dist/admin/layout-field.d.ts +6 -0
  28. package/dist/admin/layout-field.d.ts.map +1 -0
  29. package/dist/admin/layout-field.js +13 -0
  30. package/dist/admin/layout-field.test.d.ts +2 -0
  31. package/dist/admin/layout-field.test.d.ts.map +1 -0
  32. package/dist/admin/layout-field.test.js +41 -0
  33. package/dist/contract.d.ts +289 -0
  34. package/dist/contract.d.ts.map +1 -0
  35. package/dist/contract.js +176 -0
  36. package/dist/data/build-context.d.ts +20 -0
  37. package/dist/data/build-context.d.ts.map +1 -0
  38. package/dist/data/build-context.js +25 -0
  39. package/dist/data/data.test.d.ts +2 -0
  40. package/dist/data/data.test.d.ts.map +1 -0
  41. package/dist/data/data.test.js +261 -0
  42. package/dist/data/index.d.ts +10 -0
  43. package/dist/data/index.d.ts.map +1 -0
  44. package/dist/data/index.js +5 -0
  45. package/dist/data/normalize.d.ts +14 -0
  46. package/dist/data/normalize.d.ts.map +1 -0
  47. package/dist/data/normalize.js +128 -0
  48. package/dist/data/payload-data-source.d.ts +42 -0
  49. package/dist/data/payload-data-source.d.ts.map +1 -0
  50. package/dist/data/payload-data-source.js +179 -0
  51. package/dist/data/payload-data-source.test.d.ts +2 -0
  52. package/dist/data/payload-data-source.test.d.ts.map +1 -0
  53. package/dist/data/payload-data-source.test.js +160 -0
  54. package/dist/data/resolve-layout.d.ts +45 -0
  55. package/dist/data/resolve-layout.d.ts.map +1 -0
  56. package/dist/data/resolve-layout.js +111 -0
  57. package/dist/data/schema-from-fields.d.ts +63 -0
  58. package/dist/data/schema-from-fields.d.ts.map +1 -0
  59. package/dist/data/schema-from-fields.js +178 -0
  60. package/dist/data/where.d.ts +53 -0
  61. package/dist/data/where.d.ts.map +1 -0
  62. package/dist/data/where.js +210 -0
  63. package/dist/mcp/http-backend.d.ts +35 -0
  64. package/dist/mcp/http-backend.d.ts.map +1 -0
  65. package/dist/mcp/http-backend.js +337 -0
  66. package/dist/mcp/http-backend.test.d.ts +2 -0
  67. package/dist/mcp/http-backend.test.d.ts.map +1 -0
  68. package/dist/mcp/http-backend.test.js +178 -0
  69. package/dist/mcp/index.d.ts +2 -0
  70. package/dist/mcp/index.d.ts.map +1 -0
  71. package/dist/mcp/index.js +2 -0
  72. package/dist/mcp/route.d.ts +63 -0
  73. package/dist/mcp/route.d.ts.map +1 -0
  74. package/dist/mcp/route.js +230 -0
  75. package/dist/next/get-document.d.ts +64 -0
  76. package/dist/next/get-document.d.ts.map +1 -0
  77. package/dist/next/get-document.js +208 -0
  78. package/dist/next/get-document.test.d.ts +2 -0
  79. package/dist/next/get-document.test.d.ts.map +1 -0
  80. package/dist/next/get-document.test.js +155 -0
  81. package/dist/next/index.d.ts +9 -0
  82. package/dist/next/index.d.ts.map +1 -0
  83. package/dist/next/index.js +4 -0
  84. package/dist/next/next-helpers.test.d.ts +2 -0
  85. package/dist/next/next-helpers.test.d.ts.map +1 -0
  86. package/dist/next/next-helpers.test.js +122 -0
  87. package/dist/next/revalidate.d.ts +44 -0
  88. package/dist/next/revalidate.d.ts.map +1 -0
  89. package/dist/next/revalidate.js +45 -0
  90. package/dist/next/seo.d.ts +43 -0
  91. package/dist/next/seo.d.ts.map +1 -0
  92. package/dist/next/seo.js +53 -0
  93. package/dist/next/tags.d.ts +23 -0
  94. package/dist/next/tags.d.ts.map +1 -0
  95. package/dist/next/tags.js +31 -0
  96. package/dist/plugin/access.d.ts +19 -0
  97. package/dist/plugin/access.d.ts.map +1 -0
  98. package/dist/plugin/access.js +32 -0
  99. package/dist/plugin/collections/form-submissions.d.ts +10 -0
  100. package/dist/plugin/collections/form-submissions.d.ts.map +1 -0
  101. package/dist/plugin/collections/form-submissions.js +37 -0
  102. package/dist/plugin/collections/templates.d.ts +13 -0
  103. package/dist/plugin/collections/templates.d.ts.map +1 -0
  104. package/dist/plugin/collections/templates.js +118 -0
  105. package/dist/plugin/endpoints/access.test.d.ts +2 -0
  106. package/dist/plugin/endpoints/access.test.d.ts.map +1 -0
  107. package/dist/plugin/endpoints/access.test.js +91 -0
  108. package/dist/plugin/endpoints/adapter-contract.test.d.ts +2 -0
  109. package/dist/plugin/endpoints/adapter-contract.test.d.ts.map +1 -0
  110. package/dist/plugin/endpoints/adapter-contract.test.js +45 -0
  111. package/dist/plugin/endpoints/context.d.ts +65 -0
  112. package/dist/plugin/endpoints/context.d.ts.map +1 -0
  113. package/dist/plugin/endpoints/context.js +113 -0
  114. package/dist/plugin/endpoints/data-context.d.ts +8 -0
  115. package/dist/plugin/endpoints/data-context.d.ts.map +1 -0
  116. package/dist/plugin/endpoints/data-context.js +63 -0
  117. package/dist/plugin/endpoints/data-endpoints.test.d.ts +2 -0
  118. package/dist/plugin/endpoints/data-endpoints.test.d.ts.map +1 -0
  119. package/dist/plugin/endpoints/data-endpoints.test.js +125 -0
  120. package/dist/plugin/endpoints/data-query.test.d.ts +2 -0
  121. package/dist/plugin/endpoints/data-query.test.d.ts.map +1 -0
  122. package/dist/plugin/endpoints/data-query.test.js +96 -0
  123. package/dist/plugin/endpoints/data-schema.d.ts +5 -0
  124. package/dist/plugin/endpoints/data-schema.d.ts.map +1 -0
  125. package/dist/plugin/endpoints/data-schema.js +21 -0
  126. package/dist/plugin/endpoints/data.d.ts +10 -0
  127. package/dist/plugin/endpoints/data.d.ts.map +1 -0
  128. package/dist/plugin/endpoints/data.js +96 -0
  129. package/dist/plugin/endpoints/document.d.ts +11 -0
  130. package/dist/plugin/endpoints/document.d.ts.map +1 -0
  131. package/dist/plugin/endpoints/document.js +119 -0
  132. package/dist/plugin/endpoints/documents.d.ts +10 -0
  133. package/dist/plugin/endpoints/documents.d.ts.map +1 -0
  134. package/dist/plugin/endpoints/documents.js +145 -0
  135. package/dist/plugin/endpoints/endpoints.test-kit.d.ts +42 -0
  136. package/dist/plugin/endpoints/endpoints.test-kit.d.ts.map +1 -0
  137. package/dist/plugin/endpoints/endpoints.test-kit.js +141 -0
  138. package/dist/plugin/endpoints/endpoints.test.d.ts +2 -0
  139. package/dist/plugin/endpoints/endpoints.test.d.ts.map +1 -0
  140. package/dist/plugin/endpoints/endpoints.test.js +209 -0
  141. package/dist/plugin/endpoints/forms.d.ts +16 -0
  142. package/dist/plugin/endpoints/forms.d.ts.map +1 -0
  143. package/dist/plugin/endpoints/forms.js +199 -0
  144. package/dist/plugin/endpoints/forms.test.d.ts +2 -0
  145. package/dist/plugin/endpoints/forms.test.d.ts.map +1 -0
  146. package/dist/plugin/endpoints/forms.test.js +346 -0
  147. package/dist/plugin/endpoints/guards.d.ts +9 -0
  148. package/dist/plugin/endpoints/guards.d.ts.map +1 -0
  149. package/dist/plugin/endpoints/guards.js +35 -0
  150. package/dist/plugin/endpoints/index.d.ts +6 -0
  151. package/dist/plugin/endpoints/index.d.ts.map +1 -0
  152. package/dist/plugin/endpoints/index.js +32 -0
  153. package/dist/plugin/endpoints/locales-no-fallback.test.d.ts +2 -0
  154. package/dist/plugin/endpoints/locales-no-fallback.test.d.ts.map +1 -0
  155. package/dist/plugin/endpoints/locales-no-fallback.test.js +30 -0
  156. package/dist/plugin/endpoints/locales.test.d.ts +2 -0
  157. package/dist/plugin/endpoints/locales.test.d.ts.map +1 -0
  158. package/dist/plugin/endpoints/locales.test.js +128 -0
  159. package/dist/plugin/endpoints/manifest.d.ts +5 -0
  160. package/dist/plugin/endpoints/manifest.d.ts.map +1 -0
  161. package/dist/plugin/endpoints/manifest.js +18 -0
  162. package/dist/plugin/endpoints/mcp-http-backend.test.d.ts +2 -0
  163. package/dist/plugin/endpoints/mcp-http-backend.test.d.ts.map +1 -0
  164. package/dist/plugin/endpoints/mcp-http-backend.test.js +84 -0
  165. package/dist/plugin/endpoints/mcp-off.test.d.ts +2 -0
  166. package/dist/plugin/endpoints/mcp-off.test.d.ts.map +1 -0
  167. package/dist/plugin/endpoints/mcp-off.test.js +33 -0
  168. package/dist/plugin/endpoints/mcp-publish.test.d.ts +2 -0
  169. package/dist/plugin/endpoints/mcp-publish.test.d.ts.map +1 -0
  170. package/dist/plugin/endpoints/mcp-publish.test.js +42 -0
  171. package/dist/plugin/endpoints/mcp-route.test.d.ts +2 -0
  172. package/dist/plugin/endpoints/mcp-route.test.d.ts.map +1 -0
  173. package/dist/plugin/endpoints/mcp-route.test.js +132 -0
  174. package/dist/plugin/endpoints/mcp.test.d.ts +2 -0
  175. package/dist/plugin/endpoints/mcp.test.d.ts.map +1 -0
  176. package/dist/plugin/endpoints/mcp.test.js +231 -0
  177. package/dist/plugin/endpoints/media.d.ts +7 -0
  178. package/dist/plugin/endpoints/media.d.ts.map +1 -0
  179. package/dist/plugin/endpoints/media.js +115 -0
  180. package/dist/plugin/endpoints/media.test.d.ts +2 -0
  181. package/dist/plugin/endpoints/media.test.d.ts.map +1 -0
  182. package/dist/plugin/endpoints/media.test.js +95 -0
  183. package/dist/plugin/endpoints/publish-a11y.test.d.ts +2 -0
  184. package/dist/plugin/endpoints/publish-a11y.test.d.ts.map +1 -0
  185. package/dist/plugin/endpoints/publish-a11y.test.js +27 -0
  186. package/dist/plugin/endpoints/publish.d.ts +9 -0
  187. package/dist/plugin/endpoints/publish.d.ts.map +1 -0
  188. package/dist/plugin/endpoints/publish.js +81 -0
  189. package/dist/plugin/endpoints/rename.d.ts +9 -0
  190. package/dist/plugin/endpoints/rename.d.ts.map +1 -0
  191. package/dist/plugin/endpoints/rename.js +47 -0
  192. package/dist/plugin/endpoints/respond.d.ts +9 -0
  193. package/dist/plugin/endpoints/respond.d.ts.map +1 -0
  194. package/dist/plugin/endpoints/respond.js +10 -0
  195. package/dist/plugin/endpoints/samples.d.ts +5 -0
  196. package/dist/plugin/endpoints/samples.d.ts.map +1 -0
  197. package/dist/plugin/endpoints/samples.js +42 -0
  198. package/dist/plugin/endpoints/save.d.ts +9 -0
  199. package/dist/plugin/endpoints/save.d.ts.map +1 -0
  200. package/dist/plugin/endpoints/save.js +58 -0
  201. package/dist/plugin/endpoints/session.d.ts +5 -0
  202. package/dist/plugin/endpoints/session.d.ts.map +1 -0
  203. package/dist/plugin/endpoints/session.js +30 -0
  204. package/dist/plugin/endpoints/templates.test.d.ts +2 -0
  205. package/dist/plugin/endpoints/templates.test.d.ts.map +1 -0
  206. package/dist/plugin/endpoints/templates.test.js +191 -0
  207. package/dist/plugin/fields.d.ts +28 -0
  208. package/dist/plugin/fields.d.ts.map +1 -0
  209. package/dist/plugin/fields.js +62 -0
  210. package/dist/plugin/forms/forms.test.d.ts +2 -0
  211. package/dist/plugin/forms/forms.test.d.ts.map +1 -0
  212. package/dist/plugin/forms/forms.test.js +88 -0
  213. package/dist/plugin/forms/notify.d.ts +6 -0
  214. package/dist/plugin/forms/notify.d.ts.map +1 -0
  215. package/dist/plugin/forms/notify.js +13 -0
  216. package/dist/plugin/forms/rate-limit.d.ts +24 -0
  217. package/dist/plugin/forms/rate-limit.d.ts.map +1 -0
  218. package/dist/plugin/forms/rate-limit.js +36 -0
  219. package/dist/plugin/forms/validate.d.ts +22 -0
  220. package/dist/plugin/forms/validate.d.ts.map +1 -0
  221. package/dist/plugin/forms/validate.js +79 -0
  222. package/dist/plugin/hooks/layout-hook.d.ts +9 -0
  223. package/dist/plugin/hooks/layout-hook.d.ts.map +1 -0
  224. package/dist/plugin/hooks/layout-hook.js +33 -0
  225. package/dist/plugin/hooks/layout-hook.test.d.ts +2 -0
  226. package/dist/plugin/hooks/layout-hook.test.d.ts.map +1 -0
  227. package/dist/plugin/hooks/layout-hook.test.js +191 -0
  228. package/dist/plugin/hooks/process-layout.d.ts +32 -0
  229. package/dist/plugin/hooks/process-layout.d.ts.map +1 -0
  230. package/dist/plugin/hooks/process-layout.js +68 -0
  231. package/dist/plugin/hooks/read-layout.d.ts +17 -0
  232. package/dist/plugin/hooks/read-layout.d.ts.map +1 -0
  233. package/dist/plugin/hooks/read-layout.js +39 -0
  234. package/dist/plugin/index.d.ts +18 -0
  235. package/dist/plugin/index.d.ts.map +1 -0
  236. package/dist/plugin/index.js +105 -0
  237. package/dist/plugin/locales.d.ts +32 -0
  238. package/dist/plugin/locales.d.ts.map +1 -0
  239. package/dist/plugin/locales.js +56 -0
  240. package/dist/plugin/options.d.ts +77 -0
  241. package/dist/plugin/options.d.ts.map +1 -0
  242. package/dist/plugin/options.js +123 -0
  243. package/dist/plugin/plugin.test.d.ts +2 -0
  244. package/dist/plugin/plugin.test.d.ts.map +1 -0
  245. package/dist/plugin/plugin.test.js +186 -0
  246. package/dist/plugin/write-guard.d.ts +14 -0
  247. package/dist/plugin/write-guard.d.ts.map +1 -0
  248. package/dist/plugin/write-guard.js +18 -0
  249. package/package.json +107 -3
  250. package/README.md +0 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/next/index.ts"],"names":[],"mappings":"AAEA,YAAY,EACV,mBAAmB,EACnB,SAAS,EACT,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC1E,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AACzD,YAAY,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EACL,aAAa,EACb,MAAM,EACN,SAAS,EACT,SAAS,EACT,OAAO,EACP,WAAW,GACZ,MAAM,WAAW,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { getBuildrDocument, listPublishedSlugs } from './get-document.js';
2
+ export { revalidateHooks } from './revalidate.js';
3
+ export { alternatesOf, seoFromDocument } from './seo.js';
4
+ export { collectionTag, docTag, globalTag, THEME_TAG, tagsFor, templateTag, } from './tags.js';
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=next-helpers.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"next-helpers.test.d.ts","sourceRoot":"","sources":["../../src/next/next-helpers.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,122 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { alternatesOf, revalidateHooks, seoFromDocument, tagsFor } from './index.js';
3
+ describe('tagsFor', () => {
4
+ it('lists every dependency of a page once, sorted', () => {
5
+ expect(tagsFor({
6
+ collection: 'posts',
7
+ id: 3,
8
+ layoutRef: 'buildr-templates:9',
9
+ collectionsUsed: ['posts', 'products'],
10
+ globals: ['site-settings'],
11
+ })).toEqual([
12
+ 'buildr:col:posts',
13
+ 'buildr:col:products',
14
+ 'buildr:doc:posts:3',
15
+ 'buildr:global:site-settings',
16
+ 'buildr:template:9',
17
+ 'buildr:theme',
18
+ ]);
19
+ });
20
+ it('tags an own layout as the document and needs no id for a slug lookup', () => {
21
+ expect(tagsFor({ collection: 'pages', layoutRef: 'pages:5' })).toEqual([
22
+ 'buildr:col:pages',
23
+ 'buildr:doc:pages:5',
24
+ 'buildr:theme',
25
+ ]);
26
+ expect(tagsFor({ collection: 'pages', layoutRef: null })).toEqual([
27
+ 'buildr:col:pages',
28
+ 'buildr:theme',
29
+ ]);
30
+ });
31
+ });
32
+ describe('revalidateHooks', () => {
33
+ const run = () => {
34
+ const tags = [];
35
+ return { hooks: revalidateHooks((tag) => tags.push(tag)), tags };
36
+ };
37
+ const page = { collection: { slug: 'pages' } };
38
+ it('revalidates a published page and its collection', () => {
39
+ const { hooks, tags } = run();
40
+ hooks.collection.afterChange[0]({ ...page, doc: { id: 1, _status: 'published' } });
41
+ expect(tags).toEqual(['buildr:doc:pages:1', 'buildr:col:pages']);
42
+ });
43
+ it('revalidates an unpublish (it was published) but not a draft of a never-published page', () => {
44
+ const { hooks, tags } = run();
45
+ hooks.collection.afterChange[0]({
46
+ ...page,
47
+ doc: { id: 1, _status: 'draft' },
48
+ previousDoc: { id: 1, _status: 'published' },
49
+ });
50
+ expect(tags).toHaveLength(2);
51
+ tags.length = 0;
52
+ hooks.collection.afterChange[0]({
53
+ ...page,
54
+ doc: { id: 2, _status: 'draft' },
55
+ previousDoc: { id: 2, _status: 'draft' },
56
+ });
57
+ expect(tags).toEqual([]);
58
+ });
59
+ it('revalidates on delete, a global change and a template change (and the collection it targets)', () => {
60
+ const { hooks, tags } = run();
61
+ hooks.collection.afterDelete[0]({ ...page, doc: { id: 4 } });
62
+ hooks.global.afterChange[0]({ global: { slug: 'site-settings' } });
63
+ hooks.templates.afterChange[0]({
64
+ collection: { slug: 'buildr-templates' },
65
+ doc: { id: 8, targetCollection: 'pages', _status: 'published' },
66
+ previousDoc: { id: 8, targetCollection: 'posts' },
67
+ });
68
+ expect(tags).toEqual([
69
+ 'buildr:doc:pages:4',
70
+ 'buildr:col:pages',
71
+ 'buildr:global:site-settings',
72
+ 'buildr:template:8',
73
+ 'buildr:col:pages',
74
+ 'buildr:col:posts',
75
+ ]);
76
+ });
77
+ });
78
+ describe('seoFromDocument', () => {
79
+ it('maps plugin-seo fields and populated images', () => {
80
+ expect(seoFromDocument({
81
+ title: 'Doc',
82
+ excerpt: 'Short',
83
+ featuredImage: { url: '/f.png' },
84
+ meta: { title: 'SEO', description: 'Desc', image: { url: '/o.png' }, noIndex: true },
85
+ }, { locale: 'pl', draft: true })).toEqual({
86
+ title: 'Doc',
87
+ excerpt: 'Short',
88
+ featuredImage: '/f.png',
89
+ meta: { title: 'SEO', description: 'Desc', image: '/o.png', noIndex: true, canonical: null },
90
+ locale: 'pl',
91
+ draft: true,
92
+ });
93
+ });
94
+ it('ignores an image that is only an id and empty strings', () => {
95
+ const out = seoFromDocument({ title: '', featuredImage: 12, meta: { image: 3 } });
96
+ expect(out.title).toBeNull();
97
+ expect(out.featuredImage).toBeNull();
98
+ expect(out.meta?.image).toBeNull();
99
+ });
100
+ });
101
+ describe('alternatesOf', () => {
102
+ it('is empty without localization', async () => {
103
+ const payload = { config: { localization: false } };
104
+ expect(await alternatesOf({ payload, collection: 'pages', id: 1, path: () => '/x' })).toEqual({});
105
+ });
106
+ it('collects the path in every language that has the document', async () => {
107
+ const payload = {
108
+ config: { localization: { localeCodes: ['pl', 'en', 'de'] } },
109
+ findByID: async ({ locale }) => {
110
+ if (locale === 'de')
111
+ throw new Error('missing');
112
+ return { slug: `slug-${locale}` };
113
+ },
114
+ };
115
+ expect(await alternatesOf({
116
+ payload,
117
+ collection: 'pages',
118
+ id: 1,
119
+ path: (doc, locale) => `/${locale}/${String(doc['slug'])}`,
120
+ })).toEqual({ pl: '/pl/slug-pl', en: '/en/slug-en' });
121
+ });
122
+ });
@@ -0,0 +1,44 @@
1
+ /** Marks a tag stale. The default is `revalidateTag`; a test (or a host with its own cache) passes another. */
2
+ export type Revalidate = (tag: string) => void;
3
+ interface DocLike {
4
+ readonly id?: string | number;
5
+ readonly _status?: string;
6
+ readonly targetCollection?: string;
7
+ }
8
+ export interface HookArgs {
9
+ readonly doc: DocLike;
10
+ readonly previousDoc?: DocLike;
11
+ readonly collection: {
12
+ readonly slug: string;
13
+ };
14
+ }
15
+ export interface GlobalHookArgs {
16
+ readonly global: {
17
+ readonly slug: string;
18
+ };
19
+ }
20
+ export interface RevalidateHooks {
21
+ /** For a page collection: `hooks: { afterChange: [...], afterDelete: [...] }`. */
22
+ readonly collection: {
23
+ readonly afterChange: readonly [(args: HookArgs) => void];
24
+ readonly afterDelete: readonly [(args: HookArgs) => void];
25
+ };
26
+ /** For a global such as `site-settings`. */
27
+ readonly global: {
28
+ readonly afterChange: readonly [(args: GlobalHookArgs) => void];
29
+ };
30
+ /** For `buildr-templates`. */
31
+ readonly templates: {
32
+ readonly afterChange: readonly [(args: HookArgs) => void];
33
+ readonly afterDelete: readonly [(args: HookArgs) => void];
34
+ };
35
+ }
36
+ /**
37
+ * The Payload hooks that keep the cache honest (docs/payload.md#preview-url-and-revalidation).
38
+ * A change counts when the document is published, or was (an unpublish, an edit of a live page);
39
+ * a draft of a document that was never published changes nothing visitors see. A template change
40
+ * also revalidates the collection it targets, since every page on it may have changed.
41
+ */
42
+ export declare function revalidateHooks(revalidate?: Revalidate): RevalidateHooks;
43
+ export {};
44
+ //# sourceMappingURL=revalidate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revalidate.d.ts","sourceRoot":"","sources":["../../src/next/revalidate.ts"],"names":[],"mappings":"AAGA,+GAA+G;AAC/G,MAAM,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;AAW/C,UAAU,OAAO;IACf,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAChD;AACD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5C;AAED,MAAM,WAAW,eAAe;IAC9B,kFAAkF;IAClF,QAAQ,CAAC,UAAU,EAAE;QACnB,QAAQ,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC,CAAC;QAC1D,QAAQ,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC,CAAC;KAC3D,CAAC;IACF,4CAA4C;IAC5C,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC,CAAA;KAAE,CAAC;IACrF,8BAA8B;IAC9B,QAAQ,CAAC,SAAS,EAAE;QAClB,QAAQ,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC,CAAC;QAC1D,QAAQ,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC,CAAC;KAC3D,CAAC;CACH;AAID;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,UAAU,GAAE,UAA8B,GAAG,eAAe,CAsB3F"}
@@ -0,0 +1,45 @@
1
+ import { revalidateTag } from 'next/cache';
2
+ import { collectionTag, docTag, globalTag, templateTag } from './tags.js';
3
+ const defaultRevalidate = (tag) => {
4
+ try {
5
+ // Next 16 takes a cache profile as the second argument, Next 15 ignores it.
6
+ revalidateTag(tag, { expire: 0 });
7
+ }
8
+ catch {
9
+ // Outside a Next.js request (a seed script, a test) there is no cache to invalidate.
10
+ }
11
+ };
12
+ const published = (doc) => doc?._status === 'published';
13
+ /**
14
+ * The Payload hooks that keep the cache honest (docs/payload.md#preview-url-and-revalidation).
15
+ * A change counts when the document is published, or was (an unpublish, an edit of a live page);
16
+ * a draft of a document that was never published changes nothing visitors see. A template change
17
+ * also revalidates the collection it targets, since every page on it may have changed.
18
+ */
19
+ export function revalidateHooks(revalidate = defaultRevalidate) {
20
+ const pageChanged = (args) => {
21
+ if (!(published(args.doc) || published(args.previousDoc)) || args.doc.id === undefined)
22
+ return;
23
+ revalidate(docTag(args.collection.slug, args.doc.id));
24
+ revalidate(collectionTag(args.collection.slug));
25
+ };
26
+ const pageDeleted = (args) => {
27
+ if (args.doc.id !== undefined)
28
+ revalidate(docTag(args.collection.slug, args.doc.id));
29
+ revalidate(collectionTag(args.collection.slug));
30
+ };
31
+ const templateChanged = (args) => {
32
+ if (args.doc.id === undefined)
33
+ return;
34
+ revalidate(templateTag(args.doc.id));
35
+ for (const target of [args.doc.targetCollection, args.previousDoc?.targetCollection]) {
36
+ if (typeof target === 'string')
37
+ revalidate(collectionTag(target));
38
+ }
39
+ };
40
+ return {
41
+ collection: { afterChange: [pageChanged], afterDelete: [pageDeleted] },
42
+ global: { afterChange: [({ global }) => revalidate(globalTag(global.slug))] },
43
+ templates: { afterChange: [templateChanged], afterDelete: [templateChanged] },
44
+ };
45
+ }
@@ -0,0 +1,43 @@
1
+ import type { Payload } from 'payload';
2
+ /** What `buildrMetadata` (`@next-buildr/next`) takes; declared here so this package needs no runtime import of it. */
3
+ export interface SeoEntry {
4
+ readonly title?: string | null;
5
+ readonly excerpt?: string | null;
6
+ readonly featuredImage?: string | null;
7
+ readonly meta?: {
8
+ readonly title?: string | null;
9
+ readonly description?: string | null;
10
+ readonly image?: string | null;
11
+ readonly noIndex?: boolean | null;
12
+ readonly canonical?: string | null;
13
+ } | null;
14
+ readonly locale?: string;
15
+ readonly alternates?: Readonly<Record<string, string>>;
16
+ readonly draft?: boolean;
17
+ }
18
+ export interface SeoOptions {
19
+ readonly locale?: string;
20
+ readonly draft?: boolean;
21
+ /** The field holding a short summary. Default `excerpt`. */
22
+ readonly excerptField?: string;
23
+ /** The field holding the featured image (a populated upload). Default `featuredImage`. */
24
+ readonly imageField?: string;
25
+ readonly alternates?: Readonly<Record<string, string>>;
26
+ }
27
+ /**
28
+ * Maps a Payload document onto the shape `buildrMetadata` takes. Reads the fields of
29
+ * `@payloadcms/plugin-seo` (`meta.title`, `meta.description`, `meta.image`) plus `meta.noIndex` and
30
+ * `meta.canonical` when the application defines them. Images must be populated (`depth >= 1`).
31
+ */
32
+ export declare function seoFromDocument(doc: Readonly<Record<string, unknown>>, options?: SeoOptions): SeoEntry;
33
+ /**
34
+ * The path of a document in every configured language (hreflang): one read per locale, so a
35
+ * localized slug gives each language its own URL. A language without the document is left out.
36
+ */
37
+ export declare function alternatesOf(input: {
38
+ readonly payload: Payload;
39
+ readonly collection: string;
40
+ readonly id: string | number;
41
+ readonly path: (doc: Record<string, unknown>, locale: string) => string;
42
+ }): Promise<Record<string, string>>;
43
+ //# sourceMappingURL=seo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seo.d.ts","sourceRoot":"","sources":["../../src/next/seo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,sHAAsH;AACtH,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,IAAI,CAAC,EAAE;QACd,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACrC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;QAClC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACpC,GAAG,IAAI,CAAC;IACT,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACvD,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAUD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,4DAA4D;IAC5D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,0FAA0F;IAC1F,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACxD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACtC,OAAO,GAAE,UAAe,GACvB,QAAQ,CAiBV;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE;IACxC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;CACzE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAmBlC"}
@@ -0,0 +1,53 @@
1
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
2
+ const text = (value) => typeof value === 'string' && value !== '' ? value : null;
3
+ /** The URL of an upload field: a populated media document, or nothing when it is only an id. */
4
+ const urlOf = (value) => (isRecord(value) ? text(value['url']) : null);
5
+ /**
6
+ * Maps a Payload document onto the shape `buildrMetadata` takes. Reads the fields of
7
+ * `@payloadcms/plugin-seo` (`meta.title`, `meta.description`, `meta.image`) plus `meta.noIndex` and
8
+ * `meta.canonical` when the application defines them. Images must be populated (`depth >= 1`).
9
+ */
10
+ export function seoFromDocument(doc, options = {}) {
11
+ const meta = isRecord(doc['meta']) ? doc['meta'] : {};
12
+ return {
13
+ title: text(doc['title']),
14
+ excerpt: text(doc[options.excerptField ?? 'excerpt']),
15
+ featuredImage: urlOf(doc[options.imageField ?? 'featuredImage']),
16
+ meta: {
17
+ title: text(meta['title']),
18
+ description: text(meta['description']),
19
+ image: urlOf(meta['image']),
20
+ noIndex: meta['noIndex'] === true,
21
+ canonical: text(meta['canonical']),
22
+ },
23
+ ...(options.locale === undefined ? {} : { locale: options.locale }),
24
+ ...(options.alternates === undefined ? {} : { alternates: options.alternates }),
25
+ ...(options.draft === true ? { draft: true } : {}),
26
+ };
27
+ }
28
+ /**
29
+ * The path of a document in every configured language (hreflang): one read per locale, so a
30
+ * localized slug gives each language its own URL. A language without the document is left out.
31
+ */
32
+ export async function alternatesOf(input) {
33
+ const localization = input.payload.config.localization;
34
+ if (localization === false || localization === undefined)
35
+ return {};
36
+ const result = {};
37
+ for (const locale of localization.localeCodes) {
38
+ try {
39
+ const doc = await input.payload.findByID({
40
+ collection: input.collection,
41
+ id: input.id,
42
+ locale,
43
+ fallbackLocale: false,
44
+ depth: 0,
45
+ });
46
+ result[locale] = input.path(doc, locale);
47
+ }
48
+ catch {
49
+ // not published (or not readable) in this language
50
+ }
51
+ }
52
+ return result;
53
+ }
@@ -0,0 +1,23 @@
1
+ /** The cache tag convention (docs/nextjs.md#preview--draft-mode-caching-revalidation-errors). */
2
+ export declare const THEME_TAG = "buildr:theme";
3
+ export declare const docTag: (collection: string, id: string | number) => string;
4
+ export declare const collectionTag: (collection: string) => string;
5
+ export declare const globalTag: (slug: string) => string;
6
+ export declare const templateTag: (id: string | number) => string;
7
+ export interface TagInput {
8
+ readonly collection: string;
9
+ readonly id?: string | number | undefined;
10
+ /** `{collection}:{id}` of the document the layout came from; a template adds its own tag. */
11
+ readonly layoutRef?: string | null | undefined;
12
+ /** The collections a Loop or query read (`collectionsUsed` of the render). */
13
+ readonly collectionsUsed?: readonly string[] | undefined;
14
+ /** Globals the page reads (`site-settings`). */
15
+ readonly globals?: readonly string[] | undefined;
16
+ }
17
+ /**
18
+ * Every tag a rendered page depends on: its document, its collection (a slug lookup may resolve
19
+ * to another document after any change), the template its layout came from, the collections its
20
+ * queries read, the globals it reads and the theme. Sorted and free of duplicates.
21
+ */
22
+ export declare function tagsFor(input: TagInput): string[];
23
+ //# sourceMappingURL=tags.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tags.d.ts","sourceRoot":"","sources":["../../src/next/tags.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,eAAO,MAAM,SAAS,iBAAiB,CAAC;AAExC,eAAO,MAAM,MAAM,eAAgB,MAAM,MAAM,MAAM,GAAG,MAAM,KAAG,MACvB,CAAC;AAC3C,eAAO,MAAM,aAAa,eAAgB,MAAM,KAAG,MAAoC,CAAC;AACxF,eAAO,MAAM,SAAS,SAAU,MAAM,KAAG,MAAiC,CAAC;AAC3E,eAAO,MAAM,WAAW,OAAQ,MAAM,GAAG,MAAM,KAAG,MAAyC,CAAC;AAE5F,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAC1C,6FAA6F;IAC7F,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAC/C,8EAA8E;IAC9E,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACzD,gDAAgD;IAChD,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;CAClD;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,EAAE,CAcjD"}
@@ -0,0 +1,31 @@
1
+ /** The cache tag convention (docs/nextjs.md#preview--draft-mode-caching-revalidation-errors). */
2
+ export const THEME_TAG = 'buildr:theme';
3
+ export const docTag = (collection, id) => `buildr:doc:${collection}:${String(id)}`;
4
+ export const collectionTag = (collection) => `buildr:col:${collection}`;
5
+ export const globalTag = (slug) => `buildr:global:${slug}`;
6
+ export const templateTag = (id) => `buildr:template:${String(id)}`;
7
+ /**
8
+ * Every tag a rendered page depends on: its document, its collection (a slug lookup may resolve
9
+ * to another document after any change), the template its layout came from, the collections its
10
+ * queries read, the globals it reads and the theme. Sorted and free of duplicates.
11
+ */
12
+ export function tagsFor(input) {
13
+ const tags = new Set([THEME_TAG, collectionTag(input.collection)]);
14
+ if (input.id !== undefined)
15
+ tags.add(docTag(input.collection, input.id));
16
+ const ref = input.layoutRef;
17
+ if (typeof ref === 'string') {
18
+ const at = ref.indexOf(':');
19
+ const owner = ref.slice(0, at);
20
+ const id = ref.slice(at + 1);
21
+ if (owner === 'buildr-templates')
22
+ tags.add(templateTag(id));
23
+ else if (at > 0)
24
+ tags.add(docTag(owner, id));
25
+ }
26
+ for (const used of input.collectionsUsed ?? [])
27
+ tags.add(collectionTag(used));
28
+ for (const slug of input.globals ?? [])
29
+ tags.add(globalTag(slug));
30
+ return [...tags].sort();
31
+ }
@@ -0,0 +1,19 @@
1
+ import type { PayloadRequest } from 'payload';
2
+ import type { ResolvedOptions } from './options.ts';
3
+ export type Action = 'edit' | 'publish' | 'unlockTemplates';
4
+ export interface Permissions {
5
+ readonly canEdit: boolean;
6
+ readonly canPublish: boolean;
7
+ readonly canUnlockTemplates: boolean;
8
+ }
9
+ /** Whether the request was authenticated by a Payload API key (`user._strategy`, set by Payload). */
10
+ export declare const isApiKeyRequest: (req: PayloadRequest) => boolean;
11
+ /**
12
+ * Whether the user of `req` may `action`. Nobody without a user may do anything; for an
13
+ * authenticated user the plugin `access` function decides (any authenticated user when there is
14
+ * none). Payload's own collection access still applies on top: every Local API call the endpoints
15
+ * make uses the caller's `req` with `overrideAccess: false`.
16
+ */
17
+ export declare function allowed(options: Pick<ResolvedOptions, 'access' | 'mcp'>, action: Action, req: PayloadRequest): Promise<boolean>;
18
+ export declare function permissionsOf(options: Pick<ResolvedOptions, 'access' | 'mcp'>, req: PayloadRequest): Promise<Permissions>;
19
+ //# sourceMappingURL=access.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../src/plugin/access.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,iBAAiB,CAAC;AAE5D,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;CACtC;AAED,qGAAqG;AACrG,eAAO,MAAM,eAAe,QAAS,cAAc,KAAG,OAC4B,CAAC;AAKnF;;;;;GAKG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,QAAQ,GAAG,KAAK,CAAC,EAChD,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,QAAQ,GAAG,KAAK,CAAC,EAChD,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,WAAW,CAAC,CAOtB"}
@@ -0,0 +1,32 @@
1
+ /** Whether the request was authenticated by a Payload API key (`user._strategy`, set by Payload). */
2
+ export const isApiKeyRequest = (req) => req.user?._strategy === 'api-key';
3
+ const isAuthenticated = (req) => req.user !== null && req.user !== undefined;
4
+ /**
5
+ * Whether the user of `req` may `action`. Nobody without a user may do anything; for an
6
+ * authenticated user the plugin `access` function decides (any authenticated user when there is
7
+ * none). Payload's own collection access still applies on top: every Local API call the endpoints
8
+ * make uses the caller's `req` with `overrideAccess: false`.
9
+ */
10
+ export async function allowed(options, action, req) {
11
+ if (!isAuthenticated(req))
12
+ return false;
13
+ if (isApiKeyRequest(req)) {
14
+ // An API key reaches the builder only through the opt-in `mcp` option; it publishes only with `mcp.allowPublish`.
15
+ if (!options.mcp.enabled)
16
+ return false;
17
+ if (action === 'publish' && !options.mcp.allowPublish)
18
+ return false;
19
+ if (action === 'unlockTemplates')
20
+ return false;
21
+ }
22
+ const check = options.access[action];
23
+ return check === undefined ? true : Boolean(await check({ req }));
24
+ }
25
+ export async function permissionsOf(options, req) {
26
+ const [canEdit, canPublish, canUnlockTemplates] = await Promise.all([
27
+ allowed(options, 'edit', req),
28
+ allowed(options, 'publish', req),
29
+ allowed(options, 'unlockTemplates', req),
30
+ ]);
31
+ return { canEdit, canPublish, canUnlockTemplates };
32
+ }
@@ -0,0 +1,10 @@
1
+ import type { CollectionConfig } from 'payload';
2
+ /** Where submitted forms are stored (`forms.enabled`). */
3
+ export declare const FORM_SUBMISSIONS_COLLECTION = "buildr-form-submissions";
4
+ /**
5
+ * `buildr-form-submissions`: one document per accepted submission. Only the plugin's endpoint
6
+ * creates them (through the Local API, past access control), so the collection accepts no writes
7
+ * from anyone else; signed-in users read and delete them (the data is personal).
8
+ */
9
+ export declare function formSubmissionsCollection(): CollectionConfig;
10
+ //# sourceMappingURL=form-submissions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-submissions.d.ts","sourceRoot":"","sources":["../../../src/plugin/collections/form-submissions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD,0DAA0D;AAC1D,eAAO,MAAM,2BAA2B,4BAA4B,CAAC;AAErE;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI,gBAAgB,CA6B5D"}
@@ -0,0 +1,37 @@
1
+ /** Where submitted forms are stored (`forms.enabled`). */
2
+ export const FORM_SUBMISSIONS_COLLECTION = 'buildr-form-submissions';
3
+ /**
4
+ * `buildr-form-submissions`: one document per accepted submission. Only the plugin's endpoint
5
+ * creates them (through the Local API, past access control), so the collection accepts no writes
6
+ * from anyone else; signed-in users read and delete them (the data is personal).
7
+ */
8
+ export function formSubmissionsCollection() {
9
+ const signedIn = ({ req }) => Boolean(req.user);
10
+ return {
11
+ slug: FORM_SUBMISSIONS_COLLECTION,
12
+ labels: { singular: 'Form submission', plural: 'Form submissions' },
13
+ admin: { defaultColumns: ['createdAt', 'form', 'locale'] },
14
+ access: { read: signedIn, delete: signedIn, create: () => false, update: () => false },
15
+ fields: [
16
+ {
17
+ name: 'form',
18
+ type: 'group',
19
+ fields: [
20
+ { name: 'collection', type: 'text', required: true },
21
+ { name: 'documentId', type: 'text', required: true },
22
+ { name: 'nodeId', type: 'text', required: true },
23
+ ],
24
+ },
25
+ { name: 'data', type: 'json', required: true },
26
+ { name: 'locale', type: 'text' },
27
+ {
28
+ name: 'meta',
29
+ type: 'group',
30
+ fields: [
31
+ { name: 'userAgent', type: 'text' },
32
+ { name: 'ipHash', type: 'text' },
33
+ ],
34
+ },
35
+ ],
36
+ };
37
+ }
@@ -0,0 +1,13 @@
1
+ import { type CollectionConfig } from 'payload';
2
+ import type { ResolvedOptions } from '../options.ts';
3
+ /**
4
+ * `buildr-templates` (docs/payload.md): reusable layouts a collection's documents inherit. Anyone
5
+ * signed in reads them; visitors read the published ones (a rendered page needs its template);
6
+ * writing needs the builder's `edit` permission.
7
+ */
8
+ export declare function templatesCollection(options: {
9
+ readonly resolved: ResolvedOptions;
10
+ /** The collections that take templates: what `targetCollection` can be. */
11
+ readonly targets: readonly string[];
12
+ }): CollectionConfig;
13
+ //# sourceMappingURL=templates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../../src/plugin/collections/templates.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,gBAAgB,EAGtB,MAAM,SAAS,CAAC;AAKjB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAmErD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE;IAC3C,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC,GAAG,gBAAgB,CA8CnB"}
@@ -0,0 +1,118 @@
1
+ import { ValidationError, } from 'payload';
2
+ import { TEMPLATES_COLLECTION } from '../../data/resolve-layout.js';
3
+ import { allowed } from '../access.js';
4
+ import { describeDiagnostics, processLayout } from '../hooks/process-layout.js';
5
+ import { configuredLocales } from '../locales.js';
6
+ const isRecord = (value) => typeof value === 'object' && value !== null;
7
+ /**
8
+ * At most one default template per target collection: saving a default when another one is already
9
+ * the default for the same collection is rejected (the author unmarks the other one first).
10
+ */
11
+ const singleDefault = async ({ data, originalDoc, req }) => {
12
+ const original = isRecord(originalDoc) ? originalDoc : {};
13
+ const isDefault = data['isDefault'] ?? original['isDefault'];
14
+ if (isDefault !== true)
15
+ return data;
16
+ const target = data['targetCollection'] ?? original['targetCollection'];
17
+ if (typeof target !== 'string')
18
+ return data;
19
+ const ownId = original['id'];
20
+ const others = await req.payload.find({
21
+ collection: TEMPLATES_COLLECTION,
22
+ where: {
23
+ and: [
24
+ { targetCollection: { equals: target } },
25
+ { isDefault: { equals: true } },
26
+ ...(ownId === undefined ? [] : [{ id: { not_equals: ownId } }]),
27
+ ],
28
+ },
29
+ limit: 1,
30
+ depth: 0,
31
+ pagination: false,
32
+ draft: true,
33
+ req,
34
+ overrideAccess: true,
35
+ });
36
+ const other = others.docs[0];
37
+ if (other !== undefined) {
38
+ throw new ValidationError({
39
+ collection: TEMPLATES_COLLECTION,
40
+ errors: [
41
+ {
42
+ path: 'isDefault',
43
+ message: `"${String(other['title'])}" is already the default template for "${target}".`,
44
+ },
45
+ ],
46
+ req,
47
+ });
48
+ }
49
+ return data;
50
+ };
51
+ /** A stored layout is validated like the builder's: an invalid document never reaches the database. */
52
+ const templateLayoutHook = (options) => ({ value, req }) => {
53
+ if (value === undefined || value === null)
54
+ return value;
55
+ const result = processLayout(value, {
56
+ ...options,
57
+ locales: configuredLocales(req.payload.config),
58
+ });
59
+ if (!result.ok) {
60
+ throw new ValidationError({
61
+ collection: TEMPLATES_COLLECTION,
62
+ errors: [{ path: 'layout', message: describeDiagnostics(result.diagnostics) }],
63
+ req,
64
+ });
65
+ }
66
+ return result.doc;
67
+ };
68
+ /**
69
+ * `buildr-templates` (docs/payload.md): reusable layouts a collection's documents inherit. Anyone
70
+ * signed in reads them; visitors read the published ones (a rendered page needs its template);
71
+ * writing needs the builder's `edit` permission.
72
+ */
73
+ export function templatesCollection(options) {
74
+ const { resolved } = options;
75
+ const mayEdit = ({ req }) => allowed(resolved, 'edit', req);
76
+ return {
77
+ slug: TEMPLATES_COLLECTION,
78
+ labels: { singular: 'Template', plural: 'Templates' },
79
+ admin: { useAsTitle: 'title', defaultColumns: ['title', 'targetCollection', 'isDefault'] },
80
+ access: {
81
+ read: ({ req }) => (req.user ? true : { _status: { equals: 'published' } }),
82
+ create: mayEdit,
83
+ update: mayEdit,
84
+ delete: mayEdit,
85
+ },
86
+ versions: { drafts: { autosave: true }, maxPerDoc: 50 },
87
+ hooks: { beforeChange: [singleDefault] },
88
+ fields: [
89
+ { name: 'title', type: 'text', required: true },
90
+ {
91
+ name: 'targetCollection',
92
+ type: 'select',
93
+ required: true,
94
+ options: [...options.targets],
95
+ admin: { description: 'The collection whose documents can use this template.' },
96
+ },
97
+ {
98
+ name: 'isDefault',
99
+ type: 'checkbox',
100
+ defaultValue: false,
101
+ admin: {
102
+ description: 'Used by documents of the collection that have no layout and no template.',
103
+ },
104
+ },
105
+ {
106
+ name: 'layout',
107
+ type: 'json',
108
+ hooks: { beforeChange: [templateLayoutHook(resolved)] },
109
+ },
110
+ {
111
+ name: 'buildrRevision',
112
+ type: 'number',
113
+ defaultValue: 0,
114
+ admin: { hidden: true, readOnly: true },
115
+ },
116
+ ],
117
+ };
118
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=access.test.d.ts.map