S

TypeScript SDK

Complete guide to using the SimplyStack TypeScript SDK

Overview

The SimplyStack TypeScript SDK provides a simple, type-safe interface to interact with all SimplyStack API endpoints. It includes automatic error handling, TypeScript definitions, and a consistent response format across all methods.

✨ Features

  • • Full TypeScript support
  • • Automatic API key authentication
  • • Consistent error handling
  • • All CRUD operations

📦 Modules

  • • Blog posts with SEO
  • • System logs
  • • File storage with URL management
  • • Image transformations
  • • Project management

Installation

Install the SimplyStack SDK from npm:

npm install @simplystack-org/sdk
import { SimplyStackSDK } from "@simplystack-org/sdk";

// Initialize with your API key
const sdk = new SimplyStackSDK("your-api-key-here");

TypeScript Interfaces

The SDK includes comprehensive TypeScript definitions for all data structures:

BlogPost Interface

interface BlogPost {
  id: string;
  title: string;
  content: string;
  tags: string[];
  project_id: string;
  user_id: string;
  meta_title?: string;
  meta_description?: string;
  meta_keywords?: string[];
  canonical_url?: string;
  excerpt?: string;
  created_at: string;
  updated_at: string;
}

LogEntry Interface

interface LogEntry {
  id: string;
  level: string;              // "debug" | "info" | "warn" | "error"
  message: string;
  service?: string;
  metadata?: Record<string, any>;
  project_id: string;
  user_id: string | null;
  created_at: string;
}

StorageAsset Interface

interface StorageAsset {
  id: string;
  project_id: string;
  user_id: string;
  bucket: string;
  file_path: string;
  mime_type: string;
  size: number;
  label: string;
  metadata: Record<string, any> & {
    publicUrl?: string;
    originalName?: string;
    uploadedViaAPI?: boolean;
  };
  created_at: string;
}

Project Interface

interface Project {
  id: string;
  name: string;
  description?: string;
  user_id: string;
  created_at: string;
  updated_at: string;
}

ApiResponse Interface

interface ApiResponse<T> {
  data?: T;
  error?: string;
}

Enhanced Storage Types

interface StorageAssetOptions {
  url_type?: "public" | "signed" | "signed_transform";
  expires_in?: number;
  width?: number;
  height?: number;
  quality?: number;
}

interface FileUrlOptions {
  type?: "public" | "signed" | "signed_transform";
  expires_in?: number;
  width?: number;
  height?: number;
  quality?: number;
}

interface FileUrlResponse {
  url: string;
  url_type: string;
  expires_in?: number;
  transform_options?: {
    width?: number;
    height?: number;
    quality?: number;
  };
}

Blog Posts

Create Blog Post

const { data: post, error } = await sdk.createBlogPost({
  title: "My Blog Post",
  content: `
    <h1>Welcome to my blog post!</h1>
    <p>This is the content of my blog post.</p>
    <p>Created at: ${new Date().toISOString()}</p>
  `,
  excerpt: "A brief description of the blog post",
  tags: ["javascript", "typescript", "blog"],
  meta_title: "My Blog Post - SEO Title",
  meta_description: "SEO description for better search visibility",
  meta_keywords: ["blog", "javascript", "typescript"],
  canonical_url: "https://example.com/my-blog-post",
});

if (error) {
  console.error("Failed to create post:", error);
  return;
}

console.log("Post created:", post);

List Blog Posts

// Get all blog posts
const { data: posts, error } = await sdk.getBlogPosts();

// With filters
const { data: filteredPosts, error: filterError } = await sdk.getBlogPosts({
  status: "published",
  limit: 10,
});

if (error) {
  console.error("Failed to fetch posts:", error);
  return;
}

console.log(`Found ${posts?.length || 0} posts`);

Update Blog Post

const { data: updatedPost, error } = await sdk.updateBlogPost("post-id", {
  title: "Updated Blog Post Title",
  excerpt: "Updated excerpt for the blog post",
  meta_title: "Updated SEO Title",
  meta_description: "Updated SEO description",
  meta_keywords: ["updated", "keywords"],
  canonical_url: "https://example.com/updated-post",
});

// Note: Uses PUT method internally
if (error) {
  console.error("Failed to update post:", error);
  return;
}

console.log("Post updated:", updatedPost);

Delete Blog Post

const { data: result, error } = await sdk.deleteBlogPost("post-id");

if (error) {
  console.error("Failed to delete post:", error);
  return;
}

console.log("Post deleted successfully");

Logs

Create Log Entry

const { data: log, error } = await sdk.createLog({
  level: "info",                    // "debug" | "info" | "warn" | "error"
  message: "User logged in",
  service: "auth",
  metadata: {
    userId: "123",
    ip: "192.168.1.1",
    userAgent: "Mozilla/5.0...",
  },
});

if (error) {
  console.error("Failed to create log:", error);
  return;
}

console.log("Log created:", log);

List Logs with Filtering

// Get all logs
const { data: logs, error } = await sdk.getLogs();

// Filter by level and service
const { data: errorLogs, error: errorError } = await sdk.getLogs({
  level: "error",
  service: "api",
  limit: 100,
});

// Filter by date range (if your API supports it)
const { data: recentLogs, error: recentError } = await sdk.getLogs({
  level: "warn",
  limit: 50,
});

if (error) {
  console.error("Failed to fetch logs:", error);
  return;
}

console.log(`Found ${logs?.length || 0} log entries`);

Update Log Entry

const { data: updatedLog, error } = await sdk.updateLog("log-id", {
  level: "warning",
  message: "Updated message with resolution",
  service: "updated-service",
  metadata: {
    resolved: true,
    resolvedAt: new Date().toISOString(),
    resolvedBy: "admin_user_123",
  },
});

if (error) {
  console.error("Failed to update log:", error);
  return;
}

console.log("Log updated:", updatedLog);

File Storage

Upload File

// From file input element
const fileInput = document.getElementById("file") as HTMLInputElement;
const file = fileInput.files?.[0];

if (!file) {
  console.error("No file selected");
  return;
}

const { data: asset, error } = await sdk.uploadFile(file, "My Image");

if (error) {
  console.error("Upload failed:", error);
  return;
}

console.log("File uploaded:", asset);

// The uploaded file will have metadata including:
// - url: Direct access URL (if available)
// - url_type: Type of URL provided
// - metadata.publicUrl: Stored URL
// - metadata.originalName: Original filename
// - metadata.uploadedViaAPI: true (indicates API upload)

List Storage Assets

// Get all storage assets
const { data: assets, error } = await sdk.getStorageAssets();

// Filter by MIME type
const { data: images, error: imageError } = await sdk.getStorageAssets({
  mime_type: "image",
  limit: 50,
});

if (error) {
  console.error("Failed to fetch assets:", error);
  return;
}

console.log(`Found ${assets?.length || 0} storage assets`);

// Display image assets
images?.forEach(asset => {
  if (asset.metadata.publicUrl) {
    console.log(`Image: ${asset.label} - ${asset.metadata.publicUrl}`);
  }
});

Enhanced URL Management

// Get asset with public URL (default)
const { data: asset, error } = await sdk.getStorageAsset("asset-id");
console.log(asset?.url); // Public URL ready to use

// Get asset with signed URL (private access)
const { data: secureAsset, error: secureError } = await sdk.getStorageAsset("asset-id", {
  url_type: "signed",
  expires_in: 7200, // 2 hours
});

// Get asset with image transformation
const { data: thumbnail, error: thumbError } = await sdk.getStorageAsset("asset-id", {
  url_type: "signed_transform",
  width: 400,
  height: 300,
  quality: 85,
  expires_in: 3600,
});

console.log(thumbnail?.url); // Transformed image URL
console.log(thumbnail?.transform_options); // Applied transformations

Convenience Methods

// Quick thumbnail generation
const { data: thumb, error } = await sdk.getThumbnailUrl("asset-id", 200, 80);
console.log(thumb?.url); // 200x200 thumbnail at 80% quality

// Responsive images for different screen sizes
const { data: mobile } = await sdk.getResponsiveImageUrl("asset-id", 320, 75);
const { data: tablet } = await sdk.getResponsiveImageUrl("asset-id", 768, 85);
const { data: desktop } = await sdk.getResponsiveImageUrl("asset-id", 1200, 90);

// Secure signed URL with custom expiration
const { data: secure } = await sdk.getSecureUrl("asset-id", 900); // 15 minutes
console.log(secure?.url); // Secure URL
console.log(secure?.expiresIn); // 900 seconds

Advanced URL Features

// Get just the URL for better performance
const { data: urlData, error } = await sdk.getFileUrl("asset-id", {
  type: "signed_transform",
  width: 800,
  quality: 90,
});

console.log(urlData?.url); // Ready-to-use URL
console.log(urlData?.url_type); // "signed_transform"
console.log(urlData?.transform_options); // { width: 800, quality: 90 }

// Smart URL handling with automatic fallback
const { data: urlInfo, error: urlError } = await sdk.getAssetUrl(asset, {
  url_type: "signed",
  expires_in: 3600,
  forceRefresh: false, // Use cached URL if available
});

console.log(urlInfo?.url); // The URL to use
console.log(urlInfo?.isPublic); // true/false
console.log(urlInfo?.source); // "stored_url" | "fresh_api_url"

Update Asset Metadata

const { data: updatedAsset, error } = await sdk.updateStorageAsset("asset-id", {
  label: "Updated Image Label",
  metadata: {
    tags: ["important", "featured"],
    category: "hero-images",
    alt_text: "Description for accessibility",
    caption: "Image caption for display",
  },
});

if (error) {
  console.warn("Failed to update asset metadata:", error);
} else {
  console.log("Asset metadata updated:", updatedAsset);
}

Advanced Image Management

class ImageManager {
  private sdk: SimplyStackSDK;

  constructor(apiKey: string) {
    this.sdk = new SimplyStackSDK(apiKey);
  }

  async getOptimizedImageUrls(assetId: string) {
    // Get multiple sizes for responsive design
    const [thumbnail, small, medium, large] = await Promise.all([
      this.sdk.getThumbnailUrl(assetId, 150, 80),
      this.sdk.getResponsiveImageUrl(assetId, 320, 75),
      this.sdk.getResponsiveImageUrl(assetId, 768, 85),
      this.sdk.getResponsiveImageUrl(assetId, 1200, 90),
    ]);

    return {
      thumbnail: thumbnail.data?.url,
      small: small.data?.url,
      medium: medium.data?.url,
      large: large.data?.url,
    };
  }

  async createSecureGallery(assetIds: string[], userRole: string) {
    const expirationMap = {
      admin: 86400, // 24 hours
      user: 3600, // 1 hour
      guest: 900, // 15 minutes
    };

    const expiresIn = expirationMap[userRole] || 900;

    const gallery = await Promise.all(
      assetIds.map(async (id) => {
        const [secure, thumbnail] = await Promise.all([
          this.sdk.getSecureUrl(id, expiresIn),
          this.sdk.getThumbnailUrl(id, 200, 80),
        ]);

        return {
          id,
          secureUrl: secure.data?.url,
          thumbnailUrl: thumbnail.data?.url,
          expiresAt: new Date(Date.now() + expiresIn * 1000),
        };
      })
    );

    return gallery.filter((item) => item.secureUrl && item.thumbnailUrl);
  }

  async createImageGallery(assetIds: string[]) {
    const gallery = await Promise.all(
      assetIds.map(async (id) => {
        const [original, thumbnail, medium] = await Promise.all([
          this.sdk.getFileUrl(id, { type: "public" }),
          this.sdk.getThumbnailUrl(id, 150, 80),
          this.sdk.getResponsiveImageUrl(id, 600, 85),
        ]);

        return {
          id,
          original: original.data?.url,
          thumbnail: thumbnail.data?.url,
          medium: medium.data?.url,
        };
      })
    );

    return gallery;
  }
}

// Usage
const imageManager = new ImageManager(
  process.env.SIMPLYSTACK_API_KEY!
);

// Get optimized URLs for responsive design
const urls = await imageManager.getOptimizedImageUrls("asset-id");
console.log("Responsive image URLs:", urls);

// Create secure gallery for different user roles
const gallery = await imageManager.createSecureGallery(
  ["asset-1", "asset-2", "asset-3"],
  "user"
);
console.log("Secure gallery:", gallery);

Error Handling

All SDK methods return a consistent response format with either data or an error. Always check for errors before using the data.

Basic Error Handling

const { data: posts, error } = await sdk.getBlogPosts();

if (error) {
  console.error("Failed to fetch posts:", error);
  
  // You might want to show user-friendly messages
  if (error.includes("Authentication")) {
    alert("Please check your API key");
  } else if (error.includes("Network")) {
    alert("Network error. Please try again.");
  } else {
    alert("An error occurred while fetching posts");
  }
  
  return;
}

// Use data safely
console.log(`Found ${posts?.length || 0} posts`);

Advanced Error Handling

async function safeApiCall<T>(
  apiCall: () => Promise<{ data?: T; error?: string }>
): Promise<T | null> {
  try {
    const { data, error } = await apiCall();

    if (error) {
      // Log error details
      console.error("API Error:", error);
      
      // Handle specific error types
      if (error.includes("Authentication")) {
        throw new Error("Invalid API key. Please check your credentials.");
      }
      
      if (error.includes("Rate limit")) {
        throw new Error("Rate limit exceeded. Please wait before trying again.");
      }
      
      if (error.includes("Not found")) {
        throw new Error("The requested resource was not found.");
      }
      
      // Generic error
      throw new Error(`API Error: ${error}`);
    }

    return data || null;

  } catch (networkError) {
    // Handle network or other errors
    console.error("Network/Unknown error:", networkError);
    throw new Error("Network error. Please check your connection and try again.");
  }
}

// Usage
try {
  const posts = await safeApiCall(() => sdk.getBlogPosts());
  console.log("Posts loaded successfully:", posts);
} catch (error) {
  console.error("Failed to load posts:", error.message);
  // Show user-friendly error message
}

Advanced Features

Enhanced Storage System

The storage system now supports advanced URL management and image transformations:

  • Multiple URL Types: Public, signed, and signed with transformations
  • Image Transformations: Width, height, and quality adjustments
  • Smart URL Management: Automatic expiration handling and refresh
  • Performance Optimization: URL-only requests for better performance
  • Convenience Methods: Quick access to common use cases

SEO Support for Blog Posts

Blog posts support comprehensive SEO metadata:

  • meta_title: SEO title (recommended: max 60 characters)
  • meta_description: SEO description (recommended: max 160 characters)
  • meta_keywords: Array of SEO keywords
  • canonical_url: Canonical URL for the post
  • excerpt: Short excerpt for previews

Storage Asset Metadata

Storage assets include rich metadata for enhanced functionality:

  • url: Direct access URL from the API
  • url_type: Type of URL (public, signed, signed_transform)
  • expires_in: Expiration time for signed URLs
  • transform_options: Applied image transformations
  • metadata.publicUrl: Stored URL in metadata
  • metadata.originalName: Original filename when uploaded
  • metadata.uploadedViaAPI: Boolean flag indicating API upload

Advanced Usage Examples

Blog Management System

class BlogManager {
  private sdk: SimplyStackSDK;

  constructor(apiKey: string) {
    this.sdk = new SimplyStackSDK(apiKey);
  }

  async publishPost(
    title: string,
    content: string,
    seoData?: {
      meta_title?: string;
      meta_description?: string;
      meta_keywords?: string[];
      canonical_url?: string;
    }
  ) {
    const { data: post, error } = await this.sdk.createBlogPost({
      title,
      content,
      status: "published",
      ...seoData,
    });

    if (error) {
      throw new Error(`Failed to publish post: ${error}`);
    }

    return post;
  }

  async getDrafts() {
    const { data: posts, error } = await this.sdk.getBlogPosts({
      status: "draft",
    });

    if (error) {
      throw new Error(`Failed to fetch drafts: ${error}`);
    }

    return posts || [];
  }
}

// Usage
const blogManager = new BlogManager(
  process.env.SIMPLYSTACK_API_KEY!
);

Error Logging Service

class Logger {
  private sdk: SimplyStackSDK;

  constructor(apiKey: string) {
    this.sdk = new SimplyStackSDK(apiKey);
  }

  async logError(message: string, service: string, metadata?: any) {
    await this.sdk.createLog({
      level: "error",
      message,
      service,
      metadata: {
        timestamp: new Date().toISOString(),
        stack: new Error().stack,
        ...metadata,
      },
    });
  }

  async getRecentErrors(limit = 100) {
    const { data: logs } = await this.sdk.getLogs({
      level: "error",
      limit,
    });

    return logs || [];
  }
}

// Usage
const logger = new Logger(
  process.env.SIMPLYSTACK_API_KEY!
);

Best Practices

✅ Do

  • • Always check for errors before using data
  • • Use environment variables for API keys
  • • Implement proper error boundaries
  • • Add meaningful metadata to logs
  • • Use TypeScript for type safety
  • • Handle rate limiting gracefully

❌ Don't

  • • Expose API keys in client-side code
  • • Ignore error responses
  • • Make assumptions about data structure
  • • Store sensitive data in logs
  • • Skip input validation
  • • Make excessive API calls