@fynlink/sdk v0.1.1
Fyn SDK
⚠️ BETA VERSION - This SDK is currently in beta and not recommended for production use. APIs and functionality may change without notice.
The privacy-first URL shortener SDK for Node.js and TypeScript.
Installation
npm install @fynlink/sdkQuick Start
Initialize the SDK:
import { Fyn } from '@fynlink/sdk';
const fyn = new Fyn({
token: 'token_<your-token>',
secret: 'secret_<your-secret>' // Required only to view/list or update links
});Basic Usage
Create a simple link:
const link = await fyn.links.create({
target: 'https://example.com',
domain: 'fyn.li'
});
// Response
{
data: {
id: 'link_abc123',
short_url: 'https://fyn.li/xyz789',
target: {
default: 'https://example.com',
ios: null,
android: null,
geo: {}
},
clicks: 0,
created_at: '2024-03-15T10:30:00Z'
}
}Get a link:
const link = await fyn.links.get('link_abc123');
// Response
{
data: {
id: 'link_abc123',
short_url: 'https://fyn.li/xyz789',
target: {
default: 'https://example.com',
ios: null,
android: null,
geo: {}
},
clicks: 42,
created_at: '2024-03-15T10:30:00Z',
updated_at: '2024-03-15T10:30:00Z'
}
}List links:
const links = await fyn.links.list();
// Response
{
data: [/* array of link objects */],
meta: {
current_page: 1,
from: 1,
to: 10,
last_page: 5,
per_page: 10,
total: 42
}
}Delete a link:
await fyn.links.delete('link_abc123');
// Response
{
data: null
}Advanced Usage
SDK Configuration
const fyn = new Fyn({
token: 'token_<your-base64-encoded-token>',
secret: 'your-secret-key',
baseUrl: 'https://api.fyn.link/v1',
timeout: 10000, // 10 seconds
maxRetries: 3,
retryDelay: 1000,
debug: true
});Automatic Retries
The SDK includes a smart retry mechanism for handling transient failures:
// Retry behavior
- Only retries on server errors (500-504)
- No retries on client errors (400-499)
- Maximum of 1 retry with 1-second delay
- Respects rate limits (429 responses)
- Immediate failure on permission errors (403)
// Example: Server error with retry
try {
const link = await fyn.links.get('link_123');
} catch (error) {
if (error.status >= 500) {
// The SDK already attempted one retry after 1 second
console.error('Server error persisted after retry');
}
}
// Example: Rate limit handling
try {
const link = await fyn.links.get('link_123');
} catch (error) {
if (error instanceof RateLimitError) {
console.log('Rate limit exceeded');
console.log('Reset at:', error.details.reset);
// No automatic retries on rate limits
}
}Caching Strategy
The SDK implements a sophisticated caching system:
// Cache features
- In-memory cache with 5-minute TTL by default
- Supports stale-while-revalidate pattern
- Respects Cache-Control headers
- Automatic background revalidation
- Cache invalidation on write operations
// Example: Cache behavior
const link = await fyn.links.get('link_123');
// First request: Makes API call, caches response
const sameLink = await fyn.links.get('link_123');
// Subsequent request within TTL: Returns cached data
// Cache statistics
const metrics = fyn.links.getMetrics();
console.log('Cache hit ratio:', metrics.cache.hitRatio);
console.log('Cache hits:', metrics.cache.hits);
console.log('Cache misses:', metrics.cache.misses);
// Manual cache control
fyn.links.clearCache(); // Clear all cached dataCaching and ETags
The SDK automatically handles caching using ETags for improved performance:
// First request - will fetch data from server
const link = await fyn.links.get('link_abc123');
// Subsequent requests - will use cached data if not modified
const sameLink = await fyn.links.get('link_abc123');
// If the resource hasn't changed, returns cached data without making a full request
// Clear cache if needed
fyn.links.clearCache(); // Clears both response cache and ETagsResponse Headers:
// First request
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
// Subsequent request
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"When the resource hasn't changed, the server will respond with 304 Not Modified, and the SDK will automatically use the cached data. This reduces bandwidth usage and improves response times.
Creating Links with Advanced Options
const link = await fyn.links.create({
target: 'https://example.com',
domain: 'fyn.li',
title: 'My Example Link',
notes: 'Important link for documentation',
tags: ['docs', 'example'],
privateLink: true,
safeMode: true,
password: 'secretpass123',
expiry: 86400, // 24 hours in seconds
iosLink: 'https://apps.apple.com/app/myapp',
androidLink: 'https://play.google.com/store/apps/myapp',
geoTargets: {
US: 'https://us.example.com',
UK: 'https://uk.example.com'
}
});
// Response
{
data: {
id: 'link_abc123',
short_url: 'https://fyn.li/xyz789',
target: {
default: 'https://example.com',
ios: 'https://apps.apple.com/app/myapp',
android: 'https://play.google.com/store/apps/myapp',
geo: {
US: 'https://us.example.com',
UK: 'https://uk.example.com'
}
},
title: 'My Example Link',
notes: 'Important link for documentation',
tags: ['docs', 'example'],
is_private: true,
safe_mode: true,
password_enabled: true,
clicks: 0,
created_at: '2024-03-15T10:30:00Z',
updated_at: '2024-03-15T10:30:00Z',
expire_at: '2024-03-16T10:30:00Z',
tracking: 'FULL_TRACKING'
}
}Listing Links with Filters and Sorting
import { SortBy, SortOrder, LinkStatus } from 'fyn';
const links = await fyn.links.list({
page: 1,
limit: 20,
sortBy: SortBy.CreatedAt,
sortOrder: SortOrder.Desc,
filters: [
{
is_private: true,
safe_mode: true,
status: LinkStatus.Active
}
]
});
// Response
{
data: [/* array of link objects */],
meta: {
current_page: 1,
from: 1,
to: 20,
last_page: 5,
per_page: 20,
total: 100
}
}Error Handling
The SDK throws typed errors that you can catch and handle:
import {
FynError,
ValidationError,
AuthError,
NotFoundError,
RateLimitError,
TimeoutError
} from 'fyn';
try {
const link = await fyn.links.get('invalid_id');
} catch (error) {
if (error instanceof ValidationError) {
// Handle validation errors (400)
console.error('Invalid parameters:', error.message);
} else if (error instanceof AuthError) {
// Handle authentication errors (401)
console.error('Authentication failed:', error.message);
} else if (error instanceof NotFoundError) {
// Handle not found errors (404)
console.error('Link not found:', error.message);
} else if (error instanceof RateLimitError) {
// Handle rate limit errors (429)
console.error('Rate limit exceeded:', error.message);
console.log('Reset at:', error.details?.reset);
} else if (error instanceof TimeoutError) {
// Handle timeout errors (408)
console.error('Request timed out:', error.message);
console.log('Timeout setting:', error.details?.timeout);
} else if (error instanceof FynError) {
// Handle other API errors
console.error('API error:', error.message);
console.log('Status:', error.status);
console.log('Details:', error.details);
} else {
// Handle unexpected errors
console.error('Unexpected error:', error);
}
}Error Response Examples:
// Validation Error
{
error: {
message: 'Invalid parameters',
status: 400,
details: {
validation: {
target: ['Target URL is required']
}
}
}
}
// Authentication Error
{
error: {
message: 'Authentication failed',
status: 401,
details: {
reason: 'Invalid or expired token'
}
}
}
// Not Found Error
{
error: {
message: 'Link not found',
status: 404
}
}
// Rate Limit Error
{
error: {
message: 'Rate limit exceeded',
status: 429,
details: {
remaining: 0,
reset: 1678892400,
limit: 100
}
}
}
// Timeout Error
{
error: {
message: 'Request timed out after 10000ms',
status: 408,
details: {
operation: 'get link',
timeout: 10000
}
}
}Performance Metrics
The SDK includes built-in performance monitoring:
// Get metrics for the last 5 minutes
const metrics = fyn.links.getMetrics(5);
// Response
{
totalRequests: 150,
averageResponseTime: 245.5,
successRate: 99.3,
rateLimit: {
remaining: 850,
reset: 1678892400,
limit: 1000
},
cache: {
hits: 45,
misses: 15,
hitRatio: 75.0
}
}For detailed API documentation, visit our documentation.