@unchainedshop/cockpit-api 2.2.0 → 2.2.2

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
@@ -103,13 +103,17 @@ const localizedPost = await cockpit.getContentItem({
103
103
  queryParams: { fields: { title: 1, content: 1 } }
104
104
  });
105
105
 
106
- // Get multiple content items with pagination
107
- const posts = await cockpit.getContentItems('posts', {
106
+ // Get multiple content items - always returns { data, meta? }
107
+ const response = await cockpit.getContentItems('posts', {
108
108
  limit: 10,
109
- skip: 0,
110
109
  sort: { _created: -1 },
111
110
  filter: { published: true }
112
111
  });
112
+ // response: { data: Post[], meta?: { total: number } } | null
113
+
114
+ // Access items and metadata
115
+ const items = response?.data || [];
116
+ const total = response?.meta?.total;
113
117
 
114
118
  // Get tree structure
115
119
  const tree = await cockpit.getContentTree('categories', {
@@ -133,8 +137,10 @@ await cockpit.deleteContentItem('posts', '123');
133
137
  ### Pages
134
138
 
135
139
  ```typescript
136
- // List pages
137
- const allPages = await cockpit.pages({ locale: 'en', limit: 50 });
140
+ // List pages - always returns { data, meta? }
141
+ const response = await cockpit.pages({ locale: 'en', limit: 50 });
142
+ const allPages = response?.data || [];
143
+ const total = response?.meta?.total;
138
144
 
139
145
  // Get page by ID
140
146
  const page = await cockpit.pageById({ page: 'blog', id: '123', locale: 'en' });
@@ -206,9 +212,9 @@ const image = await cockpit.imageAssetById('asset-id', {
206
212
  // Health check
207
213
  const health = await cockpit.healthCheck();
208
214
 
209
- // Clear cache
210
- cockpit.clearCache(); // Clear all
211
- cockpit.clearCache('pages'); // Clear by pattern
215
+ // Clear cache (async in v2.2.0+)
216
+ await cockpit.clearCache(); // Clear all
217
+ await cockpit.clearCache('pages'); // Clear by pattern
212
218
  ```
213
219
 
214
220
  ## Lightweight Fetch Client API
@@ -228,9 +234,16 @@ const cockpit = createFetchClient({
228
234
 
229
235
  // Available methods
230
236
  const page = await cockpit.pageByRoute('/about', { locale: 'en' });
231
- const pages = await cockpit.pages({ locale: 'en' });
237
+
238
+ // List methods return { data, meta? }
239
+ const pagesResponse = await cockpit.pages({ locale: 'en' });
240
+ const pages = pagesResponse?.data || [];
241
+
232
242
  const pageById = await cockpit.pageById('blog', '123', { locale: 'en' });
233
- const items = await cockpit.getContentItems('news', { locale: 'en', limit: 10 });
243
+
244
+ const itemsResponse = await cockpit.getContentItems('news', { locale: 'en', limit: 10 });
245
+ const items = itemsResponse?.data || [];
246
+
234
247
  const item = await cockpit.getContentItem('news', '123', { locale: 'en' });
235
248
  const custom = await cockpit.fetchRaw('/custom/endpoint', { param: 'value' });
236
249
  ```
@@ -275,7 +288,10 @@ const cockpit = await CockpitAPI({
275
288
  cache: {
276
289
  max: 100, // Falls back to COCKPIT_CACHE_MAX (default: 100)
277
290
  ttl: 100000, // Falls back to COCKPIT_CACHE_TTL (default: 100000)
291
+ store: customStore, // Optional: custom async cache store (Redis, Keyv, etc.)
278
292
  },
293
+ // Or disable caching entirely
294
+ // cache: false,
279
295
  });
280
296
  ```
281
297
 
@@ -305,6 +321,79 @@ const { tenant, slug } = resolveTenantFromUrl('https://mytenant.example.com/page
305
321
  const allTenants = getTenantIds(); // From COCKPIT_SECRET_* env vars
306
322
  ```
307
323
 
324
+ ## Custom Cache Stores
325
+
326
+ v2.2.0+ supports pluggable async cache stores for Redis, Keyv, or custom implementations:
327
+
328
+ ```typescript
329
+ import { createClient } from 'redis';
330
+ import type { AsyncCacheStore } from '@unchainedshop/cockpit-api';
331
+
332
+ // Redis example
333
+ const redisClient = createClient({ url: process.env.REDIS_URL });
334
+ await redisClient.connect();
335
+
336
+ const redisStore: AsyncCacheStore = {
337
+ async get(key: string) {
338
+ const value = await redisClient.get(key);
339
+ return value ? JSON.parse(value) : undefined;
340
+ },
341
+ async set(key: string, value: unknown) {
342
+ await redisClient.set(key, JSON.stringify(value), { EX: 100 });
343
+ },
344
+ async clear(pattern?: string) {
345
+ if (pattern) {
346
+ const keys = await redisClient.keys(`${pattern}*`);
347
+ if (keys.length > 0) await redisClient.del(keys);
348
+ } else {
349
+ await redisClient.flushDb();
350
+ }
351
+ }
352
+ };
353
+
354
+ const cockpit = await CockpitAPI({
355
+ endpoint: 'https://cms.example.com/api/graphql',
356
+ cache: { store: redisStore }
357
+ });
358
+ ```
359
+
360
+ ## Response Format (v3.0.0+)
361
+
362
+ All list methods return a **consistent response format** regardless of parameters:
363
+
364
+ ```typescript
365
+ interface CockpitListResponse<T> {
366
+ data: T[];
367
+ meta?: CockpitListMeta; // Present when using pagination (skip parameter)
368
+ }
369
+ ```
370
+
371
+ ### Methods with Consistent Response Format
372
+
373
+ - `getContentItems()` - Always returns `CockpitListResponse<T> | null`
374
+ - `pages()` - Always returns `CockpitListResponse<T> | null`
375
+ - Fetch client methods - Always return `CockpitListResponse<T> | null`
376
+
377
+ ### Usage Example
378
+
379
+ ```typescript
380
+ import type { CockpitListResponse } from '@unchainedshop/cockpit-api';
381
+
382
+ // Always get { data, meta? } format
383
+ const response = await cockpit.getContentItems('posts', { limit: 10, skip: 0 });
384
+
385
+ // Access items
386
+ const items = response?.data || [];
387
+
388
+ // Access metadata (available when using skip parameter)
389
+ const total = response?.meta?.total;
390
+ ```
391
+
392
+ **Benefits:**
393
+ - No need to check if response is array or object
394
+ - Predictable type signatures
395
+ - Easier to work with pagination
396
+
308
397
  ## TypeScript Support
309
398
 
310
399
  ```typescript
@@ -314,6 +403,7 @@ import type {
314
403
  CockpitAPIOptions,
315
404
  CacheManager,
316
405
  CacheOptions,
406
+ AsyncCacheStore,
317
407
 
318
408
  // Query Options
319
409
  ContentItemQueryOptions,
@@ -330,6 +420,8 @@ import type {
330
420
  CockpitRoute,
331
421
  CockpitSearchResult,
332
422
  CockpitContentItem,
423
+ CockpitListResponse, // New: for paginated content responses
424
+ CockpitListMeta, // New: metadata in paginated responses
333
425
 
334
426
  // Schema Types
335
427
  MakeCockpitSchemaOptions,
@@ -360,6 +452,77 @@ import { ImageSizeMode, MimeType } from '@unchainedshop/cockpit-api';
360
452
  - `defaultLanguage` option to configure which language maps to Cockpit's "default" locale
361
453
  - Expanded tenant utilities: `resolveTenantFromUrl()`, `resolveTenantFromSubdomain()`
362
454
 
455
+ ### v2.2.0 (Breaking Changes)
456
+
457
+ **Async Cache Operations:**
458
+ - All cache operations are now async and return Promises
459
+ - `await cockpit.clearCache()` is now required (was synchronous in v2.1.x)
460
+ - Custom cache stores can be provided via `cache.store` option
461
+ - Cache can be explicitly disabled with `cache: false`
462
+
463
+ **Migration:**
464
+ ```typescript
465
+ // Before (v2.1.x)
466
+ cockpit.clearCache();
467
+ cockpit.clearCache('ROUTE');
468
+
469
+ // After (v2.2.0)
470
+ await cockpit.clearCache();
471
+ await cockpit.clearCache('ROUTE');
472
+ ```
473
+
474
+ ### v3.0.0 (Breaking Changes)
475
+
476
+ **Consistent List Response Format:**
477
+
478
+ All list methods now return `CockpitListResponse<T> | null` instead of varying between arrays and wrapped responses:
479
+
480
+ **Changed Methods:**
481
+ - `getContentItems()` - Now always returns `{ data: T[], meta?: {...} } | null`
482
+ - `pages()` - Now always returns `{ data: T[], meta?: {...} } | null`
483
+ - Fetch client `getContentItems()` and `pages()` - Now always return `{ data: T[], meta?: {...} } | null`
484
+
485
+ **Migration:**
486
+ ```typescript
487
+ // Before (v2.x)
488
+ const items = await cockpit.getContentItems('posts', { limit: 10 });
489
+ // items could be Post[] or null
490
+
491
+ const pages = await cockpit.pages({ limit: 10 });
492
+ // pages could be Page[] or null
493
+
494
+ // After (v3.0.0)
495
+ const itemsResponse = await cockpit.getContentItems('posts', { limit: 10 });
496
+ const items = itemsResponse?.data || [];
497
+ const total = itemsResponse?.meta?.total;
498
+
499
+ const pagesResponse = await cockpit.pages({ limit: 10 });
500
+ const pages = pagesResponse?.data || [];
501
+ const total = pagesResponse?.meta?.total;
502
+ ```
503
+
504
+ **Benefits:**
505
+ - Single, predictable return type for all list methods
506
+ - No need to check `Array.isArray()` or normalize responses
507
+ - Cleaner TypeScript types
508
+ - Metadata always accessible via `.meta` property
509
+
510
+ **TreeQueryOptions Type Correction:**
511
+
512
+ `TreeQueryOptions` no longer incorrectly includes `limit` and `skip` parameters (which were always ignored). Tree structures use `parent`, `populate`, `filter`, and `fields` instead.
513
+
514
+ ```typescript
515
+ // Before (v2.x) - allowed but ignored
516
+ await cockpit.getContentTree('categories', { limit: 10 }); // ❌ TypeScript allowed this
517
+
518
+ // After (v3.0.0) - TypeScript prevents invalid usage
519
+ await cockpit.getContentTree('categories', {
520
+ parent: 'root-id', // ✅ Correct
521
+ populate: 2, // ✅ Correct
522
+ filter: { active: true } // ✅ Correct
523
+ });
524
+ ```
525
+
363
526
  ## Peer Dependencies
364
527
 
365
528
  - `graphql` (optional) - Required for the `graphQL()` method
package/dist/client.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import type { DocumentNode } from "graphql";
5
5
  import { type CockpitAPIOptions } from "./core/config.ts";
6
- import { type ContentItemQueryOptions, type ContentListQueryOptions, type TreeQueryOptions, type AggregateQueryOptions, type CockpitContentItem, type CockpitTreeNode } from "./methods/content.ts";
6
+ import { type ContentItemQueryOptions, type ContentListQueryOptions, type TreeQueryOptions, type AggregateQueryOptions, type CockpitContentItem, type CockpitTreeNode, type CockpitListResponse } from "./methods/content.ts";
7
7
  import { type PageByIdOptions, type CockpitPage } from "./methods/pages.ts";
8
8
  import { type MenuQueryOptions, type CockpitMenu } from "./methods/menus.ts";
9
9
  import { type CockpitRoutesResponse, type CockpitSitemapEntry, type CockpitSettings } from "./methods/routes.ts";
@@ -16,12 +16,12 @@ import { type LocalizeOptions } from "./methods/localize.ts";
16
16
  export interface CockpitAPIClient {
17
17
  graphQL<T = unknown>(document: DocumentNode, variables?: Record<string, unknown>): Promise<T | null>;
18
18
  getContentItem<T = unknown>(options: ContentItemQueryOptions): Promise<T | null>;
19
- getContentItems<T = CockpitContentItem>(model: string, options?: ContentListQueryOptions): Promise<T[] | null>;
19
+ getContentItems<T = CockpitContentItem>(model: string, options?: ContentListQueryOptions): Promise<CockpitListResponse<T> | null>;
20
20
  getContentTree<T = CockpitContentItem>(model: string, options?: TreeQueryOptions): Promise<CockpitTreeNode<T>[] | null>;
21
21
  getAggregateModel<T = unknown>(options: AggregateQueryOptions): Promise<T[] | null>;
22
22
  postContentItem<T = unknown>(model: string, item: Record<string, unknown>): Promise<T | null>;
23
23
  deleteContentItem<T = unknown>(model: string, id: string): Promise<T | null>;
24
- pages<T = CockpitPage>(options?: ContentListQueryOptions): Promise<T[] | null>;
24
+ pages<T = CockpitPage>(options?: ContentListQueryOptions): Promise<CockpitListResponse<T> | null>;
25
25
  pageById<T = CockpitPage>(id: string, options?: PageByIdOptions): Promise<T | null>;
26
26
  pageByRoute<T = CockpitPage>(route: string, options?: {
27
27
  locale?: string;
@@ -39,14 +39,14 @@ export interface CockpitAPIClient {
39
39
  /**
40
40
  * Get a transformed image asset URL.
41
41
  *
42
- * **Important:** The `w` (width) or `h` (height) parameter is required by the API.
43
- * Without it, the API returns a 400 error.
42
+ * **Important:** At least one of `w` (width) or `h` (height) must be provided.
43
+ * The Cockpit CMS API requires this and returns a 400 error without it.
44
44
  *
45
45
  * @param assetId - The asset ID
46
46
  * @param queryParams - Image transformation parameters (w or h required)
47
47
  * @returns URL string to the generated image, or null if not found
48
48
  */
49
- imageAssetById(assetId: string, queryParams?: ImageAssetQueryParams): Promise<string | null>;
49
+ imageAssetById(assetId: string, queryParams: ImageAssetQueryParams): Promise<string | null>;
50
50
  getFullRouteForSlug(slug: string): Promise<string | undefined>;
51
51
  /**
52
52
  * Clear cache entries matching pattern
@@ -9,7 +9,7 @@
9
9
  * - Minimal memory footprint
10
10
  */
11
11
  import type { CockpitPage } from "../methods/pages.ts";
12
- import type { CockpitContentItem } from "../methods/content.ts";
12
+ import type { CockpitContentItem, CockpitListResponse } from "../methods/content.ts";
13
13
  /**
14
14
  * Request cache mode for fetch requests
15
15
  */
@@ -52,12 +52,12 @@ export interface PageFetchParams {
52
52
  export interface FetchClient {
53
53
  /** Fetch a page by route */
54
54
  pageByRoute<T = CockpitPage>(route: string, params?: PageFetchParams): Promise<T | null>;
55
- /** Fetch pages list */
56
- pages<T = CockpitPage>(params?: PageFetchParams): Promise<T[] | null>;
55
+ /** Fetch pages list. Always returns { data, meta? } or null. */
56
+ pages<T = CockpitPage>(params?: PageFetchParams): Promise<CockpitListResponse<T> | null>;
57
57
  /** Fetch a page by ID */
58
58
  pageById<T = CockpitPage>(id: string, params?: PageFetchParams): Promise<T | null>;
59
- /** Fetch content items */
60
- getContentItems<T = CockpitContentItem>(model: string, params?: PageFetchParams): Promise<T[] | null>;
59
+ /** Fetch content items. Always returns { data, meta? } or null. */
60
+ getContentItems<T = CockpitContentItem>(model: string, params?: PageFetchParams): Promise<CockpitListResponse<T> | null>;
61
61
  /** Fetch a single content item */
62
62
  getContentItem<T = unknown>(model: string, id?: string, params?: PageFetchParams): Promise<T | null>;
63
63
  /** Raw fetch for custom paths */
@@ -96,10 +96,18 @@ export function createFetchClient(options = {}) {
96
96
  */
97
97
  async pages(params = {}) {
98
98
  const { locale, ...rest } = params;
99
- return fetchRaw("/pages/pages", {
99
+ const result = await fetchRaw("/pages/pages", {
100
100
  locale: normalizeLocale(locale),
101
101
  ...rest,
102
102
  });
103
+ // Normalize response to always return { data, meta? }
104
+ if (result === null) {
105
+ return null;
106
+ }
107
+ if (Array.isArray(result)) {
108
+ return { data: result };
109
+ }
110
+ return result;
103
111
  },
104
112
  /**
105
113
  * Fetch a page by ID
@@ -117,10 +125,18 @@ export function createFetchClient(options = {}) {
117
125
  */
118
126
  async getContentItems(model, params = {}) {
119
127
  const { locale, ...rest } = params;
120
- return fetchRaw(`/content/items/${model}`, {
128
+ const result = await fetchRaw(`/content/items/${model}`, {
121
129
  locale: normalizeLocale(locale),
122
130
  ...rest,
123
131
  });
132
+ // Normalize response to always return { data, meta? }
133
+ if (result === null) {
134
+ return null;
135
+ }
136
+ if (Array.isArray(result)) {
137
+ return { data: result };
138
+ }
139
+ return result;
124
140
  },
125
141
  /**
126
142
  * Fetch a single content item
package/dist/index.d.ts CHANGED
@@ -18,7 +18,7 @@ export type { PageByIdOptions, PageByRouteOptions } from "./methods/pages.ts";
18
18
  export type { MenuQueryOptions } from "./methods/menus.ts";
19
19
  export type { SearchQueryOptions } from "./methods/search.ts";
20
20
  export type { LocalizeOptions } from "./methods/localize.ts";
21
- export type { CockpitContentItem, CockpitNewsItem, CockpitTreeNode, } from "./methods/content.ts";
21
+ export type { CockpitContentItem, CockpitNewsItem, CockpitTreeNode, CockpitListResponse, CockpitListMeta, } from "./methods/content.ts";
22
22
  export { ImageSizeMode, MimeType } from "./methods/assets.ts";
23
23
  export type { CockpitAsset, ImageAssetQueryParams } from "./methods/assets.ts";
24
24
  export type { CockpitPageType, CockpitPageMeta, CockpitPageSeo, CockpitLayoutBlock, CockpitPage, } from "./methods/pages.ts";
@@ -38,28 +38,38 @@ export declare enum MimeType {
38
38
  WEBP = "webp",
39
39
  BMP = "bmp"
40
40
  }
41
- export interface ImageAssetQueryParams {
41
+ /**
42
+ * Image transformation parameters for imageAssetById.
43
+ *
44
+ * At least one of `w` (width) or `h` (height) must be provided.
45
+ * The Cockpit CMS API requires this and returns a 400 error without it.
46
+ */
47
+ export type ImageAssetQueryParams = {
42
48
  m?: ImageSizeMode;
43
- w?: number;
44
- h?: number;
45
49
  q?: number;
46
50
  mime?: MimeType;
47
51
  re?: number;
48
52
  t?: string;
49
53
  o?: number;
50
- }
54
+ } & ({
55
+ w: number;
56
+ h?: number;
57
+ } | {
58
+ w?: number;
59
+ h: number;
60
+ });
51
61
  export interface AssetMethods {
52
62
  assetById<T = CockpitAsset>(assetId: string): Promise<T | null>;
53
63
  /**
54
64
  * Get a transformed image asset URL.
55
65
  *
56
- * **Important:** The `w` (width) or `h` (height) parameter is required by the API.
57
- * Without it, the API returns a 400 error.
66
+ * **Important:** At least one of `w` (width) or `h` (height) must be provided.
67
+ * The Cockpit CMS API requires this and returns a 400 error without it.
58
68
  *
59
69
  * @param assetId - The asset ID
60
70
  * @param queryParams - Image transformation parameters (w or h required)
61
71
  * @returns URL string to the generated image, or null if not found
62
72
  */
63
- imageAssetById(assetId: string, queryParams?: ImageAssetQueryParams): Promise<string | null>;
73
+ imageAssetById(assetId: string, queryParams: ImageAssetQueryParams): Promise<string | null>;
64
74
  }
65
75
  export declare function createAssetMethods(ctx: MethodContext): AssetMethods;
@@ -30,9 +30,15 @@ export interface ContentItemQueryOptions extends ListQueryOptions {
30
30
  export interface ContentListQueryOptions extends ListQueryOptions {
31
31
  queryParams?: Record<string, unknown>;
32
32
  }
33
- export interface TreeQueryOptions extends ListQueryOptions {
33
+ export interface TreeQueryOptions {
34
34
  parent?: string;
35
+ filter?: Record<string, unknown>;
36
+ fields?: Record<string, 0 | 1>;
37
+ populate?: number;
38
+ locale?: string;
35
39
  queryParams?: Record<string, unknown>;
40
+ /** Override the client-level useAdminAccess setting for this request */
41
+ useAdminAccess?: boolean;
36
42
  }
37
43
  export interface AggregateQueryOptions {
38
44
  model: string;
@@ -66,9 +72,35 @@ export interface CockpitTreeNode<T = CockpitContentItem> {
66
72
  children?: CockpitTreeNode<T>[];
67
73
  data?: T;
68
74
  }
75
+ /**
76
+ * Metadata returned with paginated content responses
77
+ */
78
+ export interface CockpitListMeta {
79
+ total?: number;
80
+ [key: string]: unknown;
81
+ }
82
+ /**
83
+ * Wrapper response format returned by Cockpit when using pagination (skip parameter)
84
+ */
85
+ export interface CockpitListResponse<T> {
86
+ data: T[];
87
+ meta?: CockpitListMeta;
88
+ }
69
89
  export interface ContentMethods {
70
90
  getContentItem<T = unknown>(options: ContentItemQueryOptions): Promise<T | null>;
71
- getContentItems<T = CockpitContentItem>(model: string, options?: ContentListQueryOptions): Promise<T[] | null>;
91
+ /**
92
+ * Get multiple content items from a collection.
93
+ *
94
+ * @returns Always returns `CockpitListResponse<T>` with data and optional meta.
95
+ * Returns `null` if collection doesn't exist.
96
+ *
97
+ * @example
98
+ * const response = await cockpit.getContentItems('posts', { limit: 10 });
99
+ * // response: { data: Post[], meta?: { total: number } } | null
100
+ * const items = response?.data || [];
101
+ * const total = response?.meta?.total;
102
+ */
103
+ getContentItems<T = CockpitContentItem>(model: string, options?: ContentListQueryOptions): Promise<CockpitListResponse<T> | null>;
72
104
  getContentTree<T = CockpitContentItem>(model: string, options?: TreeQueryOptions): Promise<CockpitTreeNode<T>[] | null>;
73
105
  getAggregateModel<T = unknown>(options: AggregateQueryOptions): Promise<T[] | null>;
74
106
  postContentItem<T = unknown>(model: string, item: Record<string, unknown>): Promise<T | null>;
@@ -33,7 +33,15 @@ export function createContentMethods(ctx) {
33
33
  populate,
34
34
  },
35
35
  });
36
- return ctx.http.fetch(url, buildFetchOptions(useAdminAccess));
36
+ const result = await ctx.http.fetch(url, buildFetchOptions(useAdminAccess));
37
+ // Normalize response to always return { data, meta? }
38
+ if (result === null) {
39
+ return null;
40
+ }
41
+ if (Array.isArray(result)) {
42
+ return { data: result };
43
+ }
44
+ return result;
37
45
  },
38
46
  async getContentTree(model, options = {}) {
39
47
  requireParam(model, "a model");
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Pages API methods
3
3
  */
4
- import type { MethodContext, ContentListQueryOptions } from "./content.ts";
4
+ import type { MethodContext, ContentListQueryOptions, CockpitListResponse } from "./content.ts";
5
5
  import type { CockpitAsset } from "./assets.ts";
6
6
  export interface PageByIdOptions {
7
7
  locale?: string;
@@ -42,7 +42,19 @@ export interface CockpitPage extends CockpitPageMeta {
42
42
  _p?: string;
43
43
  }
44
44
  export interface PagesMethods {
45
- pages<T = CockpitPage>(options?: ContentListQueryOptions): Promise<T[] | null>;
45
+ /**
46
+ * Get pages list.
47
+ *
48
+ * @returns Always returns `CockpitListResponse<T>` with data and optional meta.
49
+ * Returns `null` if pages cannot be fetched.
50
+ *
51
+ * @example
52
+ * const response = await cockpit.pages({ limit: 10, skip: 0 });
53
+ * // response: { data: CockpitPage[], meta?: { total: number } } | null
54
+ * const pages = response?.data || [];
55
+ * const total = response?.meta?.total;
56
+ */
57
+ pages<T = CockpitPage>(options?: ContentListQueryOptions): Promise<CockpitListResponse<T> | null>;
46
58
  pageById<T = CockpitPage>(id: string, options?: PageByIdOptions): Promise<T | null>;
47
59
  pageByRoute<T = CockpitPage>(route: string, options?: PageByRouteOptions | string): Promise<T | null>;
48
60
  }
@@ -10,7 +10,18 @@ export function createPagesMethods(ctx) {
10
10
  locale,
11
11
  queryParams: { ...queryParams, limit, skip, sort, filter, fields },
12
12
  });
13
- return ctx.http.fetch(url);
13
+ const result = await ctx.http.fetch(url);
14
+ // Normalize response to always return { data, meta? }
15
+ // Note: The Cockpit /api/pages/pages endpoint returns a raw array even when skip
16
+ // is provided, unlike /api/content/items/{model} which returns { data, meta }.
17
+ // This means meta.total will not be available for pages() method.
18
+ if (result === null) {
19
+ return null;
20
+ }
21
+ if (Array.isArray(result)) {
22
+ return { data: result };
23
+ }
24
+ return result;
14
25
  },
15
26
  async pageById(id, options = {}) {
16
27
  requireParam(id, "a page id");
@@ -28,7 +28,7 @@ export interface CockpitSettings {
28
28
  logo?: CockpitAsset | null;
29
29
  small?: CockpitAsset | null;
30
30
  favicon?: CockpitAsset | null;
31
- [key: string]: CockpitAsset | null | undefined;
31
+ [key: string]: CockpitAsset | null;
32
32
  };
33
33
  scripts?: {
34
34
  header?: string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unchainedshop/cockpit-api",
3
- "version": "2.2.0",
3
+ "version": "2.2.2",
4
4
  "description": "A package to interact with the Cockpit CMS API, including functionalities to handle GraphQL requests and various CMS content manipulations.",
5
5
  "main": "dist/index.js",
6
6
  "homepage": "https://unchained.shop",