@codesocietyou/contentedge-cms-sdk 0.2.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +371 -177
  2. package/package.json +33 -23
package/README.md CHANGED
@@ -1,233 +1,427 @@
1
- ## ContentEdge SDK (TypeScript)
1
+ # ContentEdge SDK (TypeScript)
2
2
 
3
- A lightweight, framework-agnostic TypeScript client for the ContentEdge headless CMS. It provides a small, typed façade over the HTTP API with pluggable auth, robust pagination helpers, asset/file utilities, and structured errors—without embedding project-specific domain models (e.g., News/Blog/Reports).
3
+ A lightweight, framework-agnostic TypeScript client for the ContentEdge headless CMS. It provides a clean, typed interface over the HTTP API with public API key authentication, robust pagination helpers, asset/file utilities, and structured errors—without embedding project-specific domain models.
4
4
 
5
5
  This SDK models the wire contract via a generic `ContentDto<C>` where `C` represents your custom fields. It does not import or depend on the CMS server code.
6
6
 
7
- ### Features
7
+ ## Features
8
8
 
9
- - Generic content model: `ContentDto<C>` with consumer-defined custom fields
10
- - Pluggable auth strategy: `AuthProvider` (includes Keycloak client-credentials)
11
- - Resilient pagination: `listAllContent` aggregates pages with dedupe and safe stop
12
- - Asset/file helpers: `buildAssetUrl` and safe `downloadFile`
13
- - Structured error model: `CmsError` with status and response data
14
- - Framework-agnostic; optional React Query adapter pattern
9
+ - **Generic content model**: `ContentDto<C>` with consumer-defined custom fields
10
+ - **Public API key authentication**: Simple header-based authentication
11
+ - **Service layer pattern**: Centralized API client with interceptors
12
+ - **React Query integration**: Optional query factory and hooks
13
+ - **Resilient pagination**: `fetchAllContent` aggregates pages with dedupe and safe stop
14
+ - **Asset/file helpers**: `buildAssetUrl` and safe `downloadFile`
15
+ - **Normalization utilities**: Transform API responses to normalized structures
16
+ - **Structured error model**: `CmsError` with status and response data
17
+ - **Framework-agnostic**: Core services work anywhere; React Query layer is optional
18
+ - **TypeScript-first**: Full type safety with generics
15
19
 
16
- ### Installation
20
+ ## Installation
17
21
 
18
22
  ```bash
19
23
  npm install @codesocietyou/contentedge-cms-sdk
20
24
  # or
21
25
  yarn add @codesocietyou/contentedge-cms-sdk
26
+ # or
27
+ pnpm add @codesocietyou/contentedge-cms-sdk
22
28
  ```
23
29
 
24
- ### Quick Start
30
+ For React Query integration, also install:
25
31
 
26
- ```ts
27
- import {
28
- CmsClient,
29
- KeycloakClientCredentialsAuth,
30
- type ContentDto
31
- } from '@codesocietyou/contentedge-cms-sdk';
32
+ ```bash
33
+ npm install @tanstack/react-query
34
+ ```
32
35
 
33
- // Auth (Keycloak client-credentials) - server-side only
34
- const auth = new KeycloakClientCredentialsAuth({
35
- tokenUrl: 'https://auth.example.com/realms/contentedge/protocol/openid-connect/token',
36
- clientId: 'contentedge-client',
37
- clientSecret: 'xxxxxx'
38
- });
36
+ ## Quick Start
37
+
38
+ ### 1. Initialize the SDK
39
39
 
40
- // Client
41
- const contentedge = new CmsClient({
42
- baseUrl: 'https://cms.example.com/api',
43
- fileBaseUrl: 'https://cms.example.com', // optional (asset host)
44
- tenant: 'your-tenant', // optional; sent as X-Tenant
45
- auth
40
+ ```typescript
41
+ import { createApiClient } from '@codesocietyou/contentedge-cms-sdk';
42
+
43
+ // Initialize once at app startup
44
+ createApiClient({
45
+ baseUrl: 'https://api.contentedge.com',
46
+ fileBaseUrl: 'https://cdn.contentedge.com', // optional
47
+ apiKey: 'your-api-key', // optional
48
+ tenant: 'your-tenant', // optional
49
+ timeoutMs: 30_000, // optional (default: 30s)
46
50
  });
51
+ ```
47
52
 
48
- // List content by type with filters/pagination
49
- const list = await contentedge.listContent({
50
- type: 'REPORT',
53
+ ### 2. Fetch Content (Basic)
54
+
55
+ ```typescript
56
+ import { fetchContentByType } from '@codesocietyou/contentedge-cms-sdk';
57
+
58
+ // Fetch paginated content
59
+ const response = await fetchContentByType({
60
+ type: 'NEWS',
51
61
  page: 0,
52
62
  size: 10,
53
63
  sortBy: 'id',
54
64
  direction: 'DESC',
55
- filters: { publicationType: 'GAMEHEARTS' } // arbitrary query params
65
+ filters: { publicationType: 'GAMEHEARTS' }, // arbitrary filters
56
66
  });
57
67
 
58
- // Get detail by id
59
- const detail = await contentedge.getContentById(123);
60
-
61
- // Download a file (Blob in browsers)
62
- const pdf = await contentedge.downloadFile('https://cms.example.com/files/doc.pdf');
63
- ```
64
-
65
- ### API
66
-
67
- - Client
68
- - `new CmsClient(config)`
69
- - `baseUrl`: CMS API base URL (e.g., `https://cms.example.com/api`)
70
- - `fileBaseUrl?`: Preferred file/asset base (often origin w/o `/api`)
71
- - `tenant?`: Adds `X-Tenant` header
72
- - `timeoutMs?`: Default 30000
73
- - `logger?`: `{ debug?, warn?, error? }`
74
- - `auth?`: `AuthProvider`
75
- - `listContent<C>(params?: ContentListParams): Promise<ContentResponse<C>>`
76
- - `getContentById<C>(id: number): Promise<ApiResponse<ContentDto<C>>>`
77
- - `listAllContent<C, T = ContentDto<C>>(params: Omit<ContentListParams, 'page'>, opts?: { mapItem?, dedupeBy?, hardStopMaxPages? }): Promise<T[]>`
78
- - `buildAssetUrl(path?: string | null): string`
79
- - `downloadFile(path: string): Promise<Blob>`
80
-
81
- - Auth
82
- - `AuthProvider`: `getAccessToken(opts?: { forceRefresh?: boolean }): Promise<string>`
83
- - `KeycloakClientCredentialsAuth({ tokenUrl, clientId, clientSecret })`
84
-
85
- - Types
86
- - `ContentDto<C extends Record<string, JsonValue>>`
87
- - `ApiResponse<T>`
88
- - `PaginatedData<T>`
89
- - `ContentResponse<C>`
90
- - `ContentListParams`:
91
- - `type?`, `page?`, `size?`, `sortBy?`, `direction?`
92
- - `filters?`: arbitrary query params
93
-
94
- - Errors
95
- - `CmsError extends Error` with `.status?: number` and `.data?: unknown`
96
-
97
- ### Mapping to your app models
98
-
99
- Keep domain mapping out of the SDK. Define custom fields and a mapper in your app:
100
-
101
- ```ts
102
- // Your custom fields
103
- type MyCustomFields = {
104
- title?: string;
105
- text?: string;
106
- insideImage?: string;
107
- outsideImage?: string;
108
- references?: string | null;
109
- pdfPath?: string | null;
110
- citation?: string | null;
111
- abstract?: string | null;
112
- team?: string | null;
113
- publicationType?: 'GAMEHEARTS' | 'EXTERNAL' | null;
114
- fake?: boolean | null;
115
- };
116
-
117
- // Your view model
118
- type NormalizedItem = {
68
+ const items = response.data.content;
69
+ ```
70
+
71
+ ### 3. Fetch Content (React Query)
72
+
73
+ ```typescript
74
+ import { useQuery } from '@tanstack/react-query';
75
+ import { contentQueries } from '@codesocietyou/contentedge-cms-sdk';
76
+
77
+ function NewsList() {
78
+ const { data, isLoading, error } = useQuery(
79
+ contentQueries.list({ type: 'NEWS', page: 0, size: 10 })
80
+ );
81
+
82
+ if (isLoading) return <div>Loading...</div>;
83
+ if (error) return <div>Error: {error.message}</div>;
84
+
85
+ return (
86
+ <ul>
87
+ {data.data.content.map(item => (
88
+ <li key={item.id}>{item.title}</li>
89
+ ))}
90
+ </ul>
91
+ );
92
+ }
93
+ ```
94
+
95
+ ### 4. Normalization
96
+
97
+ ```typescript
98
+ import {
99
+ normalizeContentItem,
100
+ fetchContentByType
101
+ } from '@codesocietyou/contentedge-cms-sdk';
102
+
103
+ // Fetch and normalize
104
+ const response = await fetchContentByType({ type: 'NEWS' });
105
+ const normalized = response.data.content.map(normalizeContentItem);
106
+
107
+ // normalized items have resolved asset URLs and standardized fields
108
+ console.log(normalized[0].insideImage); // "https://cdn.contentedge.com/images/news.jpg"
109
+ console.log(normalized[0].pdfPath); // "https://cdn.contentedge.com/files/doc.pdf"
110
+ ```
111
+
112
+ ## API Reference
113
+
114
+ ### Configuration
115
+
116
+ #### `createApiClient(config: SdkConfig): AxiosInstance`
117
+
118
+ Initialize the SDK with your CMS configuration. Call this once at app startup.
119
+
120
+ ```typescript
121
+ interface SdkConfig {
122
+ baseUrl: string; // CMS API base URL (required)
123
+ fileBaseUrl?: string; // Preferred file/asset base
124
+ apiKey?: string; // API key for authentication
125
+ tenant?: string; // Tenant identifier (sent as X-Tenant header)
126
+ timeoutMs?: number; // Request timeout (default: 30000)
127
+ logger?: {
128
+ debug?: (...args: unknown[]) => void;
129
+ warn?: (...args: unknown[]) => void;
130
+ error?: (...args: unknown[]) => void;
131
+ };
132
+ }
133
+ ```
134
+
135
+ ### Service Layer
136
+
137
+ #### `fetchContentByType<C>(params: ContentListParams): Promise<ContentResponse<C>>`
138
+
139
+ Fetch paginated content by type with filters and sorting.
140
+
141
+ ```typescript
142
+ interface ContentListParams {
143
+ type?: string; // Content type (default: 'ALL')
144
+ page?: number; // Page number (0-indexed)
145
+ size?: number; // Items per page
146
+ sortBy?: string; // Sort field (default: 'id')
147
+ direction?: 'ASC' | 'DESC'; // Sort direction (default: 'DESC')
148
+ filters?: Record<string, any>; // Arbitrary query filters
149
+ }
150
+ ```
151
+
152
+ #### `fetchContentById<C>(id: number): Promise<ApiResponse<ContentDto<C>>>`
153
+
154
+ Fetch a single content item by ID.
155
+
156
+ #### `fetchAllContent<C, T>(params, options): Promise<T[]>`
157
+
158
+ Fetch all content items across multiple pages with automatic pagination.
159
+
160
+ ```typescript
161
+ interface FetchAllOptions<C, T> {
162
+ mapItem?: (item: ContentDto<C>) => T; // Transform each item
163
+ dedupeBy?: (item: T) => string | number; // Dedupe key extractor
164
+ hardStopMaxPages?: number; // Safety limit (default: 20)
165
+ }
166
+ ```
167
+
168
+ #### `downloadFile(path: string): Promise<Blob>`
169
+
170
+ Download a file from a given path (with proper authentication for CMS files).
171
+
172
+ ### React Query Integration
173
+
174
+ #### `contentQueries`
175
+
176
+ Query options factory for use with `useQuery`:
177
+
178
+ ```typescript
179
+ // Paginated list
180
+ contentQueries.list({ type: 'NEWS', page: 0, size: 10 })
181
+
182
+ // Single item
183
+ contentQueries.detail(123)
184
+
185
+ // Fetch all (across pages)
186
+ contentQueries.listAll({ type: 'NEWS', size: 100 }, { mapItem: normalizeContentItem })
187
+ ```
188
+
189
+ #### Hooks
190
+
191
+ ```typescript
192
+ // List hook
193
+ const { data } = useContentList({ type: 'NEWS', page: 0, size: 10 });
194
+
195
+ // Detail hook
196
+ const { data } = useContentDetail(123);
197
+
198
+ // Fetch all hook
199
+ const { data } = useContentAll({ type: 'NEWS' });
200
+
201
+ // Prefetch utilities
202
+ const { prefetchList, prefetchDetail, invalidateLists } = useContentPrefetch();
203
+ ```
204
+
205
+ #### Query Keys
206
+
207
+ Hierarchical query key factory for manual cache manipulation:
208
+
209
+ ```typescript
210
+ import { contentKeys } from '@codesocietyou/contentedge-cms-sdk';
211
+
212
+ // Invalidate all lists
213
+ queryClient.invalidateQueries({ queryKey: contentKeys.lists() });
214
+
215
+ // Invalidate specific detail
216
+ queryClient.invalidateQueries({ queryKey: contentKeys.detail(123) });
217
+ ```
218
+
219
+ ### Normalization
220
+
221
+ #### `normalizeContentItem<C>(item: ContentDto<C>): NormalizedContentItem`
222
+
223
+ Transform a content item to a normalized structure with resolved asset URLs:
224
+
225
+ ```typescript
226
+ interface NormalizedContentItem {
119
227
  id: number;
120
228
  title: string;
121
229
  text: string;
122
- insideImage: string;
123
- outsideImage: string;
124
- references: string | null;
125
- pdfPath: string | null;
126
230
  type: string;
231
+ insideImage: string; // Resolved URL
232
+ outsideImage: string; // Resolved URL
233
+ pdfPath: string | null; // Resolved URL (type-aware)
234
+ references: string | null;
127
235
  citation: string | null;
128
- fake: boolean | null;
129
236
  abstract: string | null;
130
237
  team: string | null;
131
- publicationType: 'GAMEHEARTS' | 'EXTERNAL' | null;
132
- };
133
-
134
- const mapToNormalized = (item: ContentDto<MyCustomFields>): NormalizedItem => ({
135
- id: item.id,
136
- title: item.customFields.title || item.title,
137
- text: item.customFields.text || item.text,
138
- insideImage: contentedge.buildAssetUrl(item.customFields.insideImage || ''),
139
- outsideImage: contentedge.buildAssetUrl(item.customFields.outsideImage || ''),
140
- references: item.customFields.references ?? null,
141
- pdfPath: contentedge.buildAssetUrl(item.customFields.pdfPath ?? null),
142
- type: item.type,
143
- citation: item.customFields.citation ?? null,
144
- fake: item.customFields.fake ?? null,
145
- abstract: item.customFields.abstract ?? null,
146
- team: item.customFields.team ?? null,
147
- publicationType: item.customFields.publicationType ?? null
148
- });
149
-
150
- // Fetch-all with mapping + dedupe
151
- const items = await contentedge.listAllContent<MyCustomFields, NormalizedItem>(
152
- { type: 'REPORT', size: 100, sortBy: 'id', direction: 'DESC', filters: { publicationType: 'GAMEHEARTS' } },
153
- { mapItem: mapToNormalized, dedupeBy: (i) => i.id }
154
- );
238
+ publicationType: string | null;
239
+ fake: boolean | null;
240
+ }
155
241
  ```
156
242
 
157
- ### React Query (optional pattern)
158
-
159
- ```ts
160
- import { queryOptions } from '@tanstack/react-query';
161
- import type { ContentListParams, ContentDto } from '@codesocietyou/contentedge-cms-sdk';
162
-
163
- export const contentQueries = {
164
- list: (params: ContentListParams) => queryOptions({
165
- queryKey: ['content', 'list', params.type ?? 'ALL', params],
166
- queryFn: () => contentedge.listContent(params),
167
- staleTime: 5 * 60_000,
168
- gcTime: 10 * 60_000
169
- }),
170
- detail: (id: number) => queryOptions({
171
- queryKey: ['content', 'detail', id],
172
- queryFn: () => contentedge.getContentById(id),
173
- staleTime: 10 * 60_000,
174
- gcTime: 30 * 60_000
175
- }),
176
- listAll: <C, T = ContentDto<C>>(params: Omit<ContentListParams, 'page'>, mapItem: (i: ContentDto<C>) => T) =>
177
- queryOptions({
178
- queryKey: ['content', 'all', params.type ?? 'ALL', { ...params, mode: 'all' }],
179
- queryFn: () => contentedge.listAllContent(params, { mapItem }),
180
- staleTime: 5 * 60_000,
181
- gcTime: 10 * 60_000
182
- })
183
- };
184
- ```
185
-
186
- ### Error handling
187
-
188
- ```ts
243
+ #### `buildAssetUrlWithConfig(path?: string | null): string`
244
+
245
+ Build an asset URL using the SDK's configured base URLs.
246
+
247
+ ### Error Handling
248
+
249
+ ```typescript
250
+ import { CmsError } from '@codesocietyou/contentedge-cms-sdk';
251
+
189
252
  try {
190
- await contentedge.listContent({ type: 'NEWS' });
253
+ await fetchContentByType({ type: 'NEWS' });
191
254
  } catch (e) {
192
255
  if (e instanceof CmsError) {
193
- console.error('ContentEdge error', e.status, e.data);
256
+ console.error('CMS error:', e.status, e.data);
257
+ // e.status: HTTP status code
258
+ // e.data: Response body (if any)
194
259
  } else {
195
- console.error('Unknown error', e);
260
+ console.error('Unknown error:', e);
196
261
  }
197
262
  }
198
263
  ```
199
264
 
200
- 401s are retried once with a forced token refresh when an `AuthProvider` is provided.
265
+ ## Advanced Usage
201
266
 
202
- ### Security
267
+ ### Custom Fields Type
203
268
 
204
- - `KeycloakClientCredentialsAuth` is intended for server-side usage. Do not expose client secrets in browsers.
205
- - For browsers, you can implement a simple bearer token strategy:
269
+ Define your own custom fields type for full type safety:
206
270
 
207
- ```ts
208
- class BearerTokenAuth implements AuthProvider {
209
- constructor(private readonly getToken: () => Promise<string> | string) {}
210
- async getAccessToken() {
211
- return typeof this.getToken === 'function' ? await this.getToken() : this.getToken;
212
- }
271
+ ```typescript
272
+ interface MyCustomFields {
273
+ title: string;
274
+ text: string;
275
+ author?: string;
276
+ tags?: string[];
277
+ publishedAt?: string;
278
+ }
279
+
280
+ // Use with type parameter
281
+ const response = await fetchContentByType<MyCustomFields>({ type: 'ARTICLE' });
282
+ const items = response.data.content; // ContentDto<MyCustomFields>[]
283
+ ```
284
+
285
+ ### Custom Normalization
286
+
287
+ ```typescript
288
+ interface MyNormalizedItem {
289
+ id: number;
290
+ title: string;
291
+ author: string;
292
+ tags: string[];
293
+ }
294
+
295
+ function myNormalize(item: ContentDto<MyCustomFields>): MyNormalizedItem {
296
+ return {
297
+ id: item.id,
298
+ title: item.customFields.title || item.title,
299
+ author: item.customFields.author || 'Unknown',
300
+ tags: item.customFields.tags || [],
301
+ };
302
+ }
303
+
304
+ // Use with fetchAllContent
305
+ const items = await fetchAllContent<MyCustomFields, MyNormalizedItem>(
306
+ { type: 'ARTICLE', size: 100 },
307
+ { mapItem: myNormalize }
308
+ );
309
+ ```
310
+
311
+ ### React Query Configuration
312
+
313
+ ```typescript
314
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
315
+ import { createApiClient } from '@codesocietyou/contentedge-cms-sdk';
316
+
317
+ // Initialize SDK
318
+ createApiClient({
319
+ baseUrl: process.env.VITE_API_URL!,
320
+ apiKey: process.env.VITE_API_KEY,
321
+ tenant: 'my-tenant',
322
+ });
323
+
324
+ // Create query client
325
+ const queryClient = new QueryClient({
326
+ defaultOptions: {
327
+ queries: {
328
+ retry: 2,
329
+ refetchOnWindowFocus: false,
330
+ },
331
+ },
332
+ });
333
+
334
+ // Wrap app
335
+ function App() {
336
+ return (
337
+ <QueryClientProvider client={queryClient}>
338
+ <YourApp />
339
+ </QueryClientProvider>
340
+ );
213
341
  }
214
342
  ```
215
343
 
216
- - `downloadFile` avoids sending Authorization headers to non-CMS domains.
217
- - Prefer runtime configuration to inject secrets; limit scopes on your Keycloak client.
344
+ ## Migration from v0.2.x
345
+
346
+ The v1.0.0 release includes breaking changes:
347
+
348
+ ### Removed
349
+
350
+ - `CmsClient` class → Use service functions + `createApiClient()`
351
+ - `KeycloakClientCredentialsAuth` → Use API key authentication
352
+ - `AuthProvider` interface → No longer needed
218
353
 
219
- ### Endpoint assumptions
354
+ ### Migration Steps
355
+
356
+ **Before (v0.2.x):**
357
+
358
+ ```typescript
359
+ import { CmsClient, KeycloakClientCredentialsAuth } from '@codesocietyou/contentedge-cms-sdk';
360
+
361
+ const auth = new KeycloakClientCredentialsAuth({
362
+ tokenUrl: 'https://auth.example.com/token',
363
+ clientId: 'client',
364
+ clientSecret: 'secret',
365
+ });
366
+
367
+ const client = new CmsClient({
368
+ baseUrl: 'https://api.example.com',
369
+ auth,
370
+ });
371
+
372
+ const list = await client.listContent({ type: 'NEWS' });
373
+ const detail = await client.getContentById(123);
374
+ ```
375
+
376
+ **After (v1.0.0):**
377
+
378
+ ```typescript
379
+ import {
380
+ createApiClient,
381
+ fetchContentByType,
382
+ fetchContentById
383
+ } from '@codesocietyou/contentedge-cms-sdk';
384
+
385
+ // Initialize once
386
+ createApiClient({
387
+ baseUrl: 'https://api.example.com',
388
+ apiKey: 'your-api-key',
389
+ });
390
+
391
+ // Use service functions
392
+ const list = await fetchContentByType({ type: 'NEWS' });
393
+ const detail = await fetchContentById(123);
394
+ ```
395
+
396
+ ## Security
397
+
398
+ - **API Keys**: Store in environment variables, never commit to source control
399
+ - **Runtime Configuration**: Inject secrets at runtime in production
400
+ - **CORS**: Ensure your CMS API allows requests from your domain
401
+ - **Rate Limiting**: The SDK logs 429 errors; implement retry logic if needed
402
+
403
+ ## Endpoint Assumptions
220
404
 
221
405
  By default, the SDK uses:
222
- - `GET /content/type/:type`
223
- - `GET /content/:id`
224
406
 
225
- If your deployment differs, wrap or extend `CmsClient`.
407
+ - `GET /content/type/:type` - List content by type
408
+ - `GET /content/:id` - Get content by ID
409
+
410
+ If your deployment uses different endpoints, wrap or extend the service functions.
226
411
 
227
- ### Versioning
412
+ ## Contributing
228
413
 
229
- Semantic Versioning (SemVer). Breaking changes bump MAJOR.
414
+ Contributions are welcome! Please follow the existing code style and add tests for new features.
230
415
 
231
- ### License
416
+ ## Versioning
417
+
418
+ This project follows [Semantic Versioning](https://semver.org/). Breaking changes bump MAJOR.
419
+
420
+ ## License
232
421
 
233
422
  MIT
423
+
424
+ ## Support
425
+
426
+ - Issues: https://github.com/ParapluOU/contentedge-cms-sdk/issues
427
+ - Docs: https://github.com/ParapluOU/contentedge-cms-sdk#readme
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@codesocietyou/contentedge-cms-sdk",
3
3
  "private": false,
4
- "version": "0.2.2",
5
- "description": "A lightweight, framework-agnostic TypeScript client for the ContentEdge headless CMS.",
4
+ "version": "1.0.0",
5
+ "description": "A lightweight, framework-agnostic TypeScript client for the ContentEdge headless CMS with API key authentication and optional React Query integration.",
6
6
  "type": "module",
7
7
  "main": "./dist/index.cjs",
8
8
  "module": "./dist/index.js",
@@ -22,7 +22,7 @@
22
22
  "dev": "vite",
23
23
  "preview": "vite preview",
24
24
  "lint": "eslint .",
25
- "build": "tsup",
25
+ "build": "tsdown",
26
26
  "test": "vitest run",
27
27
  "test:coverage": "vitest run --coverage",
28
28
  "test:watch": "vitest",
@@ -35,7 +35,9 @@
35
35
  "cms",
36
36
  "sdk",
37
37
  "typescript",
38
- "keycloak"
38
+ "headless-cms",
39
+ "react-query",
40
+ "tanstack"
39
41
  ],
40
42
  "author": "ContentEdge",
41
43
  "license": "MIT",
@@ -48,33 +50,41 @@
48
50
  },
49
51
  "homepage": "https://github.com/ParapluOU/contentedge-cms-sdk#readme",
50
52
  "engines": {
51
- "node": ">=18"
53
+ "node": ">=24.13.1",
54
+ "npm": ">=10.0.0"
52
55
  },
53
56
  "publishConfig": {
54
57
  "access": "public",
55
58
  "provenance": true
56
59
  },
57
60
  "dependencies": {
58
- "axios": "^1.7.7"
61
+ "axios": "^1.13.3"
62
+ },
63
+ "peerDependencies": {
64
+ "@tanstack/react-query": ">=5.90.0"
65
+ },
66
+ "peerDependenciesMeta": {
67
+ "@tanstack/react-query": {
68
+ "optional": true
69
+ }
59
70
  },
60
71
  "devDependencies": {
61
- "@changesets/cli": "^2.29.7",
62
- "@eslint/js": "^9.39.1",
63
- "@types/node": "^24.10.0",
64
- "@types/react": "^19.2.2",
65
- "@types/react-dom": "^19.2.2",
66
- "@vitejs/plugin-react": "^5.1.0",
67
- "@vitest/coverage-v8": "^4.0.8",
68
- "eslint": "^9.39.1",
69
- "eslint-plugin-react-hooks": "^5.2.0",
70
- "eslint-plugin-react-refresh": "^0.4.24",
71
- "globals": "^16.5.0",
72
- "react": "^19.2.0",
73
- "react-dom": "^19.2.0",
74
- "tsup": "^8.3.0",
72
+ "@changesets/cli": "^2.29.8",
73
+ "@eslint/js": "^10.0.1",
74
+ "@tanstack/react-query": "^5.90.21",
75
+ "@types/node": "^25.2.3",
76
+ "@types/react": "^19.2.14",
77
+ "@types/react-dom": "^19.2.3",
78
+ "@vitejs/plugin-react": "^5.1.4",
79
+ "@vitest/coverage-v8": "^4.0.18",
80
+ "eslint": "^10.0.0",
81
+ "globals": "^17.3.0",
82
+ "react": "^19.2.4",
83
+ "react-dom": "^19.2.4",
84
+ "tsdown": "^0.3.0",
75
85
  "typescript": "~5.9.3",
76
- "typescript-eslint": "^8.46.4",
77
- "vite": "^7.2.2",
78
- "vitest": "^4.0.8"
86
+ "typescript-eslint": "8.55.1-alpha.4",
87
+ "vite": "^7.3.1",
88
+ "vitest": "^4.0.18"
79
89
  }
80
90
  }