@se-studio/contentful-rest-api 1.0.18 → 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,53 +1210,329 @@ 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
1229
+ const icon = createResponsiveVisual(
1230
+ lookupAsset(context, navIcon),
1231
+ lookupAsset(context, mobileNavIcon)
1009
1232
  );
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,
1233
+ const realText = useTitle ? resolveBuildYear(title) : text ? resolveBuildYear(text) : void 0;
1234
+ if (link) {
1235
+ return {
1236
+ type: "External link",
1237
+ id,
1238
+ href: link,
1239
+ icon,
1240
+ name: makeContentfulTitle(title, id),
1241
+ ...otherFields,
1242
+ text: realText
1243
+ };
1244
+ }
1245
+ if (internal) {
1246
+ const resolved = resolveLink(context, id, internal);
1247
+ return { ...resolved, icon, id, text: realText };
1248
+ }
1249
+ const blank = {
1250
+ type: "Blank link",
1251
+ nick: true,
1252
+ id,
1253
+ name: makeContentfulTitle(title, id),
1254
+ ...otherFields,
1255
+ icon,
1256
+ text: realText
1257
+ };
1258
+ return blank;
1259
+ }
1260
+ function baseNavigationItemConverter(context, entry) {
1261
+ const {
1262
+ sys: { id },
1263
+ fields
1264
+ } = entry;
1265
+ const { navigationItems } = fields;
1266
+ const link = createLink(context, entry);
1267
+ const resolvedNavigationItems = navigationItems?.map((item) => resolveNavigationItem(context, id, item)).filter((item) => item !== void 0);
1268
+ const result = {
1269
+ id,
1270
+ link,
1271
+ entries: resolvedNavigationItems
1272
+ };
1273
+ return result;
1274
+ }
1275
+ function resolveNavigation(context, link) {
1276
+ const id = link.sys.id;
1277
+ const possibleEntry = context.includes.get(id);
1278
+ if (!possibleEntry || possibleEntry.type !== "navigation") {
1279
+ return void 0;
1280
+ }
1281
+ const entry = possibleEntry.entry;
1282
+ const { fields } = entry;
1283
+ if (!fields) {
1284
+ return void 0;
1285
+ }
1286
+ const { name, entries: fieldEntries, ...rest } = fields;
1287
+ const entries = fieldEntries?.map((item) => resolveNavigationItem(context, id, item)) ?? [];
1288
+ return {
1289
+ id,
1290
+ name,
1291
+ entries,
1292
+ ...rest
1293
+ };
1294
+ }
1295
+
1296
+ // src/converters/template.ts
1297
+ function resolveTemplate(context, link) {
1298
+ const id = link.sys.id;
1299
+ const possibleEntry = context.includes.get(id);
1300
+ if (!possibleEntry || possibleEntry.type !== "template") {
1301
+ return null;
1302
+ }
1303
+ const entry = possibleEntry.entry;
1304
+ const { fields } = entry;
1305
+ if (!fields) {
1306
+ return null;
1307
+ }
1308
+ const preContent = fields.preContent?.map((content) => resolvePageContent(context, id, content)).filter((item) => item !== null) ?? [];
1309
+ const postContent = fields.postContent?.map((content) => resolvePageContent(context, id, content)).filter((item) => item !== null) ?? [];
1310
+ const menu = fields.menu ? resolveNavigation(context, fields.menu) : void 0;
1311
+ const footer = fields.footer ? resolveNavigation(context, fields.footer) : void 0;
1312
+ const stickyNav = fields.flags?.includes("Sticky nav") ?? false;
1313
+ const { backgroundColour, textColour } = fields;
1314
+ return {
1315
+ preContent,
1316
+ postContent,
1317
+ menu,
1318
+ footer,
1319
+ backgroundColour,
1320
+ textColour,
1321
+ stickyNav
1322
+ };
1323
+ }
1324
+
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;
1344
+ const {
1345
+ slug,
1346
+ title,
1347
+ description,
1348
+ featuredImage,
1349
+ tags,
1350
+ content,
1351
+ template: templateLink,
1352
+ topContent: topContentLinks,
1353
+ bottomContent: bottomContentLinks,
1354
+ articleType,
1355
+ ...simpleFields
1356
+ } = fields;
1357
+ const articleTypeLink = resolveLink(context, id, articleType);
1358
+ const template = resolveArticleTemplateHierarchy(context, articleType, templateLink);
1359
+ const topContent = topContentLinks?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
1360
+ const articleContent = content?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
1361
+ const bottomContent = bottomContentLinks?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
1362
+ const preContent = template?.preContent ?? [];
1363
+ const postContent = template?.postContent ?? [];
1364
+ const contents = addPositionMetadata([
1365
+ ...topContent,
1366
+ ...preContent,
1367
+ ...articleContent,
1368
+ ...postContent,
1369
+ ...bottomContent
1370
+ ]);
1371
+ const article = {
1372
+ type: "Article",
1373
+ id,
1374
+ slug,
1375
+ title: makeContentfulTitle(title, sys.id),
1376
+ description: makeContentfulDescription(description, sys.id),
1377
+ featuredImage: lookupAsset(context, featuredImage),
1378
+ articleType: articleTypeLink,
1379
+ tags: tags?.map((tag) => resolveLink(context, id, tag)),
1380
+ contents,
1381
+ ...simpleFields,
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
1386
+ };
1387
+ return article;
1388
+ }
1389
+ function calculateArticleTypeHref(slug) {
1390
+ return `/${slug}/`;
1391
+ }
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}"`);
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;
1404
+ return createInternalLink(
1405
+ sys.id,
1406
+ {
1407
+ cmsLabel: fields.cmsLabel,
1408
+ title: fields.title,
1409
+ featuredImage: fields.featuredImage,
1410
+ backgroundColour: fields.backgroundColour,
1411
+ textColour: fields.textColour,
1412
+ indexed: fields.indexed,
1413
+ hidden: fields.hidden,
1414
+ slug: fields.slug
1415
+ },
1416
+ context,
1417
+ calculateArticleHref(articleTypeLink.slug, fields.slug),
1418
+ "Article",
1419
+ {
1420
+ tags,
1421
+ primaryTag,
1422
+ articleType: articleTypeLink,
1423
+ date: fields.date,
1424
+ author
1425
+ }
1426
+ );
1427
+ }
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
+ );
1434
+ }
1435
+ return createInternalLink(
1436
+ sys.id,
1437
+ {
1438
+ cmsLabel: fields.name,
1439
+ title: fields.name,
1440
+ featuredImage: fields.featuredImage,
1441
+ backgroundColour: fields.backgroundColour,
1442
+ textColour: fields.textColour,
1443
+ indexed: fields.indexed,
1444
+ hidden: fields.hidden,
1445
+ slug: fields.slug
1446
+ },
1447
+ context,
1448
+ calculateArticleTypeHref(fields.slug),
1449
+ "ArticleType",
1450
+ { indexPageSlug: fields.indexPageSlug }
1451
+ );
1452
+ }
1453
+ function baseArticleTypeConverter(context, entry) {
1454
+ const { sys, fields } = entry;
1455
+ const { id } = sys;
1456
+ const {
1457
+ indexPageSlug,
1458
+ indexPageName,
1459
+ indexPageDescription,
1460
+ featuredImage,
1461
+ menu: menuLink,
1462
+ footer: footerLink,
1463
+ indexPageTemplate: templateLink,
1464
+ indexPageTopContent: topContentLinks,
1465
+ structuredData,
1466
+ slug: _slug,
1467
+ ...other
1468
+ } = fields;
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;
1472
+ const topContent = topContentLinks?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
1473
+ const preContent = template?.preContent ?? [];
1474
+ const postContent = template?.postContent ?? [];
1475
+ const contents = addPositionMetadata([...topContent, ...preContent, ...postContent]);
1476
+ const articleType = {
1477
+ type: "Article type",
1478
+ id,
1479
+ slug: indexPageSlug,
1480
+ title: makeContentfulTitle(indexPageName, sys.id),
1481
+ description: makeContentfulDescription(indexPageDescription, sys.id),
1482
+ featuredImage: lookupAsset(context, featuredImage),
1483
+ contents,
1484
+ structuredData,
1485
+ menu,
1486
+ footer,
1487
+ ...other
1488
+ };
1489
+ return articleType;
1490
+ }
1491
+
1492
+ // src/converters/collection.ts
1493
+ function baseCollectionConverter(context, entry) {
1494
+ const { sys, fields } = entry;
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
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,
1023
1536
  visual,
1024
1537
  links: linksField?.map((link) => resolveLink(context, id, link)),
1025
1538
  contents: contentsField?.map((content) => resolveCollectionContent(context, id, content))
@@ -1043,7 +1556,6 @@ function baseComponentConverter(context, entry) {
1043
1556
  body: bodyField,
1044
1557
  additionalCopy: additionalCopyField,
1045
1558
  // Field name change
1046
- componentType,
1047
1559
  showHeading,
1048
1560
  heading,
1049
1561
  otherMedia: otherMediaField,
@@ -1067,7 +1579,6 @@ function baseComponentConverter(context, entry) {
1067
1579
  id,
1068
1580
  name: cmsLabel,
1069
1581
  cmsLabel,
1070
- componentType,
1071
1582
  ...DEFAULT_POSITION_FIELDS,
1072
1583
  ...simpleFields,
1073
1584
  heading: showHeading ? heading : void 0,
@@ -1082,6 +1593,49 @@ function baseComponentConverter(context, entry) {
1082
1593
  return component;
1083
1594
  }
1084
1595
 
1596
+ // src/converters/customType.ts
1597
+ function baseCustomTypeConverter(context, entry) {
1598
+ const { sys, fields } = entry;
1599
+ const { id } = sys;
1600
+ const {
1601
+ slug,
1602
+ indexPageSlug,
1603
+ indexPageName,
1604
+ indexPageDescription,
1605
+ featuredImage,
1606
+ menu: menuLink,
1607
+ footer: footerLink,
1608
+ indexPageTemplate: templateLink,
1609
+ indexPageTopContent: topContentLinks,
1610
+ structuredData,
1611
+ ...other
1612
+ } = fields;
1613
+ const template = templateLink ? resolveTemplate(context, templateLink) : null;
1614
+ const menu = menuLink ? resolveNavigation(context, menuLink) : template?.menu;
1615
+ const footer = footerLink ? resolveNavigation(context, footerLink) : template?.footer;
1616
+ const topContent = topContentLinks?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
1617
+ const preContent = template?.preContent ?? [];
1618
+ const postContent = template?.postContent ?? [];
1619
+ const contents = addPositionMetadata([...topContent, ...preContent, ...postContent]);
1620
+ const customType = {
1621
+ type: "Custom type",
1622
+ id,
1623
+ slug,
1624
+ indexPageSlug,
1625
+ indexPageName: makeContentfulTitle(indexPageName, sys.id),
1626
+ indexPageDescription: makeContentfulDescription(indexPageDescription, sys.id),
1627
+ title: makeContentfulTitle(indexPageName, sys.id),
1628
+ description: makeContentfulDescription(indexPageDescription, sys.id),
1629
+ featuredImage: lookupAsset(context, featuredImage),
1630
+ contents,
1631
+ structuredData,
1632
+ menu,
1633
+ footer,
1634
+ ...other
1635
+ };
1636
+ return customType;
1637
+ }
1638
+
1085
1639
  // src/converters/link.ts
1086
1640
  function baseLinkConverter(context, entry) {
1087
1641
  const { sys, fields } = entry;
@@ -1123,7 +1677,9 @@ function baseLinkConverter(context, entry) {
1123
1677
  slug: internalTarget.slug,
1124
1678
  indexed: internalTarget.indexed,
1125
1679
  hidden: internalTarget.hidden,
1126
- tags: internalTarget.tags
1680
+ tags: internalTarget.tags,
1681
+ title: internalTarget.title,
1682
+ description: internalTarget.description
1127
1683
  };
1128
1684
  }
1129
1685
  if (fields.external) {
@@ -1155,124 +1711,11 @@ function baseLinkConverter(context, entry) {
1155
1711
  href,
1156
1712
  visual: asset
1157
1713
  };
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)
1185
- );
1186
- const realText = useTitle ? resolveBuildYear(title) : text ? resolveBuildYear(text) : void 0;
1187
- if (link) {
1188
- return {
1189
- type: "External link",
1190
- id,
1191
- href: link,
1192
- icon,
1193
- name: makeContentfulTitle(title, id),
1194
- ...otherFields,
1195
- text: realText
1196
- };
1197
- }
1198
- if (internal) {
1199
- const resolved = resolveLink(context, id, internal);
1200
- return { ...resolved, icon, id, text: realText };
1201
- }
1202
- const blank = {
1203
- type: "Blank link",
1204
- nick: true,
1205
- id,
1206
- name: makeContentfulTitle(title, id),
1207
- ...otherFields,
1208
- icon,
1209
- text: realText
1210
- };
1211
- return blank;
1212
- }
1213
- function baseNavigationItemConverter(context, entry) {
1214
- const {
1215
- sys: { id },
1216
- fields
1217
- } = entry;
1218
- const { navigationItems } = fields;
1219
- const link = createLink(context, entry);
1220
- const resolvedNavigationItems = navigationItems?.map((item) => resolveNavigationItem(context, id, item)).filter((item) => item !== void 0);
1221
- const result = {
1222
- id,
1223
- link,
1224
- entries: resolvedNavigationItems
1225
- };
1226
- return result;
1227
- }
1228
- function resolveNavigation(context, link) {
1229
- const id = link.sys.id;
1230
- const possibleEntry = context.includes.get(id);
1231
- if (!possibleEntry || possibleEntry.type !== "navigation") {
1232
- return void 0;
1233
- }
1234
- const entry = possibleEntry.entry;
1235
- const { fields } = entry;
1236
- if (!fields) {
1237
- return void 0;
1238
- }
1239
- const entries = fields.entries?.map((item) => resolveNavigationItem(context, id, item)) ?? [];
1240
- const { name, textColour, backgroundColour } = fields;
1241
- return {
1242
- id,
1243
- name,
1244
- entries,
1245
- textColour,
1246
- backgroundColour
1247
- };
1248
- }
1249
-
1250
- // src/converters/template.ts
1251
- function resolveTemplate(context, link) {
1252
- const id = link.sys.id;
1253
- const possibleEntry = context.includes.get(id);
1254
- if (!possibleEntry || possibleEntry.type !== "template") {
1255
- return null;
1256
- }
1257
- const entry = possibleEntry.entry;
1258
- const { fields } = entry;
1259
- if (!fields) {
1260
- return null;
1261
- }
1262
- const preContent = fields.preContent?.map((content) => resolvePageContent(context, id, content)).filter((item) => item !== null) ?? [];
1263
- const postContent = fields.postContent?.map((content) => resolvePageContent(context, id, content)).filter((item) => item !== null) ?? [];
1264
- const menu = fields.menu ? resolveNavigation(context, fields.menu) : void 0;
1265
- const footer = fields.footer ? resolveNavigation(context, fields.footer) : void 0;
1266
- const stickyNav = fields.flags?.includes("Sticky nav") ?? false;
1267
- const { backgroundColour, textColour } = fields;
1268
- return {
1269
- preContent,
1270
- postContent,
1271
- menu,
1272
- footer,
1273
- backgroundColour,
1274
- textColour,
1275
- stickyNav
1714
+ }
1715
+ return {
1716
+ ...baseProps,
1717
+ type: "Blank link",
1718
+ href: null
1276
1719
  };
1277
1720
  }
1278
1721
 
@@ -1401,16 +1844,7 @@ function basePersonLinkConverter(context, entry) {
1401
1844
  return createInternalLink(
1402
1845
  sys.id,
1403
1846
  {
1404
- cmsLabel: fields.name,
1405
- // Person has no cmsLabel, use name field
1406
- title: fields.name,
1407
- featuredImage: fields.media,
1408
- // Person uses 'media' not 'featuredImage'
1409
- backgroundColour: fields.backgroundColour,
1410
- textColour: fields.textColour,
1411
- indexed: fields.indexed,
1412
- hidden: fields.hidden,
1413
- slug: fields.slug
1847
+ ...fields
1414
1848
  },
1415
1849
  context,
1416
1850
  calculatePersonHref(fields.slug),
@@ -1418,140 +1852,39 @@ function basePersonLinkConverter(context, entry) {
1418
1852
  );
1419
1853
  }
1420
1854
 
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) {
1438
- const { sys, fields } = entry;
1439
- const { id } = sys;
1440
- const {
1441
- slug,
1442
- title,
1443
- description,
1444
- featuredImage,
1445
- tags,
1446
- content,
1447
- template: templateLink,
1448
- topContent: topContentLinks,
1449
- bottomContent: bottomContentLinks,
1450
- articleType,
1451
- ...simpleFields
1452
- } = fields;
1453
- const articleTypeLink = resolveLink(context, id, articleType);
1454
- const template = resolveArticleTemplateHierarchy(context, articleType, templateLink);
1455
- 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
- const preContent = template?.preContent ?? [];
1459
- 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",
1469
- id,
1470
- slug,
1471
- title: makeContentfulTitle(title, sys.id),
1472
- description: makeContentfulDescription(description, sys.id),
1473
- featuredImage: lookupAsset(context, featuredImage),
1474
- articleType: articleTypeLink,
1475
- tags: tags?.map((tag) => resolveLink(context, id, tag)),
1476
- 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
1482
- };
1483
- return article;
1484
- }
1485
- function calculateArticleTypeHref(slug) {
1486
- return `/${slug}/`;
1487
- }
1488
- function calculateArticleHref(articleTypeSlug, slug) {
1489
- return `${calculateArticleTypeHref(articleTypeSlug)}${slug}/`;
1490
- }
1491
- function baseArticleLinkConverter(context, entry) {
1492
- 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
- }
1516
- );
1855
+ // src/converters/tag.ts
1856
+ function calculateTagHref(slug) {
1857
+ return `/tag/${slug}/`;
1517
1858
  }
1518
- function baseArticleTypeLinkConverter(context, entry) {
1859
+ function baseTagLinkConverter(context, entry) {
1519
1860
  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
- );
1861
+ if (sys.contentType.sys.id !== "tag") {
1862
+ throw new Error(`Invalid content type: expected "tag", got "${sys.contentType.sys.id}"`);
1524
1863
  }
1525
1864
  return createInternalLink(
1526
1865
  sys.id,
1527
1866
  {
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
1867
+ ...fields
1536
1868
  },
1537
1869
  context,
1538
- calculateArticleTypeHref(fields.slug),
1539
- "ArticleType"
1870
+ calculateTagHref(fields.slug),
1871
+ "Tag"
1540
1872
  );
1541
1873
  }
1542
- function baseArticleTypeConverter(context, entry) {
1874
+ function baseTagConverter(context, entry) {
1543
1875
  const { sys, fields } = entry;
1544
1876
  const { id } = sys;
1545
1877
  const {
1878
+ name,
1546
1879
  slug,
1547
- indexPageName,
1548
- indexPageDescription,
1880
+ description,
1549
1881
  featuredImage,
1882
+ tagType,
1550
1883
  menu: menuLink,
1551
1884
  footer: footerLink,
1552
- indexPageTemplate: templateLink,
1553
- indexPageTopContent: topContentLinks,
1554
- ...simpleFields
1885
+ template: templateLink,
1886
+ topContent: topContentLinks,
1887
+ ...rest
1555
1888
  } = fields;
1556
1889
  const template = templateLink ? resolveTemplate(context, templateLink) : null;
1557
1890
  const menu = menuLink ? resolveNavigation(context, menuLink) : template?.menu;
@@ -1559,152 +1892,34 @@ function baseArticleTypeConverter(context, entry) {
1559
1892
  const topContent = topContentLinks?.map((c) => resolvePageContent(context, id, c)).filter((item) => item !== null) ?? [];
1560
1893
  const preContent = template?.preContent ?? [];
1561
1894
  const postContent = template?.postContent ?? [];
1562
- const indexPageContent = addPositionMetadata([...topContent, ...preContent, ...postContent]);
1563
- const articleType = {
1564
- type: "Article type",
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",
1565
1900
  id,
1566
1901
  slug,
1567
- title: makeContentfulTitle(indexPageName, sys.id),
1568
- description: makeContentfulDescription(indexPageDescription, sys.id),
1902
+ title: makeContentfulTitle(name, sys.id),
1903
+ description: makeContentfulDescription(description, sys.id),
1569
1904
  featuredImage: lookupAsset(context, featuredImage),
1570
- indexPageContent,
1571
- ...simpleFields,
1905
+ tagType: tagTypeName ?? null,
1906
+ contents,
1907
+ ...rest,
1572
1908
  menu,
1573
- footer
1574
- };
1575
- return articleType;
1576
- }
1577
-
1578
- // src/converters/tag.ts
1579
- function calculateTagHref(slug) {
1580
- return `/tag/${slug}/`;
1581
- }
1582
- function baseTagLinkConverter(context, entry) {
1583
- 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}"`);
1586
- }
1587
- return createInternalLink(
1588
- sys.id,
1589
- {
1590
- cmsLabel: fields.cmsLabel,
1591
- title: fields.name,
1592
- featuredImage: fields.featuredImage,
1593
- backgroundColour: fields.backgroundColour,
1594
- textColour: fields.textColour,
1595
- indexed: fields.indexed,
1596
- hidden: fields.hidden,
1597
- slug: fields.slug
1598
- },
1599
- context,
1600
- calculateTagHref(fields.slug),
1601
- "Tag"
1602
- );
1603
- }
1604
-
1605
- // src/api.ts
1606
- init_utils();
1607
- var PAGE_LINK_FIELDS = "sys,fields.cmsLabel,fields.title,fields.slug,fields.featuredImage,fields.backgroundColour,fields.textColour,fields.indexed,fields.hidden,fields.tags";
1608
- 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";
1609
- var ARTICLE_RELATED_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";
1610
- var ARTICLE_TYPE_LINK_FIELDS = "sys,fields.name,fields.slug,fields.featuredImage,fields.backgroundColour,fields.textColour,fields.indexed,fields.hidden";
1611
- var TAG_LINK_FIELDS = "sys,fields.cmsLabel,fields.name,fields.slug,fields.featuredImage,fields.backgroundColour,fields.textColour,fields.indexed,fields.hidden";
1612
- var PERSON_LINK_FIELDS = "sys,fields.name,fields.slug,fields.media,fields.backgroundColour,fields.textColour,fields.indexed,fields.hidden";
1613
- function convertAllAssets(response, context) {
1614
- const visuals = /* @__PURE__ */ new Map();
1615
- const assets = response.includes?.Asset;
1616
- if (assets && assets.length > 0) {
1617
- for (const asset of assets) {
1618
- const visual = convertAssetToVisual(context, asset);
1619
- if (visual) {
1620
- visuals.set(visual.id, visual);
1621
- }
1622
- }
1623
- }
1624
- return visuals;
1625
- }
1626
- function convertAllIncludes(response) {
1627
- const includes = /* @__PURE__ */ new Map();
1628
- const entries = [...response.items, ...response.includes?.Entry || []];
1629
- if (entries && entries.length > 0) {
1630
- for (const entry of entries) {
1631
- if (entry?.sys && entry.fields) {
1632
- includes.set(entry.sys.id, {
1633
- id: entry.sys.id,
1634
- type: entry.sys.contentType.sys.id,
1635
- entry
1636
- });
1637
- }
1638
- }
1639
- }
1640
- return includes;
1641
- }
1642
- async function contentfulPageRest(context, config, slug, options) {
1643
- const client = getContentfulClient(config, options?.preview);
1644
- const cacheTags = getCacheTags("page", slug, options?.preview);
1645
- const requestOptions = {
1646
- ...options,
1647
- next: {
1648
- ...options?.next,
1649
- tags: cacheTags
1650
- }
1651
- };
1652
- const fetchFn = async () => {
1653
- const response = await client.getEntries(
1654
- {
1655
- content_type: "page",
1656
- "fields.slug": slug,
1657
- include: 10,
1658
- locale: options?.locale,
1659
- limit: 1
1660
- },
1661
- requestOptions
1662
- );
1663
- const pageEntry = response.items[0];
1664
- if (!pageEntry || !pageEntry.fields) {
1665
- return { data: null, errors: [] };
1666
- }
1667
- try {
1668
- const assets = convertAllAssets(response, context);
1669
- const includes = convertAllIncludes(response);
1670
- const fullContext = {
1671
- ...context,
1672
- includes,
1673
- assets,
1674
- errors: []
1675
- };
1676
- const converted = fullContext.pageResolver(fullContext, pageEntry);
1677
- if (fullContext.errors.length > 0 && typeof process !== "undefined" && process.env?.NODE_ENV === "production") {
1678
- console.error("CMS conversion errors for page:", {
1679
- pageId: pageEntry.sys.id,
1680
- slug,
1681
- errors: fullContext.errors
1682
- });
1683
- }
1684
- return { data: converted, errors: fullContext.errors };
1685
- } catch (error) {
1686
- const entryId = pageEntry.sys.id;
1687
- const entryType = pageEntry.sys.contentType?.sys?.id;
1688
- const errorMessage = error instanceof Error ? error.message : "Unknown conversion error";
1689
- const cmsError = {
1690
- entryId,
1691
- entryType,
1692
- message: errorMessage,
1693
- error
1694
- };
1695
- return { data: null, errors: [cmsError] };
1696
- }
1909
+ footer
1697
1910
  };
1698
- if (options?.retry) {
1699
- return await withRetry(fetchFn, options.retry);
1700
- }
1701
- return await fetchFn();
1911
+ return tag;
1702
1912
  }
1913
+
1914
+ // src/api/context.ts
1703
1915
  function createBaseConverterContext() {
1704
1916
  const linkResolver = /* @__PURE__ */ new Map();
1705
1917
  linkResolver.set("page", basePageLinkConverter);
1706
1918
  linkResolver.set("article", baseArticleLinkConverter);
1707
- linkResolver.set("articleType", baseArticleTypeLinkConverter);
1919
+ linkResolver.set(
1920
+ "articleType",
1921
+ baseArticleTypeLinkConverter
1922
+ );
1708
1923
  linkResolver.set("tag", baseTagLinkConverter);
1709
1924
  linkResolver.set("person", basePersonLinkConverter);
1710
1925
  linkResolver.set("pageVariant", basePageVariantLinkConverter);
@@ -1721,6 +1936,8 @@ function createBaseConverterContext() {
1721
1936
  navigationItemResolver: baseNavigationItemConverter,
1722
1937
  articleResolver: baseArticleConverter,
1723
1938
  articleTypeResolver: baseArticleTypeConverter,
1939
+ tagResolver: baseTagConverter,
1940
+ customTypeResolver: baseCustomTypeConverter,
1724
1941
  componentResolver: baseComponentConverter,
1725
1942
  collectionResolver: baseCollectionConverter,
1726
1943
  linkResolver,
@@ -1728,279 +1945,26 @@ function createBaseConverterContext() {
1728
1945
  videoPrefix: ""
1729
1946
  };
1730
1947
  }
1731
- async function contentfulArticleRest(context, config, slug, articleTypeSlug, options) {
1732
- const client = getContentfulClient(config, options?.preview);
1733
- const cacheTags = getCacheTags("article", slug, options?.preview);
1734
- const requestOptions = {
1735
- ...options,
1736
- next: {
1737
- ...options?.next,
1738
- tags: cacheTags
1739
- }
1740
- };
1741
- const fetchFn = async () => {
1742
- const response = await client.getEntries(
1743
- {
1744
- content_type: "article",
1745
- "fields.slug": slug,
1746
- "fields.articleType.sys.contentType.sys.id": "articleType",
1747
- "fields.articleType.fields.slug": articleTypeSlug,
1748
- include: 10,
1749
- locale: options?.locale,
1750
- limit: 1
1751
- },
1752
- requestOptions
1753
- );
1754
- const articleEntry = response.items[0];
1755
- if (!articleEntry || !articleEntry.fields) {
1756
- return { data: null, errors: [] };
1757
- }
1758
- const assets = convertAllAssets(response, context);
1759
- const includes = convertAllIncludes(response);
1760
- const fullContext = {
1761
- ...context,
1762
- includes,
1763
- assets,
1764
- errors: []
1765
- };
1766
- try {
1767
- const converted = fullContext.articleResolver(fullContext, articleEntry);
1768
- if (fullContext.errors.length > 0 && typeof process !== "undefined" && process.env?.NODE_ENV === "production") {
1769
- console.error("CMS conversion errors for article:", {
1770
- articleId: articleEntry.sys.id,
1771
- slug,
1772
- articleTypeSlug,
1773
- errors: fullContext.errors
1774
- });
1775
- }
1776
- return { data: converted, errors: fullContext.errors };
1777
- } catch (error) {
1778
- const entryId = articleEntry.sys.id;
1779
- const entryType = articleEntry.sys.contentType?.sys?.id;
1780
- const errorMessage = error instanceof Error ? error.message : "Unknown conversion error";
1781
- const cmsError = {
1782
- entryId,
1783
- entryType,
1784
- message: errorMessage,
1785
- error
1786
- };
1787
- return { data: null, errors: [cmsError] };
1788
- }
1789
- };
1790
- if (options?.retry) {
1791
- return await withRetry(fetchFn, options.retry);
1792
- }
1793
- return await fetchFn();
1794
- }
1795
- async function contentfulArticleTypeRest(context, config, slug, options) {
1796
- const client = getContentfulClient(config, options?.preview);
1797
- const cacheTags = getCacheTags("articleType", slug, options?.preview);
1798
- const requestOptions = {
1799
- ...options,
1800
- next: {
1801
- ...options?.next,
1802
- tags: cacheTags
1803
- }
1804
- };
1805
- const fetchFn = async () => {
1806
- const response = await client.getEntries(
1807
- {
1808
- content_type: "articleType",
1809
- "fields.slug": slug,
1810
- include: 10,
1811
- locale: options?.locale,
1812
- limit: 1
1813
- },
1814
- requestOptions
1815
- );
1816
- const articleTypeEntry = response.items[0];
1817
- if (!articleTypeEntry || !articleTypeEntry.fields) {
1818
- return { data: null, errors: [] };
1819
- }
1820
- const assets = convertAllAssets(response, context);
1821
- const includes = convertAllIncludes(response);
1822
- const fullContext = {
1823
- ...context,
1824
- includes,
1825
- assets,
1826
- errors: []
1827
- };
1828
- try {
1829
- const converted = fullContext.articleTypeResolver(fullContext, articleTypeEntry);
1830
- if (fullContext.errors.length > 0 && typeof process !== "undefined" && process.env?.NODE_ENV === "production") {
1831
- console.error("CMS conversion errors for article type:", {
1832
- articleTypeId: articleTypeEntry.sys.id,
1833
- slug,
1834
- errors: fullContext.errors
1835
- });
1836
- }
1837
- return { data: converted, errors: fullContext.errors };
1838
- } catch (error) {
1839
- const entryId = articleTypeEntry.sys.id;
1840
- const entryType = articleTypeEntry.sys.contentType?.sys?.id;
1841
- const errorMessage = error instanceof Error ? error.message : "Unknown conversion error";
1842
- const cmsError = {
1843
- entryId,
1844
- entryType,
1845
- message: errorMessage,
1846
- error
1847
- };
1848
- return { data: null, errors: [cmsError] };
1849
- }
1850
- };
1851
- if (options?.retry) {
1852
- return await withRetry(fetchFn, options.retry);
1853
- }
1854
- return await fetchFn();
1855
- }
1856
- function baseArticleLinkConverterWithMetadata(context, entry) {
1857
- const baseLink = baseArticleLinkConverter(context, entry);
1858
- return {
1859
- ...baseLink,
1860
- date: entry.fields.date ? new Date(entry.fields.date) : void 0,
1861
- author: entry.fields.author ? resolveLink(context, baseLink.id, entry.fields.author) : void 0,
1862
- articleType: entry.fields.articleType ? resolveLink(context, baseLink.id, entry.fields.articleType) : void 0
1863
- };
1864
- }
1865
- async function getAllArticlesForRelated(context, config, options) {
1866
- const client = getContentfulClient(config, options?.preview);
1867
- const allArticles = [];
1868
- const errors = [];
1869
- const pageSize = 100;
1870
- let skip = 0;
1871
- let hasMore = true;
1872
- const cacheTags = getCacheTags("article", void 0, options?.preview);
1873
- const requestOptions = {
1874
- ...options,
1875
- next: {
1876
- ...options?.next,
1877
- tags: cacheTags
1878
- }
1879
- };
1880
- const fetchFn = async () => {
1881
- while (hasMore) {
1882
- const response = await client.getEntries(
1883
- {
1884
- content_type: "article",
1885
- "fields.indexed": true,
1886
- "fields.hidden[ne]": true,
1887
- order: "-fields.date",
1888
- include: 2,
1889
- // Shallow include - just enough for tags, articleType, author
1890
- locale: options?.locale,
1891
- limit: pageSize,
1892
- skip,
1893
- select: ARTICLE_RELATED_FIELDS
1894
- },
1895
- requestOptions
1896
- );
1897
- if (response.items.length === 0) {
1898
- hasMore = false;
1899
- break;
1900
- }
1901
- const assets = convertAllAssets(response, context);
1902
- const includes = convertAllIncludes(response);
1903
- const fullContext = {
1904
- ...context,
1905
- includes,
1906
- assets,
1907
- errors: []
1908
- };
1909
- for (const entry of response.items) {
1910
- if (!entry.fields) continue;
1911
- try {
1912
- const converted = baseArticleLinkConverterWithMetadata(fullContext, entry);
1913
- allArticles.push(converted);
1914
- } catch (error) {
1915
- const entryId = entry.sys.id;
1916
- const entryType = entry.sys.contentType?.sys?.id;
1917
- const errorMessage = error instanceof Error ? error.message : "Unknown conversion error";
1918
- errors.push({
1919
- entryId,
1920
- entryType,
1921
- message: errorMessage,
1922
- error
1923
- });
1924
- }
1925
- }
1926
- skip += pageSize;
1927
- if (skip >= response.total) {
1928
- hasMore = false;
1929
- }
1930
- }
1931
- return { data: allArticles, errors };
1932
- };
1933
- if (options?.retry) {
1934
- return await withRetry(fetchFn, options.retry);
1935
- }
1936
- return await fetchFn();
1937
- }
1938
- async function fetchAllLinks(contentType, client, requestOptions, converter, context, pageSize = 100, select) {
1939
- const allLinks = [];
1940
- const errors = [];
1941
- let skip = 0;
1942
- let hasMore = true;
1943
- const fetchFn = async () => {
1944
- while (hasMore) {
1945
- try {
1946
- const response = await client.getEntries(
1947
- {
1948
- content_type: contentType,
1949
- include: 2,
1950
- // Minimal include for link-only fetching
1951
- locale: requestOptions?.locale,
1952
- limit: pageSize,
1953
- skip,
1954
- ...select && { select }
1955
- },
1956
- requestOptions
1957
- );
1958
- if (response.items.length === 0) {
1959
- hasMore = false;
1960
- break;
1961
- }
1962
- const includes = convertAllIncludes(response);
1963
- const assets = convertAllAssets(response, context);
1964
- const fullContext = {
1965
- ...context,
1966
- includes,
1967
- assets,
1968
- errors: []
1969
- };
1970
- for (const entry of response.items) {
1971
- if (!entry.fields) continue;
1972
- try {
1973
- const converted = converter(
1974
- fullContext,
1975
- entry
1976
- );
1977
- converted.lastModified = entry.sys.updatedAt ? new Date(entry.sys.updatedAt) : void 0;
1978
- allLinks.push(converted);
1979
- } catch (error) {
1980
- const entryId = entry.sys.id;
1981
- const entryType = entry.sys.contentType?.sys?.id;
1982
- const errorMessage = error instanceof Error ? error.message : "Unknown conversion error";
1983
- errors.push({
1984
- entryId,
1985
- entryType,
1986
- message: errorMessage,
1987
- error
1988
- });
1989
- }
1990
- }
1991
- skip += pageSize;
1992
- if (skip >= response.total) {
1993
- hasMore = false;
1994
- }
1995
- } catch (error) {
1996
- console.error("Error fetching links", typeof error, error, JSON.stringify(error, null, 2));
1997
- throw error;
1998
- }
1999
- }
2000
- return { data: allLinks, errors };
2001
- };
2002
- 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
+ );
2003
1964
  }
1965
+
1966
+ // src/api/links.ts
1967
+ init_utils();
2004
1968
  async function contentfulAllPageLinks(context, config, options) {
2005
1969
  const client = getContentfulClient(config, options?.preview);
2006
1970
  const cacheTags = getCacheTags("page", void 0, options?.preview);
@@ -2121,34 +2085,67 @@ async function contentfulAllArticleTypeLinks(context, config, options) {
2121
2085
  }
2122
2086
  return await fetchFn();
2123
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
2124
2107
  function filterRelatedArticles(articles, options) {
2125
- 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)] : [];
2126
2121
  const scoredArticles = articles.map((article) => {
2127
2122
  let score = 0;
2128
- if (excludeIds?.includes(article.id)) {
2129
- return null;
2130
- }
2131
- if (articleTypeId && article.articleType?.id !== articleTypeId) {
2123
+ if (excludeIds.length > 0 && excludeIds.includes(article.id)) {
2132
2124
  return null;
2133
2125
  }
2134
- if (authorId && article.author?.id !== authorId) {
2135
- return null;
2126
+ if (articleTypeIds.length > 0) {
2127
+ if (!article.articleType?.id || !articleTypeIds.includes(article.articleType.id)) {
2128
+ return null;
2129
+ }
2136
2130
  }
2137
2131
  if (article.date) {
2138
- if (before && article.date > before) {
2132
+ const articleDate = new Date(article.date);
2133
+ if (before && articleDate > before) {
2139
2134
  return null;
2140
2135
  }
2141
- if (after && article.date < after) {
2136
+ if (after && articleDate < after) {
2142
2137
  return null;
2143
2138
  }
2144
2139
  }
2145
- if (tagIds && tagIds.length > 0 && article.tags) {
2140
+ if (tagIds.length > 0 && article.tags) {
2146
2141
  const articleTagIds = article.tags.map((tag) => tag.id);
2147
2142
  const matchingTags = tagIds.filter((tagId) => articleTagIds.includes(tagId));
2148
2143
  score += matchingTags.length * 10;
2149
2144
  }
2150
- if (authorId && article.author?.id === authorId) {
2151
- score += 5;
2145
+ if (authorIds.length > 0 && article.author?.id) {
2146
+ if (authorIds.includes(article.author.id)) {
2147
+ score += 5;
2148
+ }
2152
2149
  }
2153
2150
  return { article, score };
2154
2151
  }).filter((item) => item !== null);
@@ -2157,16 +2154,17 @@ function filterRelatedArticles(articles, options) {
2157
2154
  return b.score - a.score;
2158
2155
  }
2159
2156
  if (a.article.date && b.article.date) {
2160
- 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();
2161
2160
  }
2162
2161
  return 0;
2163
2162
  });
2164
- const topArticles = count ? scoredArticles.slice(0, count) : scoredArticles;
2165
- return topArticles.map(({ article }) => {
2166
- const { date: _date, author: _author, articleType: _articleType, ...cleanLink } = article;
2167
- return cleanLink;
2168
- });
2163
+ const topArticles = count !== void 0 ? scoredArticles.slice(0, count) : scoredArticles;
2164
+ return topArticles.map(({ article }) => article);
2169
2165
  }
2166
+
2167
+ // src/api/sitemap.ts
2170
2168
  function linksToSitemapEntries(links, sitemapConfig) {
2171
2169
  return links.filter((link) => link.indexed !== false && link.hidden !== true && link.href).map((link) => ({
2172
2170
  url: link.href,
@@ -2226,6 +2224,60 @@ function createSitemapProvider(fetcher, sitemapConfig) {
2226
2224
  return (context, config, options) => fetcher(context, config, sitemapConfig, options);
2227
2225
  }
2228
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
+ );
2279
+ }
2280
+
2229
2281
  // src/revalidation/handlers.ts
2230
2282
  init_tags();
2231
2283
  init_utils();
@@ -2257,6 +2309,11 @@ var pageVariantHandler = {
2257
2309
  makeTags: (extracted) => [extracted ? pageTag(extracted) : void 0],
2258
2310
  getGlobalTags: () => [PageTag]
2259
2311
  };
2312
+ var templateHandler = {
2313
+ extract: (data) => data.fields?.cmsLabel?.[defaultLocale],
2314
+ makeTags: (extracted) => [extracted ? templateTag(extracted) : void 0],
2315
+ getGlobalTags: () => [TemplateTag]
2316
+ };
2260
2317
  var contentTypeHandlers = {
2261
2318
  page: {
2262
2319
  extract: (data) => data.fields?.slug?.[defaultLocale],
@@ -2278,11 +2335,8 @@ var contentTypeHandlers = {
2278
2335
  makeTags: (extracted) => [extracted ? tagTag(extracted) : void 0],
2279
2336
  getGlobalTags: () => [TagTag]
2280
2337
  },
2281
- template: {
2282
- extract: () => void 0,
2283
- makeTags: () => void 0,
2284
- getGlobalTags: () => ["template"]
2285
- },
2338
+ // biome-ignore lint/suspicious/noExplicitAny: Any is ok for handlers with different types
2339
+ template: templateHandler,
2286
2340
  customType: {
2287
2341
  extract: () => void 0,
2288
2342
  makeTags: () => void 0,
@@ -2446,6 +2500,6 @@ function createRevalidationHandler(config = {}) {
2446
2500
  init_tags();
2447
2501
  init_utils();
2448
2502
 
2449
- 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, contentfulPageRest, contentfulPageSitemapEntries, contentfulPersonSitemapEntries, contentfulTagSitemapEntries, createBaseConverterContext, createContentfulClient, createContentfulPreviewClient, createResponsiveVisual, createRevalidationHandler, createSitemapProvider, customTypeTag, filterRelatedArticles, getAllArticlesForRelated, getAllSitemapEntries, 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 };
2450
2504
  //# sourceMappingURL=index.js.map
2451
2505
  //# sourceMappingURL=index.js.map