//#region src/types.d.ts interface ForjaClientConfig { baseUrl: string; apiKey: string; siteId: string; fetch?: typeof globalThis.fetch; /** * Public site domain. Sent as the `X-Site-Domain` header on every * request — required by the public Forms endpoints (#582/#584) which * resolve the site from the domain rather than a path parameter. * Optional: omit when only authenticated, site_id-scoped endpoints * are used. */ siteDomain?: string; } interface PaginationMeta { page: number; page_size: number; total_pages: number; total_items: number; } interface Paginated { data: T[]; meta: PaginationMeta; } interface PaginationParams { page?: number; pageSize?: number; } interface LocaleFilterParams extends PaginationParams { /** Filter to content with a localization in this locale (UUID). */ localeId?: string; } interface SearchablePaginationParams extends PaginationParams { search?: string; sortBy?: string; sortDir?: 'asc' | 'desc'; } type ContentStatus = 'Draft' | 'InReview' | 'Scheduled' | 'Published' | 'Archived'; type TranslationStatus = 'Pending' | 'InProgress' | 'Review' | 'Approved' | 'Outdated'; type PageType = 'Static' | 'Landing' | 'Contact' | 'BlogIndex' | 'Custom'; type SectionType = 'Hero' | 'Features' | 'Cta' | 'Gallery' | 'Testimonials' | 'Pricing' | 'Faq' | 'Contact' | 'Custom' | 'Stats' | 'Team' | 'Timeline' | 'LogoCloud' | 'Newsletter' | 'Video' | 'Divider' | 'Text' | 'Portfolio' | 'TagCloud' | 'Projects' | 'Blog' | 'Legal'; type CvEntryType = 'Work' | 'Education' | 'Volunteer' | 'Certification' | 'Project'; type SkillCategory = 'Programming' | 'Framework' | 'Database' | 'Devops' | 'Language' | 'SoftSkill' | 'Tool' | 'Other'; type LegalDocType = 'CookieConsent' | 'PrivacyPolicy' | 'TermsOfService' | 'Imprint' | 'Disclaimer'; interface LocalizationResponse { id: string; content_id: string; locale_id: string; title: string; subtitle: string | null; excerpt: string | null; body: string | null; meta_title: string | null; meta_description: string | null; translation_status: TranslationStatus; created_at: string; updated_at: string; } interface BlogListItem { id: string; content_id: string; slug: string | null; author: string; published_date: string; reading_time_minutes: number | null; cover_image_id: string | null; header_image_id: string | null; is_featured: boolean; is_sample: boolean; status: ContentStatus; publish_start: string | null; publish_end: string | null; created_at: string; updated_at: string; } interface BlogResponse { id: string; content_id: string; slug: string | null; author: string; published_date: string; reading_time_minutes: number | null; cover_image_id: string | null; header_image_id: string | null; is_featured: boolean; is_sample: boolean; allow_comments: boolean; status: ContentStatus; published_at: string | null; publish_start: string | null; publish_end: string | null; created_at: string; updated_at: string; } interface BlogDocumentResponse { id: string; blog_id: string; document_id: string; display_order: number; url: string | null; document_type: string; file_name: string | null; has_file: boolean; localizations: DocumentLocalizationResponse[]; created_at: string; } interface DocumentLocalizationResponse { id: string; document_id: string; locale_id: string; name: string; description: string | null; created_at: string; updated_at: string; } interface BlogDetailResponse extends BlogResponse { localizations: LocalizationResponse[]; categories: CategoryResponse[]; documents: BlogDocumentResponse[]; og_image_url: string | null; } /** * Options for blog detail lookups (`get`, `getBySlug`). The list shape * does not yet carry `localizations[]` so the resolver only applies to * the detail endpoint — list canonicalization is tracked separately. */ interface BlogDetailParams { /** * Optional locale code (e.g. `"en"`). When set, `localizations[]` * collapses to one resolved entry. See ADR 0002. */ locale?: string; } interface PageListItem { id: string; route: string; page_type: PageType; slug: string | null; is_in_navigation: boolean; status: ContentStatus; publish_start: string | null; publish_end: string | null; created_at: string; } interface PageResponse { id: string; content_id: string; route: string; page_type: PageType; template: string | null; is_in_navigation: boolean; navigation_order: number | null; parent_page_id: string | null; slug: string | null; status: ContentStatus; published_at: string | null; publish_start: string | null; publish_end: string | null; created_at: string; updated_at: string; } interface PageDetailResponse extends PageResponse { localizations: LocalizationResponse[]; og_image_url: string | null; } interface PageSectionResponse { id: string; page_id: string; section_type: SectionType; display_order: number; cover_image_id: string | null; call_to_action_route: string | null; settings: Record | null; } interface SectionLocalizationResponse { id: string; page_section_id: string; locale_id: string; title: string | null; text: string | null; button_text: string | null; /** * Per-locale override of the section's `settings.items` array. `null` * means no override — consumers fall back to the default items in * `PageSectionResponse.settings.items`. */ items: unknown[] | null; } interface NavigationMenuResponse { id: string; site_id: string; slug: string; description: string | null; max_depth: number; is_active: boolean; item_count: number; created_at: string; updated_at: string; localizations: MenuLocalizationResponse[]; } interface MenuLocalizationResponse { id: string; locale_id: string; name: string; } interface NavigationItemResponse { id: string; menu_id: string; parent_id: string | null; page_id: string | null; external_url: string | null; /** Referenced legal document; `null` alongside the other targets marks a * broken link (e.g. after a purge) awaiting repair in the admin. */ legal_document_id: string | null; icon: string | null; display_order: number; open_in_new_tab: boolean; title: string | null; } interface NavigationTree { id: string; parent_id: string | null; page_id: string | null; external_url: string | null; /** Referenced legal document. */ legal_document_id: string | null; icon: string | null; display_order: number; open_in_new_tab: boolean; title: string | null; page_slug: string | null; /** Version-chain root slug of the referenced legal document * (for `/legal/{slug}` URL construction). */ legal_slug: string | null; children: NavigationTree[]; } interface NavigationItemLocalizationResponse { id: string; navigation_item_id: string; locale_id: string; title: string; } /** * A navigation menu with its display name resolved for a requested locale. * * `resolvedName` is the `localizations` entry matching the requested locale * code, or `null` when no locale was requested, the code is not configured * for the site, or the menu has no localization for it. The menu carries no * base display name on the wire — `slug` is its technical identifier — so * consumers pick their own fallback (e.g. `resolvedName ?? menu.slug`). */ interface ResolvedNavigationMenu extends NavigationMenuResponse { resolvedName: string | null; } /** Composed result of `forja.navigation.getMenuWithTree()`. */ interface MenuWithTree { menu: ResolvedNavigationMenu; items: NavigationTree[]; } interface TagResponse { id: string; slug: string; is_global: boolean; created_at: string; } interface CategoryResponse { id: string; parent_id: string | null; slug: string; is_global: boolean; created_at: string; } interface CategoryTree { id: string; slug: string; is_global: boolean; children: CategoryTree[]; } interface CategoryWithCountResponse { id: string; parent_id: string | null; slug: string; is_global: boolean; created_at: string; blog_count: number; } interface TopContentItem { path: string; total_views: number; unique_visitors: number; } interface TrendDataPoint { date: string; total_views: number; unique_visitors: number; } interface AnalyticsReportResponse { total_views: number; total_unique_visitors: number; top_content: TopContentItem[]; trend: TrendDataPoint[]; } interface ReferrerItem { domain: string; views: number; } interface AnalyticsPageDetailResponse { path: string; total_views: number; total_unique_visitors: number; trend: TrendDataPoint[]; referrers: ReferrerItem[]; } interface TrackPageviewRequest { path: string; referrer?: string; } interface TrackPageviewResponse { ok: boolean; } interface AnalyticsReportParams { days?: number; topN?: number; startDate?: string; endDate?: string; } interface AnalyticsPageParams { path: string; days?: number; startDate?: string; endDate?: string; } interface SkillLocalizationResponse { id: string; locale_id: string; name: string; description: string | null; } interface SkillResponse { id: string; name: string; slug: string; category: SkillCategory | null; icon: string | null; proficiency_level: number | null; /** * Per-locale display names. Empty array when no localizations exist * (never null / missing). Clients pick the matching locale and fall * back per their own rules. */ localizations: SkillLocalizationResponse[]; } interface CvEntryLocalizationResponse { id: string; locale_id: string; position: string; description: string | null; achievements: unknown | null; } interface CvEntryResponse { id: string; company: string; company_url: string | null; company_logo_id: string | null; location: string; start_date: string; end_date: string | null; is_current: boolean; entry_type: CvEntryType; display_order: number; created_at: string; updated_at: string; /** * Per-locale position + description. Empty array when no localizations * exist (never null / missing). */ localizations: CvEntryLocalizationResponse[]; /** * Skill IDs linked to this CV entry. Empty array when no skills are * linked (never null / missing). */ skill_ids: string[]; } /** Pagination, search, and locale-resolver params for skill listings. */ interface SkillListParams extends SearchablePaginationParams { /** * Optional locale code (e.g. `"en"`). When set, each skill's * `localizations` array collapses to one entry resolved by the server's * fallback chain. See ADR 0002. */ locale?: string; } /** Options for skill detail lookups (`getSkill`, `getSkillBySlug`). */ interface SkillDetailParams { /** * Optional locale code. When set, `localizations` collapses to one * resolved entry. See ADR 0002. */ locale?: string; } interface CvEntryParams extends SearchablePaginationParams { entryType?: CvEntryType; /** * Optional locale code (e.g. `"en"`, `"de-AT"`). When set, each entry's * `localizations` array collapses to one entry resolved by the server's * fallback chain. Omit to receive every localization. * * See ADR 0002 (`docs/adr/0002-locale-resolver.md`). */ locale?: string; } /** Options for CV-entry detail lookups (`getEntry`). */ interface CvEntryDetailParams { /** * Optional locale code. When set, `localizations` collapses to one * resolved entry. See ADR 0002. */ locale?: string; } interface LegalDocumentResponse { id: string; cookie_name: string; document_type: LegalDocType; status: ContentStatus; slug: string | null; version: number; publish_start: string | null; publish_end: string | null; created_at: string; updated_at: string; } /** * Filters accepted by the legal-document list endpoint, on top of the * shared pagination/search/sort params. Keys are converted to snake_case * query parameters (`excludeStatus` → `exclude_status`). */ interface LegalListParams extends SearchablePaginationParams { /** Only documents with this content status. */ status?: ContentStatus; /** Exclude documents with this status (e.g. `'Archived'`). */ excludeStatus?: ContentStatus; /** * Exclude documents of this type (e.g. `'CookieConsent'`, which has its * own dedicated endpoint and UI surface). */ excludeDocumentType?: LegalDocType; } interface LegalDocLocalizationResponse { id: string; locale_id: string; title: string; intro: string | null; } interface LegalDocumentDetailResponse { id: string; cookie_name: string; document_type: LegalDocType; localizations: LegalDocLocalizationResponse[]; created_at: string; updated_at: string; } /** * Options for legal-document detail lookups (`getBySlug`, `getDetail`). * The list shape (`LegalDocumentResponse`) does not yet carry * `localizations[]` — tracked separately. */ interface LegalDetailParams { /** * Optional locale code (e.g. `"en"`). When set, `localizations[]` * (and `doc_localizations[]` on the full-detail endpoint) collapses * to one resolved entry. See ADR 0002. */ locale?: string; } interface LegalGroupResponse { id: string; cookie_name: string; display_order: number; is_required: boolean; default_enabled: boolean; } interface LegalItemResponse { id: string; cookie_name: string; display_order: number; is_required: boolean; } interface LegalGroupWithItems { id: string; cookie_name: string; display_order: number; is_required: boolean; default_enabled: boolean; items: LegalItemResponse[]; } interface LegalDocumentWithGroups { id: string; cookie_name: string; document_type: LegalDocType; groups: LegalGroupWithItems[]; } /** Custom HTML/JS snippets configured for injection into page templates. */ interface CodeInjection { /** HTML/JS to inject into */ code_injection_head: string; /** HTML/JS to inject before */ code_injection_footer: string; } /** Integration payload of the site context response. */ interface SiteContextIntegration { /** HTML/JS to inject into ; empty string when unconfigured. */ code_injection_head: string; /** HTML/JS to inject before ; empty string when unconfigured. */ code_injection_footer: string; } /** Subset of `GET /sites/{siteId}/context` consumed by the SDK. */ interface SiteContextResponse { integration: SiteContextIntegration; } interface SiteResponse { id: string; name: string; slug: string; description: string | null; logo_url: string | null; favicon_url: string | null; base_url: string | null; theme: Record | null; default_locale_id: string | null; timezone: string; is_active: boolean; created_at: string; updated_at: string; } /** * Curated public subset of the site's settings. * * Readable with any API key tier, including `Read` (Viewer). Deliberately * excludes operational configuration — allowed origins, storage quotas, * data retention, module flags, code injection — which stays on the * Admin-only raw settings endpoint. */ interface PublicSiteSettings { /** Public contact email; empty string when unset. */ contact_email: string; /** Theme color for the web manifest (hex, e.g. `#4a90d9`). */ theme_color: string; /** Background color for the web manifest (hex). */ background_color: string; /** SEO title template (e.g. `{{title}} | {{site_name}}`). */ seo_title_template: string; /** Fallback meta description; empty string when unset. */ seo_default_description: string; /** * Whether the site is in maintenance mode. SSR frontends should swap * the whole site for a maintenance page (HTTP 503) while it's set. * Optional so consumers stay compatible with pre-2.1.1 backends. */ maintenance_mode?: boolean; } interface MediaVariantResponse { id: string; variant_name: string; width: number; height: number; file_size: number; public_url: string | null; } interface MediaResponse { id: string; filename: string; original_filename: string; mime_type: string; file_size: number; storage_provider: string; public_url: string | null; width: number | null; height: number | null; duration: number | null; is_global: boolean; created_at: string; updated_at: string; variants: MediaVariantResponse[]; } interface SocialLinkResponse { id: string; title: string; url: string; icon: string; alt_text: string | null; display_order: number; } /** * Resolved UI strings for one locale: a flat `key → value` dictionary. * * Keys are dot-namespaced lowercase identifiers (e.g. `blog.min_read`, * `nav.aria.toggle_dark`). One value per key, resolved by the server's * fallback chain (requested locale → site default → first available); * keys without any localization are omitted. */ type UiStringsResponse = Record; /** Link type for project resources (repository, demo, docs, etc.). */ type ProjectLinkType = 'repository' | 'demo' | 'documentation' | 'website' | 'other'; /** Localized content for a project. */ interface ProjectLocalizationResponse { id: string; locale_id: string; title: string; short_description: string | null; description: string | null; } /** Project summary for list views. Always carries `localizations` — empty * array when the project has none, never `null` or missing. */ interface ProjectResponse { id: string; slug: string; display_order: number; is_featured: boolean; start_date: string | null; end_date: string | null; is_ongoing: boolean; status: ContentStatus; published_at: string | null; created_at: string; updated_at: string; /** Skill IDs linked to the project. Always present — empty array when none. */ skill_ids: string[]; localizations: ProjectLocalizationResponse[]; } /** External link attached to a project. */ interface ProjectLinkResponse { id: string; label: string; url: string; link_type: ProjectLinkType; icon: string | null; display_order: number; } /** Media attachment on a project (cover image, screenshots). */ interface ProjectMediaResponse { media_id: string; display_order: number; is_cover: boolean; } /** Full project detail. Inherits `skill_ids` and `localizations` from * {@link ProjectResponse}; adds links, media, and relation id sets. */ interface ProjectDetailResponse extends ProjectResponse { links: ProjectLinkResponse[]; media: ProjectMediaResponse[]; cv_entry_ids: string[]; } /** Pagination and filter params for project listings. */ interface ProjectListParams extends PaginationParams { /** Sort field (e.g. `"display_order"`, `"start_date"`). */ sortBy?: string; /** Sort direction. */ sortDir?: 'asc' | 'desc'; /** Filter to featured projects only. */ isFeatured?: boolean; /** * Optional locale code (e.g. `"en"`, `"de-AT"`). When set, each project's * `localizations` array collapses to one entry resolved by the server's * fallback chain (requested → site default → first available). Omit to * receive every localization, which is the existing default. * * See ADR 0002 (`docs/adr/0002-locale-resolver.md`). */ locale?: string; } /** Options for project detail lookups (`get`, `getBySlug`). */ interface ProjectDetailParams { /** * Optional locale code. When set, `localizations` collapses to one entry * resolved by the server's fallback chain. See ADR 0002. */ locale?: string; } /** * The set of HTTP status codes a Forja redirect may use. * * Pinned by issue #743. The same domain is enforced server-side by the * `validate_redirect_status_code` DTO validator and the * `chk_redirect_status_code` DB CHECK constraint, so consumers may rely * on this value without runtime coercion. */ type RedirectStatusCode = 301 | 302 | 307 | 308; /** A URL redirect rule. */ interface RedirectResponse { id: string; site_id: string; source_path: string; destination_path: string; status_code: RedirectStatusCode; is_active: boolean; description: string | null; created_at: string; updated_at: string; } /** * Result of a redirect path lookup. * * Returned with HTTP 200 by `GET /sites/{site_id}/redirects/lookup` on * match. No-match is signalled by **404** (RFC 7807 ProblemDetails) — * never a 200 with a null body, never a `{ redirects: [] }` list. */ interface RedirectLookupResponse { destination_path: string; status_code: RedirectStatusCode; } /** A locale configured for a site. */ interface SiteLocaleResponse { /** Composite-key half #1 — which site this assignment belongs to. */ site_id: string; /** Composite-key half #2 — which locale is attached. */ locale_id: string; /** BCP-47 code, denormalised from `locales.code`. */ code: string; /** English-language label, denormalised from `locales.name`. */ name: string; /** Locale's own name, denormalised from `locales.native_name`. */ native_name: string | null; direction: 'ltr' | 'rtl'; /** Exactly one row per site has this set to `true`. */ is_default: boolean; /** Configured-but-not-served when `false`. Consumers usually filter to `true`. */ is_active: boolean; /** * Path segment selecting this locale. `null` for the default locale * (served at the site root without a prefix). */ url_prefix: string | null; created_at: string; } /** Media item summary for list views (lighter than full MediaResponse). */ interface MediaListItem { id: string; filename: string; original_filename: string; mime_type: string; file_size: number; public_url: string | null; width: number | null; height: number | null; is_global: boolean; folder_id: string | null; created_at: string; } /** Filter params for media listings. */ interface MediaListParams extends SearchablePaginationParams { /** Filter by MIME category (e.g. `"image"`, `"video"`, `"document"`). */ mimeCategory?: string; /** Filter to a specific folder. */ folderId?: string; } /** Full legal document detail including content localizations. */ interface LegalDocumentFullDetailResponse { id: string; content_id: string; cookie_name: string; document_type: LegalDocType; status: ContentStatus; slug: string | null; version: number; parent_version_id: string | null; publish_start: string | null; publish_end: string | null; localizations: LocalizationResponse[]; doc_localizations: LegalDocLocalizationResponse[]; created_at: string; updated_at: string; } /** A version entry in a legal document's version history. */ interface LegalVersionResponse { id: string; version: number; status: ContentStatus; created_at: string; } //#endregion //#region src/http.d.ts /** * Low-level HTTP client interface used by all resource classes. * * Handles authentication (X-API-Key header), JSON serialization, and * error mapping. You don't interact with this directly — use * {@link ForjaClient} instead. */ interface HttpClient { /** Send a GET request. Query params with `undefined` values are omitted. */ get(path: string, params?: Record): Promise; /** * Send a GET request, resolving to `null` when the resource is not found * (HTTP 404 / {@link ForjaNotFoundError}) instead of throwing. Every other * error (auth, validation, server, network) still propagates. * * Centralizes the `404 -> null` discrimination that single-resource lookups * need, so resource methods don't each hand-roll a try/catch. */ getOrNull(path: string, params?: Record): Promise; /** Send a GET request and return the response as plain text (for XML, HTML, etc.). */ getText(path: string, params?: Record): Promise; /** Send a POST request with a JSON body. */ post(path: string, body?: unknown): Promise; /** Send a DELETE request. Resolves on any 2xx; throws on 4xx/5xx. */ delete(path: string): Promise; } /** * A paginated API response enriched with navigation helpers. * * Extends the raw {@link Paginated} response with methods to fetch * subsequent pages or collect all items. * * @typeParam T - The type of items in the paginated collection. * * @example * ```ts * const page1 = await forja.blogs.listPublished({ pageSize: 10 }); * * // Navigate to next page * const page2 = await page1.fetchNext(); * * // Or collect everything * const allBlogs = await page1.fetchAll(); * * // Or iterate page by page * for await (const page of page1) { * console.log(page.data); * } * ``` */ interface PaginatedResult extends Paginated { /** Fetch the next page. Returns `null` if this is the last page. */ fetchNext(): Promise | null>; /** Fetch all remaining pages and return every item as a flat array. */ fetchAll(): Promise; /** Async iterator that yields each page (including the current one). */ [Symbol.asyncIterator](): AsyncIterableIterator>; } //#endregion //#region src/resources/analytics.d.ts /** * Privacy-first analytics operations. * * Track pageviews and retrieve analytics reports. Forja analytics is * cookie-free, GDPR-compliant by design — no PII is stored, visitor * hashes are rotated daily, and raw data is pruned after a configurable retention period. * * Pageview tracking requires an API key with `Read` permission. * Reports require `Read` permission. */ declare class AnalyticsResource { private readonly http; private readonly siteId; constructor(http: HttpClient, siteId: string); /** * Track a pageview. * * **Endpoint:** `POST /sites/{siteId}/analytics/pageview` * * The server computes the visitor hash from the client IP and user agent. * No cookies or PII are stored. * * @param request - The pageview data. * @param request.path - The page path (e.g. `"/blog/hello-world"`). * @param request.referrer - Optional full referrer URL (only the domain is stored). * @returns Acknowledgement (`{ ok: true }`). * * @example * ```ts * await forja.analytics.trackPageview({ path: '/blog/hello-world' }); * ``` */ trackPageview(request: TrackPageviewRequest): Promise; /** * Fetch an analytics summary report for the site. * * **Endpoint:** `GET /sites/{siteId}/analytics/report?days=&top_n=&start_date=&end_date=` * * Returns total views, unique visitors, top content pages, and daily trends. * * @param params - Report parameters. * @param params.days - Number of days to look back (default: 30). Ignored if date range is set. * @param params.topN - Number of top content pages to include (default: 10). * @param params.startDate - Start date in `YYYY-MM-DD` format. * @param params.endDate - End date in `YYYY-MM-DD` format. * @returns The analytics report with totals, top content, and daily trend data. * * @example * ```ts * const report = await forja.analytics.getReport({ days: 7, topN: 5 }); * console.log(`${report.total_views} views, ${report.total_unique_visitors} unique`); * ``` */ getReport(params?: AnalyticsReportParams): Promise; /** * Fetch analytics for a specific page path. * * **Endpoint:** `GET /sites/{siteId}/analytics/report/page?path=&days=&start_date=&end_date=` * * Returns views, unique visitors, daily trend, and top referrer domains for a single page. * * @param params - Page analytics parameters. * @param params.path - The page path to query (e.g. `"/blog/hello-world"`). * @param params.days - Number of days to look back. * @param params.startDate - Start date in `YYYY-MM-DD` format. * @param params.endDate - End date in `YYYY-MM-DD` format. * @returns Page-level analytics with trend and referrer data. * * @example * ```ts * const stats = await forja.analytics.getPageAnalytics({ * path: '/blog/hello-world', * days: 30, * }); * console.log(stats.referrers); // [{ domain: "google.com", views: 42 }, ...] * ``` */ getPageAnalytics(params: AnalyticsPageParams): Promise; } //#endregion //#region src/resources/content-resource.d.ts /** Minimal shape every content list-params object satisfies. */ interface PaginationLike { /** 1-indexed starting page. */ page?: number; } /** Optional locale resolver for detail lookups (ADR 0002). */ interface DetailParams { locale?: string; } /** * Per-entity endpoint configuration for {@link ContentResource}. */ interface ContentResourceConfig { /** Full path of the primary list endpoint, e.g. `/sites/{siteId}/blogs/published`. */ listPath: string; /** Base segment for detail-by-id: `/{detailBase}/{id}/detail` (e.g. `blogs`). */ detailBase: string; } /** * Generic base for the public client SDK's content resources. * * Owns the CRUD-read + pagination scaffold shared verbatim across the entity * resources (`list` / `get` / `getDetail` / `getBySlug`), so each entity class * extends this and adds only its extras (`listFeatured`, `listSimilar`, * `getSections`, …) while keeping its current public method names. Slug lookups * stay per-entity: their paths, step count (blog resolves slug→id→detail), and * return shape (list vs detail) genuinely differ, so they aren't on the base. * * @typeParam TList - The list-item shape returned by the list endpoint. * @typeParam TDetail - The detail shape returned by `/{detailBase}/{id}/detail`. */ declare class ContentResource { protected readonly http: HttpClient; protected readonly siteId: string; protected readonly config: ContentResourceConfig; constructor(http: HttpClient, siteId: string, config: ContentResourceConfig); /** * The shared pagination scaffold: build a `fetchPage` closure over `path` * and wrap the first page in a {@link PaginatedResult}. Subclasses reuse this * for their bespoke list endpoints (category/featured/etc.). */ protected paginate(path: string, params?: P, extraQuery?: Record): Promise>; /** Fetch the primary paginated list (the configured `listPath`). */ list

(params?: P): Promise>; /** * Fetch an entity's full detail by UUID (`/{detailBase}/{id}/detail`). * Returns `null` on 404; every other transport error propagates. */ getDetail(id: string, params?: DetailParams): Promise; } //#endregion //#region src/resources/blogs.d.ts /** * Blog post operations. * * Provides access to published blog content, featured posts, category filtering, * similar post discovery, and individual blog retrieval by ID or slug. * * All read operations require an API key with `Read` permission. */ declare class BlogsResource extends ContentResource { constructor(http: HttpClient, siteId: string); /** * Fetch a paginated list of published blog posts. * * **Endpoint:** `GET /sites/{siteId}/blogs/published?page=&page_size=&locale_id=` * * @param params - Pagination and locale filter options. * @param params.page - 1-indexed page number (default: 1). * @param params.pageSize - Items per page (default: server-side, typically 10). * @param params.localeId - Filter to blogs with content in this locale (UUID). * @returns A paginated result with {@link PaginatedResult.fetchNext | fetchNext()}, * {@link PaginatedResult.fetchAll | fetchAll()}, and async iteration support. * * @example * ```ts * // All locales * const page1 = await forja.blogs.listPublished({ page: 1, pageSize: 10 }); * * // Filtered to a specific locale * const german = await forja.blogs.listPublished({ page: 1, localeId: 'de-locale-uuid' }); * ``` */ listPublished(params?: LocaleFilterParams): Promise>; /** * Fetch published blog posts filtered by category. * * **Endpoint:** `GET /sites/{siteId}/blogs/published/category/{categorySlug}?page=&page_size=&locale_id=` * * @param categorySlug - The category slug to filter by (e.g. `"tech"`, `"travel"`). * @param params - Pagination and locale filter options. * @returns A paginated result of blogs in the given category. * * @example * ```ts * const techBlogs = await forja.blogs.listByCategory('tech', { page: 1, localeId: 'uuid' }); * ``` */ listByCategory(categorySlug: string, params?: LocaleFilterParams): Promise>; /** * Fetch featured blog posts. * * **Endpoint:** `GET /sites/{siteId}/blogs/featured?limit=` * * @param opts.limit - Maximum number of featured posts to return (default: server-side). * @returns Array of blog detail responses (includes localizations, categories, documents). * * @example * ```ts * const featured = await forja.blogs.listFeatured({ limit: 3 }); * ``` */ listFeatured(opts?: { limit?: number; }): Promise; /** * Fetch blog posts similar to a given blog (by shared categories/tags). * * **Endpoint:** `GET /sites/{siteId}/blogs/{blogId}/similar?limit=` * * @param blogId - The UUID of the blog to find similar content for. * @param opts.limit - Maximum number of similar posts (default: server-side, typically 3). * @returns Array of similar blog list items. * * @example * ```ts * const similar = await forja.blogs.listSimilar('blog-uuid', { limit: 3 }); * ``` */ listSimilar(blogId: string, opts?: { limit?: number; }): Promise; /** * Fetch a blog post's full detail by its URL slug. * * Performs a two-step lookup: * 1. **Endpoint:** `GET /sites/{siteId}/blogs/by-slug/{slug}` — resolves slug to ID. * 2. **Endpoint:** `GET /blogs/{id}/detail` — fetches the full detail response. * * @param slug - The blog's URL slug (e.g. `"my-first-post"`). * @returns The full blog detail (with localizations, categories, documents), or `null` if not found. * * @example * ```ts * const blog = await forja.blogs.getBySlug('my-first-post'); * if (blog) { * console.log(blog.localizations[0]?.title); * } * ``` */ getBySlug(slug: string, params?: BlogDetailParams): Promise; /** * Fetch a blog post's full detail by its UUID. * * **Endpoint:** `GET /blogs/{id}/detail` * * @param idOrSlug - The blog's UUID. * @returns The full blog detail (with localizations, categories, documents), or `null` if not found. * * @example * ```ts * const blog = await forja.blogs.get('550e8400-e29b-41d4-a716-446655440000'); * ``` */ get(idOrSlug: string, params?: BlogDetailParams): Promise; /** * Fetch the site's RSS feed as raw XML. * * **Endpoint:** `GET /sites/{siteId}/feed.rss` * * @returns RSS 2.0 XML string. */ rss(): Promise; } //#endregion //#region src/resources/collections.d.ts /** * A published custom-type ("Collection") entry as served on the public * Consumer API. PII fields are stripped server-side; `data` merges the shared * and localized values with the title under the designated title field key. */ interface PublicCollectionEntry { slug: string | null; status: string; published_at: string | null; locale: string | null; data: Record; } /** A single field in a public collection schema (no PII / compliance metadata). */ interface PublicCollectionField { key: string; label: string; field_type: string; localized: boolean; /** The designated title field — renderers use it as the heading. */ is_title: boolean; enum_options?: unknown; } /** A collection's public field schema, for generic renderers. */ interface PublicCollectionSchema { key: string; name: string; content_kind: string; fields: PublicCollectionField[]; } interface PublicCollectionListParams { locale?: string; page?: number; pageSize?: number; } /** * Read operations for one custom type ("Collection"), parameterised by its * key. Obtain via {@link ForjaClient.collections | `forja.collections(key)`}. * One generic resource serves every type — no per-type codegen. * * Only types flagged publicly-readable are served; everything else surfaces * as `null` (by-slug / schema) or a 404 (`bySlug`/`schema` return `null`). */ declare class CollectionsResource { private readonly http; private readonly siteId; private readonly typeKey; constructor(http: HttpClient, siteId: string, typeKey: string); private base; /** * Paginated list of published entries. * * **Endpoint:** `GET /sites/{siteId}/collections/{typeKey}/published` */ published(params?: PublicCollectionListParams): Promise>; /** * A single published entry by slug, or `null` if not found / not public. * * **Endpoint:** `GET /sites/{siteId}/collections/{typeKey}/by-slug/{slug}` */ bySlug(slug: string, params?: { locale?: string; }): Promise; /** * The collection's public field schema, or `null` if not public. * * **Endpoint:** `GET /sites/{siteId}/collections/{typeKey}/schema` */ schema(): Promise; } //#endregion //#region src/resources/cv.d.ts /** * CV / resume operations. * * Provides access to skills (technologies, languages, tools) and CV entries * (work experience, education, certifications, projects). * * All operations require an API key with `Read` permission. */ declare class CvResource extends ContentResource { constructor(http: HttpClient, siteId: string); /** * Fetch a paginated list of skills. * * **Endpoint:** `GET /sites/{siteId}/skills?page=&page_size=&search=&sort_by=&sort_dir=` * * @param params - Pagination, search, and sort options. * @returns A paginated result of skills (name, slug, category, proficiency level). * * @example * ```ts * const skills = await forja.cv.listSkills({ page: 1, pageSize: 50 }); * const programming = skills.data.filter(s => s.category === 'Programming'); * ``` */ listSkills(params?: SkillListParams): Promise>; /** * Fetch a skill by its UUID. * * **Endpoint:** `GET /skills/{id}` * * @param id - The skill's UUID. * @returns The skill, or `null` if not found. */ getSkill(id: string, params?: SkillDetailParams): Promise; /** * Fetch a skill by its URL slug. * * **Endpoint:** `GET /skills/by-slug/{slug}` * * @param slug - The skill's slug (e.g. `"typescript"`, `"rust"`). * @returns The skill, or `null` if not found. * * @example * ```ts * const ts = await forja.cv.getSkillBySlug('typescript'); * if (ts) console.log(`${ts.name}: level ${ts.proficiency_level}`); * ``` */ getSkillBySlug(slug: string, params?: SkillDetailParams): Promise; /** * Fetch a paginated list of CV entries (work, education, certifications, etc.). * * **Endpoint:** `GET /sites/{siteId}/cv?entry_type=&page=&page_size=&search=&sort_by=&sort_dir=` * * @param params - Pagination and filter options. * @param params.entryType - Filter by entry type: `"Work"`, `"Education"`, `"Volunteer"`, `"Certification"`, or `"Project"`. * @returns A paginated result of CV entries. * * @example * ```ts * const work = await forja.cv.listEntries({ entryType: 'Work' }); * const education = await forja.cv.listEntries({ entryType: 'Education' }); * ``` */ listEntries(params?: CvEntryParams): Promise>; /** * Fetch a CV entry by its UUID. * * **Endpoint:** `GET /cv/{id}` * * @param id - The CV entry's UUID. * @param params - Optional locale resolver per ADR 0002. * @returns The CV entry, or `null` if not found. * * @example * ```ts * // All localizations (default) * const entry = await forja.cv.getEntry('entry-uuid'); * * // Single-locale collapse * const enEntry = await forja.cv.getEntry('entry-uuid', { locale: 'en' }); * ``` */ getEntry(id: string, params?: CvEntryDetailParams): Promise; /** * Fetch a CV entry's full detail by its UUID. * * **Endpoint:** `GET /cv/{id}/detail` * * For CV entries the detail shape is identical to the lightweight * {@link getEntry} shape — the relational graph is bounded, so the * `/detail` route exists purely for uniformity across content types * (ADR 0003). * * @param id - The CV entry's UUID. * @param params - Optional locale resolver per ADR 0002. * @returns The CV entry detail, or `null` if not found. */ getEntryDetail(id: string, params?: CvEntryDetailParams): Promise; } //#endregion //#region src/resources/forms.d.ts type FormFieldType = 'text' | 'textarea' | 'email' | 'number' | 'select' | 'checkbox' | 'radio' | 'date' | 'custom'; type FormBotProtection = 'none' | 'mandatory'; /** * Field-level validation rules. All optional — the form builder picks * the rules that apply to the field's type. */ interface FormFieldValidation { required?: boolean; min_length?: number; max_length?: number; min?: number; max?: number; /** ECMAScript regex pattern. Server uses Rust's `regex` crate, which * supports most ECMAScript syntax but not lookbehind/lookahead. */ pattern?: string; } interface FormFieldDefinition { id: string; /** Technical key used as the submission JSONB key. Never localized. */ label: string; /** Visitor-facing label. Set by the public endpoint when ?locale= matches a * localization; defaults to `label` when no translation applies. */ display_label?: string; field_type: FormFieldType; placeholder?: string | null; help_text?: string | null; validation: FormFieldValidation; options?: unknown; is_required: boolean; display_order: number; } interface PublicFormDefinition { id: string; site_id: string; name: string; slug: string; description?: string | null; consent_required: boolean; consent_text?: string | null; bot_protection: FormBotProtection; fields: FormFieldDefinition[]; } /** * A self-hosted ALTCHA challenge (#770), issued per form when the site uses * ALTCHA bot protection. Passed verbatim to the `` via its * `challenge` attribute (altcha v3; a JSON string is treated as inline * challenge data, a URL as an endpoint to fetch from). The widget solves it * client-side; the solved payload is submitted as * {@link SubmitFormOptions.botProtectionToken}. */ interface AltchaChallenge { algorithm: string; challenge: string; maxnumber: number; salt: string; signature: string; } interface FormSubmitData { [fieldLabel: string]: unknown; } interface FormSubmitResponse { submission_id: string; reference_code: string; } interface SubmitFormOptions { consentGiven?: boolean; botProtectionToken?: string; } interface SelfServiceLookup { status: string; created_at: string; } interface SelfServiceSubmission { reference_code: string; status: string; data: FormSubmitData; consent_given: boolean; consent_text_at_submission?: string | null; created_at: string; } /** * Client-side validation error map — keyed by field label, mirroring the * server's field-keyed error shape. */ type ValidationErrorMap = Record; /** * Forms module client (#586). Unauthenticated public endpoints; the site * is resolved by the `X-Site-Domain` header set on the `ForjaClient` * config — make sure `siteDomain` is set before calling these. */ declare class FormsResource { private readonly http; constructor(http: HttpClient); /** Fetch a form definition by slug for rendering. */ /** * Fetch a form definition for rendering. Pass `locale` (code or UUID) to * receive localized text in `name` / `description` / `consent_text` and * each field's `display_label` / `placeholder` / `help_text`. Unknown * locales fall through to the form's canonical/default-locale values. */ getForm(slug: string, opts?: { locale?: string; }): Promise; /** * Fetch a fresh self-hosted ALTCHA challenge for a form (#770). Call this * only when {@link PublicFormDefinition.bot_protection} is `'mandatory'` * and the site uses ALTCHA mode; the server returns 409 otherwise. Each * call yields a fresh, single-use challenge. */ getAltchaChallenge(slug: string): Promise; /** * Submit a form. Throws a {@link ForjaValidationError} if the server * rejects the payload with field-level errors. */ submitForm(slug: string, data: FormSubmitData, opts?: SubmitFormOptions): Promise; /** Privacy-preserving lookup: status + submitted-at only. */ lookupSubmission(referenceCode: string): Promise; /** Visitor's own full view of their submission. */ getSubmission(referenceCode: string): Promise; /** Idempotent self-service delete. Throws on 404/410. */ deleteSubmission(referenceCode: string): Promise; } /** * Validate a submission payload against a form definition. Mirrors the * server-side rules in {@link `models/form_submission.rs:validate_submission`}. * Returns an empty object on success; otherwise a `{ label: message }` map. * * Template authors call this before {@link FormsResource.submitForm} so * visitors get inline feedback without a round-trip — but the server * always re-validates, so this is purely a UX accelerator. */ declare function validateSubmission(form: PublicFormDefinition, data: FormSubmitData): ValidationErrorMap; //#endregion //#region src/resources/legal.d.ts /** * Legal document operations. * * Provides access to legal documents (privacy policy, terms of service, cookie consent, etc.), * their consent groups, and individual consent items for building cookie banners and legal pages. * * All operations require an API key with `Read` permission. */ declare class LegalResource extends ContentResource { constructor(http: HttpClient, siteId: string); /** * Fetch a paginated list of legal documents for the site. * * **Endpoint:** `GET /sites/{siteId}/legal?page=&page_size=&search=&sort_by=&sort_dir=&status=&exclude_status=&exclude_document_type=` * * @param params - Pagination, search, sort, and status/type filter options. * @returns A paginated result of legal document summaries. * * @example * ```ts * const docs = await forja.legal.list(); * // Only published documents, without the cookie-consent doc: * const published = await forja.legal.list({ * status: 'Published', * excludeDocumentType: 'CookieConsent', * }); * ``` */ list(params?: LegalListParams): Promise>; /** * Fetch a legal document by its UUID, including localizations. * * **Endpoint:** `GET /legal/{id}` * * @param id - The legal document's UUID. * @returns The document with all localizations, or `null` if not found. */ get(id: string): Promise; /** * Fetch a legal document by its URL slug. * * **Endpoint:** `GET /sites/{siteId}/legal/by-slug/{slug}` * * @param slug - The document slug (e.g. `"privacy-policy"`, `"terms-of-service"`). * @returns The document with all localizations, or `null` if not found. * * @example * ```ts * const privacy = await forja.legal.getBySlug('privacy-policy'); * if (privacy) { * const enLocale = privacy.localizations.find(l => l.locale_id === enLocaleId); * } * ``` */ getBySlug(slug: string, params?: LegalDetailParams): Promise; /** * Fetch the cookie consent document with its consent groups and items. * * **Endpoint:** `GET /sites/{siteId}/legal/cookie-consent` * * Returns a structured document with groups (e.g. "Essential", "Analytics", "Marketing") * and their items, including `is_required` and `default_enabled` flags for building * GDPR-compliant cookie consent banners. * * @returns The cookie consent document with groups and items, or `null` if not configured. * * @example * ```ts * const consent = await forja.legal.getCookieConsent(); * if (consent) { * consent.groups.forEach(group => { * console.log(group.cookie_name, group.is_required ? '(required)' : '(optional)'); * }); * } * ``` */ getCookieConsent(): Promise; /** * Fetch consent groups (with their items) for a legal document. * * **Endpoint:** `GET /legal/{documentId}/groups` * * @param documentId - The legal document's UUID. * @returns Array of consent groups, each containing their items. */ getGroups(documentId: string): Promise; /** * Fetch consent items within a specific group. * * **Endpoint:** `GET /legal/groups/{groupId}/items` * * @param groupId - The consent group's UUID. * @returns Array of consent items (cookie name, required flag, display order). */ getGroupItems(groupId: string): Promise; /** * Fetch the version history of a legal document. * * **Endpoint:** `GET /legal/{id}/versions` * * Returns all versions of the document, ordered by version number. * Each entry includes the version number, content status, and creation date. * Use for displaying a "Version history" section on legal pages. * * @param id - The legal document's UUID. * @returns Array of version entries, newest first. * * @example * ```ts * const versions = await forja.legal.listVersions('doc-uuid'); * versions.forEach(v => { * console.log(`v${v.version} — ${v.status} — ${v.created_at}`); * }); * ``` */ listVersions(id: string): Promise; } //#endregion //#region src/resources/media.d.ts /** * Media asset operations. * * Provides access to media assets (images, videos, documents) stored in the CMS. * Each media item includes its public URL, dimensions, file metadata, and * responsive variants (thumbnails, different sizes). * * Requires an API key with `Read` permission. */ declare class MediaResource { private readonly http; private readonly siteId; constructor(http: HttpClient, siteId: string); /** * Fetch a paginated list of media assets for the site. * * **Endpoint:** `GET /sites/{siteId}/media?page=&page_size=&search=&sort_by=&sort_dir=&mime_category=&folder_id=` * * Returns a lightweight list without variants. Use {@link get} to fetch full * detail with responsive variants for a specific asset. * * @param params - Pagination, search, sort, and filter options. * @param params.search - Search by filename. * @param params.mimeCategory - Filter by MIME category: `"image"`, `"video"`, `"document"`, `"audio"`. * @param params.folderId - Filter to a specific media folder (UUID). * @returns A paginated result of media list items. * * @example * ```ts * // Browse all images * const images = await forja.media.list({ mimeCategory: 'image', pageSize: 20 }); * * // Search by filename * const results = await forja.media.list({ search: 'hero-banner' }); * * // Browse a specific folder * const folder = await forja.media.list({ folderId: 'folder-uuid' }); * ``` */ list(params?: MediaListParams): Promise>; /** * Fetch a media asset by its UUID. * * **Endpoint:** `GET /media/{id}` * * Returns the full media metadata including filename, MIME type, dimensions, * public URL, and all responsive variants (thumbnails, different sizes). * * @param id - The media asset's UUID. * @returns The media asset with variants, or `null` if not found. * * @example * ```ts * const cover = await forja.media.get(blog.cover_image_id); * if (cover) { * console.log(cover.public_url); // original image URL * console.log(cover.variants); // [{ variant_name, width, height, public_url }, ...] * console.log(cover.mime_type); // "image/jpeg" * } * ``` */ get(id: string): Promise; } //#endregion //#region src/resources/site.d.ts /** * Site configuration operations. * * Provides access to the site's metadata (name, slug, timezone, etc.) * and configured locales (languages available for content). * * Requires an API key with `Read` permission. */ declare class SiteResource { private readonly http; private readonly siteId; constructor(http: HttpClient, siteId: string); /** * Fetch the site's configuration. * * **Endpoint:** `GET /sites/{siteId}` * * Returns the site's name, slug, description, logo/favicon URLs, timezone, * theme settings, and default locale. Useful for rendering site-wide UI * elements (header, footer, SEO defaults). * * @returns The site configuration. * * @example * ```ts * const site = await forja.site.get(); * console.log(site.name); // "My Website" * console.log(site.timezone); // "Europe/Vienna" * console.log(site.favicon_url); // "https://cdn.example.com/favicon.ico" * ``` */ get(): Promise; /** * Fetch all locales configured for the site. * * **Endpoint:** `GET /sites/{siteId}/locales` * * Returns the languages available for content on this site, including * the default locale, locale codes, text direction (LTR/RTL), and * active status. Use to build language switchers and determine which * locale to pass to other API calls (e.g. blog listing with `localeId`). * * @returns Array of site locales, with the default locale marked via `is_default`. * * @example * ```ts * const locales = await forja.site.listLocales(); * const defaultLocale = locales.find(l => l.is_default); * console.log(defaultLocale?.code); // "en" * * // Build a language switcher * locales * .filter(l => l.is_active) * .forEach(l => console.log(`${l.name} (${l.code})`)); * ``` */ listLocales(): Promise; /** * Fetch the curated public subset of the site's settings. * * **Endpoint:** `GET /sites/{siteId}/settings/public` * * Returns the contact email, web-manifest colors, and SEO defaults. * Works with an API key that has `Read` permission — unlike the raw * settings endpoint, which is Admin-only. * * The shape deliberately excludes operational configuration (allowed * origins, storage quotas, data retention, module flags, code * injection); use {@link getCodeInjection} for injection snippets. * * Fields that are not configured on the server fall back to the same * defaults the backend uses everywhere (empty strings for email and * description, `#ffffff` colors, `{{title}} | {{site_name}}`). * * @returns The public site settings. * * @example * ```ts * const settings = await forja.site.getSettings(); * console.log(settings.contact_email); // "hello@example.com" * console.log(settings.theme_color); // "#4a90d9" * console.log(settings.seo_title_template); // "{{title}} | {{site_name}}" * ``` */ getSettings(): Promise; /** * Get code injection scripts configured for this site. * * **Endpoint:** `GET /sites/{siteId}/context` * * Returns the custom HTML/JS snippets configured for injection into * the site's `` and footer, read from the site context's * integration payload. Use these to embed analytics tags, chat * widgets, or any custom scripts into your template. * * Fields that are not configured on the server default to empty strings. * * Requires an API key with `Read` permission. * * @returns The code injection head and footer snippets. * * @example * ```ts * const injection = await forja.site.getCodeInjection(); * // injection.code_injection_head → "" * // injection.code_injection_footer → "" * ``` */ getCodeInjection(): Promise; } //#endregion //#region src/resources/navigation.d.ts /** * Navigation menu operations. * * Provides access to navigation menus, their hierarchical tree structure, * and individual menu items. Used to render site headers, footers, and sidebars. * * All operations require an API key with `Read` permission. */ declare class NavigationResource { private readonly http; private readonly siteId; private readonly site; /** Memoized site-locales listing, shared by every `getMenuWithTree` call * on this client instance. Cleared on failure so a later call can retry. */ private localesPromise?; constructor(http: HttpClient, siteId: string, site?: SiteResource); /** * Fetch all navigation menus for the site. * * **Endpoint:** `GET /sites/{siteId}/menus` * * @returns Array of menu metadata (slug, description, depth, item count). * * @example * ```ts * const menus = await forja.navigation.listMenus(); * const primary = menus.find(m => m.slug === 'primary'); * ``` */ listMenus(): Promise; /** * Fetch a navigation menu by its UUID. * * **Endpoint:** `GET /menus/{menuId}` * * @param menuId - The menu's UUID. * @returns Menu metadata, or `null` if not found. */ getMenu(menuId: string): Promise; /** * Fetch a navigation menu by its slug. * * **Endpoint:** `GET /sites/{siteId}/menus/slug/{slug}` * * @param slug - The menu's slug (e.g. `"primary"`, `"footer"`). * @returns Menu metadata, or `null` if not found. * * @example * ```ts * const primary = await forja.navigation.getMenuBySlug('primary'); * if (primary) { * const tree = await forja.navigation.getTree(primary.id); * } * ``` */ getMenuBySlug(slug: string): Promise; /** * Fetch the hierarchical navigation tree for a menu. * * **Endpoint:** `GET /menus/{menuId}/tree?locale=` * * Returns a recursive tree of navigation items with children, page slugs, * external URLs, and icons. Optionally filtered by locale for multi-language sites. * * @param menuId - The menu's UUID. * @param opts.locale - Optional locale code (e.g. `"en"`, `"de"`) to filter localized titles. * @returns Array of root-level navigation tree nodes (each may have nested children). * * @example * ```ts * const tree = await forja.navigation.getTree('menu-uuid', { locale: 'de' }); * tree.forEach(node => { * console.log(node.title, node.page_slug, node.children.length); * }); * ``` */ getTree(menuId: string, opts?: { locale?: string; }): Promise; /** * Fetch a menu and its navigation tree in one call, with the menu's * display name resolved for a locale. * * Composes `getMenuBySlug` and `getTree`. The menu fetch and the locale * lookup run concurrently; the tree request starts once both are known, * because `opts.locale` is first resolved against the site's configured * locales — a code the site doesn't configure is not forwarded to * `getTree` (the tree endpoint rejects unknown codes), so the tree is * fetched locale-less instead. The locale code → id mapping comes from the * site-locales listing, fetched once per client instance and reused across * calls. * * `menu.resolvedName` is the localization matching `opts.locale`, or `null` * when no locale is passed, the code isn't configured for the site, or the * menu has no localization for it — fall back to your own default (e.g. * `resolvedName ?? menu.slug`). * * @param slug - The menu's slug (e.g. `"primary"`, `"footer"`). * @param opts.locale - Optional locale code (e.g. `"de"`) used to resolve * the menu name and localize item titles. * @returns `{ menu, items }`, or `null` if no menu has that slug. * * @example * ```ts * const footer = await forja.navigation.getMenuWithTree('footer', { locale: 'de' }); * if (footer) { * console.log(footer.menu.resolvedName ?? footer.menu.slug); * footer.items.forEach(node => console.log(node.title)); * } * ``` */ getMenuWithTree(slug: string, opts?: { locale?: string; }): Promise; /** * Fetch all flat (non-hierarchical) items in a menu. * * **Endpoint:** `GET /menus/{menuId}/items` * * @param menuId - The menu's UUID. * @returns Array of navigation items (use `parent_id` to reconstruct hierarchy if needed). */ listItems(menuId: string): Promise; /** * Fetch a single navigation item by its UUID. * * **Endpoint:** `GET /navigation/{itemId}` * * @param itemId - The navigation item's UUID. * @returns The navigation item, or `null` if not found. */ getItem(itemId: string): Promise; private listLocalesCached; private localeIdForCode; } //#endregion //#region src/resources/pages.d.ts /** * Extended pagination params for page listings. * Supports filtering by status, page type, and exclusion. */ interface PageListParams extends SearchablePaginationParams { /** Filter by content status (e.g. `"Published"`, `"Draft"`). */ status?: string; /** Filter by page type (e.g. `"Static"`, `"Landing"`, `"Contact"`). */ pageType?: string; /** Exclude pages with this status. */ excludeStatus?: string; } /** * CMS page operations. * * Provides access to page listings, route-based lookup, page sections, * and section localizations for multi-language content rendering. * * All operations require an API key with `Read` permission. */ declare class PagesResource extends ContentResource { constructor(http: HttpClient, siteId: string); /** * Fetch a paginated list of CMS pages. * * **Endpoint:** `GET /sites/{siteId}/pages?page=&page_size=&search=&status=&page_type=&sort_by=&sort_dir=&exclude_status=` * * @param params - Pagination, search, and filter options. * @returns A paginated result of page list items. * * @example * ```ts * const pages = await forja.pages.list({ page: 1, pageSize: 100 }); * const allPages = await pages.fetchAll(); * ``` */ list(params?: PageListParams): Promise>; /** * Fetch a page by its URL route path. * * **Endpoint:** `GET /sites/{siteId}/pages/by-route/{route}` * * Leading slashes are stripped automatically (`"/about"` and `"about"` both work). * * @param route - The page's route path (e.g. `"/about"` or `"contact"`). * @returns The page detail with localizations and OG image, or `null` if not found. * * @example * ```ts * const aboutPage = await forja.pages.getByRoute('/about'); * if (aboutPage) { * console.log(aboutPage.localizations[0]?.title); * } * ``` */ getByRoute(route: string): Promise; /** * Fetch all sections for a page. * * **Endpoint:** `GET /pages/{pageId}/sections?locale=` * * Sections are returned in display order and include type, settings, * cover image reference, and call-to-action route. When `locale` is set, * each section's `settings.items` carries that locale's items override * when one exists; sections without an override keep the default items * (ADR 0002 — omitting the param returns the defaults unchanged). * * @param pageId - The page's UUID. * @param params - Optional locale resolver (`{ locale: 'en' }`). * @returns Array of page sections. */ getSections(pageId: string, params?: DetailParams): Promise; /** * Fetch localizations for a single section. * * **Endpoint:** `GET /pages/sections/{sectionId}/localizations` * * @param sectionId - The section's UUID. * @returns Array of localized content (title, text, button text) per locale. */ getSectionLocalizations(sectionId: string): Promise; /** * Fetch localizations for all sections of a page in a single request. * * **Endpoint:** `GET /pages/{pageId}/sections/localizations` * * More efficient than calling {@link getSectionLocalizations} per section. * * @param pageId - The page's UUID. * @returns Array of all section localizations for the page. */ getPageSectionLocalizations(pageId: string): Promise; } //#endregion //#region src/resources/projects.d.ts /** * Portfolio project operations. * * Provides access to published projects with localizations, links, media * attachments, and skill/CV associations. Use for portfolio pages, project * showcases, and case studies. * * All read operations require an API key with `Read` permission. */ declare class ProjectsResource extends ContentResource { constructor(http: HttpClient, siteId: string); /** * Fetch a paginated list of published projects. * * **Endpoint:** `GET /sites/{siteId}/projects/public?page=&page_size=&sort_by=&sort_dir=&is_featured=` * * Returns projects with `published` or `scheduled` status that are within * their publish window. Results can be sorted and filtered to featured only. * * @param params - Pagination, sort, and filter options. * @param params.page - 1-indexed page number (default: 1). * @param params.pageSize - Items per page, 1–100 (default: 10). * @param params.sortBy - Sort field (e.g. `"display_order"`, `"start_date"`, `"created_at"`). * @param params.sortDir - Sort direction: `"asc"` or `"desc"`. * @param params.isFeatured - When `true`, returns only featured projects. * @returns A paginated result with {@link PaginatedResult.fetchNext | fetchNext()}, * {@link PaginatedResult.fetchAll | fetchAll()}, and async iteration support. * * @example * ```ts * // All published projects * const projects = await forja.projects.listPublished({ page: 1, pageSize: 12 }); * * // Featured projects only, sorted by display order * const featured = await forja.projects.listPublished({ * isFeatured: true, * sortBy: 'display_order', * sortDir: 'asc', * }); * ``` */ listPublished(params?: ProjectListParams): Promise>; /** * Fetch a project (lightweight list shape) by its UUID. * * **Endpoint:** `GET /projects/{id}` * * Returns the list-shape project: scalar fields, `skill_ids`, and * `localizations` (title, description per locale). The relational graph * held off the list — `links`, `media`, `cv_entry_ids` — is available * via {@link getDetail} (ADR 0001 / ADR 0003). * * @param id - The project's UUID. * @param params - Optional locale resolver per ADR 0002. * @returns The project (lightweight), or `null` if not found. * * @example * ```ts * const project = await forja.projects.get('project-uuid'); * if (project) { * const loc = project.localizations.find(l => l.locale_id === localeId); * console.log(loc?.title, loc?.short_description); * } * ``` */ get(id: string, params?: ProjectDetailParams): Promise; /** * Fetch a project by its URL slug. * * **Endpoint:** `GET /sites/{siteId}/projects/by-slug/{slug}` * * @param slug - The project's URL slug (e.g. `"forja-cms"`, `"my-portfolio"`). * @returns The project summary, or `null` if not found. * * @example * ```ts * const project = await forja.projects.getBySlug('forja-cms'); * ``` */ getBySlug(slug: string, params?: ProjectDetailParams): Promise; } //#endregion //#region src/resources/redirects.d.ts /** * URL redirect operations. * * Provides server-side redirect lookup for SSR frameworks. When a request * arrives at a path that has a redirect configured, use {@link lookup} to * check if the path should be redirected and to which destination. * * Requires an API key with `Read` permission. */ declare class RedirectsResource { private readonly http; private readonly siteId; constructor(http: HttpClient, siteId: string); /** * Look up a redirect for a given request path. * * **Endpoint:** `GET /sites/{siteId}/redirects/lookup?path=` * * Checks if the site has an active redirect configured for the given path. * Returns the destination URL and HTTP status code (301 permanent, 302 temporary, * 307 temporary preserve method, 308 permanent preserve method). * * Use this in SSR middleware to handle redirects before rendering the page. * * @param path - The request path to check (e.g. `"/old-blog-post"`, `"/legacy/page"`). * @returns The redirect destination and status code, or `null` if no redirect exists for this path. * * @example * ```ts * // In Astro middleware or Next.js middleware: * const redirect = await forja.redirects.lookup('/old-url'); * if (redirect) { * return Response.redirect(redirect.destination_path, redirect.status_code); * } * ``` * * @example * ```ts * // In Express: * app.use(async (req, res, next) => { * const redirect = await forja.redirects.lookup(req.path); * if (redirect) { * return res.redirect(redirect.status_code, redirect.destination_path); * } * next(); * }); * ``` */ lookup(path: string): Promise; } //#endregion //#region src/resources/social.d.ts /** * Social media link operations. * * Provides access to the site's configured social media links * (GitHub, Twitter/X, LinkedIn, etc.) for rendering in headers, footers, * and about pages. * * Requires an API key with `Read` permission. */ declare class SocialResource { private readonly http; private readonly siteId; constructor(http: HttpClient, siteId: string); /** * Fetch all social media links for the site. * * **Endpoint:** `GET /sites/{siteId}/social` * * Returns links sorted by `display_order`. Each link includes a title, * URL, icon identifier, and optional alt text for accessibility. * * @returns Array of social links. * * @example * ```ts * const links = await forja.social.list(); * links.forEach(link => { * console.log(`${link.title}: ${link.url} (icon: ${link.icon})`); * }); * ``` */ list(): Promise; } //#endregion //#region src/resources/taxonomy.d.ts /** * Taxonomy operations (tags and categories). * * Tags and categories are used to organize blog posts and other content. * Categories support hierarchy (parent/child), while tags are flat labels. * * All operations require an API key with `Read` permission. */ declare class TaxonomyResource { private readonly http; private readonly siteId; constructor(http: HttpClient, siteId: string); /** * Fetch a paginated list of tags for the site. * * **Endpoint:** `GET /sites/{siteId}/tags?page=&page_size=&search=&sort_by=&sort_dir=` * * @param params - Pagination, search, and sort options. * @returns A paginated result of tags. * * @example * ```ts * const tags = await forja.taxonomy.listTags({ search: 'rust', sortBy: 'slug' }); * ``` */ listTags(params?: SearchablePaginationParams): Promise>; /** * Fetch a paginated list of categories for the site. * * **Endpoint:** `GET /sites/{siteId}/categories?page=&page_size=&search=&sort_by=&sort_dir=` * * @param params - Pagination, search, and sort options. * @returns A paginated result of categories. * * @example * ```ts * const categories = await forja.taxonomy.listCategories({ page: 1, pageSize: 50 }); * ``` */ listCategories(params?: SearchablePaginationParams): Promise>; /** * Fetch all categories with their associated blog post counts. * * **Endpoint:** `GET /sites/{siteId}/categories/blog-counts` * * Useful for rendering category sidebars with post counts. * * @returns Array of categories, each with a `blog_count` field. * * @example * ```ts * const categories = await forja.taxonomy.getCategoriesWithBlogCounts(); * categories.forEach(c => console.log(`${c.slug}: ${c.blog_count} posts`)); * ``` */ getCategoriesWithBlogCounts(): Promise; /** * Fetch all tags associated with a specific content item (blog, page, etc.). * * **Endpoint:** `GET /content/{contentId}/tags` * * @param contentId - The content item's UUID (the `content_id` field, not the blog/page ID). * @returns Array of tags assigned to the content. */ getContentTags(contentId: string): Promise; /** * Fetch all categories associated with a specific content item. * * **Endpoint:** `GET /content/{contentId}/categories` * * @param contentId - The content item's UUID (the `content_id` field, not the blog/page ID). * @returns Array of categories assigned to the content. */ getContentCategories(contentId: string): Promise; /** * Fetch a single tag by its UUID. * * **Endpoint:** `GET /tags/{id}` * * @param id - The tag's UUID. * @returns The tag, or `null` if not found. * * @example * ```ts * const tag = await forja.taxonomy.getTag('tag-uuid'); * ``` */ getTag(id: string): Promise; /** * Fetch a single tag by its URL slug. * * **Endpoint:** `GET /tags/by-slug/{slug}` * * @param slug - The tag's URL slug (e.g. `"typescript"`, `"web-components"`). * @returns The tag, or `null` if not found. * * @example * ```ts * const tag = await forja.taxonomy.getTagBySlug('typescript'); * if (tag) { * console.log(tag.name, tag.slug); * } * ``` */ getTagBySlug(slug: string): Promise; /** * Fetch a single category by its UUID. * * **Endpoint:** `GET /categories/{id}` * * @param id - The category's UUID. * @returns The category, or `null` if not found. * * @example * ```ts * const category = await forja.taxonomy.getCategory('cat-uuid'); * ``` */ getCategory(id: string): Promise; /** * Fetch child categories of a parent category. * * **Endpoint:** `GET /categories/{parentId}/children` * * Use to build hierarchical category trees (e.g. nested navigation menus, * category sidebars with subcategories). * * @param parentId - The parent category's UUID. * @returns Array of child categories. * * @example * ```ts * const subcategories = await forja.taxonomy.getCategoryChildren('parent-uuid'); * subcategories.forEach(child => console.log(child.slug)); * ``` */ getCategoryChildren(parentId: string): Promise; } //#endregion //#region src/client.d.ts /** * The main entry point for the Forja CMS content SDK. * * Create an instance with your API credentials and use the resource * properties to interact with the Forja content API. * * @example * ```ts * import { ForjaClient } from '@forjacms/client'; * * const forja = new ForjaClient({ * baseUrl: 'https://cms.example.com/api/v1', * apiKey: 'your-read-api-key', * siteId: 'your-site-uuid', * }); * * const blogs = await forja.blogs.listPublished({ page: 1 }); * const site = await forja.site.get(); * const locales = await forja.site.listLocales(); * ``` */ declare class ForjaClient { /** Blog posts — published listings, featured, similar, detail by slug/ID. */ readonly blogs: BlogsResource; /** CMS pages — list, fetch by route, sections and localizations. */ readonly pages: PagesResource; /** Navigation menus — menu metadata, tree structure, items. */ readonly navigation: NavigationResource; /** Tags and categories — listing, lookup by slug, content associations, blog counts. */ readonly taxonomy: TaxonomyResource; /** Privacy-first analytics — pageview tracking and reports. */ readonly analytics: AnalyticsResource; /** CV / resume — skills and work/education entries. */ readonly cv: CvResource; /** Legal documents — privacy policy, cookie consent, terms, version history. */ readonly legal: LegalResource; /** Portfolio projects — published listings, detail, skill/CV associations. */ readonly projects: ProjectsResource; /** URL redirects — path-based lookup for SSR middleware. */ readonly redirects: RedirectsResource; /** Site configuration — name, slug, logo, timezone, locales. */ readonly site: SiteResource; /** Media assets — browse, search, filter by type, full detail with variants. */ readonly media: MediaResource; /** Social media links — GitHub, Twitter, LinkedIn, etc. */ readonly social: SocialResource; /** Forms module — public form rendering, submission, and self-service. */ readonly forms: FormsResource; /** Shared HTTP client + site id, retained so {@link collections} can build * a per-type resource on demand. */ private readonly http; private readonly siteId; /** * @param config - API connection settings. * @param config.baseUrl - Full URL to the Forja API (e.g. `https://cms.example.com/api/v1`). * @param config.apiKey - API key with at least `Read` permission. * @param config.siteId - UUID of the site to query. * @param config.fetch - Optional custom `fetch` implementation for edge runtimes or testing. */ constructor(config: ForjaClientConfig); /** * Read operations for a custom type ("Collection"), by its key. One generic * resource serves every type — no per-type codegen. * * @example * ```ts * const recipes = await forja.collections('recipe').published({ page: 1 }); * const one = await forja.collections('recipe').bySlug('spaghetti'); * const schema = await forja.collections('recipe').schema(); * ``` */ collections(typeKey: string): CollectionsResource; /** * Resolved UI strings for one locale as a flat `key → value` map — the * site-scoped dictionary for interface chrome (labels, headings, aria * texts). The locale code is required; the server resolves one value per * key via its fallback chain and omits keys without any localization. * * @example * ```ts * const strings = await forja.strings('de'); * console.log(strings['footer.built_with']); * ``` */ strings(locale: string): Promise; } //#endregion export { MediaResponse as $, CategoryTree as A, SiteContextIntegration as At, LegalDocType as B, TrackPageviewResponse as Bt, AnalyticsReportParams as C, RedirectLookupResponse as Ct, BlogListItem as D, SearchablePaginationParams as Dt, BlogDocumentResponse as E, ResolvedNavigationMenu as Et, CvEntryResponse as F, SkillResponse as Ft, LegalGroupResponse as G, LegalDocumentFullDetailResponse as H, TrendDataPoint as Ht, CvEntryType as I, SocialLinkResponse as It, LegalVersionResponse as J, LegalGroupWithItems as K, DocumentLocalizationResponse as L, TagResponse as Lt, CodeInjection as M, SiteLocaleResponse as Mt, ContentStatus as N, SiteResponse as Nt, BlogResponse as O, SectionLocalizationResponse as Ot, CvEntryParams as P, SkillCategory as Pt, MediaListParams as Q, ForjaClientConfig as R, TopContentItem as Rt, AnalyticsPageParams as S, PublicSiteSettings as St, BlogDetailResponse as T, ReferrerItem as Tt, LegalDocumentResponse as U, UiStringsResponse as Ut, LegalDocumentDetailResponse as V, TranslationStatus as Vt, LegalDocumentWithGroups as W, LocalizationResponse as X, LocaleFilterParams as Y, MediaListItem as Z, PublicCollectionListParams as _, ProjectLinkType as _t, FormFieldValidation as a, NavigationMenuResponse as at, PaginatedResult as b, ProjectMediaResponse as bt, PublicFormDefinition as c, PageListItem as ct, SubmitFormOptions as d, PageType as dt, MediaVariantResponse as et, ValidationErrorMap as f, Paginated as ft, PublicCollectionField as g, ProjectLinkResponse as gt, PublicCollectionEntry as h, ProjectDetailResponse as ht, FormFieldType as i, NavigationItemResponse as it, CategoryWithCountResponse as j, SiteContextResponse as jt, CategoryResponse as k, SectionType as kt, SelfServiceLookup as l, PageResponse as lt, CollectionsResource as m, PaginationParams as mt, FormBotProtection as n, MenuWithTree as nt, FormSubmitData as o, NavigationTree as ot, validateSubmission as p, PaginationMeta as pt, LegalItemResponse as q, FormFieldDefinition as r, NavigationItemLocalizationResponse as rt, FormSubmitResponse as s, PageDetailResponse as st, ForjaClient as t, MenuLocalizationResponse as tt, SelfServiceSubmission as u, PageSectionResponse as ut, PublicCollectionSchema as v, ProjectListParams as vt, AnalyticsReportResponse as w, RedirectResponse as wt, AnalyticsPageDetailResponse as x, ProjectResponse as xt, HttpClient as y, ProjectLocalizationResponse as yt, LegalDocLocalizationResponse as z, TrackPageviewRequest as zt };