@forjacms/client 1.2.6

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.
@@ -0,0 +1,1673 @@
1
+ //#region src/types.d.ts
2
+ interface ForjaClientConfig {
3
+ baseUrl: string;
4
+ apiKey: string;
5
+ siteId: string;
6
+ fetch?: typeof globalThis.fetch;
7
+ }
8
+ interface PaginationMeta {
9
+ page: number;
10
+ page_size: number;
11
+ total_pages: number;
12
+ total_items: number;
13
+ }
14
+ interface Paginated<T> {
15
+ data: T[];
16
+ meta: PaginationMeta;
17
+ }
18
+ interface PaginationParams {
19
+ page?: number;
20
+ pageSize?: number;
21
+ }
22
+ interface LocaleFilterParams extends PaginationParams {
23
+ /** Filter to content with a localization in this locale (UUID). */
24
+ localeId?: string;
25
+ }
26
+ interface SearchablePaginationParams extends PaginationParams {
27
+ search?: string;
28
+ sortBy?: string;
29
+ sortDir?: 'asc' | 'desc';
30
+ }
31
+ type ContentStatus = 'Draft' | 'InReview' | 'Scheduled' | 'Published' | 'Archived';
32
+ type TranslationStatus = 'Pending' | 'InProgress' | 'Review' | 'Approved' | 'Outdated';
33
+ type PageType = 'Static' | 'Landing' | 'Contact' | 'BlogIndex' | 'Custom';
34
+ type SectionType = 'Hero' | 'Features' | 'Cta' | 'Gallery' | 'Testimonials' | 'Pricing' | 'Faq' | 'Contact' | 'Custom' | 'Stats' | 'Team' | 'Timeline' | 'LogoCloud' | 'Newsletter' | 'Video' | 'Divider' | 'Text' | 'Portfolio' | 'TagCloud' | 'Projects' | 'Blog' | 'Legal';
35
+ type CvEntryType = 'Work' | 'Education' | 'Volunteer' | 'Certification' | 'Project';
36
+ type SkillCategory = 'Programming' | 'Framework' | 'Database' | 'Devops' | 'Language' | 'SoftSkill' | 'Tool' | 'Other';
37
+ type LegalDocType = 'CookieConsent' | 'PrivacyPolicy' | 'TermsOfService' | 'Imprint' | 'Disclaimer';
38
+ interface LocalizationResponse {
39
+ id: string;
40
+ content_id: string;
41
+ locale_id: string;
42
+ title: string;
43
+ subtitle: string | null;
44
+ excerpt: string | null;
45
+ body: string | null;
46
+ meta_title: string | null;
47
+ meta_description: string | null;
48
+ translation_status: TranslationStatus;
49
+ created_at: string;
50
+ updated_at: string;
51
+ }
52
+ interface BlogListItem {
53
+ id: string;
54
+ content_id: string;
55
+ slug: string | null;
56
+ author: string;
57
+ published_date: string;
58
+ reading_time_minutes: number | null;
59
+ cover_image_id: string | null;
60
+ header_image_id: string | null;
61
+ is_featured: boolean;
62
+ is_sample: boolean;
63
+ status: ContentStatus;
64
+ publish_start: string | null;
65
+ publish_end: string | null;
66
+ created_at: string;
67
+ updated_at: string;
68
+ }
69
+ interface BlogResponse {
70
+ id: string;
71
+ content_id: string;
72
+ slug: string | null;
73
+ author: string;
74
+ published_date: string;
75
+ reading_time_minutes: number | null;
76
+ cover_image_id: string | null;
77
+ header_image_id: string | null;
78
+ is_featured: boolean;
79
+ is_sample: boolean;
80
+ allow_comments: boolean;
81
+ status: ContentStatus;
82
+ published_at: string | null;
83
+ publish_start: string | null;
84
+ publish_end: string | null;
85
+ created_at: string;
86
+ updated_at: string;
87
+ }
88
+ interface BlogDocumentResponse {
89
+ id: string;
90
+ blog_id: string;
91
+ document_id: string;
92
+ display_order: number;
93
+ url: string | null;
94
+ document_type: string;
95
+ file_name: string | null;
96
+ has_file: boolean;
97
+ localizations: DocumentLocalizationResponse[];
98
+ created_at: string;
99
+ }
100
+ interface DocumentLocalizationResponse {
101
+ id: string;
102
+ document_id: string;
103
+ locale_id: string;
104
+ name: string;
105
+ description: string | null;
106
+ created_at: string;
107
+ updated_at: string;
108
+ }
109
+ interface BlogDetailResponse extends BlogResponse {
110
+ localizations: LocalizationResponse[];
111
+ categories: CategoryResponse[];
112
+ documents: BlogDocumentResponse[];
113
+ og_image_url: string | null;
114
+ }
115
+ interface PageListItem {
116
+ id: string;
117
+ route: string;
118
+ page_type: PageType;
119
+ slug: string | null;
120
+ is_in_navigation: boolean;
121
+ status: ContentStatus;
122
+ publish_start: string | null;
123
+ publish_end: string | null;
124
+ created_at: string;
125
+ }
126
+ interface PageResponse {
127
+ id: string;
128
+ content_id: string;
129
+ route: string;
130
+ page_type: PageType;
131
+ template: string | null;
132
+ is_in_navigation: boolean;
133
+ navigation_order: number | null;
134
+ parent_page_id: string | null;
135
+ slug: string | null;
136
+ status: ContentStatus;
137
+ published_at: string | null;
138
+ publish_start: string | null;
139
+ publish_end: string | null;
140
+ created_at: string;
141
+ updated_at: string;
142
+ }
143
+ interface PageDetailResponse extends PageResponse {
144
+ localizations: LocalizationResponse[];
145
+ og_image_url: string | null;
146
+ }
147
+ interface PageSectionResponse {
148
+ id: string;
149
+ page_id: string;
150
+ section_type: SectionType;
151
+ display_order: number;
152
+ cover_image_id: string | null;
153
+ call_to_action_route: string | null;
154
+ settings: Record<string, unknown> | null;
155
+ }
156
+ interface SectionLocalizationResponse {
157
+ id: string;
158
+ page_section_id: string;
159
+ locale_id: string;
160
+ title: string | null;
161
+ text: string | null;
162
+ button_text: string | null;
163
+ }
164
+ interface NavigationMenuResponse {
165
+ id: string;
166
+ site_id: string;
167
+ slug: string;
168
+ description: string | null;
169
+ max_depth: number;
170
+ is_active: boolean;
171
+ item_count: number;
172
+ created_at: string;
173
+ }
174
+ interface NavigationItemResponse {
175
+ id: string;
176
+ menu_id: string;
177
+ parent_id: string | null;
178
+ page_id: string | null;
179
+ external_url: string | null;
180
+ icon: string | null;
181
+ display_order: number;
182
+ open_in_new_tab: boolean;
183
+ title: string | null;
184
+ }
185
+ interface NavigationTree {
186
+ id: string;
187
+ parent_id: string | null;
188
+ page_id: string | null;
189
+ external_url: string | null;
190
+ icon: string | null;
191
+ display_order: number;
192
+ open_in_new_tab: boolean;
193
+ title: string | null;
194
+ page_slug: string | null;
195
+ children: NavigationTree[];
196
+ }
197
+ interface NavigationItemLocalizationResponse {
198
+ id: string;
199
+ navigation_item_id: string;
200
+ locale_id: string;
201
+ title: string;
202
+ }
203
+ interface TagResponse {
204
+ id: string;
205
+ slug: string;
206
+ is_global: boolean;
207
+ created_at: string;
208
+ }
209
+ interface CategoryResponse {
210
+ id: string;
211
+ parent_id: string | null;
212
+ slug: string;
213
+ is_global: boolean;
214
+ created_at: string;
215
+ }
216
+ interface CategoryTree {
217
+ id: string;
218
+ slug: string;
219
+ is_global: boolean;
220
+ children: CategoryTree[];
221
+ }
222
+ interface CategoryWithCountResponse {
223
+ id: string;
224
+ parent_id: string | null;
225
+ slug: string;
226
+ is_global: boolean;
227
+ created_at: string;
228
+ blog_count: number;
229
+ }
230
+ interface TopContentItem {
231
+ path: string;
232
+ total_views: number;
233
+ unique_visitors: number;
234
+ }
235
+ interface TrendDataPoint {
236
+ date: string;
237
+ total_views: number;
238
+ unique_visitors: number;
239
+ }
240
+ interface AnalyticsReportResponse {
241
+ total_views: number;
242
+ total_unique_visitors: number;
243
+ top_content: TopContentItem[];
244
+ trend: TrendDataPoint[];
245
+ }
246
+ interface ReferrerItem {
247
+ domain: string;
248
+ views: number;
249
+ }
250
+ interface AnalyticsPageDetailResponse {
251
+ path: string;
252
+ total_views: number;
253
+ total_unique_visitors: number;
254
+ trend: TrendDataPoint[];
255
+ referrers: ReferrerItem[];
256
+ }
257
+ interface TrackPageviewRequest {
258
+ path: string;
259
+ referrer?: string;
260
+ }
261
+ interface TrackPageviewResponse {
262
+ ok: boolean;
263
+ }
264
+ interface AnalyticsReportParams {
265
+ days?: number;
266
+ topN?: number;
267
+ startDate?: string;
268
+ endDate?: string;
269
+ }
270
+ interface AnalyticsPageParams {
271
+ path: string;
272
+ days?: number;
273
+ startDate?: string;
274
+ endDate?: string;
275
+ }
276
+ interface SkillResponse {
277
+ id: string;
278
+ name: string;
279
+ slug: string;
280
+ category: SkillCategory | null;
281
+ icon: string | null;
282
+ proficiency_level: number | null;
283
+ }
284
+ interface CvEntryResponse {
285
+ id: string;
286
+ company: string;
287
+ company_url: string | null;
288
+ company_logo_id: string | null;
289
+ location: string;
290
+ start_date: string;
291
+ end_date: string | null;
292
+ is_current: boolean;
293
+ entry_type: CvEntryType;
294
+ display_order: number;
295
+ created_at: string;
296
+ updated_at: string;
297
+ }
298
+ interface CvEntryParams extends SearchablePaginationParams {
299
+ entryType?: CvEntryType;
300
+ }
301
+ interface LegalDocumentResponse {
302
+ id: string;
303
+ cookie_name: string;
304
+ document_type: LegalDocType;
305
+ created_at: string;
306
+ updated_at: string;
307
+ }
308
+ interface LegalDocLocalizationResponse {
309
+ id: string;
310
+ locale_id: string;
311
+ title: string;
312
+ intro: string | null;
313
+ }
314
+ interface LegalDocumentDetailResponse {
315
+ id: string;
316
+ cookie_name: string;
317
+ document_type: LegalDocType;
318
+ localizations: LegalDocLocalizationResponse[];
319
+ created_at: string;
320
+ updated_at: string;
321
+ }
322
+ interface LegalGroupResponse {
323
+ id: string;
324
+ cookie_name: string;
325
+ display_order: number;
326
+ is_required: boolean;
327
+ default_enabled: boolean;
328
+ }
329
+ interface LegalItemResponse {
330
+ id: string;
331
+ cookie_name: string;
332
+ display_order: number;
333
+ is_required: boolean;
334
+ }
335
+ interface LegalGroupWithItems {
336
+ id: string;
337
+ cookie_name: string;
338
+ display_order: number;
339
+ is_required: boolean;
340
+ default_enabled: boolean;
341
+ items: LegalItemResponse[];
342
+ }
343
+ interface LegalDocumentWithGroups {
344
+ id: string;
345
+ cookie_name: string;
346
+ document_type: LegalDocType;
347
+ groups: LegalGroupWithItems[];
348
+ }
349
+ interface SiteResponse {
350
+ id: string;
351
+ name: string;
352
+ slug: string;
353
+ description: string | null;
354
+ logo_url: string | null;
355
+ favicon_url: string | null;
356
+ base_url: string | null;
357
+ theme: Record<string, unknown> | null;
358
+ default_locale_id: string | null;
359
+ timezone: string;
360
+ is_active: boolean;
361
+ created_at: string;
362
+ updated_at: string;
363
+ }
364
+ interface MediaVariantResponse {
365
+ id: string;
366
+ variant_name: string;
367
+ width: number;
368
+ height: number;
369
+ file_size: number;
370
+ public_url: string | null;
371
+ }
372
+ interface MediaResponse {
373
+ id: string;
374
+ filename: string;
375
+ original_filename: string;
376
+ mime_type: string;
377
+ file_size: number;
378
+ storage_provider: string;
379
+ public_url: string | null;
380
+ width: number | null;
381
+ height: number | null;
382
+ duration: number | null;
383
+ is_global: boolean;
384
+ created_at: string;
385
+ updated_at: string;
386
+ variants: MediaVariantResponse[];
387
+ }
388
+ interface SocialLinkResponse {
389
+ id: string;
390
+ title: string;
391
+ url: string;
392
+ icon: string;
393
+ alt_text: string | null;
394
+ display_order: number;
395
+ }
396
+ /** Link type for project resources (repository, demo, docs, etc.). */
397
+ type ProjectLinkType = 'repository' | 'demo' | 'documentation' | 'website' | 'other';
398
+ /** Project summary for list views. */
399
+ interface ProjectResponse {
400
+ id: string;
401
+ slug: string;
402
+ display_order: number;
403
+ is_featured: boolean;
404
+ start_date: string | null;
405
+ end_date: string | null;
406
+ is_ongoing: boolean;
407
+ status: ContentStatus;
408
+ published_at: string | null;
409
+ created_at: string;
410
+ updated_at: string;
411
+ }
412
+ /** Localized content for a project. */
413
+ interface ProjectLocalizationResponse {
414
+ id: string;
415
+ locale_id: string;
416
+ title: string;
417
+ short_description: string | null;
418
+ description: string | null;
419
+ }
420
+ /** External link attached to a project. */
421
+ interface ProjectLinkResponse {
422
+ id: string;
423
+ label: string;
424
+ url: string;
425
+ link_type: ProjectLinkType;
426
+ icon: string | null;
427
+ display_order: number;
428
+ }
429
+ /** Media attachment on a project (cover image, screenshots). */
430
+ interface ProjectMediaResponse {
431
+ media_id: string;
432
+ display_order: number;
433
+ is_cover: boolean;
434
+ }
435
+ /** Full project detail including localizations, links, and media. */
436
+ interface ProjectDetailResponse extends ProjectResponse {
437
+ localizations: ProjectLocalizationResponse[];
438
+ links: ProjectLinkResponse[];
439
+ media: ProjectMediaResponse[];
440
+ skill_ids: string[];
441
+ cv_entry_ids: string[];
442
+ }
443
+ /** Pagination and filter params for project listings. */
444
+ interface ProjectListParams extends PaginationParams {
445
+ /** Sort field (e.g. `"display_order"`, `"start_date"`). */
446
+ sortBy?: string;
447
+ /** Sort direction. */
448
+ sortDir?: 'asc' | 'desc';
449
+ /** Filter to featured projects only. */
450
+ isFeatured?: boolean;
451
+ }
452
+ /** A URL redirect rule. */
453
+ interface RedirectResponse {
454
+ id: string;
455
+ site_id: string;
456
+ source_path: string;
457
+ destination_path: string;
458
+ status_code: number;
459
+ is_active: boolean;
460
+ description: string | null;
461
+ created_at: string;
462
+ updated_at: string;
463
+ }
464
+ /** Result of a redirect path lookup. */
465
+ interface RedirectLookupResponse {
466
+ destination_path: string;
467
+ status_code: number;
468
+ }
469
+ /** A locale configured for a site. */
470
+ interface SiteLocaleResponse {
471
+ locale_id: string;
472
+ code: string;
473
+ name: string;
474
+ native_name: string | null;
475
+ direction: 'ltr' | 'rtl';
476
+ is_default: boolean;
477
+ is_active: boolean;
478
+ }
479
+ /** Media item summary for list views (lighter than full MediaResponse). */
480
+ interface MediaListItem {
481
+ id: string;
482
+ filename: string;
483
+ original_filename: string;
484
+ mime_type: string;
485
+ file_size: number;
486
+ public_url: string | null;
487
+ width: number | null;
488
+ height: number | null;
489
+ is_global: boolean;
490
+ folder_id: string | null;
491
+ created_at: string;
492
+ }
493
+ /** Filter params for media listings. */
494
+ interface MediaListParams extends SearchablePaginationParams {
495
+ /** Filter by MIME category (e.g. `"image"`, `"video"`, `"document"`). */
496
+ mimeCategory?: string;
497
+ /** Filter to a specific folder. */
498
+ folderId?: string;
499
+ }
500
+ /** Full legal document detail including content localizations. */
501
+ interface LegalDocumentFullDetailResponse {
502
+ id: string;
503
+ content_id: string;
504
+ cookie_name: string;
505
+ document_type: LegalDocType;
506
+ status: ContentStatus;
507
+ slug: string | null;
508
+ version: number;
509
+ parent_version_id: string | null;
510
+ publish_start: string | null;
511
+ publish_end: string | null;
512
+ localizations: LocalizationResponse[];
513
+ doc_localizations: LegalDocLocalizationResponse[];
514
+ created_at: string;
515
+ updated_at: string;
516
+ }
517
+ /** A version entry in a legal document's version history. */
518
+ interface LegalVersionResponse {
519
+ id: string;
520
+ version: number;
521
+ status: ContentStatus;
522
+ created_at: string;
523
+ }
524
+ //#endregion
525
+ //#region src/http.d.ts
526
+ /**
527
+ * Low-level HTTP client interface used by all resource classes.
528
+ *
529
+ * Handles authentication (X-API-Key header), JSON serialization, and
530
+ * error mapping. You don't interact with this directly — use
531
+ * {@link ForjaClient} instead.
532
+ */
533
+ interface HttpClient {
534
+ /** Send a GET request. Query params with `undefined` values are omitted. */
535
+ get<T>(path: string, params?: Record<string, string | undefined>): Promise<T>;
536
+ /** Send a GET request and return the response as plain text (for XML, HTML, etc.). */
537
+ getText(path: string, params?: Record<string, string | undefined>): Promise<string>;
538
+ /** Send a POST request with a JSON body. */
539
+ post<T>(path: string, body?: unknown): Promise<T>;
540
+ }
541
+ /**
542
+ * A paginated API response enriched with navigation helpers.
543
+ *
544
+ * Extends the raw {@link Paginated} response with methods to fetch
545
+ * subsequent pages or collect all items.
546
+ *
547
+ * @typeParam T - The type of items in the paginated collection.
548
+ *
549
+ * @example
550
+ * ```ts
551
+ * const page1 = await forja.blogs.listPublished({ pageSize: 10 });
552
+ *
553
+ * // Navigate to next page
554
+ * const page2 = await page1.fetchNext();
555
+ *
556
+ * // Or collect everything
557
+ * const allBlogs = await page1.fetchAll();
558
+ *
559
+ * // Or iterate page by page
560
+ * for await (const page of page1) {
561
+ * console.log(page.data);
562
+ * }
563
+ * ```
564
+ */
565
+ interface PaginatedResult<T> extends Paginated<T> {
566
+ /** Fetch the next page. Returns `null` if this is the last page. */
567
+ fetchNext(): Promise<PaginatedResult<T> | null>;
568
+ /** Fetch all remaining pages and return every item as a flat array. */
569
+ fetchAll(): Promise<T[]>;
570
+ /** Async iterator that yields each page (including the current one). */
571
+ [Symbol.asyncIterator](): AsyncIterableIterator<Paginated<T>>;
572
+ }
573
+ //#endregion
574
+ //#region src/resources/analytics.d.ts
575
+ /**
576
+ * Privacy-first analytics operations.
577
+ *
578
+ * Track pageviews and retrieve analytics reports. Forja analytics is
579
+ * cookie-free, GDPR-compliant by design — no PII is stored, visitor
580
+ * hashes are rotated daily, and raw data is pruned after a configurable retention period.
581
+ *
582
+ * Pageview tracking requires an API key with `Read` permission.
583
+ * Reports require `Read` permission.
584
+ */
585
+ declare class AnalyticsResource {
586
+ private readonly http;
587
+ private readonly siteId;
588
+ constructor(http: HttpClient, siteId: string);
589
+ /**
590
+ * Track a pageview.
591
+ *
592
+ * **Endpoint:** `POST /sites/{siteId}/analytics/pageview`
593
+ *
594
+ * The server computes the visitor hash from the client IP and user agent.
595
+ * No cookies or PII are stored.
596
+ *
597
+ * @param request - The pageview data.
598
+ * @param request.path - The page path (e.g. `"/blog/hello-world"`).
599
+ * @param request.referrer - Optional full referrer URL (only the domain is stored).
600
+ * @returns Acknowledgement (`{ ok: true }`).
601
+ *
602
+ * @example
603
+ * ```ts
604
+ * await forja.analytics.trackPageview({ path: '/blog/hello-world' });
605
+ * ```
606
+ */
607
+ trackPageview(request: TrackPageviewRequest): Promise<TrackPageviewResponse>;
608
+ /**
609
+ * Fetch an analytics summary report for the site.
610
+ *
611
+ * **Endpoint:** `GET /sites/{siteId}/analytics/report?days=&top_n=&start_date=&end_date=`
612
+ *
613
+ * Returns total views, unique visitors, top content pages, and daily trends.
614
+ *
615
+ * @param params - Report parameters.
616
+ * @param params.days - Number of days to look back (default: 30). Ignored if date range is set.
617
+ * @param params.topN - Number of top content pages to include (default: 10).
618
+ * @param params.startDate - Start date in `YYYY-MM-DD` format.
619
+ * @param params.endDate - End date in `YYYY-MM-DD` format.
620
+ * @returns The analytics report with totals, top content, and daily trend data.
621
+ *
622
+ * @example
623
+ * ```ts
624
+ * const report = await forja.analytics.getReport({ days: 7, topN: 5 });
625
+ * console.log(`${report.total_views} views, ${report.total_unique_visitors} unique`);
626
+ * ```
627
+ */
628
+ getReport(params?: AnalyticsReportParams): Promise<AnalyticsReportResponse>;
629
+ /**
630
+ * Fetch analytics for a specific page path.
631
+ *
632
+ * **Endpoint:** `GET /sites/{siteId}/analytics/report/page?path=&days=&start_date=&end_date=`
633
+ *
634
+ * Returns views, unique visitors, daily trend, and top referrer domains for a single page.
635
+ *
636
+ * @param params - Page analytics parameters.
637
+ * @param params.path - The page path to query (e.g. `"/blog/hello-world"`).
638
+ * @param params.days - Number of days to look back.
639
+ * @param params.startDate - Start date in `YYYY-MM-DD` format.
640
+ * @param params.endDate - End date in `YYYY-MM-DD` format.
641
+ * @returns Page-level analytics with trend and referrer data.
642
+ *
643
+ * @example
644
+ * ```ts
645
+ * const stats = await forja.analytics.getPageAnalytics({
646
+ * path: '/blog/hello-world',
647
+ * days: 30,
648
+ * });
649
+ * console.log(stats.referrers); // [{ domain: "google.com", views: 42 }, ...]
650
+ * ```
651
+ */
652
+ getPageAnalytics(params: AnalyticsPageParams): Promise<AnalyticsPageDetailResponse>;
653
+ }
654
+ //#endregion
655
+ //#region src/resources/blogs.d.ts
656
+ /**
657
+ * Blog post operations.
658
+ *
659
+ * Provides access to published blog content, featured posts, category filtering,
660
+ * similar post discovery, and individual blog retrieval by ID or slug.
661
+ *
662
+ * All read operations require an API key with `Read` permission.
663
+ */
664
+ declare class BlogsResource {
665
+ private readonly http;
666
+ private readonly siteId;
667
+ constructor(http: HttpClient, siteId: string);
668
+ /**
669
+ * Fetch a paginated list of published blog posts.
670
+ *
671
+ * **Endpoint:** `GET /sites/{siteId}/blogs/published?page=&page_size=&locale_id=`
672
+ *
673
+ * @param params - Pagination and locale filter options.
674
+ * @param params.page - 1-indexed page number (default: 1).
675
+ * @param params.pageSize - Items per page (default: server-side, typically 10).
676
+ * @param params.localeId - Filter to blogs with content in this locale (UUID).
677
+ * @returns A paginated result with {@link PaginatedResult.fetchNext | fetchNext()},
678
+ * {@link PaginatedResult.fetchAll | fetchAll()}, and async iteration support.
679
+ *
680
+ * @example
681
+ * ```ts
682
+ * // All locales
683
+ * const page1 = await forja.blogs.listPublished({ page: 1, pageSize: 10 });
684
+ *
685
+ * // Filtered to a specific locale
686
+ * const german = await forja.blogs.listPublished({ page: 1, localeId: 'de-locale-uuid' });
687
+ * ```
688
+ */
689
+ listPublished(params?: LocaleFilterParams): Promise<PaginatedResult<BlogListItem>>;
690
+ /**
691
+ * Fetch published blog posts filtered by category.
692
+ *
693
+ * **Endpoint:** `GET /sites/{siteId}/blogs/published/category/{categorySlug}?page=&page_size=&locale_id=`
694
+ *
695
+ * @param categorySlug - The category slug to filter by (e.g. `"tech"`, `"travel"`).
696
+ * @param params - Pagination and locale filter options.
697
+ * @returns A paginated result of blogs in the given category.
698
+ *
699
+ * @example
700
+ * ```ts
701
+ * const techBlogs = await forja.blogs.listByCategory('tech', { page: 1, localeId: 'uuid' });
702
+ * ```
703
+ */
704
+ listByCategory(categorySlug: string, params?: LocaleFilterParams): Promise<PaginatedResult<BlogListItem>>;
705
+ /**
706
+ * Fetch featured blog posts.
707
+ *
708
+ * **Endpoint:** `GET /sites/{siteId}/blogs/featured?limit=`
709
+ *
710
+ * @param opts.limit - Maximum number of featured posts to return (default: server-side).
711
+ * @returns Array of blog detail responses (includes localizations, categories, documents).
712
+ *
713
+ * @example
714
+ * ```ts
715
+ * const featured = await forja.blogs.listFeatured({ limit: 3 });
716
+ * ```
717
+ */
718
+ listFeatured(opts?: {
719
+ limit?: number;
720
+ }): Promise<BlogDetailResponse[]>;
721
+ /**
722
+ * Fetch blog posts similar to a given blog (by shared categories/tags).
723
+ *
724
+ * **Endpoint:** `GET /sites/{siteId}/blogs/{blogId}/similar?limit=`
725
+ *
726
+ * @param blogId - The UUID of the blog to find similar content for.
727
+ * @param opts.limit - Maximum number of similar posts (default: server-side, typically 3).
728
+ * @returns Array of similar blog list items.
729
+ *
730
+ * @example
731
+ * ```ts
732
+ * const similar = await forja.blogs.listSimilar('blog-uuid', { limit: 3 });
733
+ * ```
734
+ */
735
+ listSimilar(blogId: string, opts?: {
736
+ limit?: number;
737
+ }): Promise<BlogListItem[]>;
738
+ /**
739
+ * Fetch a blog post's full detail by its URL slug.
740
+ *
741
+ * Performs a two-step lookup:
742
+ * 1. **Endpoint:** `GET /sites/{siteId}/blogs/by-slug/{slug}` — resolves slug to ID.
743
+ * 2. **Endpoint:** `GET /blogs/{id}/detail` — fetches the full detail response.
744
+ *
745
+ * @param slug - The blog's URL slug (e.g. `"my-first-post"`).
746
+ * @returns The full blog detail (with localizations, categories, documents), or `null` if not found.
747
+ *
748
+ * @example
749
+ * ```ts
750
+ * const blog = await forja.blogs.getBySlug('my-first-post');
751
+ * if (blog) {
752
+ * console.log(blog.localizations[0]?.title);
753
+ * }
754
+ * ```
755
+ */
756
+ getBySlug(slug: string): Promise<BlogDetailResponse | null>;
757
+ /**
758
+ * Fetch a blog post's full detail by its UUID.
759
+ *
760
+ * **Endpoint:** `GET /blogs/{id}/detail`
761
+ *
762
+ * @param idOrSlug - The blog's UUID.
763
+ * @returns The full blog detail (with localizations, categories, documents), or `null` if not found.
764
+ *
765
+ * @example
766
+ * ```ts
767
+ * const blog = await forja.blogs.get('550e8400-e29b-41d4-a716-446655440000');
768
+ * ```
769
+ */
770
+ get(idOrSlug: string): Promise<BlogDetailResponse | null>;
771
+ /**
772
+ * Fetch the site's RSS feed as raw XML.
773
+ *
774
+ * **Endpoint:** `GET /sites/{siteId}/feed.rss`
775
+ *
776
+ * @returns RSS 2.0 XML string.
777
+ */
778
+ rss(): Promise<string>;
779
+ }
780
+ //#endregion
781
+ //#region src/resources/cv.d.ts
782
+ /**
783
+ * CV / resume operations.
784
+ *
785
+ * Provides access to skills (technologies, languages, tools) and CV entries
786
+ * (work experience, education, certifications, projects).
787
+ *
788
+ * All operations require an API key with `Read` permission.
789
+ */
790
+ declare class CvResource {
791
+ private readonly http;
792
+ private readonly siteId;
793
+ constructor(http: HttpClient, siteId: string);
794
+ /**
795
+ * Fetch a paginated list of skills.
796
+ *
797
+ * **Endpoint:** `GET /sites/{siteId}/skills?page=&page_size=&search=&sort_by=&sort_dir=`
798
+ *
799
+ * @param params - Pagination, search, and sort options.
800
+ * @returns A paginated result of skills (name, slug, category, proficiency level).
801
+ *
802
+ * @example
803
+ * ```ts
804
+ * const skills = await forja.cv.listSkills({ page: 1, pageSize: 50 });
805
+ * const programming = skills.data.filter(s => s.category === 'Programming');
806
+ * ```
807
+ */
808
+ listSkills(params?: SearchablePaginationParams): Promise<PaginatedResult<SkillResponse>>;
809
+ /**
810
+ * Fetch a skill by its UUID.
811
+ *
812
+ * **Endpoint:** `GET /skills/{id}`
813
+ *
814
+ * @param id - The skill's UUID.
815
+ * @returns The skill, or `null` if not found.
816
+ */
817
+ getSkill(id: string): Promise<SkillResponse | null>;
818
+ /**
819
+ * Fetch a skill by its URL slug.
820
+ *
821
+ * **Endpoint:** `GET /skills/by-slug/{slug}`
822
+ *
823
+ * @param slug - The skill's slug (e.g. `"typescript"`, `"rust"`).
824
+ * @returns The skill, or `null` if not found.
825
+ *
826
+ * @example
827
+ * ```ts
828
+ * const ts = await forja.cv.getSkillBySlug('typescript');
829
+ * if (ts) console.log(`${ts.name}: level ${ts.proficiency_level}`);
830
+ * ```
831
+ */
832
+ getSkillBySlug(slug: string): Promise<SkillResponse | null>;
833
+ /**
834
+ * Fetch a paginated list of CV entries (work, education, certifications, etc.).
835
+ *
836
+ * **Endpoint:** `GET /sites/{siteId}/cv?entry_type=&page=&page_size=&search=&sort_by=&sort_dir=`
837
+ *
838
+ * @param params - Pagination and filter options.
839
+ * @param params.entryType - Filter by entry type: `"Work"`, `"Education"`, `"Volunteer"`, `"Certification"`, or `"Project"`.
840
+ * @returns A paginated result of CV entries.
841
+ *
842
+ * @example
843
+ * ```ts
844
+ * const work = await forja.cv.listEntries({ entryType: 'Work' });
845
+ * const education = await forja.cv.listEntries({ entryType: 'Education' });
846
+ * ```
847
+ */
848
+ listEntries(params?: CvEntryParams): Promise<PaginatedResult<CvEntryResponse>>;
849
+ }
850
+ //#endregion
851
+ //#region src/resources/legal.d.ts
852
+ /**
853
+ * Legal document operations.
854
+ *
855
+ * Provides access to legal documents (privacy policy, terms of service, cookie consent, etc.),
856
+ * their consent groups, and individual consent items for building cookie banners and legal pages.
857
+ *
858
+ * All operations require an API key with `Read` permission.
859
+ */
860
+ declare class LegalResource {
861
+ private readonly http;
862
+ private readonly siteId;
863
+ constructor(http: HttpClient, siteId: string);
864
+ /**
865
+ * Fetch a paginated list of legal documents for the site.
866
+ *
867
+ * **Endpoint:** `GET /sites/{siteId}/legal?page=&page_size=&search=&sort_by=&sort_dir=`
868
+ *
869
+ * @param params - Pagination, search, and sort options.
870
+ * @returns A paginated result of legal document summaries.
871
+ *
872
+ * @example
873
+ * ```ts
874
+ * const docs = await forja.legal.list();
875
+ * ```
876
+ */
877
+ list(params?: SearchablePaginationParams): Promise<PaginatedResult<LegalDocumentResponse>>;
878
+ /**
879
+ * Fetch a legal document by its UUID, including localizations.
880
+ *
881
+ * **Endpoint:** `GET /legal/{id}`
882
+ *
883
+ * @param id - The legal document's UUID.
884
+ * @returns The document with all localizations, or `null` if not found.
885
+ */
886
+ get(id: string): Promise<LegalDocumentDetailResponse | null>;
887
+ /**
888
+ * Fetch a legal document by its URL slug.
889
+ *
890
+ * **Endpoint:** `GET /sites/{siteId}/legal/by-slug/{slug}`
891
+ *
892
+ * @param slug - The document slug (e.g. `"privacy-policy"`, `"terms-of-service"`).
893
+ * @returns The document with all localizations, or `null` if not found.
894
+ *
895
+ * @example
896
+ * ```ts
897
+ * const privacy = await forja.legal.getBySlug('privacy-policy');
898
+ * if (privacy) {
899
+ * const enLocale = privacy.localizations.find(l => l.locale_id === enLocaleId);
900
+ * }
901
+ * ```
902
+ */
903
+ getBySlug(slug: string): Promise<LegalDocumentDetailResponse | null>;
904
+ /**
905
+ * Fetch the cookie consent document with its consent groups and items.
906
+ *
907
+ * **Endpoint:** `GET /sites/{siteId}/legal/cookie-consent`
908
+ *
909
+ * Returns a structured document with groups (e.g. "Essential", "Analytics", "Marketing")
910
+ * and their items, including `is_required` and `default_enabled` flags for building
911
+ * GDPR-compliant cookie consent banners.
912
+ *
913
+ * @returns The cookie consent document with groups and items, or `null` if not configured.
914
+ *
915
+ * @example
916
+ * ```ts
917
+ * const consent = await forja.legal.getCookieConsent();
918
+ * if (consent) {
919
+ * consent.groups.forEach(group => {
920
+ * console.log(group.cookie_name, group.is_required ? '(required)' : '(optional)');
921
+ * });
922
+ * }
923
+ * ```
924
+ */
925
+ getCookieConsent(): Promise<LegalDocumentWithGroups | null>;
926
+ /**
927
+ * Fetch consent groups (with their items) for a legal document.
928
+ *
929
+ * **Endpoint:** `GET /legal/{documentId}/groups`
930
+ *
931
+ * @param documentId - The legal document's UUID.
932
+ * @returns Array of consent groups, each containing their items.
933
+ */
934
+ getGroups(documentId: string): Promise<LegalGroupWithItems[]>;
935
+ /**
936
+ * Fetch consent items within a specific group.
937
+ *
938
+ * **Endpoint:** `GET /legal/groups/{groupId}/items`
939
+ *
940
+ * @param groupId - The consent group's UUID.
941
+ * @returns Array of consent items (cookie name, required flag, display order).
942
+ */
943
+ getGroupItems(groupId: string): Promise<LegalItemResponse[]>;
944
+ /**
945
+ * Fetch the full detail of a legal document, including content localizations.
946
+ *
947
+ * **Endpoint:** `GET /legal/{id}/detail`
948
+ *
949
+ * Unlike {@link get}, this returns the full content body localizations
950
+ * (rendered text), document status, slug, version, and publish window.
951
+ * Use for rendering the full legal document page.
952
+ *
953
+ * @param id - The legal document's UUID.
954
+ * @returns The full document detail with content and doc localizations, or `null` if not found.
955
+ *
956
+ * @example
957
+ * ```ts
958
+ * const detail = await forja.legal.getDetail('doc-uuid');
959
+ * if (detail) {
960
+ * console.log(detail.version); // 3
961
+ * console.log(detail.document_type); // "PrivacyPolicy"
962
+ * console.log(detail.localizations); // content body per locale
963
+ * console.log(detail.doc_localizations); // title + intro per locale
964
+ * }
965
+ * ```
966
+ */
967
+ getDetail(id: string): Promise<LegalDocumentFullDetailResponse | null>;
968
+ /**
969
+ * Fetch the version history of a legal document.
970
+ *
971
+ * **Endpoint:** `GET /legal/{id}/versions`
972
+ *
973
+ * Returns all versions of the document, ordered by version number.
974
+ * Each entry includes the version number, content status, and creation date.
975
+ * Use for displaying a "Version history" section on legal pages.
976
+ *
977
+ * @param id - The legal document's UUID.
978
+ * @returns Array of version entries, newest first.
979
+ *
980
+ * @example
981
+ * ```ts
982
+ * const versions = await forja.legal.listVersions('doc-uuid');
983
+ * versions.forEach(v => {
984
+ * console.log(`v${v.version} — ${v.status} — ${v.created_at}`);
985
+ * });
986
+ * ```
987
+ */
988
+ listVersions(id: string): Promise<LegalVersionResponse[]>;
989
+ }
990
+ //#endregion
991
+ //#region src/resources/media.d.ts
992
+ /**
993
+ * Media asset operations.
994
+ *
995
+ * Provides access to media assets (images, videos, documents) stored in the CMS.
996
+ * Each media item includes its public URL, dimensions, file metadata, and
997
+ * responsive variants (thumbnails, different sizes).
998
+ *
999
+ * Requires an API key with `Read` permission.
1000
+ */
1001
+ declare class MediaResource {
1002
+ private readonly http;
1003
+ private readonly siteId;
1004
+ constructor(http: HttpClient, siteId: string);
1005
+ /**
1006
+ * Fetch a paginated list of media assets for the site.
1007
+ *
1008
+ * **Endpoint:** `GET /sites/{siteId}/media?page=&page_size=&search=&sort_by=&sort_dir=&mime_category=&folder_id=`
1009
+ *
1010
+ * Returns a lightweight list without variants. Use {@link get} to fetch full
1011
+ * detail with responsive variants for a specific asset.
1012
+ *
1013
+ * @param params - Pagination, search, sort, and filter options.
1014
+ * @param params.search - Search by filename.
1015
+ * @param params.mimeCategory - Filter by MIME category: `"image"`, `"video"`, `"document"`, `"audio"`.
1016
+ * @param params.folderId - Filter to a specific media folder (UUID).
1017
+ * @returns A paginated result of media list items.
1018
+ *
1019
+ * @example
1020
+ * ```ts
1021
+ * // Browse all images
1022
+ * const images = await forja.media.list({ mimeCategory: 'image', pageSize: 20 });
1023
+ *
1024
+ * // Search by filename
1025
+ * const results = await forja.media.list({ search: 'hero-banner' });
1026
+ *
1027
+ * // Browse a specific folder
1028
+ * const folder = await forja.media.list({ folderId: 'folder-uuid' });
1029
+ * ```
1030
+ */
1031
+ list(params?: MediaListParams): Promise<PaginatedResult<MediaListItem>>;
1032
+ /**
1033
+ * Fetch a media asset by its UUID.
1034
+ *
1035
+ * **Endpoint:** `GET /media/{id}`
1036
+ *
1037
+ * Returns the full media metadata including filename, MIME type, dimensions,
1038
+ * public URL, and all responsive variants (thumbnails, different sizes).
1039
+ *
1040
+ * @param id - The media asset's UUID.
1041
+ * @returns The media asset with variants, or `null` if not found.
1042
+ *
1043
+ * @example
1044
+ * ```ts
1045
+ * const cover = await forja.media.get(blog.cover_image_id);
1046
+ * if (cover) {
1047
+ * console.log(cover.public_url); // original image URL
1048
+ * console.log(cover.variants); // [{ variant_name, width, height, public_url }, ...]
1049
+ * console.log(cover.mime_type); // "image/jpeg"
1050
+ * }
1051
+ * ```
1052
+ */
1053
+ get(id: string): Promise<MediaResponse | null>;
1054
+ }
1055
+ //#endregion
1056
+ //#region src/resources/navigation.d.ts
1057
+ /**
1058
+ * Navigation menu operations.
1059
+ *
1060
+ * Provides access to navigation menus, their hierarchical tree structure,
1061
+ * and individual menu items. Used to render site headers, footers, and sidebars.
1062
+ *
1063
+ * All operations require an API key with `Read` permission.
1064
+ */
1065
+ declare class NavigationResource {
1066
+ private readonly http;
1067
+ private readonly siteId;
1068
+ constructor(http: HttpClient, siteId: string);
1069
+ /**
1070
+ * Fetch all navigation menus for the site.
1071
+ *
1072
+ * **Endpoint:** `GET /sites/{siteId}/menus`
1073
+ *
1074
+ * @returns Array of menu metadata (slug, description, depth, item count).
1075
+ *
1076
+ * @example
1077
+ * ```ts
1078
+ * const menus = await forja.navigation.listMenus();
1079
+ * const primary = menus.find(m => m.slug === 'primary');
1080
+ * ```
1081
+ */
1082
+ listMenus(): Promise<NavigationMenuResponse[]>;
1083
+ /**
1084
+ * Fetch a navigation menu by its UUID.
1085
+ *
1086
+ * **Endpoint:** `GET /menus/{menuId}`
1087
+ *
1088
+ * @param menuId - The menu's UUID.
1089
+ * @returns Menu metadata, or `null` if not found.
1090
+ */
1091
+ getMenu(menuId: string): Promise<NavigationMenuResponse | null>;
1092
+ /**
1093
+ * Fetch a navigation menu by its slug.
1094
+ *
1095
+ * **Endpoint:** `GET /sites/{siteId}/menus/slug/{slug}`
1096
+ *
1097
+ * @param slug - The menu's slug (e.g. `"primary"`, `"footer"`).
1098
+ * @returns Menu metadata, or `null` if not found.
1099
+ *
1100
+ * @example
1101
+ * ```ts
1102
+ * const primary = await forja.navigation.getMenuBySlug('primary');
1103
+ * if (primary) {
1104
+ * const tree = await forja.navigation.getTree(primary.id);
1105
+ * }
1106
+ * ```
1107
+ */
1108
+ getMenuBySlug(slug: string): Promise<NavigationMenuResponse | null>;
1109
+ /**
1110
+ * Fetch the hierarchical navigation tree for a menu.
1111
+ *
1112
+ * **Endpoint:** `GET /menus/{menuId}/tree?locale=`
1113
+ *
1114
+ * Returns a recursive tree of navigation items with children, page slugs,
1115
+ * external URLs, and icons. Optionally filtered by locale for multi-language sites.
1116
+ *
1117
+ * @param menuId - The menu's UUID.
1118
+ * @param opts.locale - Optional locale code (e.g. `"en"`, `"de"`) to filter localized titles.
1119
+ * @returns Array of root-level navigation tree nodes (each may have nested children).
1120
+ *
1121
+ * @example
1122
+ * ```ts
1123
+ * const tree = await forja.navigation.getTree('menu-uuid', { locale: 'de' });
1124
+ * tree.forEach(node => {
1125
+ * console.log(node.title, node.page_slug, node.children.length);
1126
+ * });
1127
+ * ```
1128
+ */
1129
+ getTree(menuId: string, opts?: {
1130
+ locale?: string;
1131
+ }): Promise<NavigationTree[]>;
1132
+ /**
1133
+ * Fetch all flat (non-hierarchical) items in a menu.
1134
+ *
1135
+ * **Endpoint:** `GET /menus/{menuId}/items`
1136
+ *
1137
+ * @param menuId - The menu's UUID.
1138
+ * @returns Array of navigation items (use `parent_id` to reconstruct hierarchy if needed).
1139
+ */
1140
+ listItems(menuId: string): Promise<NavigationItemResponse[]>;
1141
+ /**
1142
+ * Fetch a single navigation item by its UUID.
1143
+ *
1144
+ * **Endpoint:** `GET /navigation/{itemId}`
1145
+ *
1146
+ * @param itemId - The navigation item's UUID.
1147
+ * @returns The navigation item, or `null` if not found.
1148
+ */
1149
+ getItem(itemId: string): Promise<NavigationItemResponse | null>;
1150
+ }
1151
+ //#endregion
1152
+ //#region src/resources/pages.d.ts
1153
+ /**
1154
+ * Extended pagination params for page listings.
1155
+ * Supports filtering by status, page type, and exclusion.
1156
+ */
1157
+ interface PageListParams extends SearchablePaginationParams {
1158
+ /** Filter by content status (e.g. `"Published"`, `"Draft"`). */
1159
+ status?: string;
1160
+ /** Filter by page type (e.g. `"Static"`, `"Landing"`, `"Contact"`). */
1161
+ pageType?: string;
1162
+ /** Exclude pages with this status. */
1163
+ excludeStatus?: string;
1164
+ }
1165
+ /**
1166
+ * CMS page operations.
1167
+ *
1168
+ * Provides access to page listings, route-based lookup, page sections,
1169
+ * and section localizations for multi-language content rendering.
1170
+ *
1171
+ * All operations require an API key with `Read` permission.
1172
+ */
1173
+ declare class PagesResource {
1174
+ private readonly http;
1175
+ private readonly siteId;
1176
+ constructor(http: HttpClient, siteId: string);
1177
+ /**
1178
+ * Fetch a paginated list of CMS pages.
1179
+ *
1180
+ * **Endpoint:** `GET /sites/{siteId}/pages?page=&page_size=&search=&status=&page_type=&sort_by=&sort_dir=&exclude_status=`
1181
+ *
1182
+ * @param params - Pagination, search, and filter options.
1183
+ * @returns A paginated result of page list items.
1184
+ *
1185
+ * @example
1186
+ * ```ts
1187
+ * const pages = await forja.pages.list({ page: 1, pageSize: 100 });
1188
+ * const allPages = await pages.fetchAll();
1189
+ * ```
1190
+ */
1191
+ list(params?: PageListParams): Promise<PaginatedResult<PageListItem>>;
1192
+ /**
1193
+ * Fetch a page by its URL route path.
1194
+ *
1195
+ * **Endpoint:** `GET /sites/{siteId}/pages/by-route/{route}`
1196
+ *
1197
+ * Leading slashes are stripped automatically (`"/about"` and `"about"` both work).
1198
+ *
1199
+ * @param route - The page's route path (e.g. `"/about"` or `"contact"`).
1200
+ * @returns The page detail with localizations and OG image, or `null` if not found.
1201
+ *
1202
+ * @example
1203
+ * ```ts
1204
+ * const aboutPage = await forja.pages.getByRoute('/about');
1205
+ * if (aboutPage) {
1206
+ * console.log(aboutPage.localizations[0]?.title);
1207
+ * }
1208
+ * ```
1209
+ */
1210
+ getByRoute(route: string): Promise<PageDetailResponse | null>;
1211
+ /**
1212
+ * Fetch all sections for a page.
1213
+ *
1214
+ * **Endpoint:** `GET /pages/{pageId}/sections`
1215
+ *
1216
+ * Sections are returned in display order and include type, settings,
1217
+ * cover image reference, and call-to-action route.
1218
+ *
1219
+ * @param pageId - The page's UUID.
1220
+ * @returns Array of page sections.
1221
+ */
1222
+ getSections(pageId: string): Promise<PageSectionResponse[]>;
1223
+ /**
1224
+ * Fetch localizations for a single section.
1225
+ *
1226
+ * **Endpoint:** `GET /pages/sections/{sectionId}/localizations`
1227
+ *
1228
+ * @param sectionId - The section's UUID.
1229
+ * @returns Array of localized content (title, text, button text) per locale.
1230
+ */
1231
+ getSectionLocalizations(sectionId: string): Promise<SectionLocalizationResponse[]>;
1232
+ /**
1233
+ * Fetch localizations for all sections of a page in a single request.
1234
+ *
1235
+ * **Endpoint:** `GET /pages/{pageId}/sections/localizations`
1236
+ *
1237
+ * More efficient than calling {@link getSectionLocalizations} per section.
1238
+ *
1239
+ * @param pageId - The page's UUID.
1240
+ * @returns Array of all section localizations for the page.
1241
+ */
1242
+ getPageSectionLocalizations(pageId: string): Promise<SectionLocalizationResponse[]>;
1243
+ }
1244
+ //#endregion
1245
+ //#region src/resources/projects.d.ts
1246
+ /**
1247
+ * Portfolio project operations.
1248
+ *
1249
+ * Provides access to published projects with localizations, links, media
1250
+ * attachments, and skill/CV associations. Use for portfolio pages, project
1251
+ * showcases, and case studies.
1252
+ *
1253
+ * All read operations require an API key with `Read` permission.
1254
+ */
1255
+ declare class ProjectsResource {
1256
+ private readonly http;
1257
+ private readonly siteId;
1258
+ constructor(http: HttpClient, siteId: string);
1259
+ /**
1260
+ * Fetch a paginated list of published projects.
1261
+ *
1262
+ * **Endpoint:** `GET /sites/{siteId}/projects/public?page=&page_size=&sort_by=&sort_dir=&is_featured=`
1263
+ *
1264
+ * Returns projects with `published` or `scheduled` status that are within
1265
+ * their publish window. Results can be sorted and filtered to featured only.
1266
+ *
1267
+ * @param params - Pagination, sort, and filter options.
1268
+ * @param params.page - 1-indexed page number (default: 1).
1269
+ * @param params.pageSize - Items per page, 1–100 (default: 10).
1270
+ * @param params.sortBy - Sort field (e.g. `"display_order"`, `"start_date"`, `"created_at"`).
1271
+ * @param params.sortDir - Sort direction: `"asc"` or `"desc"`.
1272
+ * @param params.isFeatured - When `true`, returns only featured projects.
1273
+ * @returns A paginated result with {@link PaginatedResult.fetchNext | fetchNext()},
1274
+ * {@link PaginatedResult.fetchAll | fetchAll()}, and async iteration support.
1275
+ *
1276
+ * @example
1277
+ * ```ts
1278
+ * // All published projects
1279
+ * const projects = await forja.projects.listPublished({ page: 1, pageSize: 12 });
1280
+ *
1281
+ * // Featured projects only, sorted by display order
1282
+ * const featured = await forja.projects.listPublished({
1283
+ * isFeatured: true,
1284
+ * sortBy: 'display_order',
1285
+ * sortDir: 'asc',
1286
+ * });
1287
+ * ```
1288
+ */
1289
+ listPublished(params?: ProjectListParams): Promise<PaginatedResult<ProjectResponse>>;
1290
+ /**
1291
+ * Fetch a project's full detail by its UUID.
1292
+ *
1293
+ * **Endpoint:** `GET /projects/{id}`
1294
+ *
1295
+ * Returns the complete project with localizations (title, description per locale),
1296
+ * external links (repository, demo, docs), media attachments (cover image,
1297
+ * screenshots), and associations to skills and CV entries.
1298
+ *
1299
+ * @param id - The project's UUID.
1300
+ * @returns The full project detail, or `null` if not found.
1301
+ *
1302
+ * @example
1303
+ * ```ts
1304
+ * const project = await forja.projects.get('project-uuid');
1305
+ * if (project) {
1306
+ * const loc = project.localizations.find(l => l.locale_id === localeId);
1307
+ * console.log(loc?.title, loc?.short_description);
1308
+ * console.log(project.links); // [{ label, url, link_type }]
1309
+ * }
1310
+ * ```
1311
+ */
1312
+ get(id: string): Promise<ProjectDetailResponse | null>;
1313
+ /**
1314
+ * Fetch a project by its URL slug.
1315
+ *
1316
+ * **Endpoint:** `GET /sites/{siteId}/projects/by-slug/{slug}`
1317
+ *
1318
+ * @param slug - The project's URL slug (e.g. `"forja-cms"`, `"my-portfolio"`).
1319
+ * @returns The project summary, or `null` if not found.
1320
+ *
1321
+ * @example
1322
+ * ```ts
1323
+ * const project = await forja.projects.getBySlug('forja-cms');
1324
+ * ```
1325
+ */
1326
+ getBySlug(slug: string): Promise<ProjectResponse | null>;
1327
+ }
1328
+ //#endregion
1329
+ //#region src/resources/redirects.d.ts
1330
+ /**
1331
+ * URL redirect operations.
1332
+ *
1333
+ * Provides server-side redirect lookup for SSR frameworks. When a request
1334
+ * arrives at a path that has a redirect configured, use {@link lookup} to
1335
+ * check if the path should be redirected and to which destination.
1336
+ *
1337
+ * Requires an API key with `Read` permission.
1338
+ */
1339
+ declare class RedirectsResource {
1340
+ private readonly http;
1341
+ private readonly siteId;
1342
+ constructor(http: HttpClient, siteId: string);
1343
+ /**
1344
+ * Look up a redirect for a given request path.
1345
+ *
1346
+ * **Endpoint:** `GET /sites/{siteId}/redirects/lookup?path=`
1347
+ *
1348
+ * Checks if the site has an active redirect configured for the given path.
1349
+ * Returns the destination URL and HTTP status code (301 permanent, 302 temporary,
1350
+ * 307 temporary preserve method, 308 permanent preserve method).
1351
+ *
1352
+ * Use this in SSR middleware to handle redirects before rendering the page.
1353
+ *
1354
+ * @param path - The request path to check (e.g. `"/old-blog-post"`, `"/legacy/page"`).
1355
+ * @returns The redirect destination and status code, or `null` if no redirect exists for this path.
1356
+ *
1357
+ * @example
1358
+ * ```ts
1359
+ * // In Astro middleware or Next.js middleware:
1360
+ * const redirect = await forja.redirects.lookup('/old-url');
1361
+ * if (redirect) {
1362
+ * return Response.redirect(redirect.destination_path, redirect.status_code);
1363
+ * }
1364
+ * ```
1365
+ *
1366
+ * @example
1367
+ * ```ts
1368
+ * // In Express:
1369
+ * app.use(async (req, res, next) => {
1370
+ * const redirect = await forja.redirects.lookup(req.path);
1371
+ * if (redirect) {
1372
+ * return res.redirect(redirect.status_code, redirect.destination_path);
1373
+ * }
1374
+ * next();
1375
+ * });
1376
+ * ```
1377
+ */
1378
+ lookup(path: string): Promise<RedirectLookupResponse | null>;
1379
+ }
1380
+ //#endregion
1381
+ //#region src/resources/site.d.ts
1382
+ /**
1383
+ * Site configuration operations.
1384
+ *
1385
+ * Provides access to the site's metadata (name, slug, timezone, etc.)
1386
+ * and configured locales (languages available for content).
1387
+ *
1388
+ * Requires an API key with `Read` permission.
1389
+ */
1390
+ declare class SiteResource {
1391
+ private readonly http;
1392
+ private readonly siteId;
1393
+ constructor(http: HttpClient, siteId: string);
1394
+ /**
1395
+ * Fetch the site's configuration.
1396
+ *
1397
+ * **Endpoint:** `GET /sites/{siteId}`
1398
+ *
1399
+ * Returns the site's name, slug, description, logo/favicon URLs, timezone,
1400
+ * theme settings, and default locale. Useful for rendering site-wide UI
1401
+ * elements (header, footer, SEO defaults).
1402
+ *
1403
+ * @returns The site configuration.
1404
+ *
1405
+ * @example
1406
+ * ```ts
1407
+ * const site = await forja.site.get();
1408
+ * console.log(site.name); // "My Website"
1409
+ * console.log(site.timezone); // "Europe/Vienna"
1410
+ * console.log(site.favicon_url); // "https://cdn.example.com/favicon.ico"
1411
+ * ```
1412
+ */
1413
+ get(): Promise<SiteResponse>;
1414
+ /**
1415
+ * Fetch all locales configured for the site.
1416
+ *
1417
+ * **Endpoint:** `GET /sites/{siteId}/locales`
1418
+ *
1419
+ * Returns the languages available for content on this site, including
1420
+ * the default locale, locale codes, text direction (LTR/RTL), and
1421
+ * active status. Use to build language switchers and determine which
1422
+ * locale to pass to other API calls (e.g. blog listing with `localeId`).
1423
+ *
1424
+ * @returns Array of site locales, with the default locale marked via `is_default`.
1425
+ *
1426
+ * @example
1427
+ * ```ts
1428
+ * const locales = await forja.site.listLocales();
1429
+ * const defaultLocale = locales.find(l => l.is_default);
1430
+ * console.log(defaultLocale?.code); // "en"
1431
+ *
1432
+ * // Build a language switcher
1433
+ * locales
1434
+ * .filter(l => l.is_active)
1435
+ * .forEach(l => console.log(`${l.name} (${l.code})`));
1436
+ * ```
1437
+ */
1438
+ listLocales(): Promise<SiteLocaleResponse[]>;
1439
+ }
1440
+ //#endregion
1441
+ //#region src/resources/social.d.ts
1442
+ /**
1443
+ * Social media link operations.
1444
+ *
1445
+ * Provides access to the site's configured social media links
1446
+ * (GitHub, Twitter/X, LinkedIn, etc.) for rendering in headers, footers,
1447
+ * and about pages.
1448
+ *
1449
+ * Requires an API key with `Read` permission.
1450
+ */
1451
+ declare class SocialResource {
1452
+ private readonly http;
1453
+ private readonly siteId;
1454
+ constructor(http: HttpClient, siteId: string);
1455
+ /**
1456
+ * Fetch all social media links for the site.
1457
+ *
1458
+ * **Endpoint:** `GET /sites/{siteId}/social`
1459
+ *
1460
+ * Returns links sorted by `display_order`. Each link includes a title,
1461
+ * URL, icon identifier, and optional alt text for accessibility.
1462
+ *
1463
+ * @returns Array of social links.
1464
+ *
1465
+ * @example
1466
+ * ```ts
1467
+ * const links = await forja.social.list();
1468
+ * links.forEach(link => {
1469
+ * console.log(`${link.title}: ${link.url} (icon: ${link.icon})`);
1470
+ * });
1471
+ * ```
1472
+ */
1473
+ list(): Promise<SocialLinkResponse[]>;
1474
+ }
1475
+ //#endregion
1476
+ //#region src/resources/taxonomy.d.ts
1477
+ /**
1478
+ * Taxonomy operations (tags and categories).
1479
+ *
1480
+ * Tags and categories are used to organize blog posts and other content.
1481
+ * Categories support hierarchy (parent/child), while tags are flat labels.
1482
+ *
1483
+ * All operations require an API key with `Read` permission.
1484
+ */
1485
+ declare class TaxonomyResource {
1486
+ private readonly http;
1487
+ private readonly siteId;
1488
+ constructor(http: HttpClient, siteId: string);
1489
+ /**
1490
+ * Fetch a paginated list of tags for the site.
1491
+ *
1492
+ * **Endpoint:** `GET /sites/{siteId}/tags?page=&page_size=&search=&sort_by=&sort_dir=`
1493
+ *
1494
+ * @param params - Pagination, search, and sort options.
1495
+ * @returns A paginated result of tags.
1496
+ *
1497
+ * @example
1498
+ * ```ts
1499
+ * const tags = await forja.taxonomy.listTags({ search: 'rust', sortBy: 'slug' });
1500
+ * ```
1501
+ */
1502
+ listTags(params?: SearchablePaginationParams): Promise<PaginatedResult<TagResponse>>;
1503
+ /**
1504
+ * Fetch a paginated list of categories for the site.
1505
+ *
1506
+ * **Endpoint:** `GET /sites/{siteId}/categories?page=&page_size=&search=&sort_by=&sort_dir=`
1507
+ *
1508
+ * @param params - Pagination, search, and sort options.
1509
+ * @returns A paginated result of categories.
1510
+ *
1511
+ * @example
1512
+ * ```ts
1513
+ * const categories = await forja.taxonomy.listCategories({ page: 1, pageSize: 50 });
1514
+ * ```
1515
+ */
1516
+ listCategories(params?: SearchablePaginationParams): Promise<PaginatedResult<CategoryResponse>>;
1517
+ /**
1518
+ * Fetch all categories with their associated blog post counts.
1519
+ *
1520
+ * **Endpoint:** `GET /sites/{siteId}/categories/blog-counts`
1521
+ *
1522
+ * Useful for rendering category sidebars with post counts.
1523
+ *
1524
+ * @returns Array of categories, each with a `blog_count` field.
1525
+ *
1526
+ * @example
1527
+ * ```ts
1528
+ * const categories = await forja.taxonomy.getCategoriesWithBlogCounts();
1529
+ * categories.forEach(c => console.log(`${c.slug}: ${c.blog_count} posts`));
1530
+ * ```
1531
+ */
1532
+ getCategoriesWithBlogCounts(): Promise<CategoryWithCountResponse[]>;
1533
+ /**
1534
+ * Fetch all tags associated with a specific content item (blog, page, etc.).
1535
+ *
1536
+ * **Endpoint:** `GET /content/{contentId}/tags`
1537
+ *
1538
+ * @param contentId - The content item's UUID (the `content_id` field, not the blog/page ID).
1539
+ * @returns Array of tags assigned to the content.
1540
+ */
1541
+ getContentTags(contentId: string): Promise<TagResponse[]>;
1542
+ /**
1543
+ * Fetch all categories associated with a specific content item.
1544
+ *
1545
+ * **Endpoint:** `GET /content/{contentId}/categories`
1546
+ *
1547
+ * @param contentId - The content item's UUID (the `content_id` field, not the blog/page ID).
1548
+ * @returns Array of categories assigned to the content.
1549
+ */
1550
+ getContentCategories(contentId: string): Promise<CategoryResponse[]>;
1551
+ /**
1552
+ * Fetch a single tag by its UUID.
1553
+ *
1554
+ * **Endpoint:** `GET /tags/{id}`
1555
+ *
1556
+ * @param id - The tag's UUID.
1557
+ * @returns The tag, or `null` if not found.
1558
+ *
1559
+ * @example
1560
+ * ```ts
1561
+ * const tag = await forja.taxonomy.getTag('tag-uuid');
1562
+ * ```
1563
+ */
1564
+ getTag(id: string): Promise<TagResponse | null>;
1565
+ /**
1566
+ * Fetch a single tag by its URL slug.
1567
+ *
1568
+ * **Endpoint:** `GET /tags/by-slug/{slug}`
1569
+ *
1570
+ * @param slug - The tag's URL slug (e.g. `"typescript"`, `"web-components"`).
1571
+ * @returns The tag, or `null` if not found.
1572
+ *
1573
+ * @example
1574
+ * ```ts
1575
+ * const tag = await forja.taxonomy.getTagBySlug('typescript');
1576
+ * if (tag) {
1577
+ * console.log(tag.name, tag.slug);
1578
+ * }
1579
+ * ```
1580
+ */
1581
+ getTagBySlug(slug: string): Promise<TagResponse | null>;
1582
+ /**
1583
+ * Fetch a single category by its UUID.
1584
+ *
1585
+ * **Endpoint:** `GET /categories/{id}`
1586
+ *
1587
+ * @param id - The category's UUID.
1588
+ * @returns The category, or `null` if not found.
1589
+ *
1590
+ * @example
1591
+ * ```ts
1592
+ * const category = await forja.taxonomy.getCategory('cat-uuid');
1593
+ * ```
1594
+ */
1595
+ getCategory(id: string): Promise<CategoryResponse | null>;
1596
+ /**
1597
+ * Fetch child categories of a parent category.
1598
+ *
1599
+ * **Endpoint:** `GET /categories/{parentId}/children`
1600
+ *
1601
+ * Use to build hierarchical category trees (e.g. nested navigation menus,
1602
+ * category sidebars with subcategories).
1603
+ *
1604
+ * @param parentId - The parent category's UUID.
1605
+ * @returns Array of child categories.
1606
+ *
1607
+ * @example
1608
+ * ```ts
1609
+ * const subcategories = await forja.taxonomy.getCategoryChildren('parent-uuid');
1610
+ * subcategories.forEach(child => console.log(child.slug));
1611
+ * ```
1612
+ */
1613
+ getCategoryChildren(parentId: string): Promise<CategoryResponse[]>;
1614
+ }
1615
+ //#endregion
1616
+ //#region src/client.d.ts
1617
+ /**
1618
+ * The main entry point for the Forja CMS content SDK.
1619
+ *
1620
+ * Create an instance with your API credentials and use the resource
1621
+ * properties to interact with the Forja content API.
1622
+ *
1623
+ * @example
1624
+ * ```ts
1625
+ * import { ForjaClient } from '@forjacms/client';
1626
+ *
1627
+ * const forja = new ForjaClient({
1628
+ * baseUrl: 'https://cms.example.com/api/v1',
1629
+ * apiKey: 'your-read-api-key',
1630
+ * siteId: 'your-site-uuid',
1631
+ * });
1632
+ *
1633
+ * const blogs = await forja.blogs.listPublished({ page: 1 });
1634
+ * const site = await forja.site.get();
1635
+ * const locales = await forja.site.listLocales();
1636
+ * ```
1637
+ */
1638
+ declare class ForjaClient {
1639
+ /** Blog posts — published listings, featured, similar, detail by slug/ID. */
1640
+ readonly blogs: BlogsResource;
1641
+ /** CMS pages — list, fetch by route, sections and localizations. */
1642
+ readonly pages: PagesResource;
1643
+ /** Navigation menus — menu metadata, tree structure, items. */
1644
+ readonly navigation: NavigationResource;
1645
+ /** Tags and categories — listing, lookup by slug, content associations, blog counts. */
1646
+ readonly taxonomy: TaxonomyResource;
1647
+ /** Privacy-first analytics — pageview tracking and reports. */
1648
+ readonly analytics: AnalyticsResource;
1649
+ /** CV / resume — skills and work/education entries. */
1650
+ readonly cv: CvResource;
1651
+ /** Legal documents — privacy policy, cookie consent, terms, version history. */
1652
+ readonly legal: LegalResource;
1653
+ /** Portfolio projects — published listings, detail, skill/CV associations. */
1654
+ readonly projects: ProjectsResource;
1655
+ /** URL redirects — path-based lookup for SSR middleware. */
1656
+ readonly redirects: RedirectsResource;
1657
+ /** Site configuration — name, slug, logo, timezone, locales. */
1658
+ readonly site: SiteResource;
1659
+ /** Media assets — browse, search, filter by type, full detail with variants. */
1660
+ readonly media: MediaResource;
1661
+ /** Social media links — GitHub, Twitter, LinkedIn, etc. */
1662
+ readonly social: SocialResource;
1663
+ /**
1664
+ * @param config - API connection settings.
1665
+ * @param config.baseUrl - Full URL to the Forja API (e.g. `https://cms.example.com/api/v1`).
1666
+ * @param config.apiKey - API key with at least `Read` permission.
1667
+ * @param config.siteId - UUID of the site to query.
1668
+ * @param config.fetch - Optional custom `fetch` implementation for edge runtimes or testing.
1669
+ */
1670
+ constructor(config: ForjaClientConfig);
1671
+ }
1672
+ //#endregion
1673
+ export { ProjectLocalizationResponse as $, LegalVersionResponse as A, NavigationTree as B, LegalDocumentDetailResponse as C, LegalGroupResponse as D, LegalDocumentWithGroups as E, MediaResponse as F, PageType as G, PageListItem as H, MediaVariantResponse as I, PaginationParams as J, Paginated as K, NavigationItemLocalizationResponse as L, LocalizationResponse as M, MediaListItem as N, LegalGroupWithItems as O, MediaListParams as P, ProjectListParams as Q, NavigationItemResponse as R, LegalDocType as S, LegalDocumentResponse as T, PageResponse as U, PageDetailResponse as V, PageSectionResponse as W, ProjectLinkResponse as X, ProjectDetailResponse as Y, ProjectLinkType as Z, CvEntryResponse as _, TranslationStatus as _t, AnalyticsPageParams as a, SearchablePaginationParams as at, ForjaClientConfig as b, BlogDetailResponse as c, SiteLocaleResponse as ct, BlogResponse as d, SkillResponse as dt, ProjectMediaResponse as et, CategoryResponse as f, SocialLinkResponse as ft, CvEntryParams as g, TrackPageviewResponse as gt, ContentStatus as h, TrackPageviewRequest as ht, AnalyticsPageDetailResponse as i, ReferrerItem as it, LocaleFilterParams as j, LegalItemResponse as k, BlogDocumentResponse as l, SiteResponse as lt, CategoryWithCountResponse as m, TopContentItem as mt, HttpClient as n, RedirectLookupResponse as nt, AnalyticsReportParams as o, SectionLocalizationResponse as ot, CategoryTree as p, TagResponse as pt, PaginationMeta as q, PaginatedResult as r, RedirectResponse as rt, AnalyticsReportResponse as s, SectionType as st, ForjaClient as t, ProjectResponse as tt, BlogListItem as u, SkillCategory as ut, CvEntryType as v, TrendDataPoint as vt, LegalDocumentFullDetailResponse as w, LegalDocLocalizationResponse as x, DocumentLocalizationResponse as y, NavigationMenuResponse as z };