Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
7faf354
feat: d1 multi-tenancy
zpg6 Aug 23, 2025
83ad46b
fix: reuse helper function
zpg6 Aug 23, 2025
1013066
feat: example to demonstrate with
zpg6 Aug 23, 2025
c3b7d5e
feat: integrated into withCloudflare
zpg6 Aug 23, 2025
72a1ecb
chore: package tags
zpg6 Aug 23, 2025
8fe42ce
chore: migrate example
zpg6 Aug 23, 2025
f7b37bf
feat: organization mode validation
zpg6 Aug 23, 2025
11abad3
feat: workflow build file for new example
zpg6 Aug 23, 2025
160cdc5
feat: integrate organization hooks creating databases
zpg6 Aug 24, 2025
a1924d3
feat: integrate org hooks deleting databases
zpg6 Aug 24, 2025
a130521
feat: can split schema into main vs tenant
zpg6 Aug 25, 2025
2ada2a2
feat: separate schema into raw migrations
zpg6 Aug 26, 2025
e18c71b
feat: migrating schema after tenant db creation
zpg6 Aug 27, 2025
d408308
feat: create birthday in tenant db
zpg6 Aug 30, 2025
9e16750
fix: cli import
zpg6 Aug 30, 2025
fc8ccef
fix: cli settings
zpg6 Aug 30, 2025
9ec53b3
fix: cli prebuilds main package
zpg6 Aug 30, 2025
1f716f2
fix: preinstall deps
zpg6 Aug 30, 2025
3f2d8e1
fix: decouple cli
zpg6 Aug 30, 2025
8be6623
test: bypass database configuration
zpg6 Aug 30, 2025
f9d32f8
fix: less strict on database config in general
zpg6 Aug 30, 2025
ee2297d
chore: use latest adapter-router
zpg6 Aug 31, 2025
a758cd9
feat: configurable tenantId extraction
zpg6 Aug 31, 2025
c6fc325
fix: birthday date display
zpg6 Aug 31, 2025
5c33fa6
fix: birthday plugin tab
zpg6 Aug 31, 2025
f62018f
fix: birthday title / desc
zpg6 Aug 31, 2025
681d344
fix: organization display
zpg6 Aug 31, 2025
b7df242
feat: cli migrate:tenants
zpg6 Aug 31, 2025
c865e8a
fix: cli raw drizzle migration generation
zpg6 Aug 31, 2025
3ad5729
fix: simplify tenant migration tracking schema
zpg6 Aug 31, 2025
68ef78d
feat: split drizzle configuration
zpg6 Aug 31, 2025
975b7a8
fix: escape raw tenant schema
zpg6 Sep 1, 2025
7618a0b
fix: generate raw schema first run
zpg6 Sep 1, 2025
061656f
feat: filters out foreign key refs
zpg6 Sep 1, 2025
db357d6
fix: trailing commas after filter foreign keys
zpg6 Sep 1, 2025
716822e
feat: allow tenant routing to edit the data
zpg6 Sep 1, 2025
a8957af
fix: conflicting drizzle impl
zpg6 Sep 3, 2025
55aebc8
Merge branch 'main' into feat/d1-multi-tenancy
zpg6 Sep 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Build Example (OpenNextJS Org D1 Multi-Tenancy)

on:
push:
branches: [main]
paths:
- "package.json"
- "src/**"
- "tsconfig.json"
- "examples/opennextjs-org-d1-multi-tenancy/**"
- ".github/workflows/build-opennextjs-org-d1-multi-tenancy-example.yml" # This file
pull_request:
branches: [main]
paths:
- "package.json"
- "src/**"
- "tsconfig.json"
- "examples/opennextjs-org-d1-multi-tenancy/**"
- ".github/workflows/build-opennextjs-org-d1-multi-tenancy-example.yml" # This file
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.10.0
run_install: false

- name: Install root dependencies
run: pnpm install

- name: Build root package
run: pnpm build

- name: Install example dependencies
working-directory: examples/opennextjs-org-d1-multi-tenancy
run: pnpm install

- name: Build Example (OpenNextJS Org D1 Multi-Tenancy)
working-directory: examples/opennextjs-org-d1-multi-tenancy
run: pnpm build:cf
113 changes: 112 additions & 1 deletion cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ The migrate command automatically detects your database configuration from `wran
- **D1 databases**: Offers migration options (dev/remote)
- **Hyperdrive databases**: Shows informational message
- **Multiple databases**: Prompts you to choose which D1 database to migrate
- **Multi-tenancy**: Automatically detects and handles schema splitting for tenant databases

**Multi-tenancy workflow**:

```bash
# Apply tenant migrations to all tenant databases (same account)
CLOUDFLARE_D1_API_TOKEN=xxx CLOUDFLARE_ACCT_ID=yyy CLOUDFLARE_DATABASE_ID=zzz \
npx @better-auth-cloudflare/cli migrate:tenants
```

## Arguments

Expand Down Expand Up @@ -123,6 +132,15 @@ The migrate command automatically detects your database configuration from `wran
--migrate-target=<target> For migrate command: dev | remote | skip (default: skip)
```

### Multi-tenancy commands

```
migrate:tenants Apply migrations to all tenant databases
--auto-confirm Skip confirmation prompts (default: false)
--dry-run Preview what would be migrated without applying changes
--verbose Show detailed migration logs and debugging info
```

## Examples

Create a Hono app with D1 database:
Expand Down Expand Up @@ -182,9 +200,102 @@ Run migration workflow with non-interactive target:
npx @better-auth-cloudflare/cli migrate --migrate-target=dev
```

## Multi-Tenancy Workflow

The CLI provides comprehensive support for organization-based multi-tenancy with automatic schema separation and migration management.

### Automatic Multi-Tenancy Detection

The `migrate` command automatically detects multi-tenancy configurations and handles schema splitting:

```bash
# Single command handles everything for multi-tenant setups
npx @better-auth-cloudflare/cli migrate --migrate-target=dev
```

**What happens automatically:**

- Detects multi-tenancy from auth configuration (`multiTenancy` with `mode: "organization"`)
- Splits generated schemas into core auth tables vs tenant-specific tables
- Creates separate drizzle configs (`drizzle.config.ts` vs `drizzle-tenant.config.ts`)
- Generates core migrations and applies them to main database
- Generates tenant migrations and sets up tenant migration system

### Schema Separation Logic

**Core Auth Tables (Main Database):**

- `users`, `accounts`, `sessions`, `verifications`
- `tenants`, `invitations`, `organizations`, `members`

**Tenant Tables (Individual Tenant Databases):**

- All other plugin tables (e.g., `userFiles`, custom plugin tables)

### Tenant Migration Commands

Apply migrations to all active tenant databases:

```bash
# Same account scenario (3 variables)
CLOUDFLARE_D1_API_TOKEN=xxx CLOUDFLARE_ACCT_ID=yyy CLOUDFLARE_DATABASE_ID=zzz \
npx @better-auth-cloudflare/cli migrate:tenants

# Separate accounts scenario (5 variables)
CLOUDFLARE_MAIN_D1_API_TOKEN=aaa CLOUDFLARE_MAIN_ACCT_ID=bbb CLOUDFLARE_MAIN_DATABASE_ID=ccc \
CLOUDFLARE_D1_API_TOKEN=xxx CLOUDFLARE_ACCT_ID=yyy \
npx @better-auth-cloudflare/cli migrate:tenants

# Non-interactive mode (same account)
CLOUDFLARE_D1_API_TOKEN=xxx CLOUDFLARE_ACCT_ID=yyy CLOUDFLARE_DATABASE_ID=zzz \
npx @better-auth-cloudflare/cli migrate:tenants --auto-confirm

# Dry-run to preview changes (same account)
CLOUDFLARE_D1_API_TOKEN=xxx CLOUDFLARE_ACCT_ID=yyy CLOUDFLARE_DATABASE_ID=zzz \
npx @better-auth-cloudflare/cli migrate:tenants --dry-run
```

### Environment Variables for Multi-Tenancy

**For SAME account** (main and tenant DBs in same Cloudflare account - 3 variables):

```bash
CLOUDFLARE_D1_API_TOKEN # API token with D1:edit permissions
CLOUDFLARE_ACCT_ID # Account ID for both main and tenant databases
CLOUDFLARE_DATABASE_ID # Main database ID
```

**For SEPARATE accounts** (main and tenant DBs in different accounts - 5 variables):

```bash
CLOUDFLARE_MAIN_D1_API_TOKEN # API token for main database account
CLOUDFLARE_MAIN_ACCT_ID # Account ID for main database
CLOUDFLARE_MAIN_DATABASE_ID # Main database ID
CLOUDFLARE_D1_API_TOKEN # API token for tenant databases account
CLOUDFLARE_ACCT_ID # Account ID where tenant databases are managed
```

### Multi-Tenancy File Structure

```
your-project/
├── drizzle.config.ts # Main database configuration
├── drizzle/ # Main database migrations
│ ├── 0000_initial.sql
│ └── meta/
├── drizzle-tenant.config.ts # Tenant database configuration
├── drizzle-tenant/ # Tenant database migrations
│ ├── 0000_tenant_tables.sql
│ └── meta/
└── src/db/
├── auth.schema.ts # Core auth schema (main DB)
├── tenant.schema.ts # Tenant schema (tenant DBs)
└── tenant.raw.ts # Raw tenant utilities
```

---

Creates a new Better Auth Cloudflare project from Hono or OpenNext.js templates, optionally creating Cloudflare D1, KV, R2, or Hyperdrive resources for you. The migrate command runs `auth:update`, `db:generate`, and optionally `db:migrate`.
Creates a new Better Auth Cloudflare project from Hono or OpenNext.js templates, optionally creating Cloudflare D1, KV, R2, or Hyperdrive resources for you. The migrate command runs `auth:update`, `db:generate`, handles multi-tenancy schema splitting, and optionally applies migrations. The `migrate:tenants` command applies tenant migrations to all tracked tenant databases.

## Related

Expand Down
9 changes: 5 additions & 4 deletions cli/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@better-auth-cloudflare/cli",
"version": "0.1.16",
"description": "CLI to generate Better Auth Cloudflare projects (Hono or OpenNext.js)",
"name": "@zpg6-test-pkgs/better-auth-cloudflare-cli",
"version": "0.1.16-tenants.6",
"description": "CLI for project creation and tenant management in Better Auth Cloudflare projects",
"author": "Zach Grimaldi",
"repository": {
"type": "git",
Expand All @@ -22,7 +22,7 @@
"bin": {
"better-auth-cloudflare": "dist/index.js"
},
"type": "commonjs",
"type": "module",
"files": [
"dist/**/*"
],
Expand All @@ -37,6 +37,7 @@
},
"dependencies": {
"@clack/prompts": "^0.7.0",
"@zpg6-test-pkgs/drizzle-orm": "0.44.5-d1-http-test.2",
"picocolors": "^1.0.0"
},
"devDependencies": {
Expand Down
99 changes: 99 additions & 0 deletions cli/src/commands/generate-tenant-migrations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/usr/bin/env node
import { cancel, intro, outro, spinner } from "@clack/prompts";
import { existsSync, readFileSync } from "fs";
import { join } from "path";
import pc from "picocolors";
import { detectMultiTenancy, splitAuthSchema } from "../lib/tenant-migration-generator.js";

// Get package version from package.json
function getPackageVersion(): string {
try {
const packagePath = join(__dirname, "..", "..", "package.json");
const packageJson = JSON.parse(readFileSync(packagePath, "utf8"));
return packageJson.version as string;
} catch {
return "unknown";
}
}

function fatal(message: string) {
outro(pc.red(message));
console.log(pc.gray("\nNeed help?"));
console.log(pc.cyan(" Get help: npx @better-auth-cloudflare/cli --help"));
console.log(pc.cyan(" Report issues: https://github.com/zpg6/better-auth-cloudflare/issues"));
process.exit(1);
}

/**
* Command to generate tenant-specific migrations for multi-tenancy setups
*/
export async function generateTenantMigrations(): Promise<void> {
const version = getPackageVersion();
intro(`${pc.bold("Better Auth Cloudflare")} ${pc.gray("v" + version + " · generate-tenant-migrations")}`);

// Check if we're in a project directory by looking for wrangler.toml
const wranglerPath = join(process.cwd(), "wrangler.toml");
if (!existsSync(wranglerPath)) {
fatal("No wrangler.toml found. Please run this command from a Cloudflare Workers project directory.");
}

// Check if auth schema exists
const authSchemaPath = join(process.cwd(), "src/db/auth.schema.ts");
if (!existsSync(authSchemaPath)) {
fatal("auth.schema.ts not found. Please run 'npm run auth:update' first to generate the auth schema.");
}

// Check if multi-tenancy is enabled
if (!detectMultiTenancy(process.cwd())) {
fatal("Multi-tenancy not detected in your auth configuration. This command is only for multi-tenant setups.");
}

const splitSpinner = spinner();
splitSpinner.start("Splitting auth schema for multi-tenancy...");

try {
await splitAuthSchema(process.cwd());
splitSpinner.stop(pc.green("Schema successfully split!"));

outro(
pc.green("✅ Tenant migration setup complete!\n\n") +
pc.bold("Files created:\n") +
pc.cyan(" • src/db/auth.schema.ts") +
pc.gray(" - Core auth tables (main database)\n") +
pc.cyan(" • src/db/tenant.schema.ts") +
pc.gray(" - Tenant-specific tables (tenant databases)\n") +
pc.cyan(" • drizzle-tenant.config.ts") +
pc.gray(" - Config for tenant migrations\n") +
pc.cyan(" • drizzle-tenant/") +
pc.gray(" - Tenant migration files\n\n") +
pc.bold("Next steps:\n") +
pc.gray(" 1. Run ") +
pc.cyan("npm run db:generate") +
pc.gray(" to create core migrations\n") +
pc.gray(" 2. Apply core migrations to main DB: ") +
pc.cyan("npm run db:migrate:dev") +
pc.gray("\n") +
pc.gray(" 3. Apply tenant migrations: ") +
pc.cyan("npx @better-auth-cloudflare/cli migrate:tenants") +
pc.gray("\n") +
pc.gray(" 4. To update tenant migrations: ") +
pc.cyan("npx drizzle-kit generate --config=drizzle-tenant.config.ts")
);
} catch (error) {
splitSpinner.stop(pc.red("Failed to split auth schema."));
fatal(`Schema splitting failed: ${error instanceof Error ? error.message : String(error)}`);
}
}

// Handle cancellation
process.on("SIGINT", () => {
cancel("Operation cancelled.");
process.exit(0);
});

// Run if called directly
if (import.meta.url === `file://${process.argv[1]}`) {
generateTenantMigrations().catch(err => {
fatal(String(err?.message ?? err));
});
}
Loading
Loading