Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 9 additions & 9 deletions .github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ from `main`.
}
```

2. Ensure `package.json` has a `build` script and (if needed) `wrangler.jsonc` exists.
2. Ensure `package.json` has a `build` script and (if needed) `wrangler.example.jsonc` exists.
3. Push to `main` → production deploy runs when that app (or `packages/`) has changes. Open a PR → preview deploy runs
unless skipped.

Expand All @@ -50,10 +50,10 @@ Already configured; push to `main` or open PRs as usual.
| `workerBuildCommand` | `null` | pnpm script for Worker bundle (e.g. OpenNext); `null` for TanStack |
| `outputDirectory` | `"dist"` | Dir to verify after build |
| `verifyPaths` | `["dist", "cloudflare-worker.ts"]` | Paths that must exist after build |
| `wranglerConfig` | `"wrangler.jsonc"` | Wrangler config file |
| `requiresSecrets` | `[]` | _(Optional)_ Extra secrets not in wrangler.jsonc (e.g. build-time) |
| `wranglerConfig` | `"wrangler.example.jsonc"` | Wrangler config file (tracked template; user copy is gitignored) |
| `requiresSecrets` | `[]` | _(Optional)_ Extra secrets not in wrangler config (e.g. build-time) |

> **SSOT:** Placeholders in `wrangler.jsonc` `env.production` / `env.preview` are auto-detected. `requiresSecrets` is
> **SSOT:** Placeholders in `wrangler.example.jsonc` `env.production` / `env.preview` are auto-detected. `requiresSecrets` is
> only for secrets that don't appear in wrangler.

### Minimal examples
Expand Down Expand Up @@ -87,13 +87,13 @@ Already configured; push to `main` or open PRs as usual.
"buildCommand": "build",
"outputDirectory": "dist",
"verifyPaths": ["dist", "cloudflare-worker.ts"],
"wranglerConfig": "wrangler.jsonc"
"wranglerConfig": "wrangler.example.jsonc"
}
```

### Wrangler placeholders

In `wrangler.jsonc`, `ALL_CAPS_SNAKE_CASE` placeholder values in `env.production` and `env.preview` are
In `wrangler.example.jsonc`, `ALL_CAPS_SNAKE_CASE` placeholder values in `env.production` and `env.preview` are
**auto-detected** by `substitute-wrangler-secrets.py` and substituted from GitHub Secrets. No explicit key list or
per-secret workflow wiring needed — just set the placeholder and the secret.

Expand Down Expand Up @@ -204,9 +204,9 @@ pnpm preview # if available

| Issue | Check |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| App not discovered | `deployable: true` in `cloudflare-config.json`; `package.json` has required scripts; `wrangler.jsonc` present if no cloudflare-config |
| Build fails | Actions logs; locally: `pnpm --filter=@ottabase/my-app run build` |
| Deploy fails | Required secrets set; `wrangler.jsonc` valid; no unsubstituted placeholders in generated config |
| App not discovered | `deployable: true` in `cloudflare-config.json`; `package.json` has required scripts; `wrangler.example.jsonc` present if no cloudflare-config |
| Build fails | Actions logs; locally: `pnpm --filter=@ottabase/my-app run build` |
| Deploy fails | Required secrets set; `wrangler.example.jsonc` valid; no unsubstituted placeholders in generated config |
| Preview not created | PR without `#skippr` / `#skipdeploy`; secrets set; app in `APPS_TO_DEPLOY` or default |

Errors in workflows include what failed, why, and how to fix (e.g. missing secrets with links to Cloudflare).
Expand Down
4 changes: 2 additions & 2 deletions .github/instructions/agent.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export const {
useUpdate: useUpdateTodo,
useDelete: useDeleteTodo,
useInfiniteList: useTodosInfinite,
} = createModelHooks<TodoType>({ entity: 'todos' });
} = createModelHooks<TodoType>({ entityName: 'todos' });

// Usage: const { data: todo } = useTodoBySlug("slug", "my-todo-slug");
```
Expand Down Expand Up @@ -297,7 +297,7 @@ export { myTable } from '@ottabase/mypackage/schema';
// ottabase/hooks/useMyModel.ts
import { createModelHooks } from '@ottabase/ottaorm/client';

export const { useList, useCreate, useUpdate, useDelete } = createModelHooks({ entity: 'mytable' });
export const { useList, useCreate, useUpdate, useDelete } = createModelHooks({ entityName: 'mytable' });
```

### 5. Run migrations
Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,9 @@ jobs:
if [ -f "$CONFIG_JSON" ]; then
CONFIG=$(cat "$CONFIG_JSON")
else
# Check for wrangler.jsonc as fallback
if [ ! -f "${APP_PATH}/wrangler.jsonc" ]; then
echo "⚠️ Warning: No cloudflare-config.json or wrangler.jsonc for ${app_folder}, skipping"
# Check for wrangler config as fallback (.example is the tracked template)
if [ ! -f "${APP_PATH}/wrangler.example.jsonc" ] && [ ! -f "${APP_PATH}/wrangler.jsonc" ]; then
echo "⚠️ Warning: No cloudflare-config.json or wrangler config for ${app_folder}, skipping"
continue
fi
# Use TanStack defaults (default template app is TanStack)
Expand All @@ -213,7 +213,7 @@ jobs:
"workerBuildCommand": null,
"outputDirectory": "dist",
"verifyPaths": ["dist", "cloudflare-worker.ts"],
"wranglerConfig": "wrangler.jsonc",
"wranglerConfig": "wrangler.example.jsonc",
"wranglerEnv": "production",
"healthCheckPath": "/"
}'
Expand Down Expand Up @@ -322,10 +322,10 @@ jobs:
run: |
echo "🔍 Verifying required secrets for ${{ matrix.name }}..."

# Base secrets (wrangler-action needs these; not in wrangler.jsonc)
# Base secrets (wrangler-action needs these; not in wrangler config)
BASE="CLOUDFLARE_API_TOKEN CLOUDFLARE_ACCOUNT_ID"

# Derive placeholders from wrangler.jsonc env.production (single source of truth; no drift)
# Derive placeholders from wrangler config env.production (single source of truth; no drift)
PLACEHOLDERS=$(python ../../.github/scripts/substitute-wrangler-secrets.py --list-only)

# Optional extra from cloudflare-config.json requiresSecrets
Expand Down Expand Up @@ -393,7 +393,7 @@ jobs:
key:
${{ runner.os }}-nextjs-${{ matrix.folder }}-${{ hashFiles('pnpm-lock.yaml') }}-${{
hashFiles('apps/${{ matrix.folder }}/**/*.{ts,tsx,js,jsx}') }}-${{ hashFiles('apps/${{
matrix.folder }}/wrangler.jsonc', 'apps/${{ matrix.folder }}/open-next.config.ts', 'apps/${{
matrix.folder }}/wrangler.example.jsonc', 'apps/${{ matrix.folder }}/open-next.config.ts', 'apps/${{
matrix.folder }}/next.config.js') }}
restore-keys: |
${{ runner.os }}-nextjs-${{ matrix.folder }}-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('apps/${{ matrix.folder }}/**/*.{ts,tsx,js,jsx}') }}-
Expand All @@ -410,7 +410,7 @@ jobs:
key:
${{ runner.os }}-tanstack-${{ matrix.folder }}-${{ hashFiles('pnpm-lock.yaml') }}-${{
hashFiles('apps/${{ matrix.folder }}/**/*.{ts,tsx,js,jsx}') }}-${{ hashFiles('apps/${{
matrix.folder }}/wrangler.jsonc', 'apps/${{ matrix.folder }}/vite.config.ts') }}
matrix.folder }}/wrangler.example.jsonc', 'apps/${{ matrix.folder }}/vite.config.ts') }}
restore-keys: |
${{ runner.os }}-tanstack-${{ matrix.folder }}-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('apps/${{ matrix.folder }}/**/*.{ts,tsx,js,jsx}') }}-
${{ runner.os }}-tanstack-${{ matrix.folder }}-${{ hashFiles('pnpm-lock.yaml') }}-
Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/pr-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,11 @@ jobs:
if [ -f "$CONFIG_JSON" ]; then
CONFIG=$(cat "$CONFIG_JSON")
else
if [ ! -f "${APP_PATH}/wrangler.jsonc" ]; then
echo "⚠️ Warning: No cloudflare-config.json or wrangler.jsonc for ${APP_FOLDER}, skipping"
if [ ! -f "${APP_PATH}/wrangler.example.jsonc" ] && [ ! -f "${APP_PATH}/wrangler.jsonc" ]; then
echo "⚠️ Warning: No cloudflare-config.json or wrangler config for ${APP_FOLDER}, skipping"
continue
fi
CONFIG='{"deployable":true,"appType":"tanstack","buildCommand":"build","workerBuildCommand":null,"outputDirectory":"dist","verifyPaths":["dist","cloudflare-worker.ts"],"wranglerConfig":"wrangler.jsonc","wranglerEnv":"production","healthCheckPath":"/"}'
CONFIG='{"deployable":true,"appType":"tanstack","buildCommand":"build","workerBuildCommand":null,"outputDirectory":"dist","verifyPaths":["dist","cloudflare-worker.ts"],"wranglerConfig":"wrangler.example.jsonc","wranglerEnv":"production","healthCheckPath":"/"}'
fi

DEPLOYABLE=$(echo "$CONFIG" | jq -r '.deployable // true')
Expand Down Expand Up @@ -222,7 +222,7 @@ jobs:

echo "📋 Configuration loaded for ${{ matrix.name }} (preview)"

# Secrets derived from wrangler.jsonc env.preview + base + optional extra (single source of truth; no drift)
# Secrets derived from wrangler config env.preview + base + optional extra (single source of truth; no drift)
- name: Verify required secrets
working-directory: apps/${{ matrix.folder }}
env:
Expand All @@ -235,7 +235,7 @@ jobs:
# Base secrets (wrangler-action needs these)
BASE="CLOUDFLARE_API_TOKEN CLOUDFLARE_ACCOUNT_ID"

# Derive placeholders from wrangler.jsonc env.preview (single source of truth; no drift)
# Derive placeholders from wrangler config env.preview (single source of truth; no drift)
PLACEHOLDERS=$(python ../../.github/scripts/substitute-wrangler-secrets.py --list-only)

# Optional extra from cloudflare-config.json requiresPreviewSecrets
Expand Down Expand Up @@ -296,7 +296,7 @@ jobs:
key:
${{ runner.os }}-nextjs-${{ matrix.folder }}-${{ hashFiles('pnpm-lock.yaml') }}-${{
hashFiles('apps/${{ matrix.folder }}/**/*.{ts,tsx,js,jsx}') }}-${{ hashFiles('apps/${{
matrix.folder }}/wrangler.jsonc', 'apps/${{ matrix.folder }}/open-next.config.ts', 'apps/${{
matrix.folder }}/wrangler.example.jsonc', 'apps/${{ matrix.folder }}/open-next.config.ts', 'apps/${{
matrix.folder }}/next.config.js') }}
restore-keys: |
${{ runner.os }}-nextjs-${{ matrix.folder }}-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('apps/${{ matrix.folder }}/**/*.{ts,tsx,js,jsx}') }}-
Expand All @@ -313,7 +313,7 @@ jobs:
key:
${{ runner.os }}-tanstack-${{ matrix.folder }}-${{ hashFiles('pnpm-lock.yaml') }}-${{
hashFiles('apps/${{ matrix.folder }}/**/*.{ts,tsx,js,jsx}') }}-${{ hashFiles('apps/${{
matrix.folder }}/wrangler.jsonc', 'apps/${{ matrix.folder }}/vite.config.ts') }}
matrix.folder }}/wrangler.example.jsonc', 'apps/${{ matrix.folder }}/vite.config.ts') }}
restore-keys: |
${{ runner.os }}-tanstack-${{ matrix.folder }}-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('apps/${{ matrix.folder }}/**/*.{ts,tsx,js,jsx}') }}-
${{ runner.os }}-tanstack-${{ matrix.folder }}-${{ hashFiles('pnpm-lock.yaml') }}-
Expand Down
4 changes: 2 additions & 2 deletions AGENTS.MD
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export const {
useUpdate: useUpdateTodo,
useDelete: useDeleteTodo,
useInfiniteList: useTodosInfinite,
} = createModelHooks<TodoType>({ entity: 'todos' });
} = createModelHooks<TodoType>({ entityName: 'todos' });

// Usage:
// const { data: todo } = useTodoBySlug("slug", "my-todo-slug");
Expand Down Expand Up @@ -312,7 +312,7 @@ export { myTable } from '@ottabase/mypackage/schema';
// ottabase/hooks/useMyModel.ts
import { createModelHooks } from '@ottabase/ottaorm/client';

export const { useList, useCreate, useUpdate, useDelete } = createModelHooks({ entity: 'mytable' });
export const { useList, useCreate, useUpdate, useDelete } = createModelHooks({ entityName: 'mytable' });
```

### 5. Run migrations
Expand Down
102 changes: 44 additions & 58 deletions CLOUDFLARE_CONFIGURATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Create `apps/ottabase-template-app-tanstack/.env.local` with the following (if u
# Generate with: openssl rand -base64 32
AUTH_SECRET=your-32-character-secret-here
NEXTAUTH_SECRET=your-32-character-secret-here
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_URL=http://localhost:3003

# Enable auth providers (true/false)
AUTH_LOGIN_CREDENTIALS=true
Expand Down Expand Up @@ -192,7 +192,7 @@ multi-app: same placeholder name = shared resource; different names = isolated (
}
```

### 3. `apps/ottabase-template-app-tanstack/types/cloudflare.d.ts`
### 2. `apps/ottabase-template-app-tanstack/cloudflare-env.d.ts`

**Status:** ✅ Already configured

Expand Down Expand Up @@ -225,7 +225,7 @@ export interface CloudflareEnv {
}
```

### 4. `apps/ottabase-template-app-tanstack/cloudflare-worker.ts`
### 3. `apps/ottabase-template-app-tanstack/cloudflare-worker.ts`

**Status:** ✅ Already configured

Expand All @@ -242,22 +242,24 @@ export { RealtimeActor } from '@ottabase/cf-realtime/server';

### Using Drizzle with D1

The app uses `@ottabase/db` package with Drizzle adapter for D1.
The app uses `@ottabase/db` package with Drizzle adapter for D1. Access `env` directly from your Worker fetch handler:

```typescript
import { getCloudflareContext } from '@opennextjs/cloudflare';
import { createD1Driver } from '@ottabase/db/drizzle-d1';
import { setDriver } from '@ottabase/ottaorm';

export async function GET() {
const { env } = await getCloudflareContext();
// cloudflare-worker.ts
export default {
async fetch(request: Request, env: CloudflareEnv) {
const driver = createD1Driver(env.OBCF_D1);
setDriver(driver);

const driver = createD1Driver(env.OBCF_D1);
const db = driver.getDb();
const db = driver.getDb();
const users = await db.select().from(usersTable);

const users = await db.select().from(usersTable);

return Response.json(users);
}
return Response.json(users);
},
};
```

---
Expand Down Expand Up @@ -285,22 +287,7 @@ export const authConfig = createOttabaseAuthConfig({
});
```

### 4. Configure Auth

```typescript
// app/auth.ts
import { createOttabaseAuthConfig, createGoogleProvider } from '@ottabase/auth';

export const authConfig = createOttabaseAuthConfig({
d1: env.OBCF_D1,
providers: [
createGoogleProvider(env),
// Add more providers
],
});
```

### 5. Set Environment Variables
### 3. Set Environment Variables

Add to `.env.local`:

Expand All @@ -318,25 +305,25 @@ AUTH_GOOGLE_SECRET=your-google-client-secret

- [ ] **Cloudflare Resources Created**
- [ ] D1 Database exists: `wrangler d1 list`
- [ ] KV Namespace exists: `wrangler kv:namespace list`
- [ ] KV Namespace exists: `wrangler kv namespace list`
- [ ] R2 Bucket exists: `wrangler r2 bucket list`
- [ ] Queue exists: `wrangler queues list`

- [ ] **Configuration Files Updated**
- [ ] GitHub Secrets set for production: `D1_DATABASE_ID`, `KV_NAMESPACE_ID`
- [ ] GitHub Secrets set for PR preview: `D1_PREVIEW_DATABASE_ID`, `KV_PREVIEW_NAMESPACE_ID`
- [ ] `types/cloudflare.d.ts` includes all OBCF\_\* bindings
- [ ] `cloudflare-env.d.ts` includes all OBCF\_\* bindings

- [ ] **Environment Variables Set**
- [ ] `.env.local` created for local development
- [ ] Production secrets set via `wrangler secret put`

- [ ] **Database Schema Generated**
- [ ] Migrations applied to D1: `wrangler d1 migrations apply`
- [ ] Migrations applied via OttaORM: `curl -X POST http://localhost:3004/api/ottaorm/init`

- [ ] **Build & Deploy**
- [ ] Local build works: `pnpm build`
- [ ] Worker build works: `pnpm build:worker`
- [ ] Worker build works: `pnpm build` (TanStack) or `pnpm build:worker` (Next.js)
- [ ] Preview works: `pnpm preview`
- [ ] Deploy successful: `pnpm deploy`

Expand All @@ -359,28 +346,26 @@ curl https://your-app.workers.dev/api/cloudflare/r2/list

## 🔍 Accessing Cloudflare Bindings in Code

### App Router (Server Components & Route Handlers)
### Cloudflare Worker (Fetch Handler)

```typescript
import { getCloudflareContext } from '@opennextjs/cloudflare';

export async function GET() {
const { env } = await getCloudflareContext();

// Access bindings with OBCF_* names
const db = env.OBCF_D1; // D1 Database
const kv = env.OBCF_KV; // KV Namespace
const r2 = env.OBCF_R2; // R2 Bucket
const queue = env.OBCF_QUEUE; // Queue
const realtime = env.OBCF_REALTIME; // Durable Object

// Use with @ottabase packages
// D1 via OttaORM (preferred):
// import { createD1Driver } from '@ottabase/db/drizzle-d1';
// const driver = createD1Driver(db); setDriver(driver);
const kvClient = createKVClient({ namespace: kv });
const r2Client = createR2Client({ bucket: r2 });
}
// cloudflare-worker.ts
export default {
async fetch(request: Request, env: CloudflareEnv) {
// Access bindings with OBCF_* names
const db = env.OBCF_D1; // D1 Database
const kv = env.OBCF_KV; // KV Namespace
const r2 = env.OBCF_R2; // R2 Bucket
const queue = env.OBCF_QUEUE; // Queue
const realtime = env.OBCF_REALTIME; // Durable Object

// D1 via OttaORM (preferred):
// import { createD1Driver } from '@ottabase/db/drizzle-d1';
// const driver = createD1Driver(db); setDriver(driver);
const kvClient = createKVClient({ namespace: kv });
const r2Client = createR2Client({ bucket: r2 });
},
};
```

### Package Usage
Expand Down Expand Up @@ -494,14 +479,15 @@ secret: set the placeholder in `wrangler.jsonc`, add the secret to GitHub. CI au

**Cause:** D1 database doesn't have schema.

**Solution:**
**Solution:** Ottabase uses OttaORM auto-init, not wrangler migrations:

```bash
# Local
wrangler d1 migrations apply ottabase-db --local
# Local (with dev server running on port 3004)
curl -X POST http://localhost:3004/api/ottaorm/init

# Production
wrangler d1 migrations apply ottabase-db --remote
# Production (requires MIGRATION_SECRET)
curl -X POST https://your-app.workers.dev/api/ottaorm/init \
-H "Authorization: Bearer ${MIGRATION_SECRET}"
```

### "Type errors with CloudflareEnv"
Expand Down
4 changes: 2 additions & 2 deletions CLOUDFLARE_DEPLOY.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,11 +215,11 @@ pnpm build && pnpm wrangler deploy --env production
wrangler tail ottabase-template-app-tanstack

# Execute D1 commands
wrangler d1 execute ottabase-db --remote --command="SELECT * FROM User LIMIT 5"
wrangler d1 execute ottabase-db --remote --command="SELECT * FROM users LIMIT 5"

# List resources
wrangler d1 list
wrangler kv:namespace list
wrangler kv namespace list
wrangler r2 bucket list
wrangler queues list
```
Expand Down
Loading