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 🙏

© 2025 – Pkg Stats / Ryan Hefner

domainly-sdk

v1.0.1

Published

Official TypeScript SDK for Domainly External SaaS API - Manage multi-tenant custom domains with ease

Readme

Domainly SDK

npm version TypeScript License: MIT

Official TypeScript/JavaScript SDK for the Domainly External SaaS API. Easily manage multi-tenant custom domains, subdomains, and SSL certificates for your SaaS application.

🆕 What's New in v1.0.1

  • Enhanced Webhook Testing: Test webhook endpoints with custom URLs
  • Webhook Retry Functionality: Retry failed webhook deliveries
  • Webhook Signature Verification: Built-in utilities for secure webhook handling
  • Improved Error Handling: Better error messages and retry logic
  • Enhanced Validation: Comprehensive input validation utilities
  • Updated Type Definitions: Types now match the latest API implementation
  • Webhook Event Monitoring: Tools for monitoring webhook delivery health

🚀 Features

  • Complete TypeScript Support - Full type definitions for all API operations
  • Multi-tenant Management - Create and manage tenants with ease
  • Custom Domain Handling - Add, verify, and manage custom domains
  • Subdomain Operations - Create and manage subdomains for your tenants
  • Webhook Integration - Configure webhooks for real-time notifications
  • Automatic Retries - Built-in retry logic for failed requests
  • Error Handling - Comprehensive error handling with detailed error types
  • Validation Utilities - Input validation helpers for common operations
  • Multiple Output Formats - CommonJS, ES Modules, and UMD builds

📦 Installation

npm install domainly-sdk
yarn add domainly-sdk
pnpm add domainly-sdk

🏃 Quick Start

import { DomainlyClient } from 'domainly-sdk';

// Initialize the client
const client = new DomainlyClient({
  apiKey: 'dom_your_api_key_here'
});

// Create a tenant with subdomain
const tenant = await client.createTenant({
  name: 'Acme Corporation',
  createSubdomain: true,
  subdomainName: 'acme',
  metadata: { 
    plan: 'premium',
    industry: 'technology'
  }
});

// Add a custom domain
const domain = await client.createDomain({
  customDomain: 'app.acme.com',
  subdomain: 'acme',
  tenantId: tenant.data.id,
  verificationMethod: 'cname'
});

console.log('Setup complete!', { tenant, domain });

🔧 Configuration

Basic Configuration

import { DomainlyClient } from 'domainly-sdk';

const client = new DomainlyClient({
  apiKey: 'dom_your_api_key_here',
  // Optional configuration
  baseURL: 'https://api.domainly.dev/external/api', // Default
  timeout: 30000, // Default: 30 seconds
  retries: 3, // Default: 3 retries
  retryDelay: 1000, // Default: 1 second
});

Using Environment Variables

// Set environment variable
process.env.DOMAINLY_API_KEY = 'dom_your_api_key_here';

const client = new DomainlyClient({
  apiKey: process.env.DOMAINLY_API_KEY!
});

📚 API Reference

Tenant Operations

Create Tenant

const tenant = await client.createTenant({
  name: 'Customer Company',
  createSubdomain: true, // Optional
  subdomainName: 'customer', // Optional
  metadata: { // Optional
    plan: 'enterprise',
    industry: 'finance'
  }
});

Get All Tenants

const tenants = await client.getTenants();
console.log(tenants.data); // Array of tenants

Get Specific Tenant

const tenant = await client.getTenant('tenant-id');
console.log(tenant.data);

Update Tenant

const updatedTenant = await client.updateTenant('tenant-id', {
  name: 'Updated Company Name',
  status: 'ACTIVE',
  metadata: { plan: 'premium' }
});

Delete Tenant

await client.deleteTenant('tenant-id');

Domain Operations

Create Custom Domain

const domain = await client.createDomain({
  customDomain: 'app.customer.com',
  subdomain: 'customer-subdomain', // Optional
  tenantId: 'tenant-id', // Optional
  verificationMethod: 'cname', // 'cname' | 'txt' | 'http'
  autoRenewSSL: true // Optional
});

Verify Domain

const verification = await client.verifyDomain('domain-id');
console.log('Verified:', verification.data.verified);

Get CNAME Records

const records = await client.getCNAMERecords('domain-id');
console.log('CNAME records:', records.data);

List All Domains

const domains = await client.getDomains();
console.log(domains.data);

Subdomain Operations

Create Subdomain

const subdomain = await client.createSubdomain({
  subdomain: 'store',
  tenantId: 'tenant-id', // Optional
  description: 'E-commerce store subdomain'
});

Check Subdomain Availability

const availability = await client.checkSubdomainAvailability('desired-subdomain');
console.log('Available:', availability.data.available);

Webhook Management

Configure Webhooks

await client.configureWebhook({
  webhookUrl: 'https://your-app.com/webhooks/domainly',
  secret: 'your-webhook-secret',
  events: [
    'tenant.created',
    'domain.verified',
    'ssl.provisioned'
  ]
});

Get Webhook Configuration

const config = await client.getWebhookConfig();
console.log(config.data);

Test Webhook

const testResult = await client.testWebhook({
  webhookUrl: 'https://your-app.com/webhooks/domainly'
});
console.log('Webhook test:', testResult.data.success);

Get Webhook Logs

const logs = await client.getWebhookLogs({ limit: 20, offset: 0 });
console.log('Recent webhook deliveries:', logs.data.logs);
console.log('Total events:', logs.data.total);

Retry Failed Webhook

const result = await client.retryWebhook('webhook-event-id');
console.log('Retry successful:', result.success);

🛠 Utility Functions

The SDK includes utility functions for common operations:

Domain Setup Workflow

import { DomainlyUtils } from 'domainly-sdk';

const utils = new DomainlyUtils(client);

// Complete setup with tenant, subdomain, and domain
const setup = await utils.createCompleteSetup({
  tenantName: 'Acme Corporation',
  subdomainName: 'acme',
  customDomain: 'app.acme.com',
  verificationMethod: 'cname',
  metadata: { plan: 'enterprise' }
});

console.log('Setup result:', setup);

Wait for Domain Verification

// Wait for domain to be verified with polling
const verifiedDomain = await utils.waitForDomainVerification('domain-id', {
  maxAttempts: 30,
  pollInterval: 5000, // 5 seconds
  timeout: 300000 // 5 minutes
});

console.log('Domain verified!', verifiedDomain);

Batch Operations

// Create multiple tenants at once
const batchResult = await utils.batchCreateTenants([
  { name: 'Company A', createSubdomain: true, subdomainName: 'company-a' },
  { name: 'Company B', createSubdomain: true, subdomainName: 'company-b' },
  { name: 'Company C', createSubdomain: true, subdomainName: 'company-c' }
], {
  concurrency: 3,
  continueOnError: true
});

console.log(`Created ${batchResult.successful} tenants`);

✅ Validation Utilities

Input Validation

import { DomainUtils, SubdomainUtils, ValidationUtils } from 'domainly-sdk';

// Validate domain format
const domainValidation = DomainUtils.validateDomain('app.example.com');
if (!domainValidation.valid) {
  console.error('Invalid domain:', domainValidation.error);
}

// Validate subdomain format
const subdomainValidation = SubdomainUtils.validateSubdomain('my-app');
if (!subdomainValidation.valid) {
  console.error('Invalid subdomain:', subdomainValidation.error);
}

// Validate complete requests
const tenantRequestValidation = ValidationUtils.validateCreateTenantRequest({
  name: 'Test Company',
  subdomainName: 'test-company'
});

if (!tenantRequestValidation.valid) {
  console.error('Validation errors:', tenantRequestValidation.errors);
}

🎯 Helper Functions

import { SubdomainUtils, DomainUtils, ErrorUtils } from 'domainly-sdk';

// Generate slug from company name
const slug = SubdomainUtils.generateSlug('My Company Inc!');
console.log(slug); // 'my-company-inc'

// Check domain status
const isVerified = DomainUtils.isDomainVerified(domain);
const hasSSL = DomainUtils.isSSLActive(domain);

// Get verification instructions
const instructions = DomainUtils.getVerificationInstructions(domain);
console.log(instructions);

// Handle errors gracefully
if (ErrorUtils.isRetryableError(error)) {
  // Implement retry logic
}

🔄 Error Handling

The SDK provides comprehensive error handling:

import { DomainlyClient, ErrorUtils } from 'domainly-sdk';

try {
  const tenant = await client.createTenant({
    name: 'Test Company'
  });
} catch (error) {
  if (error.name === 'DomainlyError') {
    console.error('API Error:', {
      message: error.message,
      status: error.status,
      code: error.code,
      details: error.details
    });
    
    // Check if error is retryable
    if (ErrorUtils.isRetryableError(error)) {
      console.log('This error can be retried');
    }
    
    // Get user-friendly error message
    const readableError = ErrorUtils.getReadableError(error);
    console.log('User message:', readableError);
  } else {
    console.error('Unexpected error:', error);
  }
}

🔗 Webhook Events

Configure webhooks to receive real-time notifications:

Available Events

  • Tenant Events: tenant.created, tenant.updated, tenant.deleted
  • Subdomain Events: subdomain.created, subdomain.updated, subdomain.deleted
  • Domain Events: domain.created, domain.updated, domain.deleted, domain.verified, domain.failed
  • SSL Events: ssl.provisioned, ssl.expired, ssl.renewed

Webhook Payload Example

{
  "event": "domain.verified",
  "timestamp": "2024-01-15T12:00:00Z",
  "projectId": "project-789",
  "domainId": "domain-123",
  "data": {
    "customDomain": "app.customer.com",
    "status": "verified",
    "message": "Domain verification completed successfully",
    "timestamp": "2024-01-15T12:00:00Z"
  }
}

For webhook tests, the payload will include:

{
  "event": "webhook.test",
  "projectId": "your-project-id",
  "domainId": "test-domain-id",
  "data": {
    "customDomain": "test.example.com",
    "status": "verified",
    "message": "This is a test webhook from Domainly",
    "timestamp": "2024-01-15T12:00:00Z"
  },
  "timestamp": "2024-01-15T12:00:00Z"
}

Webhook Event Monitoring

import { DomainlyClient, WebhookUtils } from 'domainly-sdk';

class WebhookMonitor {
  private client: DomainlyClient;

  constructor(apiKey: string) {
    this.client = new DomainlyClient({ apiKey });
  }

  async checkWebhookHealth() {
    try {
      // Test webhook connectivity
      const testResult = await this.client.testWebhook({
        webhookUrl: 'https://your-app.com/webhooks/domainly'
      });
      
      if (testResult.data.success) {
        console.log('✅ Webhook endpoint is healthy');
      }
    } catch (error) {
      console.error('❌ Webhook test failed:', error.message);
    }
  }

  async getFailedWebhooks() {
    const logs = await this.client.getWebhookLogs({ limit: 100 });
    
    const failedEvents = logs.data.logs.filter(log => log.status === 'FAILED');
    
    console.log(`Found ${failedEvents.length} failed webhook deliveries`);
    
    return failedEvents;
  }

  async retryFailedWebhooks() {
    const failedEvents = await this.getFailedWebhooks();
    
    for (const event of failedEvents) {
      try {
        await this.client.retryWebhook(event.id);
        console.log(`✅ Retried webhook ${event.id}`);
      } catch (error) {
        console.error(`❌ Failed to retry webhook ${event.id}:`, error.message);
      }
    }
  }
}

// Usage
const monitor = new WebhookMonitor('dom_your_api_key');
await monitor.checkWebhookHealth();
await monitor.retryFailedWebhooks();

Webhook Security

Verify webhook signatures using the built-in utility:

import { WebhookUtils } from 'domainly-sdk';

// In your webhook endpoint
app.post('/webhooks/domainly', express.raw({ type: 'application/json' }), (req, res) => {
  const payload = req.body.toString();
  const signature = req.headers['x-webhook-signature'];
  const secret = process.env.DOMAINLY_WEBHOOK_SECRET;

  // Verify the webhook signature
  if (!WebhookUtils.verifySignature(payload, signature, secret)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Parse the webhook payload
  const webhookData = WebhookUtils.parsePayload(payload);
  if (!webhookData) {
    return res.status(400).json({ error: 'Invalid payload' });
  }

  // Process the webhook
  console.log('Received webhook:', webhookData.event);
  res.json({ success: true });
});

🌍 Real-world Examples

SaaS Multi-tenant Setup

import { DomainlyClient, DomainlyUtils } from 'domainly-sdk';

class SaaSIntegration {
  private domainly: DomainlyClient;
  private utils: DomainlyUtils;

  constructor(apiKey: string) {
    this.domainly = new DomainlyClient({ apiKey });
    this.utils = new DomainlyUtils(this.domainly);
  }

  async onboardCustomer(customerData: {
    name: string;
    subdomain: string;
    customDomain?: string;
  }) {
    try {
      // Create tenant with subdomain
      const tenant = await this.domainly.createTenant({
        name: customerData.name,
        createSubdomain: true,
        subdomainName: customerData.subdomain,
        metadata: { 
          onboardedAt: new Date().toISOString(),
          plan: 'starter'
        }
      });

      let domain = null;
      
      // Add custom domain if provided
      if (customerData.customDomain) {
        domain = await this.domainly.createDomain({
          customDomain: customerData.customDomain,
          subdomain: customerData.subdomain,
          tenantId: tenant.data.id,
          verificationMethod: 'cname'
        });

        // Wait for verification
        const verifiedDomain = await this.utils.waitForDomainVerification(
          domain.data.id,
          { timeout: 300000 }
        );
        
        console.log('Domain verified:', verifiedDomain.customDomain);
      }

      return {
        success: true,
        tenant: tenant.data,
        domain: domain?.data,
        accessUrl: domain?.data?.customDomain || `${customerData.subdomain}.your-app.com`
      };
    } catch (error) {
      console.error('Customer onboarding failed:', error);
      return { success: false, error: error.message };
    }
  }

  async getCustomerSummary() {
    const summary = await this.utils.getResourcesSummary();
    return summary;
  }
}

// Usage
const saas = new SaaSIntegration('dom_your_api_key');

const result = await saas.onboardCustomer({
  name: 'Acme Corporation',
  subdomain: 'acme',
  customDomain: 'app.acme.com'
});

console.log('Onboarding result:', result);

Domain Verification Flow

async function setupCustomDomain(customDomain: string, subdomain: string) {
  const client = new DomainlyClient({
    apiKey: process.env.DOMAINLY_API_KEY!
  });

  // 1. Create domain
  const domain = await client.createDomain({
    customDomain,
    subdomain,
    verificationMethod: 'cname'
  });

  // 2. Get verification records
  const records = await client.getCNAMERecords(domain.data.id);
  
  console.log('Add these DNS records:');
  records.data.forEach(record => {
    console.log(`${record.name} CNAME ${record.value}`);
  });

  // 3. Poll for verification
  console.log('Waiting for DNS propagation...');
  
  const utils = new DomainlyUtils(client);
  try {
    const verifiedDomain = await utils.waitForDomainVerification(domain.data.id, {
      maxAttempts: 60, // 5 minutes with 5-second intervals
      pollInterval: 5000
    });
    
    console.log('✅ Domain verified successfully!');
    console.log('🔒 SSL certificate will be provisioned automatically');
    
    return verifiedDomain;
  } catch (error) {
    console.error('❌ Domain verification failed:', error.message);
    throw error;
  }
}

📈 Rate Limiting

The SDK automatically handles rate limiting with exponential backoff:

  • General API calls: 1000 per hour
  • Tenant creation: 100 per hour
  • Domain creation: 20 per hour
  • DNS verification: 50 per hour

Rate limit exceeded responses (429) are automatically retried.

🔒 Security Best Practices

  1. Store API keys securely - Never commit API keys to version control
  2. Use environment variables - Store keys in environment variables or secure vaults
  3. Verify webhook signatures - Always verify webhook payloads using HMAC-SHA256
  4. Use HTTPS - Only configure HTTPS webhook endpoints
  5. Rotate API keys - Regularly rotate your API keys

🤝 Contributing

We welcome contributions! Please see our Contributing Guide for details.

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🆘 Support

🔗 Links


Made with ❤️ by the Domainly Team