@appmerge/appcenter-sdk
v1.0.3
Published
Official Node.js SDK for AppCenter - Manage apps and premium users with ease
Maintainers
Readme
AppCenter Node.js SDK
Official Node.js SDK for AppCenter - Manage apps and premium users with token-based subscriptions.
📦 Installation
npm install appcenter-sdkOr if using it locally:
const AppCenter = require('./path/to/appcenter-node');🚀 Quick Start
const AppCenter = require('appcenter-sdk');
// Initialize with your project key (from apps table)
const client = new AppCenter('your-project-key-uuid');
// Get app information
const app = await client.apps.get();
console.log(app.name, app.token_cost);
// Get premium user
const user = await client.premiumUsers.getUser('user123');
console.log(user.renewtoken, user.nonrenewtoken, user.token);
// Decrement tokens manually
await client.premiumUsers.decrementTokens('user123', 5);
// Decrement tokens by app's token_cost
await client.premiumUsers.decrementTokensByAppCost('user123');✨ Features
- ✅ Apps Management - Retrieve app information by project key
- ✅ Premium Users - Manage user tokens and subscriptions
- ✅ Token Operations - Decrement tokens manually or by app cost
- ✅ Smart Caching - Automatic 1-hour cache for app data (2000x+ performance boost)
- ✅ Error Handling - Comprehensive error classes for robust error handling
- ✅ TypeScript Support - Full TypeScript type definitions included
- ✅ Zero Configuration - Works out of the box with hardcoded Supabase credentials
📖 Complete API Reference
🔧 Initialization
const AppCenter = require('appcenter-sdk');
// Basic initialization (recommended)
const client = new AppCenter('your-project-key-uuid');
// Advanced initialization (override Supabase credentials if needed)
const client = new AppCenter('your-project-key-uuid', {
supabaseUrl: 'https://custom-url.supabase.co',
supabaseKey: 'custom-service-role-key'
});Parameters:
projectKey(string, required) - Your app's project_key from theappstable
📱 Apps Service
client.apps.get(skipCache)
Get app information by project key (with automatic caching).
Parameters:
skipCache(boolean, optional) - Set totrueto bypass cache and fetch fresh data from database. Default:false
Returns: Promise<App> - App object with all fields from the apps table
Example:
// Get app (uses cache if available)
const app = await client.apps.get();
// Force fresh data from database
const app = await client.apps.get(true);
console.log(app.name); // "AnimalScat Identifier"
console.log(app.id); // 2
console.log(app.token_cost); // 5
console.log(app.ios_active); // true
console.log(app.android_active); // true
console.log(app.project_key); // "your-project-key-uuid"App Object Structure:
{
id: 2,
name: "My App",
app_icon_url: "https://...",
ios_id: "com.example.app",
android_id: "com.example.app",
token_cost: 5, // Tokens required per use
weekly_token: 10, // Tokens for weekly subscription
yearly_token: 100, // Tokens for yearly subscription
lifetime_token: 1000, // Tokens for lifetime purchase
token_packages: {...}, // JSON array of token packages
ios_active: true,
android_active: true,
project_key: "uuid-here",
organization_id: "uuid",
ios_trial_count: 3,
android_trial_count: 7,
rc_project_id: "...",
rc_key: "...",
created_at: "2024-01-01T00:00:00Z",
order: 1
}Throws:
AppNotFoundError- If app with project key not foundDatabaseError- If database operation fails
client.apps.clearCache()
Clear the cached app data for this project key.
Returns: boolean - true if cache was cleared, false if nothing was cached
Example:
const cleared = client.apps.clearCache();
console.log('Cache cleared:', cleared);client.apps.getCacheStats()
Get cache statistics for monitoring and debugging.
Returns: Object - Cache statistics
Example:
const stats = client.apps.getCacheStats();
console.log(stats);
// { hits: 5, misses: 2, keys: 1, ksize: 40, vsize: 1600 }Stats Object:
hits- Number of successful cache retrievalsmisses- Number of times data was fetched from databasekeys- Number of cached itemsksize- Total size of keys in bytesvsize- Total size of values in bytes
👤 Premium Users Service
client.premiumUsers.getUser(userId)
Get premium user information by user_id.
Parameters:
userId(string, required) - User ID from thepremium_userstable
Returns: Promise<PremiumUser> - Premium user object
Example:
const user = await client.premiumUsers.getUser('user123');
console.log(user.renewtoken); // 50 (renewable tokens)
console.log(user.nonrenewtoken); // 10 (non-renewable tokens)
console.log(user.token); // 60 (total: computed field)
console.log(user.is_active); // true
console.log(user.package); // "premium"
console.log(user.platform); // "ios"PremiumUser Object Structure:
{
id: "uuid",
user_id: "user123",
renewtoken: 50, // Renewable (yearly) tokens
nonrenewtoken: 10, // Non-renewable (one-time) tokens
token: 60, // Total tokens (computed)
is_active: true,
package: "premium",
package_end_date: "2024-12-31T23:59:59Z",
platform: "ios",
app_id: 2,
organization_id: "uuid",
cancel_reason: null,
created_at: "2024-01-01T00:00:00Z",
updated_at: "2024-06-01T00:00:00Z"
}Throws:
UserNotFoundError- If user not foundDatabaseError- If database operation fails
client.premiumUsers.decrementTokens(userId, amount)
Decrement tokens from user balance with a manual amount.
Token Deduction Priority:
- Deducts from
renewtokenfirst - If
renewtokenis insufficient, deducts remaining fromnonrenewtoken
Parameters:
userId(string, required) - User IDamount(number, required) - Amount of tokens to decrement
Returns: Promise<PremiumUser> - Updated user object with new token balances
Example:
// User has: renewtoken=50, nonrenewtoken=10
// Decrement 5 tokens
const user = await client.premiumUsers.decrementTokens('user123', 5);
// Result: renewtoken=45, nonrenewtoken=10
// Decrement 60 tokens
const user = await client.premiumUsers.decrementTokens('user123', 60);
// Result: renewtoken=0, nonrenewtoken=0 (50 from renew + 10 from nonrenew)Throws:
UserNotFoundError- If user not foundInsufficientTokensError- If user doesn't have enough tokensDatabaseError- If database operation fails
client.premiumUsers.decrementTokensByAppCost(userId)
Decrement tokens based on the app's token_cost value.
How it works:
- Fetches app's
token_costfrom theappstable (uses cache) - Calls
decrementTokens(userId, token_cost)with that amount
Parameters:
userId(string, required) - User ID
Returns: Promise<PremiumUser> - Updated user object with new token balances
Example:
// App has token_cost = 5
const user = await client.premiumUsers.decrementTokensByAppCost('user123');
// Decrements 5 tokens from userThrows:
UserNotFoundError- If user not foundInsufficientTokensError- If user doesn't have enough tokensInvalidTokenCostError- If app's token_cost is 0 or invalidDatabaseError- If database operation fails
⚡ Performance & Caching
The SDK includes automatic caching for app data to minimize database calls:
- Cache Duration: 1 hour (3600 seconds)
- Auto Cleanup: Every 10 minutes
- Performance: 2000x+ faster (0.03ms vs 80ms)
Cache Behavior:
// First call - fetches from database
console.time('First call');
const app1 = await client.apps.get();
console.timeEnd('First call'); // ~80ms
// Second call - returns from cache
console.time('Second call');
const app2 = await client.apps.get();
console.timeEnd('Second call'); // ~0.03ms (2600x faster!)
// Force fresh data
const app3 = await client.apps.get(true); // Skips cache
// Clear cache manually
client.apps.clearCache();When to Clear Cache:
- After updating app data in the database
- When you need the absolute latest data
- During testing/debugging
🚨 Error Handling
The SDK provides specific error classes for different failure scenarios.
Error Classes
AppNotFoundError
Thrown when app with project key is not found.
try {
const app = await client.apps.get();
} catch (error) {
if (error instanceof AppCenter.AppNotFoundError) {
console.log('App not found:', error.projectKey);
}
}Properties:
projectKey(string) - The project key that was not foundcode='APP_NOT_FOUND'
UserNotFoundError
Thrown when premium user is not found.
try {
const user = await client.premiumUsers.getUser('user123');
} catch (error) {
if (error instanceof AppCenter.UserNotFoundError) {
console.log('User not found:', error.userId);
}
}Properties:
userId(string) - The user ID that was not foundcode='USER_NOT_FOUND'
InsufficientTokensError
Thrown when user doesn't have enough tokens.
try {
await client.premiumUsers.decrementTokens('user123', 50);
} catch (error) {
if (error instanceof AppCenter.InsufficientTokensError) {
console.log(`Need ${error.required} tokens, but only have ${error.available}`);
}
}Properties:
userId(string) - The user IDrequired(number) - Required token amountavailable(number) - Available token amountcode='INSUFFICIENT_TOKENS'
InvalidTokenCostError
Thrown when app token_cost is 0 or invalid.
try {
await client.premiumUsers.decrementTokensByAppCost('user123');
} catch (error) {
if (error instanceof AppCenter.InvalidTokenCostError) {
console.log(`App token_cost is ${error.tokenCost}. Cannot decrement.`);
}
}Properties:
tokenCost(number) - The invalid token cost valuecode='INVALID_TOKEN_COST'
DatabaseError
Thrown when database operation fails.
try {
const user = await client.premiumUsers.getUser('user123');
} catch (error) {
if (error instanceof AppCenter.DatabaseError) {
console.log('Database error:', error.message);
console.log('Original error:', error.originalError);
}
}Properties:
originalError(any) - The original error from Supabasecode='DATABASE_ERROR'
Complete Error Handling Example
const AppCenter = require('appcenter-sdk');
async function processUserAction(userId) {
try {
const client = new AppCenter('your-project-key-uuid');
// Get app info
const app = await client.apps.get();
// Check if feature costs tokens
if (app.token_cost === 0) {
console.log('This feature is free!');
return;
}
// Get user
const user = await client.premiumUsers.getUser(userId);
// Check if user has enough tokens
if (user.token < app.token_cost) {
console.log(`Insufficient tokens! Need ${app.token_cost}, have ${user.token}`);
return;
}
// Decrement tokens
const updatedUser = await client.premiumUsers.decrementTokensByAppCost(userId);
console.log(`Success! Remaining tokens: ${updatedUser.token}`);
} catch (error) {
if (error instanceof AppCenter.AppNotFoundError) {
console.error('App configuration error');
} else if (error instanceof AppCenter.UserNotFoundError) {
console.error('User not found - may need to create premium user');
} else if (error instanceof AppCenter.InsufficientTokensError) {
console.error(`Not enough tokens: need ${error.required}, have ${error.available}`);
} else if (error instanceof AppCenter.InvalidTokenCostError) {
console.error(`Invalid app configuration: token_cost is ${error.tokenCost}`);
} else if (error instanceof AppCenter.DatabaseError) {
console.error('Database error:', error.message);
} else {
console.error('Unexpected error:', error);
}
}
}🔧 Utility Methods
client.testConnection()
Test the connection to Supabase database.
Returns: Promise<boolean> - true if connected, false otherwise
Example:
const isConnected = await client.testConnection();
if (!isConnected) {
console.error('Failed to connect to database');
}📊 Complete Usage Example
const AppCenter = require('appcenter-sdk');
async function main() {
try {
// Initialize client
const client = new AppCenter('bad695f2-2e98-4e53-a952-14c06f6796fa');
// Test connection
const isConnected = await client.testConnection();
if (!isConnected) {
throw new Error('Failed to connect to database');
}
console.log('✅ Connected to database');
// Get app info (first call - from database)
console.time('App fetch');
const app = await client.apps.get();
console.timeEnd('App fetch');
console.log('App:', app.name);
console.log('Token Cost:', app.token_cost);
console.log('Cache Stats:', client.apps.getCacheStats());
// Get app info again (second call - from cache)
console.time('App fetch (cached)');
const app2 = await client.apps.get();
console.timeEnd('App fetch (cached)');
console.log('Cache Stats:', client.apps.getCacheStats());
// Get premium user
const userId = 'user123';
const user = await client.premiumUsers.getUser(userId);
console.log('User Tokens:', user.token);
console.log(' - Renewable:', user.renewtoken);
console.log(' - Non-Renewable:', user.nonrenewtoken);
// Check if user has enough tokens
if (user.token < app.token_cost) {
console.log('❌ Insufficient tokens');
return;
}
// Decrement tokens by app cost
const updatedUser = await client.premiumUsers.decrementTokensByAppCost(userId);
console.log('✅ Tokens decremented successfully');
console.log('Remaining Tokens:', updatedUser.token);
} catch (error) {
if (error instanceof AppCenter.AppNotFoundError) {
console.error('❌ App not found:', error.projectKey);
} else if (error instanceof AppCenter.UserNotFoundError) {
console.error('❌ User not found:', error.userId);
} else if (error instanceof AppCenter.InsufficientTokensError) {
console.error(`❌ Insufficient tokens: need ${error.required}, have ${error.available}`);
} else if (error instanceof AppCenter.InvalidTokenCostError) {
console.error(`❌ Invalid token cost: ${error.tokenCost}`);
} else if (error instanceof AppCenter.DatabaseError) {
console.error('❌ Database error:', error.message);
} else {
console.error('❌ Unknown error:', error);
}
}
}
main();📝 TypeScript Support
The SDK includes full TypeScript type definitions:
import AppCenter, {
App,
PremiumUser,
AppNotFoundError,
UserNotFoundError,
InsufficientTokensError,
InvalidTokenCostError,
DatabaseError,
} from 'appcenter-sdk';
const client = new AppCenter('your-project-key-uuid');
// Fully typed responses
const app: App = await client.apps.get();
const user: PremiumUser = await client.premiumUsers.getUser('user123');🗂️ Project Structure
appcenter-node/
├── index.js # Main entry point
├── src/
│ ├── AppCenterClient.js # Main client class
│ ├── services/
│ │ ├── apps.js # Apps service with caching
│ │ └── premiumUsers.js # Premium users service
│ ├── utils/
│ │ └── errors.js # Error classes
│ └── types/
│ └── index.d.ts # TypeScript definitions
├── examples/
│ └── basic-usage.js # Usage examples
├── test-simple.js # Simple connection test
├── test-cache.js # Cache performance test
├── package.json
└── README.md🔑 Database Schema
apps Table
The SDK connects to the apps table with the following structure:
apps (
id bigint PRIMARY KEY,
name text,
app_icon_url text,
ios_id text,
android_id text,
token_cost bigint DEFAULT 0, -- Tokens required per use
weekly_token bigint DEFAULT 0,
yearly_token bigint DEFAULT 0,
lifetime_token bigint DEFAULT 0,
token_packages jsonb,
ios_active boolean,
android_active boolean,
project_key text UNIQUE, -- Used by SDK for identification
organization_id uuid,
ios_trial_count smallint DEFAULT 0,
android_trial_count smallint DEFAULT 0,
rc_project_id text,
rc_key text,
created_at timestamp,
order integer
)premium_users Table
The SDK connects to the premium_users table:
premium_users (
id uuid PRIMARY KEY,
user_id text UNIQUE, -- Used by SDK for identification
renewtoken integer DEFAULT 0, -- Renewable (yearly) tokens
nonrenewtoken integer DEFAULT 0, -- Non-renewable (one-time) tokens
token integer GENERATED, -- Total tokens (computed)
package text,
package_end_date timestamp,
is_active boolean DEFAULT true,
platform text,
app_id bigint,
organization_id uuid,
cancel_reason text,
created_at timestamp,
updated_at timestamp
)Token Priority:
renewtokenis decremented first (renewable subscription tokens)nonrenewtokenis decremented second (one-time purchase tokens)tokenis a computed field:renewtoken + nonrenewtoken
🛠️ Configuration
Supabase Credentials
By default, the SDK uses hardcoded Supabase credentials in src/AppCenterClient.js:
// src/AppCenterClient.js (lines 25-28)
const supabaseUrl =
options.supabaseUrl || 'https://supabasekong-kwo40oswwcw0kc4ks0gs8ggs.service.appmergly.work';
const supabaseKey =
options.supabaseKey || 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...';You can override these at runtime:
const client = new AppCenter('your-project-key-uuid', {
supabaseUrl: 'https://your-project.supabase.co',
supabaseKey: 'your-service-role-key'
});Note: The SDK uses the SERVICE_ROLE_KEY (not ANON_KEY) to access all tables without RLS restrictions.
🧪 Testing
Test Connection
node test-simple.jsOutput:
✅ Successfully connected to Supabase!
⚠️ App not found (expected - use a real project_key)
⚠️ User not found (expected - use a real user_id)
✅ SDK is working correctly!Test Cache Performance
node test-cache.jsOutput:
First call: 335.581ms
Second call (cached): 0.132ms
✅ Cache is working perfectly!
Performance comparison (10 calls):
Without cache: 676.702ms
With cache: 64.377msTest with Real Data
node examples/basic-usage.js💡 Best Practices
1. Reuse Client Instances
// ❌ Don't create new instances for each request
async function badExample() {
const client = new AppCenter('project-key');
const app = await client.apps.get();
}
// ✅ Create once, reuse many times
const client = new AppCenter('project-key');
async function goodExample() {
const app = await client.apps.get(); // Uses cache!
}2. Handle Errors Gracefully
// ✅ Always catch specific errors
try {
await client.premiumUsers.decrementTokensByAppCost(userId);
} catch (error) {
if (error instanceof AppCenter.InsufficientTokensError) {
// Show "buy more tokens" UI
} else if (error instanceof AppCenter.InvalidTokenCostError) {
// Feature is free, no tokens needed
}
}3. Use Cache Wisely
// ✅ Let cache work for you
const app = await client.apps.get(); // Fast!
// ✅ Force refresh only when needed
const app = await client.apps.get(true); // After updating app settings
// ✅ Clear cache after updates
client.apps.clearCache();4. Check Before Decrementing
// ✅ Check token balance first
const user = await client.premiumUsers.getUser(userId);
const app = await client.apps.get();
if (user.token >= app.token_cost) {
await client.premiumUsers.decrementTokensByAppCost(userId);
} else {
// Offer to buy more tokens
}🐛 Troubleshooting
"App not found" Error
// Error: App not found with project key: xxxSolutions:
- Verify the project_key exists in the
appstable - Check if you're using the correct project_key (it's a UUID)
- Ensure Supabase credentials are correct
"User not found" Error
// Error: Premium user not found: user123Solutions:
- Create a premium_user record in the database
- Verify the user_id matches exactly
- Check if user record exists in
premium_userstable
"Insufficient tokens" Error
// Error: Insufficient tokens. Required: 5, Available: 0Solutions:
- Check user's token balance:
user.token - Add more tokens to user's account
- Use renewable tokens (subscription) or non-renewable tokens (one-time purchase)
Cache Not Working
// Both calls are slowSolutions:
- Check if you're creating new client instances
- Verify cache stats:
client.apps.getCacheStats() - Ensure you're not always using
skipCache = true
📄 License
ISC
🤝 Support
For issues and questions, please refer to:
- Database schema in
/backend/appcenter-api/ - Example files in
/examples/ - Test files:
test-simple.js,test-cache.js
📚 Additional Resources
- Supabase Documentation: https://supabase.com/docs
- Node-cache Documentation: https://github.com/node-cache/node-cache
- TypeScript Definitions: See
src/types/index.d.ts
Made with ❤️ for AppCenter
