@se-studio/contentful-rest-api 1.0.32 → 1.0.34

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.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { IBaseArticle, IBaseArticleType, BaseCollectionContent, IBaseCollection, IBaseComponent, IBaseCustomType, INavigationItem, IBasePage, IBaseTag, IInternalLink, IVisual, IBasePerson, INavigation, IArticleLink, IArticleTypeLink, ITyped, ISvgImage, IResponsiveVisual } from '@se-studio/core-data-types';
2
2
  import * as contentful from 'contentful';
3
- import { EntrySkeletonType, EntryFieldTypes, Entry, UnresolvedLink } from 'contentful';
3
+ import { EntrySkeletonType, EntryFieldTypes, Entry, Asset, UnresolvedLink } from 'contentful';
4
4
  import { Document } from '@contentful/rich-text-types';
5
5
  import { NextRequest, NextResponse } from 'next/server';
6
6
 
@@ -185,6 +185,7 @@ interface BaseArticleFields {
185
185
  redirectTo?: EntryFieldTypes.Symbol;
186
186
  structuredData?: EntryFieldTypes.Array<EntryFieldTypes.EntryLink<BaseSchemaSkeleton>>;
187
187
  externalLink?: EntryFieldTypes.Symbol;
188
+ download?: EntryFieldTypes.AssetLink;
188
189
  flags?: EntryFieldTypes.Array<EntryFieldTypes.Symbol<'Sticky nav'>>;
189
190
  }
190
191
  type BaseArticleSkeleton = EntrySkeletonType<BaseArticleFields, 'article'>;
@@ -406,7 +407,7 @@ type LinkResolverMap = Map<string, LinkResolverFunction>;
406
407
  type UrlCalculators = {
407
408
  page: (slug: string) => string;
408
409
  pageVariant: (slug: string) => string;
409
- article: (articleTypeSlug: string, slug: string) => string;
410
+ article: (articleTypeSlug: string, slug: string, primaryTagSlug?: string) => string;
410
411
  articleType: (slug: string) => string;
411
412
  tag: (slug: string) => string;
412
413
  person: (slug: string) => string;
@@ -430,6 +431,7 @@ type BaseConverterContext = {
430
431
  type ConverterContext = BaseConverterContext & {
431
432
  includes: Map<string, PossibleResolvedEntry>;
432
433
  assets: Map<string, IVisual>;
434
+ rawAssets: Map<string, Asset>;
433
435
  errors: CmsError[];
434
436
  };
435
437
 
@@ -437,10 +439,6 @@ declare function contentfulArticleRest(context: BaseConverterContext, config: Co
437
439
 
438
440
  declare function contentfulArticleTypeRest(context: BaseConverterContext, config: ContentfulConfig, indexPageSlug: string, options?: FetchOptions): Promise<CmsResponse<IBaseArticleType | null>>;
439
441
 
440
- declare function createBaseConverterContext(): BaseConverterContext;
441
-
442
- declare function contentfulCustomTypeRest(context: BaseConverterContext, config: ContentfulConfig, indexPageSlug: string, options?: FetchOptions): Promise<CmsResponse<IBaseCustomType | null>>;
443
-
444
442
  interface ContentfulResponse<T = any> {
445
443
  sys: {
446
444
  type: 'Array';
@@ -454,6 +452,31 @@ interface ContentfulResponse<T = any> {
454
452
  Asset?: any[];
455
453
  };
456
454
  }
455
+ interface ContentfulAssetFields {
456
+ title?: string;
457
+ description?: string;
458
+ file?: {
459
+ url: string;
460
+ fileName: string;
461
+ contentType: string;
462
+ details?: {
463
+ size?: number;
464
+ image?: {
465
+ width: number;
466
+ height: number;
467
+ };
468
+ };
469
+ };
470
+ }
471
+ interface ContentfulAssetResponse {
472
+ sys: {
473
+ id: string;
474
+ type: 'Asset';
475
+ createdAt: string;
476
+ updatedAt: string;
477
+ };
478
+ fields: ContentfulAssetFields;
479
+ }
457
480
  interface ContentfulQuery {
458
481
  content_type?: string;
459
482
  locale?: string;
@@ -469,11 +492,31 @@ declare class ContentfulFetchClient {
469
492
  private readonly isPreview;
470
493
  constructor(config: ContentfulConfig, preview?: boolean);
471
494
  getEntries<T = any>(query: ContentfulQuery, options?: FetchOptions): Promise<ContentfulResponse<T>>;
495
+ getAsset(assetId: string, options?: FetchOptions): Promise<ContentfulAssetResponse | null>;
472
496
  }
473
497
  declare function createContentfulClient(config: ContentfulConfig): ContentfulFetchClient;
474
498
  declare function createContentfulPreviewClient(config: ContentfulConfig): ContentfulFetchClient;
475
499
  declare function getContentfulClient(config: ContentfulConfig, preview?: boolean): ContentfulFetchClient;
476
500
 
501
+ declare function isBrowserViewable(contentType: string): boolean;
502
+ declare function contentfulAssetRest(config: ContentfulConfig, assetId: string, options?: FetchOptions): Promise<ContentfulAssetResponse | null>;
503
+ interface DownloadHandlerConfig {
504
+ getConfig: (preview?: boolean) => ContentfulConfig;
505
+ revalidate?: number;
506
+ preview?: boolean;
507
+ }
508
+ interface DownloadRouteParams {
509
+ params: Promise<{
510
+ assetId: string;
511
+ filename: string;
512
+ }>;
513
+ }
514
+ declare function createDownloadHandler(config: DownloadHandlerConfig): (_request: NextRequest, { params }: DownloadRouteParams) => Promise<Response>;
515
+
516
+ declare function createBaseConverterContext(): BaseConverterContext;
517
+
518
+ declare function contentfulCustomTypeRest(context: BaseConverterContext, config: ContentfulConfig, indexPageSlug: string, options?: FetchOptions): Promise<CmsResponse<IBaseCustomType | null>>;
519
+
477
520
  type IContentfulPerson = IBasePerson & {
478
521
  bio?: IContentfulRichText | null;
479
522
  };
@@ -653,4 +696,4 @@ declare class RateLimiter {
653
696
  getIntervalMs(): number;
654
697
  }
655
698
 
656
- export { AllTags, ArticleTag, ArticleTypeTag, AssetTag, AuthenticationError, BannerTag, type BaseConverterContext, type CmsError, type CmsResponse, type ContentResolverFunction, ContentfulFetchClient as ContentfulClient, type ContentfulConfig, ContentfulError, ContentfulFetchClient, type ConverterContext, CustomTypeTag, type DefaultChainModifier, EntryNotFoundError, type FetchOptions, GlobalTag, type IContentfulCollection, type IContentfulComponent, type IContentfulPerson, type IContentfulRichText, type IFetchedTemplate, type ISitemapEntry, LocationTag, NavigationTag, PageTag, PersonTag, type PreviewContentType, type PreviewEntryInfo, RateLimitError, RateLimiter, type RelatedArticlesOptions, type RetryConfig, type RevalidationConfig, type SitemapChangeFrequency, type SitemapConfig, type SitemapContentTypeConfig, type SitemapEntryProvider, TagTag, TemplateTag, type UrlCalculators, 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, getPreviewEntryInfo, getRetryAfter, isContentfulError, isRateLimitError, isRetryableError, isValidDate, locationTag, lookupAsset, notEmpty, pageTag, personTag, resolveLink, resolveLinks, resolveRichTextDocument, revalidateSingleTag, revalidateTags, safeDate, tagTag, templateTag, withRetry };
699
+ export { AllTags, ArticleTag, ArticleTypeTag, AssetTag, AuthenticationError, BannerTag, type BaseConverterContext, type CmsError, type CmsResponse, type ContentResolverFunction, ContentfulFetchClient as ContentfulClient, type ContentfulConfig, ContentfulError, ContentfulFetchClient, type ConverterContext, CustomTypeTag, type DefaultChainModifier, type DownloadHandlerConfig, type DownloadRouteParams, EntryNotFoundError, type FetchOptions, GlobalTag, type IContentfulCollection, type IContentfulComponent, type IContentfulPerson, type IContentfulRichText, type IFetchedTemplate, type ISitemapEntry, LocationTag, NavigationTag, PageTag, PersonTag, type PreviewContentType, type PreviewEntryInfo, RateLimitError, RateLimiter, type RelatedArticlesOptions, type RetryConfig, type RevalidationConfig, type SitemapChangeFrequency, type SitemapConfig, type SitemapContentTypeConfig, type SitemapEntryProvider, TagTag, TemplateTag, type UrlCalculators, ValidationError, arrayOrUndefined, articleTag, articleTypeIndexTag, articleTypeTag, assetTag, basePageConverter, calculateBackoffDelay, contentfulAllArticleLinks, contentfulAllArticleTypeLinks, contentfulAllPageLinks, contentfulAllPersonLinks, contentfulAllTagLinks, contentfulArticleRest, contentfulArticleSitemapEntries, contentfulArticleTypeRest, contentfulArticleTypeSitemapEntries, contentfulAssetRest, contentfulCustomTypeRest, contentfulPageRest, contentfulPageSitemapEntries, contentfulPersonSitemapEntries, contentfulTagRest, contentfulTagSitemapEntries, contentfulTemplateRest, createBaseConverterContext, createContentfulClient, createContentfulPreviewClient, createDownloadHandler, createResponsiveVisual, createRevalidationHandler, createSitemapProvider, customTypeTag, filterRelatedArticles, getAllSitemapEntries, getCacheTags, getCacheTagsForPreview, getCacheTagsForProduction, getContentfulClient, getPreviewEntryInfo, getRetryAfter, isBrowserViewable, isContentfulError, isRateLimitError, isRetryableError, isValidDate, locationTag, lookupAsset, notEmpty, pageTag, personTag, resolveLink, resolveLinks, resolveRichTextDocument, revalidateSingleTag, revalidateTags, safeDate, tagTag, templateTag, withRetry };
package/dist/index.js CHANGED
@@ -470,6 +470,57 @@ var ContentfulFetchClient = class {
470
470
  }
471
471
  throw new ContentfulError("Max retries exceeded", 500);
472
472
  }
473
+ /**
474
+ * Fetches a single asset from Contentful by ID with proactive rate limiting
475
+ * and automatic retry on rate limit errors.
476
+ *
477
+ * @param assetId - The Contentful asset ID
478
+ * @param options - Optional fetch options (locale, preview, caching, retry)
479
+ * @returns The asset response or null if not found
480
+ */
481
+ async getAsset(assetId, options) {
482
+ const url = `${this.baseUrl}/assets/${assetId}`;
483
+ const fetchOptions = {
484
+ headers: {
485
+ Authorization: `Bearer ${this.accessToken}`,
486
+ "Content-Type": "application/json"
487
+ }
488
+ };
489
+ if (options?.next) {
490
+ fetchOptions.next = options.next;
491
+ }
492
+ for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {
493
+ try {
494
+ await this.rateLimiter.acquire();
495
+ const response = await fetch(url, fetchOptions);
496
+ if (response.ok) {
497
+ return response.json();
498
+ }
499
+ if (response.status === 404) {
500
+ return null;
501
+ }
502
+ if (response.status === 429 && attempt < RETRY_CONFIG.maxRetries) {
503
+ const retryAfter = parseRetryAfter(response);
504
+ const delay3 = calculateBackoffDelay(attempt, RETRY_CONFIG, retryAfter);
505
+ console.warn(
506
+ `[Contentful] Rate limited (429), retrying in ${delay3}ms (attempt ${attempt + 1}/${RETRY_CONFIG.maxRetries}). Queue: ${this.rateLimiter.getQueueLength()} waiting. API: ${this.isPreview ? "preview" : "delivery"}`
507
+ );
508
+ const pauseSeconds = Math.max(retryAfter ?? 2, 2);
509
+ this.rateLimiter.pause(pauseSeconds);
510
+ await sleep2(delay3);
511
+ continue;
512
+ }
513
+ throw await parseErrorResponse(response);
514
+ } catch (error) {
515
+ if (error instanceof ContentfulError) {
516
+ throw error;
517
+ }
518
+ console.error("[Contentful] Unexpected error in asset request", error);
519
+ throw new ContentfulError("Unexpected error in asset request", 500);
520
+ }
521
+ }
522
+ throw new ContentfulError("Max retries exceeded", 500);
523
+ }
473
524
  };
474
525
  function createContentfulClient(config) {
475
526
  return new ContentfulFetchClient(config, false);
@@ -516,6 +567,20 @@ function lookupIconAsset(context, asset) {
516
567
  }
517
568
  return visual;
518
569
  }
570
+ function lookupDownloadAsset(context, asset) {
571
+ if (!asset) return void 0;
572
+ const rawAsset = context.rawAssets.get(asset.sys.id);
573
+ if (!rawAsset?.fields?.file) return void 0;
574
+ const file = rawAsset.fields.file;
575
+ if (!file.url || !file.fileName || !file.contentType) return void 0;
576
+ return {
577
+ assetId: rawAsset.sys.id,
578
+ filename: file.fileName,
579
+ contentType: file.contentType,
580
+ size: file.details?.size,
581
+ url: `https:${file.url}`
582
+ };
583
+ }
519
584
  var DEFAULT_POSITION_FIELDS = {
520
585
  index: 0,
521
586
  isFirst: false,
@@ -582,6 +647,7 @@ function convertAssetToVisual(context, asset, options) {
582
647
  const { contentType } = file;
583
648
  if (contentType?.startsWith("image/")) {
584
649
  const image = convertAssetToImage(file, fields, sys, options);
650
+ if (!image) return void 0;
585
651
  return {
586
652
  id,
587
653
  type: "Visual",
@@ -596,6 +662,7 @@ function convertAssetToVisual(context, asset, options) {
596
662
  context,
597
663
  options
598
664
  );
665
+ if (!video) return void 0;
599
666
  return {
600
667
  id,
601
668
  type: "Visual",
@@ -609,6 +676,7 @@ function convertAssetToVisual(context, asset, options) {
609
676
  sys,
610
677
  options
611
678
  );
679
+ if (!animation) return void 0;
612
680
  return {
613
681
  id,
614
682
  type: "Visual",
@@ -621,6 +689,7 @@ function convertAssetToAnimation(file, fields, sys, options) {
621
689
  const { id } = sys;
622
690
  const { title, description } = fields;
623
691
  const { url } = file;
692
+ if (!url) return void 0;
624
693
  return {
625
694
  ...options,
626
695
  id,
@@ -642,6 +711,7 @@ function convertAssetToPicture(file, fields, sys, options) {
642
711
  const { id } = sys;
643
712
  const { title, description } = fields;
644
713
  const { contentType, details, url } = file;
714
+ if (!details) return void 0;
645
715
  const { size, image } = details;
646
716
  const { width, height } = image || {};
647
717
  return {
@@ -661,6 +731,7 @@ function convertAssetToSvgImage(file, fields, sys, options) {
661
731
  const { id } = sys;
662
732
  const { title, description } = fields;
663
733
  const { contentType, details, url } = file;
734
+ if (!details) return void 0;
664
735
  const { size, image } = details;
665
736
  const { width, height } = image || {};
666
737
  return {
@@ -679,6 +750,7 @@ function convertAssetToSvgImage(file, fields, sys, options) {
679
750
  function convertAssetToVideoDetails(file, sys) {
680
751
  const { id } = sys;
681
752
  const { details, url, contentType, fileName } = file;
753
+ if (!details) return void 0;
682
754
  const { size } = details;
683
755
  return {
684
756
  id,
@@ -692,9 +764,13 @@ function convertAssetToVideo(file, fields, sys, context, options) {
692
764
  const { id } = sys;
693
765
  const { title, description } = fields;
694
766
  const { details } = file;
767
+ if (!details) {
768
+ return void 0;
769
+ }
695
770
  const { image } = details;
696
771
  const { width, height } = image || {};
697
772
  const videoDetails = convertAssetToVideoDetails(file, sys);
773
+ if (!videoDetails) return void 0;
698
774
  return {
699
775
  ...options,
700
776
  id,
@@ -1031,7 +1107,7 @@ function safeDate(date) {
1031
1107
 
1032
1108
  // src/api/helpers.ts
1033
1109
  var PAGE_LINK_FIELDS = "sys,fields.cmsLabel,fields.title,fields.slug,fields.featuredImage,fields.backgroundColour,fields.textColour,fields.indexed,fields.hidden,fields.tags";
1034
- 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";
1110
+ var ARTICLE_LINK_FIELDS = "sys,fields.cmsLabel,fields.title,fields.slug,fields.featuredImage,fields.backgroundColour,fields.textColour,fields.indexed,fields.hidden,fields.tags,fields.articleType,fields.date,fields.author,fields.externalLink,fields.download";
1035
1111
  var ARTICLE_TYPE_LINK_FIELDS = "sys,fields.name,fields.slug,fields.featuredImage,fields.backgroundColour,fields.textColour,fields.indexed,fields.hidden";
1036
1112
  var TAG_LINK_FIELDS = "sys,fields.cmsLabel,fields.name,fields.slug,fields.featuredImage,fields.backgroundColour,fields.textColour,fields.indexed,fields.hidden";
1037
1113
  var PERSON_LINK_FIELDS = "sys,fields.name,fields.slug,fields.media,fields.backgroundColour,fields.textColour,fields.indexed,fields.hidden";
@@ -1048,6 +1124,16 @@ function convertAllAssets(response, context) {
1048
1124
  }
1049
1125
  return visuals;
1050
1126
  }
1127
+ function convertAllRawAssets(response) {
1128
+ const rawAssets = /* @__PURE__ */ new Map();
1129
+ const assets = response.includes?.Asset;
1130
+ if (assets && assets.length > 0) {
1131
+ for (const asset of assets) {
1132
+ rawAssets.set(asset.sys.id, asset);
1133
+ }
1134
+ }
1135
+ return rawAssets;
1136
+ }
1051
1137
  function convertAllIncludes(response) {
1052
1138
  const includes = /* @__PURE__ */ new Map();
1053
1139
  const entries = [...response.items, ...response.includes?.Entry || []];
@@ -1095,11 +1181,13 @@ async function fetchSingleEntity(context, config, fetchConfig, options) {
1095
1181
  }
1096
1182
  try {
1097
1183
  const assets = convertAllAssets(response, context);
1184
+ const rawAssets = convertAllRawAssets(response);
1098
1185
  const includes = convertAllIncludes(response);
1099
1186
  const fullContext = {
1100
1187
  ...context,
1101
1188
  includes,
1102
1189
  assets,
1190
+ rawAssets,
1103
1191
  errors: []
1104
1192
  };
1105
1193
  const converted = fetchConfig.resolver(fullContext, entry);
@@ -1162,10 +1250,12 @@ async function fetchAllLinks(contentType, client, requestOptions, converter, con
1162
1250
  }
1163
1251
  const includes = convertAllIncludes(response);
1164
1252
  const assets = convertAllAssets(response, context);
1253
+ const rawAssets = convertAllRawAssets(response);
1165
1254
  const fullContext = {
1166
1255
  ...context,
1167
1256
  includes,
1168
1257
  assets,
1258
+ rawAssets,
1169
1259
  errors: []
1170
1260
  };
1171
1261
  for (const entry of response.items) {
@@ -1241,6 +1331,80 @@ async function contentfulArticleTypeRest(context, config, indexPageSlug, options
1241
1331
  );
1242
1332
  }
1243
1333
 
1334
+ // src/api/asset.ts
1335
+ init_utils();
1336
+ var BROWSER_VIEWABLE_TYPES = [
1337
+ "application/pdf",
1338
+ "text/plain",
1339
+ "text/html",
1340
+ "text/css",
1341
+ "text/javascript",
1342
+ "application/json"
1343
+ ];
1344
+ function isBrowserViewable(contentType) {
1345
+ if (contentType.startsWith("image/") || contentType.startsWith("video/")) {
1346
+ return true;
1347
+ }
1348
+ return BROWSER_VIEWABLE_TYPES.includes(contentType);
1349
+ }
1350
+ async function contentfulAssetRest(config, assetId, options) {
1351
+ const client = getContentfulClient(config, options?.preview);
1352
+ const cacheTags = getCacheTags("asset", assetId, options?.preview);
1353
+ return client.getAsset(assetId, {
1354
+ ...options,
1355
+ next: {
1356
+ revalidate: options?.next?.revalidate ?? 86400,
1357
+ // Default 24 hours
1358
+ tags: [...cacheTags, ...options?.next?.tags ?? []]
1359
+ }
1360
+ });
1361
+ }
1362
+ function createDownloadHandler(config) {
1363
+ const { getConfig, revalidate = 86400, preview = false } = config;
1364
+ return async function GET(_request, { params }) {
1365
+ const { assetId, filename } = await params;
1366
+ const decodedFilename = decodeURIComponent(filename);
1367
+ try {
1368
+ const contentfulConfig = getConfig(preview);
1369
+ const asset = await contentfulAssetRest(contentfulConfig, assetId, {
1370
+ preview,
1371
+ next: { revalidate }
1372
+ });
1373
+ if (!asset?.fields?.file) {
1374
+ return Response.json({ error: "Asset not found" }, { status: 404 });
1375
+ }
1376
+ const { file } = asset.fields;
1377
+ const assetUrl = `https:${file.url}`;
1378
+ const cacheTags = getCacheTags("asset", assetId, preview);
1379
+ const fileFetchOptions = {
1380
+ next: {
1381
+ revalidate,
1382
+ tags: cacheTags
1383
+ }
1384
+ };
1385
+ const fileResponse = await fetch(assetUrl, fileFetchOptions);
1386
+ if (!fileResponse.ok) {
1387
+ return Response.json({ error: "Failed to fetch file" }, { status: 502 });
1388
+ }
1389
+ const headers = new Headers();
1390
+ headers.set("Content-Type", file.contentType);
1391
+ const disposition = isBrowserViewable(file.contentType) ? "inline" : "attachment";
1392
+ headers.set("Content-Disposition", `${disposition}; filename="${decodedFilename}"`);
1393
+ if (file.details?.size) {
1394
+ headers.set("Content-Length", String(file.details.size));
1395
+ }
1396
+ headers.set("Cache-Control", `public, max-age=${revalidate}, s-maxage=${revalidate}`);
1397
+ return new Response(fileResponse.body, {
1398
+ status: 200,
1399
+ headers
1400
+ });
1401
+ } catch (error) {
1402
+ console.error("[Download] Error proxying file:", error);
1403
+ return Response.json({ error: "Internal server error" }, { status: 500 });
1404
+ }
1405
+ };
1406
+ }
1407
+
1244
1408
  // src/converters/resolver.ts
1245
1409
  function resolveHelper(context, fromId, entry, getResolver) {
1246
1410
  const id = entry.sys.id;
@@ -1734,7 +1898,7 @@ function baseArticleConverter(context, entry) {
1734
1898
  function calculateArticleTypeHref(slug) {
1735
1899
  return `/${slug}/`;
1736
1900
  }
1737
- function calculateArticleHref(articleTypeSlug, slug) {
1901
+ function calculateArticleHref(articleTypeSlug, slug, _primaryTagSlug) {
1738
1902
  return `${calculateArticleTypeHref(articleTypeSlug)}${slug}/`;
1739
1903
  }
1740
1904
  function baseArticleLinkConverter(context, entry) {
@@ -1747,26 +1911,24 @@ function baseArticleLinkConverter(context, entry) {
1747
1911
  tags: fieldsTags,
1748
1912
  author: fieldsAuthor,
1749
1913
  date,
1914
+ externalLink,
1915
+ download: fieldsDownload,
1750
1916
  ...simpleFields
1751
1917
  } = fields;
1752
1918
  const articleType = resolveLink(context, sys.id, fieldsArticleType);
1753
1919
  const tags = fieldsTags?.map((tag) => resolveLink(context, sys.id, tag));
1754
1920
  const primaryTag = tags?.[0] ?? void 0;
1755
1921
  const author = fieldsAuthor ? resolveLink(context, sys.id, fieldsAuthor) : void 0;
1756
- return createInternalLink(
1757
- sys.id,
1758
- simpleFields,
1759
- context,
1760
- context.urlCalculators.article(articleType.slug, fields.slug),
1761
- "Article",
1762
- {
1763
- tags,
1764
- primaryTag,
1765
- articleType,
1766
- date,
1767
- author
1768
- }
1769
- );
1922
+ const download = lookupDownloadAsset(context, fieldsDownload);
1923
+ const href = externalLink ? externalLink : context.urlCalculators.article(articleType.slug, fields.slug, primaryTag?.slug);
1924
+ return createInternalLink(sys.id, simpleFields, context, href, "Article", {
1925
+ tags,
1926
+ primaryTag,
1927
+ articleType,
1928
+ date,
1929
+ author,
1930
+ download
1931
+ });
1770
1932
  }
1771
1933
  function baseArticleTypeLinkConverter(context, entry) {
1772
1934
  const { sys, fields } = entry;
@@ -2291,13 +2453,15 @@ function calculateTagHref(slug) {
2291
2453
  }
2292
2454
  function baseTagLinkConverter(context, entry) {
2293
2455
  const { sys, fields } = entry;
2456
+ const { name, ...simpleFields } = fields;
2294
2457
  if (sys.contentType.sys.id !== "tag") {
2295
2458
  throw new Error(`Invalid content type: expected "tag", got "${sys.contentType.sys.id}"`);
2296
2459
  }
2297
2460
  return createInternalLink(
2298
2461
  sys.id,
2299
2462
  {
2300
- ...fields
2463
+ ...simpleFields,
2464
+ title: name
2301
2465
  },
2302
2466
  context,
2303
2467
  context.urlCalculators.tag(fields.slug),
@@ -3076,6 +3240,6 @@ function createRevalidationHandler(config = {}) {
3076
3240
  init_tags();
3077
3241
  init_utils();
3078
3242
 
3079
- 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, getPreviewEntryInfo, getRetryAfter, isContentfulError, isRateLimitError, isRetryableError, isValidDate, locationTag, lookupAsset, notEmpty, pageTag, personTag, resolveLink, resolveLinks, resolveRichTextDocument, revalidateSingleTag, revalidateTags, safeDate, tagTag, templateTag, withRetry };
3243
+ 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, contentfulAssetRest, contentfulCustomTypeRest, contentfulPageRest, contentfulPageSitemapEntries, contentfulPersonSitemapEntries, contentfulTagRest, contentfulTagSitemapEntries, contentfulTemplateRest, createBaseConverterContext, createContentfulClient, createContentfulPreviewClient, createDownloadHandler, createResponsiveVisual, createRevalidationHandler, createSitemapProvider, customTypeTag, filterRelatedArticles, getAllSitemapEntries, getCacheTags, getCacheTagsForPreview, getCacheTagsForProduction, getContentfulClient, getPreviewEntryInfo, getRetryAfter, isBrowserViewable, isContentfulError, isRateLimitError, isRetryableError, isValidDate, locationTag, lookupAsset, notEmpty, pageTag, personTag, resolveLink, resolveLinks, resolveRichTextDocument, revalidateSingleTag, revalidateTags, safeDate, tagTag, templateTag, withRetry };
3080
3244
  //# sourceMappingURL=index.js.map
3081
3245
  //# sourceMappingURL=index.js.map