TypeScriptADK-TS
Artifacts

Best Practices

Performance, security, versioning, and production considerations for artifact management

This guide covers essential best practices for using artifacts in production environments, focusing on performance optimization, security considerations, and operational excellence.

Performance Optimization

Caching Strategies

Keep a small in-process cache for hot artifacts.

const cache = new Map<string, { part: Part; ts: number }>();
const TTL_MS = 5 * 60 * 1000;

async function loadWithCache(svc: BaseArtifactService, key: {
  appName: string; userId: string; sessionId: string; filename: string; version?: number
}): Promise<Part | null> {
  const k = `${key.appName}:${key.userId}:${key.sessionId}:${key.filename}:${key.version ?? 'latest'}`;
  const hit = cache.get(k);
  if (hit && Date.now() - hit.ts < TTL_MS) return hit.part;
  const part = await svc.loadArtifact(key);
  if (part) cache.set(k, { part, ts: Date.now() });
  return part;
}

Batch Operations

await Promise.all([
  svc.saveArtifact({ /* args1 */ }),
  svc.saveArtifact({ /* args2 */ }),
  svc.saveArtifact({ /* args3 */ })
]);

Size Optimization

// Compress text/JSON before saving
const json = JSON.stringify(someData);
const compact = json.replace(/\s+/g, ' ');

// Validate size (default 10MB)
const ok = Buffer.from(part.inlineData.data, 'base64').length <= 10 * 1024 * 1024;

Security and Access Control

Data Validation

const ALLOWED = new Set(['text/plain','text/csv','application/json','image/png','image/jpeg','application/pdf']);
const MAX = 50 * 1024 * 1024;

function validatePart(filename: string, part: Part) {
  const errors: string[] = [], warnings: string[] = [];
  const mime = part.inlineData.mimeType;
  const size = Buffer.from(part.inlineData.data, 'base64').length;
  if (!ALLOWED.has(mime)) errors.push(`Unsupported MIME: ${mime}`);
  if (size > MAX) errors.push(`File too large: ${size}`);
  if (!/^[a-zA-Z0-9._-]+$/.test(filename.replace('user:', ''))) errors.push('Invalid filename');
  if (mime === 'application/json') {
    try { JSON.parse(Buffer.from(part.inlineData.data, 'base64').toString()); } catch { errors.push('Invalid JSON'); }
  }
  if (size === 0) warnings.push('Empty file');
  return { isValid: errors.length === 0, errors, warnings, size };
}

Access Control Patterns

Implement proper access controls:

class SecureArtifactService {
  constructor(
    private baseService: BaseArtifactService,
    private authService: AuthService
  ) {}

  async saveArtifact(
    args: {
      appName: string;
      userId: string;
      sessionId: string;
      filename: string;
      artifact: Part;
    },
    requestingUserId: string,
    permissions: string[]
  ): Promise<number> {
    // Verify user can save artifacts
    if (!permissions.includes('artifact:write')) {
      throw new Error('Insufficient permissions to save artifacts');
    }

    // Verify user can save to this location
    if (args.userId !== requestingUserId && !permissions.includes('artifact:admin')) {
      throw new Error('Cannot save artifacts for other users');
    }

    // Validate user namespace access
    if (args.filename.startsWith('user:') && args.userId !== requestingUserId) {
      throw new Error('Cannot save to user namespace of other users');
    }

    // Apply rate limiting
    await this.checkRateLimit(requestingUserId, 'save');

    return this.baseService.saveArtifact(args);
  }

  async loadArtifact(
    args: {
      appName: string;
      userId: string;
      sessionId: string;
      filename: string;
      version?: number;
    },
    requestingUserId: string,
    permissions: string[]
  ): Promise<Part | null> {
    // Verify user can read artifacts
    if (!permissions.includes('artifact:read')) {
      throw new Error('Insufficient permissions to read artifacts');
    }

    // Check if user can access this data
    const canAccess =
      args.userId === requestingUserId ||
      permissions.includes('artifact:admin') ||
      this.isSharedArtifact(args.filename);

    if (!canAccess) {
      throw new Error('Access denied to artifact');
    }

    return this.baseService.loadArtifact(args);
  }

  private async checkRateLimit(userId: string, operation: string): Promise<void> {
    // Implement rate limiting logic
    const key = `ratelimit:${userId}:${operation}`;
    const current = await this.getRateLimitCount(key);

    if (current > this.getRateLimit(operation)) {
      throw new Error('Rate limit exceeded');
    }

    await this.incrementRateLimit(key);
  }

  private isSharedArtifact(filename: string): boolean {
    // Define shared artifact patterns
    return filename.startsWith('shared:') || filename.startsWith('public:');
  }

  private getRateLimit(operation: string): number {
    const limits = {
      save: 100,  // 100 saves per hour
      load: 1000  // 1000 loads per hour
    };
    return limits[operation] || 10;
  }

  private async getRateLimitCount(key: string): Promise<number> {
    // Implementation depends on your rate limiting store (Redis, etc.)
    return 0;
  }

  private async incrementRateLimit(key: string): Promise<void> {
    // Implementation depends on your rate limiting store
  }
}

Version Management

Cleanup Strategies

Implement automatic cleanup for old versions:

class ArtifactVersionManager {
  constructor(private artifactService: BaseArtifactService) {}

  async cleanupOldVersions(
    appName: string,
    userId: string,
    sessionId: string,
    retentionPolicy: RetentionPolicy
  ): Promise<CleanupResult> {
    const artifacts = await this.artifactService.listArtifactKeys({
      appName,
      userId,
      sessionId
    });

    let totalDeleted = 0;
    const errors: string[] = [];

    for (const filename of artifacts) {
      try {
        const versions = await this.artifactService.listVersions({
          appName,
          userId,
          sessionId,
          filename
        });

        const versionsToDelete = this.selectVersionsForDeletion(
          versions,
          retentionPolicy
        );

        for (const version of versionsToDelete) {
          // Note: Current interface doesn't support version-specific deletion
          // This would need to be added to BaseArtifactService
          console.log(`Would delete ${filename} version ${version}`);
          totalDeleted++;
        }

      } catch (error) {
        errors.push(`Failed to cleanup ${filename}: ${error.message}`);
      }
    }

    return {
      artifactsProcessed: artifacts.length,
      versionsDeleted: totalDeleted,
      errors
    };
  }

  private selectVersionsForDeletion(
    versions: number[],
    policy: RetentionPolicy
  ): number[] {
    const sortedVersions = [...versions].sort((a, b) => b - a); // newest first

    switch (policy.strategy) {
      case 'keep_latest':
        return sortedVersions.slice(policy.count);

      case 'keep_recent':
        // Keep versions from the last N days (simplified)
        const cutoffVersion = sortedVersions[policy.count - 1] || 0;
        return versions.filter(v => v < cutoffVersion);

      case 'custom':
        return policy.customSelector(versions);

      default:
        return [];
    }
  }
}

interface RetentionPolicy {
  strategy: 'keep_latest' | 'keep_recent' | 'custom';
  count: number;
  customSelector?: (versions: number[]) => number[];
}

interface CleanupResult {
  artifactsProcessed: number;
  versionsDeleted: number;
  errors: string[];
}

Backup Strategies

Implement backup and recovery for critical artifacts:

class ArtifactBackupService {
  constructor(
    private primaryService: BaseArtifactService,
    private backupService: BaseArtifactService
  ) {}

  async saveWithBackup(args: {
    appName: string;
    userId: string;
    sessionId: string;
    filename: string;
    artifact: Part;
  }): Promise<number> {
    // Save to primary storage
    const version = await this.primaryService.saveArtifact(args);

    // Async backup (don't wait for completion)
    this.backupArtifact(args, version).catch(error => {
      console.error('Backup failed:', error);
    });

    return version;
  }

  private async backupArtifact(
    args: {
      appName: string;
      userId: string;
      sessionId: string;
      filename: string;
      artifact: Part;
    },
    version: number
  ): Promise<void> {
    try {
      // Add backup metadata
      const backupArtifact = {
        ...args.artifact,
        inlineData: {
          ...args.artifact.inlineData,
          // Add backup metadata to MIME type
          mimeType: `${args.artifact.inlineData.mimeType}; backup=true; original-version=${version}`
        }
      };

      await this.backupService.saveArtifact({
        ...args,
        artifact: backupArtifact
      });

    } catch (error) {
      console.error('Failed to create backup:', error);
      throw error;
    }
  }

  async restoreFromBackup(args: {
    appName: string;
    userId: string;
    sessionId: string;
    filename: string;
    version?: number;
  }): Promise<Part | null> {
    console.log('Attempting restore from backup...');

    try {
      const backup = await this.backupService.loadArtifact(args);

      if (backup) {
        // Remove backup metadata from MIME type
        const cleanArtifact = {
          ...backup,
          inlineData: {
            ...backup.inlineData,
            mimeType: backup.inlineData.mimeType.split(';')[0]
          }
        };

        // Restore to primary storage
        await this.primaryService.saveArtifact({
          ...args,
          artifact: cleanArtifact
        });

        return cleanArtifact;
      }

      return null;

    } catch (error) {
      console.error('Backup restore failed:', error);
      throw error;
    }
  }
}

Error Handling and Resilience

Retry Logic (simple backoff)

async function withRetry<T>(op: () => Promise<T>, attempts = 3, base = 1000) {
  let last: unknown;
  for (let i = 0; i <= attempts; i++) {
    try { return await op(); } catch (e) {
      last = e; if (i === attempts) break;
      await new Promise(r => setTimeout(r, base * Math.pow(2, i)));
    }
  }
  throw last;
}

Monitoring and Observability

Metrics Collection

Track artifact usage and performance:

class ArtifactMetricsCollector {
  private metrics = {
    saves: 0,
    loads: 0,
    errors: 0,
    totalSize: 0,
    averageSize: 0,
    operationTimes: [] as number[]
  };

  constructor(private artifactService: BaseArtifactService) {}

  async saveArtifactWithMetrics(args: {
    appName: string;
    userId: string;
    sessionId: string;
    filename: string;
    artifact: Part;
  }): Promise<number> {
    const startTime = Date.now();

    try {
      const version = await this.artifactService.saveArtifact(args);

      // Record success metrics
      this.metrics.saves++;
      const size = Buffer.from(args.artifact.inlineData.data, 'base64').length;
      this.updateSizeMetrics(size);
      this.recordOperationTime(Date.now() - startTime);

      return version;

    } catch (error) {
      this.metrics.errors++;
      throw error;
    }
  }

  async loadArtifactWithMetrics(args: {
    appName: string;
    userId: string;
    sessionId: string;
    filename: string;
    version?: number;
  }): Promise<Part | null> {
    const startTime = Date.now();

    try {
      const artifact = await this.artifactService.loadArtifact(args);

      // Record success metrics
      this.metrics.loads++;

      if (artifact) {
        const size = Buffer.from(artifact.inlineData.data, 'base64').length;
        this.updateSizeMetrics(size);
      }

      this.recordOperationTime(Date.now() - startTime);

      return artifact;

    } catch (error) {
      this.metrics.errors++;
      throw error;
    }
  }

  private updateSizeMetrics(size: number): void {
    this.metrics.totalSize += size;
    const totalOps = this.metrics.saves + this.metrics.loads;
    this.metrics.averageSize = this.metrics.totalSize / totalOps;
  }

  private recordOperationTime(time: number): void {
    this.metrics.operationTimes.push(time);

    // Keep only last 1000 operations for memory efficiency
    if (this.metrics.operationTimes.length > 1000) {
      this.metrics.operationTimes = this.metrics.operationTimes.slice(-1000);
    }
  }

  getMetrics() {
    const times = this.metrics.operationTimes;

    return {
      ...this.metrics,
      averageOperationTime: times.length > 0
        ? times.reduce((a, b) => a + b, 0) / times.length
        : 0,
      p95OperationTime: times.length > 0
        ? times.sort((a, b) => a - b)[Math.floor(times.length * 0.95)]
        : 0,
      errorRate: (this.metrics.errors / (this.metrics.saves + this.metrics.loads + this.metrics.errors)) * 100
    };
  }

  resetMetrics(): void {
    this.metrics = {
      saves: 0,
      loads: 0,
      errors: 0,
      totalSize: 0,
      averageSize: 0,
      operationTimes: []
    };
  }
}

Production Deployment Checklist

Infrastructure Setup

  • Storage Backend: Choose appropriate service (GCS, S3, database)
  • Backup Strategy: Implement automated backups
  • Monitoring: Set up metrics and alerting
  • Security: Configure proper access controls and encryption
  • Rate Limiting: Implement rate limiting to prevent abuse

Configuration

  • Size Limits: Set appropriate file size limits
  • MIME Types: Whitelist allowed file types
  • Retention Policies: Configure version cleanup
  • Caching: Implement caching layer for performance
  • Error Handling: Add comprehensive error handling

Operational Procedures

  • Monitoring Dashboard: Set up artifact usage monitoring
  • Backup Verification: Test backup and restore procedures
  • Incident Response: Document artifact-related incident procedures
  • Capacity Planning: Monitor storage growth and plan scaling
  • Security Audits: Regular security reviews of artifact access

Always test artifact operations thoroughly in staging environments before deploying to production.

How is this guide?