Adapters
The Booking Kit uses the adapter pattern to abstract external dependencies. Each adapter is a TypeScript interface — implement it for your preferred provider.
Available adapters
Section titled “Available adapters”AuthAdapter
Section titled “AuthAdapter”interface AuthAdapter { getSession(request: Request): Promise<AuthSession | null>; getUser(userId: string): Promise<AuthUser | null>;}Default: NextAuth.js 5.x Alternatives: Supabase Auth, Clerk, Lucia, custom JWT
EmailAdapter
Section titled “EmailAdapter”interface EmailAdapter { sendEmail(options: SendEmailOptions): Promise<EmailResult>;}
interface SendEmailOptions { to: string; subject: string; html: string; text?: string; from?: string; replyTo?: string; attachments?: EmailAttachment[];}Default: Resend Alternatives: SendGrid, AWS SES, Postmark, Nodemailer
CalendarAdapter
Section titled “CalendarAdapter”interface CalendarAdapter { createEvent(options: CalendarEventOptions): Promise<CalendarEventResult>; updateEvent(eventId: string, options: CalendarEventOptions): Promise<CalendarEventResult>; deleteEvent(eventId: string): Promise<void>; getConflicts(start: Date, end: Date): Promise<CalendarConflict[]>;}Default: Google Calendar (OAuth) Alternatives: Microsoft Outlook, CalDAV
JobAdapter
Section titled “JobAdapter”interface JobAdapter { enqueue(jobName: string, payload: unknown, options?: { delay?: number }): Promise<void>; scheduleAt(jobName: string, payload: unknown, runAt: Date): Promise<void>;}Default: Inngest 3.x Alternatives: Trigger.dev, BullMQ, Vercel Cron, pg-boss
PaymentAdapter
Section titled “PaymentAdapter”interface PaymentAdapter { createPaymentIntent(options: CreatePaymentIntentOptions): Promise<CreatePaymentIntentResult>; createSetupIntent(options: CreateSetupIntentOptions): Promise<CreateSetupIntentResult>; capturePayment(paymentIntentId: string): Promise<CaptureResult>; refundPayment(paymentIntentId: string, amountCents?: number): Promise<RefundResult>;}Default: Stripe Alternatives: Implement for your payment provider
StorageAdapter
Section titled “StorageAdapter”interface StorageAdapter { encrypt(plaintext: string): Promise<string>; decrypt(ciphertext: string): Promise<string>;}Default: Env var encryption key (AES-256) Alternatives: HashiCorp Vault, AWS KMS, Azure Key Vault
SmsAdapter
Section titled “SmsAdapter”interface SmsAdapter { sendSms(options: SendSmsOptions): Promise<SmsResult>;}Default: Not implemented (optional) Alternatives: Twilio, MessageBird, Vonage
Database Adapters
Section titled “Database Adapters”While The Booking Kit is built for PostgreSQL, it provides specialized adapters for other database environments.
Cloudflare D1 / SQLite (@thebookingkit/d1)
Section titled “Cloudflare D1 / SQLite (@thebookingkit/d1)”Cloudflare D1 requires special handling for date storage and concurrency. This package provides:
- Canonical Date Encoding: SQLite has no native timestamp type.
D1DateCodecensures all dates are stored as sortable UTC-Z strings. - Advisory Locking: Since SQLite lacks
EXCLUDEconstraints,D1BookingLockprovides application-level mutexes to prevent double-booking. - Schedule Adapters: Utilities to intersect personal and location schedules into RRULE windows.
import { D1DateCodec, D1BookingLock } from "@thebookingkit/d1";
// Ensure a slot is available and book it atomicallyconst lock = new D1BookingLock(db);await lock.withLock(`provider:${id}`, async () => { // Read-check-write logic here});Implementing an adapter
Section titled “Implementing an adapter”Email adapter with SendGrid
Section titled “Email adapter with SendGrid”import sgMail from "@sendgrid/mail";import type { EmailAdapter } from "@thebookingkit/core";
export const sendGridEmailAdapter: EmailAdapter = { async sendEmail(options) { sgMail.setApiKey(process.env.SENDGRID_API_KEY!);
try { const [response] = await sgMail.send({ to: options.to, from: options.from || "bookings@example.com", subject: options.subject, html: options.html, text: options.text, replyTo: options.replyTo, });
return { messageId: response.headers["x-message-id"] as string, status: "sent" as const, }; } catch (error) { console.error("SendGrid error:", error); return { status: "failed" as const, error: error instanceof Error ? error.message : "Unknown error", }; } },};Auth adapter with Supabase
Section titled “Auth adapter with Supabase”import { createClient } from "@supabase/supabase-js";import type { AuthAdapter, AuthSession, AuthUser } from "@thebookingkit/core";
const supabase = createClient( process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!);
export const supabaseAuthAdapter: AuthAdapter = { async getSession(request: Request): Promise<AuthSession | null> { const token = request.headers.get("authorization")?.split("Bearer ")[1]; if (!token) return null;
const { data, error } = await supabase.auth.getUser(token); if (error || !data.user) return null;
return { userId: data.user.id, email: data.user.email!, role: "user", }; },
async getUser(userId: string): Promise<AuthUser | null> { const { data, error } = await supabase.auth.admin.getUserById(userId); if (error || !data.user) return null;
return { id: data.user.id, email: data.user.email!, name: data.user.user_metadata?.name, }; },};Job adapter with Trigger.dev
Section titled “Job adapter with Trigger.dev”import { TriggerClient, eventTrigger } from "@trigger.dev/sdk/v3";import type { JobAdapter } from "@thebookingkit/core";
const client = new TriggerClient({ id: "booking-kit" });
export const triggerJobAdapter: JobAdapter = { async enqueue(jobName: string, payload: unknown, options?: { delay?: number }) { await client.sendEvent({ name: jobName, payload, timestamp: new Date(Date.now() + (options?.delay || 0)), }); },
async scheduleAt(jobName: string, payload: unknown, runAt: Date) { await client.sendEvent({ name: jobName, payload, timestamp: runAt, }); },};Using adapters
Section titled “Using adapters”Pass adapters to the functions that need them:
import { sendConfirmationEmail } from "@thebookingkit/server";import { sendGridEmailAdapter } from "@/adapters/email";
// Email will be sent via SendGridawait sendConfirmationEmail( bookingPayload, sendGridEmailAdapter);Adapter composition for testing
Section titled “Adapter composition for testing”import type { EmailAdapter } from "@thebookingkit/core";
// Mock adapter for testingconst mockEmailAdapter: EmailAdapter = { async sendEmail(options) { console.log(`[MOCK] Sending email to ${options.to}: ${options.subject}`); return { messageId: "mock-" + Date.now(), status: "sent" as const, }; },};
// Use in testsdescribe("Booking confirmation", () => { it("sends confirmation email", async () => { await sendConfirmationEmail(bookingData, mockEmailAdapter); // Email was logged to console for verification });});Adapters are injected at the call site, making them easy to test and swap between providers.