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