@forjacms/client 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,282 @@
1
+ # @forjacms/client
2
+
3
+ Typed TypeScript SDK for the Forja CMS content API. Works in Node.js, browsers, and edge runtimes.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @forjacms/client
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { ForjaClient } from '@forjacms/client';
15
+
16
+ const forja = new ForjaClient({
17
+ baseUrl: 'https://cms.example.com/api/v1',
18
+ apiKey: 'dk_read_...',
19
+ siteId: 'your-site-uuid',
20
+ });
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ### Blogs
26
+
27
+ ```typescript
28
+ // Published blogs (paginated)
29
+ const blogs = await forja.blogs.listPublished({ page: 1, pageSize: 10 });
30
+ console.log(blogs.data); // BlogListItem[]
31
+ console.log(blogs.meta); // { page, page_size, total_pages, total_items }
32
+
33
+ // By category
34
+ const techBlogs = await forja.blogs.listByCategory('tech', { page: 1 });
35
+
36
+ // Featured blogs
37
+ const featured = await forja.blogs.listFeatured({ limit: 5 });
38
+
39
+ // Similar posts
40
+ const similar = await forja.blogs.listSimilar('blog-uuid', { limit: 3 });
41
+
42
+ // Single blog (returns null if not found)
43
+ const blog = await forja.blogs.get('my-blog-slug');
44
+
45
+ // RSS feed
46
+ const rss = await forja.blogs.rss();
47
+ ```
48
+
49
+ ### Pages
50
+
51
+ ```typescript
52
+ // Get page by route (returns null if not found)
53
+ const page = await forja.pages.getByRoute('/about');
54
+
55
+ // Page sections
56
+ const sections = await forja.pages.getSections('page-uuid');
57
+
58
+ // Section translations
59
+ const translations = await forja.pages.getSectionLocalizations('section-uuid');
60
+ ```
61
+
62
+ ### Navigation
63
+
64
+ ```typescript
65
+ // All menus
66
+ const menus = await forja.navigation.listMenus();
67
+
68
+ // Single menu by slug
69
+ const primary = await forja.navigation.getMenuBySlug('primary');
70
+
71
+ // Navigation tree (with optional locale)
72
+ const tree = await forja.navigation.getTree('menu-uuid', { locale: 'de' });
73
+
74
+ // Menu items
75
+ const items = await forja.navigation.listItems('menu-uuid');
76
+ ```
77
+
78
+ ### Taxonomy
79
+
80
+ ```typescript
81
+ // Tags (paginated, searchable)
82
+ const tags = await forja.taxonomy.listTags({ search: 'rust', sortBy: 'slug' });
83
+
84
+ // Categories
85
+ const categories = await forja.taxonomy.listCategories();
86
+
87
+ // Categories with blog counts
88
+ const withCounts = await forja.taxonomy.getCategoriesWithBlogCounts();
89
+
90
+ // Tags/categories for a specific content item
91
+ const contentTags = await forja.taxonomy.getContentTags('content-uuid');
92
+ ```
93
+
94
+ ### Analytics
95
+
96
+ ```typescript
97
+ // Track a pageview
98
+ await forja.analytics.trackPageview({ path: '/blog/hello-world' });
99
+
100
+ // Analytics report
101
+ const report = await forja.analytics.getReport({ days: 30, topN: 10 });
102
+ console.log(report.total_views, report.total_unique_visitors);
103
+
104
+ // Page-specific analytics
105
+ const pageStats = await forja.analytics.getPageAnalytics({
106
+ path: '/blog/hello-world',
107
+ days: 7,
108
+ });
109
+ ```
110
+
111
+ ### CV / Resume
112
+
113
+ ```typescript
114
+ // Skills
115
+ const skills = await forja.cv.listSkills({ page: 1 });
116
+ const skill = await forja.cv.getSkillBySlug('typescript');
117
+
118
+ // CV entries filtered by type
119
+ const workHistory = await forja.cv.listEntries({ entryType: 'Work' });
120
+ ```
121
+
122
+ ### Legal
123
+
124
+ ```typescript
125
+ // Legal documents
126
+ const docs = await forja.legal.list();
127
+ const privacy = await forja.legal.getBySlug('privacy-policy');
128
+
129
+ // Cookie consent
130
+ const consent = await forja.legal.getCookieConsent();
131
+ ```
132
+
133
+ ## Pagination
134
+
135
+ All paginated responses include helpers for navigating pages:
136
+
137
+ ```typescript
138
+ const page1 = await forja.blogs.listPublished({ pageSize: 10 });
139
+
140
+ // Fetch next page
141
+ const page2 = await page1.fetchNext(); // null if on last page
142
+
143
+ // Fetch all items across all pages
144
+ const allBlogs = await page1.fetchAll();
145
+
146
+ // Async iterator
147
+ for await (const page of page1) {
148
+ console.log(page.data);
149
+ }
150
+ ```
151
+
152
+ ## Error Handling
153
+
154
+ The SDK throws typed errors for different failure scenarios:
155
+
156
+ ```typescript
157
+ import {
158
+ ForjaAuthError,
159
+ ForjaPermissionError,
160
+ ForjaRateLimitError,
161
+ ForjaValidationError,
162
+ ForjaServerError,
163
+ ForjaNetworkError,
164
+ } from '@forjacms/client';
165
+
166
+ try {
167
+ const blogs = await forja.blogs.listPublished();
168
+ } catch (error) {
169
+ if (error instanceof ForjaAuthError) {
170
+ // Invalid or missing API key (401)
171
+ } else if (error instanceof ForjaRateLimitError) {
172
+ // Rate limited (429) — check error.retryAfter
173
+ console.log(`Retry after ${error.retryAfter} seconds`);
174
+ } else if (error instanceof ForjaNetworkError) {
175
+ // Network failure — server unreachable
176
+ }
177
+ }
178
+ ```
179
+
180
+ Methods that fetch a single resource by ID or slug return `null` instead of throwing on 404.
181
+
182
+ ## Custom Fetch
183
+
184
+ Pass a custom `fetch` implementation for edge runtimes or testing:
185
+
186
+ ```typescript
187
+ const forja = new ForjaClient({
188
+ baseUrl: 'https://cms.example.com/api/v1',
189
+ apiKey: 'dk_read_...',
190
+ siteId: 'your-site-uuid',
191
+ fetch: customFetchFn,
192
+ });
193
+ ```
194
+
195
+ ## Framework Integration
196
+
197
+ ### React
198
+
199
+ ```typescript
200
+ import { ForjaClient } from '@forjacms/client';
201
+ import { useEffect, useState } from 'react';
202
+
203
+ const forja = new ForjaClient({ baseUrl: '...', apiKey: '...', siteId: '...' });
204
+
205
+ function BlogList() {
206
+ const [blogs, setBlogs] = useState([]);
207
+
208
+ useEffect(() => {
209
+ forja.blogs.listPublished({ page: 1 }).then((res) => setBlogs(res.data));
210
+ }, []);
211
+
212
+ return blogs.map((b) => <article key={b.id}>{b.slug}</article>);
213
+ }
214
+ ```
215
+
216
+ ### Angular (v17+)
217
+
218
+ The `@forjacms/client/angular` subpath provides Angular DI integration and a signal-based resource helper.
219
+
220
+ **Setup:**
221
+
222
+ ```typescript
223
+ // app.config.ts
224
+ import { provideForja } from '@forjacms/client/angular';
225
+
226
+ export const appConfig: ApplicationConfig = {
227
+ providers: [
228
+ provideForja({
229
+ baseUrl: environment.cmsApiUrl,
230
+ apiKey: environment.cmsApiKey,
231
+ siteId: environment.cmsSiteId,
232
+ }),
233
+ ],
234
+ };
235
+ ```
236
+
237
+ **Usage in components:**
238
+
239
+ ```typescript
240
+ import { Component } from '@angular/core';
241
+ import { injectForja, forjaResource } from '@forjacms/client/angular';
242
+
243
+ @Component({
244
+ template: `
245
+ @if (blogs.isLoading()) {
246
+ <p>Loading...</p>
247
+ } @else if (blogs.error()) {
248
+ <p>Error: {{ blogs.error()!.message }}</p>
249
+ } @else {
250
+ @for (blog of blogs.value()!.data; track blog.id) {
251
+ <article>{{ blog.slug }}</article>
252
+ }
253
+ }
254
+ <button (click)="blogs.reload()">Refresh</button>
255
+ `,
256
+ })
257
+ export class BlogListComponent {
258
+ private forja = injectForja();
259
+ blogs = forjaResource(() => this.forja.blogs.listPublished({ page: 1 }));
260
+ }
261
+ ```
262
+
263
+ The `forjaResource()` helper returns an object with:
264
+ - `value()` — Signal with the resolved data (or `undefined` while loading)
265
+ - `isLoading()` — Signal indicating loading state
266
+ - `error()` — Signal with the error (or `null` on success)
267
+ - `reload()` — Re-execute the loader
268
+
269
+ ### Vanilla TypeScript
270
+
271
+ ```typescript
272
+ import { ForjaClient } from '@forjacms/client';
273
+
274
+ const forja = new ForjaClient({ baseUrl: '...', apiKey: '...', siteId: '...' });
275
+
276
+ const blogs = await forja.blogs.listPublished();
277
+ document.getElementById('count')!.textContent = `${blogs.meta.total_items} posts`;
278
+ ```
279
+
280
+ ## License
281
+
282
+ AGPL-3.0-or-later
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../client-eFMh_n88.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;
@@ -0,0 +1,140 @@
1
+ import { b as ForjaClientConfig, t as ForjaClient } from "../client-BBAKLM2r.cjs";
2
+ import { EnvironmentProviders, InjectionToken, Signal } from "@angular/core";
3
+
4
+ //#region src/angular/provider.d.ts
5
+ /**
6
+ * Angular injection token for the {@link ForjaClient} instance.
7
+ *
8
+ * You don't need to use this directly — use {@link provideForja} and
9
+ * {@link injectForja} instead.
10
+ */
11
+ declare const FORJA_CLIENT: InjectionToken<ForjaClient>;
12
+ /**
13
+ * Provide the Forja SDK client to Angular's dependency injection system.
14
+ *
15
+ * Call this in your application's `providers` array (typically in `app.config.ts`)
16
+ * to make the {@link ForjaClient} available via {@link injectForja}.
17
+ *
18
+ * @param config - API connection settings.
19
+ * @param config.baseUrl - Full URL to the Forja API (e.g. `https://cms.example.com/api/v1`).
20
+ * @param config.apiKey - API key with at least `Read` permission.
21
+ * @param config.siteId - UUID of the site to query.
22
+ * @returns Angular environment providers to include in your app config.
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * // app.config.ts
27
+ * import { provideForja } from '@forjacms/client/angular';
28
+ *
29
+ * export const appConfig: ApplicationConfig = {
30
+ * providers: [
31
+ * provideForja({
32
+ * baseUrl: environment.cmsApiUrl,
33
+ * apiKey: environment.cmsApiKey,
34
+ * siteId: environment.cmsSiteId,
35
+ * }),
36
+ * ],
37
+ * };
38
+ * ```
39
+ */
40
+ declare function provideForja(config: ForjaClientConfig): EnvironmentProviders;
41
+ /**
42
+ * Inject the {@link ForjaClient} instance from Angular's DI system.
43
+ *
44
+ * Must be called in an injection context (component constructor, `inject()` call,
45
+ * or factory function). Requires {@link provideForja} to be configured in the
46
+ * application's providers.
47
+ *
48
+ * @returns The configured {@link ForjaClient} instance.
49
+ * @throws If called outside an injection context or without {@link provideForja}.
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * import { Component } from '@angular/core';
54
+ * import { injectForja, forjaResource } from '@forjacms/client/angular';
55
+ *
56
+ * @Component({
57
+ * template: `
58
+ * @if (blogs.isLoading()) { <p>Loading...</p> }
59
+ * @for (blog of blogs.value()?.data ?? []; track blog.id) {
60
+ * <p>{{ blog.slug }}</p>
61
+ * }
62
+ * `,
63
+ * })
64
+ * export class BlogListComponent {
65
+ * private forja = injectForja();
66
+ * blogs = forjaResource(() => this.forja.blogs.listPublished({ page: 1 }));
67
+ * }
68
+ * ```
69
+ */
70
+ declare function injectForja(): ForjaClient;
71
+ //#endregion
72
+ //#region src/angular/resource.d.ts
73
+ /**
74
+ * A reactive resource that wraps an async operation with Angular signals.
75
+ *
76
+ * Provides `value`, `isLoading`, and `error` signals for template binding,
77
+ * plus a `reload()` method to re-execute the loader.
78
+ *
79
+ * @typeParam T - The type of the resolved value.
80
+ */
81
+ interface ForjaResource<T> {
82
+ /** The resolved value, or `undefined` while loading or after an error. */
83
+ readonly value: Signal<T | undefined>;
84
+ /** `true` while the loader is executing. */
85
+ readonly isLoading: Signal<boolean>;
86
+ /** The error if the loader rejected, or `null` on success. */
87
+ readonly error: Signal<Error | null>;
88
+ /** Re-execute the loader, resetting `isLoading` and clearing `error`. */
89
+ readonly reload: () => void;
90
+ }
91
+ /**
92
+ * Create a signal-based resource from an async loader function.
93
+ *
94
+ * Immediately invokes the loader and exposes the result via Angular signals.
95
+ * Use in Angular components to bind async SDK calls to templates without
96
+ * manual subscribe/unsubscribe boilerplate.
97
+ *
98
+ * @typeParam T - The type returned by the loader promise.
99
+ * @param loader - A function that returns a `Promise<T>`. Called immediately and on each `reload()`.
100
+ * @returns A {@link ForjaResource} with reactive `value`, `isLoading`, and `error` signals.
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * import { Component } from '@angular/core';
105
+ * import { injectForja, forjaResource } from '@forjacms/client/angular';
106
+ *
107
+ * @Component({
108
+ * template: `
109
+ * @if (blogs.isLoading()) {
110
+ * <p>Loading...</p>
111
+ * } @else if (blogs.error()) {
112
+ * <p>Error: {{ blogs.error()!.message }}</p>
113
+ * } @else {
114
+ * @for (blog of blogs.value()!.data; track blog.id) {
115
+ * <article>{{ blog.slug }}</article>
116
+ * }
117
+ * }
118
+ * <button (click)="blogs.reload()">Refresh</button>
119
+ * `,
120
+ * })
121
+ * export class BlogListComponent {
122
+ * private forja = injectForja();
123
+ * blogs = forjaResource(() => this.forja.blogs.listPublished({ page: 1 }));
124
+ * }
125
+ * ```
126
+ *
127
+ * @example
128
+ * ```typescript
129
+ * // Fetch a single resource
130
+ * const blog = forjaResource(() => forja.blogs.getBySlug('my-post'));
131
+ *
132
+ * // Access in template
133
+ * // blog.value()?.localizations[0]?.title
134
+ * // blog.isLoading()
135
+ * // blog.error()?.message
136
+ * ```
137
+ */
138
+ declare function forjaResource<T>(loader: () => Promise<T>): ForjaResource<T>;
139
+ //#endregion
140
+ export { FORJA_CLIENT, type ForjaResource, forjaResource, injectForja, provideForja };
@@ -0,0 +1,140 @@
1
+ import { b as ForjaClientConfig, t as ForjaClient } from "../client-d-QwwKUW.mjs";
2
+ import { EnvironmentProviders, InjectionToken, Signal } from "@angular/core";
3
+
4
+ //#region src/angular/provider.d.ts
5
+ /**
6
+ * Angular injection token for the {@link ForjaClient} instance.
7
+ *
8
+ * You don't need to use this directly — use {@link provideForja} and
9
+ * {@link injectForja} instead.
10
+ */
11
+ declare const FORJA_CLIENT: InjectionToken<ForjaClient>;
12
+ /**
13
+ * Provide the Forja SDK client to Angular's dependency injection system.
14
+ *
15
+ * Call this in your application's `providers` array (typically in `app.config.ts`)
16
+ * to make the {@link ForjaClient} available via {@link injectForja}.
17
+ *
18
+ * @param config - API connection settings.
19
+ * @param config.baseUrl - Full URL to the Forja API (e.g. `https://cms.example.com/api/v1`).
20
+ * @param config.apiKey - API key with at least `Read` permission.
21
+ * @param config.siteId - UUID of the site to query.
22
+ * @returns Angular environment providers to include in your app config.
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * // app.config.ts
27
+ * import { provideForja } from '@forjacms/client/angular';
28
+ *
29
+ * export const appConfig: ApplicationConfig = {
30
+ * providers: [
31
+ * provideForja({
32
+ * baseUrl: environment.cmsApiUrl,
33
+ * apiKey: environment.cmsApiKey,
34
+ * siteId: environment.cmsSiteId,
35
+ * }),
36
+ * ],
37
+ * };
38
+ * ```
39
+ */
40
+ declare function provideForja(config: ForjaClientConfig): EnvironmentProviders;
41
+ /**
42
+ * Inject the {@link ForjaClient} instance from Angular's DI system.
43
+ *
44
+ * Must be called in an injection context (component constructor, `inject()` call,
45
+ * or factory function). Requires {@link provideForja} to be configured in the
46
+ * application's providers.
47
+ *
48
+ * @returns The configured {@link ForjaClient} instance.
49
+ * @throws If called outside an injection context or without {@link provideForja}.
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * import { Component } from '@angular/core';
54
+ * import { injectForja, forjaResource } from '@forjacms/client/angular';
55
+ *
56
+ * @Component({
57
+ * template: `
58
+ * @if (blogs.isLoading()) { <p>Loading...</p> }
59
+ * @for (blog of blogs.value()?.data ?? []; track blog.id) {
60
+ * <p>{{ blog.slug }}</p>
61
+ * }
62
+ * `,
63
+ * })
64
+ * export class BlogListComponent {
65
+ * private forja = injectForja();
66
+ * blogs = forjaResource(() => this.forja.blogs.listPublished({ page: 1 }));
67
+ * }
68
+ * ```
69
+ */
70
+ declare function injectForja(): ForjaClient;
71
+ //#endregion
72
+ //#region src/angular/resource.d.ts
73
+ /**
74
+ * A reactive resource that wraps an async operation with Angular signals.
75
+ *
76
+ * Provides `value`, `isLoading`, and `error` signals for template binding,
77
+ * plus a `reload()` method to re-execute the loader.
78
+ *
79
+ * @typeParam T - The type of the resolved value.
80
+ */
81
+ interface ForjaResource<T> {
82
+ /** The resolved value, or `undefined` while loading or after an error. */
83
+ readonly value: Signal<T | undefined>;
84
+ /** `true` while the loader is executing. */
85
+ readonly isLoading: Signal<boolean>;
86
+ /** The error if the loader rejected, or `null` on success. */
87
+ readonly error: Signal<Error | null>;
88
+ /** Re-execute the loader, resetting `isLoading` and clearing `error`. */
89
+ readonly reload: () => void;
90
+ }
91
+ /**
92
+ * Create a signal-based resource from an async loader function.
93
+ *
94
+ * Immediately invokes the loader and exposes the result via Angular signals.
95
+ * Use in Angular components to bind async SDK calls to templates without
96
+ * manual subscribe/unsubscribe boilerplate.
97
+ *
98
+ * @typeParam T - The type returned by the loader promise.
99
+ * @param loader - A function that returns a `Promise<T>`. Called immediately and on each `reload()`.
100
+ * @returns A {@link ForjaResource} with reactive `value`, `isLoading`, and `error` signals.
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * import { Component } from '@angular/core';
105
+ * import { injectForja, forjaResource } from '@forjacms/client/angular';
106
+ *
107
+ * @Component({
108
+ * template: `
109
+ * @if (blogs.isLoading()) {
110
+ * <p>Loading...</p>
111
+ * } @else if (blogs.error()) {
112
+ * <p>Error: {{ blogs.error()!.message }}</p>
113
+ * } @else {
114
+ * @for (blog of blogs.value()!.data; track blog.id) {
115
+ * <article>{{ blog.slug }}</article>
116
+ * }
117
+ * }
118
+ * <button (click)="blogs.reload()">Refresh</button>
119
+ * `,
120
+ * })
121
+ * export class BlogListComponent {
122
+ * private forja = injectForja();
123
+ * blogs = forjaResource(() => this.forja.blogs.listPublished({ page: 1 }));
124
+ * }
125
+ * ```
126
+ *
127
+ * @example
128
+ * ```typescript
129
+ * // Fetch a single resource
130
+ * const blog = forjaResource(() => forja.blogs.getBySlug('my-post'));
131
+ *
132
+ * // Access in template
133
+ * // blog.value()?.localizations[0]?.title
134
+ * // blog.isLoading()
135
+ * // blog.error()?.message
136
+ * ```
137
+ */
138
+ declare function forjaResource<T>(loader: () => Promise<T>): ForjaResource<T>;
139
+ //#endregion
140
+ export { FORJA_CLIENT, type ForjaResource, forjaResource, injectForja, provideForja };
@@ -0,0 +1 @@
1
+ import{t as e}from"../client-DTfhjUoX.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};