@stabledata/silo
v0.2.5
Published
JavaScript/TypeScript client for Silo asset management API with Express and Hono proxy helpers
Maintainers
Readme
@stabledata/silo
JavaScript/TypeScript client for the Silo asset management API. Handle file uploads, user management, and space organization with ease.
Features
- 🗄️ Asset Management - Upload and organize files with automatic grouping
- 📸 Live Photos - Full support for iOS Live Photos (image + video pairs)
- 👥 User Management - Create and manage users
- 🏢 Space Management - Create and manage shared spaces
- 📊 Upload Progress - Real-time upload progress callbacks
- 🛡️ Type Safe - Full TypeScript support with comprehensive error types
- 🔄 Batch Uploads - Upload multiple files efficiently
- 🔌 Proxy Helpers - Express and Hono middleware for easy backend integration
- 🔧 CLI Tool - Command-line interface for admin operations
Installation
npm install @stabledata/siloCLI Tool
The package includes a CLI tool for managing users, API keys, and spaces across environments.
Installation
# Install globally
npm install -g @stabledata/silo
# Or run directly with npx
npx @stabledata/silo --helpUsage
# Create an API key for your application
silo key create-app --name "Production API" --env prod
# Create a user with initial space
silo user create --email [email protected] --space my-space --env local
# List all users
silo user list --env staging --limit 100
# List keys for a user
silo key list --user [email protected] --env local
# Get user details
silo user get --email [email protected] --env prod
# Revoke a key
silo key revoke --key-id abc-123-def --env prodEnvironments
- local (default): Uses
http://localhost:8080and default admin key - staging: Uses staging API URL and fetches admin key from GCP Secret Manager
- production: Uses production API URL and fetches admin key from GCP Secret Manager
Environment Variables:
SILO_API_URLorSILO_HOST- Override the API URLSILO_ADMIN_KEYorADMIN_API_KEY- Your admin API key (preferred)CREATE_ADMIN_USER_KEY- Legacy admin key (still supported)
For staging/production, you need:
- Google Cloud SDK installed (
gcloud) - Authenticated:
gcloud auth login - Permission to access Secret Manager secrets
CLI Commands
Key Management:
silo key create-app- Create API key for your applicationsilo key list --user <email>- List key metadata (ID, name, usage stats)silo key rotate --name <name>- Create new admin key and show instructions to revoke old onesilo key revoke --key-id <id>- Revoke a key
User Management:
silo user create --email <email> --space <id>- Create user with spacesilo user list- List all userssilo user get --email <email>- Get user details
Note:
create-adminis deprecated, usecreate-appinstead
Key Security
Important: API keys are only shown once when created, then permanently hashed. You cannot retrieve the key value later.
- ✅ When creating: The full key is displayed - save it immediately
- ✅ When listing: Only metadata is shown (ID, name, created date, usage count)
- ❌ Cannot retrieve: Once created, the actual key value is never shown again
If you lose a key, you must create a new one and revoke the old one. Use silo key rotate to safely rotate keys.
Quick Start
For Backend/Proxy Apps (Recommended)
import express from 'express';
import { createSiloMiddleware } from '@stabledata/silo/proxy/express';
const app = express();
// After your auth middleware
app.use('/api/silo', createSiloMiddleware({
baseUrl: process.env.SILO_API_URL!,
adminToken: process.env.SILO_ADMIN_TOKEN
}));
// Upload endpoint
app.post('/api/silo/upload', async (req, res) => {
const results = await req.silo.uploadFiles('my-space', files);
res.json(results);
});See Express Example or Hono Example for complete implementations.
Direct Client Usage
import { SiloClient } from '@stabledata/silo';
const client = new SiloClient({
baseUrl: 'https://your-silo-api.com',
userId: 'your-user-id',
apiKey: 'user-api-key'
});
// Upload files
const results = await client.uploadFiles('my-space', files);Authentication Strategy
Silo uses a two-tier authentication model:
1. Admin Bearer Tokens
- Used for user provisioning (creating users)
- Should only exist on your backend server
- Never expose to frontend clients
import { createAdminClient } from '@stabledata/silo/proxy/express';
const adminClient = createAdminClient({
baseUrl: process.env.SILO_API_URL!,
adminToken: process.env.SILO_ADMIN_TOKEN!
});
// Create user during signup
const siloUser = await adminClient.createUser(
'[email protected]',
'space-123',
'User Space'
);
// Store siloUser.api_key in your database for this user2. User API Keys
- Each user gets their own API key (returned from
createUser) - Scoped to that user's spaces and permissions
- Can be safely used in authenticated requests
const userClient = new SiloClient({
baseUrl: process.env.SILO_API_URL!,
userId: user.id,
apiKey: user.siloApiKey // From your database
});Permission Model
API keys support fine-grained permissions:
- Full Access: All operations on all spaces
- Space-Scoped: Restricted to specific spaces
- Operation-Scoped: Upload-only, read-only, etc.
// Create API key with custom permissions
const apiKey = await client.createApiKey({
name: 'Upload-only key',
permissions: {
spaces: {
allowed_spaces: ['vacation-2024'],
upload: true,
download: false,
delete: false,
list_assets: true,
manage_spaces: false
}
}
});Integration Patterns
Backend Proxy (Recommended)
Your backend handles authentication and proxies Silo requests:
Frontend → Your Backend (Auth) → Silo APIBenefits:
- Admin credentials never exposed
- Centralized auth logic
- Easy to add custom business logic
- Works with any auth system (JWT, sessions, etc.)
Examples:
- Express Proxy - Traditional Node.js apps
- Hono Proxy - Modern/edge deployments
Direct Client (Mobile/Desktop Apps)
Apps use user API keys directly:
Mobile App → Silo API (with user API key)Setup:
- User signs up via your backend
- Backend creates Silo user with admin token
- Backend returns user API key to app
- App stores key securely (Keychain/KeyStore)
- App uses key for all Silo operations
// In your mobile app
const client = new SiloClient({
baseUrl: 'https://silo.your-api.com',
userId: currentUser.id,
apiKey: secureStorage.get('siloApiKey')
});API Reference
SiloClient
Constructor
new SiloClient(config: SiloClientConfig)
interface SiloClientConfig {
baseUrl: string; // Your Silo API base URL
userId: string; // Current user ID
apiKey?: string; // User API key (for user operations)
bearerToken?: string; // Admin token (for admin operations)
}User Management
createUser(email, spaceId?, spaceName?)
Create a new user with initial space (requires admin token).
const user = await adminClient.createUser(
'[email protected]',
'user-space-123',
'My Photos'
);
// user.api_key - Store this in your database!getUserByEmail(email)
Look up an existing user by email.
const user = await client.getUserByEmail('[email protected]');Space Management
createSpace(spaceId, name?)
Create a new space for organizing files.
const space = await client.createSpace('vacation-2024', 'Vacation Photos 2024');Space ID Requirements:
- 3-63 characters
- Lowercase letters, numbers, and hyphens only
- Must start and end with alphanumeric characters
listSpaces()
Get all spaces the current user has access to.
const spaces = await client.listSpaces();updateSpace(request)
Update space name and manage members.
await client.updateSpace({
space_id: 'vacation-2024',
name: 'Updated Name',
members: [
{
email: '[email protected]',
role: 'member',
operation: 'create'
}
]
});File Upload
uploadFile(spaceId, file, onProgress?)
Upload a single file.
const results = await client.uploadFile('my-space', file, (progress) => {
console.log(`${progress.message} - ${progress.progress}%`);
});uploadFiles(spaceId, files, onProgress?)
Upload multiple files with automatic asset grouping.
const files = [
new File([/* data */], 'photo.jpg'),
new File([/* data */], 'photo.mov'), // Same basename = grouped asset
new File([/* data */], 'another.jpg')
];
const results = await client.uploadFiles('my-space', files, (progress) => {
console.log(`Stage: ${progress.stage}`);
console.log(`File: ${progress.fileName}`);
console.log(`Progress: ${progress.progress}%`);
});Asset Grouping Logic:
- Files with the same basename (different extensions) are grouped together
- JPG/HEIC + MOV with same basename automatically detected as Live Photos
- Single files become
SINGLEassets - Multiple ungrouped files become
BATCHassets
uploadLivePhoto(spaceId, imageFile, videoFile, onProgress?)
Upload a Live Photo (image + video pair).
const imageFile = new File([/* data */], 'IMG_001.heic');
const videoFile = new File([/* data */], 'IMG_001.mov');
const results = await client.uploadLivePhoto('my-space', imageFile, videoFile);
// results[0].hasLiveVideo === true
// results[0].assetType === 'LIVE_PHOTO'Asset Retrieval
listAssets(spaceId, thumbnailSize?)
List all assets in a space with thumbnails. Always returns complete files array per Silo specifications.
const assetList = await client.listAssets('my-space', 'square');
assetList.assets.forEach(asset => {
// Access complete files array (ALWAYS included)
asset.files.forEach(file => {
console.log(`${file.fileName} (${file.fileRole})`);
console.log(`Direct URL: ${file.signedUrl}`);
});
// Use requestedFormatURL for gallery thumbnails
if (asset.requestedFormatURL) {
console.log(`Thumbnail: ${asset.requestedFormatURL}`);
}
});Available Thumbnail Sizes:
square- 150x150px (grids, profile pics)small- 320x240px (mobile list views)medium- 800x600px (desktop previews)large- 1920x1440px (full-screen preview)
getAssetFiles(spaceId, assetId, thumbnailSize?)
Get files for a specific asset with optional thumbnail generation.
const asset = await client.getAssetFiles('my-space', 'asset-uuid', 'square');
console.log(asset.files); // All files including thumbnailAsset Management
deleteAsset(spaceId, assetId)
Delete an asset and all its files.
await client.deleteAsset('my-space', 'asset-uuid');deleteFile(spaceId, fileId)
Delete a specific file from an asset.
await client.deleteFile('my-space', 'file-uuid');copyAsset(fromSpaceId, toSpaceId, assetId)
Copy an asset to a different space.
const result = await client.copyAsset('space-1', 'space-2', 'asset-uuid');moveAsset(fromSpaceId, toSpaceId, assetId)
Move an asset to a different space.
const result = await client.moveAsset('space-1', 'space-2', 'asset-uuid');API Key Management
createApiKey(request)
Create a new API key with custom permissions.
const apiKey = await client.createApiKey({
name: 'Mobile App Key',
permissions: {
spaces: {
upload: true,
download: true,
delete: false,
list_assets: true,
manage_spaces: false
}
},
expiresAt: '2025-12-31T23:59:59Z'
});
// apiKey.apiKey contains the full key (only returned once!)listApiKeys()
List all API keys for the current user.
const keys = await client.listApiKeys();updateApiKey(keyId, request)
Update API key properties.
await client.updateApiKey('key-id', {
name: 'Updated Name',
isActive: false
});deleteApiKey(keyId)
Delete/revoke an API key.
await client.deleteApiKey('key-id');Error Handling
The client provides typed error classes for different failure scenarios:
import {
SpaceNotFoundError,
QuotaExceededError,
InvalidFileError
} from '@stabledata/silo';
try {
await client.uploadFile('nonexistent-space', file);
} catch (error) {
if (error instanceof SpaceNotFoundError) {
console.log('Space does not exist');
} else if (error instanceof QuotaExceededError) {
console.log('Storage quota exceeded');
} else if (error instanceof InvalidFileError) {
console.log('Invalid file format');
}
}Error Types
MissingHeaderError- Required header missingInvalidUserIdError- Invalid user ID formatSpaceAccessDeniedError- No access to spaceSpaceNotFoundError- Space does not existQuotaExceededError- Storage quota exceededInvalidFileError- Invalid file format/sizeFileProcessingError- Server-side processing errorStorageError- Storage backend errorDatabaseError- Database operation errorAssetNotFoundError- Asset does not existFileNotFoundError- File does not existAssetCopyError- Asset copy/move failed
Proxy Helpers
Express
import express from 'express';
import {
createSiloMiddleware,
createAdminClient,
createUploadHandler,
createListAssetsHandler
} from '@stabledata/silo/proxy/express';
const app = express();
// Admin client for user creation
const adminClient = createAdminClient({
baseUrl: process.env.SILO_API_URL!,
adminToken: process.env.SILO_ADMIN_TOKEN!
});
// User creation endpoint
app.post('/api/users', async (req, res) => {
const siloUser = await adminClient.createUser(req.body.email);
// Store siloUser.api_key in your database
res.json({ user: siloUser });
});
// Protected routes with Silo middleware
app.use('/api/silo', authenticateUser);
app.use('/api/silo', createSiloMiddleware({
baseUrl: process.env.SILO_API_URL!,
getUserId: (req) => req.user.id,
getApiKey: (req) => req.user.siloApiKey
}));
// Upload endpoint
app.post('/api/silo/upload',
multer().array('files'),
createUploadHandler({
getSpaceId: (req) => req.body.spaceId
})
);
// List assets endpoint
app.get('/api/silo/spaces/:spaceId/assets', createListAssetsHandler());See Express Example for full implementation.
Hono
import { Hono } from 'hono';
import {
createSiloMiddleware,
createAdminClient,
createUploadHandler,
createListAssetsHandler
} from '@stabledata/silo/proxy/hono';
const app = new Hono();
// Admin client for user creation
const adminClient = createAdminClient({
baseUrl: process.env.SILO_API_URL!,
adminToken: process.env.SILO_ADMIN_TOKEN!
});
// Protected routes
const api = new Hono();
api.use('*', authenticateUser);
api.use('*', createSiloMiddleware({
baseUrl: process.env.SILO_API_URL!,
getUserId: (c) => c.get('userId'),
getApiKey: (c) => c.get('apiKey')
}));
api.post('/upload', createUploadHandler({
getSpaceId: (c) => c.req.query('space')
}));
api.get('/spaces/:spaceId/assets', createListAssetsHandler());
app.route('/api/silo', api);See Hono Example for full implementation including Cloudflare Workers deployment.
TypeScript Types
The client exports comprehensive TypeScript types:
import type {
UserResponse,
SpaceResponse,
AssetUploadResult,
UploadProgress,
SiloClientConfig,
AssetListItem,
AssetFileInfo,
ApiKeyResponse
} from '@stabledata/silo';Security Best Practices
✅ DO
Keep admin tokens secure
- Store in environment variables
- Never commit to version control
- Only use on backend servers
Store user API keys properly
- Encrypt in database
- Use secure storage on mobile (Keychain/KeyStore)
- Never log or expose in errors
Use HTTPS in production
- Always use TLS for API communications
- Validate SSL certificates
Implement rate limiting
- Protect upload endpoints
- Prevent abuse
Validate inputs
- Check file sizes and types
- Sanitize user-provided space IDs
❌ DON'T
Never expose admin tokens
- Don't send to frontend
- Don't include in client bundles
- Don't log in production
Don't trust client data
- Always verify user identity on backend
- Don't use client-provided user IDs directly
- Validate all inputs
Don't return API keys unnecessarily
- Only return during user creation
- Never include in list/get responses
- Don't log API keys
Examples
Complete Backend Integration
See working examples:
- Express Proxy Server - Traditional Node.js
- Hono Proxy Server - Modern/Edge/Cloudflare Workers
Mobile App Pattern
// 1. During signup (backend)
app.post('/signup', async (req, res) => {
const siloUser = await adminClient.createUser(req.body.email);
await db.users.create({
...req.body,
siloApiKey: siloUser.api_key // Store encrypted
});
res.json({ success: true });
});
// 2. In mobile app (after authentication)
const user = await yourAuth.getCurrentUser();
const siloClient = new SiloClient({
baseUrl: 'https://silo.your-api.com',
userId: user.id,
apiKey: await secureStorage.get('siloApiKey')
});
// 3. Upload photos
const results = await siloClient.uploadFiles('my-space', photos);Web App Pattern
// Frontend calls your backend proxy
const uploadToSilo = async (files: File[]) => {
const formData = new FormData();
files.forEach(f => formData.append('files', f));
const response = await fetch('/api/silo/upload', {
method: 'POST',
headers: {
'Authorization': `Bearer ${userToken}` // Your auth
},
body: formData
});
return response.json();
};Test Application
This package includes a complete React test application:
cd test-app
npm install
npm run devThe test app demonstrates:
- User creation and lookup
- Space management
- File upload with drag & drop
- Live Photo upload
- Real-time progress tracking
- Error handling
License
MIT
Support
For issues and feature requests, please visit the GitHub repository.
