@forjacms/client 1.7.1 → 1.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -130,6 +130,62 @@ const privacy = await forja.legal.getBySlug('privacy-policy');
130
130
  const consent = await forja.legal.getCookieConsent();
131
131
  ```
132
132
 
133
+ ## Site Locales
134
+
135
+ `SiteLocaleResponse` is the canonical shape returned by every endpoint that
136
+ lists or resolves the locales configured for a site. Pinning this contract
137
+ here because consumers have drifted in the past (inventing a `text_direction`
138
+ field, expecting a top-level `id`, treating `url_prefix` as required).
139
+
140
+ **Example payload:**
141
+
142
+ ```json
143
+ {
144
+ "site_id": "550e8400-e29b-41d4-a716-446655440000",
145
+ "locale_id": "660e8400-e29b-41d4-a716-446655440000",
146
+ "is_default": true,
147
+ "is_active": true,
148
+ "url_prefix": null,
149
+ "created_at": "2024-01-15T10:30:00Z",
150
+ "code": "en",
151
+ "name": "English",
152
+ "native_name": "English",
153
+ "direction": "ltr"
154
+ }
155
+ ```
156
+
157
+ **Identity.** The natural key is the composite `(site_id, locale_id)`. There
158
+ is no top-level `id` — a locale can be attached to many sites, and a site
159
+ can carry many locales.
160
+
161
+ **Field reference:**
162
+
163
+ | Field | Type | Notes |
164
+ | ------------- | --------------------- | ------------------------------------------------------------------------------------------------------- |
165
+ | `site_id` | UUID | Composite-key half #1. |
166
+ | `locale_id` | UUID | Composite-key half #2. |
167
+ | `is_default` | boolean | Exactly one row per site has this `true`. |
168
+ | `is_active` | boolean | Configured-but-not-served when `false`. Consumers usually filter to `is_active = true`. |
169
+ | `url_prefix` | string \| null | Path segment selecting this locale. Nullable; the default locale conventionally has none. |
170
+ | `created_at` | ISO-8601 (UTC) | When the assignment was created. |
171
+ | `code` | string | BCP-47 (e.g. `"en"`, `"de-AT"`), denormalised from the `locales` row. |
172
+ | `name` | string | English-language label (e.g. `"English"`), denormalised. |
173
+ | `native_name` | string \| null | Locale's own name (e.g. `"Deutsch"`), denormalised. Nullable for locales without a defined native form. |
174
+ | `direction` | `"ltr"` \| `"rtl"` | Text direction. **The field is `direction` — NOT `text_direction`.** |
175
+
176
+ **Denormalisation rationale.** `code`, `name`, `native_name`, and `direction`
177
+ live on the `locales` table but are inlined into the response so a consumer
178
+ can render a locale switcher without a second round-trip. The trade-off is
179
+ deliberate: assignment-rate-of-change is much lower than read-rate.
180
+
181
+ **Anti-patterns to avoid in consumers:**
182
+
183
+ - ❌ Renaming `direction` → `text_direction` (the upstream name wins).
184
+ - ❌ Expecting a top-level `id` (use the `(site_id, locale_id)` composite).
185
+ - ❌ Treating `url_prefix` as required (it's nullable; the default locale typically has none).
186
+
187
+ See [issue #742](https://github.com/dominikdorfstetter/forja/issues/742) for the canonical-contract discussion.
188
+
133
189
  ## Pagination
134
190
 
135
191
  All paginated responses include helpers for navigating pages:
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../client-FailsuXv.cjs`);let t=require(`@angular/core`);const n=new t.InjectionToken(`ForjaClient`);function r(r){return(0,t.makeEnvironmentProviders)([{provide:n,useFactory:()=>new e.t(r)}])}function i(){return(0,t.inject)(n)}function a(e){let n=(0,t.signal)(void 0),r=(0,t.signal)(!0),i=(0,t.signal)(null),a=()=>{r.set(!0),i.set(null),e().then(e=>n.set(e)).catch(e=>i.set(e instanceof Error?e:Error(String(e)))).finally(()=>r.set(!1))};return a(),{value:n.asReadonly(),isLoading:r.asReadonly(),error:i.asReadonly(),reload:a}}exports.FORJA_CLIENT=n,exports.forjaResource=a,exports.injectForja=i,exports.provideForja=r;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../client-DVY5u6LJ.cjs`);let t=require(`@angular/core`);const n=new t.InjectionToken(`ForjaClient`);function r(r){return(0,t.makeEnvironmentProviders)([{provide:n,useFactory:()=>new e.t(r)}])}function i(){return(0,t.inject)(n)}function a(e){let n=(0,t.signal)(void 0),r=(0,t.signal)(!0),i=(0,t.signal)(null),a=()=>{r.set(!0),i.set(null),e().then(e=>n.set(e)).catch(e=>i.set(e instanceof Error?e:Error(String(e)))).finally(()=>r.set(!1))};return a(),{value:n.asReadonly(),isLoading:r.asReadonly(),error:i.asReadonly(),reload:a}}exports.FORJA_CLIENT=n,exports.forjaResource=a,exports.injectForja=i,exports.provideForja=r;
@@ -1,4 +1,4 @@
1
- import { N as ForjaClientConfig, t as ForjaClient } from "../client-CvbFQ08l.cjs";
1
+ import { N as ForjaClientConfig, t as ForjaClient } from "../client-CUbs1rkJ.cjs";
2
2
  import { EnvironmentProviders, InjectionToken, Signal } from "@angular/core";
3
3
 
4
4
  //#region src/angular/provider.d.ts
@@ -1,4 +1,4 @@
1
- import { N as ForjaClientConfig, t as ForjaClient } from "../client-BTbTifpr.mjs";
1
+ import { N as ForjaClientConfig, t as ForjaClient } from "../client-HGIKi26O.mjs";
2
2
  import { EnvironmentProviders, InjectionToken, Signal } from "@angular/core";
3
3
 
4
4
  //#region src/angular/provider.d.ts
@@ -1 +1 @@
1
- import{t as e}from"../client-C-2JZF6o.mjs";import{InjectionToken as t,inject as n,makeEnvironmentProviders as r,signal as i}from"@angular/core";const a=new t(`ForjaClient`);function o(t){return r([{provide:a,useFactory:()=>new e(t)}])}function s(){return n(a)}function c(e){let t=i(void 0),n=i(!0),r=i(null),a=()=>{n.set(!0),r.set(null),e().then(e=>t.set(e)).catch(e=>r.set(e instanceof Error?e:Error(String(e)))).finally(()=>n.set(!1))};return a(),{value:t.asReadonly(),isLoading:n.asReadonly(),error:r.asReadonly(),reload:a}}export{a as FORJA_CLIENT,c as forjaResource,s as injectForja,o as provideForja};
1
+ import{t as e}from"../client-CWajZ1IX.mjs";import{InjectionToken as t,inject as n,makeEnvironmentProviders as r,signal as i}from"@angular/core";const a=new t(`ForjaClient`);function o(t){return r([{provide:a,useFactory:()=>new e(t)}])}function s(){return n(a)}function c(e){let t=i(void 0),n=i(!0),r=i(null),a=()=>{n.set(!0),r.set(null),e().then(e=>t.set(e)).catch(e=>r.set(e instanceof Error?e:Error(String(e)))).finally(()=>n.set(!1))};return a(),{value:t.asReadonly(),isLoading:n.asReadonly(),error:r.asReadonly(),reload:a}}export{a as FORJA_CLIENT,c as forjaResource,s as injectForja,o as provideForja};
@@ -120,6 +120,18 @@ interface BlogDetailResponse extends BlogResponse {
120
120
  documents: BlogDocumentResponse[];
121
121
  og_image_url: string | null;
122
122
  }
123
+ /**
124
+ * Options for blog detail lookups (`get`, `getBySlug`). The list shape
125
+ * does not yet carry `localizations[]` so the resolver only applies to
126
+ * the detail endpoint — list canonicalization is tracked separately.
127
+ */
128
+ interface BlogDetailParams {
129
+ /**
130
+ * Optional locale code (e.g. `"en"`). When set, `localizations[]`
131
+ * collapses to one resolved entry. See ADR 0002.
132
+ */
133
+ locale?: string;
134
+ }
123
135
  interface PageListItem {
124
136
  id: string;
125
137
  route: string;
@@ -281,6 +293,12 @@ interface AnalyticsPageParams {
281
293
  startDate?: string;
282
294
  endDate?: string;
283
295
  }
296
+ interface SkillLocalizationResponse {
297
+ id: string;
298
+ locale_id: string;
299
+ name: string;
300
+ description: string | null;
301
+ }
284
302
  interface SkillResponse {
285
303
  id: string;
286
304
  name: string;
@@ -288,6 +306,19 @@ interface SkillResponse {
288
306
  category: SkillCategory | null;
289
307
  icon: string | null;
290
308
  proficiency_level: number | null;
309
+ /**
310
+ * Per-locale display names. Empty array when no localizations exist
311
+ * (never null / missing). Clients pick the matching locale and fall
312
+ * back per their own rules.
313
+ */
314
+ localizations: SkillLocalizationResponse[];
315
+ }
316
+ interface CvEntryLocalizationResponse {
317
+ id: string;
318
+ locale_id: string;
319
+ position: string;
320
+ description: string | null;
321
+ achievements: unknown | null;
291
322
  }
292
323
  interface CvEntryResponse {
293
324
  id: string;
@@ -302,9 +333,52 @@ interface CvEntryResponse {
302
333
  display_order: number;
303
334
  created_at: string;
304
335
  updated_at: string;
336
+ /**
337
+ * Per-locale position + description. Empty array when no localizations
338
+ * exist (never null / missing).
339
+ */
340
+ localizations: CvEntryLocalizationResponse[];
341
+ /**
342
+ * Skill IDs linked to this CV entry. Empty array when no skills are
343
+ * linked (never null / missing).
344
+ */
345
+ skill_ids: string[];
346
+ }
347
+ /** Pagination, search, and locale-resolver params for skill listings. */
348
+ interface SkillListParams extends SearchablePaginationParams {
349
+ /**
350
+ * Optional locale code (e.g. `"en"`). When set, each skill's
351
+ * `localizations` array collapses to one entry resolved by the server's
352
+ * fallback chain. See ADR 0002.
353
+ */
354
+ locale?: string;
355
+ }
356
+ /** Options for skill detail lookups (`getSkill`, `getSkillBySlug`). */
357
+ interface SkillDetailParams {
358
+ /**
359
+ * Optional locale code. When set, `localizations` collapses to one
360
+ * resolved entry. See ADR 0002.
361
+ */
362
+ locale?: string;
305
363
  }
306
364
  interface CvEntryParams extends SearchablePaginationParams {
307
365
  entryType?: CvEntryType;
366
+ /**
367
+ * Optional locale code (e.g. `"en"`, `"de-AT"`). When set, each entry's
368
+ * `localizations` array collapses to one entry resolved by the server's
369
+ * fallback chain. Omit to receive every localization.
370
+ *
371
+ * See ADR 0002 (`docs/adr/0002-locale-resolver.md`).
372
+ */
373
+ locale?: string;
374
+ }
375
+ /** Options for CV-entry detail lookups (`getEntry`). */
376
+ interface CvEntryDetailParams {
377
+ /**
378
+ * Optional locale code. When set, `localizations` collapses to one
379
+ * resolved entry. See ADR 0002.
380
+ */
381
+ locale?: string;
308
382
  }
309
383
  interface LegalDocumentResponse {
310
384
  id: string;
@@ -327,6 +401,19 @@ interface LegalDocumentDetailResponse {
327
401
  created_at: string;
328
402
  updated_at: string;
329
403
  }
404
+ /**
405
+ * Options for legal-document detail lookups (`getBySlug`, `getDetail`).
406
+ * The list shape (`LegalDocumentResponse`) does not yet carry
407
+ * `localizations[]` — tracked separately.
408
+ */
409
+ interface LegalDetailParams {
410
+ /**
411
+ * Optional locale code (e.g. `"en"`). When set, `localizations[]`
412
+ * (and `doc_localizations[]` on the full-detail endpoint) collapses
413
+ * to one resolved entry. See ADR 0002.
414
+ */
415
+ locale?: string;
416
+ }
330
417
  interface LegalGroupResponse {
331
418
  id: string;
332
419
  cookie_name: string;
@@ -410,7 +497,16 @@ interface SocialLinkResponse {
410
497
  }
411
498
  /** Link type for project resources (repository, demo, docs, etc.). */
412
499
  type ProjectLinkType = 'repository' | 'demo' | 'documentation' | 'website' | 'other';
413
- /** Project summary for list views. */
500
+ /** Localized content for a project. */
501
+ interface ProjectLocalizationResponse {
502
+ id: string;
503
+ locale_id: string;
504
+ title: string;
505
+ short_description: string | null;
506
+ description: string | null;
507
+ }
508
+ /** Project summary for list views. Always carries `localizations` — empty
509
+ * array when the project has none, never `null` or missing. */
414
510
  interface ProjectResponse {
415
511
  id: string;
416
512
  slug: string;
@@ -423,14 +519,9 @@ interface ProjectResponse {
423
519
  published_at: string | null;
424
520
  created_at: string;
425
521
  updated_at: string;
426
- }
427
- /** Localized content for a project. */
428
- interface ProjectLocalizationResponse {
429
- id: string;
430
- locale_id: string;
431
- title: string;
432
- short_description: string | null;
433
- description: string | null;
522
+ /** Skill IDs linked to the project. Always present — empty array when none. */
523
+ skill_ids: string[];
524
+ localizations: ProjectLocalizationResponse[];
434
525
  }
435
526
  /** External link attached to a project. */
436
527
  interface ProjectLinkResponse {
@@ -447,12 +538,11 @@ interface ProjectMediaResponse {
447
538
  display_order: number;
448
539
  is_cover: boolean;
449
540
  }
450
- /** Full project detail including localizations, links, and media. */
541
+ /** Full project detail. Inherits `skill_ids` and `localizations` from
542
+ * {@link ProjectResponse}; adds links, media, and relation id sets. */
451
543
  interface ProjectDetailResponse extends ProjectResponse {
452
- localizations: ProjectLocalizationResponse[];
453
544
  links: ProjectLinkResponse[];
454
545
  media: ProjectMediaResponse[];
455
- skill_ids: string[];
456
546
  cv_entry_ids: string[];
457
547
  }
458
548
  /** Pagination and filter params for project listings. */
@@ -463,33 +553,79 @@ interface ProjectListParams extends PaginationParams {
463
553
  sortDir?: 'asc' | 'desc';
464
554
  /** Filter to featured projects only. */
465
555
  isFeatured?: boolean;
556
+ /**
557
+ * Optional locale code (e.g. `"en"`, `"de-AT"`). When set, each project's
558
+ * `localizations` array collapses to one entry resolved by the server's
559
+ * fallback chain (requested → site default → first available). Omit to
560
+ * receive every localization, which is the existing default.
561
+ *
562
+ * See ADR 0002 (`docs/adr/0002-locale-resolver.md`).
563
+ */
564
+ locale?: string;
466
565
  }
566
+ /** Options for project detail lookups (`get`, `getBySlug`). */
567
+ interface ProjectDetailParams {
568
+ /**
569
+ * Optional locale code. When set, `localizations` collapses to one entry
570
+ * resolved by the server's fallback chain. See ADR 0002.
571
+ */
572
+ locale?: string;
573
+ }
574
+ /**
575
+ * The set of HTTP status codes a Forja redirect may use.
576
+ *
577
+ * Pinned by issue #743. The same domain is enforced server-side by the
578
+ * `validate_redirect_status_code` DTO validator and the
579
+ * `chk_redirect_status_code` DB CHECK constraint, so consumers may rely
580
+ * on this value without runtime coercion.
581
+ */
582
+ type RedirectStatusCode = 301 | 302 | 307 | 308;
467
583
  /** A URL redirect rule. */
468
584
  interface RedirectResponse {
469
585
  id: string;
470
586
  site_id: string;
471
587
  source_path: string;
472
588
  destination_path: string;
473
- status_code: number;
589
+ status_code: RedirectStatusCode;
474
590
  is_active: boolean;
475
591
  description: string | null;
476
592
  created_at: string;
477
593
  updated_at: string;
478
594
  }
479
- /** Result of a redirect path lookup. */
595
+ /**
596
+ * Result of a redirect path lookup.
597
+ *
598
+ * Returned with HTTP 200 by `GET /sites/{site_id}/redirects/lookup` on
599
+ * match. No-match is signalled by **404** (RFC 7807 ProblemDetails) —
600
+ * never a 200 with a null body, never a `{ redirects: [] }` list.
601
+ */
480
602
  interface RedirectLookupResponse {
481
603
  destination_path: string;
482
- status_code: number;
604
+ status_code: RedirectStatusCode;
483
605
  }
484
606
  /** A locale configured for a site. */
485
607
  interface SiteLocaleResponse {
608
+ /** Composite-key half #1 — which site this assignment belongs to. */
609
+ site_id: string;
610
+ /** Composite-key half #2 — which locale is attached. */
486
611
  locale_id: string;
612
+ /** BCP-47 code, denormalised from `locales.code`. */
487
613
  code: string;
614
+ /** English-language label, denormalised from `locales.name`. */
488
615
  name: string;
616
+ /** Locale's own name, denormalised from `locales.native_name`. */
489
617
  native_name: string | null;
490
618
  direction: 'ltr' | 'rtl';
619
+ /** Exactly one row per site has this set to `true`. */
491
620
  is_default: boolean;
621
+ /** Configured-but-not-served when `false`. Consumers usually filter to `true`. */
492
622
  is_active: boolean;
623
+ /**
624
+ * Path segment selecting this locale. `null` for the default locale
625
+ * (served at the site root without a prefix).
626
+ */
627
+ url_prefix: string | null;
628
+ created_at: string;
493
629
  }
494
630
  /** Media item summary for list views (lighter than full MediaResponse). */
495
631
  interface MediaListItem {
@@ -770,7 +906,7 @@ declare class BlogsResource {
770
906
  * }
771
907
  * ```
772
908
  */
773
- getBySlug(slug: string): Promise<BlogDetailResponse | null>;
909
+ getBySlug(slug: string, params?: BlogDetailParams): Promise<BlogDetailResponse | null>;
774
910
  /**
775
911
  * Fetch a blog post's full detail by its UUID.
776
912
  *
@@ -784,7 +920,7 @@ declare class BlogsResource {
784
920
  * const blog = await forja.blogs.get('550e8400-e29b-41d4-a716-446655440000');
785
921
  * ```
786
922
  */
787
- get(idOrSlug: string): Promise<BlogDetailResponse | null>;
923
+ get(idOrSlug: string, params?: BlogDetailParams): Promise<BlogDetailResponse | null>;
788
924
  /**
789
925
  * Fetch the site's RSS feed as raw XML.
790
926
  *
@@ -822,7 +958,7 @@ declare class CvResource {
822
958
  * const programming = skills.data.filter(s => s.category === 'Programming');
823
959
  * ```
824
960
  */
825
- listSkills(params?: SearchablePaginationParams): Promise<PaginatedResult<SkillResponse>>;
961
+ listSkills(params?: SkillListParams): Promise<PaginatedResult<SkillResponse>>;
826
962
  /**
827
963
  * Fetch a skill by its UUID.
828
964
  *
@@ -831,7 +967,7 @@ declare class CvResource {
831
967
  * @param id - The skill's UUID.
832
968
  * @returns The skill, or `null` if not found.
833
969
  */
834
- getSkill(id: string): Promise<SkillResponse | null>;
970
+ getSkill(id: string, params?: SkillDetailParams): Promise<SkillResponse | null>;
835
971
  /**
836
972
  * Fetch a skill by its URL slug.
837
973
  *
@@ -846,7 +982,7 @@ declare class CvResource {
846
982
  * if (ts) console.log(`${ts.name}: level ${ts.proficiency_level}`);
847
983
  * ```
848
984
  */
849
- getSkillBySlug(slug: string): Promise<SkillResponse | null>;
985
+ getSkillBySlug(slug: string, params?: SkillDetailParams): Promise<SkillResponse | null>;
850
986
  /**
851
987
  * Fetch a paginated list of CV entries (work, education, certifications, etc.).
852
988
  *
@@ -863,6 +999,25 @@ declare class CvResource {
863
999
  * ```
864
1000
  */
865
1001
  listEntries(params?: CvEntryParams): Promise<PaginatedResult<CvEntryResponse>>;
1002
+ /**
1003
+ * Fetch a CV entry by its UUID.
1004
+ *
1005
+ * **Endpoint:** `GET /cv/{id}`
1006
+ *
1007
+ * @param id - The CV entry's UUID.
1008
+ * @param params - Optional locale resolver per ADR 0002.
1009
+ * @returns The CV entry, or `null` if not found.
1010
+ *
1011
+ * @example
1012
+ * ```ts
1013
+ * // All localizations (default)
1014
+ * const entry = await forja.cv.getEntry('entry-uuid');
1015
+ *
1016
+ * // Single-locale collapse
1017
+ * const enEntry = await forja.cv.getEntry('entry-uuid', { locale: 'en' });
1018
+ * ```
1019
+ */
1020
+ getEntry(id: string, params?: CvEntryDetailParams): Promise<CvEntryResponse | null>;
866
1021
  }
867
1022
  //#endregion
868
1023
  //#region src/resources/forms.d.ts
@@ -1029,7 +1184,7 @@ declare class LegalResource {
1029
1184
  * }
1030
1185
  * ```
1031
1186
  */
1032
- getBySlug(slug: string): Promise<LegalDocumentDetailResponse | null>;
1187
+ getBySlug(slug: string, params?: LegalDetailParams): Promise<LegalDocumentDetailResponse | null>;
1033
1188
  /**
1034
1189
  * Fetch the cookie consent document with its consent groups and items.
1035
1190
  *
@@ -1093,7 +1248,7 @@ declare class LegalResource {
1093
1248
  * }
1094
1249
  * ```
1095
1250
  */
1096
- getDetail(id: string): Promise<LegalDocumentFullDetailResponse | null>;
1251
+ getDetail(id: string, params?: LegalDetailParams): Promise<LegalDocumentFullDetailResponse | null>;
1097
1252
  /**
1098
1253
  * Fetch the version history of a legal document.
1099
1254
  *
@@ -1291,6 +1446,19 @@ interface PageListParams extends SearchablePaginationParams {
1291
1446
  /** Exclude pages with this status. */
1292
1447
  excludeStatus?: string;
1293
1448
  }
1449
+ /**
1450
+ * Options for page-detail lookups (`getDetail`). The list shape
1451
+ * (`PageResponse`) does not carry `localizations[]` today — adding it is
1452
+ * tracked as a separate canonicalization gap; until then the resolver
1453
+ * applies only to the detail endpoint.
1454
+ */
1455
+ interface PageDetailParams {
1456
+ /**
1457
+ * Optional locale code (e.g. `"en"`). When set, `localizations[]`
1458
+ * collapses to one resolved entry. See ADR 0002.
1459
+ */
1460
+ locale?: string;
1461
+ }
1294
1462
  /**
1295
1463
  * CMS page operations.
1296
1464
  *
@@ -1337,6 +1505,25 @@ declare class PagesResource {
1337
1505
  * ```
1338
1506
  */
1339
1507
  getByRoute(route: string): Promise<PageDetailResponse | null>;
1508
+ /**
1509
+ * Fetch a page's full detail (content localizations + computed OG image).
1510
+ *
1511
+ * **Endpoint:** `GET /pages/{id}/detail`
1512
+ *
1513
+ * @param id - The page's UUID.
1514
+ * @param params - Optional locale resolver (ADR 0002).
1515
+ * @returns The page detail, or `null` if not found.
1516
+ *
1517
+ * @example
1518
+ * ```ts
1519
+ * // Every localization (admin/editor use case)
1520
+ * const detail = await forja.pages.getDetail('page-uuid');
1521
+ *
1522
+ * // Server-side rendering — single locale
1523
+ * const en = await forja.pages.getDetail('page-uuid', { locale: 'en' });
1524
+ * ```
1525
+ */
1526
+ getDetail(id: string, params?: PageDetailParams): Promise<PageDetailResponse | null>;
1340
1527
  /**
1341
1528
  * Fetch all sections for a page.
1342
1529
  *
@@ -1438,7 +1625,7 @@ declare class ProjectsResource {
1438
1625
  * }
1439
1626
  * ```
1440
1627
  */
1441
- get(id: string): Promise<ProjectDetailResponse | null>;
1628
+ get(id: string, params?: ProjectDetailParams): Promise<ProjectDetailResponse | null>;
1442
1629
  /**
1443
1630
  * Fetch a project by its URL slug.
1444
1631
  *
@@ -1452,7 +1639,7 @@ declare class ProjectsResource {
1452
1639
  * const project = await forja.projects.getBySlug('forja-cms');
1453
1640
  * ```
1454
1641
  */
1455
- getBySlug(slug: string): Promise<ProjectResponse | null>;
1642
+ getBySlug(slug: string, params?: ProjectDetailParams): Promise<ProjectResponse | null>;
1456
1643
  }
1457
1644
  //#endregion
1458
1645
  //#region src/resources/redirects.d.ts
@@ -0,0 +1 @@
1
+ var e=class extends Error{constructor(e,t){super(e),this.code=t,this.name=`ForjaError`}},t=class extends e{constructor(e=`Invalid or missing API key`){super(e,`AUTH_ERROR`),this.name=`ForjaAuthError`}},n=class extends e{constructor(e=`Insufficient permissions`){super(e,`PERMISSION_ERROR`),this.name=`ForjaPermissionError`}},r=class extends e{constructor(e=`Resource not found`){super(e,`NOT_FOUND`),this.name=`ForjaNotFoundError`}},i=class extends e{constructor(e=`Rate limit exceeded`,t){super(e,`RATE_LIMIT`),this.retryAfter=t,this.name=`ForjaRateLimitError`}},a=class extends e{constructor(e=`Validation error`,t){super(e,`VALIDATION_ERROR`),this.details=t,this.name=`ForjaValidationError`}},o=class extends e{constructor(e=`Internal server error`,t=500){super(e,`SERVER_ERROR`),this.status=t,this.name=`ForjaServerError`}},s=class extends e{constructor(e=`Network error`,t){super(e,`NETWORK_ERROR`),this.cause=t,this.name=`ForjaNetworkError`}};function c(e,t,n){let r=e.replace(/\/+$/,``),i=t.startsWith(`/`)?t:`/${t}`,a=new URL(`${r}${i}`);if(n)for(let[e,t]of Object.entries(n))t!==void 0&&a.searchParams.set(e,t);return a.toString()}function l(e){let t={};for(let[n,r]of Object.entries(e))r!=null&&(t[u(n)]=String(r));return t}function u(e){return e.replace(/[A-Z]/g,e=>`_${e.toLowerCase()}`)}async function d(e){let s;try{let t=await e.json();s=t.detail||t.message||t.title||e.statusText}catch{s=e.statusText}switch(e.status){case 401:throw new t(s);case 403:throw new n(s);case 404:throw new r(s);case 422:throw new a(s);case 429:{let t=e.headers.get(`retry-after`);throw new i(s,t?parseInt(t,10):void 0)}default:throw e.status,new o(s,e.status)}}function f(e){let t=e.fetch??globalThis.fetch,n=e.baseUrl.replace(/\/+$/,``),r={"X-API-Key":e.apiKey,"Content-Type":`application/json`};e.siteDomain&&(r[`X-Site-Domain`]=e.siteDomain);async function i(e,i,a,o){let l=c(n,i,a),u;try{u=await t(l,{method:e,headers:r,body:o===void 0?void 0:JSON.stringify(o)})}catch(e){throw new s(`Failed to connect to the Forja API`,e instanceof Error?e:void 0)}return u.ok?u:d(u)}return{get:(e,t)=>i(`GET`,e,t).then(e=>e.json()),getText:(e,t)=>i(`GET`,e,t).then(e=>e.text()),post:(e,t)=>i(`POST`,e,void 0,t).then(e=>e.json()),delete:e=>i(`DELETE`,e).then(()=>void 0)}}function p(e,t,n){return{data:e,meta:t,async fetchNext(){if(t.page>=t.total_pages)return null;let e=await n(t.page+1);return p(e.data,e.meta,n)},async fetchAll(){let r=[...e],i=t.page;for(;i<t.total_pages;){i++;let e=await n(i);r.push(...e.data)}return r},async*[Symbol.asyncIterator](){yield{data:e,meta:t};let r=t.page;for(;r<t.total_pages;)r++,yield await n(r)}}}var m=class{constructor(e,t){this.http=e,this.siteId=t}async trackPageview(e){return this.http.post(`/sites/${this.siteId}/analytics/pageview`,e)}async getReport(e){let t=e?l({days:e.days,top_n:e.topN,start_date:e.startDate,end_date:e.endDate}):void 0;return this.http.get(`/sites/${this.siteId}/analytics/report`,t)}async getPageAnalytics(e){let t=l({path:e.path,days:e.days,start_date:e.startDate,end_date:e.endDate});return this.http.get(`/sites/${this.siteId}/analytics/report/page`,t)}},h=class{constructor(e,t){this.http=e,this.siteId=t}async listPublished(e){let t=e?l(e):void 0,n=e?.localeId?{locale_id:e.localeId}:{},r=async e=>this.http.get(`/sites/${this.siteId}/blogs/published`,{...t,...n,page:String(e)}),i=await r(e?.page??1);return p(i.data,i.meta,r)}async listByCategory(e,t){let n=t?l(t):void 0,r=t?.localeId?{locale_id:t.localeId}:{},i=async t=>this.http.get(`/sites/${this.siteId}/blogs/published/category/${encodeURIComponent(e)}`,{...n,...r,page:String(t)}),a=await i(t?.page??1);return p(a.data,a.meta,i)}async listFeatured(e){return this.http.get(`/sites/${this.siteId}/blogs/featured`,e?.limit===void 0?void 0:{limit:String(e.limit)})}async listSimilar(e,t){return this.http.get(`/sites/${this.siteId}/blogs/${encodeURIComponent(e)}/similar`,t?.limit===void 0?void 0:{limit:String(t.limit)})}async getBySlug(e,t){try{let n=`/blogs/${(await this.http.get(`/sites/${this.siteId}/blogs/by-slug/${encodeURIComponent(e)}`)).id}/detail`;return t?.locale?await this.http.get(n,{locale:t.locale}):await this.http.get(n)}catch(e){if(e instanceof r)return null;throw e}}async get(e,t){try{let n=`/blogs/${encodeURIComponent(e)}/detail`;return t?.locale?await this.http.get(n,{locale:t.locale}):await this.http.get(n)}catch(e){if(e instanceof r)return null;throw e}}async rss(){return this.http.getText(`/sites/${this.siteId}/feed.rss`)}},g=class{constructor(e,t){this.http=e,this.siteId=t}async listSkills(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/skills`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async getSkill(e,t){try{let n=`/skills/${encodeURIComponent(e)}`;return t?.locale?await this.http.get(n,{locale:t.locale}):await this.http.get(n)}catch(e){if(e instanceof r)return null;throw e}}async getSkillBySlug(e,t){try{let n=`/skills/by-slug/${encodeURIComponent(e)}`;return t?.locale?await this.http.get(n,{locale:t.locale}):await this.http.get(n)}catch(e){if(e instanceof r)return null;throw e}}async listEntries(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/cv`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async getEntry(e,t){try{let n=`/cv/${encodeURIComponent(e)}`;return t?.locale?await this.http.get(n,{locale:t.locale}):await this.http.get(n)}catch(e){if(e instanceof r)return null;throw e}}},_=class{constructor(e){this.http=e}async getForm(e,t){let n=t?.locale?{locale:t.locale}:void 0;return this.http.get(`/public/forms/${encodeURIComponent(e)}`,n)}async submitForm(e,t,n={}){return this.http.post(`/public/forms/${encodeURIComponent(e)}/submit`,{data:t,consent_given:n.consentGiven??!1,bot_protection_token:n.botProtectionToken})}async lookupSubmission(e){return this.http.post(`/public/submissions/lookup`,{reference_code:e})}async getSubmission(e){return this.http.get(`/public/submissions/${encodeURIComponent(e)}`)}async deleteSubmission(e){return this.http.delete(`/public/submissions/${encodeURIComponent(e)}`)}};const v=/^[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}$/;function y(e,t){let n={};for(let r of e.fields){let e=t[r.label],i=b(r,e);i&&(n[r.label]=i)}return n}function b(e,t){let n=t,r=n==null||typeof n==`string`&&n===``||Array.isArray(n)&&n.length===0;if((e.is_required||e.validation.required)&&r)return`${e.label} is required`;if(r)return null;switch(e.field_type){case`text`:case`textarea`:case`custom`:if(typeof n!=`string`)return`Must be a string`;if(e.validation.min_length!==void 0&&n.length<e.validation.min_length)return`Must be at least ${e.validation.min_length} characters`;if(e.validation.max_length!==void 0&&n.length>e.validation.max_length)return`Must be at most ${e.validation.max_length} characters`;if(e.validation.pattern)try{if(!new RegExp(e.validation.pattern).test(n))return`Value does not match the required pattern`}catch{return`Field has an invalid validation pattern`}return null;case`email`:return typeof n==`string`?v.test(n)?null:`Invalid email format`:`Must be a string`;case`number`:{let t=typeof n==`number`?n:Number(n);return Number.isNaN(t)?`Must be a number`:e.validation.min!==void 0&&t<e.validation.min?`Must be at least ${e.validation.min}`:e.validation.max!==void 0&&t>e.validation.max?`Must be at most ${e.validation.max}`:null}case`date`:return typeof n!=`string`||Number.isNaN(Date.parse(n))&&!/^\d{4}-\d{2}-\d{2}$/.test(n)?`Invalid date format`:null;case`select`:case`radio`:case`checkbox`:return null}}var x=class{constructor(e,t){this.http=e,this.siteId=t}async list(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/legal`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async get(e){try{return await this.http.get(`/legal/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getBySlug(e,t){try{let n=`/sites/${this.siteId}/legal/by-slug/${encodeURIComponent(e)}`;return t?.locale?await this.http.get(n,{locale:t.locale}):await this.http.get(n)}catch(e){if(e instanceof r)return null;throw e}}async getCookieConsent(){try{return await this.http.get(`/sites/${this.siteId}/legal/cookie-consent`)}catch(e){if(e instanceof r)return null;throw e}}async getGroups(e){return this.http.get(`/legal/${encodeURIComponent(e)}/groups`)}async getGroupItems(e){return this.http.get(`/legal/groups/${encodeURIComponent(e)}/items`)}async getDetail(e,t){try{let n=`/legal/${encodeURIComponent(e)}/detail`;return t?.locale?await this.http.get(n,{locale:t.locale}):await this.http.get(n)}catch(e){if(e instanceof r)return null;throw e}}async listVersions(e){return this.http.get(`/legal/${encodeURIComponent(e)}/versions`)}},S=class{constructor(e,t){this.http=e,this.siteId=t}async list(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/media`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async get(e){try{return await this.http.get(`/media/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}},C=class{constructor(e,t){this.http=e,this.siteId=t}async listMenus(){return this.http.get(`/sites/${this.siteId}/menus`)}async getMenu(e){try{return await this.http.get(`/menus/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getMenuBySlug(e){try{return await this.http.get(`/sites/${this.siteId}/menus/slug/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getTree(e,t){return this.http.get(`/menus/${encodeURIComponent(e)}/tree`,t?.locale?{locale:t.locale}:void 0)}async listItems(e){return this.http.get(`/menus/${encodeURIComponent(e)}/items`)}async getItem(e){try{return await this.http.get(`/navigation/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}},w=class{constructor(e,t){this.http=e,this.siteId=t}async list(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/pages`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async getByRoute(e){let t=e.startsWith(`/`)?e.slice(1):e;try{return await this.http.get(`/sites/${this.siteId}/pages/by-route/${encodeURIComponent(t)}`)}catch(e){if(e instanceof r)return null;throw e}}async getDetail(e,t){try{let n=`/pages/${encodeURIComponent(e)}/detail`;return t?.locale?await this.http.get(n,{locale:t.locale}):await this.http.get(n)}catch(e){if(e instanceof r)return null;throw e}}async getSections(e){return this.http.get(`/pages/${encodeURIComponent(e)}/sections`)}async getSectionLocalizations(e){return this.http.get(`/pages/sections/${encodeURIComponent(e)}/localizations`)}async getPageSectionLocalizations(e){return this.http.get(`/pages/${encodeURIComponent(e)}/sections/localizations`)}},T=class{constructor(e,t){this.http=e,this.siteId=t}async listPublished(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/projects/public`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async get(e,t){try{let n=`/projects/${encodeURIComponent(e)}`;return t?.locale?await this.http.get(n,{locale:t.locale}):await this.http.get(n)}catch(e){if(e instanceof r)return null;throw e}}async getBySlug(e,t){try{let n=`/sites/${this.siteId}/projects/by-slug/${encodeURIComponent(e)}`;return t?.locale?await this.http.get(n,{locale:t.locale}):await this.http.get(n)}catch(e){if(e instanceof r)return null;throw e}}},E=class{constructor(e,t){this.http=e,this.siteId=t}async lookup(e){try{return await this.http.get(`/sites/${this.siteId}/redirects/lookup`,{path:e})}catch(e){if(e instanceof r)return null;throw e}}},D=class{constructor(e,t){this.http=e,this.siteId=t}async get(){return this.http.get(`/sites/${this.siteId}`)}async listLocales(){return this.http.get(`/sites/${this.siteId}/locales`)}async getCodeInjection(){let e=await this.http.get(`/sites/${this.siteId}/settings`);return{code_injection_head:e.code_injection_head??``,code_injection_footer:e.code_injection_footer??``}}},O=class{constructor(e,t){this.http=e,this.siteId=t}async list(){return this.http.get(`/sites/${this.siteId}/social`)}},k=class{constructor(e,t){this.http=e,this.siteId=t}async listTags(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/tags`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async listCategories(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/categories`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async getCategoriesWithBlogCounts(){return this.http.get(`/sites/${this.siteId}/categories/blog-counts`)}async getContentTags(e){return this.http.get(`/content/${encodeURIComponent(e)}/tags`)}async getContentCategories(e){return this.http.get(`/content/${encodeURIComponent(e)}/categories`)}async getTag(e){try{return await this.http.get(`/tags/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getTagBySlug(e){try{return await this.http.get(`/tags/by-slug/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getCategory(e){try{return await this.http.get(`/categories/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getCategoryChildren(e){return this.http.get(`/categories/${encodeURIComponent(e)}/children`)}},A=class{constructor(e){let t=f(e);this.blogs=new h(t,e.siteId),this.pages=new w(t,e.siteId),this.navigation=new C(t,e.siteId),this.taxonomy=new k(t,e.siteId),this.analytics=new m(t,e.siteId),this.cv=new g(t,e.siteId),this.legal=new x(t,e.siteId),this.projects=new T(t,e.siteId),this.redirects=new E(t,e.siteId),this.site=new D(t,e.siteId),this.media=new S(t,e.siteId),this.social=new O(t,e.siteId),this.forms=new _(t)}};export{s as a,i as c,e as i,o as l,y as n,r as o,t as r,n as s,A as t,a as u};
@@ -0,0 +1 @@
1
+ var e=class extends Error{constructor(e,t){super(e),this.code=t,this.name=`ForjaError`}},t=class extends e{constructor(e=`Invalid or missing API key`){super(e,`AUTH_ERROR`),this.name=`ForjaAuthError`}},n=class extends e{constructor(e=`Insufficient permissions`){super(e,`PERMISSION_ERROR`),this.name=`ForjaPermissionError`}},r=class extends e{constructor(e=`Resource not found`){super(e,`NOT_FOUND`),this.name=`ForjaNotFoundError`}},i=class extends e{constructor(e=`Rate limit exceeded`,t){super(e,`RATE_LIMIT`),this.retryAfter=t,this.name=`ForjaRateLimitError`}},a=class extends e{constructor(e=`Validation error`,t){super(e,`VALIDATION_ERROR`),this.details=t,this.name=`ForjaValidationError`}},o=class extends e{constructor(e=`Internal server error`,t=500){super(e,`SERVER_ERROR`),this.status=t,this.name=`ForjaServerError`}},s=class extends e{constructor(e=`Network error`,t){super(e,`NETWORK_ERROR`),this.cause=t,this.name=`ForjaNetworkError`}};function c(e,t,n){let r=e.replace(/\/+$/,``),i=t.startsWith(`/`)?t:`/${t}`,a=new URL(`${r}${i}`);if(n)for(let[e,t]of Object.entries(n))t!==void 0&&a.searchParams.set(e,t);return a.toString()}function l(e){let t={};for(let[n,r]of Object.entries(e))r!=null&&(t[u(n)]=String(r));return t}function u(e){return e.replace(/[A-Z]/g,e=>`_${e.toLowerCase()}`)}async function d(e){let s;try{let t=await e.json();s=t.detail||t.message||t.title||e.statusText}catch{s=e.statusText}switch(e.status){case 401:throw new t(s);case 403:throw new n(s);case 404:throw new r(s);case 422:throw new a(s);case 429:{let t=e.headers.get(`retry-after`);throw new i(s,t?parseInt(t,10):void 0)}default:throw e.status,new o(s,e.status)}}function f(e){let t=e.fetch??globalThis.fetch,n=e.baseUrl.replace(/\/+$/,``),r={"X-API-Key":e.apiKey,"Content-Type":`application/json`};e.siteDomain&&(r[`X-Site-Domain`]=e.siteDomain);async function i(e,i,a,o){let l=c(n,i,a),u;try{u=await t(l,{method:e,headers:r,body:o===void 0?void 0:JSON.stringify(o)})}catch(e){throw new s(`Failed to connect to the Forja API`,e instanceof Error?e:void 0)}return u.ok?u:d(u)}return{get:(e,t)=>i(`GET`,e,t).then(e=>e.json()),getText:(e,t)=>i(`GET`,e,t).then(e=>e.text()),post:(e,t)=>i(`POST`,e,void 0,t).then(e=>e.json()),delete:e=>i(`DELETE`,e).then(()=>void 0)}}function p(e,t,n){return{data:e,meta:t,async fetchNext(){if(t.page>=t.total_pages)return null;let e=await n(t.page+1);return p(e.data,e.meta,n)},async fetchAll(){let r=[...e],i=t.page;for(;i<t.total_pages;){i++;let e=await n(i);r.push(...e.data)}return r},async*[Symbol.asyncIterator](){yield{data:e,meta:t};let r=t.page;for(;r<t.total_pages;)r++,yield await n(r)}}}var m=class{constructor(e,t){this.http=e,this.siteId=t}async trackPageview(e){return this.http.post(`/sites/${this.siteId}/analytics/pageview`,e)}async getReport(e){let t=e?l({days:e.days,top_n:e.topN,start_date:e.startDate,end_date:e.endDate}):void 0;return this.http.get(`/sites/${this.siteId}/analytics/report`,t)}async getPageAnalytics(e){let t=l({path:e.path,days:e.days,start_date:e.startDate,end_date:e.endDate});return this.http.get(`/sites/${this.siteId}/analytics/report/page`,t)}},h=class{constructor(e,t){this.http=e,this.siteId=t}async listPublished(e){let t=e?l(e):void 0,n=e?.localeId?{locale_id:e.localeId}:{},r=async e=>this.http.get(`/sites/${this.siteId}/blogs/published`,{...t,...n,page:String(e)}),i=await r(e?.page??1);return p(i.data,i.meta,r)}async listByCategory(e,t){let n=t?l(t):void 0,r=t?.localeId?{locale_id:t.localeId}:{},i=async t=>this.http.get(`/sites/${this.siteId}/blogs/published/category/${encodeURIComponent(e)}`,{...n,...r,page:String(t)}),a=await i(t?.page??1);return p(a.data,a.meta,i)}async listFeatured(e){return this.http.get(`/sites/${this.siteId}/blogs/featured`,e?.limit===void 0?void 0:{limit:String(e.limit)})}async listSimilar(e,t){return this.http.get(`/sites/${this.siteId}/blogs/${encodeURIComponent(e)}/similar`,t?.limit===void 0?void 0:{limit:String(t.limit)})}async getBySlug(e,t){try{let n=`/blogs/${(await this.http.get(`/sites/${this.siteId}/blogs/by-slug/${encodeURIComponent(e)}`)).id}/detail`;return t?.locale?await this.http.get(n,{locale:t.locale}):await this.http.get(n)}catch(e){if(e instanceof r)return null;throw e}}async get(e,t){try{let n=`/blogs/${encodeURIComponent(e)}/detail`;return t?.locale?await this.http.get(n,{locale:t.locale}):await this.http.get(n)}catch(e){if(e instanceof r)return null;throw e}}async rss(){return this.http.getText(`/sites/${this.siteId}/feed.rss`)}},g=class{constructor(e,t){this.http=e,this.siteId=t}async listSkills(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/skills`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async getSkill(e,t){try{let n=`/skills/${encodeURIComponent(e)}`;return t?.locale?await this.http.get(n,{locale:t.locale}):await this.http.get(n)}catch(e){if(e instanceof r)return null;throw e}}async getSkillBySlug(e,t){try{let n=`/skills/by-slug/${encodeURIComponent(e)}`;return t?.locale?await this.http.get(n,{locale:t.locale}):await this.http.get(n)}catch(e){if(e instanceof r)return null;throw e}}async listEntries(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/cv`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async getEntry(e,t){try{let n=`/cv/${encodeURIComponent(e)}`;return t?.locale?await this.http.get(n,{locale:t.locale}):await this.http.get(n)}catch(e){if(e instanceof r)return null;throw e}}},_=class{constructor(e){this.http=e}async getForm(e,t){let n=t?.locale?{locale:t.locale}:void 0;return this.http.get(`/public/forms/${encodeURIComponent(e)}`,n)}async submitForm(e,t,n={}){return this.http.post(`/public/forms/${encodeURIComponent(e)}/submit`,{data:t,consent_given:n.consentGiven??!1,bot_protection_token:n.botProtectionToken})}async lookupSubmission(e){return this.http.post(`/public/submissions/lookup`,{reference_code:e})}async getSubmission(e){return this.http.get(`/public/submissions/${encodeURIComponent(e)}`)}async deleteSubmission(e){return this.http.delete(`/public/submissions/${encodeURIComponent(e)}`)}};const v=/^[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}$/;function y(e,t){let n={};for(let r of e.fields){let e=t[r.label],i=b(r,e);i&&(n[r.label]=i)}return n}function b(e,t){let n=t,r=n==null||typeof n==`string`&&n===``||Array.isArray(n)&&n.length===0;if((e.is_required||e.validation.required)&&r)return`${e.label} is required`;if(r)return null;switch(e.field_type){case`text`:case`textarea`:case`custom`:if(typeof n!=`string`)return`Must be a string`;if(e.validation.min_length!==void 0&&n.length<e.validation.min_length)return`Must be at least ${e.validation.min_length} characters`;if(e.validation.max_length!==void 0&&n.length>e.validation.max_length)return`Must be at most ${e.validation.max_length} characters`;if(e.validation.pattern)try{if(!new RegExp(e.validation.pattern).test(n))return`Value does not match the required pattern`}catch{return`Field has an invalid validation pattern`}return null;case`email`:return typeof n==`string`?v.test(n)?null:`Invalid email format`:`Must be a string`;case`number`:{let t=typeof n==`number`?n:Number(n);return Number.isNaN(t)?`Must be a number`:e.validation.min!==void 0&&t<e.validation.min?`Must be at least ${e.validation.min}`:e.validation.max!==void 0&&t>e.validation.max?`Must be at most ${e.validation.max}`:null}case`date`:return typeof n!=`string`||Number.isNaN(Date.parse(n))&&!/^\d{4}-\d{2}-\d{2}$/.test(n)?`Invalid date format`:null;case`select`:case`radio`:case`checkbox`:return null}}var x=class{constructor(e,t){this.http=e,this.siteId=t}async list(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/legal`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async get(e){try{return await this.http.get(`/legal/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getBySlug(e,t){try{let n=`/sites/${this.siteId}/legal/by-slug/${encodeURIComponent(e)}`;return t?.locale?await this.http.get(n,{locale:t.locale}):await this.http.get(n)}catch(e){if(e instanceof r)return null;throw e}}async getCookieConsent(){try{return await this.http.get(`/sites/${this.siteId}/legal/cookie-consent`)}catch(e){if(e instanceof r)return null;throw e}}async getGroups(e){return this.http.get(`/legal/${encodeURIComponent(e)}/groups`)}async getGroupItems(e){return this.http.get(`/legal/groups/${encodeURIComponent(e)}/items`)}async getDetail(e,t){try{let n=`/legal/${encodeURIComponent(e)}/detail`;return t?.locale?await this.http.get(n,{locale:t.locale}):await this.http.get(n)}catch(e){if(e instanceof r)return null;throw e}}async listVersions(e){return this.http.get(`/legal/${encodeURIComponent(e)}/versions`)}},S=class{constructor(e,t){this.http=e,this.siteId=t}async list(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/media`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async get(e){try{return await this.http.get(`/media/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}},C=class{constructor(e,t){this.http=e,this.siteId=t}async listMenus(){return this.http.get(`/sites/${this.siteId}/menus`)}async getMenu(e){try{return await this.http.get(`/menus/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getMenuBySlug(e){try{return await this.http.get(`/sites/${this.siteId}/menus/slug/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getTree(e,t){return this.http.get(`/menus/${encodeURIComponent(e)}/tree`,t?.locale?{locale:t.locale}:void 0)}async listItems(e){return this.http.get(`/menus/${encodeURIComponent(e)}/items`)}async getItem(e){try{return await this.http.get(`/navigation/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}},w=class{constructor(e,t){this.http=e,this.siteId=t}async list(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/pages`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async getByRoute(e){let t=e.startsWith(`/`)?e.slice(1):e;try{return await this.http.get(`/sites/${this.siteId}/pages/by-route/${encodeURIComponent(t)}`)}catch(e){if(e instanceof r)return null;throw e}}async getDetail(e,t){try{let n=`/pages/${encodeURIComponent(e)}/detail`;return t?.locale?await this.http.get(n,{locale:t.locale}):await this.http.get(n)}catch(e){if(e instanceof r)return null;throw e}}async getSections(e){return this.http.get(`/pages/${encodeURIComponent(e)}/sections`)}async getSectionLocalizations(e){return this.http.get(`/pages/sections/${encodeURIComponent(e)}/localizations`)}async getPageSectionLocalizations(e){return this.http.get(`/pages/${encodeURIComponent(e)}/sections/localizations`)}},T=class{constructor(e,t){this.http=e,this.siteId=t}async listPublished(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/projects/public`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async get(e,t){try{let n=`/projects/${encodeURIComponent(e)}`;return t?.locale?await this.http.get(n,{locale:t.locale}):await this.http.get(n)}catch(e){if(e instanceof r)return null;throw e}}async getBySlug(e,t){try{let n=`/sites/${this.siteId}/projects/by-slug/${encodeURIComponent(e)}`;return t?.locale?await this.http.get(n,{locale:t.locale}):await this.http.get(n)}catch(e){if(e instanceof r)return null;throw e}}},E=class{constructor(e,t){this.http=e,this.siteId=t}async lookup(e){try{return await this.http.get(`/sites/${this.siteId}/redirects/lookup`,{path:e})}catch(e){if(e instanceof r)return null;throw e}}},D=class{constructor(e,t){this.http=e,this.siteId=t}async get(){return this.http.get(`/sites/${this.siteId}`)}async listLocales(){return this.http.get(`/sites/${this.siteId}/locales`)}async getCodeInjection(){let e=await this.http.get(`/sites/${this.siteId}/settings`);return{code_injection_head:e.code_injection_head??``,code_injection_footer:e.code_injection_footer??``}}},O=class{constructor(e,t){this.http=e,this.siteId=t}async list(){return this.http.get(`/sites/${this.siteId}/social`)}},k=class{constructor(e,t){this.http=e,this.siteId=t}async listTags(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/tags`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async listCategories(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/categories`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async getCategoriesWithBlogCounts(){return this.http.get(`/sites/${this.siteId}/categories/blog-counts`)}async getContentTags(e){return this.http.get(`/content/${encodeURIComponent(e)}/tags`)}async getContentCategories(e){return this.http.get(`/content/${encodeURIComponent(e)}/categories`)}async getTag(e){try{return await this.http.get(`/tags/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getTagBySlug(e){try{return await this.http.get(`/tags/by-slug/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getCategory(e){try{return await this.http.get(`/categories/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getCategoryChildren(e){return this.http.get(`/categories/${encodeURIComponent(e)}/children`)}},A=class{constructor(e){let t=f(e);this.blogs=new h(t,e.siteId),this.pages=new w(t,e.siteId),this.navigation=new C(t,e.siteId),this.taxonomy=new k(t,e.siteId),this.analytics=new m(t,e.siteId),this.cv=new g(t,e.siteId),this.legal=new x(t,e.siteId),this.projects=new T(t,e.siteId),this.redirects=new E(t,e.siteId),this.site=new D(t,e.siteId),this.media=new S(t,e.siteId),this.social=new O(t,e.siteId),this.forms=new _(t)}};Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return e}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return y}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return t}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return n}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return A}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return a}});
@@ -120,6 +120,18 @@ interface BlogDetailResponse extends BlogResponse {
120
120
  documents: BlogDocumentResponse[];
121
121
  og_image_url: string | null;
122
122
  }
123
+ /**
124
+ * Options for blog detail lookups (`get`, `getBySlug`). The list shape
125
+ * does not yet carry `localizations[]` so the resolver only applies to
126
+ * the detail endpoint — list canonicalization is tracked separately.
127
+ */
128
+ interface BlogDetailParams {
129
+ /**
130
+ * Optional locale code (e.g. `"en"`). When set, `localizations[]`
131
+ * collapses to one resolved entry. See ADR 0002.
132
+ */
133
+ locale?: string;
134
+ }
123
135
  interface PageListItem {
124
136
  id: string;
125
137
  route: string;
@@ -281,6 +293,12 @@ interface AnalyticsPageParams {
281
293
  startDate?: string;
282
294
  endDate?: string;
283
295
  }
296
+ interface SkillLocalizationResponse {
297
+ id: string;
298
+ locale_id: string;
299
+ name: string;
300
+ description: string | null;
301
+ }
284
302
  interface SkillResponse {
285
303
  id: string;
286
304
  name: string;
@@ -288,6 +306,19 @@ interface SkillResponse {
288
306
  category: SkillCategory | null;
289
307
  icon: string | null;
290
308
  proficiency_level: number | null;
309
+ /**
310
+ * Per-locale display names. Empty array when no localizations exist
311
+ * (never null / missing). Clients pick the matching locale and fall
312
+ * back per their own rules.
313
+ */
314
+ localizations: SkillLocalizationResponse[];
315
+ }
316
+ interface CvEntryLocalizationResponse {
317
+ id: string;
318
+ locale_id: string;
319
+ position: string;
320
+ description: string | null;
321
+ achievements: unknown | null;
291
322
  }
292
323
  interface CvEntryResponse {
293
324
  id: string;
@@ -302,9 +333,52 @@ interface CvEntryResponse {
302
333
  display_order: number;
303
334
  created_at: string;
304
335
  updated_at: string;
336
+ /**
337
+ * Per-locale position + description. Empty array when no localizations
338
+ * exist (never null / missing).
339
+ */
340
+ localizations: CvEntryLocalizationResponse[];
341
+ /**
342
+ * Skill IDs linked to this CV entry. Empty array when no skills are
343
+ * linked (never null / missing).
344
+ */
345
+ skill_ids: string[];
346
+ }
347
+ /** Pagination, search, and locale-resolver params for skill listings. */
348
+ interface SkillListParams extends SearchablePaginationParams {
349
+ /**
350
+ * Optional locale code (e.g. `"en"`). When set, each skill's
351
+ * `localizations` array collapses to one entry resolved by the server's
352
+ * fallback chain. See ADR 0002.
353
+ */
354
+ locale?: string;
355
+ }
356
+ /** Options for skill detail lookups (`getSkill`, `getSkillBySlug`). */
357
+ interface SkillDetailParams {
358
+ /**
359
+ * Optional locale code. When set, `localizations` collapses to one
360
+ * resolved entry. See ADR 0002.
361
+ */
362
+ locale?: string;
305
363
  }
306
364
  interface CvEntryParams extends SearchablePaginationParams {
307
365
  entryType?: CvEntryType;
366
+ /**
367
+ * Optional locale code (e.g. `"en"`, `"de-AT"`). When set, each entry's
368
+ * `localizations` array collapses to one entry resolved by the server's
369
+ * fallback chain. Omit to receive every localization.
370
+ *
371
+ * See ADR 0002 (`docs/adr/0002-locale-resolver.md`).
372
+ */
373
+ locale?: string;
374
+ }
375
+ /** Options for CV-entry detail lookups (`getEntry`). */
376
+ interface CvEntryDetailParams {
377
+ /**
378
+ * Optional locale code. When set, `localizations` collapses to one
379
+ * resolved entry. See ADR 0002.
380
+ */
381
+ locale?: string;
308
382
  }
309
383
  interface LegalDocumentResponse {
310
384
  id: string;
@@ -327,6 +401,19 @@ interface LegalDocumentDetailResponse {
327
401
  created_at: string;
328
402
  updated_at: string;
329
403
  }
404
+ /**
405
+ * Options for legal-document detail lookups (`getBySlug`, `getDetail`).
406
+ * The list shape (`LegalDocumentResponse`) does not yet carry
407
+ * `localizations[]` — tracked separately.
408
+ */
409
+ interface LegalDetailParams {
410
+ /**
411
+ * Optional locale code (e.g. `"en"`). When set, `localizations[]`
412
+ * (and `doc_localizations[]` on the full-detail endpoint) collapses
413
+ * to one resolved entry. See ADR 0002.
414
+ */
415
+ locale?: string;
416
+ }
330
417
  interface LegalGroupResponse {
331
418
  id: string;
332
419
  cookie_name: string;
@@ -410,7 +497,16 @@ interface SocialLinkResponse {
410
497
  }
411
498
  /** Link type for project resources (repository, demo, docs, etc.). */
412
499
  type ProjectLinkType = 'repository' | 'demo' | 'documentation' | 'website' | 'other';
413
- /** Project summary for list views. */
500
+ /** Localized content for a project. */
501
+ interface ProjectLocalizationResponse {
502
+ id: string;
503
+ locale_id: string;
504
+ title: string;
505
+ short_description: string | null;
506
+ description: string | null;
507
+ }
508
+ /** Project summary for list views. Always carries `localizations` — empty
509
+ * array when the project has none, never `null` or missing. */
414
510
  interface ProjectResponse {
415
511
  id: string;
416
512
  slug: string;
@@ -423,14 +519,9 @@ interface ProjectResponse {
423
519
  published_at: string | null;
424
520
  created_at: string;
425
521
  updated_at: string;
426
- }
427
- /** Localized content for a project. */
428
- interface ProjectLocalizationResponse {
429
- id: string;
430
- locale_id: string;
431
- title: string;
432
- short_description: string | null;
433
- description: string | null;
522
+ /** Skill IDs linked to the project. Always present — empty array when none. */
523
+ skill_ids: string[];
524
+ localizations: ProjectLocalizationResponse[];
434
525
  }
435
526
  /** External link attached to a project. */
436
527
  interface ProjectLinkResponse {
@@ -447,12 +538,11 @@ interface ProjectMediaResponse {
447
538
  display_order: number;
448
539
  is_cover: boolean;
449
540
  }
450
- /** Full project detail including localizations, links, and media. */
541
+ /** Full project detail. Inherits `skill_ids` and `localizations` from
542
+ * {@link ProjectResponse}; adds links, media, and relation id sets. */
451
543
  interface ProjectDetailResponse extends ProjectResponse {
452
- localizations: ProjectLocalizationResponse[];
453
544
  links: ProjectLinkResponse[];
454
545
  media: ProjectMediaResponse[];
455
- skill_ids: string[];
456
546
  cv_entry_ids: string[];
457
547
  }
458
548
  /** Pagination and filter params for project listings. */
@@ -463,33 +553,79 @@ interface ProjectListParams extends PaginationParams {
463
553
  sortDir?: 'asc' | 'desc';
464
554
  /** Filter to featured projects only. */
465
555
  isFeatured?: boolean;
556
+ /**
557
+ * Optional locale code (e.g. `"en"`, `"de-AT"`). When set, each project's
558
+ * `localizations` array collapses to one entry resolved by the server's
559
+ * fallback chain (requested → site default → first available). Omit to
560
+ * receive every localization, which is the existing default.
561
+ *
562
+ * See ADR 0002 (`docs/adr/0002-locale-resolver.md`).
563
+ */
564
+ locale?: string;
466
565
  }
566
+ /** Options for project detail lookups (`get`, `getBySlug`). */
567
+ interface ProjectDetailParams {
568
+ /**
569
+ * Optional locale code. When set, `localizations` collapses to one entry
570
+ * resolved by the server's fallback chain. See ADR 0002.
571
+ */
572
+ locale?: string;
573
+ }
574
+ /**
575
+ * The set of HTTP status codes a Forja redirect may use.
576
+ *
577
+ * Pinned by issue #743. The same domain is enforced server-side by the
578
+ * `validate_redirect_status_code` DTO validator and the
579
+ * `chk_redirect_status_code` DB CHECK constraint, so consumers may rely
580
+ * on this value without runtime coercion.
581
+ */
582
+ type RedirectStatusCode = 301 | 302 | 307 | 308;
467
583
  /** A URL redirect rule. */
468
584
  interface RedirectResponse {
469
585
  id: string;
470
586
  site_id: string;
471
587
  source_path: string;
472
588
  destination_path: string;
473
- status_code: number;
589
+ status_code: RedirectStatusCode;
474
590
  is_active: boolean;
475
591
  description: string | null;
476
592
  created_at: string;
477
593
  updated_at: string;
478
594
  }
479
- /** Result of a redirect path lookup. */
595
+ /**
596
+ * Result of a redirect path lookup.
597
+ *
598
+ * Returned with HTTP 200 by `GET /sites/{site_id}/redirects/lookup` on
599
+ * match. No-match is signalled by **404** (RFC 7807 ProblemDetails) —
600
+ * never a 200 with a null body, never a `{ redirects: [] }` list.
601
+ */
480
602
  interface RedirectLookupResponse {
481
603
  destination_path: string;
482
- status_code: number;
604
+ status_code: RedirectStatusCode;
483
605
  }
484
606
  /** A locale configured for a site. */
485
607
  interface SiteLocaleResponse {
608
+ /** Composite-key half #1 — which site this assignment belongs to. */
609
+ site_id: string;
610
+ /** Composite-key half #2 — which locale is attached. */
486
611
  locale_id: string;
612
+ /** BCP-47 code, denormalised from `locales.code`. */
487
613
  code: string;
614
+ /** English-language label, denormalised from `locales.name`. */
488
615
  name: string;
616
+ /** Locale's own name, denormalised from `locales.native_name`. */
489
617
  native_name: string | null;
490
618
  direction: 'ltr' | 'rtl';
619
+ /** Exactly one row per site has this set to `true`. */
491
620
  is_default: boolean;
621
+ /** Configured-but-not-served when `false`. Consumers usually filter to `true`. */
492
622
  is_active: boolean;
623
+ /**
624
+ * Path segment selecting this locale. `null` for the default locale
625
+ * (served at the site root without a prefix).
626
+ */
627
+ url_prefix: string | null;
628
+ created_at: string;
493
629
  }
494
630
  /** Media item summary for list views (lighter than full MediaResponse). */
495
631
  interface MediaListItem {
@@ -770,7 +906,7 @@ declare class BlogsResource {
770
906
  * }
771
907
  * ```
772
908
  */
773
- getBySlug(slug: string): Promise<BlogDetailResponse | null>;
909
+ getBySlug(slug: string, params?: BlogDetailParams): Promise<BlogDetailResponse | null>;
774
910
  /**
775
911
  * Fetch a blog post's full detail by its UUID.
776
912
  *
@@ -784,7 +920,7 @@ declare class BlogsResource {
784
920
  * const blog = await forja.blogs.get('550e8400-e29b-41d4-a716-446655440000');
785
921
  * ```
786
922
  */
787
- get(idOrSlug: string): Promise<BlogDetailResponse | null>;
923
+ get(idOrSlug: string, params?: BlogDetailParams): Promise<BlogDetailResponse | null>;
788
924
  /**
789
925
  * Fetch the site's RSS feed as raw XML.
790
926
  *
@@ -822,7 +958,7 @@ declare class CvResource {
822
958
  * const programming = skills.data.filter(s => s.category === 'Programming');
823
959
  * ```
824
960
  */
825
- listSkills(params?: SearchablePaginationParams): Promise<PaginatedResult<SkillResponse>>;
961
+ listSkills(params?: SkillListParams): Promise<PaginatedResult<SkillResponse>>;
826
962
  /**
827
963
  * Fetch a skill by its UUID.
828
964
  *
@@ -831,7 +967,7 @@ declare class CvResource {
831
967
  * @param id - The skill's UUID.
832
968
  * @returns The skill, or `null` if not found.
833
969
  */
834
- getSkill(id: string): Promise<SkillResponse | null>;
970
+ getSkill(id: string, params?: SkillDetailParams): Promise<SkillResponse | null>;
835
971
  /**
836
972
  * Fetch a skill by its URL slug.
837
973
  *
@@ -846,7 +982,7 @@ declare class CvResource {
846
982
  * if (ts) console.log(`${ts.name}: level ${ts.proficiency_level}`);
847
983
  * ```
848
984
  */
849
- getSkillBySlug(slug: string): Promise<SkillResponse | null>;
985
+ getSkillBySlug(slug: string, params?: SkillDetailParams): Promise<SkillResponse | null>;
850
986
  /**
851
987
  * Fetch a paginated list of CV entries (work, education, certifications, etc.).
852
988
  *
@@ -863,6 +999,25 @@ declare class CvResource {
863
999
  * ```
864
1000
  */
865
1001
  listEntries(params?: CvEntryParams): Promise<PaginatedResult<CvEntryResponse>>;
1002
+ /**
1003
+ * Fetch a CV entry by its UUID.
1004
+ *
1005
+ * **Endpoint:** `GET /cv/{id}`
1006
+ *
1007
+ * @param id - The CV entry's UUID.
1008
+ * @param params - Optional locale resolver per ADR 0002.
1009
+ * @returns The CV entry, or `null` if not found.
1010
+ *
1011
+ * @example
1012
+ * ```ts
1013
+ * // All localizations (default)
1014
+ * const entry = await forja.cv.getEntry('entry-uuid');
1015
+ *
1016
+ * // Single-locale collapse
1017
+ * const enEntry = await forja.cv.getEntry('entry-uuid', { locale: 'en' });
1018
+ * ```
1019
+ */
1020
+ getEntry(id: string, params?: CvEntryDetailParams): Promise<CvEntryResponse | null>;
866
1021
  }
867
1022
  //#endregion
868
1023
  //#region src/resources/forms.d.ts
@@ -1029,7 +1184,7 @@ declare class LegalResource {
1029
1184
  * }
1030
1185
  * ```
1031
1186
  */
1032
- getBySlug(slug: string): Promise<LegalDocumentDetailResponse | null>;
1187
+ getBySlug(slug: string, params?: LegalDetailParams): Promise<LegalDocumentDetailResponse | null>;
1033
1188
  /**
1034
1189
  * Fetch the cookie consent document with its consent groups and items.
1035
1190
  *
@@ -1093,7 +1248,7 @@ declare class LegalResource {
1093
1248
  * }
1094
1249
  * ```
1095
1250
  */
1096
- getDetail(id: string): Promise<LegalDocumentFullDetailResponse | null>;
1251
+ getDetail(id: string, params?: LegalDetailParams): Promise<LegalDocumentFullDetailResponse | null>;
1097
1252
  /**
1098
1253
  * Fetch the version history of a legal document.
1099
1254
  *
@@ -1291,6 +1446,19 @@ interface PageListParams extends SearchablePaginationParams {
1291
1446
  /** Exclude pages with this status. */
1292
1447
  excludeStatus?: string;
1293
1448
  }
1449
+ /**
1450
+ * Options for page-detail lookups (`getDetail`). The list shape
1451
+ * (`PageResponse`) does not carry `localizations[]` today — adding it is
1452
+ * tracked as a separate canonicalization gap; until then the resolver
1453
+ * applies only to the detail endpoint.
1454
+ */
1455
+ interface PageDetailParams {
1456
+ /**
1457
+ * Optional locale code (e.g. `"en"`). When set, `localizations[]`
1458
+ * collapses to one resolved entry. See ADR 0002.
1459
+ */
1460
+ locale?: string;
1461
+ }
1294
1462
  /**
1295
1463
  * CMS page operations.
1296
1464
  *
@@ -1337,6 +1505,25 @@ declare class PagesResource {
1337
1505
  * ```
1338
1506
  */
1339
1507
  getByRoute(route: string): Promise<PageDetailResponse | null>;
1508
+ /**
1509
+ * Fetch a page's full detail (content localizations + computed OG image).
1510
+ *
1511
+ * **Endpoint:** `GET /pages/{id}/detail`
1512
+ *
1513
+ * @param id - The page's UUID.
1514
+ * @param params - Optional locale resolver (ADR 0002).
1515
+ * @returns The page detail, or `null` if not found.
1516
+ *
1517
+ * @example
1518
+ * ```ts
1519
+ * // Every localization (admin/editor use case)
1520
+ * const detail = await forja.pages.getDetail('page-uuid');
1521
+ *
1522
+ * // Server-side rendering — single locale
1523
+ * const en = await forja.pages.getDetail('page-uuid', { locale: 'en' });
1524
+ * ```
1525
+ */
1526
+ getDetail(id: string, params?: PageDetailParams): Promise<PageDetailResponse | null>;
1340
1527
  /**
1341
1528
  * Fetch all sections for a page.
1342
1529
  *
@@ -1438,7 +1625,7 @@ declare class ProjectsResource {
1438
1625
  * }
1439
1626
  * ```
1440
1627
  */
1441
- get(id: string): Promise<ProjectDetailResponse | null>;
1628
+ get(id: string, params?: ProjectDetailParams): Promise<ProjectDetailResponse | null>;
1442
1629
  /**
1443
1630
  * Fetch a project by its URL slug.
1444
1631
  *
@@ -1452,7 +1639,7 @@ declare class ProjectsResource {
1452
1639
  * const project = await forja.projects.getBySlug('forja-cms');
1453
1640
  * ```
1454
1641
  */
1455
- getBySlug(slug: string): Promise<ProjectResponse | null>;
1642
+ getBySlug(slug: string, params?: ProjectDetailParams): Promise<ProjectResponse | null>;
1456
1643
  }
1457
1644
  //#endregion
1458
1645
  //#region src/resources/redirects.d.ts
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./client-FailsuXv.cjs`);function t(e){return{head:e.code_injection_head,footer:e.code_injection_footer}}exports.ForjaAuthError=e.r,exports.ForjaClient=e.t,exports.ForjaError=e.i,exports.ForjaNetworkError=e.a,exports.ForjaNotFoundError=e.o,exports.ForjaPermissionError=e.s,exports.ForjaRateLimitError=e.c,exports.ForjaServerError=e.l,exports.ForjaValidationError=e.u,exports.renderCodeInjection=t,exports.validateSubmission=e.n;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./client-DVY5u6LJ.cjs`);function t(e){return{head:e.code_injection_head,footer:e.code_injection_footer}}exports.ForjaAuthError=e.r,exports.ForjaClient=e.t,exports.ForjaError=e.i,exports.ForjaNetworkError=e.a,exports.ForjaNotFoundError=e.o,exports.ForjaPermissionError=e.s,exports.ForjaRateLimitError=e.c,exports.ForjaServerError=e.l,exports.ForjaValidationError=e.u,exports.renderCodeInjection=t,exports.validateSubmission=e.n;
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as NavigationTree, A as CvEntryResponse, At as TranslationStatus, B as LegalGroupResponse, C as BlogResponse, Ct as SkillCategory, D as CodeInjection, Dt as TopContentItem, E as CategoryWithCountResponse, Et as TagResponse, F as LegalDocType, G as LocalizationResponse, H as LegalItemResponse, I as LegalDocumentDetailResponse, J as MediaResponse, K as MediaListItem, L as LegalDocumentFullDetailResponse, M as DocumentLocalizationResponse, N as ForjaClientConfig, O as ContentStatus, Ot as TrackPageviewRequest, P as LegalDocLocalizationResponse, Q as NavigationMenuResponse, R as LegalDocumentResponse, S as BlogListItem, St as SiteResponse, T as CategoryTree, Tt as SocialLinkResponse, U as LegalVersionResponse, V as LegalGroupWithItems, W as LocaleFilterParams, X as NavigationItemLocalizationResponse, Y as MediaVariantResponse, Z as NavigationItemResponse, _ as AnalyticsPageParams, _t as ReferrerItem, a as FormFieldValidation, at as Paginated, b as BlogDetailResponse, bt as SectionType, c as PublicFormDefinition, ct as ProjectDetailResponse, d as SubmitFormOptions, dt as ProjectListParams, et as PageDetailResponse, f as ValidationErrorMap, ft as ProjectLocalizationResponse, g as AnalyticsPageDetailResponse, gt as RedirectResponse, h as PaginatedResult, ht as RedirectLookupResponse, i as FormFieldType, it as PageType, j as CvEntryType, jt as TrendDataPoint, k as CvEntryParams, kt as TrackPageviewResponse, l as SelfServiceLookup, lt as ProjectLinkResponse, m as HttpClient, mt as ProjectResponse, n as FormBotProtection, nt as PageResponse, o as FormSubmitData, ot as PaginationMeta, p as validateSubmission, pt as ProjectMediaResponse, q as MediaListParams, r as FormFieldDefinition, rt as PageSectionResponse, s as FormSubmitResponse, st as PaginationParams, t as ForjaClient, tt as PageListItem, u as SelfServiceSubmission, ut as ProjectLinkType, v as AnalyticsReportParams, vt as SearchablePaginationParams, w as CategoryResponse, wt as SkillResponse, x as BlogDocumentResponse, xt as SiteLocaleResponse, y as AnalyticsReportResponse, yt as SectionLocalizationResponse, z as LegalDocumentWithGroups } from "./client-CvbFQ08l.cjs";
1
+ import { $ as NavigationTree, A as CvEntryResponse, At as TranslationStatus, B as LegalGroupResponse, C as BlogResponse, Ct as SkillCategory, D as CodeInjection, Dt as TopContentItem, E as CategoryWithCountResponse, Et as TagResponse, F as LegalDocType, G as LocalizationResponse, H as LegalItemResponse, I as LegalDocumentDetailResponse, J as MediaResponse, K as MediaListItem, L as LegalDocumentFullDetailResponse, M as DocumentLocalizationResponse, N as ForjaClientConfig, O as ContentStatus, Ot as TrackPageviewRequest, P as LegalDocLocalizationResponse, Q as NavigationMenuResponse, R as LegalDocumentResponse, S as BlogListItem, St as SiteResponse, T as CategoryTree, Tt as SocialLinkResponse, U as LegalVersionResponse, V as LegalGroupWithItems, W as LocaleFilterParams, X as NavigationItemLocalizationResponse, Y as MediaVariantResponse, Z as NavigationItemResponse, _ as AnalyticsPageParams, _t as ReferrerItem, a as FormFieldValidation, at as Paginated, b as BlogDetailResponse, bt as SectionType, c as PublicFormDefinition, ct as ProjectDetailResponse, d as SubmitFormOptions, dt as ProjectListParams, et as PageDetailResponse, f as ValidationErrorMap, ft as ProjectLocalizationResponse, g as AnalyticsPageDetailResponse, gt as RedirectResponse, h as PaginatedResult, ht as RedirectLookupResponse, i as FormFieldType, it as PageType, j as CvEntryType, jt as TrendDataPoint, k as CvEntryParams, kt as TrackPageviewResponse, l as SelfServiceLookup, lt as ProjectLinkResponse, m as HttpClient, mt as ProjectResponse, n as FormBotProtection, nt as PageResponse, o as FormSubmitData, ot as PaginationMeta, p as validateSubmission, pt as ProjectMediaResponse, q as MediaListParams, r as FormFieldDefinition, rt as PageSectionResponse, s as FormSubmitResponse, st as PaginationParams, t as ForjaClient, tt as PageListItem, u as SelfServiceSubmission, ut as ProjectLinkType, v as AnalyticsReportParams, vt as SearchablePaginationParams, w as CategoryResponse, wt as SkillResponse, x as BlogDocumentResponse, xt as SiteLocaleResponse, y as AnalyticsReportResponse, yt as SectionLocalizationResponse, z as LegalDocumentWithGroups } from "./client-CUbs1rkJ.cjs";
2
2
 
3
3
  //#region src/code-injection.d.ts
4
4
  /**
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as NavigationTree, A as CvEntryResponse, At as TranslationStatus, B as LegalGroupResponse, C as BlogResponse, Ct as SkillCategory, D as CodeInjection, Dt as TopContentItem, E as CategoryWithCountResponse, Et as TagResponse, F as LegalDocType, G as LocalizationResponse, H as LegalItemResponse, I as LegalDocumentDetailResponse, J as MediaResponse, K as MediaListItem, L as LegalDocumentFullDetailResponse, M as DocumentLocalizationResponse, N as ForjaClientConfig, O as ContentStatus, Ot as TrackPageviewRequest, P as LegalDocLocalizationResponse, Q as NavigationMenuResponse, R as LegalDocumentResponse, S as BlogListItem, St as SiteResponse, T as CategoryTree, Tt as SocialLinkResponse, U as LegalVersionResponse, V as LegalGroupWithItems, W as LocaleFilterParams, X as NavigationItemLocalizationResponse, Y as MediaVariantResponse, Z as NavigationItemResponse, _ as AnalyticsPageParams, _t as ReferrerItem, a as FormFieldValidation, at as Paginated, b as BlogDetailResponse, bt as SectionType, c as PublicFormDefinition, ct as ProjectDetailResponse, d as SubmitFormOptions, dt as ProjectListParams, et as PageDetailResponse, f as ValidationErrorMap, ft as ProjectLocalizationResponse, g as AnalyticsPageDetailResponse, gt as RedirectResponse, h as PaginatedResult, ht as RedirectLookupResponse, i as FormFieldType, it as PageType, j as CvEntryType, jt as TrendDataPoint, k as CvEntryParams, kt as TrackPageviewResponse, l as SelfServiceLookup, lt as ProjectLinkResponse, m as HttpClient, mt as ProjectResponse, n as FormBotProtection, nt as PageResponse, o as FormSubmitData, ot as PaginationMeta, p as validateSubmission, pt as ProjectMediaResponse, q as MediaListParams, r as FormFieldDefinition, rt as PageSectionResponse, s as FormSubmitResponse, st as PaginationParams, t as ForjaClient, tt as PageListItem, u as SelfServiceSubmission, ut as ProjectLinkType, v as AnalyticsReportParams, vt as SearchablePaginationParams, w as CategoryResponse, wt as SkillResponse, x as BlogDocumentResponse, xt as SiteLocaleResponse, y as AnalyticsReportResponse, yt as SectionLocalizationResponse, z as LegalDocumentWithGroups } from "./client-BTbTifpr.mjs";
1
+ import { $ as NavigationTree, A as CvEntryResponse, At as TranslationStatus, B as LegalGroupResponse, C as BlogResponse, Ct as SkillCategory, D as CodeInjection, Dt as TopContentItem, E as CategoryWithCountResponse, Et as TagResponse, F as LegalDocType, G as LocalizationResponse, H as LegalItemResponse, I as LegalDocumentDetailResponse, J as MediaResponse, K as MediaListItem, L as LegalDocumentFullDetailResponse, M as DocumentLocalizationResponse, N as ForjaClientConfig, O as ContentStatus, Ot as TrackPageviewRequest, P as LegalDocLocalizationResponse, Q as NavigationMenuResponse, R as LegalDocumentResponse, S as BlogListItem, St as SiteResponse, T as CategoryTree, Tt as SocialLinkResponse, U as LegalVersionResponse, V as LegalGroupWithItems, W as LocaleFilterParams, X as NavigationItemLocalizationResponse, Y as MediaVariantResponse, Z as NavigationItemResponse, _ as AnalyticsPageParams, _t as ReferrerItem, a as FormFieldValidation, at as Paginated, b as BlogDetailResponse, bt as SectionType, c as PublicFormDefinition, ct as ProjectDetailResponse, d as SubmitFormOptions, dt as ProjectListParams, et as PageDetailResponse, f as ValidationErrorMap, ft as ProjectLocalizationResponse, g as AnalyticsPageDetailResponse, gt as RedirectResponse, h as PaginatedResult, ht as RedirectLookupResponse, i as FormFieldType, it as PageType, j as CvEntryType, jt as TrendDataPoint, k as CvEntryParams, kt as TrackPageviewResponse, l as SelfServiceLookup, lt as ProjectLinkResponse, m as HttpClient, mt as ProjectResponse, n as FormBotProtection, nt as PageResponse, o as FormSubmitData, ot as PaginationMeta, p as validateSubmission, pt as ProjectMediaResponse, q as MediaListParams, r as FormFieldDefinition, rt as PageSectionResponse, s as FormSubmitResponse, st as PaginationParams, t as ForjaClient, tt as PageListItem, u as SelfServiceSubmission, ut as ProjectLinkType, v as AnalyticsReportParams, vt as SearchablePaginationParams, w as CategoryResponse, wt as SkillResponse, x as BlogDocumentResponse, xt as SiteLocaleResponse, y as AnalyticsReportResponse, yt as SectionLocalizationResponse, z as LegalDocumentWithGroups } from "./client-HGIKi26O.mjs";
2
2
 
3
3
  //#region src/code-injection.d.ts
4
4
  /**
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{a as e,c as t,i as n,l as r,n as i,o as a,r as o,s,t as c,u as l}from"./client-C-2JZF6o.mjs";function u(e){return{head:e.code_injection_head,footer:e.code_injection_footer}}export{o as ForjaAuthError,c as ForjaClient,n as ForjaError,e as ForjaNetworkError,a as ForjaNotFoundError,s as ForjaPermissionError,t as ForjaRateLimitError,r as ForjaServerError,l as ForjaValidationError,u as renderCodeInjection,i as validateSubmission};
1
+ import{a as e,c as t,i as n,l as r,n as i,o as a,r as o,s,t as c,u as l}from"./client-CWajZ1IX.mjs";function u(e){return{head:e.code_injection_head,footer:e.code_injection_footer}}export{o as ForjaAuthError,c as ForjaClient,n as ForjaError,e as ForjaNetworkError,a as ForjaNotFoundError,s as ForjaPermissionError,t as ForjaRateLimitError,r as ForjaServerError,l as ForjaValidationError,u as renderCodeInjection,i as validateSubmission};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forjacms/client",
3
- "version": "1.7.1",
3
+ "version": "1.7.3",
4
4
  "description": "Typed TypeScript SDK for the Forja CMS content API",
5
5
  "author": "Dominik Dorfstetter <dominik@dorfstetter.at>",
6
6
  "main": "dist/index.cjs",
@@ -66,7 +66,7 @@
66
66
  "@angular/core": "^21.2.9",
67
67
  "@vitest/coverage-v8": "^4.0.18",
68
68
  "rxjs": "^7.8.2",
69
- "tsdown": "^0.21.4",
69
+ "tsdown": "^0.22.0",
70
70
  "typescript": "^6.0.2",
71
71
  "vitest": "^4.0.18",
72
72
  "zone.js": "^0.16.1"
@@ -1 +0,0 @@
1
- var e=class extends Error{constructor(e,t){super(e),this.code=t,this.name=`ForjaError`}},t=class extends e{constructor(e=`Invalid or missing API key`){super(e,`AUTH_ERROR`),this.name=`ForjaAuthError`}},n=class extends e{constructor(e=`Insufficient permissions`){super(e,`PERMISSION_ERROR`),this.name=`ForjaPermissionError`}},r=class extends e{constructor(e=`Resource not found`){super(e,`NOT_FOUND`),this.name=`ForjaNotFoundError`}},i=class extends e{constructor(e=`Rate limit exceeded`,t){super(e,`RATE_LIMIT`),this.retryAfter=t,this.name=`ForjaRateLimitError`}},a=class extends e{constructor(e=`Validation error`,t){super(e,`VALIDATION_ERROR`),this.details=t,this.name=`ForjaValidationError`}},o=class extends e{constructor(e=`Internal server error`,t=500){super(e,`SERVER_ERROR`),this.status=t,this.name=`ForjaServerError`}},s=class extends e{constructor(e=`Network error`,t){super(e,`NETWORK_ERROR`),this.cause=t,this.name=`ForjaNetworkError`}};function c(e,t,n){let r=e.replace(/\/+$/,``),i=t.startsWith(`/`)?t:`/${t}`,a=new URL(`${r}${i}`);if(n)for(let[e,t]of Object.entries(n))t!==void 0&&a.searchParams.set(e,t);return a.toString()}function l(e){let t={};for(let[n,r]of Object.entries(e))r!=null&&(t[u(n)]=String(r));return t}function u(e){return e.replace(/[A-Z]/g,e=>`_${e.toLowerCase()}`)}async function d(e){let s;try{let t=await e.json();s=t.detail||t.message||t.title||e.statusText}catch{s=e.statusText}switch(e.status){case 401:throw new t(s);case 403:throw new n(s);case 404:throw new r(s);case 422:throw new a(s);case 429:{let t=e.headers.get(`retry-after`);throw new i(s,t?parseInt(t,10):void 0)}default:throw e.status,new o(s,e.status)}}function f(e){let t=e.fetch??globalThis.fetch,n=e.baseUrl.replace(/\/+$/,``),r={"X-API-Key":e.apiKey,"Content-Type":`application/json`};e.siteDomain&&(r[`X-Site-Domain`]=e.siteDomain);async function i(e,i,a,o){let l=c(n,i,a),u;try{u=await t(l,{method:e,headers:r,body:o===void 0?void 0:JSON.stringify(o)})}catch(e){throw new s(`Failed to connect to the Forja API`,e instanceof Error?e:void 0)}return u.ok?u:d(u)}return{get:(e,t)=>i(`GET`,e,t).then(e=>e.json()),getText:(e,t)=>i(`GET`,e,t).then(e=>e.text()),post:(e,t)=>i(`POST`,e,void 0,t).then(e=>e.json()),delete:e=>i(`DELETE`,e).then(()=>void 0)}}function p(e,t,n){return{data:e,meta:t,async fetchNext(){if(t.page>=t.total_pages)return null;let e=await n(t.page+1);return p(e.data,e.meta,n)},async fetchAll(){let r=[...e],i=t.page;for(;i<t.total_pages;){i++;let e=await n(i);r.push(...e.data)}return r},async*[Symbol.asyncIterator](){yield{data:e,meta:t};let r=t.page;for(;r<t.total_pages;)r++,yield await n(r)}}}var m=class{constructor(e,t){this.http=e,this.siteId=t}async trackPageview(e){return this.http.post(`/sites/${this.siteId}/analytics/pageview`,e)}async getReport(e){let t=e?l({days:e.days,top_n:e.topN,start_date:e.startDate,end_date:e.endDate}):void 0;return this.http.get(`/sites/${this.siteId}/analytics/report`,t)}async getPageAnalytics(e){let t=l({path:e.path,days:e.days,start_date:e.startDate,end_date:e.endDate});return this.http.get(`/sites/${this.siteId}/analytics/report/page`,t)}},h=class{constructor(e,t){this.http=e,this.siteId=t}async listPublished(e){let t=e?l(e):void 0,n=e?.localeId?{locale_id:e.localeId}:{},r=async e=>this.http.get(`/sites/${this.siteId}/blogs/published`,{...t,...n,page:String(e)}),i=await r(e?.page??1);return p(i.data,i.meta,r)}async listByCategory(e,t){let n=t?l(t):void 0,r=t?.localeId?{locale_id:t.localeId}:{},i=async t=>this.http.get(`/sites/${this.siteId}/blogs/published/category/${encodeURIComponent(e)}`,{...n,...r,page:String(t)}),a=await i(t?.page??1);return p(a.data,a.meta,i)}async listFeatured(e){return this.http.get(`/sites/${this.siteId}/blogs/featured`,e?.limit===void 0?void 0:{limit:String(e.limit)})}async listSimilar(e,t){return this.http.get(`/sites/${this.siteId}/blogs/${encodeURIComponent(e)}/similar`,t?.limit===void 0?void 0:{limit:String(t.limit)})}async getBySlug(e){try{let t=await this.http.get(`/sites/${this.siteId}/blogs/by-slug/${encodeURIComponent(e)}`);return await this.http.get(`/blogs/${t.id}/detail`)}catch(e){if(e instanceof r)return null;throw e}}async get(e){try{return await this.http.get(`/blogs/${encodeURIComponent(e)}/detail`)}catch(e){if(e instanceof r)return null;throw e}}async rss(){return this.http.getText(`/sites/${this.siteId}/feed.rss`)}},g=class{constructor(e,t){this.http=e,this.siteId=t}async listSkills(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/skills`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async getSkill(e){try{return await this.http.get(`/skills/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getSkillBySlug(e){try{return await this.http.get(`/skills/by-slug/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async listEntries(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/cv`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}},_=class{constructor(e){this.http=e}async getForm(e,t){let n=t?.locale?{locale:t.locale}:void 0;return this.http.get(`/public/forms/${encodeURIComponent(e)}`,n)}async submitForm(e,t,n={}){return this.http.post(`/public/forms/${encodeURIComponent(e)}/submit`,{data:t,consent_given:n.consentGiven??!1,bot_protection_token:n.botProtectionToken})}async lookupSubmission(e){return this.http.post(`/public/submissions/lookup`,{reference_code:e})}async getSubmission(e){return this.http.get(`/public/submissions/${encodeURIComponent(e)}`)}async deleteSubmission(e){return this.http.delete(`/public/submissions/${encodeURIComponent(e)}`)}};const v=/^[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}$/;function y(e,t){let n={};for(let r of e.fields){let e=t[r.label],i=b(r,e);i&&(n[r.label]=i)}return n}function b(e,t){let n=t,r=n==null||typeof n==`string`&&n===``||Array.isArray(n)&&n.length===0;if((e.is_required||e.validation.required)&&r)return`${e.label} is required`;if(r)return null;switch(e.field_type){case`text`:case`textarea`:case`custom`:if(typeof n!=`string`)return`Must be a string`;if(e.validation.min_length!==void 0&&n.length<e.validation.min_length)return`Must be at least ${e.validation.min_length} characters`;if(e.validation.max_length!==void 0&&n.length>e.validation.max_length)return`Must be at most ${e.validation.max_length} characters`;if(e.validation.pattern)try{if(!new RegExp(e.validation.pattern).test(n))return`Value does not match the required pattern`}catch{return`Field has an invalid validation pattern`}return null;case`email`:return typeof n==`string`?v.test(n)?null:`Invalid email format`:`Must be a string`;case`number`:{let t=typeof n==`number`?n:Number(n);return Number.isNaN(t)?`Must be a number`:e.validation.min!==void 0&&t<e.validation.min?`Must be at least ${e.validation.min}`:e.validation.max!==void 0&&t>e.validation.max?`Must be at most ${e.validation.max}`:null}case`date`:return typeof n!=`string`||Number.isNaN(Date.parse(n))&&!/^\d{4}-\d{2}-\d{2}$/.test(n)?`Invalid date format`:null;case`select`:case`radio`:case`checkbox`:return null}}var x=class{constructor(e,t){this.http=e,this.siteId=t}async list(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/legal`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async get(e){try{return await this.http.get(`/legal/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getBySlug(e){try{return await this.http.get(`/sites/${this.siteId}/legal/by-slug/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getCookieConsent(){try{return await this.http.get(`/sites/${this.siteId}/legal/cookie-consent`)}catch(e){if(e instanceof r)return null;throw e}}async getGroups(e){return this.http.get(`/legal/${encodeURIComponent(e)}/groups`)}async getGroupItems(e){return this.http.get(`/legal/groups/${encodeURIComponent(e)}/items`)}async getDetail(e){try{return await this.http.get(`/legal/${encodeURIComponent(e)}/detail`)}catch(e){if(e instanceof r)return null;throw e}}async listVersions(e){return this.http.get(`/legal/${encodeURIComponent(e)}/versions`)}},S=class{constructor(e,t){this.http=e,this.siteId=t}async list(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/media`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async get(e){try{return await this.http.get(`/media/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}},C=class{constructor(e,t){this.http=e,this.siteId=t}async listMenus(){return this.http.get(`/sites/${this.siteId}/menus`)}async getMenu(e){try{return await this.http.get(`/menus/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getMenuBySlug(e){try{return await this.http.get(`/sites/${this.siteId}/menus/slug/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getTree(e,t){return this.http.get(`/menus/${encodeURIComponent(e)}/tree`,t?.locale?{locale:t.locale}:void 0)}async listItems(e){return this.http.get(`/menus/${encodeURIComponent(e)}/items`)}async getItem(e){try{return await this.http.get(`/navigation/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}},w=class{constructor(e,t){this.http=e,this.siteId=t}async list(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/pages`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async getByRoute(e){let t=e.startsWith(`/`)?e.slice(1):e;try{return await this.http.get(`/sites/${this.siteId}/pages/by-route/${encodeURIComponent(t)}`)}catch(e){if(e instanceof r)return null;throw e}}async getSections(e){return this.http.get(`/pages/${encodeURIComponent(e)}/sections`)}async getSectionLocalizations(e){return this.http.get(`/pages/sections/${encodeURIComponent(e)}/localizations`)}async getPageSectionLocalizations(e){return this.http.get(`/pages/${encodeURIComponent(e)}/sections/localizations`)}},T=class{constructor(e,t){this.http=e,this.siteId=t}async listPublished(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/projects/public`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async get(e){try{return await this.http.get(`/projects/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getBySlug(e){try{return await this.http.get(`/sites/${this.siteId}/projects/by-slug/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}},E=class{constructor(e,t){this.http=e,this.siteId=t}async lookup(e){try{return await this.http.get(`/sites/${this.siteId}/redirects/lookup`,{path:e})}catch(e){if(e instanceof r)return null;throw e}}},D=class{constructor(e,t){this.http=e,this.siteId=t}async get(){return this.http.get(`/sites/${this.siteId}`)}async listLocales(){return this.http.get(`/sites/${this.siteId}/locales`)}async getCodeInjection(){let e=await this.http.get(`/sites/${this.siteId}/settings`);return{code_injection_head:e.code_injection_head??``,code_injection_footer:e.code_injection_footer??``}}},O=class{constructor(e,t){this.http=e,this.siteId=t}async list(){return this.http.get(`/sites/${this.siteId}/social`)}},k=class{constructor(e,t){this.http=e,this.siteId=t}async listTags(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/tags`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async listCategories(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/categories`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async getCategoriesWithBlogCounts(){return this.http.get(`/sites/${this.siteId}/categories/blog-counts`)}async getContentTags(e){return this.http.get(`/content/${encodeURIComponent(e)}/tags`)}async getContentCategories(e){return this.http.get(`/content/${encodeURIComponent(e)}/categories`)}async getTag(e){try{return await this.http.get(`/tags/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getTagBySlug(e){try{return await this.http.get(`/tags/by-slug/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getCategory(e){try{return await this.http.get(`/categories/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getCategoryChildren(e){return this.http.get(`/categories/${encodeURIComponent(e)}/children`)}},A=class{constructor(e){let t=f(e);this.blogs=new h(t,e.siteId),this.pages=new w(t,e.siteId),this.navigation=new C(t,e.siteId),this.taxonomy=new k(t,e.siteId),this.analytics=new m(t,e.siteId),this.cv=new g(t,e.siteId),this.legal=new x(t,e.siteId),this.projects=new T(t,e.siteId),this.redirects=new E(t,e.siteId),this.site=new D(t,e.siteId),this.media=new S(t,e.siteId),this.social=new O(t,e.siteId),this.forms=new _(t)}};export{s as a,i as c,e as i,o as l,y as n,r as o,t as r,n as s,A as t,a as u};
@@ -1 +0,0 @@
1
- var e=class extends Error{constructor(e,t){super(e),this.code=t,this.name=`ForjaError`}},t=class extends e{constructor(e=`Invalid or missing API key`){super(e,`AUTH_ERROR`),this.name=`ForjaAuthError`}},n=class extends e{constructor(e=`Insufficient permissions`){super(e,`PERMISSION_ERROR`),this.name=`ForjaPermissionError`}},r=class extends e{constructor(e=`Resource not found`){super(e,`NOT_FOUND`),this.name=`ForjaNotFoundError`}},i=class extends e{constructor(e=`Rate limit exceeded`,t){super(e,`RATE_LIMIT`),this.retryAfter=t,this.name=`ForjaRateLimitError`}},a=class extends e{constructor(e=`Validation error`,t){super(e,`VALIDATION_ERROR`),this.details=t,this.name=`ForjaValidationError`}},o=class extends e{constructor(e=`Internal server error`,t=500){super(e,`SERVER_ERROR`),this.status=t,this.name=`ForjaServerError`}},s=class extends e{constructor(e=`Network error`,t){super(e,`NETWORK_ERROR`),this.cause=t,this.name=`ForjaNetworkError`}};function c(e,t,n){let r=e.replace(/\/+$/,``),i=t.startsWith(`/`)?t:`/${t}`,a=new URL(`${r}${i}`);if(n)for(let[e,t]of Object.entries(n))t!==void 0&&a.searchParams.set(e,t);return a.toString()}function l(e){let t={};for(let[n,r]of Object.entries(e))r!=null&&(t[u(n)]=String(r));return t}function u(e){return e.replace(/[A-Z]/g,e=>`_${e.toLowerCase()}`)}async function d(e){let s;try{let t=await e.json();s=t.detail||t.message||t.title||e.statusText}catch{s=e.statusText}switch(e.status){case 401:throw new t(s);case 403:throw new n(s);case 404:throw new r(s);case 422:throw new a(s);case 429:{let t=e.headers.get(`retry-after`);throw new i(s,t?parseInt(t,10):void 0)}default:throw e.status,new o(s,e.status)}}function f(e){let t=e.fetch??globalThis.fetch,n=e.baseUrl.replace(/\/+$/,``),r={"X-API-Key":e.apiKey,"Content-Type":`application/json`};e.siteDomain&&(r[`X-Site-Domain`]=e.siteDomain);async function i(e,i,a,o){let l=c(n,i,a),u;try{u=await t(l,{method:e,headers:r,body:o===void 0?void 0:JSON.stringify(o)})}catch(e){throw new s(`Failed to connect to the Forja API`,e instanceof Error?e:void 0)}return u.ok?u:d(u)}return{get:(e,t)=>i(`GET`,e,t).then(e=>e.json()),getText:(e,t)=>i(`GET`,e,t).then(e=>e.text()),post:(e,t)=>i(`POST`,e,void 0,t).then(e=>e.json()),delete:e=>i(`DELETE`,e).then(()=>void 0)}}function p(e,t,n){return{data:e,meta:t,async fetchNext(){if(t.page>=t.total_pages)return null;let e=await n(t.page+1);return p(e.data,e.meta,n)},async fetchAll(){let r=[...e],i=t.page;for(;i<t.total_pages;){i++;let e=await n(i);r.push(...e.data)}return r},async*[Symbol.asyncIterator](){yield{data:e,meta:t};let r=t.page;for(;r<t.total_pages;)r++,yield await n(r)}}}var m=class{constructor(e,t){this.http=e,this.siteId=t}async trackPageview(e){return this.http.post(`/sites/${this.siteId}/analytics/pageview`,e)}async getReport(e){let t=e?l({days:e.days,top_n:e.topN,start_date:e.startDate,end_date:e.endDate}):void 0;return this.http.get(`/sites/${this.siteId}/analytics/report`,t)}async getPageAnalytics(e){let t=l({path:e.path,days:e.days,start_date:e.startDate,end_date:e.endDate});return this.http.get(`/sites/${this.siteId}/analytics/report/page`,t)}},h=class{constructor(e,t){this.http=e,this.siteId=t}async listPublished(e){let t=e?l(e):void 0,n=e?.localeId?{locale_id:e.localeId}:{},r=async e=>this.http.get(`/sites/${this.siteId}/blogs/published`,{...t,...n,page:String(e)}),i=await r(e?.page??1);return p(i.data,i.meta,r)}async listByCategory(e,t){let n=t?l(t):void 0,r=t?.localeId?{locale_id:t.localeId}:{},i=async t=>this.http.get(`/sites/${this.siteId}/blogs/published/category/${encodeURIComponent(e)}`,{...n,...r,page:String(t)}),a=await i(t?.page??1);return p(a.data,a.meta,i)}async listFeatured(e){return this.http.get(`/sites/${this.siteId}/blogs/featured`,e?.limit===void 0?void 0:{limit:String(e.limit)})}async listSimilar(e,t){return this.http.get(`/sites/${this.siteId}/blogs/${encodeURIComponent(e)}/similar`,t?.limit===void 0?void 0:{limit:String(t.limit)})}async getBySlug(e){try{let t=await this.http.get(`/sites/${this.siteId}/blogs/by-slug/${encodeURIComponent(e)}`);return await this.http.get(`/blogs/${t.id}/detail`)}catch(e){if(e instanceof r)return null;throw e}}async get(e){try{return await this.http.get(`/blogs/${encodeURIComponent(e)}/detail`)}catch(e){if(e instanceof r)return null;throw e}}async rss(){return this.http.getText(`/sites/${this.siteId}/feed.rss`)}},g=class{constructor(e,t){this.http=e,this.siteId=t}async listSkills(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/skills`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async getSkill(e){try{return await this.http.get(`/skills/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getSkillBySlug(e){try{return await this.http.get(`/skills/by-slug/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async listEntries(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/cv`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}},_=class{constructor(e){this.http=e}async getForm(e,t){let n=t?.locale?{locale:t.locale}:void 0;return this.http.get(`/public/forms/${encodeURIComponent(e)}`,n)}async submitForm(e,t,n={}){return this.http.post(`/public/forms/${encodeURIComponent(e)}/submit`,{data:t,consent_given:n.consentGiven??!1,bot_protection_token:n.botProtectionToken})}async lookupSubmission(e){return this.http.post(`/public/submissions/lookup`,{reference_code:e})}async getSubmission(e){return this.http.get(`/public/submissions/${encodeURIComponent(e)}`)}async deleteSubmission(e){return this.http.delete(`/public/submissions/${encodeURIComponent(e)}`)}};const v=/^[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}$/;function y(e,t){let n={};for(let r of e.fields){let e=t[r.label],i=b(r,e);i&&(n[r.label]=i)}return n}function b(e,t){let n=t,r=n==null||typeof n==`string`&&n===``||Array.isArray(n)&&n.length===0;if((e.is_required||e.validation.required)&&r)return`${e.label} is required`;if(r)return null;switch(e.field_type){case`text`:case`textarea`:case`custom`:if(typeof n!=`string`)return`Must be a string`;if(e.validation.min_length!==void 0&&n.length<e.validation.min_length)return`Must be at least ${e.validation.min_length} characters`;if(e.validation.max_length!==void 0&&n.length>e.validation.max_length)return`Must be at most ${e.validation.max_length} characters`;if(e.validation.pattern)try{if(!new RegExp(e.validation.pattern).test(n))return`Value does not match the required pattern`}catch{return`Field has an invalid validation pattern`}return null;case`email`:return typeof n==`string`?v.test(n)?null:`Invalid email format`:`Must be a string`;case`number`:{let t=typeof n==`number`?n:Number(n);return Number.isNaN(t)?`Must be a number`:e.validation.min!==void 0&&t<e.validation.min?`Must be at least ${e.validation.min}`:e.validation.max!==void 0&&t>e.validation.max?`Must be at most ${e.validation.max}`:null}case`date`:return typeof n!=`string`||Number.isNaN(Date.parse(n))&&!/^\d{4}-\d{2}-\d{2}$/.test(n)?`Invalid date format`:null;case`select`:case`radio`:case`checkbox`:return null}}var x=class{constructor(e,t){this.http=e,this.siteId=t}async list(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/legal`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async get(e){try{return await this.http.get(`/legal/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getBySlug(e){try{return await this.http.get(`/sites/${this.siteId}/legal/by-slug/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getCookieConsent(){try{return await this.http.get(`/sites/${this.siteId}/legal/cookie-consent`)}catch(e){if(e instanceof r)return null;throw e}}async getGroups(e){return this.http.get(`/legal/${encodeURIComponent(e)}/groups`)}async getGroupItems(e){return this.http.get(`/legal/groups/${encodeURIComponent(e)}/items`)}async getDetail(e){try{return await this.http.get(`/legal/${encodeURIComponent(e)}/detail`)}catch(e){if(e instanceof r)return null;throw e}}async listVersions(e){return this.http.get(`/legal/${encodeURIComponent(e)}/versions`)}},S=class{constructor(e,t){this.http=e,this.siteId=t}async list(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/media`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async get(e){try{return await this.http.get(`/media/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}},C=class{constructor(e,t){this.http=e,this.siteId=t}async listMenus(){return this.http.get(`/sites/${this.siteId}/menus`)}async getMenu(e){try{return await this.http.get(`/menus/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getMenuBySlug(e){try{return await this.http.get(`/sites/${this.siteId}/menus/slug/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getTree(e,t){return this.http.get(`/menus/${encodeURIComponent(e)}/tree`,t?.locale?{locale:t.locale}:void 0)}async listItems(e){return this.http.get(`/menus/${encodeURIComponent(e)}/items`)}async getItem(e){try{return await this.http.get(`/navigation/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}},w=class{constructor(e,t){this.http=e,this.siteId=t}async list(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/pages`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async getByRoute(e){let t=e.startsWith(`/`)?e.slice(1):e;try{return await this.http.get(`/sites/${this.siteId}/pages/by-route/${encodeURIComponent(t)}`)}catch(e){if(e instanceof r)return null;throw e}}async getSections(e){return this.http.get(`/pages/${encodeURIComponent(e)}/sections`)}async getSectionLocalizations(e){return this.http.get(`/pages/sections/${encodeURIComponent(e)}/localizations`)}async getPageSectionLocalizations(e){return this.http.get(`/pages/${encodeURIComponent(e)}/sections/localizations`)}},T=class{constructor(e,t){this.http=e,this.siteId=t}async listPublished(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/projects/public`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async get(e){try{return await this.http.get(`/projects/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getBySlug(e){try{return await this.http.get(`/sites/${this.siteId}/projects/by-slug/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}},E=class{constructor(e,t){this.http=e,this.siteId=t}async lookup(e){try{return await this.http.get(`/sites/${this.siteId}/redirects/lookup`,{path:e})}catch(e){if(e instanceof r)return null;throw e}}},D=class{constructor(e,t){this.http=e,this.siteId=t}async get(){return this.http.get(`/sites/${this.siteId}`)}async listLocales(){return this.http.get(`/sites/${this.siteId}/locales`)}async getCodeInjection(){let e=await this.http.get(`/sites/${this.siteId}/settings`);return{code_injection_head:e.code_injection_head??``,code_injection_footer:e.code_injection_footer??``}}},O=class{constructor(e,t){this.http=e,this.siteId=t}async list(){return this.http.get(`/sites/${this.siteId}/social`)}},k=class{constructor(e,t){this.http=e,this.siteId=t}async listTags(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/tags`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async listCategories(e){let t=e?l(e):void 0,n=async e=>this.http.get(`/sites/${this.siteId}/categories`,{...t,page:String(e)}),r=await n(e?.page??1);return p(r.data,r.meta,n)}async getCategoriesWithBlogCounts(){return this.http.get(`/sites/${this.siteId}/categories/blog-counts`)}async getContentTags(e){return this.http.get(`/content/${encodeURIComponent(e)}/tags`)}async getContentCategories(e){return this.http.get(`/content/${encodeURIComponent(e)}/categories`)}async getTag(e){try{return await this.http.get(`/tags/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getTagBySlug(e){try{return await this.http.get(`/tags/by-slug/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getCategory(e){try{return await this.http.get(`/categories/${encodeURIComponent(e)}`)}catch(e){if(e instanceof r)return null;throw e}}async getCategoryChildren(e){return this.http.get(`/categories/${encodeURIComponent(e)}/children`)}},A=class{constructor(e){let t=f(e);this.blogs=new h(t,e.siteId),this.pages=new w(t,e.siteId),this.navigation=new C(t,e.siteId),this.taxonomy=new k(t,e.siteId),this.analytics=new m(t,e.siteId),this.cv=new g(t,e.siteId),this.legal=new x(t,e.siteId),this.projects=new T(t,e.siteId),this.redirects=new E(t,e.siteId),this.site=new D(t,e.siteId),this.media=new S(t,e.siteId),this.social=new O(t,e.siteId),this.forms=new _(t)}};Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return e}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return y}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return t}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return n}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return A}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return a}});