@ircg/ios
v1.20.9
Published
Image Optimization Service SDK for IRCG
Maintainers
Readme
@ircg/ios
Image Optimization Service (IOS) SDK for IRCG.
Installation
pnpm add @ircg/iosUsage
import { IOSClient } from '@ircg/ios';
// Initialize the client
const ios = new IOSClient({
apiKey: 'your-api-key',
baseUrl: 'https://ircg.dev' // optional, defaults to https://ircg.dev
});
// Upload an image (browser)
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];
const { optimizedImage, error } = await ios.upload({
image: file,
requireSignedURLs: false,
fields: ['imageId', 'name', 'requireSignedURLs', 'createdAt', 'hits', 'currentVariants']
});
if (error) {
console.error('Upload failed:', error.message);
return;
}
console.log(optimizedImage.imageId);
console.log(optimizedImage.currentVariants);
// Get image URL
const imageUrl = ios.getImageUrl(
optimizedImage.imageId,
'thumbnail'
);
// Upload with extended response (includes base64 placeholder)
const { optimizedImage: extendedImage, error: extError } = await ios.upload({
image: file,
requireSignedURLs: false,
fields: ['imageId', 'name', 'requireSignedURLs', 'createdAt', 'hits', 'currentVariants', 'placeholderBase64']
});
if (!extError) {
console.log(extendedImage.placeholderBase64);
}
// Get all images
const images = await ios.getAll({
page: 1,
amount: 50,
ascending: false
});
console.log(images.optimizedImages);
console.log(`Total: ${images.totalAmount}`);
// Get a specific image by ID
const { optimizedImage: image, error: getError } = await ios.getById({
imageId: 'image-id-123'
});
if (!getError) {
console.log(image);
}
// Delete an image
const { error: deleteError } = await ios.delete({ imageId: 'image-id-123' });
if (deleteError) {
console.error('Delete failed:', deleteError.message);
}
// Sign an image variant (for images with requireSignedURLs: true)
// Minimal response (default)
const { optimizedImage: minimalSig, error: sigError } = await ios.signImage({
imageId: 'image-id-123',
variant: 'thumbnail'
});
if (!sigError) {
console.log(minimalSig.sig);
console.log(minimalSig.exp);
}
// Request specific fields - TypeScript infers the correct type!
const { optimizedImage: completeSig, error: compError } = await ios.signImage({
imageId: 'image-id-123',
variant: 'thumbnail',
fields: ['imageId', 'sig', 'exp', 'signedUrl', 'hits']
});
// No type guard needed - TypeScript knows these properties exist
if (!compError) {
console.log(completeSig.imageId);
console.log(completeSig.signedUrl);
console.log(completeSig.hits);
}
// All methods return errors by default (no try-catch needed!)
const { optimizedImage, error } = await ios.signImage({
imageId: 'image-id-123',
variant: 'thumbnail',
fields: ['sig', 'exp', 'signedUrl']
});
if (error) {
// Handle error without try-catch
console.error(error.message);
return;
}
// TypeScript knows optimizedImage exists here
console.log(optimizedImage.signedUrl);
console.log(optimizedImage.hits);
// Delete example - ignore 404 errors
const deleteResult = await ios.delete(uploadImageValue.imageId);
if (deleteResult.error && deleteResult.error.status !== 404) {
console.error(`Can't delete image: ${deleteResult.error.message}`);
}API Reference
IOSClient
Constructor
new IOSClient(config: IRCGClientConfig)config.apiKey(required): Your IRCG API keyconfig.baseUrl(optional): Base URL for the API (default: 'https://ircg.dev')
Methods
upload(data) ⭐ Recommended
Upload and optimize an image. Returns errors instead of throwing exceptions.
data.image(required): File or Blob objectdata.requireSignedURLs(optional): Whether to require signed URLs (default: false)data.fields(required): Array of fields to include in the responsedata.lang(optional): Language for error messages ('es' | 'en')
Returns: Promise<{ optimizedImage?, error? }> where one is defined and the other is undefined
TypeScript automatically infers the correct return type based on the fields parameter!
// Request specific fields - TypeScript knows optimizedImage has placeholderBase64
const { optimizedImage, error } = await ios.upload({
image: file,
fields: ['imageId', 'placeholderBase64', 'currentVariants']
});
if (!error) {
console.log(optimizedImage.placeholderBase64); // ✅ Available
console.log(optimizedImage.imageId); // ✅ Available
console.log(optimizedImage.currentVariants); // ✅ Available
}
// Request only specific fields
const result = await ios.upload({
image: file,
fields: ['imageId', 'currentVariants']
});
if (!result.error) {
console.log(result.optimizedImage.currentVariants); // ✅ Available
// result.optimizedImage.placeholderBase64 // ❌ TypeScript error - not requested
}
// Request minimal fields
const minimal = await ios.upload({
image: file,
fields: ['imageId']
});
if (!minimal.error) {
console.log(minimal.optimizedImage.imageId); // ✅ Available
// minimal.optimizedImage.currentVariants // ❌ TypeScript error - not requested
}Alternative: Use uploadUnsafe() if you prefer exceptions.
getAll(options) ⭐ Recommended
Get all optimized images with pagination. Returns errors instead of throwing exceptions.
options.page(optional): Page number (default: 1)options.amount(optional): Items per page (default: 50)options.ascending(optional): Sort order (default: false)options.fields(required): Array of fields to include in the responseoptions.lang(optional): Language for error messages ('es' | 'en')
Returns: Promise<{ optimizedImages?, totalAmount?, error? }> where error is defined on failure
Alternative: Use getAllUnsafe() if you prefer exceptions.
getById(options) ⭐ Recommended
Get an optimized image by its ID. Returns errors instead of throwing exceptions.
options.imageId(required): The image IDoptions.fields(required): Array of fields to include in the responseoptions.lang(optional): Language for error messages ('es' | 'en')
Returns: Promise<{ optimizedImage?, error? }> where one is defined and the other is undefined
Alternative: Use getByIdUnsafe() if you prefer exceptions.
delete(imageId) ⭐ Recommended
Delete an optimized image. Returns errors instead of throwing exceptions.
imageId(required): The image ID to delete
Returns: Promise<{ success?, error? }> where one is defined and the other is undefined
Alternative: Use deleteUnsafe() if you prefer exceptions.
signImage(data) ⭐ Recommended
Sign an image variant to generate a signed URL (required for images with requireSignedURLs: true). Returns errors instead of throwing exceptions.
data.imageId(required): The image IDdata.variant(required): The variant name (e.g., 'thumbnail', 'medium', 'large')data.fields(required): Array of fields to include in the responsedata.lang(optional): Language for error messages ('es' | 'en')
Returns: Promise<{ optimizedImage?, error? }> where one is defined and the other is undefined
Alternative: Use signImageUnsafe() if you prefer exceptions.
TypeScript automatically infers the correct return type based on the fields parameter!
// Request minimal fields
const { optimizedImage, error } = await ios.signImage({
imageId: 'abc',
variant: 'thumb',
fields: ['sig', 'exp']
});
if (!error) {
optimizedImage.sig; // ✅ Available
optimizedImage.exp; // ✅ Available
// optimizedImage.signedUrl; // ❌ TypeScript error - not requested
}
// Request all fields - TypeScript knows it has all fields
const result = await ios.signImage({
imageId: 'abc',
variant: 'thumb',
fields: ['sig', 'exp', 'imageId', 'signedUrl', 'hits']
});
if (!result.error) {
result.optimizedImage.sig; // ✅ Available
result.optimizedImage.exp; // ✅ Available
result.optimizedImage.signedUrl; // ✅ Available
result.optimizedImage.imageId; // ✅ Available
result.optimizedImage.hits; // ✅ Available
}Available fields:
sig: The signature stringexp: Expiration timestamp (as string)imageId: The image IDsignedUrl: The complete signed URLhits: Number of times the image has been accessed
signImageUnsafe(data)
Unsafe version that throws exceptions instead of returning errors.
data.imageId(required): The image IDdata.variant(required): The variant name (e.g., 'thumbnail', 'medium', 'large')data.fields(required): Array of fields to include in the responsedata.lang(optional): Language for error messages ('es' | 'en')
Returns: Promise<{ optimizedImage: Pick<SignImage, T[number]> }>
Throws: IRCGAuthenticationError, IRCGValidationError, IRCGRateLimitError, IRCGServerError, or IRCGError
import { IRCGAuthenticationError, IRCGValidationError } from '@ircg/core';
try {
const result = await ios.signImageUnsafe({
imageId: 'abc',
variant: 'thumb'
}, 'complete');
console.log(result.optimizedImage.signedUrl);
console.log(result.optimizedImage.hits);
} catch (error) {
if (error instanceof IRCGAuthenticationError) {
console.error('Invalid API key');
} else if (error instanceof IRCGValidationError) {
console.error('Invalid data:', error.details);
}
}When to use:
- You prefer exception-based error handling
- You have existing try-catch infrastructure
- You want errors to bubble up automatically
getImageUrl(imageId, variant?, signed?)
Get the URL for a specific image variant.
imageId(required): The image IDvariant(optional): Variant name (default: 'public')signed(optional): Signature parameters{ exp: number, sig: string }
Returns: string
Return Types
- minimal: Returns only
imageIdandrequireSignedURLs - complete: Includes name, createdAt, and available variants
- extended: Includes everything from complete plus a base64 placeholder
Error Handling
All methods return errors by default - no try-catch needed! This makes error handling explicit and type-safe.
Default Behavior (Returns Errors)
import { IOSClient } from '@ircg/ios';
const ios = new IOSClient({ apiKey: 'your-api-key' });
// Example 1: Basic error handling
const { optimizedImage, error } = await ios.signImage({
imageId: 'abc123',
variant: 'thumbnail'
}, 'complete');
if (error) {
console.error('Failed to sign image:', error.message);
console.error('Status:', error.status);
console.error('Type:', error.type);
return; // or throw, or handle differently
}
// TypeScript knows optimizedImage is defined here
console.log('Signed URL:', optimizedImage.signedUrl);
console.log('Hits:', optimizedImage.hits);
// Example 2: Handling specific error types
const result = await ios.signImage({
imageId: 'xyz789',
variant: 'large'
}, 'complete');
if (result.error) {
switch (result.error.type) {
case 'authentication':
return fail(401, { message: 'Invalid API key' });
case 'validation':
return fail(400, { message: result.error.message, details: result.error.details });
case 'rate_limit':
return fail(429, { message: 'Too many requests' });
case 'server':
return fail(500, { message: 'Server error' });
default:
return fail(500, { message: result.error.message });
}
}
// Continue with success case
const signedUrl = result.optimizedImage.signedUrl;
// Example 3: In a SvelteKit form action
export const actions = {
sign_image: async ({ request }) => {
const formData = await request.formData();
const imageId = formData.get('imageId') as string;
const variant = formData.get('variant') as string;
const { optimizedImage, error } = await ios.signImage(
{ imageId, variant },
'complete'
);
if (error) {
return fail(error.status || 500, {
message: error.message,
details: error.details
});
}
return {
success: true,
signedUrl: optimizedImage.signedUrl
};
}
};// Example 4: Delete with error handling (ignore 404) const deleteResult = await ios.delete(imageId); if (deleteResult.error && deleteResult.error.status !== 404) { console.error('Failed to delete:', deleteResult.error.message); }
### Alternative: Using `*Unsafe()` Methods (Throws Exceptions)
If you prefer exceptions, use the `*Unsafe()` variants (e.g., `uploadUnsafe()`, `getAllUnsafe()`, `getByIdUnsafe()`, `deleteUnsafe()`, `signImageUnsafe()`). All unsafe methods throw typed errors.
### Available Error Types
- **`IRCGAuthenticationError`** (401/403): Invalid or missing API key
- **`IRCGValidationError`** (400): Invalid request data (includes `details` object)
- **`IRCGRateLimitError`** (429): Too many requests
- **`IRCGServerError`** (500/502/503): Server-side errors
- **`IRCGError`**: Base error class for other HTTP errors
### Example with Unsafe Methods
```typescript
import { IOSClient } from '@ircg/ios';
import {
IRCGError,
IRCGAuthenticationError,
IRCGValidationError,
IRCGRateLimitError,
IRCGServerError
} from '@ircg/core';
const ios = new IOSClient({ apiKey: 'your-api-key' });
try {
// Using the unsafe variant - throws on error
const result = await ios.signImageUnsafe({
imageId: 'abc123',
variant: 'thumbnail'
}, 'complete');
console.log(result.optimizedImage.signedUrl);
} catch (error) {
if (error instanceof IRCGAuthenticationError) {
console.error('Invalid API key:', error.message);
} else if (error instanceof IRCGValidationError) {
console.error('Validation error:', error.message);
console.error('Details:', error.details); // Field-specific errors
} else if (error instanceof IRCGRateLimitError) {
console.error('Rate limit exceeded:', error.message);
} else if (error instanceof IRCGServerError) {
console.error('Server error:', error.message);
} else if (error instanceof IRCGError) {
console.error('IRCG error:', error.message, error.status);
} else {
console.error('Unknown error:', error);
}
}Error Properties
All IRCG errors include:
message: Human-readable error messagestatus: HTTP status code (optional)details: Additional error information (optional, mainly in validation errors)
Practical Examples
Example 1: Sign Multiple Images
const imageIds = ['img1', 'img2', 'img3'];
const results = await Promise.all(
imageIds.map(imageId =>
ios.signImage({ imageId, variant: 'thumbnail' }, 'complete')
)
);
// Filter successful results
const successful = results.filter(r => !r.error);
const failed = results.filter(r => r.error);
console.log(`Signed ${successful.length} images`);
console.log(`Failed ${failed.length} images`);
// Use successful results
successful.forEach(({ optimizedImage }) => {
console.log(optimizedImage.signedUrl);
});Example 2: Retry Logic
async function signImageWithRetry(imageId: string, variant: string, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const { optimizedImage, error } = await ios.signImage(
{ imageId, variant },
'complete'
);
if (!error) {
return { success: true, data: optimizedImage };
}
// Don't retry on authentication or validation errors
if (error.type === 'authentication' || error.type === 'validation') {
return { success: false, error };
}
// Retry on server errors or rate limits
if (attempt < maxRetries) {
console.log(`Attempt ${attempt} failed, retrying...`);
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
}
return { success: false, error: { message: 'Max retries exceeded' } };
}Example 3: Caching Signatures
const signatureCache = new Map<string, { sig: string; exp: string; expiresAt: number }>();
async function getSignedUrl(imageId: string, variant: string): Promise<string | null> {
const cacheKey = `${imageId}:${variant}`;
const cached = signatureCache.get(cacheKey);
// Check if cached signature is still valid (with 5 min buffer)
if (cached && Date.now() < cached.expiresAt - 5 * 60 * 1000) {
return ios.getImageUrl(imageId, variant, {
exp: parseInt(cached.exp),
sig: cached.sig
});
}
// Get new signature
const { optimizedImage, error } = await ios.signImage(
{ imageId, variant },
'complete'
);
if (error) {
console.error('Failed to sign image:', error.message);
return null;
}
// Cache the signature
signatureCache.set(cacheKey, {
sig: optimizedImage.sig,
exp: optimizedImage.exp,
expiresAt: parseInt(optimizedImage.exp) * 1000
});
return optimizedImage.signedUrl;
}Example 4: Type-Safe Error Handling
function handleImageError(error: { message: string; status?: number; type?: string }) {
const errorMessages = {
authentication: 'Invalid API key. Please check your credentials.',
validation: 'Invalid image ID or variant.',
rate_limit: 'Too many requests. Please try again later.',
server: 'Server error. Please try again.',
unknown: 'An unexpected error occurred.'
};
const message = errorMessages[error.type as keyof typeof errorMessages] || errorMessages.unknown;
return {
userMessage: message,
technicalMessage: error.message,
statusCode: error.status || 500
};
}
// Usage
const { optimizedImage, error } = await ios.signImage(
{ imageId: 'abc', variant: 'thumb' },
'complete'
);
if (error) {
const errorInfo = handleImageError(error);
console.error(errorInfo.userMessage);
console.error('Technical:', errorInfo.technicalMessage);
}Example 5: Integration with React/Vue
// React Hook
function useSignedImage(imageId: string, variant: string) {
const [signedUrl, setSignedUrl] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
async function fetchSignedUrl() {
setLoading(true);
const { optimizedImage, error } = await ios.signImage(
{ imageId, variant },
'complete'
);
if (error) {
setError(error.message);
} else {
setSignedUrl(optimizedImage.signedUrl);
}
setLoading(false);
}
fetchSignedUrl();
}, [imageId, variant]);
return { signedUrl, loading, error };
}
// Usage in component
function ImageComponent({ imageId }: { imageId: string }) {
const { signedUrl, loading, error } = useSignedImage(imageId, 'thumbnail');
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <img src={signedUrl} alt="Optimized image" />;
}License
MIT
