@se-studio/contentful-rest-api 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1435 @@
1
+ import { createClient } from 'contentful';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __esm = (fn, res) => function __init() {
6
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
+ };
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+
13
+ // src/utils/timing.ts
14
+ var timing_exports = {};
15
+ __export(timing_exports, {
16
+ Timer: () => Timer,
17
+ createTimer: () => createTimer,
18
+ logTimingResult: () => logTimingResult
19
+ });
20
+ function formatTimingResult(result, indent = "") {
21
+ const lines = [];
22
+ let output = `${result.label}: ${result.duration}ms`;
23
+ if (result.metadata) {
24
+ const metadataStr = Object.entries(result.metadata).map(([k, v]) => `${k}: ${v}`).join(", ");
25
+ output += ` (${metadataStr})`;
26
+ }
27
+ if (result.memoryDelta) {
28
+ const mem = result.memoryDelta;
29
+ const parts = [];
30
+ if (Math.abs(mem.heapUsedMB) > 0.01) {
31
+ parts.push(`heap: ${mem.heapUsedMB >= 0 ? "+" : ""}${mem.heapUsedMB}MB`);
32
+ }
33
+ if (Math.abs(mem.rssMB) > 0.01) {
34
+ parts.push(`rss: ${mem.rssMB >= 0 ? "+" : ""}${mem.rssMB}MB`);
35
+ }
36
+ if (Math.abs(mem.externalMB) > 0.01) {
37
+ parts.push(`ext: ${mem.externalMB >= 0 ? "+" : ""}${mem.externalMB}MB`);
38
+ }
39
+ if (parts.length > 0) {
40
+ output += ` [${parts.join(", ")}]`;
41
+ }
42
+ }
43
+ lines.push(`${indent}${output}`);
44
+ if (result.children && result.children.length > 0) {
45
+ for (let i = 0; i < result.children.length; i++) {
46
+ const child = result.children[i];
47
+ if (!child) continue;
48
+ const isLast = i === result.children.length - 1;
49
+ const childIndent = indent + (isLast ? " \u2514\u2500 " : " \u251C\u2500 ");
50
+ const grandchildIndent = indent + (isLast ? " " : " \u2502 ");
51
+ const childLines = formatTimingResult(child, "").split("\n");
52
+ lines.push(childIndent + childLines[0]);
53
+ for (let j = 1; j < childLines.length; j++) {
54
+ lines.push(grandchildIndent + childLines[j]);
55
+ }
56
+ }
57
+ }
58
+ return lines.join("\n");
59
+ }
60
+ function logTimingResult(result) {
61
+ if (!shouldLog) return;
62
+ console.log(`
63
+ [Timing] ${formatTimingResult(result)}`);
64
+ }
65
+ function createTimer(label) {
66
+ return new Timer(label);
67
+ }
68
+ var isDevelopment, isDebugTimingEnabled, shouldLog, Timer;
69
+ var init_timing = __esm({
70
+ "src/utils/timing.ts"() {
71
+ isDevelopment = process.env.NODE_ENV === "development";
72
+ isDebugTimingEnabled = process.env.DEBUG_TIMING === "true";
73
+ shouldLog = isDevelopment || isDebugTimingEnabled;
74
+ Timer = class _Timer {
75
+ startTime;
76
+ memoryStart;
77
+ label;
78
+ children = [];
79
+ metadata = {};
80
+ trackMemory;
81
+ constructor(label, trackMemory = true) {
82
+ this.label = label;
83
+ this.startTime = performance.now();
84
+ this.trackMemory = trackMemory && shouldLog;
85
+ if (this.trackMemory && typeof process !== "undefined" && process.memoryUsage) {
86
+ this.memoryStart = process.memoryUsage();
87
+ }
88
+ }
89
+ /**
90
+ * Add metadata to the timing result
91
+ */
92
+ addMetadata(key, value) {
93
+ this.metadata[key] = value;
94
+ }
95
+ /**
96
+ * Create and return a child timer
97
+ */
98
+ child(label) {
99
+ return new _Timer(`${this.label} > ${label}`);
100
+ }
101
+ /**
102
+ * End the timer and return the result
103
+ */
104
+ end() {
105
+ const duration = performance.now() - this.startTime;
106
+ const result = {
107
+ label: this.label,
108
+ duration: Math.round(duration * 100) / 100,
109
+ // Round to 2 decimal places
110
+ children: this.children.length > 0 ? this.children : void 0,
111
+ metadata: Object.keys(this.metadata).length > 0 ? this.metadata : void 0
112
+ };
113
+ if (this.memoryStart && typeof process !== "undefined" && process.memoryUsage) {
114
+ const memoryEnd = process.memoryUsage();
115
+ result.memoryDelta = {
116
+ heapUsedMB: Number(
117
+ ((memoryEnd.heapUsed - this.memoryStart.heapUsed) / 1024 / 1024).toFixed(2)
118
+ ),
119
+ heapTotalMB: Number(
120
+ ((memoryEnd.heapTotal - this.memoryStart.heapTotal) / 1024 / 1024).toFixed(2)
121
+ ),
122
+ rssMB: Number(((memoryEnd.rss - this.memoryStart.rss) / 1024 / 1024).toFixed(2)),
123
+ externalMB: Number(
124
+ ((memoryEnd.external - this.memoryStart.external) / 1024 / 1024).toFixed(2)
125
+ )
126
+ };
127
+ }
128
+ return result;
129
+ }
130
+ /**
131
+ * End timer and log the result
132
+ */
133
+ endAndLog() {
134
+ const result = this.end();
135
+ if (shouldLog) {
136
+ logTimingResult(result);
137
+ }
138
+ return result;
139
+ }
140
+ /**
141
+ * Execute a function and time it
142
+ */
143
+ static async time(label, fn, metadata) {
144
+ const timer = new _Timer(label);
145
+ if (metadata) {
146
+ for (const [key, value] of Object.entries(metadata)) {
147
+ timer.addMetadata(key, value);
148
+ }
149
+ }
150
+ const result = await fn();
151
+ const timing = timer.end();
152
+ return { result, timing };
153
+ }
154
+ /**
155
+ * Execute a synchronous function and time it
156
+ */
157
+ static timeSync(label, fn, metadata) {
158
+ const timer = new _Timer(label);
159
+ if (metadata) {
160
+ for (const [key, value] of Object.entries(metadata)) {
161
+ timer.addMetadata(key, value);
162
+ }
163
+ }
164
+ const result = fn();
165
+ const timing = timer.end();
166
+ return { result, timing };
167
+ }
168
+ };
169
+ }
170
+ });
171
+ function createContentfulClient(config) {
172
+ return createClient({
173
+ space: config.spaceId,
174
+ accessToken: config.accessToken,
175
+ environment: config.environment || "master",
176
+ host: config.host,
177
+ ...config.options
178
+ });
179
+ }
180
+ function createContentfulPreviewClient(config) {
181
+ return createClient({
182
+ space: config.spaceId,
183
+ accessToken: config.accessToken,
184
+ environment: config.environment || "master",
185
+ host: "preview.contentful.com",
186
+ ...config.options
187
+ });
188
+ }
189
+ function getContentfulClient(config, preview = false) {
190
+ return (preview ? createContentfulPreviewClient(config) : createContentfulClient(config)).withoutLinkResolution;
191
+ }
192
+
193
+ // src/converters/helpers.ts
194
+ function makeContentfulTitle(title, id, prefix = "Title for ") {
195
+ return title ?? `${prefix}${id}`;
196
+ }
197
+ function makeContentfulDescription(description, id) {
198
+ return description ?? `Description for ${id}`;
199
+ }
200
+ function lookupAsset(context, asset) {
201
+ if (!asset) return void 0;
202
+ return context.assets.get(asset.sys.id);
203
+ }
204
+ var DEFAULT_POSITION_FIELDS = {
205
+ index: 0,
206
+ isFirst: false,
207
+ isLast: false,
208
+ indexOfType: 0
209
+ };
210
+ function createInternalLink(id, fields, context, href, additionalProps) {
211
+ const { cmsLabel, title, featuredImage, backgroundColour, textColour, indexed, hidden, slug } = fields;
212
+ return {
213
+ type: "Internal link",
214
+ id,
215
+ name: cmsLabel,
216
+ useName: true,
217
+ text: makeContentfulTitle(title, id),
218
+ visual: lookupAsset(context, featuredImage),
219
+ backgroundColour,
220
+ textColour,
221
+ indexed,
222
+ hidden,
223
+ slug,
224
+ href,
225
+ ...additionalProps
226
+ };
227
+ }
228
+ function addPositionMetadata(items) {
229
+ if (items.length === 0) return items;
230
+ const typeCount = /* @__PURE__ */ new Map();
231
+ return items.map((item, index) => {
232
+ const currentTypeIndex = typeCount.get(item.type) ?? 0;
233
+ typeCount.set(item.type, currentTypeIndex + 1);
234
+ return {
235
+ ...item,
236
+ index,
237
+ isFirst: index === 0,
238
+ isLast: index === items.length - 1,
239
+ indexOfType: currentTypeIndex
240
+ };
241
+ });
242
+ }
243
+
244
+ // src/converters/asset.ts
245
+ function convertAssetToVisual(asset, options) {
246
+ if (!asset) return void 0;
247
+ const { fields, sys } = asset;
248
+ if (!fields) return void 0;
249
+ const { id } = sys;
250
+ const { file } = fields;
251
+ if (!file) return void 0;
252
+ const { contentType } = file;
253
+ if (contentType?.startsWith("image/")) {
254
+ const image = convertAssetToImage(file, fields, sys, options);
255
+ return {
256
+ id,
257
+ type: "Visual",
258
+ image
259
+ };
260
+ }
261
+ if (contentType?.startsWith("video/")) {
262
+ const video = convertAssetToVideo(file, fields, sys, options);
263
+ return {
264
+ id,
265
+ type: "Visual",
266
+ video
267
+ };
268
+ }
269
+ return void 0;
270
+ }
271
+ function convertAssetToImage(file, fields, sys, options) {
272
+ const { id } = sys;
273
+ const { title, description } = fields;
274
+ const { contentType, details, url } = file;
275
+ const { size, image } = details;
276
+ const { width, height } = image || {};
277
+ return {
278
+ ...options,
279
+ id,
280
+ type: "Picture",
281
+ src: `https:${url}`,
282
+ mimeType: contentType,
283
+ size,
284
+ width: width || 0,
285
+ height: height || 0,
286
+ name: makeContentfulTitle(title, id),
287
+ description: makeContentfulDescription(description, id)
288
+ };
289
+ }
290
+ function convertAssetToVideoDetails(file, sys) {
291
+ const { id } = sys;
292
+ const { details, url, contentType, fileName } = file;
293
+ const { size } = details;
294
+ return {
295
+ id,
296
+ videoUrl: `https:${url}`,
297
+ size,
298
+ mimeType: contentType,
299
+ fileName
300
+ };
301
+ }
302
+ function convertAssetToVideo(file, fields, sys, options) {
303
+ const { id } = sys;
304
+ const { title, description } = fields;
305
+ const { details } = file;
306
+ const { image } = details;
307
+ const { width, height } = image || {};
308
+ const videoDetails = convertAssetToVideoDetails(file, sys);
309
+ return {
310
+ ...options,
311
+ id,
312
+ type: "Local video",
313
+ preview: videoDetails,
314
+ width: width || 0,
315
+ height: height || 0,
316
+ name: makeContentfulTitle(title, id),
317
+ description: makeContentfulDescription(description, id)
318
+ };
319
+ }
320
+ function createResponsiveVisual(visual, mobileVisual, customSize) {
321
+ if (!visual) return void 0;
322
+ return {
323
+ visual,
324
+ mobileVisual,
325
+ visualCustomSize: customSize
326
+ };
327
+ }
328
+ function convertMediaEntryToVisual(context, entry) {
329
+ const { fields, sys } = entry;
330
+ if (!fields) return void 0;
331
+ const baseVisual = lookupAsset(context, fields.asset);
332
+ if (!baseVisual) return void 0;
333
+ const metadata = {
334
+ name: makeContentfulTitle(fields.name, sys.id),
335
+ nameAsCaption: fields.nameAsCaption ?? null,
336
+ dontCrop: fields.dontCrop ?? null,
337
+ verticalCropPosition: fields.verticalCropPosition ?? null,
338
+ horizontalCropPosition: fields.horizontalCropPosition ?? null,
339
+ horizontalPosition: fields.horizontalPosition ?? null,
340
+ widthPercent: fields.width ?? null
341
+ };
342
+ const visual = {
343
+ ...baseVisual,
344
+ id: sys.id
345
+ };
346
+ if (baseVisual.image) {
347
+ visual.image = {
348
+ ...baseVisual.image,
349
+ ...metadata
350
+ };
351
+ } else if (baseVisual.video) {
352
+ visual.video = {
353
+ ...baseVisual.video,
354
+ ...metadata,
355
+ autoPlay: fields.autoplay ?? null,
356
+ loop: fields.loop ?? null,
357
+ hideControls: fields.hideControls ?? null
358
+ };
359
+ }
360
+ return visual;
361
+ }
362
+ function convertExternalVideoEntryToVisual(context, entry) {
363
+ const { fields, sys } = entry;
364
+ if (!fields || !fields.url) return void 0;
365
+ const previewVisual = lookupAsset(context, fields.preview);
366
+ const posterVisual = lookupAsset(context, fields.posterImage);
367
+ const preview = previewVisual?.video?.type === "Local video" ? previewVisual.video.preview : void 0;
368
+ const poster = posterVisual?.image?.type === "Picture" ? posterVisual.image.src : void 0;
369
+ const mapHorizontal = (value) => {
370
+ if (!value) return null;
371
+ return value === "Center" ? "Middle" : value;
372
+ };
373
+ const video = {
374
+ id: sys.id,
375
+ type: "External video",
376
+ name: makeContentfulTitle(fields.name, sys.id),
377
+ nameAsCaption: fields.nameAsCaption ?? null,
378
+ external: fields.url,
379
+ preview,
380
+ poster,
381
+ autoPlay: fields.autoPlay ?? null,
382
+ loop: fields.loop ?? null,
383
+ hideControls: fields.hideControls ?? null,
384
+ dontCrop: fields.dontCrop ?? null,
385
+ verticalCropPosition: fields.verticalCropPosition ?? null,
386
+ horizontalCropPosition: mapHorizontal(fields.horizontalCropPosition),
387
+ horizontalPosition: fields.horizontalPosition ?? null,
388
+ widthPercent: fields.width ?? null
389
+ };
390
+ return {
391
+ id: sys.id,
392
+ type: "Visual",
393
+ video
394
+ };
395
+ }
396
+ function lookupMediaEntry(context, link) {
397
+ if (!link) return void 0;
398
+ const id = link.sys.id;
399
+ const possibleEntry = context.includes.get(id);
400
+ if (!possibleEntry) {
401
+ return void 0;
402
+ }
403
+ const { type, entry } = possibleEntry;
404
+ if (type === "media") {
405
+ return convertMediaEntryToVisual(
406
+ context,
407
+ entry
408
+ );
409
+ }
410
+ if (type === "externalVideo") {
411
+ return convertExternalVideoEntryToVisual(
412
+ context,
413
+ entry
414
+ );
415
+ }
416
+ return void 0;
417
+ }
418
+
419
+ // src/converters/externalComponent.ts
420
+ function baseExternalComponentConverter(_context, entry) {
421
+ const { sys, fields } = entry;
422
+ const {
423
+ externalComponentType,
424
+ cmsLabel,
425
+ data,
426
+ heading: _heading,
427
+ // Exclude: not in target interface
428
+ ...simpleFields
429
+ // backgroundColour, textColour
430
+ } = fields;
431
+ return {
432
+ type: "External component",
433
+ id: sys.id,
434
+ name: cmsLabel ?? `External component ${sys.id}`,
435
+ cmsLabel: cmsLabel ?? null,
436
+ externalType: externalComponentType,
437
+ data: data ?? null,
438
+ ...DEFAULT_POSITION_FIELDS,
439
+ ...simpleFields,
440
+ backgroundOverlayOpacity: null,
441
+ backgroundVisual: void 0
442
+ };
443
+ }
444
+
445
+ // src/converters/resolver.ts
446
+ function resolveHelper(context, entry, getResolver) {
447
+ const id = entry.sys.id;
448
+ const possibleEntry = context.includes.get(id);
449
+ if (!possibleEntry) {
450
+ throw new Error(`Cannot find included entry for link with id ${id}`);
451
+ }
452
+ if (!possibleEntry.resolved) {
453
+ const resolver = getResolver(possibleEntry.type);
454
+ if (!resolver) {
455
+ throw new Error(
456
+ `No resolver found for link type ${possibleEntry.type} (${JSON.stringify(possibleEntry)}) [${JSON.stringify(entry)}]`
457
+ );
458
+ }
459
+ const resolved = resolver(
460
+ context,
461
+ possibleEntry.entry
462
+ );
463
+ possibleEntry.resolved = resolved;
464
+ return resolved;
465
+ }
466
+ return possibleEntry.resolved;
467
+ }
468
+ function resolveLink(context, entry) {
469
+ return resolveHelper(
470
+ context,
471
+ entry,
472
+ (type) => context.linkResolver.get(type)
473
+ );
474
+ }
475
+ function resolveNavigationItem(context, entry) {
476
+ return resolveHelper(
477
+ context,
478
+ entry,
479
+ () => context.navigationItemResolver
480
+ );
481
+ }
482
+ function resolveCollectionContent(context, entry) {
483
+ return resolveHelper(context, entry, (type) => {
484
+ if (type === "component") {
485
+ return context.componentResolver;
486
+ }
487
+ if (type === "collection") {
488
+ return context.collectionResolver;
489
+ }
490
+ return void 0;
491
+ });
492
+ }
493
+ function resolvePageContent(context, entry) {
494
+ const id = entry.sys.id;
495
+ const possibleEntry = context.includes.get(id);
496
+ if (!possibleEntry) {
497
+ throw new Error(`Cannot find included entry for content with id ${id}`);
498
+ }
499
+ const { type } = possibleEntry;
500
+ if (type === "component" || type === "collection") {
501
+ return resolveCollectionContent(context, entry);
502
+ }
503
+ if (type === "media") {
504
+ const visual = convertMediaEntryToVisual(
505
+ context,
506
+ possibleEntry.entry
507
+ );
508
+ if (!visual) {
509
+ throw new Error(`Failed to convert media entry with id ${id}`);
510
+ }
511
+ return visual;
512
+ }
513
+ if (type === "externalVideo") {
514
+ const visual = convertExternalVideoEntryToVisual(
515
+ context,
516
+ possibleEntry.entry
517
+ );
518
+ if (!visual) {
519
+ throw new Error(`Failed to convert externalVideo entry with id ${id}`);
520
+ }
521
+ return visual;
522
+ }
523
+ if (type === "externalComponent") {
524
+ return resolveHelper(context, entry, () => baseExternalComponentConverter);
525
+ }
526
+ if (context.linkResolver.has(type)) {
527
+ return resolveLink(context, entry);
528
+ }
529
+ throw new Error(`Unknown content type "${type}" for entry with id ${id}`);
530
+ }
531
+ function resolveRichTextDocument(context, richText) {
532
+ if (!richText) {
533
+ return void 0;
534
+ }
535
+ const resolvingEntries = /* @__PURE__ */ new Set();
536
+ const resolvedEntries = /* @__PURE__ */ new Set();
537
+ const resolveNode = (node) => {
538
+ if (!("content" in node)) {
539
+ return node;
540
+ }
541
+ const block = node;
542
+ if (block.data.target && "sys" in block.data.target && block.data.target.sys.linkType === "Entry") {
543
+ const entryId = block.data.target.sys.id;
544
+ if (resolvingEntries.has(entryId)) {
545
+ console.warn(
546
+ `Circular reference detected in rich text resolution for entry ${entryId}. Skipping to prevent infinite recursion.`
547
+ );
548
+ return {
549
+ ...block,
550
+ content: block.content?.map(resolveNode)
551
+ };
552
+ }
553
+ if (resolvedEntries.has(entryId)) {
554
+ return {
555
+ ...block,
556
+ content: block.content?.map(resolveNode)
557
+ };
558
+ }
559
+ try {
560
+ resolvingEntries.add(entryId);
561
+ const resolvedEntry = resolvePageContent(
562
+ context,
563
+ block.data.target
564
+ );
565
+ resolvingEntries.delete(entryId);
566
+ resolvedEntries.add(entryId);
567
+ return {
568
+ ...block,
569
+ data: {
570
+ ...block.data,
571
+ target: resolvedEntry
572
+ },
573
+ content: block.content?.map(resolveNode)
574
+ };
575
+ } catch (error) {
576
+ resolvingEntries.delete(entryId);
577
+ console.error(`Failed to resolve entry with id ${entryId}:`, error);
578
+ return {
579
+ ...block,
580
+ content: block.content?.map(resolveNode)
581
+ };
582
+ }
583
+ }
584
+ if (block.data.target && "sys" in block.data.target && block.data.target.sys.linkType === "Asset") {
585
+ try {
586
+ const resolvedAsset = lookupAsset(context, block.data.target);
587
+ if (resolvedAsset) {
588
+ return {
589
+ ...block,
590
+ data: {
591
+ ...block.data,
592
+ target: resolvedAsset
593
+ },
594
+ content: block.content?.map(resolveNode)
595
+ };
596
+ }
597
+ } catch (error) {
598
+ console.error(
599
+ `Failed to resolve embedded-asset-block with id ${block.data.target.sys.id}:`,
600
+ error
601
+ );
602
+ return {
603
+ ...block,
604
+ content: block.content?.map(resolveNode)
605
+ };
606
+ }
607
+ }
608
+ return {
609
+ ...block,
610
+ content: block.content?.map(resolveNode)
611
+ };
612
+ };
613
+ const resolved = resolveNode(richText);
614
+ return { json: resolved };
615
+ }
616
+
617
+ // src/converters/collection.ts
618
+ function baseCollectionConverter(context, entry) {
619
+ const { sys, fields } = entry;
620
+ const {
621
+ // Fields requiring transformation
622
+ backgroundVisual: bgVisual,
623
+ mobileBackgroundVisual: mobileBgVisual,
624
+ visual: visualField,
625
+ mobileVisual: mobileVisualField,
626
+ visualCustomSize,
627
+ icon: iconField,
628
+ links: linksField,
629
+ contents: contentsField,
630
+ body: bodyField,
631
+ additionalCopy: additionalCopyField,
632
+ // Field name change
633
+ collectionType,
634
+ // CMS-only fields (must exclude)
635
+ textSize: _textSize,
636
+ showHeading: _showHeading,
637
+ // Already handled elsewhere
638
+ cmsLabel,
639
+ ...simpleFields
640
+ // anchor, backgroundColour, textColour, preHeading, heading, postHeading, backgroundOverlayOpacity
641
+ } = fields;
642
+ const backgroundVisual = createResponsiveVisual(
643
+ lookupAsset(context, bgVisual),
644
+ lookupAsset(context, mobileBgVisual)
645
+ );
646
+ const visual = createResponsiveVisual(
647
+ lookupMediaEntry(context, visualField),
648
+ lookupMediaEntry(context, mobileVisualField),
649
+ visualCustomSize
650
+ );
651
+ return {
652
+ type: "Collection",
653
+ id: sys.id,
654
+ name: cmsLabel,
655
+ cmsLabel,
656
+ collectionType,
657
+ ...DEFAULT_POSITION_FIELDS,
658
+ ...simpleFields,
659
+ body: resolveRichTextDocument(context, bodyField),
660
+ additionalCopy: resolveRichTextDocument(context, additionalCopyField),
661
+ icon: lookupAsset(context, iconField),
662
+ backgroundVisual,
663
+ visual,
664
+ links: linksField?.map((link) => resolveLink(context, link)),
665
+ contents: contentsField?.map((content) => resolveCollectionContent(context, content))
666
+ };
667
+ }
668
+
669
+ // src/converters/component.ts
670
+ function baseComponentConverter(context, entry) {
671
+ const { sys, fields } = entry;
672
+ const {
673
+ // Fields requiring transformation
674
+ backgroundVisual: bgVisual,
675
+ mobileBackgroundVisual: mobileBgVisual,
676
+ visual: visualField,
677
+ mobileVisual: mobileVisualField,
678
+ visualCustomSize,
679
+ icon: iconField,
680
+ links: linksField,
681
+ body: bodyField,
682
+ additionalCopy: additionalCopyField,
683
+ // Field name change
684
+ componentType,
685
+ // CMS-only fields (must exclude)
686
+ textSize: _textSize,
687
+ showHeading: _showHeading,
688
+ otherMedia: _otherMedia,
689
+ otherVisuals: _otherVisuals,
690
+ // Already handled elsewhere
691
+ cmsLabel,
692
+ ...simpleFields
693
+ // anchor, backgroundColour, textColour, preHeading, heading, postHeading, backgroundOverlayOpacity
694
+ } = fields;
695
+ const backgroundVisual = createResponsiveVisual(
696
+ lookupAsset(context, bgVisual),
697
+ lookupAsset(context, mobileBgVisual)
698
+ );
699
+ const visual = createResponsiveVisual(
700
+ lookupMediaEntry(context, visualField),
701
+ lookupMediaEntry(context, mobileVisualField),
702
+ visualCustomSize
703
+ );
704
+ return {
705
+ type: "Component",
706
+ id: sys.id,
707
+ name: cmsLabel,
708
+ cmsLabel,
709
+ componentType,
710
+ ...DEFAULT_POSITION_FIELDS,
711
+ ...simpleFields,
712
+ body: resolveRichTextDocument(context, bodyField),
713
+ additionalCopy: resolveRichTextDocument(context, additionalCopyField),
714
+ icon: lookupAsset(context, iconField),
715
+ backgroundVisual,
716
+ visual,
717
+ links: linksField?.map((link) => resolveLink(context, link))
718
+ };
719
+ }
720
+
721
+ // src/converters/link.ts
722
+ function baseLinkConverter(context, entry) {
723
+ const { sys, fields } = entry;
724
+ if (sys.contentType.sys.id !== "link") {
725
+ throw new Error(`Invalid content type: expected "link", got "${sys.contentType.sys.id}"`);
726
+ }
727
+ const id = sys.id;
728
+ const name = fields.name;
729
+ const useName = fields.useName;
730
+ const text = useName ? name : fields.linkText ?? makeContentfulTitle(fields.linkText, id, "Link text for ");
731
+ const icon = lookupAsset(context, fields.icon);
732
+ const backgroundColour = fields.backgroundColour ?? null;
733
+ const textColour = fields.textColour ?? null;
734
+ const variant = fields.variant;
735
+ const size = fields.size;
736
+ const baseProps = {
737
+ id,
738
+ useName,
739
+ name,
740
+ text,
741
+ icon,
742
+ backgroundColour,
743
+ textColour,
744
+ variant,
745
+ size
746
+ };
747
+ if (fields.internal) {
748
+ const internalTarget = resolveLink(context, fields.internal);
749
+ return {
750
+ ...baseProps,
751
+ type: "Internal link",
752
+ href: internalTarget.href,
753
+ slug: internalTarget.slug,
754
+ indexed: internalTarget.indexed,
755
+ hidden: internalTarget.hidden,
756
+ tags: internalTarget.tags
757
+ };
758
+ }
759
+ if (fields.external) {
760
+ return {
761
+ ...baseProps,
762
+ type: "External link",
763
+ href: fields.external
764
+ };
765
+ }
766
+ if (fields.downloadAsset) {
767
+ const asset = lookupAsset(context, fields.downloadAsset);
768
+ let href = null;
769
+ if (asset?.image?.type === "Picture") {
770
+ href = asset.image.src;
771
+ } else if (asset?.image?.type === "Svg image") {
772
+ href = asset.image.svgSrc;
773
+ } else if (asset?.video) {
774
+ if (asset.video.type === "Local video") {
775
+ href = asset.video.preview.videoUrl;
776
+ } else if (asset.video.type === "Full video") {
777
+ href = asset.video.full.videoUrl;
778
+ } else if (asset.video.type === "External video") {
779
+ href = asset.video.external;
780
+ }
781
+ }
782
+ return {
783
+ ...baseProps,
784
+ type: "Download link",
785
+ href,
786
+ visual: asset
787
+ };
788
+ }
789
+ return {
790
+ ...baseProps,
791
+ type: "Blank link",
792
+ href: null
793
+ };
794
+ }
795
+
796
+ // src/converters/template.ts
797
+ function resolveTemplate(context, link) {
798
+ const id = link.sys.id;
799
+ const possibleEntry = context.includes.get(id);
800
+ if (!possibleEntry || possibleEntry.type !== "template") {
801
+ return null;
802
+ }
803
+ const entry = possibleEntry.entry;
804
+ const { fields } = entry;
805
+ if (!fields) {
806
+ return null;
807
+ }
808
+ const preContent = fields.preContent?.map((content) => resolvePageContent(context, content)) ?? [];
809
+ const postContent = fields.postContent?.map((content) => resolvePageContent(context, content)) ?? [];
810
+ const menu = fields.menu ? resolveNavigation(context, fields.menu) : void 0;
811
+ const footer = fields.footer ? resolveNavigation(context, fields.footer) : void 0;
812
+ const stickyNav = fields.flags?.includes("Sticky nav") ?? false;
813
+ const { backgroundColour, textColour } = fields;
814
+ return {
815
+ preContent,
816
+ postContent,
817
+ menu,
818
+ footer,
819
+ backgroundColour,
820
+ textColour,
821
+ stickyNav
822
+ };
823
+ }
824
+ function resolveNavigation(context, link) {
825
+ const id = link.sys.id;
826
+ const possibleEntry = context.includes.get(id);
827
+ if (!possibleEntry || possibleEntry.type !== "navigation") {
828
+ return void 0;
829
+ }
830
+ const entry = possibleEntry.entry;
831
+ const { sys, fields } = entry;
832
+ if (!fields) {
833
+ return void 0;
834
+ }
835
+ const entries = fields.entries?.map((item) => resolveNavigationItem(context, item)) ?? [];
836
+ const { name, textColour, backgroundColour } = fields;
837
+ return {
838
+ id: sys.id,
839
+ name,
840
+ entries,
841
+ textColour,
842
+ backgroundColour
843
+ };
844
+ }
845
+
846
+ // src/converters/page.ts
847
+ function basePageConverter(context, entry) {
848
+ const { sys, fields } = entry;
849
+ const {
850
+ slug,
851
+ title,
852
+ description,
853
+ featuredImage,
854
+ tags,
855
+ content,
856
+ template: templateLink,
857
+ topContent: topContentLinks,
858
+ bottomContent: bottomContentLinks,
859
+ ...simpleFields
860
+ } = fields;
861
+ const template = templateLink ? resolveTemplate(context, templateLink) : null;
862
+ const topContent = topContentLinks?.map((c) => resolvePageContent(context, c)) ?? [];
863
+ const pageContent = content?.map((c) => resolvePageContent(context, c)) ?? [];
864
+ const bottomContent = bottomContentLinks?.map((c) => resolvePageContent(context, c)) ?? [];
865
+ const preContent = template?.preContent ?? [];
866
+ const postContent = template?.postContent ?? [];
867
+ const contents = addPositionMetadata([
868
+ ...topContent,
869
+ ...preContent,
870
+ ...pageContent,
871
+ ...postContent,
872
+ ...bottomContent
873
+ ]);
874
+ return {
875
+ type: "Page",
876
+ id: sys.id,
877
+ isHomePage: slug === "index",
878
+ slug,
879
+ title: makeContentfulTitle(title, sys.id),
880
+ description: makeContentfulDescription(description, sys.id),
881
+ featuredImage: lookupAsset(context, featuredImage),
882
+ tags: tags?.map((tag) => resolveLink(context, tag)),
883
+ contents,
884
+ ...simpleFields,
885
+ menu: template?.menu,
886
+ footer: template?.footer
887
+ };
888
+ }
889
+ function calculatePageHref(slug) {
890
+ if (slug === "index") {
891
+ return "/";
892
+ }
893
+ return `/${slug}/`;
894
+ }
895
+ function basePageLinkConverter(context, entry) {
896
+ const { sys, fields } = entry;
897
+ if (sys.contentType.sys.id !== "page") {
898
+ throw new Error(`Invalid content type: expected "page", got "${sys.contentType.sys.id}"`);
899
+ }
900
+ return createInternalLink(
901
+ sys.id,
902
+ {
903
+ cmsLabel: fields.cmsLabel,
904
+ title: fields.title,
905
+ featuredImage: fields.featuredImage,
906
+ backgroundColour: fields.backgroundColour,
907
+ textColour: fields.textColour,
908
+ indexed: fields.indexed,
909
+ hidden: fields.hidden,
910
+ slug: fields.slug
911
+ },
912
+ context,
913
+ calculatePageHref(fields.slug),
914
+ { tags: fields.tags?.map((tag) => resolveLink(context, tag)) }
915
+ );
916
+ }
917
+ function calculatePageVariantHref(slug) {
918
+ return `/${slug}/`;
919
+ }
920
+ function basePageVariantLinkConverter(context, entry) {
921
+ const { sys, fields } = entry;
922
+ if (sys.contentType.sys.id !== "pageVariant") {
923
+ throw new Error(
924
+ `Invalid content type: expected "pageVariant", got "${sys.contentType.sys.id}"`
925
+ );
926
+ }
927
+ return createInternalLink(
928
+ sys.id,
929
+ {
930
+ cmsLabel: fields.cmsLabel,
931
+ title: fields.title,
932
+ featuredImage: fields.featuredImage,
933
+ backgroundColour: fields.backgroundColour,
934
+ textColour: fields.textColour,
935
+ indexed: fields.indexed,
936
+ hidden: fields.hidden,
937
+ slug: fields.slug
938
+ },
939
+ context,
940
+ calculatePageVariantHref(fields.slug),
941
+ { tags: fields.tags?.map((tag) => resolveLink(context, tag)) }
942
+ );
943
+ }
944
+
945
+ // src/converters/person.ts
946
+ function calculatePersonHref(slug) {
947
+ return `/people/${slug}/`;
948
+ }
949
+ function basePersonLinkConverter(context, entry) {
950
+ const { sys, fields } = entry;
951
+ if (sys.contentType.sys.id !== "person") {
952
+ throw new Error(`Invalid content type: expected "person", got "${sys.contentType.sys.id}"`);
953
+ }
954
+ return createInternalLink(
955
+ sys.id,
956
+ {
957
+ cmsLabel: fields.name,
958
+ // Person has no cmsLabel, use name field
959
+ title: fields.name,
960
+ featuredImage: fields.media,
961
+ // Person uses 'media' not 'featuredImage'
962
+ backgroundColour: fields.backgroundColour,
963
+ textColour: fields.textColour,
964
+ indexed: fields.indexed,
965
+ hidden: fields.hidden,
966
+ slug: fields.slug
967
+ },
968
+ context,
969
+ calculatePersonHref(fields.slug)
970
+ );
971
+ }
972
+
973
+ // src/converters/article.ts
974
+ function baseArticleConverter(context, entry) {
975
+ const { sys, fields } = entry;
976
+ const {
977
+ slug,
978
+ title,
979
+ description,
980
+ featuredImage,
981
+ tags,
982
+ content,
983
+ template: templateLink,
984
+ topContent: topContentLinks,
985
+ bottomContent: bottomContentLinks,
986
+ articleType,
987
+ ...simpleFields
988
+ } = fields;
989
+ const articleTypeLink = resolveLink(context, articleType);
990
+ const template = templateLink ? resolveTemplate(context, templateLink) : null;
991
+ const topContent = topContentLinks?.map((c) => resolvePageContent(context, c)) ?? [];
992
+ const articleContent = content?.map((c) => resolvePageContent(context, c)) ?? [];
993
+ const bottomContent = bottomContentLinks?.map((c) => resolvePageContent(context, c)) ?? [];
994
+ const preContent = template?.preContent ?? [];
995
+ const postContent = template?.postContent ?? [];
996
+ const contents = addPositionMetadata([
997
+ ...topContent,
998
+ ...preContent,
999
+ ...articleContent,
1000
+ ...postContent,
1001
+ ...bottomContent
1002
+ ]);
1003
+ return {
1004
+ type: "Article",
1005
+ id: sys.id,
1006
+ slug,
1007
+ title: makeContentfulTitle(title, sys.id),
1008
+ description: makeContentfulDescription(description, sys.id),
1009
+ featuredImage: lookupAsset(context, featuredImage),
1010
+ articleType: articleTypeLink,
1011
+ tags: tags?.map((tag) => resolveLink(context, tag)),
1012
+ contents,
1013
+ ...simpleFields,
1014
+ // Note: summary field exists in Contentful but is not part of IArticle interface
1015
+ // Keeping it in simpleFields for potential future use
1016
+ menu: template?.menu,
1017
+ footer: template?.footer
1018
+ };
1019
+ }
1020
+ function calculateArticleTypeHref(slug) {
1021
+ return `/${slug}/`;
1022
+ }
1023
+ function calculateArticleHref(articleTypeSlug, slug) {
1024
+ return `${calculateArticleTypeHref(articleTypeSlug)}${slug}/`;
1025
+ }
1026
+ function baseArticleLinkConverter(context, entry) {
1027
+ const { sys, fields } = entry;
1028
+ if (sys.contentType.sys.id !== "article") {
1029
+ throw new Error(`Invalid content type: expected "article", got "${sys.contentType.sys.id}"`);
1030
+ }
1031
+ const articleTypeLink = resolveLink(context, fields.articleType);
1032
+ return createInternalLink(
1033
+ sys.id,
1034
+ {
1035
+ cmsLabel: fields.cmsLabel,
1036
+ title: fields.title,
1037
+ featuredImage: fields.featuredImage,
1038
+ backgroundColour: fields.backgroundColour,
1039
+ textColour: fields.textColour,
1040
+ indexed: fields.indexed,
1041
+ hidden: fields.hidden,
1042
+ slug: fields.slug
1043
+ },
1044
+ context,
1045
+ calculateArticleHref(articleTypeLink.slug, fields.slug),
1046
+ { tags: fields.tags?.map((tag) => resolveLink(context, tag)) }
1047
+ );
1048
+ }
1049
+ function baseArticleTypeLinkConverter(context, entry) {
1050
+ const { sys, fields } = entry;
1051
+ if (sys.contentType.sys.id !== "articleType") {
1052
+ throw new Error(
1053
+ `Invalid content type: expected "articleType", got "${sys.contentType.sys.id}"`
1054
+ );
1055
+ }
1056
+ return createInternalLink(
1057
+ sys.id,
1058
+ {
1059
+ cmsLabel: fields.name,
1060
+ title: fields.name,
1061
+ featuredImage: fields.featuredImage,
1062
+ backgroundColour: fields.backgroundColour,
1063
+ textColour: fields.textColour,
1064
+ indexed: fields.indexed,
1065
+ hidden: fields.hidden,
1066
+ slug: fields.slug
1067
+ },
1068
+ context,
1069
+ calculateArticleTypeHref(fields.slug)
1070
+ );
1071
+ }
1072
+
1073
+ // src/converters/navigationItem.ts
1074
+ function createLink(context, entry) {
1075
+ const {
1076
+ sys: { id },
1077
+ fields
1078
+ } = entry;
1079
+ const { title, link, internal, icon, useTitle, ...otherFields } = fields;
1080
+ if (link) {
1081
+ return {
1082
+ type: "External link",
1083
+ id,
1084
+ href: link,
1085
+ icon: lookupAsset(context, icon),
1086
+ name: makeContentfulTitle(title, id),
1087
+ ...useTitle && { text: title },
1088
+ ...otherFields
1089
+ };
1090
+ }
1091
+ if (internal) {
1092
+ const resolved = resolveLink(context, internal);
1093
+ return resolved;
1094
+ }
1095
+ return void 0;
1096
+ }
1097
+ function baseNavigationItemConverter(context, entry) {
1098
+ const { sys, fields } = entry;
1099
+ const { longText, navigationItems } = fields;
1100
+ const link = createLink(context, entry);
1101
+ const resolvedNavigationItems = navigationItems?.map((item) => resolveNavigationItem(context, item)).filter((item) => item !== void 0);
1102
+ return {
1103
+ id: sys.id,
1104
+ longText,
1105
+ link,
1106
+ entries: resolvedNavigationItems
1107
+ };
1108
+ }
1109
+
1110
+ // src/converters/tag.ts
1111
+ function calculateTagHref(slug) {
1112
+ return `/tag/${slug}/`;
1113
+ }
1114
+ function baseTagLinkConverter(context, entry) {
1115
+ const { sys, fields } = entry;
1116
+ if (sys.contentType.sys.id !== "tag") {
1117
+ throw new Error(`Invalid content type: expected "tag", got "${sys.contentType.sys.id}"`);
1118
+ }
1119
+ return createInternalLink(
1120
+ sys.id,
1121
+ {
1122
+ cmsLabel: fields.cmsLabel,
1123
+ title: fields.name,
1124
+ featuredImage: fields.featuredImage,
1125
+ backgroundColour: fields.backgroundColour,
1126
+ textColour: fields.textColour,
1127
+ indexed: fields.indexed,
1128
+ hidden: fields.hidden,
1129
+ slug: fields.slug
1130
+ },
1131
+ context,
1132
+ calculateTagHref(fields.slug)
1133
+ );
1134
+ }
1135
+
1136
+ // src/utils/errors.ts
1137
+ var ContentfulError = class _ContentfulError extends Error {
1138
+ constructor(message, statusCode, details) {
1139
+ super(message);
1140
+ this.statusCode = statusCode;
1141
+ this.details = details;
1142
+ this.name = "ContentfulError";
1143
+ Object.setPrototypeOf(this, _ContentfulError.prototype);
1144
+ }
1145
+ };
1146
+ var RateLimitError = class _RateLimitError extends ContentfulError {
1147
+ constructor(message, retryAfter, details) {
1148
+ super(message, 429, details);
1149
+ this.retryAfter = retryAfter;
1150
+ this.name = "RateLimitError";
1151
+ Object.setPrototypeOf(this, _RateLimitError.prototype);
1152
+ }
1153
+ };
1154
+ var EntryNotFoundError = class _EntryNotFoundError extends ContentfulError {
1155
+ constructor(entryId, contentType) {
1156
+ super(`Entry not found: ${entryId}${contentType ? ` (${contentType})` : ""}`, 404);
1157
+ this.entryId = entryId;
1158
+ this.contentType = contentType;
1159
+ this.name = "EntryNotFoundError";
1160
+ Object.setPrototypeOf(this, _EntryNotFoundError.prototype);
1161
+ }
1162
+ };
1163
+ var AuthenticationError = class _AuthenticationError extends ContentfulError {
1164
+ constructor(message = "Authentication failed") {
1165
+ super(message, 401);
1166
+ this.name = "AuthenticationError";
1167
+ Object.setPrototypeOf(this, _AuthenticationError.prototype);
1168
+ }
1169
+ };
1170
+ var ValidationError = class _ValidationError extends ContentfulError {
1171
+ constructor(message, validationErrors) {
1172
+ super(message, 400, validationErrors);
1173
+ this.validationErrors = validationErrors;
1174
+ this.name = "ValidationError";
1175
+ Object.setPrototypeOf(this, _ValidationError.prototype);
1176
+ }
1177
+ };
1178
+ function isContentfulError(error) {
1179
+ return error instanceof ContentfulError;
1180
+ }
1181
+ function isRateLimitError(error) {
1182
+ return error instanceof RateLimitError;
1183
+ }
1184
+ function isRetryableError(error) {
1185
+ if (isRateLimitError(error)) {
1186
+ return true;
1187
+ }
1188
+ if (isContentfulError(error)) {
1189
+ return error.statusCode !== void 0 && (error.statusCode >= 500 || error.statusCode === 429);
1190
+ }
1191
+ return false;
1192
+ }
1193
+ function getRetryAfter(error) {
1194
+ if (isRateLimitError(error)) {
1195
+ return error.retryAfter;
1196
+ }
1197
+ return void 0;
1198
+ }
1199
+
1200
+ // src/utils/retry.ts
1201
+ var DEFAULT_RETRY_CONFIG = {
1202
+ maxRetries: 3,
1203
+ initialDelay: 1e3,
1204
+ // 1 second
1205
+ maxDelay: 3e4,
1206
+ // 30 seconds
1207
+ backoffMultiplier: 2
1208
+ };
1209
+ function sleep(ms) {
1210
+ return new Promise((resolve) => setTimeout(resolve, ms));
1211
+ }
1212
+ function calculateBackoffDelay(attempt, config, retryAfter) {
1213
+ if (retryAfter !== void 0) {
1214
+ return Math.min(retryAfter * 1e3, config.maxDelay);
1215
+ }
1216
+ const exponentialDelay = config.initialDelay * config.backoffMultiplier ** attempt;
1217
+ const jitter = Math.random() * exponentialDelay;
1218
+ return Math.min(exponentialDelay + jitter, config.maxDelay);
1219
+ }
1220
+ async function withRetry(fn, config) {
1221
+ const retryConfig = {
1222
+ ...DEFAULT_RETRY_CONFIG,
1223
+ ...config
1224
+ };
1225
+ let lastError;
1226
+ for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) {
1227
+ try {
1228
+ return await fn();
1229
+ } catch (error) {
1230
+ lastError = error;
1231
+ if (attempt === retryConfig.maxRetries) {
1232
+ break;
1233
+ }
1234
+ if (!isRetryableError(error)) {
1235
+ throw error;
1236
+ }
1237
+ const retryAfter = getRetryAfter(error);
1238
+ const delay = calculateBackoffDelay(attempt, retryConfig, retryAfter);
1239
+ if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
1240
+ console.warn(
1241
+ `Retry attempt ${attempt + 1}/${retryConfig.maxRetries} after ${delay}ms`,
1242
+ error
1243
+ );
1244
+ }
1245
+ await sleep(delay);
1246
+ }
1247
+ }
1248
+ throw lastError;
1249
+ }
1250
+ var RateLimiter = class {
1251
+ constructor(maxTokens, refillRate) {
1252
+ this.maxTokens = maxTokens;
1253
+ this.refillRate = refillRate;
1254
+ this.tokens = maxTokens;
1255
+ this.lastRefill = Date.now();
1256
+ }
1257
+ tokens;
1258
+ lastRefill;
1259
+ /**
1260
+ * Refills tokens based on time elapsed
1261
+ */
1262
+ refill() {
1263
+ const now = Date.now();
1264
+ const timePassed = (now - this.lastRefill) / 1e3;
1265
+ const tokensToAdd = timePassed * this.refillRate;
1266
+ this.tokens = Math.min(this.maxTokens, this.tokens + tokensToAdd);
1267
+ this.lastRefill = now;
1268
+ }
1269
+ /**
1270
+ * Attempts to consume a token
1271
+ * @returns true if token was consumed, false if rate limited
1272
+ */
1273
+ tryConsume() {
1274
+ this.refill();
1275
+ if (this.tokens >= 1) {
1276
+ this.tokens -= 1;
1277
+ return true;
1278
+ }
1279
+ return false;
1280
+ }
1281
+ /**
1282
+ * Waits until a token is available and consumes it
1283
+ */
1284
+ async consume() {
1285
+ while (!this.tryConsume()) {
1286
+ const waitTime = 1 / this.refillRate * 1e3;
1287
+ await sleep(waitTime);
1288
+ }
1289
+ }
1290
+ /**
1291
+ * Gets current number of available tokens
1292
+ */
1293
+ getAvailableTokens() {
1294
+ this.refill();
1295
+ return Math.floor(this.tokens);
1296
+ }
1297
+ };
1298
+
1299
+ // src/utils/index.ts
1300
+ init_timing();
1301
+
1302
+ // src/api.ts
1303
+ function convertAllAssets(response) {
1304
+ const visuals = /* @__PURE__ */ new Map();
1305
+ const assets = response.includes?.Asset;
1306
+ if (assets && assets.length > 0) {
1307
+ for (const asset of assets) {
1308
+ const visual = convertAssetToVisual(asset);
1309
+ if (visual) {
1310
+ visuals.set(visual.id, visual);
1311
+ }
1312
+ }
1313
+ }
1314
+ return visuals;
1315
+ }
1316
+ function convertAllIncludes(response) {
1317
+ const includes = /* @__PURE__ */ new Map();
1318
+ const entries = [...response.items, ...response.includes?.Entry || []];
1319
+ if (entries && entries.length > 0) {
1320
+ for (const entry of entries) {
1321
+ if (entry?.sys && entry.fields) {
1322
+ includes.set(entry.sys.id, {
1323
+ id: entry.sys.id,
1324
+ type: entry.sys.contentType.sys.id,
1325
+ entry
1326
+ });
1327
+ }
1328
+ }
1329
+ }
1330
+ return includes;
1331
+ }
1332
+ async function contentfulPageRest(context, config, slug, options) {
1333
+ const totalTimer = new Timer(`contentfulPageRest(${slug})`);
1334
+ const client = getContentfulClient(config, options?.preview);
1335
+ const fetchFn = async () => {
1336
+ const apiFetchTimer = new Timer("API fetch");
1337
+ const response = await client.getEntries({
1338
+ content_type: "page",
1339
+ "fields.slug": slug,
1340
+ include: 10,
1341
+ locale: options?.locale,
1342
+ limit: 1
1343
+ });
1344
+ const apiFetchTiming = apiFetchTimer.end();
1345
+ const pageEntry = response.items[0];
1346
+ if (!pageEntry || !pageEntry.fields) {
1347
+ totalTimer.endAndLog();
1348
+ return null;
1349
+ }
1350
+ const assetTimer = new Timer("Asset conversion");
1351
+ const assets = convertAllAssets(response);
1352
+ const assetTiming = assetTimer.end();
1353
+ assetTiming.metadata = { count: assets.size };
1354
+ const includesTimer = new Timer("Includes indexing");
1355
+ const includes = convertAllIncludes(response);
1356
+ const includesTiming = includesTimer.end();
1357
+ includesTiming.metadata = { count: includes.size };
1358
+ const fullContext = {
1359
+ ...context,
1360
+ includes,
1361
+ assets
1362
+ };
1363
+ const conversionTimer = new Timer("Page conversion");
1364
+ const converted = fullContext.pageResolver(fullContext, pageEntry);
1365
+ const conversionTiming = conversionTimer.end();
1366
+ const totalTiming = totalTimer.end();
1367
+ totalTiming.children = [apiFetchTiming, assetTiming, includesTiming, conversionTiming];
1368
+ const { logTimingResult: logTimingResult2 } = await Promise.resolve().then(() => (init_timing(), timing_exports));
1369
+ logTimingResult2(totalTiming);
1370
+ return converted;
1371
+ };
1372
+ if (options?.retry) {
1373
+ return await withRetry(fetchFn, options.retry);
1374
+ }
1375
+ return await fetchFn();
1376
+ }
1377
+ function createBaseConverterContext() {
1378
+ const linkResolver = /* @__PURE__ */ new Map();
1379
+ linkResolver.set("page", basePageLinkConverter);
1380
+ linkResolver.set("article", baseArticleLinkConverter);
1381
+ linkResolver.set(
1382
+ "articleType",
1383
+ baseArticleTypeLinkConverter
1384
+ );
1385
+ linkResolver.set("tag", baseTagLinkConverter);
1386
+ linkResolver.set("person", basePersonLinkConverter);
1387
+ linkResolver.set(
1388
+ "pageVariant",
1389
+ basePageVariantLinkConverter
1390
+ );
1391
+ linkResolver.set("link", baseLinkConverter);
1392
+ return {
1393
+ pageResolver: basePageConverter,
1394
+ navigationItemResolver: baseNavigationItemConverter,
1395
+ articleResolver: baseArticleConverter,
1396
+ componentResolver: baseComponentConverter,
1397
+ collectionResolver: baseCollectionConverter,
1398
+ linkResolver
1399
+ };
1400
+ }
1401
+ async function contentfulArticleRest(context, config, slug, articleTypeSlug, options) {
1402
+ const client = getContentfulClient(config, options?.preview);
1403
+ const fetchFn = async () => {
1404
+ const response = await client.getEntries({
1405
+ content_type: "article",
1406
+ "fields.slug": slug,
1407
+ "fields.articleType.sys.contentType.sys.id": "articleType",
1408
+ "fields.articleType.fields.slug": articleTypeSlug,
1409
+ include: 10,
1410
+ locale: options?.locale,
1411
+ limit: 1
1412
+ });
1413
+ const articleEntry = response.items[0];
1414
+ if (!articleEntry || !articleEntry.fields) {
1415
+ return null;
1416
+ }
1417
+ const assets = convertAllAssets(response);
1418
+ const includes = convertAllIncludes(response);
1419
+ const fullContext = {
1420
+ ...context,
1421
+ includes,
1422
+ assets
1423
+ };
1424
+ const converted = fullContext.articleResolver(fullContext, articleEntry);
1425
+ return converted;
1426
+ };
1427
+ if (options?.retry) {
1428
+ return await withRetry(fetchFn, options.retry);
1429
+ }
1430
+ return await fetchFn();
1431
+ }
1432
+
1433
+ export { AuthenticationError, ContentfulError, EntryNotFoundError, RateLimitError, RateLimiter, Timer, ValidationError, basePageConverter, calculateBackoffDelay, contentfulArticleRest, contentfulPageRest, createBaseConverterContext, createContentfulClient, createContentfulPreviewClient, createTimer, getContentfulClient, getRetryAfter, isContentfulError, isRateLimitError, isRetryableError, logTimingResult, withRetry };
1434
+ //# sourceMappingURL=index.js.map
1435
+ //# sourceMappingURL=index.js.map