Skip to content

Commit f04473a

Browse files
authored
Merge pull request #231 from contentstack/enhancement/DX-3430-Plugin-architecture
feat: Introduce ContentstackPlugin interface for extensible plugin architecture
2 parents 8211103 + 14170cd commit f04473a

File tree

9 files changed

+1986
-256
lines changed

9 files changed

+1986
-256
lines changed

.talismanrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
fileignoreconfig:
22
- filename: package-lock.json
3-
checksum: 17c3944c70209ee40840cee33e891b57cf1c672ecb03b410b71ad6fb0242a86e
3+
checksum: 32308dbe614c142c4804ff7c81baedddba058c5458e1d233fefb1d8070bf1905
44
- filename: test/unit/query-optimization-comprehensive.spec.ts
55
checksum: f5aaf6c784d7c101a05ca513c584bbd6e95f963d1e42779f2596050d9bcbac96
66
- filename: src/lib/entries.ts

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
### Version: 4.9.1
2-
#### Date: Aug-25-2025
1+
### Version: 4.10.0
2+
#### Date: Sep-22-2025
33
Fix: Enhance retry logic to use configured retryDelay
44
Enhancement: Caching logic to use combination of content type uid and entry uid
55

examples/custom-plugin.ts

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
/**
2+
* Example implementations of ContentstackPlugin interface
3+
* This file demonstrates various ways to create and use plugins
4+
*/
5+
import contentstack, { ContentstackPlugin } from '@contentstack/delivery-sdk';
6+
import { AxiosRequestConfig, AxiosResponse } from 'axios';
7+
8+
// Example 1: Simple logging plugin as a class
9+
class LoggingPlugin implements ContentstackPlugin {
10+
private requestCount = 0;
11+
12+
onRequest(config: AxiosRequestConfig): AxiosRequestConfig {
13+
this.requestCount++;
14+
console.log(`[Plugin] Request #${this.requestCount}: ${config.method?.toUpperCase()} ${config.url}`);
15+
16+
return {
17+
...config,
18+
headers: {
19+
...config.headers,
20+
'X-Request-ID': `req-${this.requestCount}-${Date.now()}`
21+
}
22+
};
23+
}
24+
25+
onResponse(request: AxiosRequestConfig, response: AxiosResponse, data: any): AxiosResponse {
26+
console.log(`[Plugin] Response: ${response.status} for ${request.url}`);
27+
28+
return {
29+
...response,
30+
data: {
31+
...data,
32+
_meta: {
33+
requestId: request.headers?.['X-Request-ID'],
34+
processedAt: new Date().toISOString()
35+
}
36+
}
37+
};
38+
}
39+
40+
getRequestCount(): number {
41+
return this.requestCount;
42+
}
43+
}
44+
45+
// Example 2: Authentication plugin as an object
46+
const authPlugin: ContentstackPlugin = {
47+
onRequest(config: AxiosRequestConfig): AxiosRequestConfig {
48+
return {
49+
...config,
50+
headers: {
51+
...config.headers,
52+
'X-Custom-Auth': 'Bearer my-custom-token',
53+
'X-Client-Version': '1.0.0'
54+
}
55+
};
56+
},
57+
58+
onResponse(request: AxiosRequestConfig, response: AxiosResponse, data: any): AxiosResponse {
59+
// Remove any sensitive information from the response
60+
if (data && typeof data === 'object') {
61+
const sanitized = { ...data };
62+
delete sanitized.internal_data;
63+
delete sanitized.debug_info;
64+
65+
return {
66+
...response,
67+
data: sanitized
68+
};
69+
}
70+
71+
return response;
72+
}
73+
};
74+
75+
// Example 3: Performance monitoring plugin
76+
class PerformancePlugin implements ContentstackPlugin {
77+
private requestTimes = new Map<string, number>();
78+
private metrics = {
79+
totalRequests: 0,
80+
totalTime: 0,
81+
slowRequests: 0,
82+
errors: 0
83+
};
84+
85+
onRequest(config: AxiosRequestConfig): AxiosRequestConfig {
86+
const requestId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
87+
this.requestTimes.set(requestId, performance.now());
88+
this.metrics.totalRequests++;
89+
90+
return {
91+
...config,
92+
metadata: {
93+
...config.metadata,
94+
requestId,
95+
startTime: performance.now()
96+
}
97+
};
98+
}
99+
100+
onResponse(request: AxiosRequestConfig, response: AxiosResponse, data: any): AxiosResponse {
101+
const requestId = request.metadata?.requestId;
102+
const startTime = this.requestTimes.get(requestId);
103+
104+
if (startTime) {
105+
const duration = performance.now() - startTime;
106+
this.metrics.totalTime += duration;
107+
108+
if (duration > 1000) { // Slow request threshold: 1 second
109+
this.metrics.slowRequests++;
110+
console.warn(`[Performance] Slow request detected: ${duration.toFixed(2)}ms for ${request.url}`);
111+
}
112+
113+
this.requestTimes.delete(requestId);
114+
}
115+
116+
if (response.status >= 400) {
117+
this.metrics.errors++;
118+
}
119+
120+
return {
121+
...response,
122+
data: {
123+
...data,
124+
_performance: {
125+
duration: startTime ? performance.now() - startTime : 0,
126+
timestamp: new Date().toISOString()
127+
}
128+
}
129+
};
130+
}
131+
132+
getMetrics() {
133+
return {
134+
...this.metrics,
135+
averageTime: this.metrics.totalRequests > 0
136+
? this.metrics.totalTime / this.metrics.totalRequests
137+
: 0
138+
};
139+
}
140+
141+
printReport() {
142+
const metrics = this.getMetrics();
143+
console.log(`
144+
Performance Report:
145+
- Total Requests: ${metrics.totalRequests}
146+
- Average Response Time: ${metrics.averageTime.toFixed(2)}ms
147+
- Slow Requests: ${metrics.slowRequests}
148+
- Errors: ${metrics.errors}
149+
`);
150+
}
151+
}
152+
153+
// Example 4: Caching plugin with TTL
154+
class CachePlugin implements ContentstackPlugin {
155+
private cache = new Map<string, { data: any; timestamp: number; ttl: number }>();
156+
private defaultTTL = 5 * 60 * 1000; // 5 minutes
157+
158+
onRequest(config: AxiosRequestConfig): AxiosRequestConfig {
159+
const cacheKey = this.generateCacheKey(config);
160+
const cached = this.cache.get(cacheKey);
161+
162+
if (cached && Date.now() - cached.timestamp < cached.ttl) {
163+
// In a real implementation, you might want to return cached data directly
164+
// This is just for demonstration
165+
console.log(`[Cache] Cache hit for ${cacheKey}`);
166+
return { ...config, fromCache: true };
167+
}
168+
169+
return { ...config, cacheKey };
170+
}
171+
172+
onResponse(request: AxiosRequestConfig, response: AxiosResponse, data: any): AxiosResponse {
173+
if (response.status === 200 && request.cacheKey && !request.fromCache) {
174+
this.cache.set(request.cacheKey, {
175+
data,
176+
timestamp: Date.now(),
177+
ttl: this.defaultTTL
178+
});
179+
console.log(`[Cache] Cached response for ${request.cacheKey}`);
180+
}
181+
182+
return response;
183+
}
184+
185+
private generateCacheKey(config: AxiosRequestConfig): string {
186+
return `${config.method || 'GET'}_${config.url}_${JSON.stringify(config.params || {})}`;
187+
}
188+
189+
clearCache(): void {
190+
this.cache.clear();
191+
}
192+
193+
getCacheStats() {
194+
return {
195+
size: this.cache.size,
196+
keys: Array.from(this.cache.keys())
197+
};
198+
}
199+
}
200+
201+
// Example 5: Error handling and retry plugin
202+
const errorHandlingPlugin: ContentstackPlugin = {
203+
onRequest(config: AxiosRequestConfig): AxiosRequestConfig {
204+
return {
205+
...config,
206+
retryAttempts: config.retryAttempts || 0,
207+
maxRetries: 3
208+
};
209+
},
210+
211+
onResponse(request: AxiosRequestConfig, response: AxiosResponse, data: any): AxiosResponse {
212+
if (response.status >= 400) {
213+
console.error(`[Error] HTTP ${response.status}: ${response.statusText} for ${request.url}`);
214+
215+
// Log additional error context
216+
if (response.status >= 500) {
217+
console.error('[Error] Server error detected. Consider implementing retry logic.');
218+
} else if (response.status === 429) {
219+
console.warn('[Error] Rate limit exceeded. Implementing backoff strategy recommended.');
220+
}
221+
}
222+
223+
return {
224+
...response,
225+
data: {
226+
...data,
227+
_error_handled: response.status >= 400,
228+
_error_code: response.status >= 400 ? response.status : null
229+
}
230+
};
231+
}
232+
};
233+
234+
// Usage example
235+
function createStackWithPlugins() {
236+
const loggingPlugin = new LoggingPlugin();
237+
const performancePlugin = new PerformancePlugin();
238+
const cachePlugin = new CachePlugin();
239+
240+
const stack = contentstack.stack({
241+
apiKey: 'your-api-key',
242+
deliveryToken: 'your-delivery-token',
243+
environment: 'your-environment',
244+
plugins: [
245+
loggingPlugin, // Logs all requests/responses
246+
authPlugin, // Adds authentication headers
247+
performancePlugin, // Monitors performance
248+
cachePlugin, // Caches responses
249+
errorHandlingPlugin // Handles errors gracefully
250+
]
251+
});
252+
253+
// You can access plugin methods if needed
254+
console.log(`Total requests made: ${loggingPlugin.getRequestCount()}`);
255+
256+
// Print performance report
257+
performancePlugin.printReport();
258+
259+
// Check cache stats
260+
console.log('Cache stats:', cachePlugin.getCacheStats());
261+
262+
return stack;
263+
}
264+
265+
// Export for use in other files
266+
export {
267+
LoggingPlugin,
268+
PerformancePlugin,
269+
CachePlugin,
270+
authPlugin,
271+
errorHandlingPlugin,
272+
createStackWithPlugins
273+
};

0 commit comments

Comments
 (0)