@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 +19 -1
- package/dist/client.js +35 -14
- package/dist/core/cache.d.ts +196 -8
- package/dist/core/cache.js +101 -14
- package/dist/core/config.d.ts +56 -7
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/methods/menus.d.ts +1 -1
- package/dist/methods/system.d.ts +19 -1
- package/dist/methods/system.js +2 -2
- package/dist/transformers/meta.d.ts +16 -0
- package/dist/transformers/meta.js +52 -0
- package/dist/utils/meta.d.ts +29 -0
- package/dist/utils/meta.js +33 -0
- package/dist/utils/route-map.js +4 -4
- package/package.json +1 -1
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
|
-
|
|
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
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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)
|
package/dist/core/cache.d.ts
CHANGED
|
@@ -1,19 +1,207 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cache management with
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
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;
|
package/dist/core/cache.js
CHANGED
|
@@ -1,32 +1,119 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cache management with
|
|
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
|
|
7
|
-
*
|
|
8
|
+
* Creates a default LRU-based async cache store
|
|
9
|
+
* Wraps lru-cache in async interface for consistency
|
|
8
10
|
*/
|
|
9
|
-
|
|
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
|
-
|
|
18
|
-
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
19
|
+
async get(key) {
|
|
20
|
+
return cache.get(key);
|
|
19
21
|
},
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
23
|
+
async set(key, value) {
|
|
24
|
+
cache.set(key, value);
|
|
22
25
|
},
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
+
}
|
package/dist/core/config.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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.
|
package/dist/core/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/core/index.js
CHANGED
|
@@ -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";
|
package/dist/methods/menus.d.ts
CHANGED
package/dist/methods/system.d.ts
CHANGED
|
@@ -10,6 +10,24 @@ export interface CockpitHealthCheck {
|
|
|
10
10
|
}
|
|
11
11
|
export interface SystemMethods {
|
|
12
12
|
healthCheck<T = unknown>(): Promise<T | null>;
|
|
13
|
-
|
|
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;
|
package/dist/methods/system.js
CHANGED
|
@@ -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
|
+
}
|
package/dist/utils/route-map.js
CHANGED
|
@@ -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.
|
|
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",
|