@unchainedshop/cockpit-api 2.1.2 → 2.2.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.
package/dist/client.d.ts CHANGED
@@ -48,7 +48,25 @@ export interface CockpitAPIClient {
48
48
  */
49
49
  imageAssetById(assetId: string, queryParams?: ImageAssetQueryParams): Promise<string | null>;
50
50
  getFullRouteForSlug(slug: string): Promise<string | undefined>;
51
- clearCache(pattern?: string): void;
51
+ /**
52
+ * Clear cache entries matching pattern
53
+ *
54
+ * **BREAKING CHANGE (v3.0.0)**: This method is now async and returns a Promise
55
+ *
56
+ * @param pattern - Optional pattern to clear specific cache entries
57
+ * @returns Promise that resolves when clearing is complete
58
+ *
59
+ * @example Clear all cache
60
+ * ```typescript
61
+ * await client.clearCache();
62
+ * ```
63
+ *
64
+ * @example Clear route cache only
65
+ * ```typescript
66
+ * await client.clearCache('ROUTE');
67
+ * ```
68
+ */
69
+ clearCache(pattern?: string): Promise<void>;
52
70
  }
53
71
  /**
54
72
  * Creates a Cockpit API client
package/dist/client.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Cockpit API Client Factory
3
3
  */
4
4
  import { createConfig } from "./core/config.js";
5
- import { createCacheManager } from "./core/cache.js";
5
+ import { createCacheManager, createNoOpCacheManager, } from "./core/cache.js";
6
6
  import { createUrlBuilder } from "./core/url-builder.js";
7
7
  import { createHttpClient } from "./core/http.js";
8
8
  import { createImagePathTransformer } from "./transformers/image-path.js";
@@ -39,19 +39,40 @@ export async function CockpitAPI(options = {}) {
39
39
  // Create configuration
40
40
  const config = createConfig(options);
41
41
  const endpointString = config.endpoint.toString();
42
- // Create cache manager - env vars take precedence, then options, then cache.ts defaults
43
- const envCacheMax = process.env["COCKPIT_CACHE_MAX"];
44
- const envCacheTtl = process.env["COCKPIT_CACHE_TTL"];
45
- const cacheOptions = {};
46
- const maxValue = options.cache?.max ??
47
- (envCacheMax !== undefined ? parseInt(envCacheMax, 10) : undefined);
48
- const ttlValue = options.cache?.ttl ??
49
- (envCacheTtl !== undefined ? parseInt(envCacheTtl, 10) : undefined);
50
- if (maxValue !== undefined)
51
- cacheOptions.max = maxValue;
52
- if (ttlValue !== undefined)
53
- cacheOptions.ttl = ttlValue;
54
- const cache = createCacheManager(config.cachePrefix, cacheOptions);
42
+ // Create cache manager based on options
43
+ let cache;
44
+ if (options.cache === false) {
45
+ // Cache explicitly disabled - use no-op cache
46
+ cache = createNoOpCacheManager();
47
+ }
48
+ else {
49
+ // Cache enabled - determine options
50
+ const envCacheMax = process.env["COCKPIT_CACHE_MAX"];
51
+ const envCacheTtl = process.env["COCKPIT_CACHE_TTL"];
52
+ const cacheOptions = {};
53
+ // If custom store provided, use it directly
54
+ if (options.cache && "store" in options.cache) {
55
+ cacheOptions.store = options.cache.store;
56
+ }
57
+ else {
58
+ // Use max/ttl from options or env vars (for default LRU store)
59
+ const maxValue = options.cache && "max" in options.cache
60
+ ? options.cache.max
61
+ : envCacheMax !== undefined
62
+ ? parseInt(envCacheMax, 10)
63
+ : undefined;
64
+ const ttlValue = options.cache && "ttl" in options.cache
65
+ ? options.cache.ttl
66
+ : envCacheTtl !== undefined
67
+ ? parseInt(envCacheTtl, 10)
68
+ : undefined;
69
+ if (maxValue !== undefined)
70
+ cacheOptions.max = maxValue;
71
+ if (ttlValue !== undefined)
72
+ cacheOptions.ttl = ttlValue;
73
+ }
74
+ cache = createCacheManager(config.cachePrefix, cacheOptions);
75
+ }
55
76
  // Generate route replacements for image path transformer (optional)
56
77
  const routeReplacements = options.preloadRoutes === true
57
78
  ? await generateCmsRouteReplacements(endpointString, options.tenant, cache)
@@ -1,19 +1,207 @@
1
1
  /**
2
- * Cache management with LRU cache and tenant isolation
2
+ * Cache management with pluggable async stores and tenant isolation
3
+ *
4
+ * v3.0.0 Breaking Change: All cache operations are now async
5
+ */
6
+ /**
7
+ * Async cache store interface that custom cache implementations must implement
8
+ *
9
+ * @example Redis implementation
10
+ * ```typescript
11
+ * import { createClient } from 'redis';
12
+ * import type { AsyncCacheStore } from '@unchainedshop/cockpit-api';
13
+ *
14
+ * const redisClient = createClient({ url: 'redis://localhost:6379' });
15
+ * await redisClient.connect();
16
+ *
17
+ * const redisStore: AsyncCacheStore = {
18
+ * async get(key: string) {
19
+ * const value = await redisClient.get(key);
20
+ * return value ? JSON.parse(value) : undefined;
21
+ * },
22
+ * async set(key: string, value: unknown) {
23
+ * await redisClient.set(key, JSON.stringify(value), { EX: 100 });
24
+ * },
25
+ * async clear(pattern?: string) {
26
+ * if (pattern) {
27
+ * const keys = await redisClient.keys(`${pattern}*`);
28
+ * if (keys.length > 0) await redisClient.del(keys);
29
+ * } else {
30
+ * await redisClient.flushDb();
31
+ * }
32
+ * }
33
+ * };
34
+ * ```
35
+ *
36
+ * @example Keyv implementation
37
+ * ```typescript
38
+ * import Keyv from 'keyv';
39
+ * import type { AsyncCacheStore } from '@unchainedshop/cockpit-api';
40
+ *
41
+ * const keyv = new Keyv('redis://localhost:6379');
42
+ *
43
+ * const keyvStore: AsyncCacheStore = {
44
+ * async get(key: string) {
45
+ * return await keyv.get(key);
46
+ * },
47
+ * async set(key: string, value: unknown) {
48
+ * await keyv.set(key, value, 100000); // 100000ms TTL
49
+ * },
50
+ * async clear(pattern?: string) {
51
+ * if (!pattern) {
52
+ * await keyv.clear();
53
+ * }
54
+ * // Note: Keyv doesn't have native pattern matching
55
+ * // Pattern matching requires custom implementation
56
+ * }
57
+ * };
58
+ * ```
59
+ */
60
+ export interface AsyncCacheStore {
61
+ /**
62
+ * Retrieve a value from cache
63
+ * @param key - Cache key
64
+ * @returns Promise resolving to the cached value, or undefined if not found
65
+ */
66
+ get(key: string): Promise<unknown>;
67
+ /**
68
+ * Store a value in cache
69
+ * @param key - Cache key
70
+ * @param value - Value to store (must be serializable for external stores)
71
+ * @returns Promise that resolves when storage is complete
72
+ */
73
+ set(key: string, value: NonNullable<unknown>): Promise<void>;
74
+ /**
75
+ * Clear cache entries
76
+ * @param pattern - Optional pattern to match keys (implementation-specific)
77
+ * If not provided, clears all entries
78
+ * @returns Promise that resolves when clearing is complete
79
+ */
80
+ clear(pattern?: string): Promise<void>;
81
+ }
82
+ /**
83
+ * Cache configuration options
3
84
  */
4
85
  export interface CacheOptions {
5
- /** Maximum number of entries (default: 100) */
86
+ /**
87
+ * Maximum number of entries (default: 100)
88
+ * Only used with default LRU store. Ignored when custom store is provided.
89
+ */
6
90
  max?: number;
7
- /** Time-to-live in milliseconds (default: 100000) */
91
+ /**
92
+ * Time-to-live in milliseconds (default: 100000)
93
+ * Only used with default LRU store. Ignored when custom store is provided.
94
+ */
8
95
  ttl?: number;
96
+ /**
97
+ * Custom async cache store implementation
98
+ * If provided, max and ttl options are ignored
99
+ *
100
+ * @example Redis store
101
+ * ```typescript
102
+ * import { createClient } from 'redis';
103
+ *
104
+ * const redisClient = createClient();
105
+ * await redisClient.connect();
106
+ *
107
+ * const client = await CockpitAPI({
108
+ * endpoint: 'https://cms.example.com/api/graphql',
109
+ * cache: {
110
+ * store: {
111
+ * async get(key) {
112
+ * const val = await redisClient.get(key);
113
+ * return val ? JSON.parse(val) : undefined;
114
+ * },
115
+ * async set(key, value) {
116
+ * await redisClient.set(key, JSON.stringify(value), { EX: 100 });
117
+ * },
118
+ * async clear(pattern) {
119
+ * if (pattern) {
120
+ * const keys = await redisClient.keys(`${pattern}*`);
121
+ * if (keys.length > 0) await redisClient.del(keys);
122
+ * } else {
123
+ * await redisClient.flushDb();
124
+ * }
125
+ * }
126
+ * }
127
+ * }
128
+ * });
129
+ * ```
130
+ */
131
+ store?: AsyncCacheStore;
9
132
  }
133
+ /**
134
+ * Async cache manager interface
135
+ * All cache operations return promises in v3.0.0+
136
+ */
10
137
  export interface CacheManager {
11
- get(key: string): unknown;
12
- set(key: string, value: NonNullable<unknown>): void;
13
- clear(pattern?: string): void;
138
+ /**
139
+ * Get a value from cache
140
+ * @param key - Cache key (will be prefixed internally)
141
+ * @returns Promise resolving to cached value or undefined if not found
142
+ */
143
+ get(key: string): Promise<unknown>;
144
+ /**
145
+ * Set a value in cache
146
+ * @param key - Cache key (will be prefixed internally)
147
+ * @param value - Value to cache
148
+ * @returns Promise that resolves when caching is complete
149
+ */
150
+ set(key: string, value: NonNullable<unknown>): Promise<void>;
151
+ /**
152
+ * Clear cache entries matching pattern
153
+ * @param pattern - Optional pattern to match (relative to cache prefix)
154
+ * @returns Promise that resolves when clearing is complete
155
+ */
156
+ clear(pattern?: string): Promise<void>;
14
157
  }
15
158
  /**
16
- * Creates a cache manager with prefixed keys
17
- * Each call creates a new LRU cache instance - no shared state
159
+ * Creates a cache manager with prefixed keys and async operations
160
+ * Each call creates a new cache instance - no shared state
161
+ *
162
+ * @param cachePrefix - Prefix for all cache keys (includes endpoint and tenant)
163
+ * @param options - Cache configuration options
164
+ * @returns Async cache manager
165
+ *
166
+ * @example Using default LRU cache
167
+ * ```typescript
168
+ * const cache = createCacheManager('https://cms.example.com:default:', {
169
+ * max: 100,
170
+ * ttl: 100000
171
+ * });
172
+ *
173
+ * await cache.set('key1', { data: 'value' });
174
+ * const value = await cache.get('key1');
175
+ * await cache.clear('ROUTE');
176
+ * ```
177
+ *
178
+ * @example Using custom Redis store
179
+ * ```typescript
180
+ * const redisClient = createClient();
181
+ * await redisClient.connect();
182
+ *
183
+ * const cache = createCacheManager('https://cms.example.com:default:', {
184
+ * store: {
185
+ * async get(key) { ... },
186
+ * async set(key, value) { ... },
187
+ * async clear(pattern) { ... }
188
+ * }
189
+ * });
190
+ * ```
18
191
  */
19
192
  export declare function createCacheManager(cachePrefix: string, options?: CacheOptions): CacheManager;
193
+ /**
194
+ * Creates a no-op cache manager that doesn't cache anything
195
+ * Used when caching is explicitly disabled
196
+ *
197
+ * @returns No-op cache manager
198
+ *
199
+ * @example
200
+ * ```typescript
201
+ * const cache = createNoOpCacheManager();
202
+ * await cache.set('key', 'value'); // Does nothing
203
+ * const result = await cache.get('key'); // Always returns undefined
204
+ * await cache.clear(); // Does nothing
205
+ * ```
206
+ */
207
+ export declare function createNoOpCacheManager(): CacheManager;
@@ -1,32 +1,119 @@
1
1
  /**
2
- * Cache management with LRU cache and tenant isolation
2
+ * Cache management with pluggable async stores and tenant isolation
3
+ *
4
+ * v3.0.0 Breaking Change: All cache operations are now async
3
5
  */
4
6
  import { LRUCache } from "lru-cache";
5
7
  /**
6
- * Creates a cache manager with prefixed keys
7
- * Each call creates a new LRU cache instance - no shared state
8
+ * Creates a default LRU-based async cache store
9
+ * Wraps lru-cache in async interface for consistency
8
10
  */
9
- export function createCacheManager(cachePrefix, options = {}) {
11
+ function createDefaultLRUStore(options) {
10
12
  const cache = new LRUCache({
11
13
  max: options.max ?? 100,
12
14
  ttl: options.ttl ?? 100000,
13
15
  allowStale: false,
14
16
  });
15
- const prefixedKey = (key) => `${cachePrefix}${key}`;
16
17
  return {
17
- get(key) {
18
- return cache.get(prefixedKey(key));
18
+ // eslint-disable-next-line @typescript-eslint/require-await
19
+ async get(key) {
20
+ return cache.get(key);
19
21
  },
20
- set(key, value) {
21
- cache.set(prefixedKey(key), value);
22
+ // eslint-disable-next-line @typescript-eslint/require-await
23
+ async set(key, value) {
24
+ cache.set(key, value);
22
25
  },
23
- clear(pattern) {
24
- const prefix = pattern !== undefined ? `${cachePrefix}${pattern}` : cachePrefix;
25
- for (const key of cache.keys()) {
26
- if (key.startsWith(prefix)) {
27
- cache.delete(key);
26
+ // eslint-disable-next-line @typescript-eslint/require-await
27
+ async clear(pattern) {
28
+ if (pattern === undefined) {
29
+ cache.clear();
30
+ }
31
+ else {
32
+ for (const key of cache.keys()) {
33
+ if (key.startsWith(pattern)) {
34
+ cache.delete(key);
35
+ }
28
36
  }
29
37
  }
30
38
  },
31
39
  };
32
40
  }
41
+ /**
42
+ * Creates a cache manager with prefixed keys and async operations
43
+ * Each call creates a new cache instance - no shared state
44
+ *
45
+ * @param cachePrefix - Prefix for all cache keys (includes endpoint and tenant)
46
+ * @param options - Cache configuration options
47
+ * @returns Async cache manager
48
+ *
49
+ * @example Using default LRU cache
50
+ * ```typescript
51
+ * const cache = createCacheManager('https://cms.example.com:default:', {
52
+ * max: 100,
53
+ * ttl: 100000
54
+ * });
55
+ *
56
+ * await cache.set('key1', { data: 'value' });
57
+ * const value = await cache.get('key1');
58
+ * await cache.clear('ROUTE');
59
+ * ```
60
+ *
61
+ * @example Using custom Redis store
62
+ * ```typescript
63
+ * const redisClient = createClient();
64
+ * await redisClient.connect();
65
+ *
66
+ * const cache = createCacheManager('https://cms.example.com:default:', {
67
+ * store: {
68
+ * async get(key) { ... },
69
+ * async set(key, value) { ... },
70
+ * async clear(pattern) { ... }
71
+ * }
72
+ * });
73
+ * ```
74
+ */
75
+ export function createCacheManager(cachePrefix, options = {}) {
76
+ // Use custom store if provided, otherwise create default LRU store
77
+ const store = options.store ?? createDefaultLRUStore(options);
78
+ const prefixedKey = (key) => `${cachePrefix}${key}`;
79
+ return {
80
+ async get(key) {
81
+ return await store.get(prefixedKey(key));
82
+ },
83
+ async set(key, value) {
84
+ await store.set(prefixedKey(key), value);
85
+ },
86
+ async clear(pattern) {
87
+ const prefix = pattern !== undefined ? `${cachePrefix}${pattern}` : cachePrefix;
88
+ await store.clear(prefix);
89
+ },
90
+ };
91
+ }
92
+ /**
93
+ * Creates a no-op cache manager that doesn't cache anything
94
+ * Used when caching is explicitly disabled
95
+ *
96
+ * @returns No-op cache manager
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * const cache = createNoOpCacheManager();
101
+ * await cache.set('key', 'value'); // Does nothing
102
+ * const result = await cache.get('key'); // Always returns undefined
103
+ * await cache.clear(); // Does nothing
104
+ * ```
105
+ */
106
+ export function createNoOpCacheManager() {
107
+ return {
108
+ // eslint-disable-next-line @typescript-eslint/require-await
109
+ async get() {
110
+ return undefined;
111
+ },
112
+ async set() {
113
+ // No-op
114
+ },
115
+ async clear() {
116
+ // No-op
117
+ },
118
+ };
119
+ }
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Configuration management for Cockpit API client
3
3
  */
4
+ import type { CacheOptions } from "./cache.ts";
4
5
  export interface CockpitAPIOptions {
5
6
  /** Cockpit CMS endpoint URL (falls back to COCKPIT_GRAPHQL_ENDPOINT env var) */
6
7
  endpoint?: string;
@@ -15,13 +16,61 @@ export interface CockpitAPIOptions {
15
16
  * When a request uses this language, it will be sent as "default" to Cockpit.
16
17
  */
17
18
  defaultLanguage?: string | null;
18
- /** Cache configuration */
19
- cache?: {
20
- /** Max entries (falls back to COCKPIT_CACHE_MAX env var, default: 100) */
21
- max?: number;
22
- /** TTL in ms (falls back to COCKPIT_CACHE_TTL env var, default: 100000) */
23
- ttl?: number;
24
- };
19
+ /**
20
+ * Cache configuration
21
+ *
22
+ * - Set to `false` to disable caching entirely
23
+ * - Set to an object to configure cache behavior
24
+ * - Omit to use default LRU cache with env var fallbacks
25
+ *
26
+ * @example Disable cache
27
+ * ```typescript
28
+ * const client = await CockpitAPI({
29
+ * endpoint: 'https://cms.example.com',
30
+ * cache: false
31
+ * });
32
+ * ```
33
+ *
34
+ * @example Custom cache options
35
+ * ```typescript
36
+ * const client = await CockpitAPI({
37
+ * endpoint: 'https://cms.example.com',
38
+ * cache: { max: 200, ttl: 300000 }
39
+ * });
40
+ * ```
41
+ *
42
+ * @example Redis store
43
+ * ```typescript
44
+ * import { createClient } from 'redis';
45
+ *
46
+ * const redisClient = createClient({ url: process.env.REDIS_URL });
47
+ * await redisClient.connect();
48
+ *
49
+ * const client = await CockpitAPI({
50
+ * endpoint: 'https://cms.example.com',
51
+ * cache: {
52
+ * store: {
53
+ * async get(key) {
54
+ * const val = await redisClient.get(key);
55
+ * return val ? JSON.parse(val) : undefined;
56
+ * },
57
+ * async set(key, value) {
58
+ * await redisClient.set(key, JSON.stringify(value), { EX: 100 });
59
+ * },
60
+ * async clear(pattern) {
61
+ * if (pattern) {
62
+ * const keys = await redisClient.keys(\`\${pattern}*\`);
63
+ * if (keys.length > 0) await redisClient.del(keys);
64
+ * } else {
65
+ * await redisClient.flushDb();
66
+ * }
67
+ * }
68
+ * }
69
+ * }
70
+ * });
71
+ * ```
72
+ */
73
+ cache?: false | CacheOptions;
25
74
  /**
26
75
  * Preload route replacements during client initialization.
27
76
  * When true, fetches page routes to enable `pages://id` link resolution in responses.
@@ -2,8 +2,8 @@
2
2
  * Core module exports
3
3
  */
4
4
  export { buildQueryString, encodeQueryParam } from "./query-string.ts";
5
- export type { CacheManager, CacheOptions } from "./cache.ts";
6
- export { createCacheManager } from "./cache.ts";
5
+ export type { CacheManager, CacheOptions, AsyncCacheStore } from "./cache.ts";
6
+ export { createCacheManager, createNoOpCacheManager } from "./cache.ts";
7
7
  export type { CockpitConfig } from "./config.ts";
8
8
  export { createConfig } from "./config.ts";
9
9
  export type { UrlBuilder, UrlBuildOptions } from "./url-builder.ts";
@@ -2,7 +2,7 @@
2
2
  * Core module exports
3
3
  */
4
4
  export { buildQueryString, encodeQueryParam } from "./query-string.js";
5
- export { createCacheManager } from "./cache.js";
5
+ export { createCacheManager, createNoOpCacheManager } from "./cache.js";
6
6
  export { createConfig } from "./config.js";
7
7
  export { createUrlBuilder } from "./url-builder.js";
8
8
  export { createHttpClient } from "./http.js";
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  export { CockpitAPI } from "./client.ts";
7
7
  export type { CockpitAPIClient } from "./client.ts";
8
8
  export type { CockpitAPIOptions } from "./core/config.ts";
9
- export type { CacheManager, CacheOptions } from "./core/cache.ts";
9
+ export type { CacheManager, CacheOptions, AsyncCacheStore, } from "./core/cache.ts";
10
10
  export { getTenantIds, resolveTenantFromUrl, resolveTenantFromSubdomain, } from "./utils/tenant.ts";
11
11
  export type { TenantUrlResult, ResolveTenantFromUrlOptions, ResolveTenantFromSubdomainOptions, } from "./utils/tenant.ts";
12
12
  export { generateCmsRouteReplacements, generateCollectionAndSingletonSlugRouteMap, } from "./utils/route-map.ts";
@@ -24,7 +24,7 @@ export interface CockpitMenuLink {
24
24
  meta?: {
25
25
  key: string;
26
26
  value: string;
27
- }[];
27
+ }[] | Record<string, string>;
28
28
  }
29
29
  export interface CockpitMenu {
30
30
  _id: string;
@@ -10,6 +10,24 @@ export interface CockpitHealthCheck {
10
10
  }
11
11
  export interface SystemMethods {
12
12
  healthCheck<T = unknown>(): Promise<T | null>;
13
- clearCache(pattern?: string): void;
13
+ /**
14
+ * Clear cache entries matching pattern
15
+ *
16
+ * **BREAKING CHANGE (v3.0.0)**: This method is now async and returns a Promise
17
+ *
18
+ * @param pattern - Optional pattern to clear specific cache entries
19
+ * @returns Promise that resolves when clearing is complete
20
+ *
21
+ * @example Clear all cache
22
+ * ```typescript
23
+ * await client.clearCache();
24
+ * ```
25
+ *
26
+ * @example Clear route cache only
27
+ * ```typescript
28
+ * await client.clearCache('ROUTE');
29
+ * ```
30
+ */
31
+ clearCache(pattern?: string): Promise<void>;
14
32
  }
15
33
  export declare function createSystemMethods(ctx: MethodContext): SystemMethods;
@@ -7,8 +7,8 @@ export function createSystemMethods(ctx) {
7
7
  const url = ctx.url.build("/system/healthcheck");
8
8
  return ctx.http.fetch(url);
9
9
  },
10
- clearCache(pattern) {
11
- ctx.cache.clear(pattern);
10
+ async clearCache(pattern) {
11
+ await ctx.cache.clear(pattern);
12
12
  },
13
13
  };
14
14
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Meta transformer for converting Cockpit CMS meta arrays to objects
3
+ *
4
+ * This transformer converts meta properties from array format:
5
+ * meta: [{ key: 'foo', value: 'bar' }]
6
+ *
7
+ * To object format:
8
+ * meta: { foo: 'bar' }
9
+ */
10
+ import type { ResponseTransformer } from "./image-path.ts";
11
+ /**
12
+ * Creates a transformer that converts meta arrays to objects.
13
+ * This transformation affects all meta properties in the response,
14
+ * including nested ones in children arrays.
15
+ */
16
+ export declare function createMetaTransformer(): ResponseTransformer;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Meta transformer for converting Cockpit CMS meta arrays to objects
3
+ *
4
+ * This transformer converts meta properties from array format:
5
+ * meta: [{ key: 'foo', value: 'bar' }]
6
+ *
7
+ * To object format:
8
+ * meta: { foo: 'bar' }
9
+ */
10
+ import { logger } from "../cockpit-logger.js";
11
+ import { transformCockpitMeta } from "../utils/meta.js";
12
+ /**
13
+ * Recursively transforms all meta arrays in a data structure to objects
14
+ */
15
+ function transformMetaRecursive(data) {
16
+ if (data === null || data === undefined || typeof data !== "object") {
17
+ return data;
18
+ }
19
+ if (Array.isArray(data)) {
20
+ return data.map(transformMetaRecursive);
21
+ }
22
+ const result = {};
23
+ for (const [key, value] of Object.entries(data)) {
24
+ if (key === "meta" && Array.isArray(value)) {
25
+ // Transform meta array to object
26
+ result[key] = transformCockpitMeta(value);
27
+ }
28
+ else {
29
+ // Recursively transform nested objects
30
+ result[key] = transformMetaRecursive(value);
31
+ }
32
+ }
33
+ return result;
34
+ }
35
+ /**
36
+ * Creates a transformer that converts meta arrays to objects.
37
+ * This transformation affects all meta properties in the response,
38
+ * including nested ones in children arrays.
39
+ */
40
+ export function createMetaTransformer() {
41
+ return {
42
+ transform(originalResponse) {
43
+ try {
44
+ return transformMetaRecursive(originalResponse);
45
+ }
46
+ catch (error) {
47
+ logger.warn("Cockpit: Failed to transform meta arrays", error);
48
+ return originalResponse;
49
+ }
50
+ },
51
+ };
52
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Transforms Cockpit meta array into an object for easier access
3
+ *
4
+ * @param meta Array of {key, value} objects from Cockpit API
5
+ * @returns Object with key-value pairs
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const meta = [
10
+ * { key: 'layout', value: 'shop' },
11
+ * { key: 'isCallToAction', value: 'true' },
12
+ * ];
13
+ * const metaObj = transformCockpitMeta(meta);
14
+ * if (metaObj.layout === 'shop') {
15
+ * // Handle shop layout
16
+ * }
17
+ * ```
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * // Safe to use with optional meta
22
+ * const metaObj = transformCockpitMeta(link.meta);
23
+ * const layout = metaObj.layout ?? 'default';
24
+ * ```
25
+ */
26
+ export declare function transformCockpitMeta(meta?: {
27
+ key: string;
28
+ value: string;
29
+ }[]): Record<string, string>;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Transforms Cockpit meta array into an object for easier access
3
+ *
4
+ * @param meta Array of {key, value} objects from Cockpit API
5
+ * @returns Object with key-value pairs
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const meta = [
10
+ * { key: 'layout', value: 'shop' },
11
+ * { key: 'isCallToAction', value: 'true' },
12
+ * ];
13
+ * const metaObj = transformCockpitMeta(meta);
14
+ * if (metaObj.layout === 'shop') {
15
+ * // Handle shop layout
16
+ * }
17
+ * ```
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * // Safe to use with optional meta
22
+ * const metaObj = transformCockpitMeta(link.meta);
23
+ * const layout = metaObj.layout ?? 'default';
24
+ * ```
25
+ */
26
+ export function transformCockpitMeta(meta) {
27
+ if (!meta || !Array.isArray(meta))
28
+ return {};
29
+ return meta.reduce((acc, { key, value }) => {
30
+ acc[key] = value;
31
+ return acc;
32
+ }, {});
33
+ }
@@ -8,7 +8,7 @@ import { logger } from "../cockpit-logger.js";
8
8
  export async function generateCmsRouteReplacements(endpoint, tenant, cache) {
9
9
  const cacheKey = `ROUTE_REPLACEMENT_MAP:${tenant ?? "default"}`;
10
10
  if (cache) {
11
- const cached = cache.get(cacheKey);
11
+ const cached = (await cache.get(cacheKey));
12
12
  if (cached)
13
13
  return cached;
14
14
  }
@@ -31,7 +31,7 @@ export async function generateCmsRouteReplacements(endpoint, tenant, cache) {
31
31
  return { ...result, [key]: value };
32
32
  }, {});
33
33
  if (cache) {
34
- cache.set(cacheKey, replacement);
34
+ await cache.set(cacheKey, replacement);
35
35
  }
36
36
  return replacement;
37
37
  }
@@ -46,7 +46,7 @@ export async function generateCmsRouteReplacements(endpoint, tenant, cache) {
46
46
  export async function generateCollectionAndSingletonSlugRouteMap(endpoint, tenant, cache) {
47
47
  const cacheKey = `SLUG_ROUTE_MAP:${tenant ?? "default"}`;
48
48
  if (cache) {
49
- const cached = cache.get(cacheKey);
49
+ const cached = (await cache.get(cacheKey));
50
50
  if (cached)
51
51
  return cached;
52
52
  }
@@ -75,7 +75,7 @@ export async function generateCollectionAndSingletonSlugRouteMap(endpoint, tenan
75
75
  return { ...result, [entityName]: _r };
76
76
  }, {});
77
77
  if (cache) {
78
- cache.set(cacheKey, pageMap);
78
+ await cache.set(cacheKey, pageMap);
79
79
  }
80
80
  return pageMap;
81
81
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unchainedshop/cockpit-api",
3
- "version": "2.1.2",
3
+ "version": "2.2.0",
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",