Skip to main content
The @revstackhq/providers-registry package provides a runtime registry system for managing billing provider implementations. It handles lazy-loading, manifest extraction, and provider instantiation.

Registry Functions

registerProvider

Registers a provider loader function in the global registry. This is typically called during application initialization.
function registerProvider(slug: string, loader: ProviderLoader): void
slug
string
required
Unique provider identifier (e.g., "stripe", "paddle", "polar")
loader
ProviderLoader
required
Lazy loader function that returns the provider module
import { registerProvider } from "@revstackhq/providers-registry";

registerProvider("stripe", async () => {
  return await import("@revstackhq/provider-stripe");
});

registerProvider("paddle", async () => {
  return await import("@revstackhq/provider-paddle");
});

getProviderLoader

Retrieves a registered provider loader by slug.
function getProviderLoader(slug: string): ProviderLoader | undefined
slug
string
required
The provider slug to look up
Returns: ProviderLoader | undefined - The loader function if registered, otherwise undefined
import { getProviderLoader } from "@revstackhq/providers-registry";

const loader = getProviderLoader("stripe");
if (loader) {
  const module = await loader();
  console.log(module.manifest);
}

listRegisteredProviders

Returns an array of all registered provider slugs in alphabetical order.
function listRegisteredProviders(): string[]
Returns: string[] - Sorted array of provider slugs
import { listRegisteredProviders } from "@revstackhq/providers-registry";

const providers = listRegisteredProviders();
console.log("Available providers:", providers);
// Output: ["paddle", "polar", "stripe"]

loadManifest

Loads and extracts the manifest from a provider module.
async function loadManifest(
  slug: string,
  loader: ProviderLoader
): Promise<ProviderManifest | null>
slug
string
required
Provider slug (used for error messages)
loader
ProviderLoader
required
The provider loader function
Returns: ProviderManifest | null - The provider’s manifest, or null if loading failed
import { loadManifest } from "@revstackhq/providers-registry";

const loader = () => import("@revstackhq/provider-stripe");
const manifest = await loadManifest("stripe", loader);

if (manifest) {
  console.log(`${manifest.name} v${manifest.version}`);
  console.log("Capabilities:", manifest.capabilities);
}
Manifest validation: If a provider module doesn’t export a manifest constant, this function returns null and logs an error.

buildCatalog

Registers multiple providers at once and extracts their manifests. This is the recommended way to initialize the provider registry.
async function buildCatalog(
  config: Record<string, ProviderLoader>
): Promise<ProviderManifest[]>
config
Record<string, ProviderLoader>
required
Map of provider slugs to their loader functions
Returns: Promise<ProviderManifest[]> - Array of successfully loaded manifests
import { buildCatalog } from "@revstackhq/providers-registry";

const manifests = await buildCatalog({
  stripe: () => import("@revstackhq/provider-stripe"),
  paddle: () => import("@revstackhq/provider-paddle"),
  polar: () => import("@revstackhq/provider-polar"),
  paypal: () => import("@revstackhq/provider-paypal"),
});

console.log(`Loaded ${manifests.length} providers`);

// Display provider info
manifests.forEach(manifest => {
  console.log(`- ${manifest.name} (${manifest.slug})`);
  console.log(`  Version: ${manifest.version}`);
  console.log(`  Status: ${manifest.status}`);
});

ProviderFactory

Factory class for instantiating provider SDK instances.

create

Creates a provider instance by slug. The provider must be registered before calling this method.
static async create(slug: string): Promise<IProvider>
slug
string
required
The provider slug to instantiate
Returns: Promise<IProvider> - Fully initialized provider instance Throws: Error if the provider is not registered or fails to load
import { ProviderFactory } from "@revstackhq/providers-registry";

try {
  const stripe = await ProviderFactory.create("stripe");
  
  // Use the provider
  const result = await stripe.createCustomer(ctx, {
    email: "[email protected]",
    name: "John Doe",
  });
  
  console.log("Customer ID:", result.data);
} catch (error) {
  console.error("Failed to create provider:", error.message);
}
Registration required: ProviderFactory.create() will throw an error if the provider hasn’t been registered via registerProvider() or buildCatalog().

Types

ProviderLoader

Type definition for a provider lazy-loader function:
type ProviderLoader = () => Promise<ProviderModule>
The loader should return a dynamic import that resolves to a ProviderModule.

ProviderModule

The expected structure of a provider package:
DefaultProvider
new () => IProvider
required
The provider class constructor. Must implement IProvider
manifest
ProviderManifest
required
The provider’s manifest containing metadata and capabilities
interface ProviderModule {
  DefaultProvider: new () => IProvider;
  manifest: ProviderManifest;
}
Example provider package structure:
// @revstackhq/provider-stripe/src/index.ts
import { IProvider, ProviderManifest } from "@revstackhq/providers-core";

export const manifest: ProviderManifest = {
  slug: "stripe",
  name: "Stripe",
  version: "1.2.0",
  // ... other manifest fields
};

export class DefaultProvider implements IProvider {
  readonly manifest = manifest;
  
  // Implement IProvider methods...
}

Complete Example

Here’s a complete example of setting up and using the provider registry:
import {
  registerProvider,
  buildCatalog,
  listRegisteredProviders,
  ProviderFactory,
} from "@revstackhq/providers-registry";

// Option 1: Register providers individually
registerProvider("stripe", () => import("@revstackhq/provider-stripe"));
registerProvider("paddle", () => import("@revstackhq/provider-paddle"));

// Option 2: Register providers in bulk (recommended)
const manifests = await buildCatalog({
  stripe: () => import("@revstackhq/provider-stripe"),
  paddle: () => import("@revstackhq/provider-paddle"),
  polar: () => import("@revstackhq/provider-polar"),
});

// List all registered providers
const availableProviders = listRegisteredProviders();
console.log("Available providers:", availableProviders);

// Create provider instances
const stripe = await ProviderFactory.create("stripe");
const paddle = await ProviderFactory.create("paddle");

// Use the providers
const ctx = {
  logger: console,
  config: {
    apiKey: process.env.STRIPE_API_KEY,
  },
  metadata: {
    workspaceId: "ws_123",
  },
};

const customerResult = await stripe.createCustomer(ctx, {
  email: "[email protected]",
  name: "Jane Smith",
});

if (customerResult.status === "success") {
  console.log("Customer created:", customerResult.data);
}

Dynamic Provider Loading

The registry system supports dynamic provider loading at runtime, which is useful for:
  • Reducing bundle size: Only load providers that are actually used
  • Plugin systems: Allow users to install custom providers
  • Multi-tenant setups: Load different providers per workspace

Example: Dynamic Loading

import { registerProvider, ProviderFactory } from "@revstackhq/providers-registry";

// Function to dynamically register a provider at runtime
async function enableProvider(slug: string) {
  const loaderMap = {
    stripe: () => import("@revstackhq/provider-stripe"),
    paddle: () => import("@revstackhq/provider-paddle"),
    polar: () => import("@revstackhq/provider-polar"),
    // Add more as needed
  };

  const loader = loaderMap[slug];
  if (!loader) {
    throw new Error(`Unknown provider: ${slug}`);
  }

  registerProvider(slug, loader);
  console.log(`Provider '${slug}' registered successfully`);
}

// Later in your application
await enableProvider("stripe");
const stripe = await ProviderFactory.create("stripe");

Error Handling

The factory and registry functions provide detailed error messages:
import { ProviderFactory, listRegisteredProviders } from "@revstackhq/providers-registry";

try {
  const provider = await ProviderFactory.create("unknown-provider");
} catch (error) {
  console.error(error.message);
  // Output: Provider 'unknown-provider' is not registered in Revstack. 
  //         Available providers: [paddle, polar, stripe]
  
  // You can also check available providers programmatically
  const available = listRegisteredProviders();
  console.log("Available providers:", available);
}

Best Practices

Use buildCatalog for initialization: It’s more concise and performs batch loading efficiently.
Lazy loading benefits: Provider modules are only loaded when first used, reducing initial bundle size and startup time.
Module structure validation: Ensure your provider modules export both DefaultProvider (class) and manifest (object). Missing exports will cause runtime errors.

Use Cases

Multi-Provider Application

import { buildCatalog, ProviderFactory } from "@revstackhq/providers-registry";

// Initialize all providers
await buildCatalog({
  stripe: () => import("@revstackhq/provider-stripe"),
  paddle: () => import("@revstackhq/provider-paddle"),
});

// Function to get provider based on workspace config
async function getWorkspaceProvider(workspaceId: string) {
  const workspace = await db.workspaces.findById(workspaceId);
  return await ProviderFactory.create(workspace.billingProvider);
}

// Use in request handler
app.post("/api/customers", async (req, res) => {
  const provider = await getWorkspaceProvider(req.workspaceId);
  const result = await provider.createCustomer(ctx, req.body);
  res.json(result);
});

Provider Marketplace UI

import { buildCatalog } from "@revstackhq/providers-registry";

const manifests = await buildCatalog({
  stripe: () => import("@revstackhq/provider-stripe"),
  paddle: () => import("@revstackhq/provider-paddle"),
  polar: () => import("@revstackhq/provider-polar"),
});

// Display providers in UI
manifests.forEach(manifest => {
  renderProviderCard({
    name: manifest.name,
    description: manifest.description,
    icon: manifest.media.icon,
    status: manifest.status,
    categories: manifest.categories,
    pricing: manifest.pricing,
    capabilities: manifest.capabilities,
  });
});

Build docs developers (and LLMs) love