npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@appmerge/appcenter-sdk

v1.0.3

Published

Official Node.js SDK for AppCenter - Manage apps and premium users with ease

Readme

AppCenter Node.js SDK

Official Node.js SDK for AppCenter - Manage apps and premium users with token-based subscriptions.


📦 Installation

npm install appcenter-sdk

Or 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 the apps table

📱 Apps Service

client.apps.get(skipCache)

Get app information by project key (with automatic caching).

Parameters:

  • skipCache (boolean, optional) - Set to true to 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 found
  • DatabaseError - 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 retrievals
  • misses - Number of times data was fetched from database
  • keys - Number of cached items
  • ksize - Total size of keys in bytes
  • vsize - 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 the premium_users table

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 found
  • DatabaseError - If database operation fails

client.premiumUsers.decrementTokens(userId, amount)

Decrement tokens from user balance with a manual amount.

Token Deduction Priority:

  1. Deducts from renewtoken first
  2. If renewtoken is insufficient, deducts remaining from nonrenewtoken

Parameters:

  • userId (string, required) - User ID
  • amount (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 found
  • InsufficientTokensError - If user doesn't have enough tokens
  • DatabaseError - If database operation fails

client.premiumUsers.decrementTokensByAppCost(userId)

Decrement tokens based on the app's token_cost value.

How it works:

  1. Fetches app's token_cost from the apps table (uses cache)
  2. 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 user

Throws:

  • UserNotFoundError - If user not found
  • InsufficientTokensError - If user doesn't have enough tokens
  • InvalidTokenCostError - If app's token_cost is 0 or invalid
  • DatabaseError - 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 found
  • code = '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 found
  • code = '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 ID
  • required (number) - Required token amount
  • available (number) - Available token amount
  • code = '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 value
  • code = '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 Supabase
  • code = '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:

  • renewtoken is decremented first (renewable subscription tokens)
  • nonrenewtoken is decremented second (one-time purchase tokens)
  • token is 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.js

Output:

✅ 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.js

Output:

First call: 335.581ms
Second call (cached): 0.132ms
✅ Cache is working perfectly!

Performance comparison (10 calls):
Without cache: 676.702ms
With cache: 64.377ms

Test 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: xxx

Solutions:

  1. Verify the project_key exists in the apps table
  2. Check if you're using the correct project_key (it's a UUID)
  3. Ensure Supabase credentials are correct

"User not found" Error

// Error: Premium user not found: user123

Solutions:

  1. Create a premium_user record in the database
  2. Verify the user_id matches exactly
  3. Check if user record exists in premium_users table

"Insufficient tokens" Error

// Error: Insufficient tokens. Required: 5, Available: 0

Solutions:

  1. Check user's token balance: user.token
  2. Add more tokens to user's account
  3. Use renewable tokens (subscription) or non-renewable tokens (one-time purchase)

Cache Not Working

// Both calls are slow

Solutions:

  1. Check if you're creating new client instances
  2. Verify cache stats: client.apps.getCacheStats()
  3. 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