Adapters Deep Dive

Universal SDK Configuration (Framework-Agnostic)

This page provides a comprehensive explanation of adapters in our React marketplace.

Remember: This is our marketplace's pattern. Your marketplace can organize adapters differently (Level 1 Personalization).


What Are Adapters?

Adapters configure ONE SDK or technology in a framework-agnostic way.

Key Principle

Adapters must be framework-agnostic:

  • ❌ No Next.js specific code
  • ❌ No Remix specific code
  • ❌ No framework imports
  • βœ… Universal SDK configuration
  • βœ… Can be imported by connectors
  • βœ… Works with ANY framework

Why Do We Need Adapters?

The Problem Without Adapters

Without adapters, every connector duplicates SDK configuration:

// ❌ BAD: Duplication everywhere
// connectors/auth/better-auth-nextjs/config.ts
export const auth = betterAuth({
  database: { provider: 'postgresql', url: process.env.DATABASE_URL },
  emailAndPassword: { enabled: true },
  // ... 50 lines of config
});
 
// connectors/auth/better-auth-remix/config.ts
export const auth = betterAuth({
  database: { provider: 'postgresql', url: process.env.DATABASE_URL },
  emailAndPassword: { enabled: true },
  // ... same 50 lines duplicated!
});

Problems:

  • πŸ”΄ Configuration duplicated across connectors
  • πŸ”΄ Update one place, forget the other
  • πŸ”΄ Inconsistencies creep in
  • πŸ”΄ Single SDK instance not guaranteed

The Solution: Adapters

With adapters, configuration is centralized:

// βœ… GOOD: Single source of truth
// adapters/auth/better-auth/config.ts
export const auth = betterAuth({
  database: { provider: 'postgresql', url: process.env.DATABASE_URL },
  emailAndPassword: { enabled: true },
  // ... 50 lines of config (ONCE!)
});
 
// connectors/auth/better-auth-nextjs/config.ts
import { auth as baseAuth } from '@/lib/auth/better-auth';
export const auth = betterAuth({
  ...baseAuth.options,  // ← Import base config
  plugins: [nextCookies()],  // ← Add Next.js specific
});
 
// connectors/auth/better-auth-remix/config.ts
import { auth as baseAuth } from '@/lib/auth/better-auth';
export const auth = betterAuth({
  ...baseAuth.options,  // ← Same base config
  plugins: [remixCookies()],  // ← Add Remix specific
});

Benefits:

  • βœ… Configuration defined once
  • βœ… Connectors import and extend
  • βœ… Single SDK instance
  • βœ… Consistency guaranteed

Adapter Structure

Minimal Adapter

adapters/[category]/[technology]/
β”œβ”€β”€ adapter.json       (Metadata)
β”œβ”€β”€ blueprint.ts       (Generation logic)
β”œβ”€β”€ templates/         (Code templates)
β”‚   β”œβ”€β”€ config.ts.tpl
β”‚   β”œβ”€β”€ client.ts.tpl
β”‚   └── types.ts.tpl
└── README.md          (Documentation)

Example: Better Auth Adapter

adapters/auth/better-auth/
β”œβ”€β”€ adapter.json
β”œβ”€β”€ blueprint.ts
β”œβ”€β”€ templates/
β”‚   β”œβ”€β”€ better-auth-config.ts.tpl     (Universal auth config)
β”‚   β”œβ”€β”€ better-auth-client.ts.tpl     (Auth client)
β”‚   └── types.ts.tpl                   (Core types)
└── README.md

What it deploys:

Generated in user's project:
src/lib/auth/
β”œβ”€β”€ better-auth-config.ts    (Universal config)
β”œβ”€β”€ better-auth-client.ts    (Client instance)
└── types.ts                  (Auth types)

Adapter Metadata (adapter.json)

{
  "name": "Better Auth",
  "id": "adapters/auth/better-auth",
  "type": "adapter",
  "category": "auth",
  "version": "1.0.0",
  "description": "Better Auth universal authentication configuration",
  
  "provides": [
    {
      "name": "better-auth",
      "version": "1.0.0",
      "description": "Authentication SDK"
    }
  ],
  
  "capabilities": {
    "provides": ["auth", "session-management", "better-auth"],
    "requires": []
  }
}

Key Fields:

  • id: Unique identifier (full path)
  • category: Grouping (auth, payment, ai, email, etc.)
  • provides: Packages installed
  • capabilities.provides: What this adapter enables
  • capabilities.requires: Dependencies (usually empty for adapters)

Adapter Blueprint (blueprint.ts)

// adapters/auth/better-auth/blueprint.ts
import { defineBlueprint } from '@architech/core';
 
export default defineBlueprint({
  actions: [
    // 1. Install Better Auth package
    {
      action: 'install_packages',
      packages: [
        'better-auth@^1.0.0',
        '@better-auth/drizzle@^1.0.0',
      ],
    },
    
    // 2. Deploy universal config
    {
      action: 'deploy_template',
      template: 'better-auth-config.ts.tpl',
      target: '{{paths.auth_config}}/better-auth-config.ts',
      priority: 1,  // Base config (connectors can extend)
    },
    
    // 3. Deploy client
    {
      action: 'deploy_template',
      template: 'better-auth-client.ts.tpl',
      target: '{{paths.auth_config}}/better-auth-client.ts',
      priority: 1,
    },
    
    // 4. Deploy types
    {
      action: 'deploy_template',
      template: 'types.ts.tpl',
      target: '{{paths.auth_config}}/types.ts',
      priority: 1,
    },
  ],
});

Key Concepts:

  • priority: 1: Base configuration (connectors can override with priority 2)
  • {{paths.auth_config}}: Deployment path variable
  • Templates: .tpl files with placeholders

Adapter Templates

Example: Universal Config Template

// adapters/auth/better-auth/templates/better-auth-config.ts.tpl
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "@better-auth/drizzle";
import { db } from "@/db/client";
 
/**
 * Better Auth Configuration (Universal)
 * 
 * This configuration works with any framework.
 * Connectors extend this with framework-specific plugins.
 */
export const auth = betterAuth({
  // Database adapter (framework-agnostic)
  database: drizzleAdapter(db, {
    provider: "pg",
  }),
  
  // Email and password authentication
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: false,
  },
  
  // Session configuration
  session: {
    expiresIn: 60 * 60 * 24 * 7, // 7 days
    updateAge: 60 * 60 * 24, // 1 day
  },
  
  // User configuration
  user: {
    additionalFields: {
      role: {
        type: "string",
        required: false,
        defaultValue: "user",
      },
    },
  },
});
 
export type Auth = typeof auth;

Note: No framework-specific code! Works with Next.js, Remix, etc.


Real Examples

Example 1: Auth Adapter

Structure:

adapters/auth/better-auth/
β”œβ”€β”€ adapter.json
β”œβ”€β”€ blueprint.ts
└── templates/
    β”œβ”€β”€ better-auth-config.ts.tpl    (Universal auth config)
    β”œβ”€β”€ better-auth-client.ts.tpl    (Client instance)
    └── types.ts.tpl                  (Auth types)

What it provides:

  • βœ… Better Auth SDK configuration
  • βœ… Database adapter setup
  • βœ… Session configuration
  • βœ… Auth client instance
  • βœ… Core auth types

What connectors can add:

  • Next.js cookies plugin
  • Remix cookies plugin
  • API routes
  • Middleware

Example 2: Payment Adapter

Structure:

adapters/payment/stripe/
β”œβ”€β”€ adapter.json
β”œβ”€β”€ blueprint.ts
└── templates/
    β”œβ”€β”€ stripe-config.ts.tpl     (Stripe client config)
    └── types.ts.tpl              (Payment types)

What it provides:

// stripe-config.ts.tpl
import Stripe from 'stripe';
 
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-06-20',
  typescript: true,
});
 
export const STRIPE_CONFIG = {
  currency: 'usd',
  successUrl: `${process.env.NEXT_PUBLIC_URL}/payment/success`,
  cancelUrl: `${process.env.NEXT_PUBLIC_URL}/payment/cancel`,
};

Framework-agnostic: No Next.js, no Remix, just Stripe client.


Example 3: AI Adapter

Structure:

adapters/ai/vercel-ai-sdk/
β”œβ”€β”€ adapter.json
β”œβ”€β”€ blueprint.ts
└── templates/
    β”œβ”€β”€ ai-config.ts.tpl     (AI SDK config)
    └── types.ts.tpl          (AI types)

What it provides:

// ai-config.ts.tpl
import { createOpenAI } from '@ai-sdk/openai';
import { createAnthropic } from '@ai-sdk/anthropic';
 
export const openai = createOpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});
 
export const anthropic = createAnthropic({
  apiKey: process.env.ANTHROPIC_API_KEY,
});
 
export const AI_CONFIG = {
  defaultProvider: 'openai',
  defaultModel: 'gpt-4-turbo',
  temperature: 0.7,
  maxTokens: 2000,
};

When to Create an Adapter

βœ… Create an Adapter When:

1. Configuring a New SDK/Library

// βœ… Good: Universal SDK config
adapters/email/resend/
  β†’ Resend client configuration
  β†’ Email templates
  β†’ Universal email types

2. Configuration Can Be Shared Across Frameworks

// βœ… Good: Works with Next.js, Remix, etc.
adapters/database/drizzle/
  β†’ Drizzle ORM config
  β†’ Schema setup
  β†’ Database client

3. You Want Single Source of Truth

// βœ… Good: One config, multiple connectors
adapters/monitoring/sentry/
  β†’ Sentry DSN config
  β†’ Error tracking setup
  β†’ Universal Sentry client
 
connectors/monitoring/sentry-nextjs/ β†’ Imports adapter config
connectors/monitoring/sentry-remix/  β†’ Imports adapter config

❌ Don't Create an Adapter When:

1. Configuration Is Framework-Specific

// ❌ Bad: Next.js specific = connector, not adapter
adapters/framework/nextjs/  
  β†’ This should be framework/nextjs, not an "adapter"

2. No SDK to Configure

// ❌ Bad: Just utilities, no SDK
adapters/utils/lodash/
  β†’ Just install lodash directly

3. It's Application-Specific Logic

// ❌ Bad: Business logic = feature, not adapter
adapters/teams/custom-teams/
  β†’ This should be a feature, not adapter

Adapter vs Connector

Key Differences

AspectAdapterConnector
PurposeConfigure SDK universallyIntegrate SDK + Framework
Framework-Specific?❌ Noβœ… Yes
RequiresNothing (or other adapters)One or more adapters
ProvidesSDK configurationFramework integration
Exampleadapters/auth/better-auth/connectors/auth/better-auth-nextjs/

The Flow

1. Adapter (Universal)
   ↓
2. Connector (Framework-Specific)
   ↓
3. Feature (Business Logic)

Example:

adapters/auth/better-auth/
  β†’ Universal Better Auth config
  
connectors/auth/better-auth-nextjs/
  β†’ Imports adapter config
  β†’ Adds Next.js plugins
  β†’ Creates API routes
  β†’ Provides SDK-native hooks
  
features/auth/
  β†’ Uses hooks from connector
  β†’ Provides UI components

Best Practices

1. Keep Adapters Framework-Agnostic

βœ… Good:

// adapters/auth/better-auth/config.ts.tpl
export const auth = betterAuth({
  database: drizzleAdapter(db),  // βœ… Universal
  emailAndPassword: { enabled: true },
});

❌ Bad:

// adapters/auth/better-auth/config.ts.tpl
import { cookies } from 'next/headers';  // ❌ Next.js import!
 
export const auth = betterAuth({
  plugins: [nextCookies()],  // ❌ Framework-specific!
});

2. Use Deployment Paths

βœ… Good:

{
  action: 'deploy_template',
  target: '{{paths.auth_config}}/config.ts',  // βœ… Path variable
}

❌ Bad:

{
  action: 'deploy_template',
  target: 'src/lib/auth/config.ts',  // ❌ Hardcoded path
}

3. Set Priority Appropriately

βœ… Good:

{
  action: 'deploy_template',
  target: '{{paths.auth_config}}/config.ts',
  priority: 1,  // βœ… Base config (connectors can override)
}

4. Export SDK Instance

βœ… Good:

// Export SDK instance for connectors to import
export const stripe = new Stripe(/* ... */);
export const auth = betterAuth(/* ... */);
export const openai = createOpenAI(/* ... */);

Common Patterns

Pattern 1: SDK Client Adapter

Purpose: Provide configured SDK client

// adapters/payment/stripe/templates/config.ts.tpl
import Stripe from 'stripe';
 
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-06-20',
  typescript: true,
});

Usage:

// connectors/payment/stripe-nextjs/server.ts
import { stripe } from '@/lib/payment/stripe';  // Import from adapter
 
export async function createCheckout() {
  return stripe.checkout.sessions.create({ /* ... */ });
}

Pattern 2: Configuration Object Adapter

Purpose: Provide configuration constants

// adapters/ai/vercel-ai-sdk/templates/config.ts.tpl
export const AI_CONFIG = {
  defaultProvider: 'openai',
  defaultModel: 'gpt-4-turbo',
  temperature: 0.7,
  maxTokens: 2000,
} as const;

Pattern 3: Factory Function Adapter

Purpose: Provide factory for creating instances

// adapters/database/drizzle/templates/client.ts.tpl
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
 
const connectionString = process.env.DATABASE_URL!;
const client = postgres(connectionString);
 
export const db = drizzle(client);

Summary

Key Takeaways

  1. Adapters are framework-agnostic

    • No Next.js, no Remix, no framework imports
    • Universal SDK configuration
  2. Adapters are imported by connectors

    • Connectors extend adapter config
    • Single source of truth
  3. Adapters provide base configuration

    • Priority 1 (connectors can override with priority 2)
    • SDK instances and config constants
  4. Adapters enable reusability

    • One adapter, multiple connectors
    • Consistency across frameworks

Decision Tree

Do I need to configure an SDK?
β”œβ”€ YES β†’ Is the config framework-specific?
β”‚  β”œβ”€ NO  β†’ βœ… CREATE ADAPTER
β”‚  └─ YES β†’ ❌ CREATE CONNECTOR (not adapter)
β”‚
└─ NO β†’ Is it business logic?
   β”œβ”€ YES β†’ ❌ CREATE FEATURE (not adapter)
   └─ NO  β†’ ❌ JUST INSTALL PACKAGE

Next Steps


Remember: Adapters provide universal SDK configuration. Connectors add framework-specific integration!