@se-studio/contentful-rest-api 1.0.17 → 1.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -36,6 +36,9 @@ function personTag(slug) {
36
36
  function assetTag(assetId) {
37
37
  return `${AssetTag}#${assetId}`;
38
38
  }
39
+ function templateTag(label) {
40
+ return `${TemplateTag}#${label}`;
41
+ }
39
42
  function locationTag(slug) {
40
43
  return `${LocationTag}#${slug}`;
41
44
  }
@@ -306,14 +309,25 @@ var DEFAULT_POSITION_FIELDS = {
306
309
  indexOfType: 0
307
310
  };
308
311
  function createInternalLink(id, fields, context, href, internalType, additionalProps) {
309
- const { cmsLabel, title, featuredImage, backgroundColour, textColour, indexed, hidden, slug } = fields;
312
+ const {
313
+ cmsLabel,
314
+ title,
315
+ featuredImage,
316
+ backgroundColour,
317
+ textColour,
318
+ indexed,
319
+ hidden,
320
+ slug,
321
+ description
322
+ } = fields;
323
+ const text = makeContentfulTitle(title, id);
310
324
  return {
311
325
  type: "Internal link",
312
326
  internalType,
313
327
  id,
314
328
  name: cmsLabel ?? "",
315
329
  useName: true,
316
- text: makeContentfulTitle(title, id),
330
+ text,
317
331
  visual: lookupAsset(context, featuredImage),
318
332
  backgroundColour,
319
333
  textColour,
@@ -321,6 +335,8 @@ function createInternalLink(id, fields, context, href, internalType, additionalP
321
335
  hidden,
322
336
  slug,
323
337
  href,
338
+ title,
339
+ description,
324
340
  ...additionalProps
325
341
  };
326
342
  }
@@ -573,6 +589,9 @@ function lookupMediaEntry(context, link) {
573
589
  return void 0;
574
590
  }
575
591
 
592
+ // src/api/helpers.ts
593
+ init_utils();
594
+
576
595
  // src/utils/arrayUtils.ts
577
596
  function notEmpty(value) {
578
597
  if (value === null || value === void 0) return false;
@@ -620,11 +639,19 @@ function sleep(ms) {
620
639
  }
621
640
  function calculateBackoffDelay(attempt, config, retryAfter) {
622
641
  if (retryAfter !== void 0) {
623
- return Math.min(retryAfter * 1e3, config.maxDelay);
642
+ const result2 = Math.min(retryAfter * 1e3, config.maxDelay);
643
+ console.log(
644
+ `Calculated backoff delay: ${result2}ms (attempt ${attempt + 1}), retryAfter: ${retryAfter}`
645
+ );
646
+ return result2;
624
647
  }
625
648
  const exponentialDelay = config.initialDelay * config.backoffMultiplier ** attempt;
626
649
  const jitter = Math.random() * exponentialDelay;
627
- return Math.min(exponentialDelay + jitter, config.maxDelay);
650
+ const result = Math.min(exponentialDelay + jitter, config.maxDelay);
651
+ console.log(
652
+ `Calculated backoff delay: ${result}ms (attempt ${attempt + 1}), exponentialDelay: ${exponentialDelay}, jitter: ${jitter}`
653
+ );
654
+ return result;
628
655
  }
629
656
  async function withRetry(fn, config) {
630
657
  const retryConfig = {
@@ -705,6 +732,211 @@ var RateLimiter = class {
705
732
  }
706
733
  };
707
734
 
735
+ // src/api/helpers.ts
736
+ var PAGE_LINK_FIELDS = "sys,fields.cmsLabel,fields.title,fields.slug,fields.featuredImage,fields.backgroundColour,fields.textColour,fields.indexed,fields.hidden,fields.tags";
737
+ 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";
738
+ var ARTICLE_TYPE_LINK_FIELDS = "sys,fields.name,fields.slug,fields.featuredImage,fields.backgroundColour,fields.textColour,fields.indexed,fields.hidden";
739
+ var TAG_LINK_FIELDS = "sys,fields.cmsLabel,fields.name,fields.slug,fields.featuredImage,fields.backgroundColour,fields.textColour,fields.indexed,fields.hidden";
740
+ var PERSON_LINK_FIELDS = "sys,fields.name,fields.slug,fields.media,fields.backgroundColour,fields.textColour,fields.indexed,fields.hidden";
741
+ function convertAllAssets(response, context) {
742
+ const visuals = /* @__PURE__ */ new Map();
743
+ const assets = response.includes?.Asset;
744
+ if (assets && assets.length > 0) {
745
+ for (const asset of assets) {
746
+ const visual = convertAssetToVisual(context, asset);
747
+ if (visual) {
748
+ visuals.set(visual.id, visual);
749
+ }
750
+ }
751
+ }
752
+ return visuals;
753
+ }
754
+ function convertAllIncludes(response) {
755
+ const includes = /* @__PURE__ */ new Map();
756
+ const entries = [...response.items, ...response.includes?.Entry || []];
757
+ if (entries && entries.length > 0) {
758
+ for (const entry of entries) {
759
+ if (entry?.sys && entry.fields) {
760
+ includes.set(entry.sys.id, {
761
+ id: entry.sys.id,
762
+ type: entry.sys.contentType.sys.id,
763
+ entry
764
+ });
765
+ }
766
+ }
767
+ }
768
+ return includes;
769
+ }
770
+ async function fetchSingleEntity(context, config, fetchConfig, options) {
771
+ const client = getContentfulClient(config, options?.preview);
772
+ const cacheTags = getCacheTags(
773
+ fetchConfig.cacheTagType,
774
+ fetchConfig.cacheTagIdentifier,
775
+ options?.preview
776
+ );
777
+ const requestOptions = {
778
+ ...options,
779
+ next: {
780
+ ...options?.next,
781
+ tags: cacheTags
782
+ }
783
+ };
784
+ const fetchFn = async () => {
785
+ const response = await client.getEntries(
786
+ {
787
+ content_type: fetchConfig.contentType,
788
+ ...fetchConfig.query,
789
+ include: 10,
790
+ locale: options?.locale,
791
+ limit: 1
792
+ },
793
+ requestOptions
794
+ );
795
+ const entry = response.items[0];
796
+ if (!entry || !entry.fields) {
797
+ return { data: null, errors: [] };
798
+ }
799
+ try {
800
+ const assets = convertAllAssets(response, context);
801
+ const includes = convertAllIncludes(response);
802
+ const fullContext = {
803
+ ...context,
804
+ includes,
805
+ assets,
806
+ errors: []
807
+ };
808
+ const converted = fetchConfig.resolver(fullContext, entry);
809
+ if (fullContext.errors.length > 0 && typeof process !== "undefined" && process.env?.NODE_ENV === "production") {
810
+ console.error(`CMS conversion errors for ${fetchConfig.contentType}:`, {
811
+ entryId: entry.sys.id,
812
+ ...fetchConfig.errorLogContext,
813
+ errors: fullContext.errors
814
+ });
815
+ }
816
+ return { data: converted, errors: fullContext.errors };
817
+ } catch (error) {
818
+ const entryId = entry.sys.id;
819
+ const entryType = entry.sys.contentType?.sys?.id;
820
+ const errorMessage = error instanceof Error ? error.message : "Unknown conversion error";
821
+ const cmsError = {
822
+ entryId,
823
+ entryType,
824
+ message: errorMessage,
825
+ error
826
+ };
827
+ return { data: null, errors: [cmsError] };
828
+ }
829
+ };
830
+ if (options?.retry) {
831
+ return await withRetry(fetchFn, options.retry);
832
+ }
833
+ return await fetchFn();
834
+ }
835
+ async function fetchAllLinks(contentType, client, requestOptions, converter, context, pageSize = 100, select) {
836
+ const allLinks = [];
837
+ const errors = [];
838
+ let skip = 0;
839
+ let hasMore = true;
840
+ const fetchFn = async () => {
841
+ while (hasMore) {
842
+ try {
843
+ const response = await client.getEntries(
844
+ {
845
+ content_type: contentType,
846
+ include: 2,
847
+ // Minimal include for link-only fetching
848
+ locale: requestOptions?.locale,
849
+ limit: pageSize,
850
+ skip,
851
+ ...select && { select }
852
+ },
853
+ requestOptions
854
+ );
855
+ if (response.items.length === 0) {
856
+ hasMore = false;
857
+ break;
858
+ }
859
+ const includes = convertAllIncludes(response);
860
+ const assets = convertAllAssets(response, context);
861
+ const fullContext = {
862
+ ...context,
863
+ includes,
864
+ assets,
865
+ errors: []
866
+ };
867
+ for (const entry of response.items) {
868
+ if (!entry.fields) continue;
869
+ try {
870
+ const converted = converter(
871
+ fullContext,
872
+ entry
873
+ );
874
+ converted.lastModified = entry.sys.updatedAt ? new Date(entry.sys.updatedAt) : void 0;
875
+ allLinks.push(converted);
876
+ } catch (error) {
877
+ const entryId = entry.sys.id;
878
+ const entryType = entry.sys.contentType?.sys?.id;
879
+ const errorMessage = error instanceof Error ? error.message : "Unknown conversion error";
880
+ errors.push({
881
+ entryId,
882
+ entryType,
883
+ message: errorMessage,
884
+ error
885
+ });
886
+ }
887
+ }
888
+ skip += pageSize;
889
+ if (skip >= response.total) {
890
+ hasMore = false;
891
+ }
892
+ } catch (error) {
893
+ console.error("Error fetching links", typeof error, error, JSON.stringify(error, null, 2));
894
+ throw error;
895
+ }
896
+ }
897
+ return { data: allLinks, errors };
898
+ };
899
+ return await fetchFn();
900
+ }
901
+
902
+ // src/api/article.ts
903
+ async function contentfulArticleRest(context, config, slug, articleTypeSlug, options) {
904
+ return fetchSingleEntity(
905
+ context,
906
+ config,
907
+ {
908
+ contentType: "article",
909
+ cacheTagType: "article",
910
+ cacheTagIdentifier: slug,
911
+ query: {
912
+ "fields.slug": slug,
913
+ "fields.articleType.sys.contentType.sys.id": "articleType",
914
+ "fields.articleType.fields.slug": articleTypeSlug
915
+ },
916
+ resolver: (ctx, entry) => ctx.articleResolver(ctx, entry),
917
+ errorLogContext: { slug, articleTypeSlug }
918
+ },
919
+ options
920
+ );
921
+ }
922
+
923
+ // src/api/article-type.ts
924
+ async function contentfulArticleTypeRest(context, config, indexPageSlug, options) {
925
+ return fetchSingleEntity(
926
+ context,
927
+ config,
928
+ {
929
+ contentType: "articleType",
930
+ cacheTagType: "articleType",
931
+ cacheTagIdentifier: indexPageSlug,
932
+ query: { "fields.indexPageSlug": indexPageSlug },
933
+ resolver: (ctx, entry) => ctx.articleTypeResolver(ctx, entry),
934
+ errorLogContext: { indexPageSlug }
935
+ },
936
+ options
937
+ );
938
+ }
939
+
708
940
  // src/converters/resolver.ts
709
941
  function resolveHelper(context, fromId, entry, getResolver) {
710
942
  const id = entry.sys.id;
@@ -758,17 +990,22 @@ function resolveNavigationItem(context, fromId, entry) {
758
990
  return result;
759
991
  }
760
992
  function resolveCollectionContent(context, fromId, entry) {
761
- return resolveHelper(context, fromId, entry, (type) => {
762
- const resolver = context.contentResolver.get(type);
763
- if (resolver) {
764
- return resolver;
765
- }
766
- const linkResolver = context.linkResolver.get(type);
767
- if (linkResolver) {
768
- return linkResolver;
993
+ return resolveHelper(
994
+ context,
995
+ fromId,
996
+ entry,
997
+ (type) => {
998
+ const resolver = context.contentResolver.get(type);
999
+ if (resolver) {
1000
+ return resolver;
1001
+ }
1002
+ const linkResolver = context.linkResolver.get(type);
1003
+ if (linkResolver) {
1004
+ return linkResolver;
1005
+ }
1006
+ return void 0;
769
1007
  }
770
- return void 0;
771
- });
1008
+ );
772
1009
  }
773
1010
  function resolvePageContent(context, fromId, entry) {
774
1011
  const id = entry.sys.id;
@@ -973,215 +1210,25 @@ function resolveRichTextDocument(context, fromId, richText) {
973
1210
  return { json: resolved };
974
1211
  }
975
1212
 
976
- // src/converters/collection.ts
977
- function baseCollectionConverter(context, entry) {
978
- const { sys, fields } = entry;
979
- const { id } = sys;
1213
+ // src/converters/navigationItem.ts
1214
+ function createLink(context, entry) {
980
1215
  const {
981
- // Fields requiring transformation
982
- backgroundVisual: bgVisual,
983
- mobileBackgroundVisual: mobileBgVisual,
984
- visual: visualField,
985
- mobileVisual: mobileVisualField,
986
- visualCustomSize,
987
- icon: iconField,
988
- links: linksField,
989
- contents: contentsField,
990
- body: bodyField,
991
- additionalCopy: additionalCopyField,
992
- // Field name change
993
- collectionType,
994
- showHeading,
995
- heading,
996
- // Already handled elsewhere
997
- cmsLabel,
998
- ...simpleFields
999
- // anchor, backgroundColour, textColour, preHeading, heading, postHeading, backgroundOverlayOpacity
1216
+ sys: { id },
1217
+ fields
1218
+ } = entry;
1219
+ const {
1220
+ title,
1221
+ link,
1222
+ text,
1223
+ internal,
1224
+ icon: navIcon,
1225
+ mobileIcon: mobileNavIcon,
1226
+ useTitle,
1227
+ ...otherFields
1000
1228
  } = fields;
1001
- const backgroundVisual = createResponsiveVisual(
1002
- lookupAsset(context, bgVisual),
1003
- lookupAsset(context, mobileBgVisual)
1004
- );
1005
- const visual = createResponsiveVisual(
1006
- lookupMediaEntry(context, visualField),
1007
- lookupMediaEntry(context, mobileVisualField),
1008
- visualCustomSize
1009
- );
1010
- const collection = {
1011
- type: "Collection",
1012
- id,
1013
- name: cmsLabel,
1014
- cmsLabel,
1015
- collectionType,
1016
- ...DEFAULT_POSITION_FIELDS,
1017
- ...simpleFields,
1018
- heading: showHeading ? heading : void 0,
1019
- body: resolveRichTextDocument(context, id, bodyField),
1020
- additionalCopy: resolveRichTextDocument(context, id, additionalCopyField),
1021
- icon: lookupAsset(context, iconField),
1022
- backgroundVisual,
1023
- visual,
1024
- links: linksField?.map((link) => resolveLink(context, id, link)),
1025
- contents: contentsField?.map((content) => resolveCollectionContent(context, id, content))
1026
- };
1027
- return collection;
1028
- }
1029
-
1030
- // src/converters/component.ts
1031
- function baseComponentConverter(context, entry) {
1032
- const { sys, fields } = entry;
1033
- const { id } = sys;
1034
- const {
1035
- // Fields requiring transformation
1036
- backgroundVisual: bgVisual,
1037
- mobileBackgroundVisual: mobileBgVisual,
1038
- visual: visualField,
1039
- mobileVisual: mobileVisualField,
1040
- visualCustomSize,
1041
- icon: iconField,
1042
- links: linksField,
1043
- body: bodyField,
1044
- additionalCopy: additionalCopyField,
1045
- // Field name change
1046
- componentType,
1047
- showHeading,
1048
- heading,
1049
- otherMedia: otherMediaField,
1050
- // Already handled elsewhere
1051
- cmsLabel,
1052
- ...simpleFields
1053
- // anchor, backgroundColour, textColour, preHeading, heading, postHeading, backgroundOverlayOpacity
1054
- } = fields;
1055
- const backgroundVisual = createResponsiveVisual(
1056
- lookupAsset(context, bgVisual),
1057
- lookupAsset(context, mobileBgVisual)
1058
- );
1059
- const visual = createResponsiveVisual(
1060
- lookupMediaEntry(context, visualField),
1061
- lookupMediaEntry(context, mobileVisualField),
1062
- visualCustomSize
1063
- );
1064
- const otherMedia = otherMediaField?.map((media) => lookupAsset(context, media));
1065
- const component = {
1066
- type: "Component",
1067
- id,
1068
- name: cmsLabel,
1069
- cmsLabel,
1070
- componentType,
1071
- ...DEFAULT_POSITION_FIELDS,
1072
- ...simpleFields,
1073
- heading: showHeading ? heading : void 0,
1074
- body: resolveRichTextDocument(context, id, bodyField),
1075
- additionalCopy: resolveRichTextDocument(context, id, additionalCopyField),
1076
- icon: lookupAsset(context, iconField),
1077
- backgroundVisual,
1078
- visual,
1079
- links: resolveLinks(context, id, linksField),
1080
- otherMedia: arrayOrUndefined(otherMedia?.filter(notEmpty))
1081
- };
1082
- return component;
1083
- }
1084
-
1085
- // src/converters/link.ts
1086
- function baseLinkConverter(context, entry) {
1087
- const { sys, fields } = entry;
1088
- if (sys.contentType.sys.id !== "link") {
1089
- throw new Error(`Invalid content type: expected "link", got "${sys.contentType.sys.id}"`);
1090
- }
1091
- const id = sys.id;
1092
- const name = fields.name;
1093
- const useName = fields.useName;
1094
- const text = resolveBuildYear(
1095
- useName ? name : fields.linkText ?? makeContentfulTitle(fields.linkText, id, "Link text for ")
1096
- );
1097
- const icon = createResponsiveVisual(
1098
- lookupAsset(context, fields.icon),
1099
- lookupAsset(context, fields.mobileIcon)
1100
- );
1101
- const backgroundColour = fields.backgroundColour ?? null;
1102
- const textColour = fields.textColour ?? null;
1103
- const variant = fields.variant;
1104
- const size = fields.size;
1105
- const baseProps = {
1106
- id,
1107
- useName,
1108
- name,
1109
- text,
1110
- icon,
1111
- backgroundColour,
1112
- textColour,
1113
- variant,
1114
- size
1115
- };
1116
- if (fields.internal) {
1117
- const internalTarget = resolveLink(context, id, fields.internal);
1118
- return {
1119
- ...baseProps,
1120
- type: "Internal link",
1121
- internalType: internalTarget.internalType,
1122
- href: internalTarget.href,
1123
- slug: internalTarget.slug,
1124
- indexed: internalTarget.indexed,
1125
- hidden: internalTarget.hidden,
1126
- tags: internalTarget.tags
1127
- };
1128
- }
1129
- if (fields.external) {
1130
- return {
1131
- ...baseProps,
1132
- type: "External link",
1133
- href: fields.external
1134
- };
1135
- }
1136
- if (fields.downloadAsset) {
1137
- const asset = lookupAsset(context, fields.downloadAsset);
1138
- let href = null;
1139
- if (asset?.image?.type === "Picture") {
1140
- href = asset.image.src;
1141
- } else if (asset?.image?.type === "Svg image") {
1142
- href = asset.image.svgSrc;
1143
- } else if (asset?.video) {
1144
- if (asset.video.type === "Local video") {
1145
- href = asset.video.preview.videoUrl;
1146
- } else if (asset.video.type === "Full video") {
1147
- href = asset.video.full.videoUrl;
1148
- } else if (asset.video.type === "External video") {
1149
- href = asset.video.external;
1150
- }
1151
- }
1152
- return {
1153
- ...baseProps,
1154
- type: "Download link",
1155
- href,
1156
- visual: asset
1157
- };
1158
- }
1159
- return {
1160
- ...baseProps,
1161
- type: "Blank link",
1162
- href: null
1163
- };
1164
- }
1165
-
1166
- // src/converters/navigationItem.ts
1167
- function createLink(context, entry) {
1168
- const {
1169
- sys: { id },
1170
- fields
1171
- } = entry;
1172
- const {
1173
- title,
1174
- link,
1175
- text,
1176
- internal,
1177
- icon: navIcon,
1178
- mobileIcon: mobileNavIcon,
1179
- useTitle,
1180
- ...otherFields
1181
- } = fields;
1182
- const icon = createResponsiveVisual(
1183
- lookupAsset(context, navIcon),
1184
- lookupAsset(context, mobileNavIcon)
1229
+ const icon = createResponsiveVisual(
1230
+ lookupAsset(context, navIcon),
1231
+ lookupAsset(context, mobileNavIcon)
1185
1232
  );
1186
1233
  const realText = useTitle ? resolveBuildYear(title) : text ? resolveBuildYear(text) : void 0;
1187
1234
  if (link) {
@@ -1236,14 +1283,13 @@ function resolveNavigation(context, link) {
1236
1283
  if (!fields) {
1237
1284
  return void 0;
1238
1285
  }
1239
- const entries = fields.entries?.map((item) => resolveNavigationItem(context, id, item)) ?? [];
1240
- const { name, textColour, backgroundColour } = fields;
1286
+ const { name, entries: fieldEntries, ...rest } = fields;
1287
+ const entries = fieldEntries?.map((item) => resolveNavigationItem(context, id, item)) ?? [];
1241
1288
  return {
1242
1289
  id,
1243
1290
  name,
1244
1291
  entries,
1245
- textColour,
1246
- backgroundColour
1292
+ ...rest
1247
1293
  };
1248
1294
  }
1249
1295
 
@@ -1276,12 +1322,25 @@ function resolveTemplate(context, link) {
1276
1322
  };
1277
1323
  }
1278
1324
 
1279
- // src/converters/page.ts
1280
- function basePageConverter(context, entry) {
1281
- const {
1282
- sys: { id },
1283
- fields
1284
- } = entry;
1325
+ // src/converters/article.ts
1326
+ function resolveArticleTemplateHierarchy(context, articleTypeLink, directTemplateLink) {
1327
+ if (directTemplateLink) {
1328
+ return resolveTemplate(context, directTemplateLink);
1329
+ }
1330
+ const articleTypeEntry = context.includes.get(articleTypeLink.sys.id);
1331
+ if (!articleTypeEntry) {
1332
+ console.warn(`ArticleType entry not found for id: ${articleTypeLink.sys.id}`);
1333
+ return null;
1334
+ }
1335
+ const articleTypeFields = articleTypeEntry.entry.fields;
1336
+ if (articleTypeFields?.articleTemplate) {
1337
+ return resolveTemplate(context, articleTypeFields.articleTemplate);
1338
+ }
1339
+ return null;
1340
+ }
1341
+ function baseArticleConverter(context, entry) {
1342
+ const { sys, fields } = entry;
1343
+ const { id } = sys;
1285
1344
  const {
1286
1345
  slug,
1287
1346
  title,
@@ -1289,60 +1348,61 @@ function basePageConverter(context, entry) {
1289
1348
  featuredImage,
1290
1349
  tags,
1291
1350
  content,
1292
- menu: pageMenu,
1293
- footer: pageFooter,
1294
1351
  template: templateLink,
1295
1352
  topContent: topContentLinks,
1296
1353
  bottomContent: bottomContentLinks,
1354
+ articleType,
1297
1355
  ...simpleFields
1298
1356
  } = fields;
1299
- const pageMenuNav = pageMenu ? resolveNavigation(context, pageMenu) : void 0;
1300
- const pageFooterNav = pageFooter ? resolveNavigation(context, pageFooter) : void 0;
1301
- const template = templateLink ? resolveTemplate(context, templateLink) : null;
1357
+ const articleTypeLink = resolveLink(context, id, articleType);
1358
+ const template = resolveArticleTemplateHierarchy(context, articleType, templateLink);
1302
1359
  const topContent = topContentLinks?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
1303
- const pageContent = content?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
1360
+ const articleContent = content?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
1304
1361
  const bottomContent = bottomContentLinks?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
1305
1362
  const preContent = template?.preContent ?? [];
1306
1363
  const postContent = template?.postContent ?? [];
1307
1364
  const contents = addPositionMetadata([
1308
1365
  ...topContent,
1309
1366
  ...preContent,
1310
- ...pageContent,
1367
+ ...articleContent,
1311
1368
  ...postContent,
1312
1369
  ...bottomContent
1313
1370
  ]);
1314
- const page = {
1315
- type: "Page",
1371
+ const article = {
1372
+ type: "Article",
1316
1373
  id,
1317
- isHomePage: slug === "index",
1318
1374
  slug,
1319
- title: makeContentfulTitle(title, id),
1320
- description: makeContentfulDescription(description, id),
1375
+ title: makeContentfulTitle(title, sys.id),
1376
+ description: makeContentfulDescription(description, sys.id),
1321
1377
  featuredImage: lookupAsset(context, featuredImage),
1378
+ articleType: articleTypeLink,
1322
1379
  tags: tags?.map((tag) => resolveLink(context, id, tag)),
1323
1380
  contents,
1324
1381
  ...simpleFields,
1325
- menu: pageMenuNav ?? template?.menu,
1326
- footer: pageFooterNav ?? template?.footer
1382
+ // Note: summary field exists in Contentful but is not part of IArticle interface
1383
+ // Keeping it in simpleFields for potential future use
1384
+ menu: template?.menu,
1385
+ footer: template?.footer
1327
1386
  };
1328
- return page;
1387
+ return article;
1329
1388
  }
1330
- function calculatePageHref(slug) {
1331
- if (slug === "index") {
1332
- return "/";
1333
- }
1389
+ function calculateArticleTypeHref(slug) {
1334
1390
  return `/${slug}/`;
1335
1391
  }
1336
- function basePageLinkConverter(context, entry) {
1337
- const {
1338
- sys: { id, contentType },
1339
- fields
1340
- } = entry;
1341
- if (contentType.sys.id !== "page") {
1342
- throw new Error(`Invalid content type: expected "page", got "${contentType.sys.id}"`);
1392
+ function calculateArticleHref(articleTypeSlug, slug) {
1393
+ return `${calculateArticleTypeHref(articleTypeSlug)}${slug}/`;
1394
+ }
1395
+ function baseArticleLinkConverter(context, entry) {
1396
+ const { sys, fields } = entry;
1397
+ if (sys.contentType.sys.id !== "article") {
1398
+ throw new Error(`Invalid content type: expected "article", got "${sys.contentType.sys.id}"`);
1343
1399
  }
1400
+ const articleTypeLink = resolveLink(context, sys.id, fields.articleType);
1401
+ const tags = fields.tags?.map((tag) => resolveLink(context, sys.id, tag));
1402
+ const primaryTag = tags?.[0] ?? void 0;
1403
+ const author = fields.author ? resolveLink(context, sys.id, fields.author) : void 0;
1344
1404
  return createInternalLink(
1345
- id,
1405
+ sys.id,
1346
1406
  {
1347
1407
  cmsLabel: fields.cmsLabel,
1348
1408
  title: fields.title,
@@ -1354,58 +1414,30 @@ function basePageLinkConverter(context, entry) {
1354
1414
  slug: fields.slug
1355
1415
  },
1356
1416
  context,
1357
- calculatePageHref(fields.slug),
1358
- "Page",
1359
- { tags: fields.tags?.map((tag) => resolveLink(context, id, tag)) }
1417
+ calculateArticleHref(articleTypeLink.slug, fields.slug),
1418
+ "Article",
1419
+ {
1420
+ tags,
1421
+ primaryTag,
1422
+ articleType: articleTypeLink,
1423
+ date: fields.date,
1424
+ author
1425
+ }
1360
1426
  );
1361
1427
  }
1362
- function calculatePageVariantHref(slug) {
1363
- return `/${slug}/`;
1364
- }
1365
- function basePageVariantLinkConverter(context, entry) {
1366
- const {
1367
- sys: { id, contentType },
1368
- fields
1369
- } = entry;
1370
- if (contentType.sys.id !== "pageVariant") {
1371
- throw new Error(`Invalid content type: expected "pageVariant", got "${contentType.sys.id}"`);
1428
+ function baseArticleTypeLinkConverter(context, entry) {
1429
+ const { sys, fields } = entry;
1430
+ if (sys.contentType.sys.id !== "articleType") {
1431
+ throw new Error(
1432
+ `Invalid content type: expected "articleType", got "${sys.contentType.sys.id}"`
1433
+ );
1372
1434
  }
1373
1435
  return createInternalLink(
1374
- id,
1375
- {
1376
- cmsLabel: fields.cmsLabel,
1377
- title: fields.title,
1378
- featuredImage: fields.featuredImage,
1379
- backgroundColour: fields.backgroundColour,
1380
- textColour: fields.textColour,
1381
- indexed: fields.indexed,
1382
- hidden: fields.hidden,
1383
- slug: fields.slug
1384
- },
1385
- context,
1386
- calculatePageVariantHref(fields.slug),
1387
- "Page",
1388
- { tags: fields.tags?.map((tag) => resolveLink(context, id, tag)) }
1389
- );
1390
- }
1391
-
1392
- // src/converters/person.ts
1393
- function calculatePersonHref(slug) {
1394
- return `/people/${slug}/`;
1395
- }
1396
- function basePersonLinkConverter(context, entry) {
1397
- const { sys, fields } = entry;
1398
- if (sys.contentType.sys.id !== "person") {
1399
- throw new Error(`Invalid content type: expected "person", got "${sys.contentType.sys.id}"`);
1400
- }
1401
- return createInternalLink(
1402
- sys.id,
1436
+ sys.id,
1403
1437
  {
1404
1438
  cmsLabel: fields.name,
1405
- // Person has no cmsLabel, use name field
1406
1439
  title: fields.name,
1407
- featuredImage: fields.media,
1408
- // Person uses 'media' not 'featuredImage'
1440
+ featuredImage: fields.featuredImage,
1409
1441
  backgroundColour: fields.backgroundColour,
1410
1442
  textColour: fields.textColour,
1411
1443
  indexed: fields.indexed,
@@ -1413,137 +1445,161 @@ function basePersonLinkConverter(context, entry) {
1413
1445
  slug: fields.slug
1414
1446
  },
1415
1447
  context,
1416
- calculatePersonHref(fields.slug),
1417
- "Person"
1448
+ calculateArticleTypeHref(fields.slug),
1449
+ "ArticleType",
1450
+ { indexPageSlug: fields.indexPageSlug }
1418
1451
  );
1419
1452
  }
1420
-
1421
- // src/converters/article.ts
1422
- function resolveArticleTemplateHierarchy(context, articleTypeLink, directTemplateLink) {
1423
- if (directTemplateLink) {
1424
- return resolveTemplate(context, directTemplateLink);
1425
- }
1426
- const articleTypeEntry = context.includes.get(articleTypeLink.sys.id);
1427
- if (!articleTypeEntry) {
1428
- console.warn(`ArticleType entry not found for id: ${articleTypeLink.sys.id}`);
1429
- return null;
1430
- }
1431
- const articleTypeFields = articleTypeEntry.entry.fields;
1432
- if (articleTypeFields?.articleTemplate) {
1433
- return resolveTemplate(context, articleTypeFields.articleTemplate);
1434
- }
1435
- return null;
1436
- }
1437
- function baseArticleConverter(context, entry) {
1453
+ function baseArticleTypeConverter(context, entry) {
1438
1454
  const { sys, fields } = entry;
1439
1455
  const { id } = sys;
1440
1456
  const {
1441
- slug,
1442
- title,
1443
- description,
1457
+ indexPageSlug,
1458
+ indexPageName,
1459
+ indexPageDescription,
1444
1460
  featuredImage,
1445
- tags,
1446
- content,
1447
- template: templateLink,
1448
- topContent: topContentLinks,
1449
- bottomContent: bottomContentLinks,
1450
- articleType,
1451
- ...simpleFields
1461
+ menu: menuLink,
1462
+ footer: footerLink,
1463
+ indexPageTemplate: templateLink,
1464
+ indexPageTopContent: topContentLinks,
1465
+ structuredData,
1466
+ slug: _slug,
1467
+ ...other
1452
1468
  } = fields;
1453
- const articleTypeLink = resolveLink(context, id, articleType);
1454
- const template = resolveArticleTemplateHierarchy(context, articleType, templateLink);
1469
+ const template = templateLink ? resolveTemplate(context, templateLink) : null;
1470
+ const menu = menuLink ? resolveNavigation(context, menuLink) : template?.menu;
1471
+ const footer = footerLink ? resolveNavigation(context, footerLink) : template?.footer;
1455
1472
  const topContent = topContentLinks?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
1456
- const articleContent = content?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
1457
- const bottomContent = bottomContentLinks?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
1458
1473
  const preContent = template?.preContent ?? [];
1459
1474
  const postContent = template?.postContent ?? [];
1460
- const contents = addPositionMetadata([
1461
- ...topContent,
1462
- ...preContent,
1463
- ...articleContent,
1464
- ...postContent,
1465
- ...bottomContent
1466
- ]);
1467
- const article = {
1468
- type: "Article",
1475
+ const contents = addPositionMetadata([...topContent, ...preContent, ...postContent]);
1476
+ const articleType = {
1477
+ type: "Article type",
1469
1478
  id,
1470
- slug,
1471
- title: makeContentfulTitle(title, sys.id),
1472
- description: makeContentfulDescription(description, sys.id),
1479
+ slug: indexPageSlug,
1480
+ title: makeContentfulTitle(indexPageName, sys.id),
1481
+ description: makeContentfulDescription(indexPageDescription, sys.id),
1473
1482
  featuredImage: lookupAsset(context, featuredImage),
1474
- articleType: articleTypeLink,
1475
- tags: tags?.map((tag) => resolveLink(context, id, tag)),
1476
1483
  contents,
1477
- ...simpleFields,
1478
- // Note: summary field exists in Contentful but is not part of IArticle interface
1479
- // Keeping it in simpleFields for potential future use
1480
- menu: template?.menu,
1481
- footer: template?.footer
1484
+ structuredData,
1485
+ menu,
1486
+ footer,
1487
+ ...other
1482
1488
  };
1483
- return article;
1484
- }
1485
- function calculateArticleTypeHref(slug) {
1486
- return `/${slug}/`;
1487
- }
1488
- function calculateArticleHref(articleTypeSlug, slug) {
1489
- return `${calculateArticleTypeHref(articleTypeSlug)}${slug}/`;
1489
+ return articleType;
1490
1490
  }
1491
- function baseArticleLinkConverter(context, entry) {
1491
+
1492
+ // src/converters/collection.ts
1493
+ function baseCollectionConverter(context, entry) {
1492
1494
  const { sys, fields } = entry;
1493
- if (sys.contentType.sys.id !== "article") {
1494
- throw new Error(`Invalid content type: expected "article", got "${sys.contentType.sys.id}"`);
1495
- }
1496
- const articleTypeLink = resolveLink(context, sys.id, fields.articleType);
1497
- return createInternalLink(
1498
- sys.id,
1499
- {
1500
- cmsLabel: fields.cmsLabel,
1501
- title: fields.title,
1502
- featuredImage: fields.featuredImage,
1503
- backgroundColour: fields.backgroundColour,
1504
- textColour: fields.textColour,
1505
- indexed: fields.indexed,
1506
- hidden: fields.hidden,
1507
- slug: fields.slug
1508
- },
1509
- context,
1510
- calculateArticleHref(articleTypeLink.slug, fields.slug),
1511
- "Article",
1512
- {
1513
- tags: fields.tags?.map((tag) => resolveLink(context, sys.id, tag)),
1514
- articleType: articleTypeLink
1515
- }
1495
+ const { id } = sys;
1496
+ const {
1497
+ // Fields requiring transformation
1498
+ backgroundVisual: bgVisual,
1499
+ mobileBackgroundVisual: mobileBgVisual,
1500
+ visual: visualField,
1501
+ mobileVisual: mobileVisualField,
1502
+ visualCustomSize,
1503
+ icon: iconField,
1504
+ links: linksField,
1505
+ contents: contentsField,
1506
+ body: bodyField,
1507
+ additionalCopy: additionalCopyField,
1508
+ showHeading,
1509
+ heading,
1510
+ // Already handled elsewhere
1511
+ cmsLabel,
1512
+ ...simpleFields
1513
+ // anchor, backgroundColour, textColour, preHeading, heading, postHeading, backgroundOverlayOpacity
1514
+ } = fields;
1515
+ const backgroundVisual = createResponsiveVisual(
1516
+ lookupAsset(context, bgVisual),
1517
+ lookupAsset(context, mobileBgVisual)
1518
+ );
1519
+ const visual = createResponsiveVisual(
1520
+ lookupMediaEntry(context, visualField),
1521
+ lookupMediaEntry(context, mobileVisualField),
1522
+ visualCustomSize
1516
1523
  );
1524
+ const collection = {
1525
+ type: "Collection",
1526
+ id,
1527
+ name: cmsLabel,
1528
+ cmsLabel,
1529
+ ...DEFAULT_POSITION_FIELDS,
1530
+ ...simpleFields,
1531
+ heading: showHeading ? heading : void 0,
1532
+ body: resolveRichTextDocument(context, id, bodyField),
1533
+ additionalCopy: resolveRichTextDocument(context, id, additionalCopyField),
1534
+ icon: lookupAsset(context, iconField),
1535
+ backgroundVisual,
1536
+ visual,
1537
+ links: linksField?.map((link) => resolveLink(context, id, link)),
1538
+ contents: contentsField?.map((content) => resolveCollectionContent(context, id, content))
1539
+ };
1540
+ return collection;
1517
1541
  }
1518
- function baseArticleTypeLinkConverter(context, entry) {
1542
+
1543
+ // src/converters/component.ts
1544
+ function baseComponentConverter(context, entry) {
1519
1545
  const { sys, fields } = entry;
1520
- if (sys.contentType.sys.id !== "articleType") {
1521
- throw new Error(
1522
- `Invalid content type: expected "articleType", got "${sys.contentType.sys.id}"`
1523
- );
1524
- }
1525
- return createInternalLink(
1526
- sys.id,
1527
- {
1528
- cmsLabel: fields.name,
1529
- title: fields.name,
1530
- featuredImage: fields.featuredImage,
1531
- backgroundColour: fields.backgroundColour,
1532
- textColour: fields.textColour,
1533
- indexed: fields.indexed,
1534
- hidden: fields.hidden,
1535
- slug: fields.slug
1536
- },
1537
- context,
1538
- calculateArticleTypeHref(fields.slug),
1539
- "ArticleType"
1546
+ const { id } = sys;
1547
+ const {
1548
+ // Fields requiring transformation
1549
+ backgroundVisual: bgVisual,
1550
+ mobileBackgroundVisual: mobileBgVisual,
1551
+ visual: visualField,
1552
+ mobileVisual: mobileVisualField,
1553
+ visualCustomSize,
1554
+ icon: iconField,
1555
+ links: linksField,
1556
+ body: bodyField,
1557
+ additionalCopy: additionalCopyField,
1558
+ // Field name change
1559
+ showHeading,
1560
+ heading,
1561
+ otherMedia: otherMediaField,
1562
+ // Already handled elsewhere
1563
+ cmsLabel,
1564
+ ...simpleFields
1565
+ // anchor, backgroundColour, textColour, preHeading, heading, postHeading, backgroundOverlayOpacity
1566
+ } = fields;
1567
+ const backgroundVisual = createResponsiveVisual(
1568
+ lookupAsset(context, bgVisual),
1569
+ lookupAsset(context, mobileBgVisual)
1570
+ );
1571
+ const visual = createResponsiveVisual(
1572
+ lookupMediaEntry(context, visualField),
1573
+ lookupMediaEntry(context, mobileVisualField),
1574
+ visualCustomSize
1540
1575
  );
1576
+ const otherMedia = otherMediaField?.map((media) => lookupAsset(context, media));
1577
+ const component = {
1578
+ type: "Component",
1579
+ id,
1580
+ name: cmsLabel,
1581
+ cmsLabel,
1582
+ ...DEFAULT_POSITION_FIELDS,
1583
+ ...simpleFields,
1584
+ heading: showHeading ? heading : void 0,
1585
+ body: resolveRichTextDocument(context, id, bodyField),
1586
+ additionalCopy: resolveRichTextDocument(context, id, additionalCopyField),
1587
+ icon: lookupAsset(context, iconField),
1588
+ backgroundVisual,
1589
+ visual,
1590
+ links: resolveLinks(context, id, linksField),
1591
+ otherMedia: arrayOrUndefined(otherMedia?.filter(notEmpty))
1592
+ };
1593
+ return component;
1541
1594
  }
1542
- function baseArticleTypeConverter(context, entry) {
1595
+
1596
+ // src/converters/customType.ts
1597
+ function baseCustomTypeConverter(context, entry) {
1543
1598
  const { sys, fields } = entry;
1544
1599
  const { id } = sys;
1545
1600
  const {
1546
1601
  slug,
1602
+ indexPageSlug,
1547
1603
  indexPageName,
1548
1604
  indexPageDescription,
1549
1605
  featuredImage,
@@ -1551,7 +1607,8 @@ function baseArticleTypeConverter(context, entry) {
1551
1607
  footer: footerLink,
1552
1608
  indexPageTemplate: templateLink,
1553
1609
  indexPageTopContent: topContentLinks,
1554
- ...simpleFields
1610
+ structuredData,
1611
+ ...other
1555
1612
  } = fields;
1556
1613
  const template = templateLink ? resolveTemplate(context, templateLink) : null;
1557
1614
  const menu = menuLink ? resolveNavigation(context, menuLink) : template?.menu;
@@ -1559,36 +1616,179 @@ function baseArticleTypeConverter(context, entry) {
1559
1616
  const topContent = topContentLinks?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
1560
1617
  const preContent = template?.preContent ?? [];
1561
1618
  const postContent = template?.postContent ?? [];
1562
- const indexPageContent = addPositionMetadata([...topContent, ...preContent, ...postContent]);
1563
- const articleType = {
1564
- type: "Article type",
1619
+ const contents = addPositionMetadata([...topContent, ...preContent, ...postContent]);
1620
+ const customType = {
1621
+ type: "Custom type",
1565
1622
  id,
1566
1623
  slug,
1624
+ indexPageSlug,
1625
+ indexPageName: makeContentfulTitle(indexPageName, sys.id),
1626
+ indexPageDescription: makeContentfulDescription(indexPageDescription, sys.id),
1567
1627
  title: makeContentfulTitle(indexPageName, sys.id),
1568
1628
  description: makeContentfulDescription(indexPageDescription, sys.id),
1569
1629
  featuredImage: lookupAsset(context, featuredImage),
1570
- indexPageContent,
1571
- ...simpleFields,
1630
+ contents,
1631
+ structuredData,
1572
1632
  menu,
1573
- footer
1633
+ footer,
1634
+ ...other
1574
1635
  };
1575
- return articleType;
1636
+ return customType;
1576
1637
  }
1577
1638
 
1578
- // src/converters/tag.ts
1579
- function calculateTagHref(slug) {
1580
- return `/tag/${slug}/`;
1581
- }
1582
- function baseTagLinkConverter(context, entry) {
1639
+ // src/converters/link.ts
1640
+ function baseLinkConverter(context, entry) {
1583
1641
  const { sys, fields } = entry;
1584
- if (sys.contentType.sys.id !== "tag") {
1585
- throw new Error(`Invalid content type: expected "tag", got "${sys.contentType.sys.id}"`);
1642
+ if (sys.contentType.sys.id !== "link") {
1643
+ throw new Error(`Invalid content type: expected "link", got "${sys.contentType.sys.id}"`);
1644
+ }
1645
+ const id = sys.id;
1646
+ const name = fields.name;
1647
+ const useName = fields.useName;
1648
+ const text = resolveBuildYear(
1649
+ useName ? name : fields.linkText ?? makeContentfulTitle(fields.linkText, id, "Link text for ")
1650
+ );
1651
+ const icon = createResponsiveVisual(
1652
+ lookupAsset(context, fields.icon),
1653
+ lookupAsset(context, fields.mobileIcon)
1654
+ );
1655
+ const backgroundColour = fields.backgroundColour ?? null;
1656
+ const textColour = fields.textColour ?? null;
1657
+ const variant = fields.variant;
1658
+ const size = fields.size;
1659
+ const baseProps = {
1660
+ id,
1661
+ useName,
1662
+ name,
1663
+ text,
1664
+ icon,
1665
+ backgroundColour,
1666
+ textColour,
1667
+ variant,
1668
+ size
1669
+ };
1670
+ if (fields.internal) {
1671
+ const internalTarget = resolveLink(context, id, fields.internal);
1672
+ return {
1673
+ ...baseProps,
1674
+ type: "Internal link",
1675
+ internalType: internalTarget.internalType,
1676
+ href: internalTarget.href,
1677
+ slug: internalTarget.slug,
1678
+ indexed: internalTarget.indexed,
1679
+ hidden: internalTarget.hidden,
1680
+ tags: internalTarget.tags,
1681
+ title: internalTarget.title,
1682
+ description: internalTarget.description
1683
+ };
1684
+ }
1685
+ if (fields.external) {
1686
+ return {
1687
+ ...baseProps,
1688
+ type: "External link",
1689
+ href: fields.external
1690
+ };
1691
+ }
1692
+ if (fields.downloadAsset) {
1693
+ const asset = lookupAsset(context, fields.downloadAsset);
1694
+ let href = null;
1695
+ if (asset?.image?.type === "Picture") {
1696
+ href = asset.image.src;
1697
+ } else if (asset?.image?.type === "Svg image") {
1698
+ href = asset.image.svgSrc;
1699
+ } else if (asset?.video) {
1700
+ if (asset.video.type === "Local video") {
1701
+ href = asset.video.preview.videoUrl;
1702
+ } else if (asset.video.type === "Full video") {
1703
+ href = asset.video.full.videoUrl;
1704
+ } else if (asset.video.type === "External video") {
1705
+ href = asset.video.external;
1706
+ }
1707
+ }
1708
+ return {
1709
+ ...baseProps,
1710
+ type: "Download link",
1711
+ href,
1712
+ visual: asset
1713
+ };
1714
+ }
1715
+ return {
1716
+ ...baseProps,
1717
+ type: "Blank link",
1718
+ href: null
1719
+ };
1720
+ }
1721
+
1722
+ // src/converters/page.ts
1723
+ function basePageConverter(context, entry) {
1724
+ const {
1725
+ sys: { id },
1726
+ fields
1727
+ } = entry;
1728
+ const {
1729
+ slug,
1730
+ title,
1731
+ description,
1732
+ featuredImage,
1733
+ tags,
1734
+ content,
1735
+ menu: pageMenu,
1736
+ footer: pageFooter,
1737
+ template: templateLink,
1738
+ topContent: topContentLinks,
1739
+ bottomContent: bottomContentLinks,
1740
+ ...simpleFields
1741
+ } = fields;
1742
+ const pageMenuNav = pageMenu ? resolveNavigation(context, pageMenu) : void 0;
1743
+ const pageFooterNav = pageFooter ? resolveNavigation(context, pageFooter) : void 0;
1744
+ const template = templateLink ? resolveTemplate(context, templateLink) : null;
1745
+ const topContent = topContentLinks?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
1746
+ const pageContent = content?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
1747
+ const bottomContent = bottomContentLinks?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
1748
+ const preContent = template?.preContent ?? [];
1749
+ const postContent = template?.postContent ?? [];
1750
+ const contents = addPositionMetadata([
1751
+ ...topContent,
1752
+ ...preContent,
1753
+ ...pageContent,
1754
+ ...postContent,
1755
+ ...bottomContent
1756
+ ]);
1757
+ const page = {
1758
+ type: "Page",
1759
+ id,
1760
+ isHomePage: slug === "index",
1761
+ slug,
1762
+ title: makeContentfulTitle(title, id),
1763
+ description: makeContentfulDescription(description, id),
1764
+ featuredImage: lookupAsset(context, featuredImage),
1765
+ tags: tags?.map((tag) => resolveLink(context, id, tag)),
1766
+ contents,
1767
+ ...simpleFields,
1768
+ menu: pageMenuNav ?? template?.menu,
1769
+ footer: pageFooterNav ?? template?.footer
1770
+ };
1771
+ return page;
1772
+ }
1773
+ function calculatePageHref(slug) {
1774
+ if (slug === "index") {
1775
+ return "/";
1776
+ }
1777
+ return `/${slug}/`;
1778
+ }
1779
+ function basePageLinkConverter(context, entry) {
1780
+ const {
1781
+ sys: { id, contentType },
1782
+ fields
1783
+ } = entry;
1784
+ if (contentType.sys.id !== "page") {
1785
+ throw new Error(`Invalid content type: expected "page", got "${contentType.sys.id}"`);
1586
1786
  }
1587
1787
  return createInternalLink(
1588
- sys.id,
1788
+ id,
1589
1789
  {
1590
1790
  cmsLabel: fields.cmsLabel,
1591
- title: fields.name,
1791
+ title: fields.title,
1592
1792
  featuredImage: fields.featuredImage,
1593
1793
  backgroundColour: fields.backgroundColour,
1594
1794
  textColour: fields.textColour,
@@ -1597,401 +1797,174 @@ function baseTagLinkConverter(context, entry) {
1597
1797
  slug: fields.slug
1598
1798
  },
1599
1799
  context,
1600
- calculateTagHref(fields.slug),
1601
- "Tag"
1800
+ calculatePageHref(fields.slug),
1801
+ "Page",
1802
+ { tags: fields.tags?.map((tag) => resolveLink(context, id, tag)) }
1602
1803
  );
1603
1804
  }
1604
-
1605
- // src/api.ts
1606
- init_utils();
1607
- function convertAllAssets(response, context) {
1608
- const visuals = /* @__PURE__ */ new Map();
1609
- const assets = response.includes?.Asset;
1610
- if (assets && assets.length > 0) {
1611
- for (const asset of assets) {
1612
- const visual = convertAssetToVisual(context, asset);
1613
- if (visual) {
1614
- visuals.set(visual.id, visual);
1615
- }
1616
- }
1617
- }
1618
- return visuals;
1619
- }
1620
- function convertAllIncludes(response) {
1621
- const includes = /* @__PURE__ */ new Map();
1622
- const entries = [...response.items, ...response.includes?.Entry || []];
1623
- if (entries && entries.length > 0) {
1624
- for (const entry of entries) {
1625
- if (entry?.sys && entry.fields) {
1626
- includes.set(entry.sys.id, {
1627
- id: entry.sys.id,
1628
- type: entry.sys.contentType.sys.id,
1629
- entry
1630
- });
1631
- }
1632
- }
1633
- }
1634
- return includes;
1805
+ function calculatePageVariantHref(slug) {
1806
+ return `/${slug}/`;
1635
1807
  }
1636
- async function contentfulPageRest(context, config, slug, options) {
1637
- const client = getContentfulClient(config, options?.preview);
1638
- const cacheTags = getCacheTags("page", slug, options?.preview);
1639
- const requestOptions = {
1640
- ...options,
1641
- next: {
1642
- ...options?.next,
1643
- tags: cacheTags
1644
- }
1645
- };
1646
- const fetchFn = async () => {
1647
- const response = await client.getEntries(
1648
- {
1649
- content_type: "page",
1650
- "fields.slug": slug,
1651
- include: 10,
1652
- locale: options?.locale,
1653
- limit: 1
1654
- },
1655
- requestOptions
1656
- );
1657
- const pageEntry = response.items[0];
1658
- if (!pageEntry || !pageEntry.fields) {
1659
- return { data: null, errors: [] };
1660
- }
1661
- try {
1662
- const assets = convertAllAssets(response, context);
1663
- const includes = convertAllIncludes(response);
1664
- const fullContext = {
1665
- ...context,
1666
- includes,
1667
- assets,
1668
- errors: []
1669
- };
1670
- const converted = fullContext.pageResolver(fullContext, pageEntry);
1671
- if (fullContext.errors.length > 0 && typeof process !== "undefined" && process.env?.NODE_ENV === "production") {
1672
- console.error("CMS conversion errors for page:", {
1673
- pageId: pageEntry.sys.id,
1674
- slug,
1675
- errors: fullContext.errors
1676
- });
1677
- }
1678
- return { data: converted, errors: fullContext.errors };
1679
- } catch (error) {
1680
- const entryId = pageEntry.sys.id;
1681
- const entryType = pageEntry.sys.contentType?.sys?.id;
1682
- const errorMessage = error instanceof Error ? error.message : "Unknown conversion error";
1683
- const cmsError = {
1684
- entryId,
1685
- entryType,
1686
- message: errorMessage,
1687
- error
1688
- };
1689
- return { data: null, errors: [cmsError] };
1690
- }
1691
- };
1692
- if (options?.retry) {
1693
- return await withRetry(fetchFn, options.retry);
1808
+ function basePageVariantLinkConverter(context, entry) {
1809
+ const {
1810
+ sys: { id, contentType },
1811
+ fields
1812
+ } = entry;
1813
+ if (contentType.sys.id !== "pageVariant") {
1814
+ throw new Error(`Invalid content type: expected "pageVariant", got "${contentType.sys.id}"`);
1694
1815
  }
1695
- return await fetchFn();
1696
- }
1697
- function createBaseConverterContext() {
1698
- const linkResolver = /* @__PURE__ */ new Map();
1699
- linkResolver.set("page", basePageLinkConverter);
1700
- linkResolver.set("article", baseArticleLinkConverter);
1701
- linkResolver.set("articleType", baseArticleTypeLinkConverter);
1702
- linkResolver.set("tag", baseTagLinkConverter);
1703
- linkResolver.set("person", basePersonLinkConverter);
1704
- linkResolver.set("pageVariant", basePageVariantLinkConverter);
1705
- linkResolver.set("link", baseLinkConverter);
1706
- const contentResolver = /* @__PURE__ */ new Map();
1707
- contentResolver.set("collection", baseCollectionConverter);
1708
- contentResolver.set("component", baseComponentConverter);
1709
- contentResolver.set(
1710
- "externalComponent",
1711
- baseComponentConverter
1816
+ return createInternalLink(
1817
+ id,
1818
+ {
1819
+ cmsLabel: fields.cmsLabel,
1820
+ title: fields.title,
1821
+ featuredImage: fields.featuredImage,
1822
+ backgroundColour: fields.backgroundColour,
1823
+ textColour: fields.textColour,
1824
+ indexed: fields.indexed,
1825
+ hidden: fields.hidden,
1826
+ slug: fields.slug
1827
+ },
1828
+ context,
1829
+ calculatePageVariantHref(fields.slug),
1830
+ "Page",
1831
+ { tags: fields.tags?.map((tag) => resolveLink(context, id, tag)) }
1712
1832
  );
1713
- return {
1714
- pageResolver: basePageConverter,
1715
- navigationItemResolver: baseNavigationItemConverter,
1716
- articleResolver: baseArticleConverter,
1717
- articleTypeResolver: baseArticleTypeConverter,
1718
- componentResolver: baseComponentConverter,
1719
- collectionResolver: baseCollectionConverter,
1720
- linkResolver,
1721
- contentResolver,
1722
- videoPrefix: ""
1723
- };
1724
1833
  }
1725
- async function contentfulArticleRest(context, config, slug, articleTypeSlug, options) {
1726
- const client = getContentfulClient(config, options?.preview);
1727
- const cacheTags = getCacheTags("article", slug, options?.preview);
1728
- const requestOptions = {
1729
- ...options,
1730
- next: {
1731
- ...options?.next,
1732
- tags: cacheTags
1733
- }
1734
- };
1735
- const fetchFn = async () => {
1736
- const response = await client.getEntries(
1737
- {
1738
- content_type: "article",
1739
- "fields.slug": slug,
1740
- "fields.articleType.sys.contentType.sys.id": "articleType",
1741
- "fields.articleType.fields.slug": articleTypeSlug,
1742
- include: 10,
1743
- locale: options?.locale,
1744
- limit: 1
1745
- },
1746
- requestOptions
1747
- );
1748
- const articleEntry = response.items[0];
1749
- if (!articleEntry || !articleEntry.fields) {
1750
- return { data: null, errors: [] };
1751
- }
1752
- const assets = convertAllAssets(response, context);
1753
- const includes = convertAllIncludes(response);
1754
- const fullContext = {
1755
- ...context,
1756
- includes,
1757
- assets,
1758
- errors: []
1759
- };
1760
- try {
1761
- const converted = fullContext.articleResolver(fullContext, articleEntry);
1762
- if (fullContext.errors.length > 0 && typeof process !== "undefined" && process.env?.NODE_ENV === "production") {
1763
- console.error("CMS conversion errors for article:", {
1764
- articleId: articleEntry.sys.id,
1765
- slug,
1766
- articleTypeSlug,
1767
- errors: fullContext.errors
1768
- });
1769
- }
1770
- return { data: converted, errors: fullContext.errors };
1771
- } catch (error) {
1772
- const entryId = articleEntry.sys.id;
1773
- const entryType = articleEntry.sys.contentType?.sys?.id;
1774
- const errorMessage = error instanceof Error ? error.message : "Unknown conversion error";
1775
- const cmsError = {
1776
- entryId,
1777
- entryType,
1778
- message: errorMessage,
1779
- error
1780
- };
1781
- return { data: null, errors: [cmsError] };
1782
- }
1783
- };
1784
- if (options?.retry) {
1785
- return await withRetry(fetchFn, options.retry);
1786
- }
1787
- return await fetchFn();
1834
+
1835
+ // src/converters/person.ts
1836
+ function calculatePersonHref(slug) {
1837
+ return `/people/${slug}/`;
1788
1838
  }
1789
- async function contentfulArticleTypeRest(context, config, slug, options) {
1790
- const client = getContentfulClient(config, options?.preview);
1791
- const cacheTags = getCacheTags("articleType", slug, options?.preview);
1792
- const requestOptions = {
1793
- ...options,
1794
- next: {
1795
- ...options?.next,
1796
- tags: cacheTags
1797
- }
1798
- };
1799
- const fetchFn = async () => {
1800
- const response = await client.getEntries(
1801
- {
1802
- content_type: "articleType",
1803
- "fields.slug": slug,
1804
- include: 10,
1805
- locale: options?.locale,
1806
- limit: 1
1807
- },
1808
- requestOptions
1809
- );
1810
- const articleTypeEntry = response.items[0];
1811
- if (!articleTypeEntry || !articleTypeEntry.fields) {
1812
- return { data: null, errors: [] };
1813
- }
1814
- const assets = convertAllAssets(response, context);
1815
- const includes = convertAllIncludes(response);
1816
- const fullContext = {
1817
- ...context,
1818
- includes,
1819
- assets,
1820
- errors: []
1821
- };
1822
- try {
1823
- const converted = fullContext.articleTypeResolver(fullContext, articleTypeEntry);
1824
- if (fullContext.errors.length > 0 && typeof process !== "undefined" && process.env?.NODE_ENV === "production") {
1825
- console.error("CMS conversion errors for article type:", {
1826
- articleTypeId: articleTypeEntry.sys.id,
1827
- slug,
1828
- errors: fullContext.errors
1829
- });
1830
- }
1831
- return { data: converted, errors: fullContext.errors };
1832
- } catch (error) {
1833
- const entryId = articleTypeEntry.sys.id;
1834
- const entryType = articleTypeEntry.sys.contentType?.sys?.id;
1835
- const errorMessage = error instanceof Error ? error.message : "Unknown conversion error";
1836
- const cmsError = {
1837
- entryId,
1838
- entryType,
1839
- message: errorMessage,
1840
- error
1841
- };
1842
- return { data: null, errors: [cmsError] };
1843
- }
1844
- };
1845
- if (options?.retry) {
1846
- return await withRetry(fetchFn, options.retry);
1839
+ function basePersonLinkConverter(context, entry) {
1840
+ const { sys, fields } = entry;
1841
+ if (sys.contentType.sys.id !== "person") {
1842
+ throw new Error(`Invalid content type: expected "person", got "${sys.contentType.sys.id}"`);
1843
+ }
1844
+ return createInternalLink(
1845
+ sys.id,
1846
+ {
1847
+ ...fields
1848
+ },
1849
+ context,
1850
+ calculatePersonHref(fields.slug),
1851
+ "Person"
1852
+ );
1853
+ }
1854
+
1855
+ // src/converters/tag.ts
1856
+ function calculateTagHref(slug) {
1857
+ return `/tag/${slug}/`;
1858
+ }
1859
+ function baseTagLinkConverter(context, entry) {
1860
+ const { sys, fields } = entry;
1861
+ if (sys.contentType.sys.id !== "tag") {
1862
+ throw new Error(`Invalid content type: expected "tag", got "${sys.contentType.sys.id}"`);
1847
1863
  }
1848
- return await fetchFn();
1864
+ return createInternalLink(
1865
+ sys.id,
1866
+ {
1867
+ ...fields
1868
+ },
1869
+ context,
1870
+ calculateTagHref(fields.slug),
1871
+ "Tag"
1872
+ );
1849
1873
  }
1850
- function baseArticleLinkConverterWithMetadata(context, entry) {
1851
- const baseLink = baseArticleLinkConverter(context, entry);
1852
- return {
1853
- ...baseLink,
1854
- date: entry.fields.date ? new Date(entry.fields.date) : void 0,
1855
- author: entry.fields.author ? resolveLink(context, baseLink.id, entry.fields.author) : void 0,
1856
- articleType: entry.fields.articleType ? resolveLink(context, baseLink.id, entry.fields.articleType) : void 0
1874
+ function baseTagConverter(context, entry) {
1875
+ const { sys, fields } = entry;
1876
+ const { id } = sys;
1877
+ const {
1878
+ name,
1879
+ slug,
1880
+ description,
1881
+ featuredImage,
1882
+ tagType,
1883
+ menu: menuLink,
1884
+ footer: footerLink,
1885
+ template: templateLink,
1886
+ topContent: topContentLinks,
1887
+ ...rest
1888
+ } = fields;
1889
+ const template = templateLink ? resolveTemplate(context, templateLink) : null;
1890
+ const menu = menuLink ? resolveNavigation(context, menuLink) : template?.menu;
1891
+ const footer = footerLink ? resolveNavigation(context, footerLink) : template?.footer;
1892
+ const topContent = topContentLinks?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
1893
+ const preContent = template?.preContent ?? [];
1894
+ const postContent = template?.postContent ?? [];
1895
+ const contents = addPositionMetadata([...topContent, ...preContent, ...postContent]);
1896
+ const tagTypeEntry = tagType ? context.includes.get(tagType.sys.id) : null;
1897
+ const tagTypeName = tagTypeEntry?.entry?.fields?.name;
1898
+ const tag = {
1899
+ type: "Tag",
1900
+ id,
1901
+ slug,
1902
+ title: makeContentfulTitle(name, sys.id),
1903
+ description: makeContentfulDescription(description, sys.id),
1904
+ featuredImage: lookupAsset(context, featuredImage),
1905
+ tagType: tagTypeName ?? null,
1906
+ contents,
1907
+ ...rest,
1908
+ menu,
1909
+ footer
1857
1910
  };
1911
+ return tag;
1858
1912
  }
1859
- async function getAllArticlesForRelated(context, config, options) {
1860
- const client = getContentfulClient(config, options?.preview);
1861
- const allArticles = [];
1862
- const errors = [];
1863
- const pageSize = 100;
1864
- let skip = 0;
1865
- let hasMore = true;
1866
- const cacheTags = getCacheTags("article", void 0, options?.preview);
1867
- const requestOptions = {
1868
- ...options,
1869
- next: {
1870
- ...options?.next,
1871
- tags: cacheTags
1872
- }
1873
- };
1874
- const fetchFn = async () => {
1875
- while (hasMore) {
1876
- const response = await client.getEntries(
1877
- {
1878
- content_type: "article",
1879
- "fields.indexed": true,
1880
- "fields.hidden[ne]": true,
1881
- order: "-fields.date",
1882
- include: 2,
1883
- // Shallow include - just enough for tags, articleType, author
1884
- locale: options?.locale,
1885
- limit: pageSize,
1886
- skip
1887
- },
1888
- requestOptions
1889
- );
1890
- if (response.items.length === 0) {
1891
- hasMore = false;
1892
- break;
1893
- }
1894
- const assets = convertAllAssets(response, context);
1895
- const includes = convertAllIncludes(response);
1896
- const fullContext = {
1897
- ...context,
1898
- includes,
1899
- assets,
1900
- errors: []
1901
- };
1902
- for (const entry of response.items) {
1903
- if (!entry.fields) continue;
1904
- try {
1905
- const converted = baseArticleLinkConverterWithMetadata(fullContext, entry);
1906
- allArticles.push(converted);
1907
- } catch (error) {
1908
- const entryId = entry.sys.id;
1909
- const entryType = entry.sys.contentType?.sys?.id;
1910
- const errorMessage = error instanceof Error ? error.message : "Unknown conversion error";
1911
- errors.push({
1912
- entryId,
1913
- entryType,
1914
- message: errorMessage,
1915
- error
1916
- });
1917
- }
1918
- }
1919
- skip += pageSize;
1920
- if (skip >= response.total) {
1921
- hasMore = false;
1922
- }
1923
- }
1924
- return { data: allArticles, errors };
1913
+
1914
+ // src/api/context.ts
1915
+ function createBaseConverterContext() {
1916
+ const linkResolver = /* @__PURE__ */ new Map();
1917
+ linkResolver.set("page", basePageLinkConverter);
1918
+ linkResolver.set("article", baseArticleLinkConverter);
1919
+ linkResolver.set(
1920
+ "articleType",
1921
+ baseArticleTypeLinkConverter
1922
+ );
1923
+ linkResolver.set("tag", baseTagLinkConverter);
1924
+ linkResolver.set("person", basePersonLinkConverter);
1925
+ linkResolver.set("pageVariant", basePageVariantLinkConverter);
1926
+ linkResolver.set("link", baseLinkConverter);
1927
+ const contentResolver = /* @__PURE__ */ new Map();
1928
+ contentResolver.set("collection", baseCollectionConverter);
1929
+ contentResolver.set("component", baseComponentConverter);
1930
+ contentResolver.set(
1931
+ "externalComponent",
1932
+ baseComponentConverter
1933
+ );
1934
+ return {
1935
+ pageResolver: basePageConverter,
1936
+ navigationItemResolver: baseNavigationItemConverter,
1937
+ articleResolver: baseArticleConverter,
1938
+ articleTypeResolver: baseArticleTypeConverter,
1939
+ tagResolver: baseTagConverter,
1940
+ customTypeResolver: baseCustomTypeConverter,
1941
+ componentResolver: baseComponentConverter,
1942
+ collectionResolver: baseCollectionConverter,
1943
+ linkResolver,
1944
+ contentResolver,
1945
+ videoPrefix: ""
1925
1946
  };
1926
- if (options?.retry) {
1927
- return await withRetry(fetchFn, options.retry);
1928
- }
1929
- return await fetchFn();
1930
1947
  }
1931
- async function fetchAllLinks(contentType, client, requestOptions, converter, context, pageSize = 100) {
1932
- const allLinks = [];
1933
- const errors = [];
1934
- let skip = 0;
1935
- let hasMore = true;
1936
- const fetchFn = async () => {
1937
- while (hasMore) {
1938
- try {
1939
- const response = await client.getEntries(
1940
- {
1941
- content_type: contentType,
1942
- include: 2,
1943
- // Minimal include for link-only fetching
1944
- locale: requestOptions?.locale,
1945
- limit: pageSize,
1946
- skip
1947
- },
1948
- requestOptions
1949
- );
1950
- if (response.items.length === 0) {
1951
- hasMore = false;
1952
- break;
1953
- }
1954
- const includes = convertAllIncludes(response);
1955
- const assets = convertAllAssets(response, context);
1956
- const fullContext = {
1957
- ...context,
1958
- includes,
1959
- assets,
1960
- errors: []
1961
- };
1962
- for (const entry of response.items) {
1963
- if (!entry.fields) continue;
1964
- try {
1965
- const converted = converter(
1966
- fullContext,
1967
- entry
1968
- );
1969
- allLinks.push(converted);
1970
- } catch (error) {
1971
- const entryId = entry.sys.id;
1972
- const entryType = entry.sys.contentType?.sys?.id;
1973
- const errorMessage = error instanceof Error ? error.message : "Unknown conversion error";
1974
- errors.push({
1975
- entryId,
1976
- entryType,
1977
- message: errorMessage,
1978
- error
1979
- });
1980
- }
1981
- }
1982
- skip += pageSize;
1983
- if (skip >= response.total) {
1984
- hasMore = false;
1985
- }
1986
- } catch (error) {
1987
- console.error("Error fetching links", typeof error, error, JSON.stringify(error, null, 2));
1988
- throw error;
1989
- }
1990
- }
1991
- return { data: allLinks, errors };
1992
- };
1993
- return await fetchFn();
1948
+
1949
+ // src/api/custom-type.ts
1950
+ async function contentfulCustomTypeRest(context, config, indexPageSlug, options) {
1951
+ return fetchSingleEntity(
1952
+ context,
1953
+ config,
1954
+ {
1955
+ contentType: "customType",
1956
+ cacheTagType: "customType",
1957
+ cacheTagIdentifier: indexPageSlug,
1958
+ query: { "fields.indexPageSlug": indexPageSlug },
1959
+ resolver: (ctx, entry) => ctx.customTypeResolver(ctx, entry),
1960
+ errorLogContext: { indexPageSlug }
1961
+ },
1962
+ options
1963
+ );
1994
1964
  }
1965
+
1966
+ // src/api/links.ts
1967
+ init_utils();
1995
1968
  async function contentfulAllPageLinks(context, config, options) {
1996
1969
  const client = getContentfulClient(config, options?.preview);
1997
1970
  const cacheTags = getCacheTags("page", void 0, options?.preview);
@@ -2002,7 +1975,15 @@ async function contentfulAllPageLinks(context, config, options) {
2002
1975
  tags: cacheTags
2003
1976
  }
2004
1977
  };
2005
- const fetchFn = () => fetchAllLinks("page", client, requestOptions, basePageLinkConverter, context);
1978
+ const fetchFn = () => fetchAllLinks(
1979
+ "page",
1980
+ client,
1981
+ requestOptions,
1982
+ basePageLinkConverter,
1983
+ context,
1984
+ 100,
1985
+ PAGE_LINK_FIELDS
1986
+ );
2006
1987
  if (options?.retry) {
2007
1988
  return await withRetry(fetchFn, options.retry);
2008
1989
  }
@@ -2018,7 +1999,15 @@ async function contentfulAllArticleLinks(context, config, options) {
2018
1999
  tags: cacheTags
2019
2000
  }
2020
2001
  };
2021
- const fetchFn = () => fetchAllLinks("article", client, requestOptions, baseArticleLinkConverter, context);
2002
+ const fetchFn = () => fetchAllLinks(
2003
+ "article",
2004
+ client,
2005
+ requestOptions,
2006
+ baseArticleLinkConverter,
2007
+ context,
2008
+ 100,
2009
+ ARTICLE_LINK_FIELDS
2010
+ );
2022
2011
  if (options?.retry) {
2023
2012
  return await withRetry(fetchFn, options.retry);
2024
2013
  }
@@ -2034,7 +2023,15 @@ async function contentfulAllTagLinks(context, config, options) {
2034
2023
  tags: cacheTags
2035
2024
  }
2036
2025
  };
2037
- const fetchFn = () => fetchAllLinks("tag", client, requestOptions, baseTagLinkConverter, context);
2026
+ const fetchFn = () => fetchAllLinks(
2027
+ "tag",
2028
+ client,
2029
+ requestOptions,
2030
+ baseTagLinkConverter,
2031
+ context,
2032
+ 100,
2033
+ TAG_LINK_FIELDS
2034
+ );
2038
2035
  if (options?.retry) {
2039
2036
  return await withRetry(fetchFn, options.retry);
2040
2037
  }
@@ -2050,7 +2047,15 @@ async function contentfulAllPersonLinks(context, config, options) {
2050
2047
  tags: cacheTags
2051
2048
  }
2052
2049
  };
2053
- const fetchFn = () => fetchAllLinks("person", client, requestOptions, basePersonLinkConverter, context);
2050
+ const fetchFn = () => fetchAllLinks(
2051
+ "person",
2052
+ client,
2053
+ requestOptions,
2054
+ basePersonLinkConverter,
2055
+ context,
2056
+ 100,
2057
+ PERSON_LINK_FIELDS
2058
+ );
2054
2059
  if (options?.retry) {
2055
2060
  return await withRetry(fetchFn, options.retry);
2056
2061
  }
@@ -2066,40 +2071,81 @@ async function contentfulAllArticleTypeLinks(context, config, options) {
2066
2071
  tags: cacheTags
2067
2072
  }
2068
2073
  };
2069
- const fetchFn = () => fetchAllLinks("articleType", client, requestOptions, baseArticleTypeLinkConverter, context);
2074
+ const fetchFn = () => fetchAllLinks(
2075
+ "articleType",
2076
+ client,
2077
+ requestOptions,
2078
+ baseArticleTypeLinkConverter,
2079
+ context,
2080
+ 100,
2081
+ ARTICLE_TYPE_LINK_FIELDS
2082
+ );
2070
2083
  if (options?.retry) {
2071
2084
  return await withRetry(fetchFn, options.retry);
2072
2085
  }
2073
2086
  return await fetchFn();
2074
2087
  }
2088
+
2089
+ // src/api/page.ts
2090
+ async function contentfulPageRest(context, config, slug, options) {
2091
+ return fetchSingleEntity(
2092
+ context,
2093
+ config,
2094
+ {
2095
+ contentType: "page",
2096
+ cacheTagType: "page",
2097
+ cacheTagIdentifier: slug,
2098
+ query: { "fields.slug": slug },
2099
+ resolver: (ctx, entry) => ctx.pageResolver(ctx, entry),
2100
+ errorLogContext: { slug }
2101
+ },
2102
+ options
2103
+ );
2104
+ }
2105
+
2106
+ // src/api/related-articles.ts
2075
2107
  function filterRelatedArticles(articles, options) {
2076
- const { articleTypeId, tagIds, authorId, excludeIds, before, after, count } = options;
2108
+ const {
2109
+ excludeArticleIds,
2110
+ articleTypeIds: rawArticleTypeIds,
2111
+ tagIds: rawTagIds,
2112
+ authorIds: rawAuthorIds,
2113
+ before,
2114
+ after,
2115
+ count
2116
+ } = options;
2117
+ const excludeIds = excludeArticleIds ? [...new Set(excludeArticleIds)] : [];
2118
+ const articleTypeIds = rawArticleTypeIds ? [...new Set(rawArticleTypeIds)] : [];
2119
+ const tagIds = rawTagIds ? [...new Set(rawTagIds)] : [];
2120
+ const authorIds = rawAuthorIds ? [...new Set(rawAuthorIds)] : [];
2077
2121
  const scoredArticles = articles.map((article) => {
2078
2122
  let score = 0;
2079
- if (excludeIds?.includes(article.id)) {
2123
+ if (excludeIds.length > 0 && excludeIds.includes(article.id)) {
2080
2124
  return null;
2081
2125
  }
2082
- if (articleTypeId && article.articleType?.id !== articleTypeId) {
2083
- return null;
2084
- }
2085
- if (authorId && article.author?.id !== authorId) {
2086
- return null;
2126
+ if (articleTypeIds.length > 0) {
2127
+ if (!article.articleType?.id || !articleTypeIds.includes(article.articleType.id)) {
2128
+ return null;
2129
+ }
2087
2130
  }
2088
2131
  if (article.date) {
2089
- if (before && article.date > before) {
2132
+ const articleDate = new Date(article.date);
2133
+ if (before && articleDate > before) {
2090
2134
  return null;
2091
2135
  }
2092
- if (after && article.date < after) {
2136
+ if (after && articleDate < after) {
2093
2137
  return null;
2094
2138
  }
2095
2139
  }
2096
- if (tagIds && tagIds.length > 0 && article.tags) {
2140
+ if (tagIds.length > 0 && article.tags) {
2097
2141
  const articleTagIds = article.tags.map((tag) => tag.id);
2098
2142
  const matchingTags = tagIds.filter((tagId) => articleTagIds.includes(tagId));
2099
2143
  score += matchingTags.length * 10;
2100
2144
  }
2101
- if (authorId && article.author?.id === authorId) {
2102
- score += 5;
2145
+ if (authorIds.length > 0 && article.author?.id) {
2146
+ if (authorIds.includes(article.author.id)) {
2147
+ score += 5;
2148
+ }
2103
2149
  }
2104
2150
  return { article, score };
2105
2151
  }).filter((item) => item !== null);
@@ -2108,15 +2154,128 @@ function filterRelatedArticles(articles, options) {
2108
2154
  return b.score - a.score;
2109
2155
  }
2110
2156
  if (a.article.date && b.article.date) {
2111
- return b.article.date.getTime() - a.article.date.getTime();
2157
+ const articleDateA = new Date(a.article.date);
2158
+ const articleDateB = new Date(b.article.date);
2159
+ return articleDateB.getTime() - articleDateA.getTime();
2112
2160
  }
2113
2161
  return 0;
2114
2162
  });
2115
- const topArticles = count ? scoredArticles.slice(0, count) : scoredArticles;
2116
- return topArticles.map(({ article }) => {
2117
- const { date: _date, author: _author, articleType: _articleType, ...cleanLink } = article;
2118
- return cleanLink;
2119
- });
2163
+ const topArticles = count !== void 0 ? scoredArticles.slice(0, count) : scoredArticles;
2164
+ return topArticles.map(({ article }) => article);
2165
+ }
2166
+
2167
+ // src/api/sitemap.ts
2168
+ function linksToSitemapEntries(links, sitemapConfig) {
2169
+ return links.filter((link) => link.indexed !== false && link.hidden !== true && link.href).map((link) => ({
2170
+ url: link.href,
2171
+ lastModified: link.lastModified,
2172
+ changeFrequency: sitemapConfig?.changeFrequency,
2173
+ priority: sitemapConfig?.priority
2174
+ }));
2175
+ }
2176
+ async function contentfulPageSitemapEntries(context, config, sitemapConfig, options) {
2177
+ const response = await contentfulAllPageLinks(context, config, options);
2178
+ return {
2179
+ data: linksToSitemapEntries(response.data, sitemapConfig),
2180
+ errors: response.errors
2181
+ };
2182
+ }
2183
+ async function contentfulArticleSitemapEntries(context, config, sitemapConfig, options) {
2184
+ const response = await contentfulAllArticleLinks(context, config, options);
2185
+ return {
2186
+ data: linksToSitemapEntries(response.data, sitemapConfig),
2187
+ errors: response.errors
2188
+ };
2189
+ }
2190
+ async function contentfulArticleTypeSitemapEntries(context, config, sitemapConfig, options) {
2191
+ const response = await contentfulAllArticleTypeLinks(context, config, options);
2192
+ return {
2193
+ data: linksToSitemapEntries(response.data, sitemapConfig),
2194
+ errors: response.errors
2195
+ };
2196
+ }
2197
+ async function contentfulTagSitemapEntries(context, config, sitemapConfig, options) {
2198
+ const response = await contentfulAllTagLinks(context, config, options);
2199
+ return {
2200
+ data: linksToSitemapEntries(response.data, sitemapConfig),
2201
+ errors: response.errors
2202
+ };
2203
+ }
2204
+ async function contentfulPersonSitemapEntries(context, config, sitemapConfig, options) {
2205
+ const response = await contentfulAllPersonLinks(context, config, options);
2206
+ return {
2207
+ data: linksToSitemapEntries(response.data, sitemapConfig),
2208
+ errors: response.errors
2209
+ };
2210
+ }
2211
+ async function getAllSitemapEntries(context, config, sitemapConfig, options) {
2212
+ const allEntries = [];
2213
+ const allErrors = [];
2214
+ const results = await Promise.all(
2215
+ sitemapConfig.providers.map((provider) => provider(context, config, options))
2216
+ );
2217
+ for (const result of results) {
2218
+ allEntries.push(...result.data);
2219
+ allErrors.push(...result.errors);
2220
+ }
2221
+ return { data: allEntries, errors: allErrors };
2222
+ }
2223
+ function createSitemapProvider(fetcher, sitemapConfig) {
2224
+ return (context, config, options) => fetcher(context, config, sitemapConfig, options);
2225
+ }
2226
+
2227
+ // src/api/tag.ts
2228
+ async function contentfulTagRest(context, config, slug, options) {
2229
+ return fetchSingleEntity(
2230
+ context,
2231
+ config,
2232
+ {
2233
+ contentType: "tag",
2234
+ cacheTagType: "tag",
2235
+ cacheTagIdentifier: slug,
2236
+ query: { "fields.slug": slug },
2237
+ resolver: (ctx, entry) => ctx.tagResolver(ctx, entry),
2238
+ errorLogContext: { slug }
2239
+ },
2240
+ options
2241
+ );
2242
+ }
2243
+
2244
+ // src/api/template.ts
2245
+ function templateConverter(context, entry) {
2246
+ const { fields, sys } = entry;
2247
+ const id = sys.id;
2248
+ const preContent = fields.preContent?.map((content) => resolvePageContent(context, id, content)).filter((item) => item !== null) ?? [];
2249
+ const postContent = fields.postContent?.map((content) => resolvePageContent(context, id, content)).filter((item) => item !== null) ?? [];
2250
+ const menu = fields.menu ? resolveNavigation(context, fields.menu) : void 0;
2251
+ const footer = fields.footer ? resolveNavigation(context, fields.footer) : void 0;
2252
+ const stickyNav = fields.flags?.includes("Sticky nav") ?? false;
2253
+ return {
2254
+ id,
2255
+ cmsLabel: fields.cmsLabel,
2256
+ preContent,
2257
+ postContent,
2258
+ menu,
2259
+ footer,
2260
+ backgroundColour: fields.backgroundColour,
2261
+ textColour: fields.textColour,
2262
+ stickyNav
2263
+ };
2264
+ }
2265
+ async function contentfulTemplateRest(context, config, cmsLabel, options) {
2266
+ return fetchSingleEntity(
2267
+ context,
2268
+ config,
2269
+ {
2270
+ contentType: "template",
2271
+ cacheTagType: "template",
2272
+ cacheTagIdentifier: cmsLabel,
2273
+ query: { "fields.cmsLabel": cmsLabel },
2274
+ resolver: (ctx, entry) => templateConverter(ctx, entry),
2275
+ errorLogContext: { cmsLabel }
2276
+ },
2277
+ options
2278
+ );
2120
2279
  }
2121
2280
 
2122
2281
  // src/revalidation/handlers.ts
@@ -2150,6 +2309,11 @@ var pageVariantHandler = {
2150
2309
  makeTags: (extracted) => [extracted ? pageTag(extracted) : void 0],
2151
2310
  getGlobalTags: () => [PageTag]
2152
2311
  };
2312
+ var templateHandler = {
2313
+ extract: (data) => data.fields?.cmsLabel?.[defaultLocale],
2314
+ makeTags: (extracted) => [extracted ? templateTag(extracted) : void 0],
2315
+ getGlobalTags: () => [TemplateTag]
2316
+ };
2153
2317
  var contentTypeHandlers = {
2154
2318
  page: {
2155
2319
  extract: (data) => data.fields?.slug?.[defaultLocale],
@@ -2171,11 +2335,8 @@ var contentTypeHandlers = {
2171
2335
  makeTags: (extracted) => [extracted ? tagTag(extracted) : void 0],
2172
2336
  getGlobalTags: () => [TagTag]
2173
2337
  },
2174
- template: {
2175
- extract: () => void 0,
2176
- makeTags: () => void 0,
2177
- getGlobalTags: () => ["template"]
2178
- },
2338
+ // biome-ignore lint/suspicious/noExplicitAny: Any is ok for handlers with different types
2339
+ template: templateHandler,
2179
2340
  customType: {
2180
2341
  extract: () => void 0,
2181
2342
  makeTags: () => void 0,
@@ -2339,6 +2500,6 @@ function createRevalidationHandler(config = {}) {
2339
2500
  init_tags();
2340
2501
  init_utils();
2341
2502
 
2342
- 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, calculateBackoffDelay, contentfulAllArticleLinks, contentfulAllArticleTypeLinks, contentfulAllPageLinks, contentfulAllPersonLinks, contentfulAllTagLinks, contentfulArticleRest, contentfulArticleTypeRest, contentfulPageRest, createBaseConverterContext, createContentfulClient, createContentfulPreviewClient, createResponsiveVisual, createRevalidationHandler, customTypeTag, filterRelatedArticles, getAllArticlesForRelated, getCacheTags, getCacheTagsForPreview, getCacheTagsForProduction, getContentfulClient, getRetryAfter, isContentfulError, isRateLimitError, isRetryableError, isValidDate, locationTag, lookupAsset, notEmpty, pageTag, personTag, resolveLink, resolveLinks, resolveRichTextDocument, revalidateSingleTag, revalidateTags, safeDate, tagTag, withRetry };
2503
+ 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, calculateBackoffDelay, contentfulAllArticleLinks, contentfulAllArticleTypeLinks, contentfulAllPageLinks, contentfulAllPersonLinks, contentfulAllTagLinks, contentfulArticleRest, contentfulArticleSitemapEntries, contentfulArticleTypeRest, contentfulArticleTypeSitemapEntries, contentfulCustomTypeRest, contentfulPageRest, contentfulPageSitemapEntries, contentfulPersonSitemapEntries, contentfulTagRest, contentfulTagSitemapEntries, contentfulTemplateRest, createBaseConverterContext, createContentfulClient, createContentfulPreviewClient, createResponsiveVisual, createRevalidationHandler, createSitemapProvider, customTypeTag, filterRelatedArticles, getAllSitemapEntries, getCacheTags, getCacheTagsForPreview, getCacheTagsForProduction, getContentfulClient, getRetryAfter, isContentfulError, isRateLimitError, isRetryableError, isValidDate, locationTag, lookupAsset, notEmpty, pageTag, personTag, resolveLink, resolveLinks, resolveRichTextDocument, revalidateSingleTag, revalidateTags, safeDate, tagTag, templateTag, withRetry };
2343
2504
  //# sourceMappingURL=index.js.map
2344
2505
  //# sourceMappingURL=index.js.map