SDK Best Practices
This guide covers best practices, optimization techniques, and recommended patterns for using the PromptCompose SDK effectively in production environments.
SDK Configuration Best Practices
Environment-Based Configuration
Set up different configurations for different environments:
// config/promptcompose.ts
interface PromptComposeConfig {
apiKey: string;
projectId: string;
debug: boolean;
apiUrl?: string;
}
function getConfig(): PromptComposeConfig {
const env = process.env.NODE_ENV || 'development';
const configs = {
production: {
apiKey: process.env.PROMPT_COMPOSE_API_KEY!,
projectId: process.env.PROMPT_COMPOSE_PROJECT_ID!,
debug: false,
apiUrl: 'https://api.promptcompose.io/api/v1'
},
staging: {
apiKey: process.env.STAGING_PROMPT_COMPOSE_API_KEY!,
projectId: process.env.STAGING_PROMPT_COMPOSE_PROJECT_ID!,
debug: true,
apiUrl: 'https://staging-api.promptcompose.io/api/v1'
},
development: {
apiKey: process.env.DEV_PROMPT_COMPOSE_API_KEY!,
projectId: process.env.DEV_PROMPT_COMPOSE_PROJECT_ID!,
debug: true,
apiUrl: 'http://localhost:8080/api/v1'
}
};
return configs[env] || configs.development;
}
export const config = getConfig();
Singleton Pattern for SDK Instance
class PromptComposeService {
private static instance: PromptCompose;
private static initialized = false;
static async getInstance(): Promise<PromptCompose> {
if (!this.instance) {
const config = getConfig();
this.instance = new PromptCompose(
config.apiKey,
config.projectId,
{ debug: config.debug }
);
// Set custom API URL if provided
if (config.apiUrl) {
process.env.PROMPT_COMPOSE_API_URL = config.apiUrl;
}
}
if (!this.initialized) {
await this.instance.init();
this.initialized = true;
}
return this.instance;
}
}
// Usage
const promptCompose = await PromptComposeService.getInstance();
Performance Optimization
Intelligent Caching
interface CacheEntry {
content: string;
timestamp: number;
variant?: string;
test?: string;
}
class PromptCache {
private cache = new Map<string, CacheEntry>();
private readonly cacheTimeout = 5 * 60 * 1000; // 5 minutes
getCacheKey(promptId: string, config?: any, variables?: any): string {
const configStr = config ? JSON.stringify(config) : '';
const variablesStr = variables ? JSON.stringify(variables) : '';
return `${promptId}-${configStr}-${variablesStr}`;
}
get(key: string): CacheEntry | null {
const entry = this.cache.get(key);
if (!entry) return null;
if (Date.now() - entry.timestamp > this.cacheTimeout) {
this.cache.delete(key);
return null;
}
return entry;
}
set(key: string, content: string, metadata?: any): void {
this.cache.set(key, {
content,
timestamp: Date.now(),
variant: metadata?.variant?.name,
test: metadata?.abTest?.name
});
}
clear(): void {
this.cache.clear();
}
// Clean expired entries
cleanup(): void {
const now = Date.now();
for (const [key, entry] of this.cache.entries()) {
if (now - entry.timestamp > this.cacheTimeout) {
this.cache.delete(key);
}
}
}
}
// Usage with SDK
class CachedPromptService {
private promptCompose: PromptCompose;
private cache = new PromptCache();
constructor(promptCompose: PromptCompose) {
this.promptCompose = promptCompose;
// Clean up cache every 10 minutes
setInterval(() => this.cache.cleanup(), 10 * 60 * 1000);
}
async resolvePrompt(promptId: string, config?: any, variables?: any) {
const cacheKey = this.cache.getCacheKey(promptId, config, variables);
const cached = this.cache.get(cacheKey);
if (cached) {
return { content: cached.content, fromCache: true };
}
const result = await this.promptCompose.resolvePrompt(promptId, config, variables);
this.cache.set(cacheKey, result.content, { variant: result.variant, abTest: result.abTest });
return result;
}
}
Batch Operations
class BatchPromptResolver {
private promptCompose: PromptCompose;
private batchSize = 10;
private batchTimeout = 100; // ms
private pendingRequests: Array<{
promptId: string;
config?: any;
variables?: any;
resolve: (result: any) => void;
reject: (error: any) => void;
}> = [];
private batchTimer?: NodeJS.Timeout;
constructor(promptCompose: PromptCompose) {
this.promptCompose = promptCompose;
}
async resolve(promptId: string, config?: any, variables?: any): Promise<any> {
return new Promise((resolve, reject) => {
this.pendingRequests.push({
promptId,
config,
variables,
resolve,
reject
});
this.scheduleBatch();
});
}
private scheduleBatch(): void {
if (this.batchTimer) return;
this.batchTimer = setTimeout(async () => {
await this.processBatch();
}, this.batchTimeout);
if (this.pendingRequests.length >= this.batchSize) {
clearTimeout(this.batchTimer);
this.batchTimer = undefined;
await this.processBatch();
}
}
private async processBatch(): Promise<void> {
const requests = this.pendingRequests.splice(0, this.batchSize);
this.batchTimer = undefined;
const promises = requests.map(async (request) => {
try {
const result = await this.promptCompose.resolvePrompt(
request.promptId,
request.config,
request.variables
);
request.resolve(result);
} catch (error) {
request.reject(error);
}
});
await Promise.all(promises);
// Schedule next batch if there are more requests
if (this.pendingRequests.length > 0) {
this.scheduleBatch();
}
}
}
Error Handling Best Practices
Graceful Degradation
class ResilientPromptService {
private promptCompose: PromptCompose;
private fallbackContent = new Map<string, string>();
private retryConfig = {
maxRetries: 3,
backoffMultiplier: 2,
initialDelay: 1000
};
constructor(promptCompose: PromptCompose) {
this.promptCompose = promptCompose;
}
setFallback(promptId: string, content: string): void {
this.fallbackContent.set(promptId, content);
}
async resolveWithFallback(
promptId: string,
config?: any,
variables?: any
): Promise<string> {
try {
const result = await this.resolveWithRetry(promptId, config, variables);
return result.content;
} catch (error) {
console.error('Prompt resolution failed:', error.message);
// Use fallback content if available
const fallback = this.fallbackContent.get(promptId);
if (fallback) {
console.log('Using fallback content for prompt:', promptId);
return this.interpolateFallback(fallback, variables || {});
}
// Last resort generic fallback
return 'We apologize, but the requested content is temporarily unavailable.';
}
}
private async resolveWithRetry(
promptId: string,
config?: any,
variables?: any,
attempt = 1
): Promise<any> {
try {
return await this.promptCompose.resolvePrompt(promptId, config, variables);
} catch (error) {
if (attempt >= this.retryConfig.maxRetries || error instanceof ValidationError) {
throw error;
}
const delay = this.retryConfig.initialDelay *
Math.pow(this.retryConfig.backoffMultiplier, attempt - 1);
console.warn(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
return this.resolveWithRetry(promptId, config, variables, attempt + 1);
}
}
private interpolateFallback(content: string, variables: Record<string, any>): string {
return content.replace(/\{\{\s*([\w.-]+)\s*\}\}/g, (_, key) => {
return variables[key] !== undefined ? String(variables[key]) : `{{${key}}}`;
});
}
}
Circuit Breaker Pattern
class PromptServiceCircuitBreaker {
private promptCompose: PromptCompose;
private failureThreshold = 5;
private recoveryTimeout = 60000; // 1 minute
private failureCount = 0;
private lastFailureTime = 0;
private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
constructor(promptCompose: PromptCompose) {
this.promptCompose = promptCompose;
}
async resolve(promptId: string, config?: any, variables?: any): Promise<any> {
if (this.state === 'OPEN') {
if (Date.now() - this.lastFailureTime > this.recoveryTimeout) {
this.state = 'HALF_OPEN';
this.failureCount = 0;
} else {
throw new Error('Service temporarily unavailable (circuit breaker open)');
}
}
try {
const result = await this.promptCompose.resolvePrompt(promptId, config, variables);
if (this.state === 'HALF_OPEN') {
this.state = 'CLOSED';
this.failureCount = 0;
}
return result;
} catch (error) {
this.failureCount++;
this.lastFailureTime = Date.now();
if (this.failureCount >= this.failureThreshold) {
this.state = 'OPEN';
console.warn('Circuit breaker opened due to repeated failures');
}
throw error;
}
}
}
A/B Testing Best Practices
Session Management
class SessionManager {
private static sessions = new Map<string, string>();
static getSessionId(userId: string, context?: any): string {
const key = `${userId}-${context?.deviceType || 'unknown'}`;
if (!this.sessions.has(key)) {
const sessionId = this.generateSessionId(userId, context);
this.sessions.set(key, sessionId);
}
return this.sessions.get(key)!;
}
private static generateSessionId(userId: string, context?: any): string {
const timestamp = Date.now();
const deviceType = context?.deviceType || 'web';
const hash = this.simpleHash(`${userId}-${timestamp}-${deviceType}`);
return `${userId}-${deviceType}-${hash}`;
}
private static simpleHash(str: string): string {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32-bit integer
}
return Math.abs(hash).toString(16).substr(0, 8);
}
}
Conversion Tracking
class ConversionTracker {
private promptCompose: PromptCompose;
private pendingConversions = new Map<string, any>();
private conversionWindow = 24 * 60 * 60 * 1000; // 24 hours
constructor(promptCompose: PromptCompose) {
this.promptCompose = promptCompose;
// Cleanup expired conversions every hour
setInterval(() => this.cleanup(), 60 * 60 * 1000);
}
trackPromptServed(
promptId: string,
userId: string,
result: any,
context?: any
): void {
if (result.variant && result.abTest) {
const key = `${userId}-${promptId}`;
this.pendingConversions.set(key, {
testId: result.abTest.publicId,
variantId: result.variant.publicId,
userId,
promptId,
servedAt: Date.now(),
context
});
}
}
async trackConversion(
promptId: string,
userId: string,
status: 'success' | 'fail' | 'timeout' | 'error',
metadata?: any
): Promise<void> {
const key = `${userId}-${promptId}`;
const conversion = this.pendingConversions.get(key);
if (!conversion) {
console.warn('No pending conversion found for:', key);
return;
}
try {
await this.promptCompose.reportABResult(conversion.testId, {
variantId: conversion.variantId,
status,
sessionId: SessionManager.getSessionId(userId, conversion.context)
});
console.log(`Conversion reported: ${status} for variant ${conversion.variantId}`);
// Track analytics
this.trackAnalytics(conversion, status, metadata);
// Remove from pending
this.pendingConversions.delete(key);
} catch (error) {
console.error('Failed to report conversion:', error.message);
}
}
private trackAnalytics(conversion: any, status: string, metadata?: any): void {
// Integrate with your analytics service
console.log('Analytics event:', {
event: 'prompt_conversion',
testId: conversion.testId,
variantId: conversion.variantId,
userId: conversion.userId,
promptId: conversion.promptId,
status,
timeToConversion: Date.now() - conversion.servedAt,
metadata
});
}
private cleanup(): void {
const now = Date.now();
for (const [key, conversion] of this.pendingConversions.entries()) {
if (now - conversion.servedAt > this.conversionWindow) {
this.pendingConversions.delete(key);
}
}
}
}
Security Best Practices
Secure Variable Handling
class SecureVariableManager {
private sensitiveFields = ['password', 'token', 'apiKey', 'secret', 'ssn', 'creditCard'];
sanitizeVariables(variables: Record<string, any>): Record<string, any> {
const sanitized = { ...variables };
for (const key in sanitized) {
// Remove sensitive fields
if (this.isSensitiveField(key)) {
delete sanitized[key];
console.warn(`Removed sensitive field: ${key}`);
}
// Sanitize string values
if (typeof sanitized[key] === 'string') {
sanitized[key] = this.sanitizeString(sanitized[key]);
}
}
return sanitized;
}
private isSensitiveField(fieldName: string): boolean {
const lowerField = fieldName.toLowerCase();
return this.sensitiveFields.some(sensitive =>
lowerField.includes(sensitive.toLowerCase())
);
}
private sanitizeString(value: string): string {
// Remove potential XSS payloads
return value
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
.replace(/javascript:/gi, '')
.replace(/on\w+\s*=/gi, '')
.trim();
}
}
Rate Limiting
class RateLimiter {
private requests = new Map<string, number[]>();
private maxRequests = 100; // per minute
private windowSize = 60 * 1000; // 1 minute
canMakeRequest(identifier: string): boolean {
const now = Date.now();
const userRequests = this.requests.get(identifier) || [];
// Remove old requests outside the window
const validRequests = userRequests.filter(
timestamp => now - timestamp < this.windowSize
);
if (validRequests.length >= this.maxRequests) {
return false;
}
validRequests.push(now);
this.requests.set(identifier, validRequests);
return true;
}
getRemainingRequests(identifier: string): number {
const userRequests = this.requests.get(identifier) || [];
const now = Date.now();
const validRequests = userRequests.filter(
timestamp => now - timestamp < this.windowSize
);
return Math.max(0, this.maxRequests - validRequests.length);
}
}
Monitoring and Logging
Structured Logging
class PromptLogger {
private logger: any; // Your logging service
constructor(logger: any) {
this.logger = logger;
}
logPromptResolution(
promptId: string,
result: any,
duration: number,
userId?: string,
context?: any
): void {
this.logger.info('prompt_resolved', {
promptId,
source: result.source,
variant: result.variant?.name,
test: result.abTest?.name,
duration,
userId,
context,
timestamp: new Date().toISOString()
});
}
logError(
operation: string,
error: Error,
promptId?: string,
userId?: string
): void {
this.logger.error('prompt_error', {
operation,
error: error.message,
stack: error.stack,
promptId,
userId,
timestamp: new Date().toISOString()
});
}
logConversion(
testId: string,
variantId: string,
status: string,
userId: string,
metadata?: any
): void {
this.logger.info('ab_test_conversion', {
testId,
variantId,
status,
userId,
metadata,
timestamp: new Date().toISOString()
});
}
}
Health Monitoring
class PromptServiceHealthMonitor {
private promptCompose: PromptCompose;
private healthCheckInterval = 30000; // 30 seconds
private healthStatus = {
isHealthy: true,
lastCheck: Date.now(),
consecutiveFailures: 0,
averageResponseTime: 0
};
constructor(promptCompose: PromptCompose) {
this.promptCompose = promptCompose;
this.startHealthChecks();
}
private startHealthChecks(): void {
setInterval(async () => {
await this.performHealthCheck();
}, this.healthCheckInterval);
}
private async performHealthCheck(): Promise<void> {
const startTime = Date.now();
try {
await this.promptCompose.init();
const responseTime = Date.now() - startTime;
this.updateHealthStatus(true, responseTime);
console.log('Health check passed:', {
responseTime,
status: 'healthy'
});
} catch (error) {
this.updateHealthStatus(false, Date.now() - startTime);
console.error('Health check failed:', {
error: error.message,
consecutiveFailures: this.healthStatus.consecutiveFailures
});
}
}
private updateHealthStatus(success: boolean, responseTime: number): void {
this.healthStatus.lastCheck = Date.now();
if (success) {
this.healthStatus.isHealthy = true;
this.healthStatus.consecutiveFailures = 0;
this.healthStatus.averageResponseTime =
(this.healthStatus.averageResponseTime * 0.9) + (responseTime * 0.1);
} else {
this.healthStatus.consecutiveFailures++;
if (this.healthStatus.consecutiveFailures >= 3) {
this.healthStatus.isHealthy = false;
}
}
}
getHealthStatus(): any {
return { ...this.healthStatus };
}
}
Production Deployment
Environment Configuration
// Production-ready configuration
const productionConfig = {
promptCompose: new PromptCompose(
process.env.PROMPT_COMPOSE_API_KEY!,
process.env.PROMPT_COMPOSE_PROJECT_ID!,
{
debug: false,
timeout: 30000,
retries: 3
}
),
cache: {
enabled: true,
timeout: 5 * 60 * 1000, // 5 minutes
maxSize: 1000
},
rateLimit: {
maxRequests: 1000,
windowSize: 60 * 1000 // 1 minute
},
fallback: {
enabled: true,
defaultMessage: 'Content is temporarily unavailable.'
}
};
Docker Configuration
# Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# Environment variables
ENV NODE_ENV=production
ENV PROMPT_COMPOSE_API_KEY=""
ENV PROMPT_COMPOSE_PROJECT_ID=""
EXPOSE 3000
CMD ["node", "dist/server.js"]
Kubernetes Deployment
# k8s-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: promptcompose-app
spec:
replicas: 3
selector:
matchLabels:
app: promptcompose-app
template:
metadata:
labels:
app: promptcompose-app
spec:
containers:
- name: app
image: your-app:latest
env:
- name: PROMPT_COMPOSE_API_KEY
valueFrom:
secretKeyRef:
name: promptcompose-secrets
key: api-key
- name: PROMPT_COMPOSE_PROJECT_ID
valueFrom:
secretKeyRef:
name: promptcompose-secrets
key: project-id
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
Summary
Key best practices for PromptCompose SDK:
Configuration
- ✅ Use environment-based configuration
- ✅ Implement singleton pattern for SDK instances
- ✅ Centralize API URL and credential management
Performance
- ✅ Implement intelligent caching strategies
- ✅ Use batch operations for multiple requests
- ✅ Optimize variable processing
Error Handling
- ✅ Implement graceful degradation with fallbacks
- ✅ Use circuit breaker pattern for resilience
- ✅ Implement retry logic with exponential backoff
A/B Testing
- ✅ Use consistent session IDs for user experience
- ✅ Implement comprehensive conversion tracking
- ✅ Track analytics for data-driven decisions
Security
- ✅ Sanitize variables to prevent XSS
- ✅ Implement rate limiting
- ✅ Remove sensitive data from variables
Monitoring
- ✅ Use structured logging for observability
- ✅ Implement health monitoring
- ✅ Track performance metrics
These practices ensure your PromptCompose integration is robust, secure, and performant in production environments.