1.1.1 • Published 6 months ago

@daveyplate/better-auth-instantdb v1.1.1

Weekly downloads
-
License
MIT
Repository
-
Last release
6 months ago

Better Auth InstantDB Adapter

A seamless integration between Better Auth and InstantDB that allows you to use InstantDB as your authentication database.

Better Auth UI Integration

  • Own Your Auth

š• @daveycodez

ā˜•ļø Buy me a coffee

Installation

pnpm add @daveyplate/better-auth-instantdb@latest

Features

  • šŸ” Complete Authentication: Leverage Better Auth's authentication features with InstantDB as your database
  • šŸ”„ Session Sync: Automatically synchronize auth sessions between Better Auth and InstantDB
  • šŸ› ļø Customizable: Configure the adapter to match your specific needs
  • 🧩 Type-Safe: Fully typed with TypeScript for improved developer experience

Usage

Basic Setup

First you need to add the InstantDB Adapter to your Better Auth config.

auth.ts

import { betterAuth } from 'better-auth'
import { instantDBAdapter } from '@daveyplate/better-auth-instantdb'
import { init } from "@instantdb/admin";
import schema from "instant.schema";

// Create InstantDB admin client
const adminDb = init({
    appId: process.env.INSTANT_APP_ID,
    apiKey: process.env.INSTANT_API_KEY
    schema
});

// Create Better Auth instance with InstantDB adapter
export const auth = betterAuth({
    database: instantDBAdapter({
        db: adminDb,
        usePlural: true, // Optional: set to true if your schema uses plural table names
        debugLogs: false  // Optional: set to true to see detailed logs
    }),
    // Other Better Auth configuration options
    emailAndPassword: { enabled: true }
})

Client-Side Usage

Synchronize authentication state between Better Auth and InstantDB:

providers.tsx

"use client"

import { useSession } from '@/lib/auth-client'
import { init } from '@instantdb/react'
import { useInstantAuth } from '@daveyplate/better-auth-instantdb'

// Initialize InstantDB client
const db = init({ 
    appId: process.env.NEXT_PUBLIC_INSTANT_APP_ID
})

export function Providers({ children }) {
    const { data: sessionData, isPending } = useSession()
    
    // Set up InstantDB auth sync with Better Auth
    useInstantAuth({ 
        db, 
        sessionData,
        isPending
    })
    
    return (
        // Your application code
        {children}
    )
}

InstantDB Schema and Permissions Setup

āš ļø Important: You must manually create and configure the InstantDB schema and permissions files for this adapter to work correctly.

1. Create Schema File

Create an instant.schema.ts file with the required entities for Better Auth:

instant.schema.ts

import { i } from "@instantdb/react";

const _schema = i.schema({
  entities: {
    // System entities
    $files: i.entity({
      path: i.string().unique().indexed(),
      url: i.any(),
    }),
    $users: i.entity({
      email: i.string().unique().indexed(),
    }),
    // Authentication entities
    users: i.entity({
      createdAt: i.date(),
      email: i.string().unique(),
      emailVerified: i.boolean(),
      image: i.string(),
      name: i.string(),
      updatedAt: i.date(),
    }),
    sessions: i.entity({
      createdAt: i.date(),
      expiresAt: i.date().indexed(),
      ipAddress: i.string(),
      token: i.string(),
      updatedAt: i.date(),
      userAgent: i.string(),
      userId: i.string(),
    }),
    accounts: i.entity({
      accessToken: i.string(),
      accessTokenExpiresAt: i.date(),
      accountId: i.string(),
      createdAt: i.date(),
      idToken: i.string(),
      password: i.string(),
      providerId: i.string(),
      refreshToken: i.string(),
      refreshTokenExpiresAt: i.date(),
      scope: i.string(),
      updatedAt: i.date(),
      userId: i.string().indexed(),
    }),
    verifications: i.entity({
      createdAt: i.date().indexed(),
      expiresAt: i.date().indexed(),
      identifier: i.string(),
      updatedAt: i.date(),
      value: i.string(),
    }),
    // Optional entities for additional features (public profile example)
    profiles: i.entity({
      createdAt: i.date(),
      image: i.string(),
      name: i.string(),
      updatedAt: i.date(),
    }),
  },
  links: {
    // Required links for auth
    users$user: {
      forward: {
        on: "users",
        has: "one",
        label: "$user",
        onDelete: "cascade",
      },
      reverse: {
        on: "$users",
        has: "one",
        label: "user",
      },
    },
    sessionsUser: {
      forward: {
        on: "sessions",
        has: "one",
        label: "user",
        onDelete: "cascade",
      },
      reverse: {
        on: "users",
        has: "many",
        label: "sessions",
      },
    },
    accountsUser: {
      forward: {
        on: "accounts",
        has: "one",
        label: "user",
        onDelete: "cascade",
      },
      reverse: {
        on: "users",
        has: "many",
        label: "accounts",
      },
    },
    // Optional links (public profile example)
    profilesUser: {
      forward: {
        on: "profiles",
        has: "one",
        label: "user",
        onDelete: "cascade",
      },
      reverse: {
        on: "users",
        has: "one",
        label: "profile",
      },
    },
    // Add your custom links here
  },
});

// This helps TypeScript display nicer intellisense
type _AppSchema = typeof _schema;
interface AppSchema extends _AppSchema {}
const schema: AppSchema = _schema;

export type { AppSchema };
export default schema;

2. Create Permissions File

Create an instant.perms.ts file to secure your schema:

// instant.perms.ts
import type { InstantRules } from "@instantdb/react";

const rules = {
  // Prevent creation of new attributes without explicit schema changes
  attrs: {
    allow: {
      $default: "false",
    },
  },
  // Auth entities permissions
  users: {
    bind: ["isOwner", "auth.id != null && auth.id == data.id"],
    allow: {
      view: "isOwner",
      create: "false",
      delete: "false",
      update: "isOwner && (newData.email == data.email) && (newData.emailVerified == data.emailVerified) && (newData.createdAt == data.createdAt)",
    },
  },
  accounts: {
    bind: ["isOwner", "auth.id != null && auth.id == data.userId"],
    allow: {
      view: "isOwner",
      create: "false",
      delete: "false",
      update: "false",
    },
  },
  sessions: {
    bind: ["isOwner", "auth.id != null && auth.id == data.userId"],
    allow: {
      view: "isOwner",
      create: "false",
      delete: "false",
      update: "false",
    },
  },
  verifications: {
    allow: {
      $default: "false"
    }
  },
  // Optional permissions (public profile example)
  profiles: {
    bind: ["isOwner", "auth.id != null && auth.id == data.id"],
    allow: {
      view: "true",
      create: "false",
      delete: "false",
      update: "isOwner",
    },
  },
  // Add your custom entity permissions here
} satisfies InstantRules;

export default rules;

3. Push Schema and Permissions to InstantDB

After creating these files, use the InstantDB CLI to push them to your app:

# Push schema
npx instant-cli@latest push schema

# Push permissions
npx instant-cli@latest push perms

4. Initialize InstantDB with Your Schema

Update your client-side InstantDB initialization to use your schema:

/database/instant.ts

import { init } from "@instantdb/react"
import schema from "../../instant.schema"

export const db = init({
    appId: process.env.NEXT_PUBLIC_INSTANT_APP_ID,
    schema
})

API Reference

instantDBAdapter(options)

Creates an adapter that allows Better Auth to use InstantDB as its database.

Options

OptionTypeDefaultDescription
dbInstantAdminDatabase(required)An InstantDB admin client instance
usePluralbooleantrueSet to false if your schema uses singular table names
debugLogsbooleanfalseSet to true to enable detailed logging
transactionHooksPromise<TransactionChunk<any, any>[]>undefinedCustom hooks for create and update operations

useInstantAuth({ db, useSession })

A React hook that synchronizes authentication state between Better Auth and InstantDB.

Parameters

ParameterTypeDescription
dbInstantReactWebDatabaseAn InstantDB client instance
useSessionfunctionThe useSession hook from Better Auth

useInstantAuth({ db, sessionData, isPending })

An alternative form of the React hook that synchronizes authentication state between Better Auth and InstantDB.

Parameters

ParameterTypeDescription
dbInstantReactWebDatabaseAn InstantDB client instance
sessionData{ session: Session; user: User } \| nullSession data from Better Auth
isPendingbooleanWhether the session data is still loading

Advanced Usage

Custom Transaction Hooks

You can extend the adapter's behavior with custom transaction hooks:

Sync public profile with user entity

instantDBAdapter({
    db,
    usePlural: true,
    transactionHooks: {
        create: async ({ model, data }) => {
            if (model === "users") {
                const transactions = [
                    db.tx.profiles[data.id]
                        .update({
                            name: data.name,
                            image: data.image,
                            createdAt: Date.now(),
                            updatedAt: Date.now()
                        })
                        .link({ user: data.id })
                ]

                return transactions
            }
        },
        update: async ({ model, update, where }) => {
            if (model === "users") {
                const result = await db.query({ profiles: { $: { where: parseWhere(where) } } })

                return result.profiles.map((profile) =>
                    db.tx.profiles[profile.id].update({
                        name: update.name,
                        image: update.image,
                        updatedAt: Date.now()
                    })
                )
            }
        }
    }
})

License

MIT

1.1.1

6 months ago

1.1.0

6 months ago

1.0.4

6 months ago

1.0.3

6 months ago

1.0.2

6 months ago

1.0.1

6 months ago

1.0.0

6 months ago