@se-studio/contentful-rest-api 1.0.46 → 1.0.47

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 (298) hide show
  1. package/CHANGELOG.md +416 -0
  2. package/dist/api/article-type.d.ts +37 -0
  3. package/dist/api/article-type.d.ts.map +1 -0
  4. package/dist/api/article-type.js +134 -0
  5. package/dist/api/article-type.js.map +1 -0
  6. package/dist/api/article.d.ts +32 -0
  7. package/dist/api/article.d.ts.map +1 -0
  8. package/dist/api/article.js +43 -0
  9. package/dist/api/article.js.map +1 -0
  10. package/dist/api/asset.d.ts +28 -0
  11. package/dist/api/asset.d.ts.map +1 -0
  12. package/dist/api/asset.js +55 -0
  13. package/dist/api/asset.js.map +1 -0
  14. package/dist/api/context.d.ts +24 -0
  15. package/dist/api/context.d.ts.map +1 -0
  16. package/dist/api/context.js +62 -0
  17. package/dist/api/context.js.map +1 -0
  18. package/dist/api/custom-type.d.ts +37 -0
  19. package/dist/api/custom-type.d.ts.map +1 -0
  20. package/dist/api/custom-type.js +44 -0
  21. package/dist/api/custom-type.js.map +1 -0
  22. package/dist/api/helpers.d.ts +73 -0
  23. package/dist/api/helpers.d.ts.map +1 -0
  24. package/dist/api/helpers.js +296 -0
  25. package/dist/api/helpers.js.map +1 -0
  26. package/dist/api/index.d.ts +22 -0
  27. package/dist/api/index.d.ts.map +1 -0
  28. package/dist/api/index.js +28 -0
  29. package/dist/api/index.js.map +1 -0
  30. package/dist/api/links.d.ts +109 -0
  31. package/dist/api/links.d.ts.map +1 -0
  32. package/dist/api/links.js +199 -0
  33. package/dist/api/links.js.map +1 -0
  34. package/dist/api/page.d.ts +33 -0
  35. package/dist/api/page.d.ts.map +1 -0
  36. package/dist/api/page.js +40 -0
  37. package/dist/api/page.js.map +1 -0
  38. package/dist/api/person.d.ts +37 -0
  39. package/dist/api/person.d.ts.map +1 -0
  40. package/dist/api/person.js +134 -0
  41. package/dist/api/person.js.map +1 -0
  42. package/dist/api/preview.d.ts +55 -0
  43. package/dist/api/preview.d.ts.map +1 -0
  44. package/dist/api/preview.js +160 -0
  45. package/dist/api/preview.js.map +1 -0
  46. package/dist/api/related-articles.d.ts +22 -0
  47. package/dist/api/related-articles.d.ts.map +1 -0
  48. package/dist/api/related-articles.js +82 -0
  49. package/dist/api/related-articles.js.map +1 -0
  50. package/dist/api/server-asset.d.ts +40 -0
  51. package/dist/api/server-asset.d.ts.map +1 -0
  52. package/dist/api/server-asset.js +65 -0
  53. package/dist/api/server-asset.js.map +1 -0
  54. package/dist/api/sitemap.d.ts +131 -0
  55. package/dist/api/sitemap.d.ts.map +1 -0
  56. package/dist/api/sitemap.js +199 -0
  57. package/dist/api/sitemap.js.map +1 -0
  58. package/dist/api/tag.d.ts +37 -0
  59. package/dist/api/tag.d.ts.map +1 -0
  60. package/dist/api/tag.js +131 -0
  61. package/dist/api/tag.js.map +1 -0
  62. package/dist/api/template.d.ts +49 -0
  63. package/dist/api/template.d.ts.map +1 -0
  64. package/dist/api/template.js +88 -0
  65. package/dist/api/template.js.map +1 -0
  66. package/dist/api/types.d.ts +98 -0
  67. package/dist/api/types.d.ts.map +1 -0
  68. package/dist/api/types.js +2 -0
  69. package/dist/api/types.js.map +1 -0
  70. package/dist/baseTypes/baseAlternatePageContent.d.ts +24 -0
  71. package/dist/baseTypes/baseAlternatePageContent.d.ts.map +1 -0
  72. package/dist/baseTypes/baseAlternatePageContent.js +2 -0
  73. package/dist/baseTypes/baseAlternatePageContent.js.map +1 -0
  74. package/dist/baseTypes/baseArticle.d.ts +179 -0
  75. package/dist/baseTypes/baseArticle.d.ts.map +1 -0
  76. package/dist/baseTypes/baseArticle.js +2 -0
  77. package/dist/baseTypes/baseArticle.js.map +1 -0
  78. package/dist/baseTypes/baseArticleType.d.ts +156 -0
  79. package/dist/baseTypes/baseArticleType.d.ts.map +1 -0
  80. package/dist/baseTypes/baseArticleType.js +2 -0
  81. package/dist/baseTypes/baseArticleType.js.map +1 -0
  82. package/dist/baseTypes/baseBanner.d.ts +76 -0
  83. package/dist/baseTypes/baseBanner.d.ts.map +1 -0
  84. package/dist/baseTypes/baseBanner.js +2 -0
  85. package/dist/baseTypes/baseBanner.js.map +1 -0
  86. package/dist/baseTypes/baseCollection.d.ts +132 -0
  87. package/dist/baseTypes/baseCollection.d.ts.map +1 -0
  88. package/dist/baseTypes/baseCollection.js +2 -0
  89. package/dist/baseTypes/baseCollection.js.map +1 -0
  90. package/dist/baseTypes/baseComponent.d.ts +130 -0
  91. package/dist/baseTypes/baseComponent.d.ts.map +1 -0
  92. package/dist/baseTypes/baseComponent.js +2 -0
  93. package/dist/baseTypes/baseComponent.js.map +1 -0
  94. package/dist/baseTypes/baseCustomType.d.ts +126 -0
  95. package/dist/baseTypes/baseCustomType.d.ts.map +1 -0
  96. package/dist/baseTypes/baseCustomType.js +2 -0
  97. package/dist/baseTypes/baseCustomType.js.map +1 -0
  98. package/dist/baseTypes/baseExternalComponent.d.ts +66 -0
  99. package/dist/baseTypes/baseExternalComponent.d.ts.map +1 -0
  100. package/dist/baseTypes/baseExternalComponent.js +2 -0
  101. package/dist/baseTypes/baseExternalComponent.js.map +1 -0
  102. package/dist/baseTypes/baseExternalVideo.d.ts +85 -0
  103. package/dist/baseTypes/baseExternalVideo.d.ts.map +1 -0
  104. package/dist/baseTypes/baseExternalVideo.js +2 -0
  105. package/dist/baseTypes/baseExternalVideo.js.map +1 -0
  106. package/dist/baseTypes/baseLink.d.ts +90 -0
  107. package/dist/baseTypes/baseLink.d.ts.map +1 -0
  108. package/dist/baseTypes/baseLink.js +2 -0
  109. package/dist/baseTypes/baseLink.js.map +1 -0
  110. package/dist/baseTypes/baseMedia.d.ts +92 -0
  111. package/dist/baseTypes/baseMedia.d.ts.map +1 -0
  112. package/dist/baseTypes/baseMedia.js +2 -0
  113. package/dist/baseTypes/baseMedia.js.map +1 -0
  114. package/dist/baseTypes/baseNavigation.d.ts +36 -0
  115. package/dist/baseTypes/baseNavigation.d.ts.map +1 -0
  116. package/dist/baseTypes/baseNavigation.js +2 -0
  117. package/dist/baseTypes/baseNavigation.js.map +1 -0
  118. package/dist/baseTypes/baseNavigationItem.d.ts +96 -0
  119. package/dist/baseTypes/baseNavigationItem.d.ts.map +1 -0
  120. package/dist/baseTypes/baseNavigationItem.js +2 -0
  121. package/dist/baseTypes/baseNavigationItem.js.map +1 -0
  122. package/dist/baseTypes/basePage.d.ts +120 -0
  123. package/dist/baseTypes/basePage.d.ts.map +1 -0
  124. package/dist/baseTypes/basePage.js +2 -0
  125. package/dist/baseTypes/basePage.js.map +1 -0
  126. package/dist/baseTypes/basePageTest.d.ts +66 -0
  127. package/dist/baseTypes/basePageTest.d.ts.map +1 -0
  128. package/dist/baseTypes/basePageTest.js +2 -0
  129. package/dist/baseTypes/basePageTest.js.map +1 -0
  130. package/dist/baseTypes/basePageVariant.d.ts +123 -0
  131. package/dist/baseTypes/basePageVariant.d.ts.map +1 -0
  132. package/dist/baseTypes/basePageVariant.js +2 -0
  133. package/dist/baseTypes/basePageVariant.js.map +1 -0
  134. package/dist/baseTypes/basePerson.d.ts +111 -0
  135. package/dist/baseTypes/basePerson.d.ts.map +1 -0
  136. package/dist/baseTypes/basePerson.js +2 -0
  137. package/dist/baseTypes/basePerson.js.map +1 -0
  138. package/dist/baseTypes/baseSchema.d.ts +18 -0
  139. package/dist/baseTypes/baseSchema.d.ts.map +1 -0
  140. package/dist/baseTypes/baseSchema.js +2 -0
  141. package/dist/baseTypes/baseSchema.js.map +1 -0
  142. package/dist/baseTypes/baseShared.d.ts +30 -0
  143. package/dist/baseTypes/baseShared.d.ts.map +1 -0
  144. package/dist/baseTypes/baseShared.js +2 -0
  145. package/dist/baseTypes/baseShared.js.map +1 -0
  146. package/dist/baseTypes/baseTag.d.ts +108 -0
  147. package/dist/baseTypes/baseTag.d.ts.map +1 -0
  148. package/dist/baseTypes/baseTag.js +2 -0
  149. package/dist/baseTypes/baseTag.js.map +1 -0
  150. package/dist/baseTypes/baseTagType.d.ts +11 -0
  151. package/dist/baseTypes/baseTagType.d.ts.map +1 -0
  152. package/dist/baseTypes/baseTagType.js +2 -0
  153. package/dist/baseTypes/baseTagType.js.map +1 -0
  154. package/dist/baseTypes/baseTemplate.d.ts +55 -0
  155. package/dist/baseTypes/baseTemplate.d.ts.map +1 -0
  156. package/dist/baseTypes/baseTemplate.js +2 -0
  157. package/dist/baseTypes/baseTemplate.js.map +1 -0
  158. package/dist/client.d.ts +143 -0
  159. package/dist/client.d.ts.map +1 -0
  160. package/dist/client.js +268 -0
  161. package/dist/client.js.map +1 -0
  162. package/dist/converters/article.d.ts +42 -0
  163. package/dist/converters/article.d.ts.map +1 -0
  164. package/dist/converters/article.js +220 -0
  165. package/dist/converters/article.js.map +1 -0
  166. package/dist/converters/asset.d.ts +22 -0
  167. package/dist/converters/asset.d.ts.map +1 -0
  168. package/dist/converters/asset.js +282 -0
  169. package/dist/converters/asset.js.map +1 -0
  170. package/dist/converters/collection.d.ts +26 -0
  171. package/dist/converters/collection.d.ts.map +1 -0
  172. package/dist/converters/collection.js +50 -0
  173. package/dist/converters/collection.js.map +1 -0
  174. package/dist/converters/component.d.ts +26 -0
  175. package/dist/converters/component.d.ts.map +1 -0
  176. package/dist/converters/component.js +54 -0
  177. package/dist/converters/component.js.map +1 -0
  178. package/dist/converters/customType.d.ts +24 -0
  179. package/dist/converters/customType.d.ts.map +1 -0
  180. package/dist/converters/customType.js +71 -0
  181. package/dist/converters/customType.js.map +1 -0
  182. package/dist/converters/externalComponent.d.ts +22 -0
  183. package/dist/converters/externalComponent.d.ts.map +1 -0
  184. package/dist/converters/externalComponent.js +34 -0
  185. package/dist/converters/externalComponent.js.map +1 -0
  186. package/dist/converters/helpers.d.ts +116 -0
  187. package/dist/converters/helpers.d.ts.map +1 -0
  188. package/dist/converters/helpers.js +128 -0
  189. package/dist/converters/helpers.js.map +1 -0
  190. package/dist/converters/iconCollector.d.ts +65 -0
  191. package/dist/converters/iconCollector.d.ts.map +1 -0
  192. package/dist/converters/iconCollector.js +282 -0
  193. package/dist/converters/iconCollector.js.map +1 -0
  194. package/dist/converters/index.d.ts +18 -0
  195. package/dist/converters/index.d.ts.map +1 -0
  196. package/dist/converters/index.js +18 -0
  197. package/dist/converters/index.js.map +1 -0
  198. package/dist/converters/link.d.ts +11 -0
  199. package/dist/converters/link.d.ts.map +1 -0
  200. package/dist/converters/link.js +96 -0
  201. package/dist/converters/link.js.map +1 -0
  202. package/dist/converters/navigationItem.d.ts +11 -0
  203. package/dist/converters/navigationItem.d.ts.map +1 -0
  204. package/dist/converters/navigationItem.js +73 -0
  205. package/dist/converters/navigationItem.js.map +1 -0
  206. package/dist/converters/page.d.ts +44 -0
  207. package/dist/converters/page.d.ts.map +1 -0
  208. package/dist/converters/page.js +121 -0
  209. package/dist/converters/page.js.map +1 -0
  210. package/dist/converters/person.d.ts +40 -0
  211. package/dist/converters/person.d.ts.map +1 -0
  212. package/dist/converters/person.js +109 -0
  213. package/dist/converters/person.js.map +1 -0
  214. package/dist/converters/resolver.d.ts +29 -0
  215. package/dist/converters/resolver.d.ts.map +1 -0
  216. package/dist/converters/resolver.js +317 -0
  217. package/dist/converters/resolver.js.map +1 -0
  218. package/dist/converters/schema.d.ts +14 -0
  219. package/dist/converters/schema.d.ts.map +1 -0
  220. package/dist/converters/schema.js +18 -0
  221. package/dist/converters/schema.js.map +1 -0
  222. package/dist/converters/svgProcessor.d.ts +23 -0
  223. package/dist/converters/svgProcessor.d.ts.map +1 -0
  224. package/dist/converters/svgProcessor.js +47 -0
  225. package/dist/converters/svgProcessor.js.map +1 -0
  226. package/dist/converters/tag.d.ts +25 -0
  227. package/dist/converters/tag.d.ts.map +1 -0
  228. package/dist/converters/tag.js +98 -0
  229. package/dist/converters/tag.js.map +1 -0
  230. package/dist/converters/template.d.ts +26 -0
  231. package/dist/converters/template.d.ts.map +1 -0
  232. package/dist/converters/template.js +44 -0
  233. package/dist/converters/template.js.map +1 -0
  234. package/dist/index.d.ts +28 -721
  235. package/dist/index.d.ts.map +1 -0
  236. package/dist/index.js +37 -3634
  237. package/dist/index.js.map +1 -1
  238. package/dist/revalidation/handlers.d.ts +52 -0
  239. package/dist/revalidation/handlers.d.ts.map +1 -0
  240. package/dist/revalidation/handlers.js +130 -0
  241. package/dist/revalidation/handlers.js.map +1 -0
  242. package/dist/revalidation/index.d.ts +3 -0
  243. package/dist/revalidation/index.d.ts.map +1 -0
  244. package/dist/revalidation/index.js +4 -0
  245. package/dist/revalidation/index.js.map +1 -0
  246. package/dist/revalidation/nextjs-route.d.ts +31 -0
  247. package/dist/revalidation/nextjs-route.d.ts.map +1 -0
  248. package/dist/revalidation/nextjs-route.js +34 -0
  249. package/dist/revalidation/nextjs-route.js.map +1 -0
  250. package/dist/revalidation/route.d.ts +3 -0
  251. package/dist/revalidation/route.d.ts.map +1 -0
  252. package/dist/revalidation/route.js +97 -0
  253. package/dist/revalidation/route.js.map +1 -0
  254. package/dist/revalidation/server-utils.d.ts +22 -0
  255. package/dist/revalidation/server-utils.d.ts.map +1 -0
  256. package/dist/revalidation/server-utils.js +41 -0
  257. package/dist/revalidation/server-utils.js.map +1 -0
  258. package/dist/revalidation/tags.d.ts +81 -0
  259. package/dist/revalidation/tags.d.ts.map +1 -0
  260. package/dist/revalidation/tags.js +117 -0
  261. package/dist/revalidation/tags.js.map +1 -0
  262. package/dist/revalidation/utils.d.ts +21 -0
  263. package/dist/revalidation/utils.d.ts.map +1 -0
  264. package/dist/revalidation/utils.js +51 -0
  265. package/dist/revalidation/utils.js.map +1 -0
  266. package/dist/server.d.ts +7 -0
  267. package/dist/server.d.ts.map +1 -0
  268. package/dist/server.js +10 -0
  269. package/dist/server.js.map +1 -0
  270. package/dist/types.d.ts +67 -0
  271. package/dist/types.d.ts.map +1 -0
  272. package/dist/types.js +2 -0
  273. package/dist/types.js.map +1 -0
  274. package/dist/utils/arrayUtils.d.ts +3 -0
  275. package/dist/utils/arrayUtils.d.ts.map +1 -0
  276. package/dist/utils/arrayUtils.js +12 -0
  277. package/dist/utils/arrayUtils.js.map +1 -0
  278. package/dist/utils/dateUtils.d.ts +9 -0
  279. package/dist/utils/dateUtils.d.ts.map +1 -0
  280. package/dist/utils/dateUtils.js +26 -0
  281. package/dist/utils/dateUtils.js.map +1 -0
  282. package/dist/utils/errors.d.ts +56 -0
  283. package/dist/utils/errors.d.ts.map +1 -0
  284. package/dist/utils/errors.js +100 -0
  285. package/dist/utils/errors.js.map +1 -0
  286. package/dist/utils/index.d.ts +8 -0
  287. package/dist/utils/index.d.ts.map +1 -0
  288. package/dist/utils/index.js +8 -0
  289. package/dist/utils/index.js.map +1 -0
  290. package/dist/utils/json-utils.d.ts +17 -0
  291. package/dist/utils/json-utils.d.ts.map +1 -0
  292. package/dist/utils/json-utils.js +43 -0
  293. package/dist/utils/json-utils.js.map +1 -0
  294. package/dist/utils/retry.d.ts +112 -0
  295. package/dist/utils/retry.d.ts.map +1 -0
  296. package/dist/utils/retry.js +221 -0
  297. package/dist/utils/retry.js.map +1 -0
  298. package/package.json +12 -6
package/dist/index.js CHANGED
@@ -1,3635 +1,38 @@
1
- import { revalidateTag } from 'next/cache';
2
- import { isSvgImage } from '@se-studio/core-data-types';
3
- import { NextResponse } from 'next/server';
4
-
5
- var __defProp = Object.defineProperty;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __esm = (fn, res) => function __init() {
8
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
9
- };
10
- var __export = (target, all) => {
11
- for (var name in all)
12
- __defProp(target, name, { get: all[name], enumerable: true });
13
- };
14
-
15
- // src/revalidation/tags.ts
16
- function pageTag(slug) {
17
- return `${PageTag}#${slug}`;
18
- }
19
- function articleTag(slug) {
20
- return `${ArticleTag}#${slug}`;
21
- }
22
- function articleTypeTag(slug) {
23
- return `${ArticleTypeTag}#${slug}`;
24
- }
25
- function articleTypeIndexTag(slug) {
26
- return `${ArticleTypeTag}#index#${slug}`;
27
- }
28
- function customTypeTag(slug) {
29
- return `${CustomTypeTag}#${slug}`;
30
- }
31
- function tagTag(slug) {
32
- return `${TagTag}#${slug}`;
33
- }
34
- function personTag(slug) {
35
- return `${PersonTag}#${slug}`;
36
- }
37
- function assetTag(assetId) {
38
- return `${AssetTag}#${assetId}`;
39
- }
40
- function templateTag(label) {
41
- return `${TemplateTag}#${label}`;
42
- }
43
- function locationTag(slug) {
44
- return `${LocationTag}#${slug}`;
45
- }
46
- var PageTag, ArticleTag, ArticleTypeTag, ArticleTypeIndexTag, CustomTypeTag, TagTag, PersonTag, AssetTag, TemplateTag, NavigationTag, BannerTag, LocationTag, GlobalTag, AllTags;
47
- var init_tags = __esm({
48
- "src/revalidation/tags.ts"() {
49
- PageTag = "page";
50
- ArticleTag = "article";
51
- ArticleTypeTag = "articleType";
52
- ArticleTypeIndexTag = "articleTypeIndex";
53
- CustomTypeTag = "customType";
54
- TagTag = "tag";
55
- PersonTag = "person";
56
- AssetTag = "asset";
57
- TemplateTag = "template";
58
- NavigationTag = "navigation";
59
- BannerTag = "banner";
60
- LocationTag = "location";
61
- GlobalTag = "global";
62
- AllTags = [
63
- PageTag,
64
- ArticleTag,
65
- ArticleTypeTag,
66
- ArticleTypeIndexTag,
67
- CustomTypeTag,
68
- TagTag,
69
- PersonTag,
70
- AssetTag,
71
- TemplateTag,
72
- NavigationTag,
73
- BannerTag,
74
- LocationTag,
75
- "hubspot-forms"
76
- // Additional tag from the old implementation
77
- ];
78
- }
79
- });
80
-
81
- // src/revalidation/utils.ts
82
- var utils_exports = {};
83
- __export(utils_exports, {
84
- getCacheTags: () => getCacheTags,
85
- getCacheTagsForPreview: () => getCacheTagsForPreview,
86
- getCacheTagsForProduction: () => getCacheTagsForProduction,
87
- revalidateSingleTag: () => revalidateSingleTag,
88
- revalidateTags: () => revalidateTags
89
- });
90
- function getCacheTagsForPreview() {
91
- return [GlobalTag];
92
- }
93
- function getCacheTagsForProduction(contentType, slug) {
94
- switch (contentType) {
95
- case "page":
96
- return slug ? [pageTag(slug), PageTag] : [PageTag];
97
- case "article":
98
- return slug ? [articleTag(slug), ArticleTag] : [ArticleTag];
99
- case "articleType":
100
- return slug ? [articleTypeTag(slug), ArticleTypeTag] : [ArticleTypeTag];
101
- case "customType":
102
- return slug ? [customTypeTag(slug), CustomTypeTag] : [CustomTypeTag];
103
- case "tag":
104
- return slug ? [tagTag(slug), TagTag] : [TagTag];
105
- case "person":
106
- return slug ? [personTag(slug), PersonTag] : [PersonTag];
107
- case "asset":
108
- return slug ? [assetTag(slug), AssetTag] : [AssetTag];
109
- case "location":
110
- return slug ? [locationTag(slug), LocationTag] : [LocationTag];
111
- default:
112
- return [contentType];
113
- }
114
- }
115
- function getCacheTags(contentType, slug, preview = false) {
116
- if (preview) {
117
- return getCacheTagsForPreview();
118
- }
119
- return getCacheTagsForProduction(contentType, slug);
120
- }
121
- async function revalidateTags(tags, reason) {
122
- await delay(500);
123
- for (const tag of tags) {
124
- console.log(`Revalidating tag: ${tag} - ${reason}`);
125
- revalidateTag(tag);
126
- }
127
- }
128
- async function revalidateSingleTag(tag, reason) {
129
- await delay(500);
130
- console.log(`Revalidating single tag: ${tag} - ${reason}`);
131
- revalidateTag(tag);
132
- }
133
- function delay(ms) {
134
- return new Promise((resolve) => setTimeout(resolve, ms));
135
- }
136
- var init_utils = __esm({
137
- "src/revalidation/utils.ts"() {
138
- init_tags();
139
- }
140
- });
141
-
142
- // src/utils/errors.ts
143
- var ContentfulError = class _ContentfulError extends Error {
144
- constructor(message, statusCode, details) {
145
- super(message);
146
- this.statusCode = statusCode;
147
- this.details = details;
148
- this.name = "ContentfulError";
149
- Object.setPrototypeOf(this, _ContentfulError.prototype);
150
- }
151
- };
152
- var RateLimitError = class _RateLimitError extends ContentfulError {
153
- constructor(message, retryAfter, details) {
154
- super(message, 429, details);
155
- this.retryAfter = retryAfter;
156
- this.name = "RateLimitError";
157
- Object.setPrototypeOf(this, _RateLimitError.prototype);
158
- }
159
- };
160
- var EntryNotFoundError = class _EntryNotFoundError extends ContentfulError {
161
- constructor(entryId, contentType) {
162
- super(`Entry not found: ${entryId}${contentType ? ` (${contentType})` : ""}`, 404);
163
- this.entryId = entryId;
164
- this.contentType = contentType;
165
- this.name = "EntryNotFoundError";
166
- Object.setPrototypeOf(this, _EntryNotFoundError.prototype);
167
- }
168
- };
169
- var AuthenticationError = class _AuthenticationError extends ContentfulError {
170
- constructor(message = "Authentication failed") {
171
- super(message, 401);
172
- this.name = "AuthenticationError";
173
- Object.setPrototypeOf(this, _AuthenticationError.prototype);
174
- }
175
- };
176
- var ValidationError = class _ValidationError extends ContentfulError {
177
- constructor(message, validationErrors) {
178
- super(message, 400, validationErrors);
179
- this.validationErrors = validationErrors;
180
- this.name = "ValidationError";
181
- Object.setPrototypeOf(this, _ValidationError.prototype);
182
- }
183
- };
184
- function isContentfulError(error) {
185
- return error instanceof ContentfulError;
186
- }
187
- function isRateLimitError(error) {
188
- return error instanceof RateLimitError;
189
- }
190
- function isRetryableError(error) {
191
- if (isRateLimitError(error)) {
192
- return true;
193
- }
194
- if (isContentfulError(error)) {
195
- return error.statusCode !== void 0 && (error.statusCode >= 500 || error.statusCode === 429);
196
- }
197
- return false;
198
- }
199
- function getRetryAfter(error) {
200
- if (isRateLimitError(error)) {
201
- return error.retryAfter;
202
- }
203
- return void 0;
204
- }
205
-
206
- // src/utils/retry.ts
207
- var DEFAULT_RETRY_CONFIG = {
208
- maxRetries: 3,
209
- initialDelay: 1e3,
210
- // 1 second
211
- maxDelay: 3e4,
212
- // 30 seconds
213
- backoffMultiplier: 2
214
- };
215
- function sleep(ms) {
216
- return new Promise((resolve) => setTimeout(resolve, ms));
217
- }
218
- function calculateBackoffDelay(attempt, config, retryAfter) {
219
- if (retryAfter !== void 0) {
220
- return Math.min(retryAfter * 1e3, config.maxDelay);
221
- }
222
- const exponentialDelay = config.initialDelay * config.backoffMultiplier ** attempt;
223
- const jitter = Math.random() * exponentialDelay;
224
- return Math.min(exponentialDelay + jitter, config.maxDelay);
225
- }
226
- async function withRetry(fn, config) {
227
- const retryConfig = {
228
- ...DEFAULT_RETRY_CONFIG,
229
- ...config
230
- };
231
- let lastError;
232
- for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) {
233
- try {
234
- return await fn();
235
- } catch (error) {
236
- lastError = error;
237
- if (attempt === retryConfig.maxRetries) {
238
- break;
239
- }
240
- if (!isRetryableError(error)) {
241
- throw error;
242
- }
243
- const retryAfter = getRetryAfter(error);
244
- const delay3 = calculateBackoffDelay(attempt, retryConfig, retryAfter);
245
- if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
246
- console.warn(
247
- `Retry attempt ${attempt + 1}/${retryConfig.maxRetries} after ${delay3}ms`,
248
- error
249
- );
250
- }
251
- await sleep(delay3);
252
- }
253
- }
254
- throw lastError;
255
- }
256
- var RateLimiter = class {
257
- /** Minimum interval between requests in milliseconds */
258
- intervalMs;
259
- /** Timestamp when the next request can be made */
260
- nextAvailableTime = 0;
261
- /** Queue of pending requests */
262
- queue = [];
263
- /** Whether the queue processor is running */
264
- processing = false;
265
- /** Additional pause time from 429 responses (ms) */
266
- pauseUntil = 0;
267
- constructor(requestsPerSecond, _refillRate) {
268
- this.intervalMs = Math.ceil(1e3 / (requestsPerSecond * 0.8));
269
- }
270
- /**
271
- * Calculates how long to wait before the next request can be made
272
- */
273
- getWaitTime() {
274
- const now = Date.now();
275
- const waitForInterval = Math.max(0, this.nextAvailableTime - now);
276
- const waitForPause = Math.max(0, this.pauseUntil - now);
277
- return Math.max(waitForInterval, waitForPause);
278
- }
279
- /**
280
- * Processes the queue, releasing requests one at a time with proper spacing
281
- */
282
- async processQueue() {
283
- if (this.processing) {
284
- return;
285
- }
286
- this.processing = true;
287
- while (this.queue.length > 0) {
288
- const waitTime = this.getWaitTime();
289
- if (waitTime > 0) {
290
- await sleep(waitTime);
291
- }
292
- this.nextAvailableTime = Date.now() + this.intervalMs;
293
- const entry = this.queue.shift();
294
- entry?.resolve();
295
- }
296
- this.processing = false;
297
- }
298
- /**
299
- * Acquires permission to make a request.
300
- * Returns a promise that resolves when the request can proceed.
301
- * Requests are processed in FIFO order with minimum interval spacing.
302
- *
303
- * @returns Promise that resolves when rate limit allows the request
304
- */
305
- async acquire() {
306
- const waitTime = this.getWaitTime();
307
- if (waitTime === 0 && this.queue.length === 0) {
308
- this.nextAvailableTime = Date.now() + this.intervalMs;
309
- return;
310
- }
311
- return new Promise((resolve) => {
312
- this.queue.push({ resolve });
313
- this.processQueue();
314
- });
315
- }
316
- /**
317
- * Gets the number of requests currently waiting in the queue
318
- */
319
- getQueueLength() {
320
- return this.queue.length;
321
- }
322
- /**
323
- * Pauses the rate limiter for a specified duration.
324
- * All requests (current and queued) will wait until pause expires.
325
- * Does NOT block - just sets the pause time and returns.
326
- *
327
- * @param seconds - Duration to pause in seconds
328
- */
329
- pause(seconds) {
330
- const pauseUntil = Date.now() + seconds * 1e3;
331
- if (pauseUntil > this.pauseUntil) {
332
- this.pauseUntil = pauseUntil;
333
- }
334
- }
335
- /**
336
- * Gets the current interval between requests in milliseconds
337
- */
338
- getIntervalMs() {
339
- return this.intervalMs;
340
- }
341
- };
342
- var CONTENTFUL_RATE_LIMITS = {
343
- /** Content Delivery API: 55 requests/second */
344
- delivery: 55,
345
- /** Content Preview API: 14 requests/second */
346
- preview: 14
347
- };
348
- var deliveryRateLimiter = new RateLimiter(CONTENTFUL_RATE_LIMITS.delivery);
349
- var previewRateLimiter = new RateLimiter(CONTENTFUL_RATE_LIMITS.preview);
350
- function getRateLimiter(preview) {
351
- return preview ? previewRateLimiter : deliveryRateLimiter;
352
- }
353
-
354
- // src/client.ts
355
- function buildQueryString(query) {
356
- const params = new URLSearchParams();
357
- Object.entries(query).forEach(([key, value]) => {
358
- if (value !== void 0 && value !== null) {
359
- params.append(key, String(value));
360
- }
361
- });
362
- return params.toString();
363
- }
364
- function sleep2(ms) {
365
- return new Promise((resolve) => setTimeout(resolve, ms));
366
- }
367
- var RETRY_CONFIG = {
368
- maxRetries: 10,
369
- initialDelay: 1e3,
370
- maxDelay: 15e3,
371
- backoffMultiplier: 2
372
- };
373
- function parseRetryAfter(response) {
374
- const retryAfterHeader = response.headers.get("X-Contentful-RateLimit-Reset") || response.headers.get("Retry-After");
375
- if (retryAfterHeader) {
376
- const parsed = Number.parseInt(retryAfterHeader, 10);
377
- return Number.isNaN(parsed) ? void 0 : parsed;
378
- }
379
- return void 0;
380
- }
381
- async function parseErrorResponse(response) {
382
- const statusCode = response.status;
383
- let errorData;
384
- try {
385
- errorData = await response.json();
386
- } catch {
387
- errorData = { message: response.statusText };
388
- }
389
- const message = errorData?.message || `Contentful API error: ${statusCode}`;
390
- switch (statusCode) {
391
- case 401:
392
- return new AuthenticationError(message);
393
- case 404:
394
- return new EntryNotFoundError(
395
- errorData?.sys?.id || "unknown",
396
- errorData?.sys?.contentType?.sys?.id
397
- );
398
- case 429: {
399
- const retryAfterHeader = response.headers.get("X-Contentful-RateLimit-Reset") || response.headers.get("Retry-After");
400
- const retryAfter = retryAfterHeader ? Number.parseInt(retryAfterHeader, 10) : void 0;
401
- return new RateLimitError(message, retryAfter, errorData);
402
- }
403
- case 400:
404
- return new ValidationError(message, errorData);
405
- default:
406
- return new ContentfulError(message, statusCode, errorData);
407
- }
408
- }
409
- var ContentfulFetchClient = class {
410
- baseUrl;
411
- accessToken;
412
- rateLimiter;
413
- isPreview;
414
- constructor(config, preview = false) {
415
- const host = config.host || (preview ? "preview.contentful.com" : "cdn.contentful.com");
416
- const environment = config.environment || "master";
417
- this.baseUrl = `https://${host}/spaces/${config.spaceId}/environments/${environment}`;
418
- this.accessToken = config.accessToken;
419
- this.isPreview = preview;
420
- this.rateLimiter = getRateLimiter(preview);
421
- }
422
- /**
423
- * Fetches entries from Contentful with proactive rate limiting
424
- * and automatic retry on rate limit errors.
425
- *
426
- * Rate limits are applied proactively:
427
- * - Preview API: 14 requests/second
428
- * - Delivery API: 55 requests/second
429
- *
430
- * If a 429 is still received (e.g., due to other processes), retries with backoff.
431
- */
432
- async getEntries(query, options) {
433
- const queryString = buildQueryString(query);
434
- const url = `${this.baseUrl}/entries?${queryString}`;
435
- const fetchOptions = {
436
- headers: {
437
- Authorization: `Bearer ${this.accessToken}`,
438
- "Content-Type": "application/json"
439
- }
440
- };
441
- if (options?.next) {
442
- fetchOptions.next = options.next;
443
- }
444
- for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {
445
- try {
446
- await this.rateLimiter.acquire();
447
- const response = await fetch(url, fetchOptions);
448
- if (response.ok) {
449
- return response.json();
450
- }
451
- if (response.status === 429 && attempt < RETRY_CONFIG.maxRetries) {
452
- const retryAfter = parseRetryAfter(response);
453
- const delay3 = calculateBackoffDelay(attempt, RETRY_CONFIG, retryAfter);
454
- console.warn(
455
- `[Contentful] Rate limited (429), retrying in ${delay3}ms (attempt ${attempt + 1}/${RETRY_CONFIG.maxRetries}). Queue: ${this.rateLimiter.getQueueLength()} waiting. API: ${this.isPreview ? "preview" : "delivery"}`
456
- );
457
- const pauseSeconds = Math.max(retryAfter ?? 2, 2);
458
- this.rateLimiter.pause(pauseSeconds);
459
- await sleep2(delay3);
460
- continue;
461
- }
462
- throw await parseErrorResponse(response);
463
- } catch (error) {
464
- if (error instanceof ContentfulError) {
465
- throw error;
466
- }
467
- console.error("[Contentful] Unexpected error in request", error);
468
- throw new ContentfulError("Unexpected error in request", 500);
469
- }
470
- }
471
- throw new ContentfulError("Max retries exceeded", 500);
472
- }
473
- /**
474
- * Fetches a single asset from Contentful by ID with proactive rate limiting
475
- * and automatic retry on rate limit errors.
476
- *
477
- * @param assetId - The Contentful asset ID
478
- * @param options - Optional fetch options (locale, preview, caching, retry)
479
- * @returns The asset response or null if not found
480
- */
481
- async getAsset(assetId, options) {
482
- const url = `${this.baseUrl}/assets/${assetId}`;
483
- const fetchOptions = {
484
- headers: {
485
- Authorization: `Bearer ${this.accessToken}`,
486
- "Content-Type": "application/json"
487
- }
488
- };
489
- if (options?.next) {
490
- fetchOptions.next = options.next;
491
- }
492
- for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {
493
- try {
494
- await this.rateLimiter.acquire();
495
- const response = await fetch(url, fetchOptions);
496
- if (response.ok) {
497
- return response.json();
498
- }
499
- if (response.status === 404) {
500
- return null;
501
- }
502
- if (response.status === 429 && attempt < RETRY_CONFIG.maxRetries) {
503
- const retryAfter = parseRetryAfter(response);
504
- const delay3 = calculateBackoffDelay(attempt, RETRY_CONFIG, retryAfter);
505
- console.warn(
506
- `[Contentful] Rate limited (429), retrying in ${delay3}ms (attempt ${attempt + 1}/${RETRY_CONFIG.maxRetries}). Queue: ${this.rateLimiter.getQueueLength()} waiting. API: ${this.isPreview ? "preview" : "delivery"}`
507
- );
508
- const pauseSeconds = Math.max(retryAfter ?? 2, 2);
509
- this.rateLimiter.pause(pauseSeconds);
510
- await sleep2(delay3);
511
- continue;
512
- }
513
- throw await parseErrorResponse(response);
514
- } catch (error) {
515
- if (error instanceof ContentfulError) {
516
- throw error;
517
- }
518
- console.error("[Contentful] Unexpected error in asset request", error);
519
- throw new ContentfulError("Unexpected error in asset request", 500);
520
- }
521
- }
522
- throw new ContentfulError("Max retries exceeded", 500);
523
- }
524
- };
525
- function createContentfulClient(config) {
526
- return new ContentfulFetchClient(config, false);
527
- }
528
- function createContentfulPreviewClient(config) {
529
- return new ContentfulFetchClient(config, true);
530
- }
531
- function getContentfulClient(config, preview) {
532
- return preview ? createContentfulPreviewClient(config) : createContentfulClient(config);
533
- }
534
-
535
- // src/converters/helpers.ts
536
- function resolveBuildYear(value) {
537
- const buildYear = (/* @__PURE__ */ new Date()).getFullYear();
538
- return value.replaceAll("#buildYear#", buildYear.toString());
539
- }
540
- function makeContentfulTitle(title, id, prefix = "Title for ") {
541
- return title ?? `${prefix}${id}`;
542
- }
543
- function stringOrUndefined(value) {
544
- if (value && value.length > 0) {
545
- return value;
546
- }
547
- return void 0;
548
- }
549
- function makeContentfulDescription(description, id) {
550
- return description ?? `Description for ${id}`;
551
- }
552
- function lookupAsset(context, asset) {
553
- if (!asset) return void 0;
554
- return context.assets.get(asset.sys.id);
555
- }
556
- function lookupIconAsset(context, asset) {
557
- const visual = lookupAsset(context, asset);
558
- if (!visual) return void 0;
559
- if (visual.image?.type === "Svg image") {
560
- return {
561
- ...visual,
562
- image: {
563
- ...visual.image,
564
- isIcon: true
565
- }
566
- };
567
- }
568
- return visual;
569
- }
570
- function lookupDownloadAsset(context, asset) {
571
- if (!asset) return void 0;
572
- const rawAsset = context.rawAssets.get(asset.sys.id);
573
- if (!rawAsset?.fields?.file) return void 0;
574
- const file = rawAsset.fields.file;
575
- if (!file.url || !file.fileName || !file.contentType) return void 0;
576
- return {
577
- assetId: rawAsset.sys.id,
578
- filename: file.fileName,
579
- contentType: file.contentType,
580
- size: file.details?.size,
581
- url: `https:${file.url}`
582
- };
583
- }
584
- var DEFAULT_POSITION_FIELDS = {
585
- index: 0,
586
- isFirst: false,
587
- isLast: false,
588
- indexOfType: 0
589
- };
590
- function createInternalLink(id, fields, context, href, internalType, additionalProps) {
591
- const {
592
- cmsLabel,
593
- title,
594
- name,
595
- useName,
596
- featuredImage,
597
- backgroundColour,
598
- textColour,
599
- indexed,
600
- hidden,
601
- slug,
602
- description
603
- } = fields;
604
- const text = makeContentfulTitle(title, id);
605
- return {
606
- type: "Internal link",
607
- internalType,
608
- id,
609
- name: name ?? cmsLabel ?? "",
610
- useName,
611
- text,
612
- visual: lookupAsset(context, featuredImage),
613
- backgroundColour,
614
- textColour,
615
- indexed,
616
- hidden,
617
- slug,
618
- href,
619
- title,
620
- description,
621
- ...additionalProps
622
- };
623
- }
624
- function addPositionMetadata(items) {
625
- if (items.length === 0) return items;
626
- const typeCount = /* @__PURE__ */ new Map();
627
- return items.map((item, index) => {
628
- const currentTypeIndex = typeCount.get(item.type) ?? 0;
629
- typeCount.set(item.type, currentTypeIndex + 1);
630
- return {
631
- ...item,
632
- index,
633
- isFirst: index === 0,
634
- isLast: index === items.length - 1,
635
- indexOfType: currentTypeIndex
636
- };
637
- });
638
- }
639
- function calculateContentCount(...contents) {
640
- return contents.reduce((acc, content) => acc + content.length, 0);
641
- }
642
-
643
- // src/converters/asset.ts
644
- function convertAssetToVisual(context, asset, options) {
645
- if (!asset) return void 0;
646
- const { fields, sys } = asset;
647
- if (!fields) return void 0;
648
- const { id } = sys;
649
- const { file } = fields;
650
- if (!file) return void 0;
651
- const { contentType } = file;
652
- if (contentType?.startsWith("image/")) {
653
- const image = convertAssetToImage(file, fields, sys, options);
654
- if (!image) return void 0;
655
- return {
656
- id,
657
- type: "Visual",
658
- image
659
- };
660
- }
661
- if (contentType?.startsWith("video/")) {
662
- const video = convertAssetToVideo(
663
- file,
664
- fields,
665
- sys,
666
- context,
667
- options
668
- );
669
- if (!video) return void 0;
670
- return {
671
- id,
672
- type: "Visual",
673
- video
674
- };
675
- }
676
- if (contentType === "application/json") {
677
- const animation = convertAssetToAnimation(
678
- file,
679
- fields,
680
- sys,
681
- options
682
- );
683
- if (!animation) return void 0;
684
- return {
685
- id,
686
- type: "Visual",
687
- animation
688
- };
689
- }
690
- return void 0;
691
- }
692
- function convertAssetToAnimation(file, fields, sys, options) {
693
- const { id } = sys;
694
- const { title, description } = fields;
695
- const { url } = file;
696
- if (!url) return void 0;
697
- return {
698
- ...options,
699
- id,
700
- type: "Animation",
701
- animationSrc: `https:${url}`,
702
- name: makeContentfulTitle(title, id),
703
- description: stringOrUndefined(description)
704
- };
705
- }
706
- function convertAssetToImage(file, fields, sys, options) {
707
- const { contentType } = file;
708
- if (contentType === "image/svg+xml") {
709
- return convertAssetToSvgImage(file, fields, sys, options);
710
- } else {
711
- return convertAssetToPicture(file, fields, sys, options);
712
- }
713
- }
714
- function convertAssetToPicture(file, fields, sys, options) {
715
- const { id } = sys;
716
- const { title, description } = fields;
717
- const { contentType, details, url } = file;
718
- if (!details) return void 0;
719
- const { size, image } = details;
720
- const { width, height } = image || {};
721
- return {
722
- ...options,
723
- id,
724
- type: "Picture",
725
- src: `https:${url}`,
726
- mimeType: contentType,
727
- size,
728
- width: width || 0,
729
- height: height || 0,
730
- name: makeContentfulTitle(title, id),
731
- description: makeContentfulDescription(description, id)
732
- };
733
- }
734
- function convertAssetToSvgImage(file, fields, sys, options) {
735
- const { id } = sys;
736
- const { title, description } = fields;
737
- const { contentType, details, url } = file;
738
- if (!details) return void 0;
739
- const { size, image } = details;
740
- const { width, height } = image || {};
741
- return {
742
- ...options,
743
- id,
744
- type: "Svg image",
745
- svgSrc: `https:${url}`,
746
- mimeType: contentType,
747
- size,
748
- width: width || 0,
749
- height: height || 0,
750
- name: makeContentfulTitle(title, id),
751
- description: stringOrUndefined(description)
752
- };
753
- }
754
- function convertAssetToVideoDetails(file, sys) {
755
- const { id } = sys;
756
- const { details, url, contentType, fileName } = file;
757
- if (!details) return void 0;
758
- const { size } = details;
759
- return {
760
- id,
761
- videoUrl: `https:${url}`,
762
- size,
763
- mimeType: contentType,
764
- fileName
765
- };
766
- }
767
- function convertAssetToVideo(file, fields, sys, context, options) {
768
- const { id } = sys;
769
- const { title, description } = fields;
770
- const { details } = file;
771
- if (!details) {
772
- return void 0;
773
- }
774
- const { image } = details;
775
- const { width, height } = image || {};
776
- const videoDetails = convertAssetToVideoDetails(file, sys);
777
- if (!videoDetails) return void 0;
778
- return {
779
- ...options,
780
- id,
781
- type: "Local video",
782
- preview: videoDetails,
783
- videoPrefix: context.videoPrefix,
784
- width: width || 0,
785
- height: height || 0,
786
- name: makeContentfulTitle(title, id),
787
- description: makeContentfulDescription(description, id)
788
- };
789
- }
790
- function createResponsiveVisual(visual, mobileVisual, customSize) {
791
- if (!visual) return void 0;
792
- return {
793
- visual,
794
- mobileVisual,
795
- visualCustomSize: customSize
796
- };
797
- }
798
- function convertMediaEntryToVisual(context, entry) {
799
- const { fields, sys } = entry;
800
- if (!fields) return void 0;
801
- const baseVisual = lookupAsset(context, fields.asset);
802
- if (!baseVisual) return void 0;
803
- const { name, mask: maskDropdown, maskImage, ...otherFields } = fields;
804
- let mask;
805
- if (maskImage) {
806
- const maskVisual = lookupAsset(context, maskImage);
807
- if (maskVisual?.image?.type === "Svg image") {
808
- mask = maskVisual.image.svgSrc;
809
- } else if (maskVisual?.image?.type === "Picture") {
810
- mask = maskVisual.image.src;
811
- }
812
- } else if (maskDropdown && context.maskResolver) {
813
- mask = context.maskResolver(maskDropdown);
814
- }
815
- const metadata = {
816
- name: makeContentfulTitle(name, sys.id),
817
- ...otherFields
818
- };
819
- const visual = {
820
- ...baseVisual,
821
- id: sys.id,
822
- mask
823
- };
824
- if (baseVisual.image) {
825
- visual.image = {
826
- ...baseVisual.image,
827
- ...metadata
828
- };
829
- } else if (baseVisual.video) {
830
- visual.video = {
831
- ...baseVisual.video,
832
- ...metadata
833
- };
834
- } else if (baseVisual.animation) {
835
- visual.animation = {
836
- ...baseVisual.animation,
837
- ...metadata
838
- };
839
- }
840
- return visual;
841
- }
842
- function convertExternalVideoEntryToVisual(context, entry) {
843
- const { fields, sys } = entry;
844
- if (!fields || !fields.url) return void 0;
845
- const previewVisual = lookupAsset(context, fields.preview);
846
- const posterVisual = lookupAsset(context, fields.posterImage);
847
- const preview = previewVisual?.video?.type === "Local video" ? previewVisual.video.preview : void 0;
848
- const poster = posterVisual?.image?.type === "Picture" ? posterVisual.image.src : void 0;
849
- const mapHorizontal = (value) => {
850
- if (!value) return null;
851
- return value === "Center" ? "Middle" : value;
852
- };
853
- const video = {
854
- id: sys.id,
855
- type: "External video",
856
- name: makeContentfulTitle(fields.name, sys.id),
857
- nameAsCaption: fields.nameAsCaption ?? null,
858
- external: fields.url,
859
- preview,
860
- poster,
861
- autoPlay: fields.autoPlay ?? null,
862
- loop: fields.loop ?? null,
863
- hideControls: fields.hideControls ?? null,
864
- dontCrop: fields.dontCrop ?? null,
865
- verticalCropPosition: fields.verticalCropPosition ?? null,
866
- horizontalCropPosition: mapHorizontal(fields.horizontalCropPosition),
867
- horizontalPosition: fields.horizontalPosition ?? null,
868
- widthPercent: fields.width ?? null
869
- };
870
- return {
871
- id: sys.id,
872
- type: "Visual",
873
- video
874
- };
875
- }
876
- function lookupMediaEntry(context, link) {
877
- if (!link) return void 0;
878
- const id = link.sys.id;
879
- const possibleEntry = context.includes.get(id);
880
- if (!possibleEntry) {
881
- return void 0;
882
- }
883
- const { type, entry } = possibleEntry;
884
- if (type === "media") {
885
- return convertMediaEntryToVisual(
886
- context,
887
- entry
888
- );
889
- }
890
- if (type === "externalVideo") {
891
- return convertExternalVideoEntryToVisual(
892
- context,
893
- entry
894
- );
895
- }
896
- return void 0;
897
- }
898
-
899
- // src/converters/svgProcessor.ts
900
- async function fetchSvgContent(url) {
901
- const response = await fetch(url);
902
- if (!response.ok) {
903
- throw new Error(`Failed to fetch SVG from ${url}: ${response.statusText}`);
904
- }
905
- return await response.text();
906
- }
907
- function processSvgForSprite(svgContent, iconId) {
908
- const viewBoxMatch = svgContent.match(/viewBox\s*=\s*["']([^"']+)["']/i);
909
- const viewBox = viewBoxMatch ? viewBoxMatch[1] : "0 0 24 24";
910
- const innerContentMatch = svgContent.match(/<svg[^>]*>([\s\S]*)<\/svg>/i);
911
- if (!innerContentMatch) {
912
- throw new Error(`Invalid SVG content: could not parse SVG structure`);
913
- }
914
- let innerContent = innerContentMatch[1] ?? "";
915
- innerContent = innerContent.replace(/\s(width|height)=["'][^"']*["']/gi, "");
916
- const symbolId = `icon-${iconId}`;
917
- return `<symbol id="${symbolId}" viewBox="${viewBox}">${innerContent}</symbol>`;
918
- }
919
-
920
- // src/converters/iconCollector.ts
921
- function extractIconFromVisual(visual) {
922
- if (!visual?.image) {
923
- return void 0;
924
- }
925
- if (isSvgImage(visual.image) && visual.image.isIcon) {
926
- return visual.image;
927
- }
928
- return void 0;
929
- }
930
- function extractIconsFromResponsiveVisual(visual) {
931
- const icons = [];
932
- if (visual?.visual) {
933
- const svg = extractIconFromVisual(visual.visual);
934
- if (svg) {
935
- icons.push(svg);
936
- }
937
- }
938
- if (visual?.mobileVisual) {
939
- const svg = extractIconFromVisual(visual.mobileVisual);
940
- if (svg) {
941
- icons.push(svg);
942
- }
943
- }
944
- return icons;
945
- }
946
- function collectIconsFromComponent(component) {
947
- if (!component) {
948
- return [];
949
- }
950
- const icon = extractIconFromVisual(component.icon);
951
- return icon ? [icon] : [];
952
- }
953
- function collectIconsFromCollection(collection) {
954
- if (!collection) {
955
- return [];
956
- }
957
- const icon = extractIconFromVisual(collection.icon);
958
- return icon ? [icon] : [];
959
- }
960
- function collectIconsFromNavigation(navigation) {
961
- if (!navigation) {
962
- return [];
963
- }
964
- const icons = [];
965
- const seenIds = /* @__PURE__ */ new Set();
966
- function addIcon(icon) {
967
- if (!seenIds.has(icon.id)) {
968
- seenIds.add(icon.id);
969
- icons.push(icon);
970
- }
971
- }
972
- function processNavItem(item) {
973
- if (item.link?.icon) {
974
- const linkIcons = extractIconsFromResponsiveVisual(item.link.icon);
975
- for (const icon of linkIcons) {
976
- addIcon(icon);
977
- }
978
- }
979
- if (item.entries) {
980
- for (const nestedItem of item.entries) {
981
- processNavItem(nestedItem);
982
- }
983
- }
984
- }
985
- for (const item of navigation.entries) {
986
- processNavItem(item);
987
- }
988
- return icons;
989
- }
990
- function collectIconsFromRichText(richText, processContent) {
991
- if (!richText?.json) {
992
- return;
993
- }
994
- const traverseNode = (node) => {
995
- if (node.data?.target && typeof node.data.target === "object" && "type" in node.data.target && "id" in node.data.target) {
996
- processContent(node.data.target);
997
- }
998
- if ("content" in node && Array.isArray(node.content)) {
999
- for (const child of node.content) {
1000
- if (typeof child === "object" && child !== null && "nodeType" in child) {
1001
- traverseNode(child);
1002
- }
1003
- }
1004
- }
1005
- };
1006
- traverseNode(richText.json);
1007
- }
1008
- function collectIconsFromContent(contents) {
1009
- if (!contents) {
1010
- return [];
1011
- }
1012
- const icons = [];
1013
- const seenIds = /* @__PURE__ */ new Set();
1014
- const processedIds = /* @__PURE__ */ new Set();
1015
- function addIcon(icon) {
1016
- if (!seenIds.has(icon.id)) {
1017
- seenIds.add(icon.id);
1018
- icons.push(icon);
1019
- }
1020
- }
1021
- function processContentItem(content) {
1022
- if (processedIds.has(content.id)) {
1023
- return;
1024
- }
1025
- processedIds.add(content.id);
1026
- if (content.type === "Component") {
1027
- const componentIcons = collectIconsFromComponent(content);
1028
- for (const icon of componentIcons) {
1029
- addIcon(icon);
1030
- }
1031
- const component = content;
1032
- if (component.contents) {
1033
- for (const nested of component.contents) {
1034
- processContentItem(nested);
1035
- }
1036
- }
1037
- collectIconsFromRichText(component.body, processContentItem);
1038
- collectIconsFromRichText(component.additionalCopy, processContentItem);
1039
- }
1040
- if (content.type === "Collection") {
1041
- const collectionIcons = collectIconsFromCollection(content);
1042
- for (const icon of collectionIcons) {
1043
- addIcon(icon);
1044
- }
1045
- const collection = content;
1046
- if (collection.contents) {
1047
- for (const nested of collection.contents) {
1048
- if (typeof nested === "object" && nested !== null && "type" in nested && "id" in nested) {
1049
- processContentItem(nested);
1050
- }
1051
- }
1052
- }
1053
- collectIconsFromRichText(collection.body, processContentItem);
1054
- collectIconsFromRichText(collection.additionalCopy, processContentItem);
1055
- }
1056
- if (content.type === "Internal link" || content.type === "External link" || content.type === "Download link" || content.type === "Blank link") {
1057
- const link = content;
1058
- if (link.icon) {
1059
- const linkIcons = extractIconsFromResponsiveVisual(link.icon);
1060
- for (const icon of linkIcons) {
1061
- addIcon(icon);
1062
- }
1063
- }
1064
- }
1065
- }
1066
- for (const content of contents) {
1067
- processContentItem(content);
1068
- }
1069
- return icons;
1070
- }
1071
- function deduplicateIcons(...iconArrays) {
1072
- const seenIds = /* @__PURE__ */ new Set();
1073
- const icons = [];
1074
- for (const arr of iconArrays) {
1075
- for (const icon of arr) {
1076
- if (!seenIds.has(icon.id)) {
1077
- seenIds.add(icon.id);
1078
- icons.push(icon);
1079
- }
1080
- }
1081
- }
1082
- return icons.length > 0 ? icons : void 0;
1083
- }
1084
- async function processIconsForSprite(icons) {
1085
- if (icons.length === 0) {
1086
- return [];
1087
- }
1088
- const processedIcons = await Promise.all(
1089
- icons.map(async (icon) => {
1090
- try {
1091
- const svgContent = await fetchSvgContent(icon.svgSrc);
1092
- const symbolContent = processSvgForSprite(svgContent, icon.id);
1093
- return {
1094
- ...icon,
1095
- symbolContent
1096
- };
1097
- } catch (error) {
1098
- console.error(`Failed to process icon ${icon.id}:`, error);
1099
- return icon;
1100
- }
1101
- })
1102
- );
1103
- return processedIcons;
1104
- }
1105
-
1106
- // src/api/helpers.ts
1107
- init_utils();
1108
-
1109
- // src/utils/arrayUtils.ts
1110
- function notEmpty(value) {
1111
- if (value === null || value === void 0) return false;
1112
- return true;
1113
- }
1114
- function arrayOrUndefined(array) {
1115
- if (array && array.length > 0) {
1116
- return array;
1117
- }
1118
- return void 0;
1119
- }
1120
-
1121
- // src/utils/dateUtils.ts
1122
- function isValidDate(date) {
1123
- if (date instanceof Date) {
1124
- return !Number.isNaN(date.getTime());
1125
- }
1126
- if (typeof date === "string" || typeof date === "number") {
1127
- const parsed = new Date(date);
1128
- return !Number.isNaN(parsed.getTime()) && parsed.getTime() > 0;
1129
- }
1130
- return false;
1131
- }
1132
- function safeDate(date) {
1133
- if (!isValidDate(date)) {
1134
- return null;
1135
- }
1136
- if (date instanceof Date) {
1137
- return date;
1138
- }
1139
- return new Date(date);
1140
- }
1141
-
1142
- // src/api/helpers.ts
1143
- function buildFetchOptions(options, defaultDraftOnly = false) {
1144
- const preview = options?.preview === void 0 ? defaultDraftOnly : options.preview;
1145
- return {
1146
- preview,
1147
- cache: "force-cache",
1148
- ...options
1149
- };
1150
- }
1151
- function getContentfulConfig(preview) {
1152
- const spaceId = process.env.CONTENTFUL_SPACE_ID;
1153
- const environment = process.env.CONTENTFUL_ENVIRONMENT || process.env.CONTENTFUL_ENVIRONMENT_NAME;
1154
- const accessToken = preview ? process.env.CONTENTFUL_PREVIEW_ACCESS_TOKEN : process.env.CONTENTFUL_ACCESS_TOKEN;
1155
- if (!spaceId) {
1156
- throw new Error("CONTENTFUL_SPACE_ID environment variable is required");
1157
- }
1158
- if (!accessToken) {
1159
- if (preview) {
1160
- throw new Error("CONTENTFUL_PREVIEW_ACCESS_TOKEN environment variable is required");
1161
- }
1162
- throw new Error("CONTENTFUL_ACCESS_TOKEN environment variable is required");
1163
- }
1164
- if (!environment) {
1165
- throw new Error(
1166
- "CONTENTFUL_ENVIRONMENT or CONTENTFUL_ENVIRONMENT_NAME environment variable is required"
1167
- );
1168
- }
1169
- return {
1170
- spaceId,
1171
- accessToken,
1172
- environment
1173
- };
1174
- }
1175
- var PAGE_LINK_FIELDS = "sys,fields.cmsLabel,fields.title,fields.slug,fields.featuredImage,fields.backgroundColour,fields.textColour,fields.indexed,fields.hidden,fields.tags";
1176
- var ARTICLE_LINK_FIELDS = "sys,fields.cmsLabel,fields.title,fields.slug,fields.featuredImage,fields.backgroundColour,fields.textColour,fields.indexed,fields.hidden,fields.tags,fields.articleType,fields.date,fields.author,fields.externalLink,fields.download";
1177
- var ARTICLE_TYPE_LINK_FIELDS = "sys,fields.name,fields.slug,fields.featuredImage,fields.backgroundColour,fields.textColour,fields.indexed,fields.hidden";
1178
- var TAG_LINK_FIELDS = "sys,fields.cmsLabel,fields.name,fields.slug,fields.featuredImage,fields.backgroundColour,fields.textColour,fields.indexed,fields.hidden";
1179
- var PERSON_LINK_FIELDS = "sys,fields.name,fields.slug,fields.media,fields.backgroundColour,fields.textColour,fields.indexed,fields.hidden";
1180
- function convertAllAssets(response, context) {
1181
- const visuals = /* @__PURE__ */ new Map();
1182
- const assets = response.includes?.Asset;
1183
- if (assets && assets.length > 0) {
1184
- for (const asset of assets) {
1185
- const visual = convertAssetToVisual(context, asset);
1186
- if (visual) {
1187
- visuals.set(visual.id, visual);
1188
- }
1189
- }
1190
- }
1191
- return visuals;
1192
- }
1193
- function convertAllRawAssets(response) {
1194
- const rawAssets = /* @__PURE__ */ new Map();
1195
- const assets = response.includes?.Asset;
1196
- if (assets && assets.length > 0) {
1197
- for (const asset of assets) {
1198
- rawAssets.set(asset.sys.id, asset);
1199
- }
1200
- }
1201
- return rawAssets;
1202
- }
1203
- function convertAllIncludes(response) {
1204
- const includes = /* @__PURE__ */ new Map();
1205
- const entries = [...response.items, ...response.includes?.Entry || []];
1206
- if (entries && entries.length > 0) {
1207
- for (const entry of entries) {
1208
- if (entry?.sys && entry.fields) {
1209
- includes.set(entry.sys.id, {
1210
- id: entry.sys.id,
1211
- type: entry.sys.contentType.sys.id,
1212
- entry
1213
- });
1214
- }
1215
- }
1216
- }
1217
- return includes;
1218
- }
1219
- async function fetchSingleEntity(context, config, fetchConfig, options) {
1220
- const client = getContentfulClient(config, options.preview);
1221
- const cacheTags = getCacheTags(
1222
- fetchConfig.cacheTagType,
1223
- fetchConfig.cacheTagIdentifier,
1224
- options?.preview
1225
- );
1226
- const requestOptions = {
1227
- ...options,
1228
- next: {
1229
- ...options?.next,
1230
- tags: cacheTags
1231
- }
1232
- };
1233
- const fetchFn = async () => {
1234
- const response = await client.getEntries(
1235
- {
1236
- content_type: fetchConfig.contentType,
1237
- ...fetchConfig.query,
1238
- include: 10,
1239
- locale: options?.locale,
1240
- limit: 1
1241
- },
1242
- requestOptions
1243
- );
1244
- const entry = response.items[0];
1245
- if (!entry || !entry.fields) {
1246
- return { data: null, errors: [] };
1247
- }
1248
- try {
1249
- const assets = convertAllAssets(response, context);
1250
- const rawAssets = convertAllRawAssets(response);
1251
- const includes = convertAllIncludes(response);
1252
- const fullContext = {
1253
- ...context,
1254
- includes,
1255
- assets,
1256
- rawAssets,
1257
- errors: []
1258
- };
1259
- const converted = fetchConfig.resolver(fullContext, entry);
1260
- if (converted && typeof converted === "object" && "icons" in converted) {
1261
- const icons = converted.icons;
1262
- if (icons && Array.isArray(icons) && icons.length > 0) {
1263
- const processedIcons = await processIconsForSprite(icons);
1264
- converted.icons = processedIcons.length > 0 ? processedIcons : void 0;
1265
- }
1266
- }
1267
- if (fullContext.errors.length > 0 && typeof process !== "undefined" && process.env?.NODE_ENV === "production") {
1268
- console.error(`CMS conversion errors for ${fetchConfig.contentType}:`, {
1269
- entryId: entry.sys.id,
1270
- ...fetchConfig.errorLogContext,
1271
- errors: fullContext.errors
1272
- });
1273
- }
1274
- return { data: converted, errors: fullContext.errors };
1275
- } catch (error) {
1276
- const entryId = entry.sys.id;
1277
- const entryType = entry.sys.contentType?.sys?.id;
1278
- const errorMessage = error instanceof Error ? error.message : "Unknown conversion error";
1279
- const cmsError = {
1280
- entryId,
1281
- entryType,
1282
- message: errorMessage,
1283
- error
1284
- };
1285
- return { data: null, errors: [cmsError] };
1286
- }
1287
- };
1288
- if (options?.retry) {
1289
- return await withRetry(fetchFn, options.retry);
1290
- }
1291
- return await fetchFn();
1292
- }
1293
- async function fetchAllLinks(contentType, client, requestOptions, converter, context, pageSize = 100, select) {
1294
- const allLinks = [];
1295
- const errors = [];
1296
- let skip = 0;
1297
- let hasMore = true;
1298
- const fetchFn = async () => {
1299
- while (hasMore) {
1300
- try {
1301
- const response = await client.getEntries(
1302
- {
1303
- content_type: contentType,
1304
- include: 2,
1305
- // Minimal include for link-only fetching
1306
- locale: requestOptions?.locale,
1307
- limit: pageSize,
1308
- skip,
1309
- ...select && { select }
1310
- },
1311
- requestOptions
1312
- );
1313
- if (response.items.length === 0) {
1314
- hasMore = false;
1315
- break;
1316
- }
1317
- const includes = convertAllIncludes(response);
1318
- const assets = convertAllAssets(response, context);
1319
- const rawAssets = convertAllRawAssets(response);
1320
- const fullContext = {
1321
- ...context,
1322
- includes,
1323
- assets,
1324
- rawAssets,
1325
- errors: []
1326
- };
1327
- for (const entry of response.items) {
1328
- if (!entry.fields) continue;
1329
- try {
1330
- const converted = converter(
1331
- fullContext,
1332
- entry
1333
- );
1334
- converted.lastModified = entry.sys.updatedAt ? new Date(entry.sys.updatedAt) : void 0;
1335
- allLinks.push(converted);
1336
- } catch (error) {
1337
- const entryId = entry.sys.id;
1338
- const entryType = entry.sys.contentType?.sys?.id;
1339
- const errorMessage = error instanceof Error ? error.message : "Unknown conversion error";
1340
- errors.push({
1341
- entryId,
1342
- entryType,
1343
- message: errorMessage,
1344
- error
1345
- });
1346
- }
1347
- }
1348
- skip += pageSize;
1349
- if (skip >= response.total) {
1350
- hasMore = false;
1351
- }
1352
- } catch (error) {
1353
- console.error("Error fetching links", typeof error, error, JSON.stringify(error, null, 2));
1354
- throw error;
1355
- }
1356
- }
1357
- return { data: allLinks, errors };
1358
- };
1359
- return await fetchFn();
1360
- }
1361
-
1362
- // src/api/article.ts
1363
- async function contentfulArticleRest(context, config, slug, articleTypeSlug, options) {
1364
- return fetchSingleEntity(
1365
- context,
1366
- config,
1367
- {
1368
- contentType: "article",
1369
- cacheTagType: "article",
1370
- cacheTagIdentifier: slug,
1371
- query: {
1372
- "fields.slug": slug,
1373
- "fields.articleType.sys.contentType.sys.id": "articleType",
1374
- "fields.articleType.fields.slug": articleTypeSlug
1375
- },
1376
- resolver: (ctx, entry) => ctx.articleResolver(ctx, entry),
1377
- errorLogContext: { slug, articleTypeSlug }
1378
- },
1379
- options
1380
- );
1381
- }
1382
-
1383
- // src/api/article-type.ts
1384
- init_utils();
1385
- async function contentfulArticleTypeRest(context, config, slug, options) {
1386
- const client = getContentfulClient(config, options.preview);
1387
- const articleTypeCacheTags = getCacheTags("articleType", slug, options?.preview);
1388
- const customTypeCacheTags = options?.customType ? getCacheTags("customType", options.customType, options?.preview) : [];
1389
- const requestOptions = {
1390
- ...options,
1391
- next: {
1392
- ...options?.next,
1393
- tags: [...articleTypeCacheTags, ...customTypeCacheTags]
1394
- }
1395
- };
1396
- const fetchFn = async () => {
1397
- const articleTypePromise = client.getEntries(
1398
- {
1399
- content_type: "articleType",
1400
- "fields.slug": slug,
1401
- include: 10,
1402
- locale: options?.locale,
1403
- limit: 1
1404
- },
1405
- requestOptions
1406
- );
1407
- const customTypePromise = options?.customType ? client.getEntries(
1408
- {
1409
- content_type: "customType",
1410
- "fields.slug": options.customType,
1411
- include: 10,
1412
- locale: options?.locale,
1413
- limit: 1
1414
- },
1415
- requestOptions
1416
- ) : Promise.resolve(null);
1417
- const [articleTypeResponse, customTypeResponse] = await Promise.all([
1418
- articleTypePromise,
1419
- customTypePromise
1420
- ]);
1421
- const articleTypeEntry = articleTypeResponse.items[0];
1422
- if (!articleTypeEntry || !articleTypeEntry.fields) {
1423
- return { data: null, errors: [] };
1424
- }
1425
- const customTypeEntry = customTypeResponse?.items[0];
1426
- try {
1427
- const assets = convertAllAssets(articleTypeResponse, context);
1428
- const rawAssets = convertAllRawAssets(articleTypeResponse);
1429
- const includes = convertAllIncludes(articleTypeResponse);
1430
- if (customTypeResponse) {
1431
- const customAssets = convertAllAssets(customTypeResponse, context);
1432
- const customRawAssets = convertAllRawAssets(customTypeResponse);
1433
- const customIncludes = convertAllIncludes(customTypeResponse);
1434
- for (const [key, value] of customAssets) assets.set(key, value);
1435
- for (const [key, value] of customRawAssets) rawAssets.set(key, value);
1436
- for (const [key, value] of customIncludes) includes.set(key, value);
1437
- }
1438
- const fullContext = {
1439
- ...context,
1440
- includes,
1441
- assets,
1442
- rawAssets,
1443
- errors: []
1444
- };
1445
- const converted = context.articleTypeResolver(fullContext, articleTypeEntry, customTypeEntry);
1446
- if (converted?.icons && converted.icons.length > 0) {
1447
- const processedIcons = await processIconsForSprite(converted.icons);
1448
- converted.icons = processedIcons.length > 0 ? processedIcons : void 0;
1449
- }
1450
- if (fullContext.errors.length > 0 && typeof process !== "undefined" && process.env?.NODE_ENV === "production") {
1451
- console.error(`CMS conversion errors for articleType:`, {
1452
- entryId: articleTypeEntry.sys.id,
1453
- slug,
1454
- errors: fullContext.errors
1455
- });
1456
- }
1457
- return { data: converted, errors: fullContext.errors };
1458
- } catch (error) {
1459
- const entryId = articleTypeEntry?.sys.id || "unknown";
1460
- const entryType = articleTypeEntry?.sys.contentType?.sys?.id;
1461
- const errorMessage = error instanceof Error ? error.message : "Unknown conversion error";
1462
- const cmsError = {
1463
- entryId,
1464
- entryType,
1465
- message: errorMessage,
1466
- error
1467
- };
1468
- return { data: null, errors: [cmsError] };
1469
- }
1470
- };
1471
- if (options?.retry) {
1472
- return await withRetry(fetchFn, options.retry);
1473
- }
1474
- return await fetchFn();
1475
- }
1476
-
1477
- // src/api/asset.ts
1478
- init_utils();
1479
- var BROWSER_VIEWABLE_TYPES = [
1480
- "application/pdf",
1481
- "text/plain",
1482
- "text/html",
1483
- "text/css",
1484
- "text/javascript",
1485
- "application/json"
1486
- ];
1487
- function isBrowserViewable(contentType) {
1488
- if (contentType.startsWith("image/") || contentType.startsWith("video/")) {
1489
- return true;
1490
- }
1491
- return BROWSER_VIEWABLE_TYPES.includes(contentType);
1492
- }
1493
- async function contentfulAssetRest(config, assetId, options) {
1494
- const client = getContentfulClient(config, options.preview);
1495
- const cacheTags = getCacheTags("asset", assetId, options?.preview);
1496
- return client.getAsset(assetId, {
1497
- ...options,
1498
- next: {
1499
- revalidate: options?.next?.revalidate ?? 86400,
1500
- // Default 24 hours
1501
- tags: [...cacheTags, ...options?.next?.tags ?? []]
1502
- }
1503
- });
1504
- }
1505
- function createDownloadHandler(config) {
1506
- const { getConfig, revalidate = 86400, preview = false } = config;
1507
- return async function GET(_request, { params }) {
1508
- const { assetId, filename } = await params;
1509
- const decodedFilename = decodeURIComponent(filename);
1510
- try {
1511
- const contentfulConfig = getConfig(preview);
1512
- const asset = await contentfulAssetRest(contentfulConfig, assetId, {
1513
- preview,
1514
- next: { revalidate }
1515
- });
1516
- if (!asset?.fields?.file) {
1517
- return Response.json({ error: "Asset not found" }, { status: 404 });
1518
- }
1519
- const { file } = asset.fields;
1520
- const assetUrl = `https:${file.url}`;
1521
- const cacheTags = getCacheTags("asset", assetId, preview);
1522
- const fileFetchOptions = {
1523
- next: {
1524
- revalidate,
1525
- tags: cacheTags
1526
- }
1527
- };
1528
- const fileResponse = await fetch(assetUrl, fileFetchOptions);
1529
- if (!fileResponse.ok) {
1530
- return Response.json({ error: "Failed to fetch file" }, { status: 502 });
1531
- }
1532
- const headers = new Headers();
1533
- headers.set("Content-Type", file.contentType);
1534
- const disposition = isBrowserViewable(file.contentType) ? "inline" : "attachment";
1535
- headers.set("Content-Disposition", `${disposition}; filename="${decodedFilename}"`);
1536
- if (file.details?.size) {
1537
- headers.set("Content-Length", String(file.details.size));
1538
- }
1539
- headers.set("Cache-Control", `public, max-age=${revalidate}, s-maxage=${revalidate}`);
1540
- return new Response(fileResponse.body, {
1541
- status: 200,
1542
- headers
1543
- });
1544
- } catch (error) {
1545
- console.error("[Download] Error proxying file:", error);
1546
- return Response.json({ error: "Internal server error" }, { status: 500 });
1547
- }
1548
- };
1549
- }
1550
-
1551
- // src/converters/schema.ts
1552
- function baseSchemaConverter(_context, entry) {
1553
- const { sys, fields } = entry;
1554
- const { cmsLabel, markup } = fields;
1555
- return {
1556
- id: sys.id,
1557
- name: cmsLabel,
1558
- markup: markup ?? "",
1559
- description: `Schema for ${cmsLabel}`
1560
- };
1561
- }
1562
-
1563
- // src/converters/resolver.ts
1564
- function resolveHelper(context, fromId, entry, getResolver) {
1565
- const id = entry.sys.id;
1566
- const possibleEntry = context.includes.get(id);
1567
- if (!possibleEntry) {
1568
- throw new Error(`Cannot find included entry for link from ${fromId} with id ${id}`);
1569
- }
1570
- if (!possibleEntry.resolved) {
1571
- const resolver = getResolver(possibleEntry.type);
1572
- if (!resolver) {
1573
- throw new Error(
1574
- `No resolver found for link type ${possibleEntry.type} (${JSON.stringify(possibleEntry)}) [${JSON.stringify(entry)}]`
1575
- );
1576
- }
1577
- if (typeof resolver !== "function") {
1578
- console.log("Resolver type", possibleEntry.type, typeof resolver, resolver);
1579
- }
1580
- const resolved = resolver(
1581
- context,
1582
- possibleEntry.entry
1583
- );
1584
- possibleEntry.resolved = resolved;
1585
- return resolved;
1586
- }
1587
- return possibleEntry.resolved;
1588
- }
1589
- function resolveSchema(context, fromId, entry) {
1590
- return resolveHelper(
1591
- context,
1592
- fromId,
1593
- entry,
1594
- () => baseSchemaConverter
1595
- );
1596
- }
1597
- function resolveLink(context, fromId, entry) {
1598
- return resolveHelper(
1599
- context,
1600
- fromId,
1601
- entry,
1602
- (type) => context.linkResolver.get(type)
1603
- );
1604
- }
1605
- function resolveLinks(context, fromId, entries) {
1606
- return arrayOrUndefined(entries?.map((entry) => resolveLink(context, fromId, entry)));
1607
- }
1608
- function resolveContent(context, fromId, entry) {
1609
- return resolveHelper(context, fromId, entry, (type) => {
1610
- const resolver = context.contentResolver.get(type);
1611
- return resolver;
1612
- });
1613
- }
1614
- function resolveNavigationItem(context, fromId, entry) {
1615
- const result = resolveHelper(
1616
- context,
1617
- fromId,
1618
- entry,
1619
- () => context.navigationItemResolver
1620
- );
1621
- return result;
1622
- }
1623
- function resolveCollectionContent(context, fromId, entry) {
1624
- const id = entry.sys.id;
1625
- const possibleEntry = context.includes.get(id);
1626
- if (!possibleEntry) {
1627
- throw new Error(
1628
- `Cannot find included entry for collection content from ${fromId} with id ${id}`
1629
- );
1630
- }
1631
- if (possibleEntry.resolved) {
1632
- return possibleEntry.resolved;
1633
- }
1634
- const { type } = possibleEntry;
1635
- if (type === "media") {
1636
- const visual = convertMediaEntryToVisual(
1637
- context,
1638
- possibleEntry.entry
1639
- );
1640
- if (!visual) {
1641
- throw new Error(`Failed to convert media entry with id ${id}`);
1642
- }
1643
- possibleEntry.resolved = visual;
1644
- return visual;
1645
- }
1646
- if (type === "externalVideo") {
1647
- const visual = convertExternalVideoEntryToVisual(
1648
- context,
1649
- possibleEntry.entry
1650
- );
1651
- if (!visual) {
1652
- throw new Error(`Failed to convert externalVideo entry with id ${id}`);
1653
- }
1654
- possibleEntry.resolved = visual;
1655
- return visual;
1656
- }
1657
- return resolveHelper(
1658
- context,
1659
- fromId,
1660
- entry,
1661
- (resolverType) => {
1662
- const resolver = context.contentResolver.get(resolverType);
1663
- if (resolver) {
1664
- return resolver;
1665
- }
1666
- const linkResolver = context.linkResolver.get(resolverType);
1667
- if (linkResolver) {
1668
- return linkResolver;
1669
- }
1670
- return void 0;
1671
- }
1672
- );
1673
- }
1674
- function resolvePageContent(context, fromId, entry) {
1675
- const id = entry.sys.id;
1676
- const possibleEntry = context.includes.get(id);
1677
- if (!possibleEntry) {
1678
- const errorMessage = `Cannot find included entry for content from ${fromId} with id ${id}`;
1679
- const cmsError = {
1680
- entryId: id,
1681
- entryType: "unknown",
1682
- message: errorMessage,
1683
- error: new Error(errorMessage)
1684
- };
1685
- context.errors.push(cmsError);
1686
- console.error(errorMessage);
1687
- return null;
1688
- }
1689
- const { type } = possibleEntry;
1690
- if (type === "media") {
1691
- try {
1692
- const visual = convertMediaEntryToVisual(
1693
- context,
1694
- possibleEntry.entry
1695
- );
1696
- if (!visual) {
1697
- const errorMessage = `Failed to convert media entry with id ${id}`;
1698
- const cmsError = {
1699
- entryId: id,
1700
- entryType: type,
1701
- message: errorMessage,
1702
- error: new Error(errorMessage)
1703
- };
1704
- context.errors.push(cmsError);
1705
- console.error(errorMessage);
1706
- return null;
1707
- }
1708
- return visual;
1709
- } catch (error) {
1710
- const errorMessage = `Error converting media entry with id ${id}: ${error instanceof Error ? error.message : "Unknown error"}`;
1711
- const cmsError = {
1712
- entryId: id,
1713
- entryType: type,
1714
- message: errorMessage,
1715
- error
1716
- };
1717
- context.errors.push(cmsError);
1718
- console.error(errorMessage);
1719
- return null;
1720
- }
1721
- }
1722
- if (type === "externalVideo") {
1723
- try {
1724
- const visual = convertExternalVideoEntryToVisual(
1725
- context,
1726
- possibleEntry.entry
1727
- );
1728
- if (!visual) {
1729
- const errorMessage = `Failed to convert externalVideo entry with id ${id}`;
1730
- const cmsError = {
1731
- entryId: id,
1732
- entryType: type,
1733
- message: errorMessage,
1734
- error: new Error(errorMessage)
1735
- };
1736
- context.errors.push(cmsError);
1737
- console.error(errorMessage);
1738
- return null;
1739
- }
1740
- return visual;
1741
- } catch (error) {
1742
- const errorMessage = `Error converting externalVideo entry with id ${id}: ${error instanceof Error ? error.message : "Unknown error"}`;
1743
- const cmsError = {
1744
- entryId: id,
1745
- entryType: type,
1746
- message: errorMessage,
1747
- error
1748
- };
1749
- context.errors.push(cmsError);
1750
- console.error(errorMessage);
1751
- return null;
1752
- }
1753
- }
1754
- try {
1755
- if (context.contentResolver.has(type)) {
1756
- return resolveContent(context, fromId, entry);
1757
- }
1758
- if (context.linkResolver.has(type)) {
1759
- return resolveLink(context, fromId, entry);
1760
- }
1761
- const errorMessage = `Unknown content type "${type}" for entry with id ${id}`;
1762
- const cmsError = {
1763
- entryId: id,
1764
- entryType: type,
1765
- message: errorMessage,
1766
- error: new Error(errorMessage)
1767
- };
1768
- context.errors.push(cmsError);
1769
- console.error(errorMessage);
1770
- return null;
1771
- } catch (error) {
1772
- const errorMessage = `Error resolving content entry with id ${id} of type ${type}: ${error instanceof Error ? error.message : "Unknown error"}`;
1773
- const cmsError = {
1774
- entryId: id,
1775
- entryType: type,
1776
- message: errorMessage,
1777
- error
1778
- };
1779
- context.errors.push(cmsError);
1780
- console.error(errorMessage);
1781
- return null;
1782
- }
1783
- }
1784
- function resolveRichTextDocument(context, fromId, richText) {
1785
- if (!richText) {
1786
- return void 0;
1787
- }
1788
- const resolvingEntries = /* @__PURE__ */ new Set();
1789
- const resolvedEntries = /* @__PURE__ */ new Set();
1790
- const resolveNode = (node) => {
1791
- if (!("content" in node)) {
1792
- return node;
1793
- }
1794
- const block = node;
1795
- if (block.data.target && "sys" in block.data.target && block.data.target.sys.linkType === "Entry") {
1796
- const entryId = block.data.target.sys.id;
1797
- if (resolvingEntries.has(entryId)) {
1798
- console.warn(
1799
- `Circular reference detected in rich text resolution for entry ${entryId}. Skipping to prevent infinite recursion.`
1800
- );
1801
- return {
1802
- ...block,
1803
- content: block.content?.map(resolveNode)
1804
- };
1805
- }
1806
- if (resolvedEntries.has(entryId)) {
1807
- return {
1808
- ...block,
1809
- content: block.content?.map(resolveNode)
1810
- };
1811
- }
1812
- try {
1813
- resolvingEntries.add(entryId);
1814
- const resolvedEntry = resolvePageContent(
1815
- context,
1816
- fromId,
1817
- block.data.target
1818
- );
1819
- resolvingEntries.delete(entryId);
1820
- resolvedEntries.add(entryId);
1821
- if (resolvedEntry === null) {
1822
- return {
1823
- ...block,
1824
- content: block.content?.map(resolveNode)
1825
- };
1826
- }
1827
- return {
1828
- ...block,
1829
- data: {
1830
- ...block.data,
1831
- target: resolvedEntry
1832
- },
1833
- content: block.content?.map(resolveNode)
1834
- };
1835
- } catch (error) {
1836
- resolvingEntries.delete(entryId);
1837
- console.error(`Failed to resolve entry with id ${entryId}:`, error);
1838
- return {
1839
- ...block,
1840
- content: block.content?.map(resolveNode)
1841
- };
1842
- }
1843
- }
1844
- if (block.data.target && "sys" in block.data.target && block.data.target.sys.linkType === "Asset") {
1845
- try {
1846
- const resolvedAsset = lookupAsset(context, block.data.target);
1847
- if (resolvedAsset) {
1848
- return {
1849
- ...block,
1850
- data: {
1851
- ...block.data,
1852
- target: resolvedAsset
1853
- },
1854
- content: block.content?.map(resolveNode)
1855
- };
1856
- }
1857
- } catch (error) {
1858
- console.error(
1859
- `Failed to resolve embedded-asset-block with id ${block.data.target.sys.id}:`,
1860
- error
1861
- );
1862
- return {
1863
- ...block,
1864
- content: block.content?.map(resolveNode)
1865
- };
1866
- }
1867
- }
1868
- return {
1869
- ...block,
1870
- content: block.content?.map(resolveNode)
1871
- };
1872
- };
1873
- const resolved = resolveNode(richText);
1874
- return { json: resolved };
1875
- }
1876
-
1877
- // src/converters/navigationItem.ts
1878
- function createLink(context, entry) {
1879
- const {
1880
- sys: { id },
1881
- fields
1882
- } = entry;
1883
- const {
1884
- title,
1885
- link,
1886
- text,
1887
- internal,
1888
- icon: navIcon,
1889
- mobileIcon: mobileNavIcon,
1890
- useTitle,
1891
- ...otherFields
1892
- } = fields;
1893
- const icon = createResponsiveVisual(
1894
- lookupIconAsset(context, navIcon),
1895
- lookupIconAsset(context, mobileNavIcon)
1896
- );
1897
- const realText = useTitle ? resolveBuildYear(title) : text ? resolveBuildYear(text) : void 0;
1898
- if (link) {
1899
- return {
1900
- type: "External link",
1901
- id,
1902
- href: link,
1903
- icon,
1904
- name: makeContentfulTitle(title, id),
1905
- ...otherFields,
1906
- text: realText
1907
- };
1908
- }
1909
- if (internal) {
1910
- const resolved = resolveLink(context, id, internal);
1911
- return { ...resolved, icon, id, text: realText };
1912
- }
1913
- const blank = {
1914
- type: "Blank link",
1915
- nick: true,
1916
- id,
1917
- name: makeContentfulTitle(title, id),
1918
- ...otherFields,
1919
- icon,
1920
- text: realText
1921
- };
1922
- return blank;
1923
- }
1924
- function baseNavigationItemConverter(context, entry) {
1925
- const {
1926
- sys: { id },
1927
- fields
1928
- } = entry;
1929
- const { navigationItems } = fields;
1930
- const link = createLink(context, entry);
1931
- const resolvedNavigationItems = navigationItems?.map((item) => resolveNavigationItem(context, id, item)).filter((item) => item !== void 0);
1932
- const result = {
1933
- id,
1934
- link,
1935
- entries: resolvedNavigationItems
1936
- };
1937
- return result;
1938
- }
1939
- function resolveNavigation(context, link) {
1940
- const id = link.sys.id;
1941
- const possibleEntry = context.includes.get(id);
1942
- if (!possibleEntry || possibleEntry.type !== "navigation") {
1943
- return void 0;
1944
- }
1945
- const entry = possibleEntry.entry;
1946
- const { fields } = entry;
1947
- if (!fields) {
1948
- return void 0;
1949
- }
1950
- const { name, entries: fieldEntries, ...rest } = fields;
1951
- const entries = fieldEntries?.map((item) => resolveNavigationItem(context, id, item)) ?? [];
1952
- return {
1953
- id,
1954
- name,
1955
- entries,
1956
- ...rest
1957
- };
1958
- }
1959
-
1960
- // src/converters/template.ts
1961
- function resolveTemplate(context, link) {
1962
- const id = link.sys.id;
1963
- const possibleEntry = context.includes.get(id);
1964
- if (!possibleEntry || possibleEntry.type !== "template") {
1965
- return null;
1966
- }
1967
- const entry = possibleEntry.entry;
1968
- const { fields } = entry;
1969
- if (!fields) {
1970
- return null;
1971
- }
1972
- const preContent = fields.preContent?.map((content) => resolvePageContent(context, id, content)).filter((item) => item !== null) ?? [];
1973
- const postContent = fields.postContent?.map((content) => resolvePageContent(context, id, content)).filter((item) => item !== null) ?? [];
1974
- const menu = fields.menu ? resolveNavigation(context, fields.menu) : void 0;
1975
- const footer = fields.footer ? resolveNavigation(context, fields.footer) : void 0;
1976
- const stickyNav = fields.flags?.includes("Sticky nav") ?? false;
1977
- const { backgroundColour, textColour } = fields;
1978
- return {
1979
- preContent,
1980
- postContent,
1981
- menu,
1982
- footer,
1983
- backgroundColour,
1984
- textColour,
1985
- stickyNav
1986
- };
1987
- }
1988
-
1989
- // src/converters/article.ts
1990
- function resolveArticleTemplateHierarchy(context, articleTypeLink, directTemplateLink) {
1991
- if (directTemplateLink) {
1992
- return resolveTemplate(context, directTemplateLink);
1993
- }
1994
- const articleTypeEntry = context.includes.get(articleTypeLink.sys.id);
1995
- if (!articleTypeEntry) {
1996
- console.warn(`ArticleType entry not found for id: ${articleTypeLink.sys.id}`);
1997
- return null;
1998
- }
1999
- const articleTypeFields = articleTypeEntry.entry.fields;
2000
- if (articleTypeFields?.articleTemplate) {
2001
- return resolveTemplate(context, articleTypeFields.articleTemplate);
2002
- }
2003
- return null;
2004
- }
2005
- function baseArticleConverter(context, entry) {
2006
- const { sys, fields } = entry;
2007
- const { id } = sys;
2008
- const {
2009
- slug,
2010
- title,
2011
- description,
2012
- featuredImage,
2013
- tags,
2014
- content,
2015
- template: templateLink,
2016
- topContent: topContentLinks,
2017
- bottomContent: bottomContentLinks,
2018
- articleType,
2019
- structuredData,
2020
- ...simpleFields
2021
- } = fields;
2022
- const articleTypeLink = resolveLink(context, id, articleType);
2023
- const template = resolveArticleTemplateHierarchy(context, articleType, templateLink);
2024
- const topContent = topContentLinks?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
2025
- const articleContent = content?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
2026
- const bottomContent = bottomContentLinks?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
2027
- const preContent = template?.preContent ?? [];
2028
- const postContent = template?.postContent ?? [];
2029
- const contents = addPositionMetadata([
2030
- ...topContent,
2031
- ...preContent,
2032
- ...articleContent,
2033
- ...postContent,
2034
- ...bottomContent
2035
- ]);
2036
- const finalMenu = template?.menu;
2037
- const finalFooter = template?.footer;
2038
- const icons = deduplicateIcons(
2039
- collectIconsFromContent(contents),
2040
- collectIconsFromNavigation(finalMenu),
2041
- collectIconsFromNavigation(finalFooter)
2042
- );
2043
- const articleTitle = makeContentfulTitle(title, sys.id);
2044
- const article = {
2045
- type: "Article",
2046
- id,
2047
- slug,
2048
- name: articleTitle,
2049
- title: articleTitle,
2050
- description: makeContentfulDescription(description, sys.id),
2051
- featuredImage: lookupAsset(context, featuredImage),
2052
- contentCount: calculateContentCount(topContent, articleContent, bottomContent),
2053
- articleType: articleTypeLink,
2054
- tags: tags?.map((tag) => resolveLink(context, id, tag)),
2055
- contents,
2056
- icons,
2057
- structuredData: structuredData?.map((link) => resolveSchema(context, id, link)).filter((item) => item !== null),
2058
- ...simpleFields,
2059
- // Note: summary field exists in Contentful but is not part of IArticle interface
2060
- // Keeping it in simpleFields for potential future use
2061
- menu: finalMenu,
2062
- footer: finalFooter
2063
- };
2064
- return article;
2065
- }
2066
- function baseArticleLinkConverter(context, entry) {
2067
- const { sys, fields } = entry;
2068
- if (sys.contentType.sys.id !== "article") {
2069
- throw new Error(`Invalid content type: expected "article", got "${sys.contentType.sys.id}"`);
2070
- }
2071
- const {
2072
- articleType: fieldsArticleType,
2073
- tags: fieldsTags,
2074
- author: fieldsAuthor,
2075
- date,
2076
- externalLink,
2077
- download: fieldsDownload,
2078
- ...simpleFields
2079
- } = fields;
2080
- const articleType = resolveLink(context, sys.id, fieldsArticleType);
2081
- const tags = fieldsTags?.map((tag) => resolveLink(context, sys.id, tag));
2082
- const primaryTag = tags?.[0] ?? void 0;
2083
- const author = fieldsAuthor ? resolveLink(context, sys.id, fieldsAuthor) : void 0;
2084
- const download = lookupDownloadAsset(context, fieldsDownload);
2085
- const href = externalLink ? externalLink : context.urlCalculators.article(articleType.slug, fields.slug, primaryTag?.slug);
2086
- return createInternalLink(
2087
- sys.id,
2088
- { ...simpleFields, name: fields.cmsLabel },
2089
- context,
2090
- href,
2091
- "Article",
2092
- {
2093
- tags,
2094
- primaryTag,
2095
- articleType,
2096
- date,
2097
- author,
2098
- download
2099
- }
2100
- );
2101
- }
2102
- function baseArticleTypeLinkConverter(context, entry) {
2103
- const { sys, fields } = entry;
2104
- if (sys.contentType.sys.id !== "articleType") {
2105
- throw new Error(
2106
- `Invalid content type: expected "articleType", got "${sys.contentType.sys.id}"`
2107
- );
2108
- }
2109
- const { name, cmsLabel, featuredImage, slug, ...simpleFields } = fields;
2110
- return createInternalLink(
2111
- sys.id,
2112
- {
2113
- cmsLabel,
2114
- name,
2115
- title: name,
2116
- featuredImage,
2117
- slug,
2118
- ...simpleFields
2119
- },
2120
- context,
2121
- context.urlCalculators.articleType(slug),
2122
- "ArticleType"
2123
- );
2124
- }
2125
- function baseArticleTypeConverter(context, entry, customTypeEntry) {
2126
- const { sys, fields } = entry;
2127
- const { id } = sys;
2128
- const {
2129
- name,
2130
- indexPageDescription,
2131
- indexPageTitle,
2132
- featuredImage,
2133
- menu: menuLink,
2134
- footer: footerLink,
2135
- indexPageTemplate: templateLink,
2136
- indexPageTopContent: topContentLinks,
2137
- structuredData,
2138
- indexPageStructuredData,
2139
- slug,
2140
- ...other
2141
- } = fields;
2142
- const customType = customTypeEntry ? context.customTypeResolver(context, customTypeEntry) : void 0;
2143
- let template = templateLink ? resolveTemplate(context, templateLink) : null;
2144
- if (!template && customTypeEntry?.fields.template) {
2145
- template = resolveTemplate(context, customTypeEntry.fields.template);
2146
- }
2147
- const menu = menuLink ? resolveNavigation(context, menuLink) : customTypeEntry?.fields.menu ? resolveNavigation(context, customTypeEntry.fields.menu) : template?.menu;
2148
- const footer = footerLink ? resolveNavigation(context, footerLink) : customTypeEntry?.fields.footer ? resolveNavigation(context, customTypeEntry.fields.footer) : template?.footer;
2149
- const topContent = topContentLinks?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
2150
- const preContent = template?.preContent ?? [];
2151
- const postContent = template?.postContent ?? [];
2152
- const contents = addPositionMetadata([...topContent, ...preContent, ...postContent]);
2153
- const finalMenu = menu;
2154
- const finalFooter = footer;
2155
- const icons = deduplicateIcons(
2156
- collectIconsFromContent(contents),
2157
- collectIconsFromNavigation(finalMenu),
2158
- collectIconsFromNavigation(finalFooter)
2159
- );
2160
- const articleType = {
2161
- type: "Article type",
2162
- id,
2163
- slug,
2164
- name,
2165
- title: makeContentfulTitle(indexPageTitle, sys.id),
2166
- description: makeContentfulDescription(indexPageDescription, sys.id),
2167
- featuredImage: lookupAsset(context, featuredImage),
2168
- contentCount: 0,
2169
- contents,
2170
- icons,
2171
- structuredData: [
2172
- ...structuredData?.map((link) => resolveSchema(context, id, link)) ?? [],
2173
- ...indexPageStructuredData?.map((link) => resolveSchema(context, id, link)) ?? []
2174
- ].filter((item) => item !== null),
2175
- menu: finalMenu,
2176
- footer: finalFooter,
2177
- ...other
2178
- };
2179
- if (customType) {
2180
- articleType.customType = customType;
2181
- }
2182
- return articleType;
2183
- }
2184
-
2185
- // src/converters/collection.ts
2186
- function baseCollectionConverter(context, entry) {
2187
- const { sys, fields } = entry;
2188
- const { id } = sys;
2189
- const {
2190
- // Fields requiring transformation
2191
- backgroundVisual: bgVisual,
2192
- mobileBackgroundVisual: mobileBgVisual,
2193
- visual: visualField,
2194
- mobileVisual: mobileVisualField,
2195
- visualCustomSize,
2196
- icon: iconField,
2197
- links: linksField,
2198
- contents: contentsField,
2199
- body: bodyField,
2200
- additionalCopy: additionalCopyField,
2201
- showHeading,
2202
- heading,
2203
- // Already handled elsewhere
2204
- cmsLabel,
2205
- ...simpleFields
2206
- // anchor, backgroundColour, textColour, preHeading, heading, postHeading, backgroundOverlayOpacity
2207
- } = fields;
2208
- const backgroundVisual = createResponsiveVisual(
2209
- lookupAsset(context, bgVisual),
2210
- lookupAsset(context, mobileBgVisual)
2211
- );
2212
- const visual = createResponsiveVisual(
2213
- lookupMediaEntry(context, visualField),
2214
- lookupMediaEntry(context, mobileVisualField),
2215
- visualCustomSize
2216
- );
2217
- const collection = {
2218
- type: "Collection",
2219
- id,
2220
- name: cmsLabel,
2221
- cmsLabel,
2222
- ...DEFAULT_POSITION_FIELDS,
2223
- ...simpleFields,
2224
- heading: showHeading ? heading : void 0,
2225
- body: resolveRichTextDocument(context, id, bodyField),
2226
- additionalCopy: resolveRichTextDocument(context, id, additionalCopyField),
2227
- icon: lookupIconAsset(context, iconField),
2228
- backgroundVisual,
2229
- visual,
2230
- links: linksField?.map((link) => resolveLink(context, id, link)),
2231
- contents: contentsField?.map((content) => resolveCollectionContent(context, id, content))
2232
- };
2233
- return collection;
2234
- }
2235
-
2236
- // src/converters/component.ts
2237
- function baseComponentConverter(context, entry) {
2238
- const { sys, fields } = entry;
2239
- const { id } = sys;
2240
- const {
2241
- // Fields requiring transformation
2242
- backgroundVisual: bgVisual,
2243
- mobileBackgroundVisual: mobileBgVisual,
2244
- visual: visualField,
2245
- mobileVisual: mobileVisualField,
2246
- visualCustomSize,
2247
- icon: iconField,
2248
- links: linksField,
2249
- body: bodyField,
2250
- additionalCopy: additionalCopyField,
2251
- // Field name change
2252
- showHeading,
2253
- heading,
2254
- otherMedia: otherMediaField,
2255
- // Already handled elsewhere
2256
- cmsLabel,
2257
- ...simpleFields
2258
- // anchor, backgroundColour, textColour, preHeading, heading, postHeading, backgroundOverlayOpacity
2259
- } = fields;
2260
- const backgroundVisual = createResponsiveVisual(
2261
- lookupAsset(context, bgVisual),
2262
- lookupAsset(context, mobileBgVisual)
2263
- );
2264
- const visual = createResponsiveVisual(
2265
- lookupMediaEntry(context, visualField),
2266
- lookupMediaEntry(context, mobileVisualField),
2267
- visualCustomSize
2268
- );
2269
- const otherMedia = otherMediaField?.map((media) => lookupAsset(context, media));
2270
- const component = {
2271
- type: "Component",
2272
- id,
2273
- name: cmsLabel,
2274
- cmsLabel,
2275
- ...DEFAULT_POSITION_FIELDS,
2276
- ...simpleFields,
2277
- heading: showHeading ? heading : void 0,
2278
- body: resolveRichTextDocument(context, id, bodyField),
2279
- additionalCopy: resolveRichTextDocument(context, id, additionalCopyField),
2280
- icon: lookupIconAsset(context, iconField),
2281
- backgroundVisual,
2282
- visual,
2283
- links: resolveLinks(context, id, linksField),
2284
- otherMedia: arrayOrUndefined(otherMedia?.filter(notEmpty))
2285
- };
2286
- return component;
2287
- }
2288
-
2289
- // src/converters/customType.ts
2290
- function baseCustomTypeConverter(context, entry) {
2291
- const { sys, fields } = entry;
2292
- const { id } = sys;
2293
- const {
2294
- name,
2295
- slug,
2296
- indexPageTitle,
2297
- indexPageDescription,
2298
- featuredImage,
2299
- menu: menuLink,
2300
- footer: footerLink,
2301
- indexPageTemplate: templateLink,
2302
- indexPageTopContent: topContentLinks,
2303
- structuredData,
2304
- indexPageStructuredData,
2305
- ...other
2306
- } = fields;
2307
- const template = templateLink ? resolveTemplate(context, templateLink) : null;
2308
- const menu = menuLink ? resolveNavigation(context, menuLink) : template?.menu;
2309
- const footer = footerLink ? resolveNavigation(context, footerLink) : template?.footer;
2310
- const topContent = topContentLinks?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
2311
- const preContent = template?.preContent ?? [];
2312
- const postContent = template?.postContent ?? [];
2313
- const contents = addPositionMetadata([...topContent, ...preContent, ...postContent]);
2314
- const icons = deduplicateIcons(
2315
- collectIconsFromContent(contents),
2316
- collectIconsFromNavigation(menu),
2317
- collectIconsFromNavigation(footer)
2318
- );
2319
- const title = makeContentfulTitle(indexPageTitle, sys.id);
2320
- const description = makeContentfulDescription(indexPageDescription, sys.id);
2321
- const customType = {
2322
- type: "Custom type",
2323
- id,
2324
- slug,
2325
- name,
2326
- title,
2327
- description,
2328
- featuredImage: lookupAsset(context, featuredImage),
2329
- contentCount: 0,
2330
- contents,
2331
- icons,
2332
- structuredData: [
2333
- ...structuredData?.map((link) => resolveSchema(context, id, link)) ?? [],
2334
- ...indexPageStructuredData?.map((link) => resolveSchema(context, id, link)) ?? []
2335
- ].filter((item) => item !== null),
2336
- menu,
2337
- footer,
2338
- ...other
2339
- };
2340
- return customType;
2341
- }
2342
- function baseCustomTypeLinkConverter(context, entry) {
2343
- const {
2344
- sys: { id, contentType },
2345
- fields
2346
- } = entry;
2347
- if (contentType.sys.id !== "customType") {
2348
- throw new Error(`Invalid content type: expected "customType", got "${contentType.sys.id}"`);
2349
- }
2350
- return createInternalLink(
2351
- id,
2352
- fields,
2353
- context,
2354
- context.urlCalculators.customType(fields.slug),
2355
- "CustomType"
2356
- );
2357
- }
2358
-
2359
- // src/converters/externalComponent.ts
2360
- function baseExternalComponentConverter(context, entry) {
2361
- const { sys, fields } = entry;
2362
- const {
2363
- cmsLabel,
2364
- backgroundVisual: bgVisual,
2365
- mobileBackgroundVisual: mobileBgVisual,
2366
- ...simpleFields
2367
- } = fields;
2368
- const backgroundVisual = createResponsiveVisual(
2369
- lookupAsset(context, bgVisual),
2370
- lookupAsset(context, mobileBgVisual)
2371
- );
2372
- const externalComponent = {
2373
- type: "External component",
2374
- id: sys.id,
2375
- name: makeContentfulTitle(cmsLabel, sys.id),
2376
- cmsLabel,
2377
- ...DEFAULT_POSITION_FIELDS,
2378
- ...simpleFields,
2379
- backgroundVisual
2380
- };
2381
- return externalComponent;
2382
- }
2383
-
2384
- // src/converters/link.ts
2385
- function baseLinkConverter(context, entry) {
2386
- const { sys, fields } = entry;
2387
- if (sys.contentType.sys.id !== "link") {
2388
- throw new Error(`Invalid content type: expected "link", got "${sys.contentType.sys.id}"`);
2389
- }
2390
- const id = sys.id;
2391
- const name = fields.name;
2392
- const useName = fields.useName;
2393
- const text = resolveBuildYear(
2394
- useName ? name : fields.linkText ?? makeContentfulTitle(fields.linkText, id, "Link text for ")
2395
- );
2396
- const icon = createResponsiveVisual(
2397
- lookupIconAsset(context, fields.icon),
2398
- lookupIconAsset(context, fields.mobileIcon)
2399
- );
2400
- const backgroundColour = fields.backgroundColour ?? null;
2401
- const textColour = fields.textColour ?? null;
2402
- const variant = fields.variant;
2403
- const size = fields.size;
2404
- const baseProps = {
2405
- id,
2406
- useName,
2407
- name,
2408
- text,
2409
- icon,
2410
- backgroundColour,
2411
- textColour,
2412
- variant,
2413
- size
2414
- };
2415
- if (fields.internal) {
2416
- const internalTarget = resolveLink(context, id, fields.internal);
2417
- return {
2418
- ...baseProps,
2419
- type: "Internal link",
2420
- internalType: internalTarget.internalType,
2421
- href: internalTarget.href,
2422
- slug: internalTarget.slug,
2423
- indexed: internalTarget.indexed,
2424
- hidden: internalTarget.hidden,
2425
- tags: internalTarget.tags,
2426
- title: internalTarget.title,
2427
- description: internalTarget.description
2428
- };
2429
- }
2430
- if (fields.external) {
2431
- return {
2432
- ...baseProps,
2433
- type: "External link",
2434
- href: fields.external
2435
- };
2436
- }
2437
- if (fields.downloadAsset) {
2438
- const asset = lookupAsset(context, fields.downloadAsset);
2439
- let href = null;
2440
- if (asset?.image?.type === "Picture") {
2441
- href = asset.image.src;
2442
- } else if (asset?.image?.type === "Svg image") {
2443
- href = asset.image.svgSrc;
2444
- } else if (asset?.video) {
2445
- if (asset.video.type === "Local video") {
2446
- href = asset.video.preview.videoUrl;
2447
- } else if (asset.video.type === "Full video") {
2448
- href = asset.video.full.videoUrl;
2449
- } else if (asset.video.type === "External video") {
2450
- href = asset.video.external;
2451
- }
2452
- }
2453
- return {
2454
- ...baseProps,
2455
- type: "Download link",
2456
- href,
2457
- visual: asset
2458
- };
2459
- }
2460
- return {
2461
- ...baseProps,
2462
- type: "Blank link",
2463
- href: null
2464
- };
2465
- }
2466
-
2467
- // src/converters/page.ts
2468
- function basePageConverter(context, entry) {
2469
- const {
2470
- sys: { id },
2471
- fields
2472
- } = entry;
2473
- const {
2474
- slug,
2475
- title,
2476
- description,
2477
- featuredImage,
2478
- tags,
2479
- content,
2480
- menu: pageMenu,
2481
- footer: pageFooter,
2482
- template: templateLink,
2483
- topContent: topContentLinks,
2484
- bottomContent: bottomContentLinks,
2485
- structuredData,
2486
- ...simpleFields
2487
- } = fields;
2488
- const pageMenuNav = pageMenu ? resolveNavigation(context, pageMenu) : void 0;
2489
- const pageFooterNav = pageFooter ? resolveNavigation(context, pageFooter) : void 0;
2490
- const template = templateLink ? resolveTemplate(context, templateLink) : null;
2491
- const topContent = topContentLinks?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
2492
- const pageContent = content?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
2493
- const bottomContent = bottomContentLinks?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
2494
- const preContent = template?.preContent ?? [];
2495
- const postContent = template?.postContent ?? [];
2496
- const contents = addPositionMetadata([
2497
- ...topContent,
2498
- ...preContent,
2499
- ...pageContent,
2500
- ...postContent,
2501
- ...bottomContent
2502
- ]);
2503
- const finalMenu = pageMenuNav ?? template?.menu;
2504
- const finalFooter = pageFooterNav ?? template?.footer;
2505
- const icons = deduplicateIcons(
2506
- collectIconsFromContent(contents),
2507
- collectIconsFromNavigation(finalMenu),
2508
- collectIconsFromNavigation(finalFooter)
2509
- );
2510
- const page = {
2511
- type: "Page",
2512
- id,
2513
- isHomePage: slug === "index",
2514
- slug,
2515
- name: title,
2516
- title: makeContentfulTitle(title, id),
2517
- description: makeContentfulDescription(description, id),
2518
- featuredImage: lookupAsset(context, featuredImage),
2519
- tags: tags?.map((tag) => resolveLink(context, id, tag)),
2520
- contentCount: calculateContentCount(topContent, pageContent, bottomContent),
2521
- contents,
2522
- icons,
2523
- structuredData: structuredData?.map((link) => resolveSchema(context, id, link)).filter((item) => item !== null),
2524
- ...simpleFields,
2525
- menu: finalMenu,
2526
- footer: finalFooter
2527
- };
2528
- return page;
2529
- }
2530
- function calculatePageHref(slug) {
2531
- if (slug === "index") {
2532
- return "/";
2533
- }
2534
- return `/${slug}/`;
2535
- }
2536
- function basePageLinkConverter(context, entry) {
2537
- const {
2538
- sys: { id, contentType },
2539
- fields
2540
- } = entry;
2541
- if (contentType.sys.id !== "page") {
2542
- throw new Error(`Invalid content type: expected "page", got "${contentType.sys.id}"`);
2543
- }
2544
- return createInternalLink(
2545
- id,
2546
- {
2547
- ...fields,
2548
- name: fields.cmsLabel
2549
- },
2550
- context,
2551
- context.urlCalculators.page(fields.slug),
2552
- "Page",
2553
- { tags: fields.tags?.map((tag) => resolveLink(context, id, tag)) }
2554
- );
2555
- }
2556
- function calculatePageVariantHref(slug) {
2557
- return `/${slug}/`;
2558
- }
2559
- function basePageVariantLinkConverter(context, entry) {
2560
- const {
2561
- sys: { id, contentType },
2562
- fields
2563
- } = entry;
2564
- if (contentType.sys.id !== "pageVariant") {
2565
- throw new Error(`Invalid content type: expected "pageVariant", got "${contentType.sys.id}"`);
2566
- }
2567
- return createInternalLink(
2568
- id,
2569
- {
2570
- ...fields,
2571
- name: fields.cmsLabel
2572
- },
2573
- context,
2574
- context.urlCalculators.pageVariant(fields.slug),
2575
- "Page",
2576
- { tags: fields.tags?.map((tag) => resolveLink(context, id, tag)) }
2577
- );
2578
- }
2579
-
2580
- // src/converters/person.ts
2581
- function basePersonLinkConverter(context, entry) {
2582
- const { sys, fields } = entry;
2583
- const {
2584
- name,
2585
- bio: fieldsBio,
2586
- jobTitle,
2587
- phoneNumber,
2588
- emailAddress,
2589
- linkedIn,
2590
- location,
2591
- media,
2592
- ...simpleFields
2593
- } = fields;
2594
- if (sys.contentType.sys.id !== "person") {
2595
- throw new Error(`Invalid content type: expected "person", got "${sys.contentType.sys.id}"`);
2596
- }
2597
- const title = makeContentfulTitle(name, sys.id);
2598
- return createInternalLink(
2599
- sys.id,
2600
- {
2601
- ...simpleFields,
2602
- name,
2603
- title,
2604
- featuredImage: media,
2605
- cmsLabel: title
2606
- },
2607
- context,
2608
- context.urlCalculators.person(fields.slug),
2609
- "Person",
2610
- {
2611
- bio: resolveRichTextDocument(context, sys.id, fieldsBio),
2612
- jobTitle,
2613
- phoneNumber,
2614
- emailAddress,
2615
- linkedIn,
2616
- location
2617
- }
2618
- );
2619
- }
2620
- function basePersonConverter(context, entry, customTypeEntry) {
2621
- const {
2622
- sys: { id, contentType },
2623
- fields
2624
- } = entry;
2625
- if (contentType.sys.id !== "person") {
2626
- throw new Error(`Invalid content type: expected "person", got "${contentType.sys.id}"`);
2627
- }
2628
- const {
2629
- slug,
2630
- name,
2631
- description,
2632
- media,
2633
- bio: bioField,
2634
- content: contentLinks,
2635
- structuredData,
2636
- ...rest
2637
- } = fields;
2638
- const customType = customTypeEntry ? context.customTypeResolver(context, customTypeEntry) : void 0;
2639
- const template = customTypeEntry?.fields.template ? resolveTemplate(context, customTypeEntry.fields.template) : null;
2640
- const personContent = contentLinks?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
2641
- const preContent = template?.preContent ?? [];
2642
- const postContent = template?.postContent ?? [];
2643
- const contents = addPositionMetadata([...preContent, ...personContent, ...postContent]);
2644
- const menu = customTypeEntry?.fields.menu ? resolveNavigation(context, customTypeEntry.fields.menu) : template?.menu;
2645
- const footer = customTypeEntry?.fields.footer ? resolveNavigation(context, customTypeEntry.fields.footer) : template?.footer;
2646
- const icons = deduplicateIcons(
2647
- collectIconsFromContent(contents),
2648
- collectIconsFromNavigation(menu),
2649
- collectIconsFromNavigation(footer)
2650
- );
2651
- return {
2652
- type: "Person",
2653
- id,
2654
- slug,
2655
- name,
2656
- title: makeContentfulTitle(name, id),
2657
- description: description ?? `Description for ${id}`,
2658
- featuredImage: lookupAsset(context, media),
2659
- // Person uses 'media' not 'featuredImage'
2660
- href: context.urlCalculators.person(slug),
2661
- bio: resolveRichTextDocument(context, id, bioField) ?? null,
2662
- contentCount: calculateContentCount(personContent),
2663
- contents,
2664
- menu,
2665
- footer,
2666
- icons,
2667
- customType,
2668
- structuredData: structuredData?.map((link) => resolveSchema(context, id, link)).filter((item) => item !== null),
2669
- ...rest
2670
- };
2671
- }
2672
-
2673
- // src/converters/tag.ts
2674
- function baseTagLinkConverter(context, entry) {
2675
- const { sys, fields } = entry;
2676
- const { name, cmsLabel, ...simpleFields } = fields;
2677
- if (sys.contentType.sys.id !== "tag") {
2678
- throw new Error(`Invalid content type: expected "tag", got "${sys.contentType.sys.id}"`);
2679
- }
2680
- return createInternalLink(
2681
- sys.id,
2682
- {
2683
- ...simpleFields,
2684
- cmsLabel,
2685
- name,
2686
- useName: false,
2687
- title: name
2688
- },
2689
- context,
2690
- context.urlCalculators.tag(fields.slug),
2691
- "Tag"
2692
- );
2693
- }
2694
- function baseTagConverter(context, entry, customTypeEntry) {
2695
- const { sys, fields } = entry;
2696
- const { id } = sys;
2697
- const {
2698
- name,
2699
- slug,
2700
- description,
2701
- featuredImage,
2702
- tagType,
2703
- menu: menuLink,
2704
- footer: footerLink,
2705
- template: templateLink,
2706
- topContent: topContentLinks,
2707
- structuredData,
2708
- ...rest
2709
- } = fields;
2710
- const customType = customTypeEntry ? context.customTypeResolver(context, customTypeEntry) : void 0;
2711
- let template = templateLink ? resolveTemplate(context, templateLink) : null;
2712
- if (!template && customTypeEntry?.fields.template) {
2713
- template = resolveTemplate(context, customTypeEntry.fields.template);
2714
- }
2715
- const menu = menuLink ? resolveNavigation(context, menuLink) : customTypeEntry?.fields.menu ? resolveNavigation(context, customTypeEntry.fields.menu) : template?.menu;
2716
- const footer = footerLink ? resolveNavigation(context, footerLink) : customTypeEntry?.fields.footer ? resolveNavigation(context, customTypeEntry.fields.footer) : template?.footer;
2717
- const topContent = topContentLinks?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
2718
- const preContent = template?.preContent ?? [];
2719
- const postContent = template?.postContent ?? [];
2720
- const contents = addPositionMetadata([...topContent, ...preContent, ...postContent]);
2721
- const icons = deduplicateIcons(
2722
- collectIconsFromContent(contents),
2723
- collectIconsFromNavigation(menu),
2724
- collectIconsFromNavigation(footer)
2725
- );
2726
- const tagTypeEntry = tagType ? context.includes.get(tagType.sys.id) : null;
2727
- const tagTypeName = tagTypeEntry?.entry?.fields?.name;
2728
- const tag = {
2729
- type: "Tag",
2730
- id,
2731
- slug,
2732
- name,
2733
- title: makeContentfulTitle(name, sys.id),
2734
- description: makeContentfulDescription(description, sys.id),
2735
- featuredImage: lookupAsset(context, featuredImage),
2736
- tagType: tagTypeName ?? null,
2737
- contentCount: calculateContentCount(topContent),
2738
- contents,
2739
- icons,
2740
- structuredData: structuredData?.map((link) => resolveSchema(context, id, link)).filter((item) => item !== null),
2741
- ...rest,
2742
- menu,
2743
- footer
2744
- };
2745
- if (customType) {
2746
- tag.customType = customType;
2747
- }
2748
- return tag;
2749
- }
2750
-
2751
- // src/api/context.ts
2752
- function createBaseConverterContext(urlCalculators) {
2753
- const linkResolver = /* @__PURE__ */ new Map();
2754
- linkResolver.set("page", basePageLinkConverter);
2755
- linkResolver.set("article", baseArticleLinkConverter);
2756
- linkResolver.set(
2757
- "articleType",
2758
- baseArticleTypeLinkConverter
2759
- );
2760
- linkResolver.set("tag", baseTagLinkConverter);
2761
- linkResolver.set("person", basePersonLinkConverter);
2762
- linkResolver.set("pageVariant", basePageVariantLinkConverter);
2763
- linkResolver.set("link", baseLinkConverter);
2764
- linkResolver.set("customType", baseCustomTypeLinkConverter);
2765
- const contentResolver = /* @__PURE__ */ new Map();
2766
- contentResolver.set("collection", baseCollectionConverter);
2767
- contentResolver.set("component", baseComponentConverter);
2768
- contentResolver.set(
2769
- "externalComponent",
2770
- baseExternalComponentConverter
2771
- );
2772
- return {
2773
- pageResolver: basePageConverter,
2774
- navigationItemResolver: baseNavigationItemConverter,
2775
- articleResolver: baseArticleConverter,
2776
- articleTypeResolver: baseArticleTypeConverter,
2777
- tagResolver: baseTagConverter,
2778
- personResolver: basePersonConverter,
2779
- customTypeResolver: baseCustomTypeConverter,
2780
- componentResolver: baseComponentConverter,
2781
- collectionResolver: baseCollectionConverter,
2782
- linkResolver,
2783
- contentResolver,
2784
- urlCalculators,
2785
- videoPrefix: ""
2786
- };
2787
- }
2788
-
2789
- // src/api/custom-type.ts
2790
- async function contentfulCustomTypeRest(context, config, slug, options) {
2791
- return fetchSingleEntity(
2792
- context,
2793
- config,
2794
- {
2795
- contentType: "customType",
2796
- cacheTagType: "customType",
2797
- cacheTagIdentifier: slug,
2798
- query: { "fields.slug": slug },
2799
- resolver: (ctx, entry) => ctx.customTypeResolver(ctx, entry),
2800
- errorLogContext: { slug }
2801
- },
2802
- options
2803
- );
2804
- }
2805
-
2806
- // src/api/links.ts
2807
- init_utils();
2808
- async function contentfulAllPageLinks(context, config, options) {
2809
- const client = getContentfulClient(config, options.preview);
2810
- const cacheTags = getCacheTags("page", void 0, options?.preview);
2811
- const requestOptions = {
2812
- ...options,
2813
- next: {
2814
- ...options?.next,
2815
- tags: cacheTags
2816
- }
2817
- };
2818
- const fetchFn = () => fetchAllLinks(
2819
- "page",
2820
- client,
2821
- requestOptions,
2822
- basePageLinkConverter,
2823
- context,
2824
- 100,
2825
- PAGE_LINK_FIELDS
2826
- );
2827
- if (options?.retry) {
2828
- return await withRetry(fetchFn, options.retry);
2829
- }
2830
- return await fetchFn();
2831
- }
2832
- async function contentfulAllArticleLinks(context, config, options) {
2833
- const client = getContentfulClient(config, options.preview);
2834
- const cacheTags = getCacheTags("article", void 0, options?.preview);
2835
- const requestOptions = {
2836
- ...options,
2837
- next: {
2838
- ...options?.next,
2839
- tags: cacheTags
2840
- }
2841
- };
2842
- const fetchFn = () => fetchAllLinks(
2843
- "article",
2844
- client,
2845
- requestOptions,
2846
- baseArticleLinkConverter,
2847
- context,
2848
- 100,
2849
- ARTICLE_LINK_FIELDS
2850
- );
2851
- if (options?.retry) {
2852
- return await withRetry(fetchFn, options.retry);
2853
- }
2854
- return await fetchFn();
2855
- }
2856
- async function contentfulAllTagLinks(context, config, options) {
2857
- const client = getContentfulClient(config, options.preview);
2858
- const cacheTags = getCacheTags("tag", void 0, options?.preview);
2859
- const requestOptions = {
2860
- ...options,
2861
- next: {
2862
- ...options?.next,
2863
- tags: cacheTags
2864
- }
2865
- };
2866
- const fetchFn = () => fetchAllLinks(
2867
- "tag",
2868
- client,
2869
- requestOptions,
2870
- baseTagLinkConverter,
2871
- context,
2872
- 100,
2873
- TAG_LINK_FIELDS
2874
- );
2875
- if (options?.retry) {
2876
- return await withRetry(fetchFn, options.retry);
2877
- }
2878
- return await fetchFn();
2879
- }
2880
- async function contentfulAllPersonLinks(context, config, options) {
2881
- const client = getContentfulClient(config, options.preview);
2882
- const cacheTags = getCacheTags("person", void 0, options?.preview);
2883
- const requestOptions = {
2884
- ...options,
2885
- next: {
2886
- ...options?.next,
2887
- tags: cacheTags
2888
- }
2889
- };
2890
- const fetchFn = () => fetchAllLinks(
2891
- "person",
2892
- client,
2893
- requestOptions,
2894
- basePersonLinkConverter,
2895
- context,
2896
- 100,
2897
- PERSON_LINK_FIELDS
2898
- );
2899
- if (options?.retry) {
2900
- return await withRetry(fetchFn, options.retry);
2901
- }
2902
- return await fetchFn();
2903
- }
2904
- async function contentfulAllArticleTypeLinks(context, config, options) {
2905
- const client = getContentfulClient(config, options.preview);
2906
- const cacheTags = getCacheTags("articleType", void 0, options?.preview);
2907
- const requestOptions = {
2908
- ...options,
2909
- next: {
2910
- ...options?.next,
2911
- tags: cacheTags
2912
- }
2913
- };
2914
- const fetchFn = () => fetchAllLinks(
2915
- "articleType",
2916
- client,
2917
- requestOptions,
2918
- baseArticleTypeLinkConverter,
2919
- context,
2920
- 100,
2921
- ARTICLE_TYPE_LINK_FIELDS
2922
- );
2923
- if (options?.retry) {
2924
- return await withRetry(fetchFn, options.retry);
2925
- }
2926
- return await fetchFn();
2927
- }
2928
-
2929
- // src/api/page.ts
2930
- async function contentfulPageRest(context, config, slug, options) {
2931
- return fetchSingleEntity(
2932
- context,
2933
- config,
2934
- {
2935
- contentType: "page",
2936
- cacheTagType: "page",
2937
- cacheTagIdentifier: slug,
2938
- query: { "fields.slug": slug },
2939
- resolver: (ctx, entry) => ctx.pageResolver(ctx, entry),
2940
- errorLogContext: { slug }
2941
- },
2942
- options
2943
- );
2944
- }
2945
-
2946
- // src/api/person.ts
2947
- init_utils();
2948
- async function contentfulPersonRest(context, config, slug, options) {
2949
- const client = getContentfulClient(config, options.preview);
2950
- const personCacheTags = getCacheTags("person", slug, options?.preview);
2951
- const customTypeCacheTags = options?.customType ? getCacheTags("customType", options.customType, options?.preview) : [];
2952
- const requestOptions = {
2953
- ...options,
2954
- next: {
2955
- ...options?.next,
2956
- tags: [...personCacheTags, ...customTypeCacheTags]
2957
- }
2958
- };
2959
- const fetchFn = async () => {
2960
- const personPromise = client.getEntries(
2961
- {
2962
- content_type: "person",
2963
- "fields.slug": slug,
2964
- include: 10,
2965
- locale: options?.locale,
2966
- limit: 1
2967
- },
2968
- requestOptions
2969
- );
2970
- const customTypePromise = options?.customType ? client.getEntries(
2971
- {
2972
- content_type: "customType",
2973
- "fields.slug": options.customType,
2974
- include: 10,
2975
- locale: options?.locale,
2976
- limit: 1
2977
- },
2978
- requestOptions
2979
- ) : Promise.resolve(null);
2980
- const [personResponse, customTypeResponse] = await Promise.all([
2981
- personPromise,
2982
- customTypePromise
2983
- ]);
2984
- const personEntry = personResponse.items[0];
2985
- if (!personEntry || !personEntry.fields) {
2986
- return { data: null, errors: [] };
2987
- }
2988
- const customTypeEntry = customTypeResponse?.items[0];
2989
- try {
2990
- const assets = convertAllAssets(personResponse, context);
2991
- const rawAssets = convertAllRawAssets(personResponse);
2992
- const includes = convertAllIncludes(personResponse);
2993
- if (customTypeResponse) {
2994
- const customAssets = convertAllAssets(customTypeResponse, context);
2995
- const customRawAssets = convertAllRawAssets(customTypeResponse);
2996
- const customIncludes = convertAllIncludes(customTypeResponse);
2997
- for (const [key, value] of customAssets) assets.set(key, value);
2998
- for (const [key, value] of customRawAssets) rawAssets.set(key, value);
2999
- for (const [key, value] of customIncludes) includes.set(key, value);
3000
- }
3001
- const fullContext = {
3002
- ...context,
3003
- includes,
3004
- assets,
3005
- rawAssets,
3006
- errors: []
3007
- };
3008
- const converted = context.personResolver(fullContext, personEntry, customTypeEntry);
3009
- if (converted?.icons && converted.icons.length > 0) {
3010
- const processedIcons = await processIconsForSprite(converted.icons);
3011
- converted.icons = processedIcons.length > 0 ? processedIcons : void 0;
3012
- }
3013
- if (fullContext.errors.length > 0 && typeof process !== "undefined" && process.env?.NODE_ENV === "production") {
3014
- console.error(`CMS conversion errors for person:`, {
3015
- entryId: personEntry.sys.id,
3016
- slug,
3017
- errors: fullContext.errors
3018
- });
3019
- }
3020
- return { data: converted, errors: fullContext.errors };
3021
- } catch (error) {
3022
- const entryId = personEntry?.sys.id || "unknown";
3023
- const entryType = personEntry?.sys.contentType?.sys?.id;
3024
- const errorMessage = error instanceof Error ? error.message : "Unknown conversion error";
3025
- const cmsError = {
3026
- entryId,
3027
- entryType,
3028
- message: errorMessage,
3029
- error
3030
- };
3031
- return { data: null, errors: [cmsError] };
3032
- }
3033
- };
3034
- if (options?.retry) {
3035
- return await withRetry(fetchFn, options.retry);
3036
- }
3037
- return await fetchFn();
3038
- }
3039
-
3040
- // src/api/preview.ts
3041
- async function getPreviewEntryInfo(context, config, entryId, options) {
3042
- const client = getContentfulClient(config, true);
3043
- const response = await client.getEntries(
3044
- {
3045
- "sys.id": entryId,
3046
- include: 1
3047
- // Need 1 level for Article → ArticleType slug
3048
- },
3049
- {
3050
- ...options,
3051
- preview: true
3052
- }
3053
- );
3054
- const entry = response.items[0];
3055
- if (!entry) {
3056
- return null;
3057
- }
3058
- const contentTypeId = entry.sys.contentType.sys.id;
3059
- const { fields } = entry;
3060
- switch (contentTypeId) {
3061
- case "page": {
3062
- const slug = fields.slug;
3063
- if (!slug) return null;
3064
- return {
3065
- contentType: "page",
3066
- entryId,
3067
- slug,
3068
- href: context.urlCalculators.page(slug)
3069
- };
3070
- }
3071
- case "pageVariant": {
3072
- const slug = fields.slug;
3073
- if (!slug) return null;
3074
- return {
3075
- contentType: "pageVariant",
3076
- entryId,
3077
- slug,
3078
- href: context.urlCalculators.pageVariant(slug)
3079
- };
3080
- }
3081
- case "article": {
3082
- const slug = fields.slug;
3083
- const articleTypeSlug = getArticleTypeSlug(entry, response.includes?.Entry);
3084
- if (!slug || !articleTypeSlug) return null;
3085
- return {
3086
- contentType: "article",
3087
- entryId,
3088
- slug,
3089
- articleTypeSlug,
3090
- href: context.urlCalculators.article(articleTypeSlug, slug)
3091
- };
3092
- }
3093
- case "articleType": {
3094
- const slug = fields.slug;
3095
- if (!slug) return null;
3096
- return {
3097
- contentType: "articleType",
3098
- entryId,
3099
- slug,
3100
- href: context.urlCalculators.articleType(slug)
3101
- };
3102
- }
3103
- case "tag": {
3104
- const slug = fields.slug;
3105
- if (!slug) return null;
3106
- return {
3107
- contentType: "tag",
3108
- entryId,
3109
- slug,
3110
- href: context.urlCalculators.tag(slug)
3111
- };
3112
- }
3113
- case "person": {
3114
- const slug = fields.slug;
3115
- if (!slug) return null;
3116
- return {
3117
- contentType: "person",
3118
- entryId,
3119
- slug,
3120
- href: context.urlCalculators.person(slug)
3121
- };
3122
- }
3123
- case "customType": {
3124
- const slug = fields.slug;
3125
- if (!slug) return null;
3126
- return {
3127
- contentType: "customType",
3128
- entryId,
3129
- slug,
3130
- href: context.urlCalculators.customType(slug)
3131
- };
3132
- }
3133
- default:
3134
- return null;
3135
- }
3136
- }
3137
- function getArticleTypeSlug(entry, includes) {
3138
- const articleTypeField = entry.fields.articleType;
3139
- if (!articleTypeField) {
3140
- return void 0;
3141
- }
3142
- if (articleTypeField.fields?.slug) {
3143
- return articleTypeField.fields.slug;
3144
- }
3145
- const articleTypeId = articleTypeField.sys?.id;
3146
- if (!articleTypeId || !includes) {
3147
- return void 0;
3148
- }
3149
- const articleTypeEntry = includes.find((inc) => inc.sys.id === articleTypeId);
3150
- return articleTypeEntry?.fields?.slug;
3151
- }
3152
-
3153
- // src/api/related-articles.ts
3154
- function filterRelatedArticles(articles, options) {
3155
- const {
3156
- excludeArticleIds,
3157
- articleTypeIds: rawArticleTypeIds,
3158
- tagIds: rawTagIds,
3159
- authorIds: rawAuthorIds,
3160
- before,
3161
- after,
3162
- count
3163
- } = options;
3164
- const excludeIds = excludeArticleIds ? [...new Set(excludeArticleIds)] : [];
3165
- const articleTypeIds = rawArticleTypeIds ? [...new Set(rawArticleTypeIds)] : [];
3166
- const tagIds = rawTagIds ? [...new Set(rawTagIds)] : [];
3167
- const authorIds = rawAuthorIds ? [...new Set(rawAuthorIds)] : [];
3168
- const scoredArticles = articles.map((article) => {
3169
- let score = 0;
3170
- if (excludeIds.length > 0 && excludeIds.includes(article.id)) {
3171
- return null;
3172
- }
3173
- if (articleTypeIds.length > 0) {
3174
- if (!article.articleType?.id || !articleTypeIds.includes(article.articleType.id)) {
3175
- return null;
3176
- }
3177
- }
3178
- if (article.date) {
3179
- const articleDate = new Date(article.date);
3180
- if (before && articleDate > before) {
3181
- return null;
3182
- }
3183
- if (after && articleDate < after) {
3184
- return null;
3185
- }
3186
- }
3187
- if (tagIds.length > 0 && article.tags) {
3188
- const articleTagIds = article.tags.map((tag) => tag.id);
3189
- const matchingTags = tagIds.filter((tagId) => articleTagIds.includes(tagId));
3190
- score += matchingTags.length * 10;
3191
- }
3192
- if (authorIds.length > 0 && article.author?.id) {
3193
- if (authorIds.includes(article.author.id)) {
3194
- score += 5;
3195
- }
3196
- }
3197
- return { article, score };
3198
- }).filter((item) => item !== null);
3199
- scoredArticles.sort((a, b) => {
3200
- if (b.score !== a.score) {
3201
- return b.score - a.score;
3202
- }
3203
- if (a.article.date && b.article.date) {
3204
- const articleDateA = new Date(a.article.date);
3205
- const articleDateB = new Date(b.article.date);
3206
- return articleDateB.getTime() - articleDateA.getTime();
3207
- }
3208
- return 0;
3209
- });
3210
- const topArticles = count !== void 0 ? scoredArticles.slice(0, count) : scoredArticles;
3211
- return topArticles.map(({ article }) => article);
3212
- }
3213
-
3214
- // src/api/sitemap.ts
3215
- function linksToSitemapEntries(links, sitemapConfig) {
3216
- return links.filter((link) => link.indexed !== false && link.hidden !== true && link.href).map((link) => ({
3217
- url: link.href,
3218
- lastModified: link.lastModified,
3219
- changeFrequency: sitemapConfig?.changeFrequency,
3220
- priority: sitemapConfig?.priority
3221
- }));
3222
- }
3223
- async function contentfulPageSitemapEntries(context, config, sitemapConfig, options) {
3224
- const response = await contentfulAllPageLinks(context, config, options);
3225
- return {
3226
- data: linksToSitemapEntries(response.data, sitemapConfig),
3227
- errors: response.errors
3228
- };
3229
- }
3230
- async function contentfulArticleSitemapEntries(context, config, sitemapConfig, options) {
3231
- const response = await contentfulAllArticleLinks(context, config, options);
3232
- return {
3233
- data: linksToSitemapEntries(response.data, sitemapConfig),
3234
- errors: response.errors
3235
- };
3236
- }
3237
- async function contentfulArticleTypeSitemapEntries(context, config, sitemapConfig, options) {
3238
- const response = await contentfulAllArticleTypeLinks(context, config, options);
3239
- return {
3240
- data: linksToSitemapEntries(response.data, sitemapConfig),
3241
- errors: response.errors
3242
- };
3243
- }
3244
- async function contentfulTagSitemapEntries(context, config, sitemapConfig, options) {
3245
- const response = await contentfulAllTagLinks(context, config, options);
3246
- return {
3247
- data: linksToSitemapEntries(response.data, sitemapConfig),
3248
- errors: response.errors
3249
- };
3250
- }
3251
- async function contentfulPersonSitemapEntries(context, config, sitemapConfig, options) {
3252
- const response = await contentfulAllPersonLinks(context, config, options);
3253
- return {
3254
- data: linksToSitemapEntries(response.data, sitemapConfig),
3255
- errors: response.errors
3256
- };
3257
- }
3258
- async function getAllSitemapEntries(context, config, sitemapConfig, options) {
3259
- const allEntries = [];
3260
- const allErrors = [];
3261
- const results = await Promise.all(
3262
- sitemapConfig.providers.map((provider) => provider(context, config, options))
3263
- );
3264
- for (const result of results) {
3265
- allEntries.push(...result.data);
3266
- allErrors.push(...result.errors);
3267
- }
3268
- return { data: allEntries, errors: allErrors };
3269
- }
3270
- function createSitemapProvider(fetcher, sitemapConfig) {
3271
- return (context, config, options) => fetcher(context, config, sitemapConfig, options);
3272
- }
3273
-
3274
- // src/api/tag.ts
3275
- init_utils();
3276
- async function contentfulTagRest(context, config, slug, options) {
3277
- const client = getContentfulClient(config, options.preview);
3278
- const tagCacheTags = getCacheTags("tag", slug, options?.preview);
3279
- const customTypeCacheTags = options?.customType ? getCacheTags("customType", options.customType, options?.preview) : [];
3280
- const requestOptions = {
3281
- ...options,
3282
- next: {
3283
- ...options?.next,
3284
- tags: [...tagCacheTags, ...customTypeCacheTags]
3285
- }
3286
- };
3287
- const fetchFn = async () => {
3288
- const tagPromise = client.getEntries(
3289
- {
3290
- content_type: "tag",
3291
- "fields.slug": slug,
3292
- include: 10,
3293
- locale: options?.locale,
3294
- limit: 1
3295
- },
3296
- requestOptions
3297
- );
3298
- const customTypePromise = options?.customType ? client.getEntries(
3299
- {
3300
- content_type: "customType",
3301
- "fields.slug": options.customType,
3302
- include: 10,
3303
- locale: options?.locale,
3304
- limit: 1
3305
- },
3306
- requestOptions
3307
- ) : Promise.resolve(null);
3308
- const [tagResponse, customTypeResponse] = await Promise.all([tagPromise, customTypePromise]);
3309
- const tagEntry = tagResponse.items[0];
3310
- if (!tagEntry || !tagEntry.fields) {
3311
- return { data: null, errors: [] };
3312
- }
3313
- const customTypeEntry = customTypeResponse?.items[0];
3314
- try {
3315
- const assets = convertAllAssets(tagResponse, context);
3316
- const rawAssets = convertAllRawAssets(tagResponse);
3317
- const includes = convertAllIncludes(tagResponse);
3318
- if (customTypeResponse) {
3319
- const customAssets = convertAllAssets(customTypeResponse, context);
3320
- const customRawAssets = convertAllRawAssets(customTypeResponse);
3321
- const customIncludes = convertAllIncludes(customTypeResponse);
3322
- for (const [key, value] of customAssets) assets.set(key, value);
3323
- for (const [key, value] of customRawAssets) rawAssets.set(key, value);
3324
- for (const [key, value] of customIncludes) includes.set(key, value);
3325
- }
3326
- const fullContext = {
3327
- ...context,
3328
- includes,
3329
- assets,
3330
- rawAssets,
3331
- errors: []
3332
- };
3333
- const converted = context.tagResolver(fullContext, tagEntry, customTypeEntry);
3334
- if (converted?.icons && converted.icons.length > 0) {
3335
- const processedIcons = await processIconsForSprite(converted.icons);
3336
- converted.icons = processedIcons.length > 0 ? processedIcons : void 0;
3337
- }
3338
- if (fullContext.errors.length > 0 && typeof process !== "undefined" && process.env?.NODE_ENV === "production") {
3339
- console.error(`CMS conversion errors for tag:`, {
3340
- entryId: tagEntry.sys.id,
3341
- slug,
3342
- errors: fullContext.errors
3343
- });
3344
- }
3345
- return { data: converted, errors: fullContext.errors };
3346
- } catch (error) {
3347
- const entryId = tagEntry?.sys.id || "unknown";
3348
- const entryType = tagEntry?.sys.contentType?.sys?.id;
3349
- const errorMessage = error instanceof Error ? error.message : "Unknown conversion error";
3350
- const cmsError = {
3351
- entryId,
3352
- entryType,
3353
- message: errorMessage,
3354
- error
3355
- };
3356
- return { data: null, errors: [cmsError] };
3357
- }
3358
- };
3359
- if (options?.retry) {
3360
- return await withRetry(fetchFn, options.retry);
3361
- }
3362
- return await fetchFn();
3363
- }
3364
-
3365
- // src/api/template.ts
3366
- function templateConverter(context, entry) {
3367
- const { fields, sys } = entry;
3368
- const id = sys.id;
3369
- const preContent = fields.preContent?.map((content) => resolvePageContent(context, id, content)).filter((item) => item !== null) ?? [];
3370
- const postContent = fields.postContent?.map((content) => resolvePageContent(context, id, content)).filter((item) => item !== null) ?? [];
3371
- const menu = fields.menu ? resolveNavigation(context, fields.menu) : void 0;
3372
- const footer = fields.footer ? resolveNavigation(context, fields.footer) : void 0;
3373
- const stickyNav = fields.flags?.includes("Sticky nav") ?? false;
3374
- const contentIcons = collectIconsFromContent([...preContent, ...postContent]);
3375
- const menuIcons = collectIconsFromNavigation(menu);
3376
- const footerIcons = collectIconsFromNavigation(footer);
3377
- const allIcons = [...contentIcons, ...menuIcons, ...footerIcons];
3378
- const seenIconIds = /* @__PURE__ */ new Set();
3379
- const icons = allIcons.filter((icon) => {
3380
- if (seenIconIds.has(icon.id)) {
3381
- return false;
3382
- }
3383
- seenIconIds.add(icon.id);
3384
- return true;
3385
- });
3386
- return {
3387
- id,
3388
- cmsLabel: fields.cmsLabel,
3389
- preContent,
3390
- postContent,
3391
- menu,
3392
- footer,
3393
- backgroundColour: fields.backgroundColour,
3394
- textColour: fields.textColour,
3395
- stickyNav,
3396
- icons: icons.length > 0 ? icons : void 0
3397
- };
3398
- }
3399
- async function contentfulTemplateRest(context, config, cmsLabel, options) {
3400
- return fetchSingleEntity(
3401
- context,
3402
- config,
3403
- {
3404
- contentType: "template",
3405
- cacheTagType: "template",
3406
- cacheTagIdentifier: cmsLabel,
3407
- query: { "fields.cmsLabel": cmsLabel },
3408
- resolver: (ctx, entry) => templateConverter(ctx, entry),
3409
- errorLogContext: { cmsLabel }
3410
- },
3411
- options
3412
- );
3413
- }
3414
-
3415
- // src/revalidation/handlers.ts
3416
- init_tags();
3417
- init_utils();
3418
- var defaultLocale = "en-US";
3419
- var articleTypeHandler = {
3420
- extract: (data) => ({
3421
- slug: data.fields?.slug?.[defaultLocale]
3422
- }),
3423
- makeTags: (extracted) => [extracted.slug ? articleTypeIndexTag(extracted.slug) : void 0],
3424
- getGlobalTags: () => [ArticleTypeTag, ArticleTypeIndexTag]
3425
- };
3426
- var articleHandler = {
3427
- extract: (data) => ({
3428
- slug: data.fields?.slug?.[defaultLocale]
3429
- }),
3430
- makeTags: (extracted) => [extracted?.slug ? articleTag(extracted.slug) : void 0],
3431
- getGlobalTags: () => [ArticleTag],
3432
- rebuildSearch: true
3433
- };
3434
- var pageVariantHandler = {
3435
- extract: (data) => {
3436
- const pageVariantSlug = data.fields?.slug?.[defaultLocale];
3437
- return pageVariantSlug;
3438
- },
3439
- makeTags: (extracted) => [extracted ? pageTag(extracted) : void 0],
3440
- getGlobalTags: () => [PageTag]
3441
- };
3442
- var templateHandler = {
3443
- extract: (data) => data.fields?.cmsLabel?.[defaultLocale],
3444
- makeTags: (extracted) => [extracted ? templateTag(extracted) : void 0],
3445
- getGlobalTags: () => [TemplateTag]
3446
- };
3447
- var contentTypeHandlers = {
3448
- page: {
3449
- extract: (data) => data.fields?.slug?.[defaultLocale],
3450
- makeTags: (extracted) => [extracted ? pageTag(extracted) : void 0],
3451
- getGlobalTags: () => [PageTag]
3452
- },
3453
- pageVariant: pageVariantHandler,
3454
- // biome-ignore lint/suspicious/noExplicitAny: Any is ok for handlers with different types
3455
- article: articleHandler,
3456
- // biome-ignore lint/suspicious/noExplicitAny: Any is ok for handlers with different types
3457
- articleType: articleTypeHandler,
3458
- person: {
3459
- extract: (data) => data.fields?.slug?.[defaultLocale],
3460
- makeTags: (extracted) => [extracted ? personTag(extracted) : void 0],
3461
- getGlobalTags: () => [PersonTag]
3462
- },
3463
- tag: {
3464
- extract: (data) => data.fields?.slug?.[defaultLocale],
3465
- makeTags: (extracted) => [extracted ? tagTag(extracted) : void 0],
3466
- getGlobalTags: () => [TagTag]
3467
- },
3468
- // biome-ignore lint/suspicious/noExplicitAny: Any is ok for handlers with different types
3469
- template: templateHandler,
3470
- customType: {
3471
- extract: () => void 0,
3472
- makeTags: () => void 0,
3473
- getGlobalTags: () => [CustomTypeTag]
3474
- },
3475
- navigation: {
3476
- extract: () => void 0,
3477
- makeTags: () => void 0,
3478
- getGlobalTags: () => [NavigationTag]
3479
- },
3480
- banner: {
3481
- extract: () => void 0,
3482
- makeTags: () => void 0,
3483
- getGlobalTags: () => [BannerTag]
3484
- },
3485
- location: {
3486
- extract: (data) => data.fields?.slug?.[defaultLocale],
3487
- makeTags: (extracted) => [extracted ? locationTag(extracted) : void 0],
3488
- getGlobalTags: () => [LocationTag]
3489
- }
3490
- };
3491
- async function revalidateAsset(content, message, preview = false) {
3492
- const id = content.sys.id;
3493
- const fileName = content.fields?.file?.[defaultLocale]?.fileName;
3494
- const tags = [assetTag(id), fileName ? assetTag(fileName) : void 0, AssetTag].filter(
3495
- (tag) => tag !== void 0
3496
- );
3497
- if (preview) {
3498
- await revalidateSingleTag(GlobalTag, message);
3499
- } else {
3500
- await revalidateTags(tags, message);
3501
- }
3502
- return true;
3503
- }
3504
- async function revalidateEntry(handler, content, contentType, preview = false) {
3505
- const extracted = await Promise.resolve(handler.extract(content));
3506
- const tags = handler.makeTags(extracted);
3507
- const validTags = tags?.filter((tag) => tag !== void 0) ?? [];
3508
- const globalTags = handler.getGlobalTags();
3509
- const tagsToRevalidate = [...validTags, ...globalTags];
3510
- if (preview) {
3511
- await revalidateSingleTag(GlobalTag, `publish ${contentType} (preview mode)`);
3512
- } else {
3513
- await revalidateTags(tagsToRevalidate, `publish ${contentType}`);
3514
- }
3515
- return true;
3516
- }
3517
- async function revalidateDeletedEntry(contentType, preview = false) {
3518
- const { getCacheTagsForProduction: getCacheTagsForProduction2 } = await Promise.resolve().then(() => (init_utils(), utils_exports));
3519
- const tags = getCacheTagsForProduction2(contentType);
3520
- if (preview) {
3521
- await revalidateSingleTag(GlobalTag, `delete ${contentType} (preview mode)`);
3522
- } else {
3523
- await revalidateTags(tags, `delete ${contentType}`);
3524
- }
3525
- return true;
3526
- }
3527
- async function revalidateDeletedAsset(assetId, preview = false) {
3528
- const tags = [assetTag(assetId), AssetTag];
3529
- if (preview) {
3530
- await revalidateSingleTag(GlobalTag, `delete asset ${assetId} (preview mode)`);
3531
- } else {
3532
- await revalidateTags(tags, `delete asset ${assetId}`);
3533
- }
3534
- return true;
3535
- }
3536
- init_tags();
3537
- init_utils();
3538
- async function publishAsset(content, preview) {
3539
- return await revalidateAsset(content, "publish asset", preview);
3540
- }
3541
- async function deleteAsset(content, preview) {
3542
- return await revalidateDeletedAsset(content.sys.id, preview);
3543
- }
3544
- async function publishEntry(content, preview) {
3545
- const contentType = content.sys.contentType.sys.id;
3546
- const handler = contentTypeHandlers[contentType];
3547
- if (!handler) {
3548
- console.warn(`Unknown content type: ${contentType}`);
3549
- return false;
3550
- }
3551
- return await revalidateEntry(handler, content, contentType, preview);
3552
- }
3553
- async function deleteEntry(content, preview) {
3554
- console.log(`Delete entry: ${content.sys.id}`);
3555
- const contentType = content.sys.contentType.sys.id;
3556
- const success = await revalidateDeletedEntry(contentType, preview);
3557
- return success;
3558
- }
3559
- async function revalidateAll(preview) {
3560
- console.log("Revalidating all tags");
3561
- await delay2(500);
3562
- if (preview) {
3563
- await revalidateSingleTag(GlobalTag, "global revalidation");
3564
- } else {
3565
- for (const tag of AllTags) {
3566
- await revalidateSingleTag(tag, "global revalidation");
3567
- }
3568
- }
3569
- return true;
3570
- }
3571
- function delay2(ms) {
3572
- return new Promise((resolve) => setTimeout(resolve, ms));
3573
- }
3574
- async function handleRevalidation(request, preview = false) {
3575
- try {
3576
- const all = request.headers.get("REVALIDATE_ALL");
3577
- const single = request.headers.get("REVALIDATION_TAG");
3578
- if (all) {
3579
- const success2 = await revalidateAll(preview);
3580
- return NextResponse.json({ revalidated: success2, now: Date.now() }, { status: 200 });
3581
- }
3582
- if (single) {
3583
- const success2 = await revalidateSingleTag(single, "single tag revalidation");
3584
- return NextResponse.json({ revalidated: success2, now: Date.now() }, { status: 200 });
3585
- }
3586
- const content = await request.json();
3587
- let success = false;
3588
- switch (content.sys.type) {
3589
- case "DeletedAsset":
3590
- success = await deleteAsset(content, preview);
3591
- break;
3592
- case "DeletedEntry":
3593
- success = await deleteEntry(content, preview);
3594
- break;
3595
- case "Asset":
3596
- success = await publishAsset(content, preview);
3597
- break;
3598
- case "Entry":
3599
- success = await publishEntry(content, preview);
3600
- break;
3601
- default:
3602
- console.warn(`Unknown message type: ${content.sys.type}`);
3603
- return NextResponse.json({ message: "Unknown message type" }, { status: 400 });
3604
- }
3605
- if (!success) {
3606
- return NextResponse.json({ message: "Unknown content type" }, { status: 400 });
3607
- }
3608
- return NextResponse.json({ revalidated: success, now: Date.now() }, { status: 200 });
3609
- } catch (error) {
3610
- console.error("Error in revalidation:", error);
3611
- return NextResponse.json({ message: "Error in revalidation" }, { status: 500 });
3612
- }
3613
- }
3614
-
3615
- // src/revalidation/nextjs-route.ts
3616
- function createRevalidationHandler(config = {}) {
3617
- return async function POST(request) {
3618
- const { secret, validateSecret = true, preview = false } = config;
3619
- if (validateSecret && secret) {
3620
- const requestSecret = request.headers.get("REVALIDATION_SECRET");
3621
- if (requestSecret !== secret) {
3622
- return NextResponse.json({ message: "Invalid secret" }, { status: 401 });
3623
- }
3624
- }
3625
- return await handleRevalidation(request, preview);
3626
- };
3627
- }
3628
-
3629
- // src/revalidation/index.ts
3630
- init_tags();
3631
- init_utils();
3632
-
3633
- export { AllTags, ArticleTag, ArticleTypeTag, AssetTag, AuthenticationError, BannerTag, ContentfulError, CustomTypeTag, EntryNotFoundError, GlobalTag, LocationTag, NavigationTag, PageTag, PersonTag, RateLimitError, RateLimiter, TagTag, TemplateTag, ValidationError, arrayOrUndefined, articleTag, articleTypeIndexTag, articleTypeTag, assetTag, basePageConverter, buildFetchOptions, calculateBackoffDelay, calculatePageHref, calculatePageVariantHref, contentfulAllArticleLinks, contentfulAllArticleTypeLinks, contentfulAllPageLinks, contentfulAllPersonLinks, contentfulAllTagLinks, contentfulArticleRest, contentfulArticleSitemapEntries, contentfulArticleTypeRest, contentfulArticleTypeSitemapEntries, contentfulAssetRest, contentfulCustomTypeRest, contentfulPageRest, contentfulPageSitemapEntries, contentfulPersonRest, contentfulPersonSitemapEntries, contentfulTagRest, contentfulTagSitemapEntries, contentfulTemplateRest, createBaseConverterContext, createContentfulClient, createContentfulPreviewClient, createDownloadHandler, createResponsiveVisual, createRevalidationHandler, createSitemapProvider, customTypeTag, filterRelatedArticles, getAllSitemapEntries, getCacheTags, getCacheTagsForPreview, getCacheTagsForProduction, getContentfulClient, getContentfulConfig, getPreviewEntryInfo, getRetryAfter, isBrowserViewable, isContentfulError, isRateLimitError, isRetryableError, isValidDate, locationTag, lookupAsset, notEmpty, pageTag, personTag, resolveLink, resolveLinks, resolveRichTextDocument, revalidateSingleTag, revalidateTags, safeDate, tagTag, templateTag, withRetry };
3634
- //# sourceMappingURL=index.js.map
1
+ /**
2
+ * @se-studio/contentful-rest-api
3
+ *
4
+ * Type-safe Contentful REST API client with caching and rate limiting
5
+ * for Next.js applications.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { contentfulPageRest, createContentfulClient } from '@se-studio/contentful-rest-api';
10
+ *
11
+ * const page = await contentfulPageRest(
12
+ * {
13
+ * spaceId: process.env.CONTENTFUL_SPACE_ID!,
14
+ * accessToken: process.env.CONTENTFUL_ACCESS_TOKEN!,
15
+ * },
16
+ * 'home'
17
+ * );
18
+ * ```
19
+ */
20
+ // API functions
21
+ export { buildFetchOptions, contentfulAllArticleLinks, contentfulAllArticleTypeLinks, contentfulAllPageLinks, contentfulAllPersonLinks, contentfulAllTagLinks, contentfulArticleRest, contentfulArticleSitemapEntries, contentfulArticleTypeRest, contentfulArticleTypeSitemapEntries,
22
+ // Asset/download functions
23
+ contentfulAssetRest, contentfulCustomTypeRest, contentfulPageRest, contentfulPageSitemapEntries, contentfulPersonRest, contentfulPersonSitemapEntries, contentfulTagRest, contentfulTagSitemapEntries, contentfulTemplateRest, createBaseConverterContext, createSitemapProvider, filterRelatedArticles, getAllSitemapEntries, getContentfulConfig,
24
+ // Preview
25
+ getPreviewEntryInfo, isBrowserViewable, } from './api';
26
+ // Client exports
27
+ export { createContentfulClient, createContentfulPreviewClient, getContentfulClient, } from './client';
28
+ // Converter exports
29
+ export { basePageConverter, calculatePageHref, calculatePageVariantHref, createResponsiveVisual, lookupAsset, resolveLink, resolveLinks, resolveRichTextDocument, } from './converters';
30
+ // Revalidation utilities
31
+ export { AllTags, ArticleTag, ArticleTypeTag, AssetTag, articleTag, articleTypeIndexTag, articleTypeTag, assetTag, BannerTag, CustomTypeTag, customTypeTag, GlobalTag, getCacheTags,
32
+ // Utility functions
33
+ getCacheTagsForPreview, getCacheTagsForProduction, LocationTag, locationTag, NavigationTag,
34
+ // Tag constants and functions
35
+ PageTag, PersonTag, pageTag, personTag, TagTag, TemplateTag, tagTag, templateTag, } from './revalidation';
36
+ // Error handling and retry utilities
37
+ export { AuthenticationError, arrayOrUndefined, ContentfulError, calculateBackoffDelay, EntryNotFoundError, getRetryAfter, isContentfulError, isRateLimitError, isRetryableError, isValidDate, notEmpty, RateLimitError, RateLimiter, safeDate, ValidationError, withRetry, } from './utils';
3635
38
  //# sourceMappingURL=index.js.map