From 268c4ab15e9454ba1bee0f71392db1bbee252623 Mon Sep 17 00:00:00 2001 From: BroUnion Date: Tue, 31 Mar 2026 15:58:06 +0000 Subject: [PATCH 1/6] Refactor code structure for improved readability and maintainability --- .github/workflows/ci.yml | 30 +- CodeRabbit_Full_Codebase_review.md | 903 +++++++++++++++++++++++++++ Dockerfile.project | 6 +- apps/dashboard/src/vite-env.d.ts | 2 +- docker-compose.dev.yml | 2 - docker-compose.self-hosted.yml | 2 +- docker-compose.yml | 4 +- packages/cli/src/index.ts | 31 +- packages/client/src/iac/provider.tsx | 61 +- 9 files changed, 999 insertions(+), 42 deletions(-) create mode 100644 CodeRabbit_Full_Codebase_review.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4209292..e0e3a1e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,15 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version: "1.3.10" + + - name: Cache bun install + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} + restore-keys: | + ${{ runner.os }}-bun- - name: Install dependencies run: bun install --frozen-lockfile @@ -36,7 +44,15 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version: "1.3.10" + + - name: Cache bun install + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} + restore-keys: | + ${{ runner.os }}-bun- - name: Install dependencies run: bun install --frozen-lockfile @@ -54,7 +70,15 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version: "1.3.10" + + - name: Cache bun install + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} + restore-keys: | + ${{ runner.os }}-bun- - name: Install dependencies run: bun install --frozen-lockfile diff --git a/CodeRabbit_Full_Codebase_review.md b/CodeRabbit_Full_Codebase_review.md new file mode 100644 index 0000000..935d064 --- /dev/null +++ b/CodeRabbit_Full_Codebase_review.md @@ -0,0 +1,903 @@ +## chore: trigger CodeRabbit review - dashboard pages (4/7) #13 + +```text +Verify each finding against the current code and only fix it if needed. + +Inline comments: +In `@apps/dashboard/src/pages/projects/ProjectAuthPage.tsx`: +- Line 129: Remove the trailing whitespace at the end of the line inside the +ProjectAuthPage component (file ProjectAuthPage.tsx) β€” open the ProjectAuthPage +function/component and delete any extra space characters at the end of line 129 +(or run your editor/formatter to trim trailing whitespace) so the file has no +trailing whitespace on that line. + +In `@apps/dashboard/src/pages/projects/ProjectEnvPage.tsx`: +- Line 170: The file ProjectEnvPage.tsx contains trailing whitespace on the line +within the ProjectEnvPage component; remove the extra space character(s) at the +end of that line (ensure no trailing spaces remain in the JSX/TSX file) so the +file has no trailing whitespace. + +In `@apps/dashboard/src/pages/projects/ProjectIaCQueryPage.tsx`: +- Line 170: The file ProjectIaCQueryPage.tsx contains trailing whitespace at +EOF; remove the extra blank spaces/newline characters at the end of the file so +the file ends cleanly with no trailing whitespace, then re-run your +formatter/linter (e.g., Prettier/ESLint or your editor's trim trailing +whitespace setting) to ensure this is not reintroduced; verify by opening +ProjectIaCQueryPage.tsx and deleting the trailing spaces or saving with "trim +trailing whitespace" enabled. + +In `@apps/dashboard/src/pages/settings/SettingsPage.tsx`: +- Line 316: Remove the trailing whitespace at the end of the file by editing +SettingsPage.tsx: locate the final closing brace of the SettingsPage component +(the lone "}" shown in the diff) or the file end and delete any trailing +spaces/newline characters after it so the file ends cleanly with the closing +brace (or a single newline) and no extra whitespace. + +--- + +Outside diff comments: +In `@apps/dashboard/src/pages/projects/ProjectAuthPage.tsx`: +- Around line 25-31: updateMutation currently doesn't invalidate the cached +project auth query so the UI can remain stale; import and call useQueryClient(), +get queryClient in the component, and in updateMutation's onSuccess handler call +queryClient.invalidateQueries(QK.projectAuthConfig(projectId)) (keep the +existing toast.success call) so the cache for QK.projectAuthConfig(projectId) is +refreshed after the mutation. +- Around line 18-121: Replace the local useState config form in ProjectAuthPage +with React Hook Form + Zod: remove const [config, setConfig] and instead create +a zod schema for the auth config (fields like google_enabled, github_enabled, +discord_enabled, twitter_enabled, password_enabled, magic_link_enabled, +session_days), use useForm({ resolver: zodResolver(schema), defaultValues: +authConfig }) and populate defaults from data?.config; replace direct +value/checked props on Switch and Input with react-hook-form bindingsβ€”use +Controller for controlled components (Switch, numeric Input) and register for +simple fields; wire form submission/update to call updateMutation.mutate(values) +(or call updateMutation.mutate on controlled value changes via handleSubmit) and +remove setConfig calls; ensure validation/parsing of session_days via the schema +and show validation errors similarly to SettingsPage.tsx. + +In `@apps/dashboard/src/pages/projects/ProjectEnvPage.tsx`: +- Around line 28-29: The add-variable form in ProjectEnvPage currently uses +local state variables newKey/newValue (setNewKey/setNewValue) and manual +handlers; replace this with React Hook Form and a Zod resolver: create a Zod +schema for the variable shape (e.g., key pattern and required value), initialize +useForm({ resolver: zodResolver(schema) }) inside the ProjectEnvPage component, +remove useState declarations for newKey/newValue and their setters, register the +inputs with form.register("key") and form.register("value"), replace the submit +handler to use handleSubmit and read data from the form values, and surface +validation errors via formState.errors so the UI shows consistent validation +messages. Ensure the same submit logic (the function currently called to add the +variable) is invoked with the validated form data and reset the form via reset() +after successful submission. + +In `@apps/dashboard/src/pages/projects/ProjectIaCQueryPage.tsx`: +- Around line 28-42: The mutation queryMutation currently allows non-SELECT SQL +and may mutate project data without causing cache refresh; update the onSuccess +handler to invalidate relevant cached queries (e.g., call the react-query client +to invalidate QK.project(projectId) and any other project-related keys) so UI +reflects changes, or alternatively enforce SELECT-only SQL at the API layer; +locate useMutation's onSuccess in ProjectIaCQueryPage and add the +invalidateQueries call(s) for QK.project(projectId) (and related keys) to ensure +cached project data is refreshed after a successful mutation. + +In `@apps/dashboard/src/pages/settings/SettingsPage.tsx`: +- Around line 273-312: Replace the custom modal markup that is conditionally +rendered when showResetDialog is true with the shared ConfirmDialog component: +use ConfirmDialog (instead of the wrapper div and inner content) wired to +open={showResetDialog} and onOpenChange={setShowResetDialog}, pass a +destructive/variant prop, the title "Factory Reset", the descriptive body text, +and set the confirmation input by connecting ConfirmDialog's confirm value to +resetConfirm and its onConfirmChange to setResetConfirm; wire the confirm action +to call resetMutation.mutate and set the confirm button disabled state to match +the existing logic (resetConfirm !== instanceData?.instance?.name || +resetMutation.isPending) and show the pending label when +resetMutation.isPending. Ensure you pass requireTyping (or add support for it to +ConfirmDialog) so the dialog requires typing instanceData?.instance?.name to +enable the destructive action. + +``` + + +--------- + +## chore: trigger CodeRabbit review - config files & Dockerfiles (7/7)#10 + + +```txt + +
+🧩 Analysis chain + +`@Helal-maker` β€” triggering the review now. While that runs, let me pull the PR diff to analyze the files and draft the agent prompt. + + + + + + +--- + +🏁 Script executed: +``` +#!/bin/bash +# Get PR details, files changed, and diff +gh pr view 10 --repo Helal-maker/Betterbase --json title,body,files,additions,deletions,changedFiles +echo "---FILES---" +gh pr diff 10 --repo Helal-maker/Betterbase +``` + +Length of output: 7093 + +--- + +The diff is pure whitespace β€” need to read the actual file content to find real issues. + +--- + +🏁 Script executed: +``` +#!/bin/bash +echo "=== .coderabbit.yaml ===" && cat .coderabbit.yaml +echo "=== .github/workflows/ci.yml ===" && cat .github/workflows/ci.yml +echo "=== Dockerfile ===" && cat Dockerfile +echo "=== Dockerfile.project ===" && cat Dockerfile.project +echo "=== apps/dashboard/Dockerfile ===" && cat apps/dashboard/Dockerfile +echo "=== apps/dashboard/src/vite-env.d.ts ===" && cat apps/dashboard/src/vite-env.d.ts +echo "=== docker-compose.dev.yml ===" && cat docker-compose.dev.yml +echo "=== docker-compose.production.yml ===" && cat docker-compose.production.yml +echo "=== docker-compose.self-hosted.yml ===" && cat docker-compose.self-hosted.yml +echo "=== docker-compose.yml ===" && cat docker-compose.yml +echo "=== packages/cli/src/build.ts ===" && cat packages/cli/src/build.ts +echo "=== packages/cli/src/constants.ts ===" && cat packages/cli/src/constants.ts +echo "=== packages/cli/src/index.ts ===" && cat packages/cli/src/index.ts +echo "=== packages/client/src/iac/provider.tsx ===" && cat packages/client/src/iac/provider.tsx +echo "=== packages/server/Dockerfile ===" && cat packages/server/Dockerfile +``` + +Length of output: 74631 + +--- + + + +
+ +`@Helal-maker` β€” review triggered. Here is the full analysis and agent prompt based on the actual bugs found in the codebase. + +--- + +## Issues Found + +### πŸ”΄ Critical + +**`packages/cli/src/index.ts` β€” `init` command action arguments are swapped** + +Commander.js passes positional arguments _before_ the options object. The current handler: +```ts +.action(async (options: { iac?: boolean }, projectName?: string) => { + await runInitCommand({ projectName, ...options }); +}) +``` +…receives `(projectNameString, optionsObject, command)`. The types are reversed: `options` receives the string `"my-app"`, and `projectName` receives the Commander options object. `runInitCommand` gets `{ projectName: { iac: false }, iac: undefined }`. **`bb init my-app` is broken** β€” project name is always `undefined`. + +Fix: +```ts +.action(async (projectName: string | undefined, options: { iac?: boolean }) => { + await runInitCommand({ projectName, ...options }); +}) +``` + +--- + +### 🟠 Major + +**1. `packages/cli/src/index.ts` β€” `PUBLIC_COMMANDS` missing raw flag strings** + +`checkAuthHook` reads `process.argv[2]` literally. `bb --version` β†’ `process.argv[2]` is `"--version"`, not `"version"`. `PUBLIC_COMMANDS` contains `"version"` and `"help"` but not `"--version"`, `"-v"`, `"--help"`, `"-h"`. Unauthenticated users running `bb --version` or `bb --help` get "Not logged in." + +Fix: +```ts +const PUBLIC_COMMANDS = ["login", "logout", "version", "help", "init", "--version", "-v", "--help", "-h", "-V"]; +``` + +--- + +**2. `packages/client/src/iac/provider.tsx` β€” WebSocket reconnect creates a dead socket** + +The `ws.onclose` handler creates a new `WebSocket` in a `setTimeout` but attaches **none** of the event handlers (`onopen`, `onclose`, `onmessage`) to the new socket. The reconnected socket never calls `setWsReady(true)`, never handles pings, and never reconnects again if it closes. + +Additionally, the cleanup function `return () => { ws.close() }` does not cancel the `setTimeout`, so if the component unmounts while the timer is pending, a new socket is still created and a post-unmount state update fires. + +Fix: +```ts +useEffect(() => { + let timeoutId: ReturnType | null = null; + let isCleaned = false; + + function connect() { + if (isCleaned) return; + const wsUrl = `${config.url.replace(/^http/, "ws")}/betterbase/ws?project=${config.projectSlug ?? "default"}`; + const ws = new WebSocket(wsUrl); + wsRef.current = ws; + + ws.onopen = () => { if (!isCleaned) setWsReady(true); }; + ws.onclose = () => { + if (isCleaned) return; + setWsReady(false); + timeoutId = setTimeout(connect, 3_000); + }; + ws.onmessage = (event) => { + const msg = JSON.parse(event.data); + if (msg.type === "ping") ws.send(JSON.stringify({ type: "pong" })); + }; + } + + connect(); + + return () => { + isCleaned = true; + if (timeoutId !== null) clearTimeout(timeoutId); + wsRef.current?.close(); + }; +}, [config.url, config.projectSlug]); +``` + +--- + +**3. `packages/cli/src/index.ts` β€” `branch` default action reads wrong parameter** + +```ts +branch + .argument("[project-root]", "project root directory", process.cwd()) + .option("-p, --project-root ", ...) + .action(async (options) => { // ← "options" is actually the arg string + const projectRoot = options.projectRoot || process.cwd(); // always cwd() + }); +``` +Commander passes `(argValue, optionsObject, command)`. Fix: +```ts +.action(async (projectRootArg: string, options: { projectRoot?: string }) => { + const projectRoot = projectRootArg || options.projectRoot || process.cwd(); + await runBranchCommand([], projectRoot); +}) +``` + +--- + +**4. `packages/cli/src/index.ts` β€” `fn deploy` same class of bug** + +```ts +fn.command("deploy") + .argument("", "function name") + .option("--sync-env", ...) + .argument("[project-root]", ...) + .action(async (name: string, options: { syncEnv?: boolean; projectRoot?: string }) => { + const projectRoot = options.projectRoot ?? process.cwd(); // always cwd() + }); +``` +Commander passes `(name, projectRoot, options, command)`. Fix: +```ts +.action(async (name: string, projectRootArg: string, options: { syncEnv?: boolean }) => { + const projectRoot = projectRootArg ?? process.cwd(); + await runFunctionCommand(["deploy", name, options.syncEnv ? "--sync-env" : ""], projectRoot); +}) +``` + +--- + +**5. `Dockerfile.project` β€” `deps` stage is dead code** + +The `builder` stage starts `FROM base AS builder` and re-runs `bun install --frozen-lockfile` independently. The `deps` stage is never referenced with `COPY --from=deps`. Every build re-installs all deps from scratch, defeating Docker layer caching. + +Fix β€” builder should use deps: +```dockerfile +FROM deps AS builder +WORKDIR /app +# deps already installed, just copy source +COPY . . +RUN bun run build +``` + +--- + +**6. `docker-compose.self-hosted.yml` β€” nginx healthcheck is wrong** + +```yaml +healthcheck: + test: ["CMD", "nginx", "-t"] # tests config syntax, not live HTTP +``` +`nginx -t` passes even if nginx isn't bound to port 80. Replace: +```yaml +healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost/health"] + interval: 30s + timeout: 10s + retries: 3 +``` + +--- + +**7. `docker-compose.yml` β€” unpinned `minio:latest` and `mc:latest`** + +Production and self-hosted compose files pin MinIO to `RELEASE.2024-11-07T19-31-41Z`. Dev uses `latest`. This causes environment divergence. Pin to the same versions used in production. + +--- + +### πŸ”΅ Minor + +**8. `apps/dashboard/src/vite-env.d.ts` β€” `VITE_API_URL` typed as optional** + +Per `.coderabbit.yaml`, `VITE_API_URL` is the only env var and it's required. Typing it `string?` means the compiler won't flag missing runtime values. Change to `readonly VITE_API_URL: string`. + +**9. `.github/workflows/ci.yml` β€” `bun-version: latest` not reproducible** + +All three jobs use `latest`. A Bun release could silently break CI. Pin to a specific version (e.g., `1.3.9` to match the Dockerfiles) and add a `cache: bun` step. + +**10. `docker-compose.dev.yml` β€” deprecated `version` field** + +`version: "3.9"` is obsolete in Docker Compose v2. Remove the field entirely. + +--- + +## πŸ€– Agent Prompt (paste this to Kilo Code) + +``` +## Kilo Code β€” BetterBase Bug Fix Pass + +You are Kilo Code operating on the BetterBase monorepo. Fix ALL issues listed below +in a single orchestrated pass. Do not skip minor issues. + +### Runtime/Stack +- Bun (use process.execPath in spawn, never "bun" string) +- Hono (packages/server), React Router v7 (apps/dashboard), Drizzle ORM +- BetterAuth, JWT HS256, bcrypt rounds=12 + +### Critical Invariants β€” do NOT break while fixing +1. /admin/auth/setup returns 410 if any admin exists +2. Admin keys are SHA-256 hashed; plaintext returned once only +3. audit_log has no update/delete routes +4. logWebhookDelivery must be awaited +5. Request logging and audit log writes are fire-and-forget +6. Dashboard colors use CSS variables only +7. Dashboard API calls go through src/lib/api.ts only +8. Query keys come from QK factory in src/lib/query-keys.ts +9. Destructive UI actions require ConfirmDialog +10. PUBLIC_COMMANDS must contain: ["login", "logout", "init", "--version", "-v", "--help", "-h", "-V"] + +--- + +### CRITICAL β€” Fix First + +#### [C-1] packages/cli/src/index.ts β€” init command action args swapped +File: packages/cli/src/index.ts +The `init` command's `.action()` handler has argument types reversed. +Commander passes (positionalArg, optionsObject, command). Current code has them swapped. +Fix: + WRONG: async (options: { iac?: boolean }, projectName?: string) + RIGHT: async (projectName: string | undefined, options: { iac?: boolean }) +After fix, verify: runInitCommand receives the actual project name string, not an object. + +--- + +### MAJOR β€” Fix Before Merge + +#### [M-1] packages/cli/src/index.ts β€” PUBLIC_COMMANDS missing raw flag strings +checkAuthHook reads process.argv[2] literally. "bb --version" has argv[2]="--version". +Add all flag variants: + const PUBLIC_COMMANDS = ["login", "logout", "version", "help", "init", + "--version", "-v", "--help", "-h", "-V"]; + +#### [M-2] packages/client/src/iac/provider.tsx β€” WebSocket reconnect is broken +In ws.onclose, a new WebSocket is created via setTimeout but has NO event handlers +(no onopen, onclose, onmessage). The reconnected socket never calls setWsReady(true), +never pings back, and never reconnects again if it drops. +Additionally, the cleanup function does not cancel the setTimeout, causing a post-unmount +state update and a leaked socket. + +Fix: Extract a connect() function that self-references in its own onclose. Track the +timeout ID and clear it in cleanup. Set isCleaned=true in cleanup to guard all callbacks. + +Full corrected useEffect: +```ts +useEffect(() => { + let timeoutId: ReturnType | null = null; + let isCleaned = false; + + function connect() { + if (isCleaned) return; + const wsUrl = `${config.url.replace(/^http/, "ws")}/betterbase/ws?project=${config.projectSlug ?? "default"}`; + const ws = new WebSocket(wsUrl); + wsRef.current = ws; + ws.onopen = () => { if (!isCleaned) setWsReady(true); }; + ws.onclose = () => { + if (isCleaned) return; + setWsReady(false); + timeoutId = setTimeout(connect, 3_000); + }; + ws.onmessage = (event) => { + const msg = JSON.parse(event.data); + if (msg.type === "ping") ws.send(JSON.stringify({ type: "pong" })); + }; + } + + connect(); + return () => { + isCleaned = true; + if (timeoutId !== null) clearTimeout(timeoutId); + wsRef.current?.close(); + }; +}, [config.url, config.projectSlug]); +``` + +#### [M-3] packages/cli/src/index.ts β€” branch default action reads wrong parameter +Commander passes (argValue, optionsObject, command). +Current: .action(async (options) => { options.projectRoot ... }) β€” options is the arg string. +Fix: + .action(async (projectRootArg: string, options: { projectRoot?: string }) => { + const projectRoot = projectRootArg || options.projectRoot || process.cwd(); + await runBranchCommand([], projectRoot); + }) + +#### [M-4] packages/cli/src/index.ts β€” fn deploy action reads wrong parameter +Same class of bug. fn deploy has arguments (name, projectRoot) + options. +Current: .action(async (name, options) => { options.projectRoot ... }) β€” always undefined. +Fix: + .action(async (name: string, projectRootArg: string, options: { syncEnv?: boolean }) => { + const projectRoot = projectRootArg ?? process.cwd(); + await runFunctionCommand(["deploy", name, options.syncEnv ? "--sync-env" : ""], projectRoot); + }) + +#### [M-5] Dockerfile.project β€” deps stage is unused dead code +The builder stage starts FROM base and re-runs bun install independently. +The deps stage is never referenced with COPY --from=deps. +Fix: Change builder to start FROM deps AS builder and remove the redundant +bun install line in the builder stage. + +#### [M-6] docker-compose.self-hosted.yml β€” nginx healthcheck wrong +Current: test: ["CMD", "nginx", "-t"] +This checks config syntax only, not whether nginx is actually serving HTTP. +Fix: + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost/health"] + interval: 30s + timeout: 10s + retries: 3 + +#### [M-7] docker-compose.yml β€” unpinned minio:latest and mc:latest +minio and minio-init use :latest. All other compose files pin to +RELEASE.2024-11-07T19-31-41Z and RELEASE.2024-11-08T03-47-05Z respectively. +Fix: pin to the same versions used in docker-compose.production.yml and +docker-compose.self-hosted.yml. + +--- + +### MINOR β€” Fix in Same Pass + +#### [m-1] apps/dashboard/src/vite-env.d.ts β€” VITE_API_URL typed as optional +VITE_API_URL is the only env var and is required at runtime. Change: + readonly VITE_API_URL?: string β†’ readonly VITE_API_URL: string + +#### [m-2] .github/workflows/ci.yml β€” bun-version: latest not reproducible +All 3 jobs use bun-version: latest. Pin to 1.3.9 (matches all Dockerfiles). +Also add caching: + - uses: oven-sh/setup-bun@v2 + with: + bun-version: "1.3.9" + # After checkout, before install: + - uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} + +#### [m-3] docker-compose.dev.yml β€” deprecated version field +Remove the `version: "3.9"` top-level field. It is ignored by Docker Compose v2 +and generates a deprecation warning. + +--- + +### Verification Steps (run after all fixes) +1. Confirm PUBLIC_COMMANDS contains all 10 entries listed in invariant `#10` above +2. Trace init, branch (default action), and fn deploy in packages/cli/src/index.ts β€” + verify each .action() parameter order matches Commander's (args..., options, command) convention +3. Inspect the WebSocket useEffect β€” confirm isCleaned guard, clearTimeout, and + all three handlers (onopen, onclose, onmessage) are wired on every connect() call +4. In Dockerfile.project, confirm `builder` stage uses `FROM deps AS builder` + and does NOT have a second `bun install` line +5. In docker-compose.self-hosted.yml, confirm nginx healthcheck hits localhost HTTP +6. In docker-compose.yml, confirm both minio and minio-init use pinned image tags +7. Run: bun typecheck on packages/cli and packages/client + +### Fix Summary Format +After completing all fixes, output: + [C-1] file:line β€” what was wrong β€” what was changed + [M-1] file:line β€” what was wrong β€” what was changed + ... (one line per issue) +``` + +
+βœ… Actions performed + +Review triggered. + +> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused. + +
+ +``` +------ + + +chore: trigger CodeRabbit review - dashboard components (5/7) #12 + + +```txt + +`@Helal-maker` β€” Review complete. Here's the full picture: + +--- + +## Review Summary + +This PR contains **no functional changes** β€” only trailing blank lines appended to 30+ component files. There are **no bugs, errors, logic issues, type errors, or security vulnerabilities** to fix. No exported APIs, component logic, or control flow were modified in any file. + +--- + +## Agent AI Prompt + +Since there are no bugs in this PR, the agent prompt below is a **template** based on the actual findings (none). You can reuse this format for future PRs where real issues exist: + +--- + +``` +You are a senior TypeScript/React engineer. Your task is to fix the following bugs +and issues found in the codebase during a code review. + +## Repository Context +- Framework: React + TypeScript +- App path: apps/dashboard/src/ +- Component paths: apps/dashboard/src/components/ + +## Bugs & Issues Found +> NONE β€” This PR contains only trailing whitespace changes across the following files: +> - CommandPalette.tsx, ErrorBoundary.tsx, LiveLogStream.tsx +> - auth/AuthGuard.tsx, auth/SetupGuard.tsx +> - ui/Avatar.tsx, ui/ConfirmDialog.tsx, ui/EmptyState.tsx, ui/PageHeader.tsx, +> ui/PageSkeleton.tsx, ui/StatCard.tsx +> - ui/alert-dialog.tsx, ui/badge.tsx, ui/button.tsx, ui/card.tsx, +> ui/collapsible.tsx, ui/dialog.tsx, ui/dropdown-menu.tsx, ui/input.tsx, +> ui/label.tsx, ui/popover.tsx, ui/progress.tsx, ui/scroll-area.tsx, +> ui/select.tsx, ui/separator.tsx, ui/sheet.tsx, ui/skeleton.tsx, +> ui/switch.tsx, ui/table.tsx, ui/tabs.tsx, ui/textarea.tsx, ui/tooltip.tsx +> +> No action required for this PR. + +## Instructions for Agent +1. Read each file listed under "Bugs & Issues Found". +2. Apply only the minimal change needed to fix the described issue. +3. Do NOT refactor unrelated code. +4. Do NOT change exported function signatures or component props. +5. After each fix, run TypeScript type checking to confirm no new errors are introduced. +6. Output a unified diff for each changed file. +``` + +--- + +For future PRs with real issues, I'll populate the `## Bugs & Issues Found` section with structured entries like: + +``` +### Bug 1 β€” [File path] +- **Line**: 42 +- **Issue**: [Description of the problem] +- **Fix**: [Exact change to make] +- **Severity**: Critical / High / Medium / Low +``` + +
+βœ… Actions performed + +Review triggered. + +> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused. + +
+```` + +----- + + + +chore: trigger CodeRabbit review - dashboard hooks, lib, layouts (6/7) +#11 + + + +```txt +Verify each finding against the current code and only fix it if needed. + +Outside diff comments: +In `@apps/dashboard/src/layouts/AppLayout.tsx`: +- Around line 66-73: The component is assigning window.onkeydown during render +which reassigns global handlers; move this logic into a useEffect inside +AppLayout: create a keydown handler function that checks (e.metaKey || +e.ctrlKey) && e.key === "k" and calls e.preventDefault() then setCmdOpen(true), +register it with window.addEventListener("keydown", handler) in the effect, and +remove it in the cleanup with window.removeEventListener("keydown", handler); do +not assign window.onkeydown directly and include setCmdOpen (or no deps if using +the stable setter) in the effect dependencies to ensure correct lifecycle. +``` + +------ + +chore: trigger CodeRabbit review - CLI utils & shared (2/7) +#8 + + + +```txt + +No actionable comments were generated in the recent review. πŸŽ‰ + +
+ℹ️ Recent review info + +
+βš™οΈ Run configuration + +**Configuration used**: Path: .coderabbit.yaml + +**Review profile**: ASSERTIVE + +**Plan**: Pro + +**Run ID**: `0bbdc724-6ed1-4028-bf7e-ed6c84e471fa` + +
+ +
+πŸ“₯ Commits + +Reviewing files that changed from the base of the PR and between 3ebfdf25598c83da4874c165dad32949363a6e7c and 959a29be2e1f4a99867c8b2e32b08a3da9fb9988. + +
+ +
+πŸ“’ Files selected for processing (19) + +* `packages/cli/src/utils/api-client.ts` +* `packages/cli/src/utils/context-generator.ts` +* `packages/cli/src/utils/credentials.ts` +* `packages/cli/src/utils/logger.ts` +* `packages/cli/src/utils/prompts.ts` +* `packages/cli/src/utils/provider-prompts.ts` +* `packages/cli/src/utils/route-scanner.ts` +* `packages/cli/src/utils/scanner.ts` +* `packages/cli/src/utils/schema-scanner.ts` +* `packages/cli/src/utils/spinner.ts` +* `packages/shared/src/constants.ts` +* `packages/shared/src/errors.ts` +* `packages/shared/src/index.ts` +* `packages/shared/src/types.ts` +* `packages/shared/src/utils.ts` +* `templates/iac/betterbase/cron.ts` +* `templates/iac/betterbase/mutations/todos.ts` +* `templates/iac/betterbase/queries/todos.ts` +* `templates/iac/betterbase/schema.ts` + +
+ +
+ +--- + + + + +## Walkthrough + +This PR contains whitespace-only edits and minor reformatting across 19 files in CLI utilities, shared modules, and template examples. No functional logic, control flow, types, exported APIs, or runtime behavior were changed. + +## Changes + +|Cohort / File(s)|Summary| +|---|---| +|**CLI Utilities**
`packages/cli/src/utils/api-client.ts`, `packages/cli/src/utils/context-generator.ts`, `packages/cli/src/utils/credentials.ts`, `packages/cli/src/utils/logger.ts`, `packages/cli/src/utils/prompts.ts`, `packages/cli/src/utils/provider-prompts.ts`, `packages/cli/src/utils/route-scanner.ts`, `packages/cli/src/utils/scanner.ts`, `packages/cli/src/utils/schema-scanner.ts`, `packages/cli/src/utils/spinner.ts`|Added trailing blank lines to 10 utility files; no changes to logic, types, exports, or runtime behavior.| +|**Shared Package**
`packages/shared/src/constants.ts`, `packages/shared/src/errors.ts`, `packages/shared/src/index.ts`, `packages/shared/src/types.ts`, `packages/shared/src/utils.ts`|Added trailing blank lines to 5 shared module files; no declaration, type, or API signature changes.| +|**Template Files**
`templates/iac/betterbase/cron.ts`, `templates/iac/betterbase/mutations/todos.ts`, `templates/iac/betterbase/queries/todos.ts`, `templates/iac/betterbase/schema.ts`|Added final newlines and adjusted indentation/whitespace in template examples; no changes to exported names, argument schemas, handlers, or runtime behavior.| + +## Estimated code review effort + +🎯 1 (Trivial) | ⏱️ ~2 minutes + +## Suggested labels + +`codex` + + + + + + +
+πŸš₯ Pre-merge checks | βœ… 1 | ❌ 2 + +### ❌ Failed checks (2 warnings) + +| Check name | Status | Explanation | Resolution | +| :----------------: | :--------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------- | +| Title check | ⚠️ Warning | Title claims to trigger CodeRabbit review but actual changes are only trailing whitespace additions across 20 files with no functional code modifications. | Use a title that accurately describes the changes, such as 'chore: add trailing newlines to CLI utils and shared packages' or similar. | +| Docstring Coverage | ⚠️ Warning | Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. | + +
+βœ… Passed checks (1 passed) + +| Check name | Status | Explanation | +| :---------------: | :------- | :---------------------------------------------------------- | +| Description Check | βœ… Passed | Check skipped - CodeRabbit’s high-level summary is enabled. | + +
+ +✏️ Tip: You can configure your own custom pre-merge checks in the settings. + +
+ + + + + +
+✨ Finishing Touches + +
+πŸ§ͺ Generate unit tests (beta) + +- [ ] Create PR with unit tests +- [ ] Commit unit tests in branch `trigger-coderabbit-2` + +
+ +
+ + + + + +--- + +Thanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=Helal-maker/Betterbase&utm_content=8)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. + +
+❀️ Share + +- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai) +- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai) +- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai) +- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code) + +
+ +Comment `@coderabbitai help` to get the list of available commands and usage tips. + + + + + + + + + + + + +``` +----- + + + +chore: trigger CodeRabbit review - templates & scripts (3/7) +#9 + + +```txt +Verify each finding against the current code and only fix it if needed. + +Inline comments: +In `@templates/auth/src/routes/auth.ts`: +- Line 107: The OTP and backup-code generation in auth.ts (variables like otp at +the Math.random() line and the backup/code generation around lines 169-171 and +291) use non-cryptographic Math.random() and must be replaced with a +cryptographically secure RNG; import Node's crypto and use +crypto.randomInt(100000, 1000000) to produce a uniformly distributed 6-digit OTP +(ensure string zero-padding if needed) and use crypto.randomBytes (then +hex/base32/BASE62-encode or map bytes to the allowed alphabet) to generate +backup codes of the required length/entropy, replacing usages of Math.random() +throughout auth.ts (e.g., where otp and backup codes are created) so all auth +secrets come from crypto-secure randomness. +- Around line 135-136: The conditional guards in the verification endpoints +incorrectly allow bypass because they check "process.env.NODE_ENV === +'development' || code.length === 6" (which is always true due to schema +validation) before issuing sessionId; remove the "|| code.length === 6" from +those conditionals and replace with actual verification logic: call the existing +OTP/verification routine or compare the provided code against the +stored/expected code (e.g., via a verifyCode/validateOtp function or DB lookup) +and only generate sessionId (crypto.randomUUID()) when that verification +succeeds; ensure the development-only bypass remains strictly tied to NODE_ENV +=== 'development' and update all occurrences that currently use the "code.length +=== 6" pattern. +- Around line 68-83: The current token check (token.startsWith("dev-token-")) in +the auth route unconditionally accepts dev magic links; restrict this behavior +so it only runs in development: guard the dev-branch with an environment/config +check (e.g., process.env.NODE_ENV === "development" or a feature flag) around +the token.startsWith check and its mock session creation (the code that calls +crypto.randomUUID() and returns the dev user object), and otherwise fall through +to the real verification path or reject the token; update any tests or comments +to reflect that dev-token handling is disabled outside development. + +In `@templates/base/src/lib/realtime.ts`: +- Around line 263-265: Subscription lookup uses client.subscriptions.get(table) +but subscriptions are keyed by `${table}:${event}`, causing subscription to be +undefined and filters in matchesFilter to be skipped; update the lookup where +subscription is retrieved (the code that currently does +client.subscriptions.get(table)) to use the composite key `${table}:${event}` +(or otherwise derive the correct key from the message's event and table) so that +subscription?.filter is the actual stored filter before calling +this.matchesFilter(subscription?.filter, data), ensuring filters are enforced. + +In `@templates/base/src/routes/index.ts`: +- Around line 16-23: The response currently exposes err.message and err.cause +for HTTP exceptions in non-development environments; change the logic so +sensitive details are only returned when env.NODE_ENV === "development". Update +the showDetailedError calculation (remove isHttpError from it) and restrict +stack and details to only be set when showDetailedError is true; for example +keep isHttpError only for setting status but ensure error: showDetailedError ? +err.message : "Internal Server Error" and details: showDetailedError && +isHttpError ? ((err as { cause?: unknown }).cause ?? null) : null so no +exception messages/causes are leaked in production (referencing +showDetailedError, isHttpError, env.NODE_ENV, err.message, err.stack, and the +details expression). + +In `@templates/base/src/routes/storage.ts`: +- Line 402: Update the three storage route patterns that currently use ":key" so +they accept nested paths by replacing each occurrence of "/:bucket/:key" with +Hono's regex form "/:bucket/:key{.+}" (and similarly "/:bucket/:key{.+}/public" +and "/:bucket/:key{.+}/sign"); locate the routes defined via +storageRouter.get(...) (the one starting at the shown diff and the two other +routes referenced) and change their route strings only, leaving parameter access +as c.req.param("key") unchanged. + +In `@templates/base/src/routes/users.ts`: +- Line 91: The POST handler currently validates request input but doesn't +persist the new user; replace the TODO by calling the DB insert to save the +parsed user (e.g., invoke db.insert(users).values(parsed) and await the result) +or call a dedicated UsersService (e.g., UsersService.create(parsed)) to persist +and return the persisted record/ID; ensure you handle and propagate DB errors +(try/catch) and return appropriate HTTP responses (201 on success with created +user or ID, 500 on DB error) from the POST handler. +```` +--- + +chore: trigger CodeRabbit review - CLI commands (1/7) +#7 + +```txt +Verify each finding against the current code and only fix it if needed. + +Outside diff comments: +In `@packages/cli/src/commands/dev/process-manager.ts`: +- Around line 21-23: The spawn call that sets this._proc currently hardcodes +"bun" as the runtime binary; update that spawn invocation (the code creating +this._proc via spawn) to use process.execPath as the executable and pass the +original arguments (e.g., "run" and entryPoint) as the args array, keeping cwd: +this._projectRoot unchanged so the CLI runtime invariant is respected; modify +the cmd/args in the spawn call (referencing spawn, this._proc, and entryPoint) +accordingly. + +In `@packages/cli/src/commands/init.ts`: +- Line 186: Replace the hardcoded "bun" argument in the Bun.spawn call that +creates installProcess with process.execPath so the spawned process uses the +current Node/Bun executable path; update the array passed to Bun.spawn in the +installDependencies/init logic (the installProcess creation) to use +process.execPath followed by "install" (i.e., [process.execPath, "install"]) and +ensure any related tests or callers expecting "bun" are adjusted accordingly. +``` + + diff --git a/Dockerfile.project b/Dockerfile.project index aa29501..6196aff 100644 --- a/Dockerfile.project +++ b/Dockerfile.project @@ -54,14 +54,10 @@ RUN bun install --frozen-lockfile # ---------------------------------------------------------------------------- # Stage 3: Builder # ---------------------------------------------------------------------------- -FROM base AS builder +FROM deps AS builder WORKDIR /app -# Copy lockfile and install all dependencies -COPY package.json bun.lock ./ -RUN bun install --frozen-lockfile - # Copy source code COPY . . diff --git a/apps/dashboard/src/vite-env.d.ts b/apps/dashboard/src/vite-env.d.ts index 6bb3492..42039ea 100644 --- a/apps/dashboard/src/vite-env.d.ts +++ b/apps/dashboard/src/vite-env.d.ts @@ -1,7 +1,7 @@ /// interface ImportMetaEnv { - readonly VITE_API_URL?: string; + readonly VITE_API_URL: string; } interface ImportMeta { diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 50ba660..d3b398a 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,5 +1,3 @@ -version: "3.9" - # Local development: runs Inngest dev server only. # BetterBase server runs outside Docker via: bun run dev # diff --git a/docker-compose.self-hosted.yml b/docker-compose.self-hosted.yml index e1309ca..67df7b3 100644 --- a/docker-compose.self-hosted.yml +++ b/docker-compose.self-hosted.yml @@ -148,7 +148,7 @@ services: volumes: - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro healthcheck: - test: ["CMD", "nginx", "-t"] + test: ["CMD", "wget", "-qO-", "http://localhost/health"] interval: 30s timeout: 10s retries: 3 diff --git a/docker-compose.yml b/docker-compose.yml index 111aa14..5bd9c99 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,7 +31,7 @@ services: # MinIO (S3-compatible storage) minio: - image: minio/minio:latest + image: minio/minio:RELEASE.2026-03-17T21-25-16Z container_name: betterbase-minio-local restart: unless-stopped command: server /data --console-address ":9001" @@ -51,7 +51,7 @@ services: # MinIO Init (Create bucket on startup) minio-init: - image: minio/mc:latest + image: minio/mc:RELEASE.2024-11-08T03-47-05Z container_name: betterbase-minio-init-local depends_on: minio: diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index ce7127e..f964e1b 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,5 +1,5 @@ -import { Command, CommanderError } from "commander"; import chalk from "chalk"; +import { Command, CommanderError } from "commander"; import packageJson from "../package.json"; import { runAuthAddProviderCommand, runAuthSetupCommand } from "./commands/auth"; import { runBranchCommand } from "./commands/branch"; @@ -29,7 +29,18 @@ import { runWebhookCommand } from "./commands/webhook"; import * as logger from "./utils/logger"; // Commands that don't require authentication -const PUBLIC_COMMANDS = ["login", "logout", "version", "help", "init"]; +const PUBLIC_COMMANDS = [ + "login", + "logout", + "version", + "help", + "init", + "--version", + "-v", + "--help", + "-h", + "-V", +]; /** * Check if the user is authenticated before running a command. @@ -120,7 +131,7 @@ export function createProgram(): Command { .description("Initialize a BetterBase project with BetterBase template (betterbase/ functions)") .option("--no-iac", "Use interactive mode instead of BetterBase template (for legacy projects)") .argument("[project-name]", "project name") - .action(async (options: { iac?: boolean }, projectName?: string) => { + .action(async (projectName: string | undefined, options: { iac?: boolean }) => { await runInitCommand({ projectName, ...options }); }); @@ -476,9 +487,11 @@ export function createProgram(): Command { .argument("", "function name") .option("--sync-env", "Sync environment variables from .env") .argument("[project-root]", "project root directory", process.cwd()) - .action(async (name: string, options: { syncEnv?: boolean; projectRoot?: string }) => { - const projectRoot = options.projectRoot ?? process.cwd(); - await runFunctionCommand(["deploy", name, options.syncEnv ? "--sync-env" : ""], projectRoot); + .action(async (name: string, projectRootArg: string, options: { syncEnv?: boolean }) => { + await runFunctionCommand( + ["deploy", name, options.syncEnv ? "--sync-env" : ""], + projectRootArg, + ); }); // ── bb login β€” STAGED FOR ACTIVATION ──────────────────────────────────────── @@ -541,10 +554,10 @@ export function createProgram(): Command { }); branch - .argument("[project-root]", "project root directory", process.cwd()) + .argument("[project-root]", "project root directory") .option("-p, --project-root ", "project root directory", process.cwd()) - .action(async (options) => { - const projectRoot = options.projectRoot || process.cwd(); + .action(async (projectRootArg: string, options: { projectRoot?: string }) => { + const projectRoot = projectRootArg || options.projectRoot || process.cwd(); await runBranchCommand([], projectRoot); }); diff --git a/packages/client/src/iac/provider.tsx b/packages/client/src/iac/provider.tsx index 891c435..e002dce 100644 --- a/packages/client/src/iac/provider.tsx +++ b/packages/client/src/iac/provider.tsx @@ -25,30 +25,53 @@ export function BetterbaseProvider({ const [wsReady, setWsReady] = React.useState(false); useEffect(() => { - const wsUrl = `${config.url.replace(/^http/, "ws")}/betterbase/ws?project=${config.projectSlug ?? "default"}`; - const ws = new WebSocket(wsUrl); + let timeoutId: ReturnType | null = null; + let isCleaned = false; + let reconnectDelayMs = 3_000; + const maxReconnectDelayMs = 30_000; - ws.onopen = () => { - setWsReady(true); - }; - ws.onclose = () => { - setWsReady(false); - // Reconnect after 3 seconds - setTimeout(() => { - wsRef.current = new WebSocket(wsUrl); - }, 3_000); - }; + function connect() { + if (isCleaned) return; + const wsUrl = `${config.url.replace(/^http/, "ws")}/betterbase/ws?project=${config.projectSlug ?? "default"}`; + const ws = new WebSocket(wsUrl); + wsRef.current = ws; - wsRef.current = ws; + ws.onopen = () => { + if (!isCleaned) { + setWsReady(true); + reconnectDelayMs = 3_000; + } + }; + ws.onerror = (err) => { + if (isCleaned) return; + console.error("WebSocket error", err); + setWsReady(false); + timeoutId = setTimeout(connect, reconnectDelayMs); + reconnectDelayMs = Math.min(reconnectDelayMs * 2, maxReconnectDelayMs); + }; + ws.onclose = () => { + if (isCleaned) return; + setWsReady(false); + timeoutId = setTimeout(connect, reconnectDelayMs); + reconnectDelayMs = Math.min(reconnectDelayMs * 2, maxReconnectDelayMs); + }; - // Handle pings - ws.onmessage = (event) => { - const msg = JSON.parse(event.data); - if (msg.type === "ping") ws.send(JSON.stringify({ type: "pong" })); - }; + ws.onmessage = (event) => { + try { + const msg = JSON.parse(event.data); + if (msg.type === "ping") ws.send(JSON.stringify({ type: "pong" })); + } catch { + return; + } + }; + } + + connect(); return () => { - ws.close(); + isCleaned = true; + if (timeoutId !== null) clearTimeout(timeoutId); + wsRef.current?.close(); }; }, [config.url, config.projectSlug]); From a9cf90f81a5bd45c2138a4ec7a0c7a727096f1df Mon Sep 17 00:00:00 2001 From: BroUnion Date: Tue, 31 Mar 2026 16:02:53 +0000 Subject: [PATCH 2/6] ci: simplify bun setup by enabling caching for faster installs docker: update MinIO images to latest stable versions cli: refactor deploy command argument handling for clarity provider: remove redundant WebSocket reconnection logic --- .github/workflows/ci.yml | 27 +++------------------------ docker-compose.yml | 4 ++-- packages/cli/src/index.ts | 9 ++++----- packages/client/src/iac/provider.tsx | 3 --- 4 files changed, 9 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e0e3a1e..0eb1dbf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,14 +16,7 @@ jobs: uses: oven-sh/setup-bun@v2 with: bun-version: "1.3.10" - - - name: Cache bun install - uses: actions/cache@v4 - with: - path: ~/.bun/install/cache - key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} - restore-keys: | - ${{ runner.os }}-bun- + cache: true - name: Install dependencies run: bun install --frozen-lockfile @@ -45,14 +38,7 @@ jobs: uses: oven-sh/setup-bun@v2 with: bun-version: "1.3.10" - - - name: Cache bun install - uses: actions/cache@v4 - with: - path: ~/.bun/install/cache - key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} - restore-keys: | - ${{ runner.os }}-bun- + cache: true - name: Install dependencies run: bun install --frozen-lockfile @@ -71,14 +57,7 @@ jobs: uses: oven-sh/setup-bun@v2 with: bun-version: "1.3.10" - - - name: Cache bun install - uses: actions/cache@v4 - with: - path: ~/.bun/install/cache - key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} - restore-keys: | - ${{ runner.os }}-bun- + cache: true - name: Install dependencies run: bun install --frozen-lockfile diff --git a/docker-compose.yml b/docker-compose.yml index 5bd9c99..6679b84 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,7 +31,7 @@ services: # MinIO (S3-compatible storage) minio: - image: minio/minio:RELEASE.2026-03-17T21-25-16Z + image: bitnami/minio:2025.1.16 container_name: betterbase-minio-local restart: unless-stopped command: server /data --console-address ":9001" @@ -51,7 +51,7 @@ services: # MinIO Init (Create bucket on startup) minio-init: - image: minio/mc:RELEASE.2024-11-08T03-47-05Z + image: minio/mc:RELEASE.2025-08-13T08-35-41Z container_name: betterbase-minio-init-local depends_on: minio: diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index f964e1b..df0a8dc 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -488,10 +488,9 @@ export function createProgram(): Command { .option("--sync-env", "Sync environment variables from .env") .argument("[project-root]", "project root directory", process.cwd()) .action(async (name: string, projectRootArg: string, options: { syncEnv?: boolean }) => { - await runFunctionCommand( - ["deploy", name, options.syncEnv ? "--sync-env" : ""], - projectRootArg, - ); + const args = ["deploy", name]; + if (options.syncEnv) args.push("--sync-env"); + await runFunctionCommand(args, projectRootArg); }); // ── bb login β€” STAGED FOR ACTIVATION ──────────────────────────────────────── @@ -556,7 +555,7 @@ export function createProgram(): Command { branch .argument("[project-root]", "project root directory") .option("-p, --project-root ", "project root directory", process.cwd()) - .action(async (projectRootArg: string, options: { projectRoot?: string }) => { + .action(async (projectRootArg: string | undefined, options: { projectRoot?: string }) => { const projectRoot = projectRootArg || options.projectRoot || process.cwd(); await runBranchCommand([], projectRoot); }); diff --git a/packages/client/src/iac/provider.tsx b/packages/client/src/iac/provider.tsx index e002dce..b2ee5ed 100644 --- a/packages/client/src/iac/provider.tsx +++ b/packages/client/src/iac/provider.tsx @@ -45,9 +45,6 @@ export function BetterbaseProvider({ ws.onerror = (err) => { if (isCleaned) return; console.error("WebSocket error", err); - setWsReady(false); - timeoutId = setTimeout(connect, reconnectDelayMs); - reconnectDelayMs = Math.min(reconnectDelayMs * 2, maxReconnectDelayMs); }; ws.onclose = () => { if (isCleaned) return; From afb4917838de96d0d502ba7eeb040967c1b34299 Mon Sep 17 00:00:00 2001 From: BroUnion Date: Tue, 31 Mar 2026 16:12:19 +0000 Subject: [PATCH 3/6] feat: add initial documentation for BetterBase including NOTICE, README, and SELF_HOSTED files --- 03_test_suite.md => specs/03_test_suite.md | 0 BETTERBASE.md => specs/BETTERBASE.md | 0 .../BetterBase_Competitive_Plan.md | 0 .../BetterBase_Dashboard_Backend_Spec.md | 0 .../BetterBase_Dashboard_Frontend_Spec.md | 0 .../BetterBase_IaC_Phase2_Spec.md | 0 .../BetterBase_IaC_Phase3_Spec.md | 0 .../BetterBase_InfraAsCode_Spec.md | 0 .../BetterBase_Inngest_Dashboard_Spec.md | 0 .../BetterBase_Inngest_Spec.md | 0 .../BetterBase_Observability_Spec.docx.md | 0 .../BetterBase_SelfHosted_Spec.md | 0 specs/CODEBASE_MAP.md | 2218 +++++++++++++++++ CONTRIBUTING.md => specs/CONTRIBUTING.md | 0 .../CodeRabbit_Full_Codebase_review.md | 0 NOTICE.md => specs/NOTICE.md | 0 specs/README.md | 647 +++++ SELF_HOSTED.md => specs/SELF_HOSTED.md | 0 18 files changed, 2865 insertions(+) rename 03_test_suite.md => specs/03_test_suite.md (100%) rename BETTERBASE.md => specs/BETTERBASE.md (100%) rename BetterBase_Competitive_Plan.md => specs/BetterBase_Competitive_Plan.md (100%) rename BetterBase_Dashboard_Backend_Spec.md => specs/BetterBase_Dashboard_Backend_Spec.md (100%) rename BetterBase_Dashboard_Frontend_Spec.md => specs/BetterBase_Dashboard_Frontend_Spec.md (100%) rename BetterBase_IaC_Phase2_Spec.md => specs/BetterBase_IaC_Phase2_Spec.md (100%) rename BetterBase_IaC_Phase3_Spec.md => specs/BetterBase_IaC_Phase3_Spec.md (100%) rename BetterBase_InfraAsCode_Spec.md => specs/BetterBase_InfraAsCode_Spec.md (100%) rename BetterBase_Inngest_Dashboard_Spec.md => specs/BetterBase_Inngest_Dashboard_Spec.md (100%) rename BetterBase_Inngest_Spec.md => specs/BetterBase_Inngest_Spec.md (100%) rename BetterBase_Observability_Spec.docx.md => specs/BetterBase_Observability_Spec.docx.md (100%) rename BetterBase_SelfHosted_Spec.md => specs/BetterBase_SelfHosted_Spec.md (100%) create mode 100644 specs/CODEBASE_MAP.md rename CONTRIBUTING.md => specs/CONTRIBUTING.md (100%) rename CodeRabbit_Full_Codebase_review.md => specs/CodeRabbit_Full_Codebase_review.md (100%) rename NOTICE.md => specs/NOTICE.md (100%) create mode 100644 specs/README.md rename SELF_HOSTED.md => specs/SELF_HOSTED.md (100%) diff --git a/03_test_suite.md b/specs/03_test_suite.md similarity index 100% rename from 03_test_suite.md rename to specs/03_test_suite.md diff --git a/BETTERBASE.md b/specs/BETTERBASE.md similarity index 100% rename from BETTERBASE.md rename to specs/BETTERBASE.md diff --git a/BetterBase_Competitive_Plan.md b/specs/BetterBase_Competitive_Plan.md similarity index 100% rename from BetterBase_Competitive_Plan.md rename to specs/BetterBase_Competitive_Plan.md diff --git a/BetterBase_Dashboard_Backend_Spec.md b/specs/BetterBase_Dashboard_Backend_Spec.md similarity index 100% rename from BetterBase_Dashboard_Backend_Spec.md rename to specs/BetterBase_Dashboard_Backend_Spec.md diff --git a/BetterBase_Dashboard_Frontend_Spec.md b/specs/BetterBase_Dashboard_Frontend_Spec.md similarity index 100% rename from BetterBase_Dashboard_Frontend_Spec.md rename to specs/BetterBase_Dashboard_Frontend_Spec.md diff --git a/BetterBase_IaC_Phase2_Spec.md b/specs/BetterBase_IaC_Phase2_Spec.md similarity index 100% rename from BetterBase_IaC_Phase2_Spec.md rename to specs/BetterBase_IaC_Phase2_Spec.md diff --git a/BetterBase_IaC_Phase3_Spec.md b/specs/BetterBase_IaC_Phase3_Spec.md similarity index 100% rename from BetterBase_IaC_Phase3_Spec.md rename to specs/BetterBase_IaC_Phase3_Spec.md diff --git a/BetterBase_InfraAsCode_Spec.md b/specs/BetterBase_InfraAsCode_Spec.md similarity index 100% rename from BetterBase_InfraAsCode_Spec.md rename to specs/BetterBase_InfraAsCode_Spec.md diff --git a/BetterBase_Inngest_Dashboard_Spec.md b/specs/BetterBase_Inngest_Dashboard_Spec.md similarity index 100% rename from BetterBase_Inngest_Dashboard_Spec.md rename to specs/BetterBase_Inngest_Dashboard_Spec.md diff --git a/BetterBase_Inngest_Spec.md b/specs/BetterBase_Inngest_Spec.md similarity index 100% rename from BetterBase_Inngest_Spec.md rename to specs/BetterBase_Inngest_Spec.md diff --git a/BetterBase_Observability_Spec.docx.md b/specs/BetterBase_Observability_Spec.docx.md similarity index 100% rename from BetterBase_Observability_Spec.docx.md rename to specs/BetterBase_Observability_Spec.docx.md diff --git a/BetterBase_SelfHosted_Spec.md b/specs/BetterBase_SelfHosted_Spec.md similarity index 100% rename from BetterBase_SelfHosted_Spec.md rename to specs/BetterBase_SelfHosted_Spec.md diff --git a/specs/CODEBASE_MAP.md b/specs/CODEBASE_MAP.md new file mode 100644 index 0000000..c981e62 --- /dev/null +++ b/specs/CODEBASE_MAP.md @@ -0,0 +1,2218 @@ +# BetterBase β€” Codebase Map + +> Last updated: 2026-03-30 + +## What is BetterBase? + +AI-native Backend-as-a-Service platform built with Bun. Define your backend in TypeScript using the Convex-inspired IaC layer, or use traditional Drizzle + Hono patterns. + +--- + +## Quick Start + +```bash +bun install -g @betterbase/cli +bb init my-app +cd my-app +bun install +bb dev +``` + +--- + +## Project Structure + +### IaC Pattern (Recommended) + +``` +my-app/ +β”œβ”€β”€ betterbase/ +β”‚ β”œβ”€β”€ schema.ts # defineSchema() + defineTable() +β”‚ β”œβ”€β”€ queries/ # query() functions (auto-realtime) +β”‚ β”œβ”€β”€ mutations/ # mutation() functions (transactions) +β”‚ β”œβ”€β”€ actions/ # action() functions (side-effects) +β”‚ └── cron.ts # scheduled functions +β”œβ”€β”€ betterbase.config.ts # Optional config +└── package.json +``` + +### Original Pattern (Advanced) + +``` +my-app/ +β”œβ”€β”€ src/ +β”‚ β”œβ”€β”€ db/schema.ts # Drizzle schema +β”‚ β”œβ”€β”€ routes/ # Hono routes +β”‚ └── functions/ # Serverless functions +└── package.json +``` + +Both patterns work together. + +--- + +## Package Architecture + +| Package | Purpose | +|---------|---------| +| `@betterbase/cli` | CLI tool (`bb` command) | +| `@betterbase/client` | TypeScript SDK for frontend | +| `@betterbase/core` | Core backend engine | +| `@betterbase/shared` | Shared utilities | +| `@betterbase/server` | Self-hosted admin API | +| `apps/dashboard` | Admin dashboard (React) | + +--- + +## Core IaC Modules + +### validators.ts +`v.string()`, `v.number()`, `v.id()`, etc. β€” Zod-backed validators + +### schema.ts +`defineSchema()`, `defineTable()` β€” schema definition with index builders + +### functions.ts +`query()`, `mutation()`, `action()` β€” function primitives with context types + +### db-context.ts +`DatabaseReader`, `DatabaseWriter` β€” typed DB access layer + +### function-registry.ts +Scans `betterbase/` directory, registers functions + +--- + +## CLI Commands + +| Command | Description | +|---------|-------------| +| `bb init` | Create new project | +| `bb dev` | Start dev server | +| `bb iac sync` | Sync schema to DB | +| `bb iac analyze` | Query diagnostics | +| `bb migrate` | Run migrations | +| `bb generate` | Generate types | + +--- + +## IaC Modules (`packages/core/src/iac/`) + +| File | Purpose | +|------|---------| +| `validators.ts` | `v.string()`, `v.number()`, `v.id()`, etc. β€” Zod-backed | +| `schema.ts` | `defineSchema()`, `defineTable()` with index builders | +| `functions.ts` | `query()`, `mutation()`, `action()` primitives | +| `db-context.ts` | `DatabaseReader`, `DatabaseWriter` | +| `function-registry.ts` | Scans `betterbase/`, registers functions | +| `schema-serializer.ts` | Serialize schema to JSON | +| `schema-diff.ts` | Diff two schemas, detect changes | +| `generators/drizzle-schema-gen.ts` | Generate Drizzle schema | +| `generators/migration-gen.ts` | Generate SQL migrations | +| `generators/api-typegen.ts` | Generate TypeScript types | +| `errors.ts` | Error classes with suggestions | +| `cron.ts` | `cron()` scheduled functions | + +--- + +## Dashboard (`apps/dashboard/`) + +React admin dashboard for self-hosted management. + +### Pages + +| Page | Route | Description | +|------|-------|-------------| +| Overview | `/` | Metrics, charts, activity | +| Setup | `/setup` | Initial setup | +| Login | `/login` | Authentication | +| NotFound | `/*` | 404 page | +| Projects | `/projects` | List all projects | +| Project Detail | `/projects/:id` | Project settings | +| Project Functions | `/projects/:id/functions` | Serverless functions | +| Project IaC Functions | `/projects/:id/iac/functions` | IaC function management | +| Project IaC Schema | `/projects/:id/iac/schema` | IaC schema view | +| Project IaC Jobs | `/projects/:id/iac/jobs` | IaC job history | +| Project IaC Query | `/projects/:id/iac/query` | IaC query diagnostics | +| Project IaC Realtime | `/projects/:id/iac/realtime` | IaC realtime settings | +| Project Database | `/projects/:id/database` | Database management | +| Project Auth | `/projects/:id/auth` | Auth configuration | +| Project Env | `/projects/:id/env` | Environment variables | +| Project Webhooks | `/projects/:id/webhooks` | Project webhooks | +| Project Users | `/projects/:id/users` | Project user management | +| Project User | `/projects/:id/users/:userId` | User detail page | +| Project Realtime | `/projects/:id/realtime` | Realtime settings | +| Project Observability | `/projects/:id/observability` | Observability metrics | +| Storage | `/storage` | Storage buckets | +| Storage Bucket | `/storage/:bucketId` | Bucket details | +| Logs | `/logs` | Request logs | +| Function Invocations | `/functions` | Function invocation logs | +| Webhook Deliveries | `/webhook-deliveries` | Webhook delivery logs | +| Team | `/team` | Team management | +| Audit | `/audit` | Audit log | +| Observability | `/observability` | System observability | +| Settings | `/settings` | Instance settings | +| Settings SMTP | `/settings/smtp` | SMTP configuration | +| Settings API Keys | `/settings/api-keys` | API key management | +| Settings Notifications | `/settings/notifications` | Notification settings | +| Settings Inngest | `/settings/inngest` | Inngest dashboard | + +### UI Components + +| Component | Description | +|-----------|-------------| +| label | Label component | +| tooltip | Tooltip component | +| scroll-area | Scroll area wrapper | +| separator | Vertical/horizontal separator | +| popover | Popover component | +| switch | Toggle switch | +| textarea | Text area input | +| progress | Progress bar | +| skeleton | Loading skeleton | +| sheet | Slide-out panel | +| badge | Status badge | +| collapsible | Collapsible content | +| Avatar | User avatar | +| ConfirmDialog | Confirmation dialog | +| LiveLogStream | Live log streaming | +| CommandPalette | Command palette (Ctrl+K) | +| ErrorBoundary | Error boundary wrapper | + +### Hooks + +| Hook | Description | +|------|-------------| +| useLogStream | Live log streaming hook | +| useGlobalMetrics | Global metrics hook | +| useTheme | Theme management hook | + +### Libs + +| File | Description | +|------|-------------| +| query-keys.ts | TanStack Query keys | +| inngest-client.ts | Inngest client | +| api.ts | API client | +| utils.ts | Utility functions | + +### Layouts + +| Layout | Description | +|--------|-------------| +| AppLayout | Main application layout | + +### Tech Stack + +- React Router v7 +- TanStack Query v5 +- Tailwind CSS v4 +- shadcn/ui components +- Recharts for charts + +### Architecture Flow Diagram + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ CLIENT LAYER β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Web SDK β”‚ β”‚ React Hooks β”‚ β”‚ Mobile β”‚ β”‚ GraphQL β”‚ β”‚ +β”‚ β”‚@betterbase β”‚ β”‚ @betterbase β”‚ β”‚ SDK β”‚ β”‚ Client β”‚ β”‚ +β”‚ β”‚ /client β”‚ β”‚ /client β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ β”‚ β”‚ + β–Ό β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ API GATEWAY (Hono) β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ REST API β”‚ β”‚ GraphQL β”‚ β”‚ Auth β”‚ β”‚ Storage β”‚ β”‚ Realtime β”‚ β”‚ Webhooks β”‚ β”‚ +β”‚ β”‚/api/v1/* β”‚ β”‚ /graphqlβ”‚ β”‚/api/authβ”‚ β”‚/storage β”‚ β”‚/realtime β”‚ β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ + β–Ό β–Ό β–Ό β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ CORE SERVICES LAYER β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Query β”‚ β”‚ Auth β”‚ β”‚ Realtime β”‚ β”‚ Storage β”‚ β”‚ Function β”‚ β”‚ Webhook β”‚ β”‚ +β”‚ β”‚ Engine β”‚ β”‚ Service β”‚ β”‚ Service β”‚ β”‚ Service β”‚ β”‚ Runtime β”‚ β”‚ Dispatch β”‚ β”‚ +β”‚ β”‚(Drizzle) β”‚ β”‚(Better β”‚ β”‚(WebSocketβ”‚ β”‚ (S3) β”‚ β”‚ (Bun) β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ Auth) β”‚ β”‚) β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ DATA LAYER β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ SQLite β”‚ β”‚PostgreSQLβ”‚ β”‚ MySQL β”‚ β”‚ Neon β”‚ β”‚ Turso β”‚ β”‚ Supabase β”‚ β”‚ +β”‚ β”‚ (dev) β”‚ β”‚ β”‚ β”‚ β”‚ β”‚(serverlessβ”‚ β”‚ (libSQL) β”‚ β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ STORAGE LAYER β”‚ β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ β”‚ β”‚ AWS S3 β”‚ β”‚Cloudflareβ”‚ β”‚Backblaze β”‚ β”‚ MinIO β”‚ β”‚ Local β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ R2 β”‚ β”‚ B2 β”‚ β”‚ β”‚ β”‚ Disk β”‚ β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Self-Hosted Deployment Architecture + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ SELF-HOSTED DEPLOYMENT β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ External Clients β”‚ β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ β”‚ β”‚ Web App β”‚ β”‚ CLI (bb) β”‚ β”‚ Mobile β”‚ β”‚ Dashboard β”‚ β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β–Ό β–Ό β–Ό β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ NGINX Reverse Proxy β”‚ β”‚ +β”‚ β”‚ (docker/nginx/nginx.conf) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β–Ό β–Ό β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Dashboard β”‚ β”‚ Server β”‚ β”‚ Inngest β”‚ β”‚ +β”‚ β”‚ (React App) β”‚ β”‚ (@betterbase β”‚ β”‚ (Workflow β”‚ β”‚ +β”‚ β”‚ Behind nginx β”‚ β”‚ /server) β”‚ β”‚ Engine) β”‚ β”‚ +β”‚ β”‚ (not direct) β”‚ β”‚ Port: 3001 β”‚ β”‚ Port: 8288 β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ PostgreSQL β”‚ β”‚ +β”‚ β”‚ (Database) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +``` +betterbase/ +β”œβ”€β”€ package.json # Root workspace config (name: "betterbase") +β”œβ”€β”€ turbo.json # Turborepo task configuration +β”œβ”€β”€ tsconfig.base.json # Shared TypeScript config (ES2022, strict) +β”œβ”€β”€ bun.lock # Bun lockfile +β”œβ”€β”€ CODEBASE_MAP.md # This file +β”œβ”€β”€ README.md # Project documentation +β”œβ”€β”€ .gitignore # Git ignore patterns +β”œβ”€β”€ .npmignore # npm ignore patterns +β”‚ +β”œβ”€β”€ packages/ +β”‚ β”œβ”€β”€ cli/ # @betterbase/cli - CLI tool (bb command) +β”‚ β”‚ β”œβ”€β”€ package.json +β”‚ β”‚ β”œβ”€β”€ tsconfig.json +β”‚ β”‚ β”œβ”€β”€ src/ +β”‚ β”‚ β”‚ β”œβ”€β”€ index.ts # Main CLI entry point +β”‚ β”‚ β”‚ β”œβ”€β”€ build.ts # Build script +β”‚ β”‚ β”‚ β”œβ”€β”€ constants.ts # Shared constants +β”‚ β”‚ β”‚ β”œβ”€β”€ commands/ # CLI commands (20+ files) +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ auth.ts # bb auth setup - BetterAuth integration +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ auth-providers.ts # bb auth add-provider - OAuth provider management +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ dev.ts # bb dev - Development server with watch +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ function.ts # bb function - Edge function management +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ generate.ts # bb generate crud - CRUD route generation +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ graphql.ts # bb graphql - GraphQL management +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ init.ts # bb init - Project initialization +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ login.ts # bb login - Cloud authentication +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ migrate.ts # bb migrate - Database migrations +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ migrate-utils.ts # Migration utilities +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ rls.ts # bb rls - Row Level Security management +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ rls-test.ts # bb rls test - RLS policy testing +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ storage.ts # bb storage - Storage bucket management +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ webhook.ts # bb webhook - Webhook management +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ branch.ts # bb branch - Branch management +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ iac/ # IaC commands (NEW in Phase 3) +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ analyze.ts # bb iac analyze - Query diagnostics +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ export.ts # bb iac export - Data export +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ import.ts # bb iac import - Data import +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ generate.ts # bb iac generate - Function code gen +β”‚ β”‚ β”‚ β”‚ β”‚ └── sync.ts # bb iac sync - Schema sync +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ migrate/ # Migration tools +β”‚ β”‚ β”‚ β”‚ β”‚ └── from-convex.ts # bb migrate from-convex +β”‚ β”‚ β”‚ β”‚ └── dev/ # Dev mode utilities +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ process-manager.ts # Server process management +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ watcher.ts # File watcher for hot reload +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ error-formatter.ts # Error formatting +β”‚ β”‚ β”‚ β”‚ └── query-log.ts # Query logging (NEW) +β”‚ β”‚ β”‚ └── utils/ # CLI utilities (9 files) +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ api-client.ts # API client for cloud operations +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ context-generator.ts # Generates .betterbase-context.json +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ credentials.ts # Credentials management +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ logger.ts # Colored console logging +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ prompts.ts # Interactive prompts (Inquirer) +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ provider-prompts.ts # Database provider selection +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ route-scanner.ts # Hono route scanning +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ schema-scanner.ts # Drizzle schema scanning +β”‚ β”‚ β”‚ β”‚ └── scanner.ts # Schema scanner core +β”‚ β”‚ β”‚ β”‚ β”‚ └── spinner.ts # Spinner utilities +β”‚ β”‚ └── test/ # CLI tests (30+ test files) +β”‚ β”‚ +β”‚ β”œβ”€β”€ client/ # @betterbase/client - TypeScript SDK +β”‚ β”‚ β”œβ”€β”€ package.json +β”‚ β”‚ β”œβ”€β”€ tsconfig.json +β”‚ β”‚ β”œβ”€β”€ tsconfig.test.json +β”‚ β”‚ β”œβ”€β”€ README.md +β”‚ β”‚ β”œβ”€β”€ src/ # Client SDK source +β”‚ β”‚ β”‚ β”œβ”€β”€ index.ts # Package exports +β”‚ β”‚ β”‚ β”œβ”€β”€ auth.ts # Authentication client +β”‚ β”‚ β”‚ β”œβ”€β”€ build.ts # Build configuration +β”‚ β”‚ β”‚ β”œβ”€β”€ client.ts # Main client factory +β”‚ β”‚ β”‚ β”œβ”€β”€ errors.ts # Client error classes +β”‚ β”‚ β”‚ β”œβ”€β”€ query-builder.ts # Chainable query builder +β”‚ β”‚ β”‚ β”œβ”€β”€ realtime.ts # Realtime subscription client +β”‚ β”‚ β”‚ β”œβ”€β”€ storage.ts # Storage client +β”‚ β”‚ β”‚ β”œβ”€β”€ types.ts # TypeScript definitions +β”‚ β”‚ β”‚ └── iac/ # IaC client (Phase 3) +β”‚ β”‚ β”‚ β”œβ”€β”€ index.ts # IaC exports +β”‚ β”‚ β”‚ β”œβ”€β”€ hooks.ts # React hooks (useQuery, useMutation, useAction) +β”‚ β”‚ β”‚ β”œβ”€β”€ vanilla.ts # Non-React client +β”‚ β”‚ β”‚ β”œβ”€β”€ paginated-query.ts # Paginated query support +β”‚ β”‚ β”‚ └── embeddings.ts # Embedding utilities +β”‚ β”‚ └── test/ # Client tests (10+ test files) +β”‚ β”‚ +β”‚ β”œβ”€β”€ core/ # @betterbase/core - Core backend engine +β”‚ β”‚ β”œβ”€β”€ package.json +β”‚ β”‚ β”œβ”€β”€ README.md +β”‚ β”‚ β”œβ”€β”€ tsconfig.json +β”‚ β”‚ └── src/ +β”‚ β”‚ β”œβ”€β”€ index.ts # Core exports +β”‚ β”‚ β”œβ”€β”€ auto-rest.ts # Auto-REST: Automatic CRUD route generation +β”‚ β”‚ β”œβ”€β”€ config/ # Configuration modules +β”‚ β”‚ β”‚ β”œβ”€β”€ index.ts # Config exports +β”‚ β”‚ β”‚ β”œβ”€β”€ schema.ts # Project config schema (Zod) +β”‚ β”‚ β”‚ └── drizzle-generator.ts # Drizzle config generator +β”‚ β”‚ β”œβ”€β”€ functions/ # Serverless functions +β”‚ β”‚ β”‚ β”œβ”€β”€ index.ts # Functions exports +β”‚ β”‚ β”‚ β”œβ”€β”€ bundler.ts # Function bundler (esbuild) +β”‚ β”‚ β”‚ β”œβ”€β”€ deployer.ts # Function deployer +β”‚ β”‚ β”‚ └── local-runtime.ts # Local functions runtime +β”‚ β”‚ β”œβ”€β”€ graphql/ # GraphQL server +β”‚ β”‚ β”‚ β”œβ”€β”€ index.ts # GraphQL exports +β”‚ β”‚ β”‚ β”œβ”€β”€ resolvers.ts # GraphQL resolvers +β”‚ β”‚ β”‚ β”œβ”€β”€ schema-generator.ts # Schema from DB +β”‚ β”‚ β”‚ β”œβ”€β”€ sdl-exporter.ts # SDL export +β”‚ β”‚ β”‚ β”œβ”€β”€ server.ts # GraphQL HTTP server +β”‚ β”‚ β”‚ └── realtime-bridge.ts # GraphQL subscriptions bridge +β”‚ β”‚ β”œβ”€β”€ middleware/ # Middleware +β”‚ β”‚ β”‚ β”œβ”€β”€ index.ts # Middleware exports +β”‚ β”‚ β”‚ β”œβ”€β”€ rls-session.ts # RLS session middleware +β”‚ β”‚ β”‚ └── request-logger.ts # Request logging middleware +β”‚ β”‚ β”œβ”€β”€ migration/ # Database migrations +β”‚ β”‚ β”‚ β”œβ”€β”€ index.ts # Migration exports +β”‚ β”‚ β”‚ └── rls-migrator.ts # RLS policy migration +β”‚ β”‚ β”œβ”€β”€ providers/ # Database providers +β”‚ β”‚ β”‚ β”œβ”€β”€ index.ts # Provider exports +β”‚ β”‚ β”‚ β”œβ”€β”€ types.ts # Provider interfaces +β”‚ β”‚ β”‚ β”œβ”€β”€ neon.ts # Neon serverless PostgreSQL +β”‚ β”‚ β”‚ β”œβ”€β”€ planetscale.ts # PlanetScale MySQL +β”‚ β”‚ β”‚ β”œβ”€β”€ postgres.ts # PostgreSQL +β”‚ β”‚ β”‚ β”œβ”€β”€ supabase.ts # Supabase-compatible +β”‚ β”‚ β”‚ └── turso.ts # Turso libSQL +β”‚ β”‚ β”œβ”€β”€ rls/ # Row Level Security +β”‚ β”‚ β”‚ β”œβ”€β”€ index.ts # RLS exports +β”‚ β”‚ β”‚ β”œβ”€β”€ types.ts # RLS type definitions +β”‚ β”‚ β”‚ β”œβ”€β”€ scanner.ts # RLS policy scanner +β”‚ β”‚ β”‚ β”œβ”€β”€ generator.ts # RLS policy generator +β”‚ β”‚ β”‚ β”œβ”€β”€ evaluator.ts # RLS policy evaluator (SQLite) +β”‚ β”‚ β”‚ └── auth-bridge.ts # Auth-RLS bridge +β”‚ β”‚ β”œβ”€β”€ storage/ # Storage adapter +β”‚ β”‚ β”‚ β”œβ”€β”€ index.ts # Storage exports +β”‚ β”‚ β”‚ β”œβ”€β”€ types.ts # Storage types +β”‚ β”‚ β”‚ β”œβ”€β”€ s3-adapter.ts # S3-compatible adapter +β”‚ β”‚ β”‚ β”œβ”€β”€ image-transformer.ts # Image transformations (Sharp) +β”‚ β”‚ β”‚ └── policy-engine.ts # Storage policy engine +β”‚ β”‚ β”œβ”€β”€ webhooks/ # Webhook handling +β”‚ β”‚ β”‚ β”œβ”€β”€ index.ts # Webhook exports +β”‚ β”‚ β”‚ β”œβ”€β”€ types.ts # Webhook types +β”‚ β”‚ β”‚ β”œβ”€β”€ dispatcher.ts # Event dispatcher +β”‚ β”‚ β”‚ β”œβ”€β”€ integrator.ts # DB trigger integration +β”‚ β”‚ β”‚ β”œβ”€β”€ signer.ts # Payload signing +β”‚ β”‚ β”‚ β”œβ”€β”€ startup.ts # Server initialization +β”‚ β”‚ β”‚ └── schema.sql # Webhook schema +β”‚ β”‚ β”œβ”€β”€ vector/ # Vector search (pgvector) +β”‚ β”‚ β”‚ β”œβ”€β”€ index.ts # Vector exports +β”‚ β”‚ β”‚ β”œβ”€β”€ types.ts # Vector column types +β”‚ β”‚ β”‚ β”œβ”€β”€ embeddings.ts # Embedding providers (OpenAI, Cohere) +β”‚ β”‚ β”‚ └── search.ts # Vector similarity search +β”‚ β”‚ β”œβ”€β”€ branching/ # Preview environments +β”‚ β”‚ β”‚ β”œβ”€β”€ index.ts # Branching exports +β”‚ β”‚ β”‚ β”œβ”€β”€ types.ts # Branch types +β”‚ β”‚ β”‚ β”œβ”€β”€ database.ts # Database branching +β”‚ β”‚ β”‚ └── storage.ts # Storage branching +β”‚ β”‚ β”œβ”€β”€ logger/ # Logging +β”‚ β”‚ β”‚ β”œβ”€β”€ index.ts # Logger exports +β”‚ β”‚ β”‚ └── file-transport.ts # File transport +β”‚ β”‚ └── realtime/ # Realtime subscriptions +β”‚ β”‚ β”œβ”€β”€ index.ts # Realtime exports +β”‚ β”‚ └── channel-manager.ts # Channel manager +β”‚ β”‚ +β”‚ β”œβ”€β”€ shared/ # @betterbase/shared - Shared utilities +β”‚ β”‚ β”œβ”€β”€ package.json +β”‚ β”‚ β”œβ”€β”€ tsconfig.json +β”‚ β”‚ └── src/ +β”‚ β”‚ β”œβ”€β”€ index.ts # Main exports +β”‚ β”‚ β”œβ”€β”€ types.ts # Shared types +β”‚ β”‚ β”œβ”€β”€ errors.ts # Shared error classes +β”‚ β”‚ β”œβ”€β”€ constants.ts # Shared constants +β”‚ β”‚ └── utils.ts # Utility functions +β”‚ β”‚ +β”‚ β”œβ”€β”€ server/ # @betterbase/server - Self-hosted server +β”‚ β”‚ β”œβ”€β”€ package.json +β”‚ β”‚ β”œβ”€β”€ tsconfig.json +β”‚ β”‚ β”œβ”€β”€ Dockerfile +β”‚ β”‚ β”œβ”€β”€ migrations/ # Database migrations +β”‚ β”‚ β”‚ β”œβ”€β”€ 001_initial_schema.sql +β”‚ β”‚ β”‚ β”œβ”€β”€ 002_admin_users.sql +β”‚ β”‚ β”‚ β”œβ”€β”€ 003_projects.sql +β”‚ β”‚ β”‚ β”œβ”€β”€ 004_logs.sql +β”‚ β”‚ β”‚ └── 014_inngest_support.sql +β”‚ β”‚ └── src/ +β”‚ β”‚ β”œβ”€β”€ index.ts # Server entry point +β”‚ β”‚ β”œβ”€β”€ types.d.ts # TypeScript declarations +β”‚ β”‚ β”œβ”€β”€ lib/ +β”‚ β”‚ β”‚ β”œβ”€β”€ db.ts # Database connection +β”‚ β”‚ β”‚ β”œβ”€β”€ migrate.ts # Migration runner +β”‚ β”‚ β”‚ β”œβ”€β”€ env.ts # Environment validation +β”‚ β”‚ β”‚ β”œβ”€β”€ auth.ts # Auth utilities +β”‚ β”‚ β”‚ β”œβ”€β”€ admin-middleware.ts # Admin auth middleware +β”‚ β”‚ β”‚ β”œβ”€β”€ inngest.ts # Inngest client & functions +β”‚ β”‚ β”‚ β”œβ”€β”€ webhook-dispatcher.ts # Webhook event dispatcher +β”‚ β”‚ β”‚ β”œβ”€β”€ webhook-logger.ts # Webhook delivery logging +β”‚ β”‚ β”‚ β”œβ”€β”€ function-logger.ts # Function invocation logging +β”‚ β”‚ β”‚ └── audit.ts # Audit logging +β”‚ β”‚ └── routes/ +β”‚ β”‚ β”œβ”€β”€ admin/ # Admin API routes +β”‚ β”‚ β”‚ β”œβ”€β”€ index.ts +β”‚ β”‚ β”‚ β”œβ”€β”€ auth.ts +β”‚ β”‚ β”‚ β”œβ”€β”€ projects.ts +β”‚ β”‚ β”‚ β”œβ”€β”€ users.ts +β”‚ β”‚ β”‚ β”œβ”€β”€ metrics.ts +β”‚ β”‚ β”‚ β”œβ”€β”€ metrics-enhanced.ts # Enhanced metrics +β”‚ β”‚ β”‚ β”œβ”€β”€ storage.ts +β”‚ β”‚ β”‚ β”œβ”€β”€ webhooks.ts +β”‚ β”‚ β”‚ β”œβ”€β”€ functions.ts +β”‚ β”‚ β”‚ β”œβ”€β”€ logs.ts +β”‚ β”‚ β”‚ β”œβ”€β”€ audit.ts +β”‚ β”‚ β”‚ β”œβ”€β”€ roles.ts # Role management +β”‚ β”‚ β”‚ β”œβ”€β”€ notifications.ts # Notification system +β”‚ β”‚ β”‚ β”œβ”€β”€ smtp.ts # SMTP configuration +β”‚ β”‚ β”‚ β”œβ”€β”€ api-keys.ts # API key management +β”‚ β”‚ β”‚ β”œβ”€β”€ cli-sessions.ts # CLI session management +β”‚ β”‚ β”‚ β”œβ”€β”€ inngest.ts # Inngest integration +β”‚ β”‚ β”‚ β”œβ”€β”€ instance.ts # Instance settings +β”‚ β”‚ β”‚ └── project-scoped/ # Project-specific routes +β”‚ β”‚ β”‚ β”œβ”€β”€ index.ts +β”‚ β”‚ β”‚ β”œβ”€β”€ database.ts # Database management +β”‚ β”‚ β”‚ β”œβ”€β”€ functions.ts # Function management +β”‚ β”‚ β”‚ β”œβ”€β”€ users.ts # Project users +β”‚ β”‚ β”‚ β”œβ”€β”€ env.ts # Environment variables +β”‚ β”‚ β”‚ β”œβ”€β”€ auth-config.ts # Auth configuration +β”‚ β”‚ β”‚ β”œβ”€β”€ webhooks.ts # Project webhooks +β”‚ β”‚ β”‚ β”œβ”€β”€ realtime.ts # Realtime settings +β”‚ β”‚ β”‚ └── iac.ts # IaC management +β”‚ β”‚ β”œβ”€β”€ betterbase/ # BetterBase IaC API routes +β”‚ β”‚ β”‚ β”œβ”€β”€ index.ts # Main IaC API handler +β”‚ β”‚ β”‚ └── ws.ts # WebSocket handler +β”‚ β”‚ └── device/ # Device auth routes +β”‚ β”‚ └── index.ts +β”‚ β”‚ +β”œβ”€β”€ apps/ +β”‚ β”œβ”€β”€ dashboard/ # Admin dashboard for self-hosted +β”‚ β”‚ β”œβ”€β”€ Dockerfile +β”‚ └── test-project/ # Example/test project +β”‚ β”œβ”€β”€ betterbase.config.ts # Project configuration +β”‚ β”œβ”€β”€ drizzle.config.ts # Drizzle configuration +β”‚ β”œβ”€β”€ package.json +β”‚ β”œβ”€β”€ tsconfig.json +β”‚ β”œβ”€β”€ README.md +β”‚ β”œβ”€β”€ src/ +β”‚ β”‚ β”œβ”€β”€ index.ts # App entry point (Hono server) +β”‚ β”‚ β”œβ”€β”€ auth/ +β”‚ β”‚ β”‚ β”œβ”€β”€ index.ts # Auth module +β”‚ β”‚ β”‚ └── types.ts # Auth types +β”‚ β”‚ β”œβ”€β”€ db/ +β”‚ β”‚ β”‚ β”œβ”€β”€ index.ts # Database setup +β”‚ β”‚ β”‚ β”œβ”€β”€ migrate.ts # Migration runner +β”‚ β”‚ β”‚ β”œβ”€β”€ schema.ts # Database schema +β”‚ β”‚ β”‚ └── policies/ # RLS policies +β”‚ β”‚ β”‚ └── .gitkeep +β”‚ β”‚ β”œβ”€β”€ functions/ # Serverless functions +β”‚ β”‚ β”‚ └── hello/ # Example function +β”‚ β”‚ β”‚ └── index.ts +β”‚ β”‚ β”œβ”€β”€ lib/ +β”‚ β”‚ β”‚ β”œβ”€β”€ env.ts # Environment vars +β”‚ β”‚ β”‚ └── realtime.ts # Realtime events +β”‚ β”‚ β”œβ”€β”€ middleware/ +β”‚ β”‚ β”‚ β”œβ”€β”€ auth.ts # Auth middleware +β”‚ β”‚ β”‚ └── validation.ts # Validation middleware +β”‚ β”‚ └── routes/ +β”‚ β”‚ β”œβ”€β”€ index.ts # Routes registration +β”‚ β”‚ β”œβ”€β”€ health.ts # Health check +β”‚ β”‚ β”œβ”€β”€ storage.ts # Storage routes +β”‚ β”‚ β”œβ”€β”€ users.ts # User CRUD routes +β”‚ β”‚ β”œβ”€β”€ webhooks.ts # Webhook routes +β”‚ β”‚ └── graphql.d.ts # GraphQL types +β”‚ └── test/ # Project tests +β”‚ β”œβ”€β”€ crud.test.ts +β”‚ └── health.test.ts +β”‚ +β”œβ”€β”€ templates/ +β”‚ β”œβ”€β”€ base/ # Base project template +β”‚ β”‚ β”œβ”€β”€ src/ +β”‚ β”‚ β”‚ β”œβ”€β”€ index.ts # Main entry point +β”‚ β”‚ β”‚ β”œβ”€β”€ routes/ # API routes +β”‚ β”‚ β”‚ β”œβ”€β”€ functions/ # Edge functions +β”‚ β”‚ β”‚ β”œβ”€β”€ auth/ # Authentication +β”‚ β”‚ β”‚ β”œβ”€β”€ middleware/ # Middleware +β”‚ β”‚ β”‚ β”œβ”€β”€ lib/ # Utilities +β”‚ β”‚ β”‚ └── db/ # Database (schema, migrations) +β”‚ β”‚ β”œβ”€β”€ test/ +β”‚ β”‚ β”œβ”€β”€ betterbase.config.ts +β”‚ β”‚ β”œβ”€β”€ drizzle.config.ts +β”‚ β”‚ β”œβ”€β”€ package.json +β”‚ β”‚ └── README.md +β”‚ β”‚ +β”‚ └── auth/ # Auth project template +β”‚ β”œβ”€β”€ src/ +β”‚ β”‚ β”œβ”€β”€ routes/ +β”‚ β”‚ β”‚ β”œβ”€β”€ auth.ts # Auth routes +β”‚ β”‚ β”‚ └── auth-example.ts +β”‚ β”‚ β”œβ”€β”€ auth/ +β”‚ β”‚ β”‚ β”œβ”€β”€ index.ts +β”‚ β”‚ β”‚ └── types.ts +β”‚ β”‚ β”œβ”€β”€ db/ +β”‚ β”‚ β”‚ β”œβ”€β”€ schema.ts +β”‚ β”‚ β”‚ β”œβ”€β”€ auth-schema.ts +β”‚ β”‚ β”‚ └── index.ts +β”‚ β”‚ └── middleware/ +β”‚ β”‚ └── auth.ts +β”‚ β”œβ”€β”€ README.md +β”‚ └── (template files) +β”‚ +β”œβ”€β”€ cli-auth-page/ # Standalone auth page for CLI (not a template) +β”‚ β”œβ”€β”€ .gitignore +β”‚ └── index.html # Auth UI entry +β”‚ +β”œβ”€β”€ new-features-docs/ # Documentation for new features +β”‚ └── README_START_HERE.md +β”‚ +└── scripts/ # Build/release scripts +``` + +--- + +## Docker Deployment + +Betterbase includes production-ready Docker configuration for self-hosted deployment. + +### Docker Files + +| File | Purpose | +|------|---------| +| `Dockerfile` | Monorepo build (for developing Betterbase itself) | +| `Dockerfile.project` | Project template for deploying user projects | +| `docker-compose.yml` | Development environment with PostgreSQL | +| `docker-compose.dev.yml` | Inngest dev server for local development | +| `docker-compose.production.yml` | Production-ready configuration | +| `docker-compose.self-hosted.yml` | Self-hosted deployment with dashboard | +| `docker/nginx/nginx.conf` | Nginx reverse proxy configuration | +| `.dockerignore` | Optimizes Docker builds | +| `.env.example` | Environment variable template | + +### Quick Start + +```bash +# Development with Docker Compose +docker-compose up -d + +# Production deployment +docker-compose -f docker-compose.production.yml up -d +``` + +### Docker Features + +- **Multi-stage builds** for minimal image size +- **PostgreSQL** included in dev environment +- **Inngest** for durable workflows and background jobs +- **Health checks** for reliability +- **Non-root user** for security +- **Volume mounts** for hot-reload in development +- **External database support** - Neon, Supabase, RDS, etc. +- **S3-compatible storage** - R2, S3, B2, MinIO + +--- + +## Inngest Integration + +Betterbase uses [Inngest](https://www.inngest.com/) for durable workflows and background jobs. + +### Deployment Modes + +| Mode | Inngest Backend | Used By | +|------|----------------|---------| +| Cloud | `https://api.inngest.com` | BetterBase Cloud | +| Self-Hosted | `http://inngest:8288` | Docker deployment | +| Local Dev | `http://localhost:8288` | Development | + +### Inngest Functions + +| Function | Trigger | Description | +|----------|---------|-------------| +| `deliverWebhook` | Event | Retryable webhook delivery with auto-backoff | +| `evaluateNotificationRule` | Event | Email/webhook notifications on threshold breach | +| `exportProjectUsers` | Event | Background CSV export | +| `pollNotificationRules` | Cron `*/5 * * * *` | 5-minute metric polling | + +### Environment Variables + +| Variable | Description | +|----------|-------------| +| `INNGEST_BASE_URL` | Inngest backend URL | +| `INNGEST_SIGNING_KEY` | Verifies Inngestβ†’Server callbacks | +| `INNGEST_EVENT_KEY` | Authenticates Serverβ†’Inngest events | + +--- + +## Root-Level Files + +### [`package.json`](package.json) +**Purpose:** Root workspace configuration for Turborepo monorepo. +- **Key Fields:** `name: "betterbase"`, workspaces: `["packages/*", "apps/*"]` +- **Scripts:** Build, test, and dev scripts using turbo +- **Dependencies:** `turbo@^2.3.0`, `bun` (package manager) + +### [`turbo.json`](turbo.json) +**Purpose:** Turborepo task configuration defining build pipelines. +- **Tasks:** `build`, `test`, `lint` with cache settings +- **Dependencies:** Build depends on ^build, test depends on ^test +- **Cache:** Remote caching enabled for CI/CD + +### [`tsconfig.base.json`](tsconfig.base.json) +**Purpose:** Shared TypeScript configuration for all packages. +- **Target:** ES2022 +- **Module:** NodeNext +- **Strict:** Enabled +- **Module Resolution:** NodeNext + +--- + +## Documentation (docs/) + +Comprehensive documentation for BetterBase. + +| Directory | Content | +|-----------|---------| +| `docs/getting-started/` | Installation, quick start, your first project | +| `docs/features/` | Authentication, database, storage, realtime, webhooks, functions, RLS, GraphQL | +| `docs/core/` | Core modules documentation (overview, config, providers, middleware, etc.) | +| `docs/client/` | Client SDK documentation | +| `docs/guides/` | Deployment, scaling, monitoring, production checklist | +| `docs/iac/` | Infrastructure as Code documentation (schema, functions, client hooks, etc.) | +| `docs/api-reference/` | REST API, GraphQL API, CLI commands, Client SDK reference | +| `docs/templates/` | Template documentation | +| `docs/examples/` | Example projects (ecommerce, blog, chat-app, todo-app) | + +--- + +## packages/core + +`@betterbase/core` - Core backend engine with all server-side functionality. + +### Core Modules + +#### [`src/config/index.ts`](packages/core/src/config/index.ts) +**Purpose:** Configuration module exports. + +#### [`src/config/schema.ts`](packages/core/src/config/schema.ts) +**Purpose:** Project configuration schema validation using Zod. +- **Exports:** `ProviderTypeSchema`, `BetterBaseConfigSchema`, `defineConfig`, `validateConfig`, `parseConfig`, `assertConfig` +- **Key Types:** `ProviderType`, `BetterBaseConfig` +- **Validation Features:** + - Validates project configuration structure + - Provider-specific validation (e.g., Turso requires url and authToken) + - Storage configuration validation + - Webhook configuration validation + - GraphQL configuration validation + +#### [`src/config/drizzle-generator.ts`](packages/core/src/config/drizzle-generator.ts) +**Purpose:** Drizzle configuration generator based on provider type. +- **Exports:** `generateDrizzleConfig`, `getDialectForProvider`, `getDriverForProvider`, `getRequiredEnvVars` +- **Key Types:** `DrizzleDriver`, `DrizzleDialect`, `DrizzleConfigOutput`, `DbCredentials` +- **Supported Providers:** Neon, Turso, PlanetScale, Supabase, PostgreSQL, managed + +### functions/ + +#### [`functions/index.ts`](packages/core/src/functions/index.ts) +**Purpose:** Serverless functions module exports. + +#### [`functions/bundler.ts`](packages/core/src/functions/bundler.ts) +**Purpose:** Bundles serverless functions using Bun's build API for edge compatibility. +- **Exports:** `bundleFunction`, `readFunctionConfig`, `listFunctions`, `isFunctionBuilt` +- **Key Types:** `BundleResult`, `FunctionConfig`, `FunctionInfo` +- **Features:** + - Bundles TypeScript functions into single JavaScript file + - Supports Cloudflare Workers and Vercel Edge runtime + - Handles function configuration from config.ts + - Lists all functions in project + - Checks if function has been built + +#### [`functions/deployer.ts`](packages/core/src/functions/deployer.ts) +**Purpose:** Deploys serverless functions to cloud providers. +- **Exports:** `deployToCloudflare`, `deployToVercel`, `syncEnvToCloudflare`, `syncEnvToVercel`, `getCloudflareLogs`, `getVercelLogs` +- **Key Types:** `DeployResult` +- **Features:** + - Deploys to Cloudflare Workers using Wrangler CLI + - Deploys to Vercel Edge using Vercel CLI + - Syncs environment variables + - Retrieves function logs + +### graphql/ + +#### [`graphql/index.ts`](packages/core/src/graphql/index.ts) +**Purpose:** GraphQL module exports. + +#### [`graphql/resolvers.ts`](packages/core/src/graphql/resolvers.ts) +**Purpose:** GraphQL resolver generator that calls Drizzle ORM directly. +- **Exports:** `generateResolvers`, `GraphQLContext`, `GraphQLResolver`, `Resolvers`, `ResolverGenerationConfig` +- **Key Features:** + - Auto-generates resolvers from Drizzle schema + - Supports queries, mutations, and subscriptions (placeholder) + - Respect auth context from BetterAuth + - Custom hooks for before/after mutations + - Error handling + +#### [`graphql/schema-generator.ts`](packages/core/src/graphql/schema-generator.ts) +**Purpose:** Generates GraphQL schema from Drizzle ORM schema. +- **Exports:** `generateGraphQLSchema`, `GraphQLGenerationConfig` +- **Key Features:** + - Auto-generates GraphQL types from Drizzle schema + - Creates input types for mutations + - Generates query types for tables + - Supports custom type mappings + - Handles relationships and pagination + +#### [`graphql/sdl-exporter.ts`](packages/core/src/graphql/sdl-exporter.ts) +**Purpose:** Exports GraphQL schema as SDL (Schema Definition Language) string. +- **Exports:** `exportSDL`, `exportTypeSDL`, `saveSDL` +- **Key Features:** + - Exports complete GraphQL schema as SDL + - Exports individual types as SDL + - Saves SDL to file + - Customizable output options (descriptions, sorting) + +#### [`graphql/server.ts`](packages/core/src/graphql/server.ts) +**Purpose:** GraphQL HTTP server using graphql-yoga that integrates with Hono. +- **Exports:** `createGraphQLServer`, `startGraphQLServer`, `GraphQLConfig` +- **Key Features:** + - Creates Hono-compatible GraphQL server + - Supports authentication + - GraphQL Playground in development + - Subscriptions support + - Health check endpoint + +### middleware/ + +#### [`middleware/index.ts`](packages/core/src/middleware/index.ts) +**Purpose:** Middleware module exports. + +#### [`middleware/rls-session.ts`](packages/core/src/middleware/rls-session.ts) +**Purpose:** RLS session middleware for Hono. +- **Exports:** `rlsSession`, `requireRLS`, `clearRLS`, `getRLSUserId`, `isRLSSessionSet` +- **Key Constants:** `RLS_USER_ID_KEY`, `RLS_SESSION_SET_KEY` +- **Key Types:** `RLSCContext` +- **Features:** + - Reads authenticated user from BetterAuth session + - Makes user ID available for RLS policies + - Idempotent operations (safe to call multiple times) + - Requires RLS to be set for protected routes + - Clears RLS context (e.g., on logout) + +### migration/ + +#### [`migration/index.ts`](packages/core/src/migration/index.ts) +**Purpose:** Migration module exports. + +#### [`migration/rls-migrator.ts`](packages/core/src/migration/rls-migrator.ts) +**Purpose:** Applies RLS policies to the database. +- **Exports:** `applyAuthFunction`, `dropAuthFunctionSQL`, `applyPolicies`, `dropPolicies`, `dropTableRLS`, `applyRLSMigration`, `getAppliedPolicies` +- **Features:** + - Applies auth.uid() function to database + - Creates RLS policies from policy definitions + - Idempotent operations (safe to run multiple times) + - Drops RLS policies from database + - Disables RLS on tables + - Gets information about applied policies + +### providers/ + +#### [`providers/index.ts`](packages/core/src/providers/index.ts) +**Purpose:** Database providers module exports. +- **Exports:** `resolveProvider`, `resolveProviderByType`, `getSupportedProviders`, `providerSupportsRLS`, `getProviderDialect`, `ManagedProviderNotSupportedError` + +#### [`providers/types.ts`](packages/core/src/providers/types.ts) +**Purpose:** Provider interface definitions. +- **Exports:** All provider types and interfaces +- **Key Types:** + - `DatabaseDialect`: "postgres", "mysql", "sqlite" + - `ProviderConfig`: Configuration for connecting to a database provider + - `DatabaseConnection`: Database connection wrapper + - `DrizzleMigrationDriver`: Migration driver interface + - `ProviderAdapter`: Provider adapter interface + - `onchange()`: CDC (Change Data Capture) callback for database changes +- **Provider-Specific Types:** + - `NeonProviderConfig`, `NeonDatabaseConnection`, `NeonMigrationDriver` + - `TursoProviderConfig`, `TursoDatabaseConnection`, `TursoMigrationDriver` + - `PlanetScaleProviderConfig`, `PlanetScaleDatabaseConnection`, `PlanetScaleMigrationDriver` + - `SupabaseProviderConfig`, `SupabaseDatabaseConnection`, `SupabaseMigrationDriver` + - `PostgresProviderConfig`, `PostgresDatabaseConnection`, `PostgresMigrationDriver` + - `ManagedProviderConfig` + +#### [`providers/neon.ts`](packages/core/src/providers/neon.ts) +**Purpose:** Neon database provider implementation. + +#### [`providers/planetscale.ts`](packages/core/src/providers/planetscale.ts) +**Purpose:** PlanetScale database provider implementation. + +#### [`providers/postgres.ts`](packages/core/src/providers/postgres.ts) +**Purpose:** PostgreSQL database provider implementation. + +#### [`providers/supabase.ts`](packages/core/src/providers/supabase.ts) +**Purpose:** Supabase database provider implementation. + +#### [`providers/turso.ts`](packages/core/src/providers/turso.ts) +**Purpose:** Turso database provider implementation. + +### rls/ + +#### [`rls/index.ts`](packages/core/src/rls/index.ts) +**Purpose:** RLS module exports. + +#### [`rls/auth-bridge.ts`](packages/core/src/rls/auth-bridge.ts) +**Purpose:** Creates the auth.uid() PostgreSQL function for RLS policies. +- **Exports:** `generateAuthFunction`, `generateAuthFunctionWithSetting`, `dropAuthFunction`, `setCurrentUserId`, `clearCurrentUserId`, `generateIsAuthenticatedCheck`, `dropIsAuthenticatedCheck`, `generateAllAuthFunctions`, `dropAllAuthFunctions` +- **Key Features:** + - Generates SQL to create auth.uid() function + - Generates SQL to set/clear current user ID + - Generates is_authenticated() helper function + - Handles SQL injection protection + - Generates all auth bridge functions at once + +#### [`rls/generator.ts`](packages/core/src/rls/generator.ts) +**Purpose:** RLS Policy SQL Generator. +- **Exports:** `policyToSQL`, `dropPolicySQL`, `dropPolicyByName`, `disableRLS`, `hasPolicyConditions`, `policiesToSQL`, `dropPoliciesSQL` +- **Key Types:** `PolicyOperation` +- **Features:** + - Generates SQL to create RLS policies + - Generates SQL to drop RLS policies + - Handles policy operations (select, insert, update, delete) + - Checks if policy has conditions + - Converts policies to SQL statements + +#### [`rls/scanner.ts`](packages/core/src/rls/scanner.ts) +**Purpose:** Scans a project for policy definition files and loads them. +- **Exports:** `scanPolicies`, `scanPoliciesStrict`, `listPolicyFiles`, `getPolicyFileInfo`, `PolicyScanError` +- **Key Types:** `ScanResult`, `PolicyFileInfo` +- **Features:** + - Scans project for policy files (*.policy.ts) + - Loads policy definitions + - Handles errors gracefully + - Lists policy files without loading them + - Extracts metadata from policy files + +#### [`rls/types.ts`](packages/core/src/rls/types.ts) +**Purpose:** RLS (Row Level Security) Policy Definition Types. +- **Exports:** `definePolicy`, `isPolicyDefinition`, `mergePolicies` +- **Key Types:** `PolicyDefinition`, `PolicyConfig` +- **Features:** + - Helper function to create policy definitions + - Type guard to check if value is a valid PolicyDefinition + - Merges multiple policy configs for the same table + +#### [`rls/evaluator.ts`](packages/core/src/rls/evaluator.ts) +**Purpose:** RLS Policy Evaluator for enforcing row-level security. +- **Exports:** `evaluatePolicy`, `applyRLSSelect`, `applyRLSInsert`, `applyRLSUpdate`, `applyRLSDelete` +- **Key Features:** + - Evaluates RLS policies for database operations + - Supports SELECT, INSERT, UPDATE, DELETE operations + - SQLite-compatible policy evaluation + - `evaluatePolicy()` function for evaluating policy expressions + - Applies RLS policies to Drizzle queries + +### storage/ + +#### [`storage/index.ts`](packages/core/src/storage/index.ts) +**Purpose:** Storage Module - Fluent Builder API. +- **Exports:** `createStorage`, `resolveStorageAdapter`, `Storage` +- **Key Types:** `StorageFactory`, `BucketClient`, `StorageConfig`, `UploadOptions`, `SignedUrlOptions`, `UploadResult`, `StorageObject` +- **Features:** + - Supabase-compatible storage API + - Fluent `.from(bucket)` API + - Resolves storage adapter based on provider + - Handles async operations with { data, error } pattern + +#### [`storage/s3-adapter.ts`](packages/core/src/storage/s3-adapter.ts) +**Purpose:** S3-Compatible Storage Adapter Implementation. +- **Exports:** `S3StorageAdapter`, `createS3Adapter` +- **Key Features:** + - Implements StorageAdapter interface for S3-compatible services + - Supports AWS S3, Cloudflare R2, Backblaze B2, MinIO + - Handles upload, download, delete, list, signed URL operations + - Uses AWS SDK v3 + - Converts ReadableStream to Buffer for Bun runtime + +#### [`storage/types.ts`](packages/core/src/storage/types.ts) +**Purpose:** Storage Types for S3-Compatible Storage Adapter. +- **Key Types:** + - `StorageProvider`: "s3", "r2", "backblaze", "minio", "managed" + - `StorageConfig`: Union of all storage provider config types + - `UploadOptions`: File upload options (contentType, metadata, isPublic) + - `SignedUrlOptions`: Signed URL options (expiresIn) + - `UploadResult`: Result of successful upload + - `StorageObject`: Represents a storage object + - `StorageAdapter`: Core storage adapter interface + - `AllowedMimeTypes`: Array of allowed MIME types for uploads + - `BucketConfig`: Bucket configuration with size limits and allowed types + +#### [`storage/policy-engine.ts`](packages/core/src/storage/policy-engine.ts) +**Purpose:** Storage Policy Engine for evaluating access policies. +- **Exports:** `evaluateStoragePolicy`, `checkStorageAccess`, `StoragePolicy` +- **Key Features:** + - Evaluates storage access policies + - Supports path-based access control + - Integrates with RLS user context + - `evaluateStoragePolicy()` function for policy evaluation + +#### [`storage/image-transformer.ts`](packages/core/src/storage/image-transformer.ts) +**Purpose:** On-the-fly image transformations using Sharp. +- **Exports:** `ImageTransformer`, `createImageTransformer`, `TransformOptions` +- **Key Features:** + - Resize images (width, height, fit modes) + - Crop images to specific dimensions + - Format conversion (JPEG, PNG, WebP, AVIF) + - Quality adjustment + - Auto-optimization + - Lazy transformation (on-demand) + +### vector/ + +Vector Search module for pgvector support in PostgreSQL. + +#### [`vector/types.ts`](packages/core/src/vector/types.ts) +**Purpose:** Vector Search Type Definitions. +- **Key Types:** + - `EmbeddingProvider`: "openai" | "cohere" | "huggingface" | "custom" + - `SimilarityMetric`: "cosine" | "euclidean" | "inner_product" + - `EmbeddingConfig`: Configuration for embedding generation + - `EmbeddingInput`: Input for generating an embedding + - `EmbeddingResult`: Generated embedding result + - `SearchOptions`: Options for vector search + - `VectorSearchResult`: Search result with similarity score + +#### [`vector/embeddings.ts`](packages/core/src/vector/embeddings.ts) +**Purpose:** Embedding Generation Providers. +- **Exports:** `generateEmbedding`, `generateEmbeddings`, `normalizeVector`, `computeCosineSimilarity`, `createEmbeddingConfig`, `EmbeddingProviderBase`, `OpenAIEmbeddingProvider`, `CohereEmbeddingProvider`, `createEmbeddingProvider`, `DEFAULT_EMBEDDING_CONFIGS`, `validateEmbeddingDimensions` +- **Key Features:** + - OpenAI embeddings provider (text-embedding-3-small, text-embedding-3-large, text-embedding-ada-002) + - Cohere embeddings provider (embed-english-v3.0, embed-multilingual-v3.0) + - Vector normalization utilities + - Cosine similarity computation + - Configurable embedding dimensions + +#### [`vector/search.ts`](packages/core/src/vector/search.ts) +**Purpose:** Vector Similarity Search Functions. +- **Exports:** `VECTOR_OPERATORS`, `vectorDistance`, `cosineDistance`, `euclideanDistance`, `innerProductDistance`, `vectorSearch`, `createVectorIndex` +- **Key Features:** + - pgvector operator mappings for PostgreSQL + - Cosine distance calculation + - Euclidean distance calculation + - Inner product calculation + - Vector search with filtering and pagination + - Drizzle ORM integration for type-safe queries + +#### [`vector/index.ts`](packages/core/src/vector/index.ts) +**Purpose:** Vector Module - Main entry point. +- **Exports:** All types and functions from the vector module +- **Key Features:** + - Unified API for embedding generation and vector search + - Support for multiple embedding providers + - Type-safe vector operations with Drizzle ORM + +### branching/ + +Preview Environments module for creating isolated development branches. + +#### [`branching/types.ts`](packages/core/src/branching/types.ts) +**Purpose:** Branching/Preview Environment Types. +- **Key Types:** + - `BranchStatus`: Enum (ACTIVE, SLEEPING, DELETED) + - `BranchConfig`: Configuration for a preview environment + - `PreviewEnvironment`: Complete preview environment definition + - `CreateBranchOptions`: Options for creating a new branch + - `BranchingConfig`: Global branching configuration + - `BranchOperationResult`: Result of branch operations + - `BranchListResult`: List of branches with pagination + +#### [`branching/database.ts`](packages/core/src/branching/database.ts) +**Purpose:** Database Branching for Preview Environments. +- **Exports:** `DatabaseBranching`, `createDatabaseBranching`, `buildBranchConfig` +- **Key Features:** + - Creates isolated database copies for preview environments + - Supports PostgreSQL database cloning + - Manages connection strings for branch databases + - Handles database cleanup on branch deletion + +#### [`branching/storage.ts`](packages/core/src/branching/storage.ts) +**Purpose:** Storage Branching for Preview Environments. +- **Exports:** `StorageBranching`, `createStorageBranching` +- **Key Features:** + - Creates isolated storage buckets for preview environments + - Supports S3-compatible storage backends + - Manages storage namespace per branch + - Handles storage cleanup on branch deletion + +#### [`branching/index.ts`](packages/core/src/branching/index.ts) +**Purpose:** Branching Module - Main Orchestration. +- **Exports:** `BranchManager`, `createBranchManager`, `getAllBranches`, `clearAllBranches` +- **Key Features:** + - Orchestrates database and storage branching together + - Creates and manages preview environments + - Handles branch sleep/wake cycles + - Provides unified API for branch operations + +### auto-rest.ts + +#### [`auto-rest.ts`](packages/core/src/auto-rest.ts) +**Purpose:** Automatic CRUD Route Generation from Drizzle Schema. +- **Exports:** `mountAutoRest`, `AutoRestOptions`, `DrizzleTable`, `DrizzleDB` +- **Key Features:** + - Runtime route registration for all tables in schema + - Auto-generates full CRUD operations + - Configurable base path (default: /api) + - Supports table exclusion + - RLS enforcement option + - Generated Routes: + - `GET /api/:table` - List all rows (paginated) + - `GET /api/:table/:id` - Get single row by ID + - `POST /api/:table` - Insert new row + - `PATCH /api/:table/:id` - Update existing row + - `DELETE /api/:table/:id` - Delete row + +### iac/ (NEW - Phase 3) + +Infrastructure as Code module - Convex-inspired database and functions. + +#### [`iac/index.ts`](packages/core/src/iac/index.ts) +**Purpose:** IaC module exports. +- **Exports:** `query`, `mutation`, `action`, `defineSchema`, `defineTable`, `v`, `cron` + +#### [`iac/schema.ts`](packages/core/src/iac/schema.ts) +**Purpose:** Schema definition with `defineSchema` and `defineTable`. +- **Exports:** `defineSchema`, `defineTable`, `SchemaDefinition`, `TableDefinition` +- **Key Features:** + - Define tables with fields and indexes + - Support for full-text and vector fields + - Index definitions for query optimization + +#### [`iac/functions.ts`](packages/core/src/iac/functions.ts) +**Purpose:** Function registration (query, mutation, action). +- **Exports:** `query`, `mutation`, `action`, `QueryRegistration`, `MutationRegistration`, `ActionRegistration` +- **Key Features:** + - Optimistic updates support (`optimistic` field) + - Argument validation with v.* validators + - Handler functions with full ctx access + +#### [`iac/validators.ts`](packages/core/src/iac/validators.ts) +**Purpose:** Validators for IaC function arguments. +- **Exports:** `v` object with `string()`, `number()`, `boolean()`, `id()`, `optional()`, `array()`, `object()`, `fullText()`, `vector()` +- **Key Types:** `VString`, `VNumber`, `VBoolean`, `VAny` +- **Key Features:** + - Type-safe argument validation + - `fullText()` for PostgreSQL FTS fields + - `vector(dimensions)` for pgvector fields + +#### [`iac/db-context.ts`](packages/core/src/iac/db-context.ts) +**Purpose:** Database context for IaC functions. +- **Exports:** `DatabaseReader`, `DatabaseWriter`, `QueryBuilder` +- **Key Methods:** + - `get(table, id)` - Get single document + - `query(table)` - Create query builder + - `insert(table, doc)` - Insert document + - `patch(table, id, doc)` - Update document + - `delete(table, id)` - Delete document + - `execute(sql, params)` - Raw SQL execution (NEW) + - `search(table, query)` - Full-text search (NEW) + - `similarity(table, embedding, options)` - Vector search (NEW) + - `analyze(query)` - Query diagnostics (NEW) + +#### [`iac/cron.ts`](packages/core/src/iac/cron.ts) +**Purpose:** Cron job scheduling for scheduled tasks. +- **Exports:** `cron`, `getCronJobs`, `CronJob` +- **Key Features:** + - Cron expression scheduling + - Registered jobs run on schedule + +#### [`iac/errors.ts`](packages/core/src/iac/errors.ts) (NEW) +**Purpose:** Improved error classes with suggestions. +- **Exports:** `IaCError`, `ValidationError`, `DatabaseError`, `AuthError`, `NotFoundError`, `formatError` +- **Key Features:** + - Error codes and suggestions + - Auto-suggestions for common errors + - Links to documentation + +#### [`iac/schema-serializer.ts`](packages/core/src/iac/schema-serializer.ts) +**Purpose:** Serializes IaC schema to Drizzle schema. +- **Exports:** `serializeSchema`, `serializeTable` + +#### [`iac/schema-diff.ts`](packages/core/src/iac/schema-diff.ts) +**Purpose:** Computes schema diffs for migrations. +- **Exports:** `diffSchema`, `SchemaDiff` + +#### [`iac/function-registry.ts`](packages/core/src/iac/function-registry.ts) +**Purpose:** Registry for all IaC functions. +- **Exports:** `registerFunction`, `lookupFunction`, `listFunctions` + +#### [`iac/storage/`](packages/core/src/iac/storage/) +**Purpose:** IaC storage context for file operations. + +#### [`iac/storage/storage-ctx.ts`](packages/core/src/iac/storage/storage-ctx.ts) +**Purpose:** Storage operations for IaC functions. +- **Exports:** `StorageContext`, `createStorageContext` +- **Key Features:** + - File upload and download + - Signed URL generation + - Storage policy enforcement + +#### [`iac/realtime/`](packages/core/src/iac/realtime/) +**Purpose:** IaC realtime subscription infrastructure. + +#### [`iac/realtime/table-dep-inferrer.ts`](packages/core/src/iac/realtime/table-dep-inferrer.ts) +**Purpose:** Infers table dependencies from queries for cache invalidation. +- **Exports:** `inferTableDependencies`, `TableDependencies` + +#### [`iac/realtime/invalidation-manager.ts`](packages/core/src/iac/realtime/invalidation-manager.ts) +**Purpose:** Manages query result cache invalidation. +- **Exports:** `InvalidationManager`, `createInvalidationManager` +- **Key Features:** + - Tracks query dependencies + - Invalidates cached results on data changes + +#### [`iac/realtime/subscription-tracker.ts`](packages/core/src/iac/realtime/subscription-tracker.ts) +**Purpose:** Tracks active subscriptions for realtime updates. +- **Exports:** `SubscriptionTracker`, `createSubscriptionTracker` +- **Key Features:** + - Manages subscription lifecycle + - Broadcasts updates to subscribers + +### webhooks/ + +#### [`webhooks/index.ts`](packages/core/src/webhooks/index.ts) +**Purpose:** Webhook module exports. + +#### [`webhooks/dispatcher.ts`](packages/core/src/webhooks/dispatcher.ts) +**Purpose:** WebhookDispatcher handles sending webhook payloads to configured endpoints. +- **Exports:** `WebhookDispatcher` +- **Key Types:** `RetryConfig`, `WebhookDeliveryLog` +- **Features:** + - Handles webhook dispatch with retry logic + - Tests webhooks by sending synthetic payload + - Tracks delivery logs + - Fire-and-forget pattern + - Retry with exponential backoff + +#### [`webhooks/integrator.ts`](packages/core/src/webhooks/integrator.ts) +**Purpose:** Connects WebhookDispatcher to realtime event emitter. +- **Exports:** `connectToRealtime` +- **Features:** + - Listens for database change events + - Bridges Phase 6 (Realtime WebSockets) with Phase 13 (Webhooks) + - Handles 'db:change', 'db:insert', 'db:update', 'db:delete' events + +#### [`webhooks/signer.ts`](packages/core/src/webhooks/signer.ts) +**Purpose:** Signs and verifies webhook payloads using HMAC-SHA256. +- **Exports:** `signPayload`, `verifySignature` +- **Features:** + - Signs payload with secret using HMAC-SHA256 + - Verifies signatures using timing-safe comparison + - Prevents timing attacks + - Handles both string and object payloads + +#### [`webhooks/startup.ts`](packages/core/src/webhooks/startup.ts) +**Purpose:** Initializes webhooks from configuration during server startup. +- **Exports:** `initializeWebhooks` +- **Key Features:** + - Loads webhooks from BetterBase config + - Resolves environment variable references + - Creates webhook dispatcher + - Connects to realtime emitter + - Handles missing environment variables + +#### [`webhooks/types.ts`](packages/core/src/webhooks/types.ts) +**Purpose:** Webhook configuration and payload types. +- **Key Types:** + - `WebhookConfig`: Webhook configuration (id, table, events, url, secret, enabled) + - `WebhookPayload`: Payload sent to webhook endpoint (id, webhook_id, table, type, record, old_record, timestamp) + +### logger/ + +Logging module for application-wide logging capabilities. + +#### [`logger/index.ts`](packages/core/src/logger/index.ts) +**Purpose:** Logger module exports. +- **Exports:** `BetterBaseLogger`, `createLogger`, `logLevel`, `LogEntry` +- **Key Features:** + - Configurable log levels (debug, info, warn, error) + - Structured logging with metadata + - File transport support + - Console output with colors + +#### [`logger/file-transport.ts`](packages/core/src/logger/file-transport.ts) +**Purpose:** File-based logging transport. +- **Exports:** `FileTransport`, `createFileTransport` +- **Key Features:** + - Rotating log files + - Configurable file paths + - Log rotation by size or time + +### realtime/ + +Realtime subscriptions module for WebSocket-based live data updates. + +#### [`realtime/index.ts`](packages/core/src/realtime/index.ts) +**Purpose:** Realtime module exports. +- **Exports:** `RealtimeManager`, `createRealtimeManager`, `Channel`, `Subscription` + +#### [`realtime/channel-manager.ts`](packages/core/src/realtime/channel-manager.ts) +**Purpose:** Channel manager for managing WebSocket subscriptions. +- **Exports:** `ChannelManager`, `createChannelManager` +- **Key Features:** + - Subscribe to database changes (INSERT, UPDATE, DELETE) + - Filter by table, schema, or specific records + - Automatic reconnection with exponential backoff + - Presence detection for collaborative features + +--- + +## packages/client + +`@betterbase/client` - TypeScript SDK for BetterBase backends (like `@supabase/supabase-js`). + +### Client Modules + +#### [`src/auth.ts`](packages/client/src/auth.ts) +**Purpose:** Authentication client for BetterAuth integration. +- **Exports:** `AuthClient`, `authClient`, `createAuthClientInstance` +- **Key Types:** `User`, `Session`, `StorageAdapter` +- **Features:** + - Wraps BetterAuth client + - Handles sign up, sign in, sign out, get session + - Manages session token in localStorage + - On auth state change callback + - Fallback storage adapter + - **New Authentication Methods:** + - `sendMagicLink(email)` - Send magic link for passwordless login + - `verifyMagicLink(email, code)` - Verify magic link code + - `sendOtp(email)` - Send one-time password + - `verifyOtp(email, code)` - Verify OTP code + - `mfa.enable()` - Enable multi-factor authentication + - `mfa.verify(code)` - Verify MFA code + - `mfa.disable()` - Disable MFA + - `mfa.challenge()` - Challenge MFA + - `sendPhoneVerification(phone)` - Send phone verification SMS + - `verifyPhone(phone, code)` - Verify phone number + +#### [`src/client.ts`](packages/client/src/client.ts) +**Purpose:** Main BetterBase client constructor. +- **Exports:** `createClient`, `BetterBaseClient` +- **Key Types:** `BetterBaseConfig` +- **Features:** + - Configuration validation with Zod + - Initializes auth, realtime, and storage clients + - Manages authentication state + - Provides fetch method with authenticated headers + - Query builder support + +#### [`src/query-builder.ts`](packages/client/src/query-builder.ts) +**Purpose:** Chainable query builder for database operations. +- **Exports:** `QueryBuilder` +- **Key Types:** `QueryBuilderOptions`, `QueryOptions` +- **Methods:** + - `select(fields)`: Select fields to retrieve + - `eq(column, value)`: Add equality filter + - `in(column, values)`: Add IN filter + - `limit(count)`: Limit number of results + - `offset(count)`: Offset results + - `order(column, direction)`: Sort results + - `execute()`: Execute query + - `single(id)`: Get single record by ID + - `insert(data)`: Insert new record + - `update(id, data)`: Update record + - `delete(id)`: Delete record + +#### [`src/realtime.ts`](packages/client/src/realtime.ts) +**Purpose:** Real-time subscription client for database changes. +- **Exports:** `RealtimeClient` +- **Key Types:** `RealtimeCallback`, `RealtimeSubscription`, `RealtimeEvent` +- **Features:** + - WebSocket-based realtime updates + - Subscription management + - Reconnect logic with exponential backoff + - Event filtering + - Supports INSERT, UPDATE, DELETE, and * (all) events + +#### [`src/storage.ts`](packages/client/src/storage.ts) +**Purpose:** Storage client for file operations. +- **Exports:** `Storage`, `StorageBucketClient` +- **Key Types:** `UploadOptions`, `SignedUrlOptions`, `StorageFile`, `UploadResult`, `PublicUrlResult`, `SignedUrlResult`, `RemoveResult` +- **Features:** + - Supabase-compatible storage API + - Fluent `.from(bucket)` API + - Upload, download, remove, list operations + - Public URL and signed URL generation + - Handles File, Blob, and ArrayBuffer inputs + - Error handling with { data, error } pattern + +#### [`src/types.ts`](packages/client/src/types.ts) +**Purpose:** TypeScript type definitions for client. +- **Exports:** All client types and interfaces + +#### [`src/errors.ts`](packages/client/src/errors.ts) +**Purpose:** Client-side error classes. +- **Exports:** Custom error classes (AuthError, NetworkError, ValidationError, etc.) + +#### [`src/index.ts`](packages/client/src/index.ts) +**Purpose:** Client package entry point. +- **Exports:** All public APIs from the client package + +#### [`src/build.ts`](packages/client/src/build.ts) +**Purpose:** Build configuration for client package. + +### IaC Client Modules (NEW - Phase 3) + +#### [`src/iac/provider.tsx`](packages/client/src/iac/provider.tsx) +**Purpose:** React context provider for IaC functions. +- **Exports:** `BetterBaseReactProvider`, `useIaC` +- **Key Features:** + - Wraps React app with BetterBase context + - `useIaC()` hook returns query/mutation/action functions + - Configurable base URL and auth token + - Compatible with TanStack Query + +#### [`src/iac/hooks.ts`](packages/client/src/iac/hooks.ts) +**Purpose:** React hooks for IaC functions (query, mutation). +- **Exports:** `useQuery`, `useMutation`, `useAction` +- **Key Features:** + - `useQuery(path, args)` - Subscribe to query results + - `useMutation(path)` - Execute mutations with optimistic updates + - `useAction(path)` - Execute one-off actions + - Optimistic updates support (`optimisticData` return) + +#### [`src/iac/vanilla.ts`](packages/client/src/iac/vanilla.ts) +**Purpose:** Non-React IaC client for vanilla JS/other frameworks. +- **Exports:** `createIaCClient`, `IaCClient` +- **Key Methods:** + - `query(path, args)` - Execute query + - `mutation(path, args, options)` - Execute mutation + - `action(path, args)` - Execute action + +#### [`src/iac/embeddings.ts`](packages/client/src/iac/embeddings.ts) +**Purpose:** Embedding generation utilities for vector search. +- **Exports:** `generateEmbedding`, `createEmbeddingProvider` +- **Key Features:** + - OpenAI embeddings support + - Cohere embeddings support + - Text-to-vector conversion + +#### [`src/iac/paginated-query.ts`](packages/client/src/iac/paginated-query.ts) +**Purpose:** Paginated query support for IaC functions. +- **Exports:** `PaginatedQuery`, `createPaginatedQuery` +- **Key Features:** + - Cursor-based pagination + - Limit and offset support + - Total count estimation + +--- + +## packages/cli + +Canonical `@betterbase/cli` implementation - the `bb` command-line tool. + +### CLI Commands + +#### [`commands/init.ts`](packages/cli/src/commands/init.ts) +**Purpose:** `bb init` command - scaffolds new BetterBase projects. +- **Exports:** `runInitCommand(options)` - main command function, `InitCommandOptions` - type +- **Key Functions:** `installDependencies()`, `initializeGitRepository()`, `buildPackageJson()`, `buildDrizzleConfig()`, `buildSchema()`, `buildMigrateScript()`, `buildDbIndex()`, `buildAuthMiddleware()`, `buildReadme()`, `buildRoutesIndex()`, `writeProjectFiles()` +- **Internal Deps:** `../utils/logger`, `../utils/prompts` +- **Usage Patterns:** Typically called by developers starting a new project. Uses interactive prompts to gather project name, database mode, and options. Creates a complete project structure with sensible defaults. +- **Implementation Details:** Uses Inquirer for interactive prompts, writes files synchronously using fs module. Supports three database modes: local (SQLite), neon (PostgreSQL), turso (LibSQL). Generates Zod-validated config. Implements file templating with template literals for code generation. +- **External Deps:** `inquirer`, `zod`, `chalk` +- **Cross-Ref:** [`packages/cli/src/utils/prompts.ts`](packages/cli/src/utils/prompts.ts), [`apps/test-project/`](apps/test-project/) + +#### [`commands/dev.ts`](packages/cli/src/commands/dev.ts) +**Purpose:** `bb dev` command - watches schema/routes and regenerates context. +- **Exports:** `runDevCommand(projectRoot)` - returns cleanup function +- **Internal Deps:** `../utils/context-generator`, `../utils/logger` +- **Usage Patterns:** Runs during development to continuously regenerate `.betterbase-context.json` as files change. +- **Implementation Details:** Sets up file watchers on schema and routes directories, triggers context regeneration on changes. Returns cleanup function to stop watchers. +- **External Deps:** `bun`, `chalk` +- **Cross-Ref:** [`packages/cli/src/utils/context-generator.ts`](packages/cli/src/utils/context-generator.ts) + +#### [`commands/migrate.ts`](packages/cli/src/commands/migrate.ts) +**Purpose:** `bb migrate` commands - generates and applies migrations with safety checks. +- **Exports:** `runMigrateCommand(options)` - main function, `MigrateCommandOptions` - type, `MigrationChange` - interface, `MigrationChangeType` - type +- **Key Functions:** `runDrizzleKit()`, `listSqlFiles()`, `analyzeMigration()`, `displayDiff()`, `confirmDestructive()`, `backupDatabase()`, `restoreBackup()`, `splitStatements()`, `collectChangesFromGenerate()` +- **Internal Deps:** `../constants`, `../utils/logger`, `../utils/prompts` +- **Usage Patterns:** Called during database schema changes. Generates migration files, optionally previews changes, applies with safety checks. +- **Implementation Details:** Wraps DrizzleKit for migration generation. Implements visual diff display with color-coded changes. Prompts for confirmation on destructive operations. Creates automatic backups before dangerous migrations. Parses SQL files to extract migration metadata. +- **External Deps:** `drizzle-orm`, `drizzle-kit`, `inquirer`, `chalk`, `zod` + +#### [`commands/auth.ts`](packages/cli/src/commands/auth.ts) +**Purpose:** `bb auth setup` command - scaffolds BetterAuth integration. +- **Exports:** `runAuthSetupCommand(projectRoot)` - main function +- **Key Constants:** `AUTH_SCHEMA_BLOCK` - sessions/accounts tables SQL, `AUTH_ROUTE_FILE` - auth routes template, `AUTH_MIDDLEWARE_FILE` - requireAuth/optionalAuth middleware +- **Key Functions:** `appendIfMissing()`, `ensurePasswordHashColumn()`, `ensureAuthInConfig()`, `ensureEnvVar()`, `ensureRoutesIndexHook()` +- **Internal Deps:** `../utils/logger` +- **Usage Patterns:** Run after project initialization to add authentication. Modifies existing files to integrate BetterAuth. +- **Implementation Details:** Injects SQL schema blocks into existing schema file, adds auth routes to routes index, creates auth middleware. Uses file patching rather than full file generation for integration. +- **External Deps:** `better-auth`, `chalk` + +#### [`commands/generate.ts`](packages/cli/src/commands/generate.ts) +**Purpose:** `bb generate crud` command - generates CRUD routes for a table. +- **Exports:** `runGenerateCrudCommand(projectRoot, tableName)` - main function +- **Key Functions:** `toSingular()`, `schemaTypeToZod()`, `buildSchemaShape()`, `buildFilterableColumns()`, `buildFilterCoercers()`, `generateRouteFile()`, `updateMainRouter()`, `ensureRealtimeUtility()`, `ensureZodValidatorInstalled()` +- **Internal Deps:** `../utils/schema-scanner`, `../utils/logger` +- **Usage Patterns:** Called after creating a database table to auto-generate REST API routes. Saves developers from writing boilerplate CRUD code. +- **Implementation Details:** Scans Drizzle schema to understand table structure. Maps Drizzle column types to Zod schemas. Generates Hono routes with type-safe handlers. Updates route index to register new endpoints. +- **External Deps:** `zod`, `hono`, `drizzle-orm`, `chalk` +- **Cross-Ref:** [`packages/cli/src/utils/scanner.ts`](packages/cli/src/utils/scanner.ts) + +#### [`commands/function.ts`](packages/cli/src/commands/function.ts) +**Purpose:** `bb function` command - manages serverless functions. +- **Exports:** Function management commands (create, deploy, list, invoke) +- **Key Functions:** Function deployment and bundling +- **Internal Deps:** `../utils/logger`, `../utils/prompts` +- **Usage Patterns:** Deploy and manage serverless functions. +- **Implementation Details:** Handles function bundling, deployment to edge, and invocation. +- **External Deps:** `chalk`, `inquirer` + +#### [`commands/graphql.ts`](packages/cli/src/commands/graphql.ts) +**Purpose:** `bb graphql` command - GraphQL schema management. +- **Exports:** GraphQL schema generation and introspection commands +- **Key Functions:** Schema generation, SDL export +- **Internal Deps:** `../utils/logger`, `../utils/prompts` +- **Usage Patterns:** Generate GraphQL schema from database, export SDL. +- **Implementation Details:** Uses Drizzle introspection to generate GraphQL types. +- **External Deps:** `chalk`, `inquirer` + +#### [`commands/login.ts`](packages/cli/src/commands/login.ts) +**Purpose:** `bb login` command - authenticate with BetterBase cloud. +- **Exports:** `runLoginCommand(options)` - main function +- **Internal Deps:** `../utils/logger` +- **Usage Patterns:** Authenticate to BetterBase to access cloud features. +- **Implementation Details:** Handles OAuth flow or API key authentication. +- **External Deps:** `chalk` + +#### [`commands/rls.ts`](packages/cli/src/commands/rls.ts) +**Purpose:** `bb rls` command - Row Level Security management. +- **Exports:** RLS policy management commands +- **Key Functions:** Policy creation, enable/disable RLS +- **Internal Deps:** `../utils/logger` +- **Usage Patterns:** Manage RLS policies for tables. +- **Implementation Details:** Generates RLS policies based on table structure. +- **External Deps:** `chalk`, `drizzle-orm` + +#### [`commands/storage.ts`](packages/cli/src/commands/storage.ts) +**Purpose:** `bb storage` command - storage bucket management. +- **Exports:** Storage bucket management commands +- **Key Functions:** Bucket CRUD operations, policy management +- **Internal Deps:** `../utils/logger`, `../utils/prompts` +- **Usage Patterns:** Manage storage buckets and files. +- **Implementation Details:** Integrates with S3-compatible storage. +- **External Deps:** `chalk`, `inquirer` + +#### [`commands/webhook.ts`](packages/cli/src/commands/webhook.ts) +**Purpose:** `bb webhook` command - webhook management. +- **Exports:** Webhook lifecycle management commands +- **Key Functions:** Webhook creation, testing, logging +- **Internal Deps:** `../utils/logger` +- **Usage Patterns:** Register and manage webhooks for database events. +- **Implementation Details:** Handles webhook registration and event dispatch. +- **External Deps:** `chalk` + +#### [`commands/branch.ts`](packages/cli/src/commands/branch.ts) +**Purpose:** `bb branch` command - Preview Environment management. +- **Exports:** `runBranchCreateCommand`, `runBranchDeleteCommand`, `runBranchListCommand`, `runBranchStatusCommand`, `runBranchWakeCommand`, `runBranchSleepCommand` +- **Key Functions:** + - `runBranchCreateCommand` - Creates a new preview environment + - `runBranchDeleteCommand` - Deletes a preview environment + - `runBranchListCommand` - Lists all preview environments + - `runBranchStatusCommand` - Checks branch status + - `runBranchWakeCommand` - Wakes a sleeping preview + - `runBranchSleepCommand` - Puts a preview to sleep +- **Key Features:** + - `bb branch create ` - Create preview environment + - `bb branch delete ` - Delete preview environment + - `bb branch list` - List all preview environments + - `bb branch status ` - Check branch status + - `bb branch wake ` - Wake sleeping preview + - `bb branch sleep ` - Sleep preview +- **Internal Deps:** `../utils/logger`, `@betterbase/shared`, `@betterbase/core/branching` +- **Usage Patterns:** Manage preview environments for development branches. +- **External Deps:** `chalk` + +#### [`commands/auth-providers.ts`](packages/cli/src/commands/auth-providers.ts) +**Purpose:** `bb auth add-provider` command - OAuth provider management. +- **Exports:** `runAuthProviderCommand(options)` - main function +- **Key Functions:** `addProvider()`, `removeProvider()`, `listProviders()` +- **Supported Providers:** google, github, discord, apple, microsoft, twitter, facebook +- **Internal Deps:** `../utils/logger`, `../utils/prompts` +- **Usage Patterns:** Add or remove OAuth authentication providers. +- **Implementation Details:** Updates BetterAuth configuration with provider credentials. +- **External Deps:** `chalk`, `inquirer` + +#### [`commands/migrate-utils.ts`](packages/cli/src/commands/migrate-utils.ts) +**Purpose:** Migration utilities for the CLI. +- **Exports:** `analyzeMigration()`, `splitStatements()`, `collectChangesFromGenerate()` +- **Key Functions:** Migration analysis and SQL parsing +- **Internal Deps:** `../utils/logger`, `zod` +- **Usage Patterns:** Used by migrate command for migration management. +- **Implementation Details:** Parses SQL files, analyzes changes, supports rollback planning. +- **External Deps:** `zod`, `chalk` + +#### [`commands/rls-test.ts`](packages/cli/src/commands/rls-test.ts) +**Purpose:** `bb rls test` command - Test RLS policies for a table. +- **Exports:** `runRLSTestCommand(projectRoot, tableName)` - main function +- **Key Functions:** `testRLSPolicies()`, `verifyPolicy()`, `simulateQuery()` +- **Internal Deps:** `../utils/logger`, `../utils/schema-scanner` +- **Usage Patterns:** Test RLS policies before deploying to production. +- **Implementation Details:** Simulates queries as different users to verify RLS policy correctness. +- **External Deps:** `chalk`, `drizzle-orm` + +### CLI Utilities + +#### [`utils/logger.ts`](packages/cli/src/utils/logger.ts) +**Purpose:** Colored console logging utilities. +- **Exports:** `info(message)`, `warn(message)`, `error(message)`, `success(message)` +- **Internal Deps:** `chalk` +- **Usage Patterns:** Used throughout CLI commands for consistent, colored output. +- **Implementation Details:** Thin wrapper around Chalk with pre-configured color schemes. Info = cyan, Warn = yellow, Error = red, Success = green. +- **External Deps:** `chalk` + +#### [`utils/prompts.ts`](packages/cli/src/utils/prompts.ts) +**Purpose:** Interactive prompt utilities wrapping Inquirer. +- **Exports:** `text(options)`, `confirm(options)`, `select(options)` +- **Internal Deps:** `inquirer`, `zod` +- **Usage Patterns:** Used by CLI commands that need user input during execution. +- **Implementation Details:** Wraps Inquirer with Zod validation on input. Provides typed promise-based API. +- **External Deps:** `inquirer`, `zod` + +#### [`utils/context-generator.ts`](packages/cli/src/utils/context-generator.ts) +**Purpose:** Generates `.betterbase-context.json` for AI agents. +- **Exports:** `ContextGenerator` - class, `BetterBaseContext` - interface +- **Class Methods:** `generate(projectRoot)` - main method, `generateAIPrompt()` - creates AI-readable prompt +- **Internal Deps:** `./route-scanner`, `./schema-scanner`, `./logger` +- **Usage Patterns:** Called during `bb dev` or `bb generate` to create context file. Used by AI assistants to understand the project structure. +- **Implementation Details:** Scans schema and routes, aggregates metadata, outputs JSON file with tables, routes, and AI-readable prompt. The AI prompt helps contextualize the project for LLM-based development assistance. +- **External Deps:** `typescript`, `zod`, `chalk` +- **Cross-Ref:** [`packages/cli/src/utils/route-scanner.ts`](packages/cli/src/utils/route-scanner.ts), [`packages/cli/src/utils/scanner.ts`](packages/cli/src/utils/scanner.ts) + +#### [`utils/route-scanner.ts`](packages/cli/src/utils/route-scanner.ts) +**Purpose:** Scans Hono routes directory and extracts endpoint metadata. +- **Exports:** `RouteScanner` - class, `RouteInfo` - interface +- **Class Methods:** `scan(routesDir)` - main method, `scanFile()` - parses single file, `findSchemaUsage()` - detects Zod schemas +- **Internal Deps:** `typescript` (TS AST parser) +- **Usage Patterns:** Used by context generator to discover all API endpoints in the project. +- **Implementation Details:** Uses TypeScript compiler API to parse route files. Extracts HTTP method, path, auth requirements, and Zod schemas. Handles Hono's chainable API pattern detection. +- **External Deps:** `typescript` + +#### [`utils/scanner.ts`](packages/cli/src/utils/scanner.ts) +**Purpose:** Scans Drizzle schema files and extracts table metadata. +- **Exports:** `SchemaScanner` - class, `ColumnInfo` - type, `TableInfo` - type, `ColumnInfoSchema`, `TableInfoSchema`, `TablesRecordSchema` - Zod schemas +- **Class Methods:** `scan()` - main method, `parseTable()`, `parseColumn()`, `parseIndexes()` +- **Internal Deps:** `typescript`, `zod`, `./logger` +- **Usage Patterns:** Used by generate command and context generator to understand database schema. +- **Implementation Details:** Parses TypeScript schema files using TypeScript compiler API. Extracts table names, column definitions, relations, indexes. Returns typed metadata for code generation. +- **External Deps:** `typescript`, `zod` + +#### [`utils/schema-scanner.ts`](packages/cli/src/utils/schema-scanner.ts) +**Purpose:** Re-exports from scanner.ts for cleaner imports. +- **Exports:** `SchemaScanner` - class (re-export), `ColumnInfo` - type (re-export), `TableInfo` - type (re-export) +- **Usage Patterns:** Import point for schema scanning functionality. +- **External Deps:** None (re-exports) + +#### [`utils/provider-prompts.ts`](packages/cli/src/utils/provider-prompts.ts) +**Purpose:** Database provider selection prompts. +- **Exports:** Provider selection utilities +- **Usage Patterns:** Used by init command to select database provider. +- **Implementation Details:** Provides interactive selection for database providers (PostgreSQL, MySQL, SQLite). +- **External Deps:** `inquirer` + +### CLI Tests + +#### [`test/smoke.test.ts`](packages/cli/test/smoke.test.ts) +**Purpose:** Basic CLI tests verifying command registration. +- **Tests:** Program name, init argument, generate crud, auth setup, dev, migrate commands +- **Usage Patterns:** Smoke tests run in CI to verify CLI is functional after changes. + +#### [`test/scanner.test.ts`](packages/cli/test/scanner.test.ts) +**Purpose:** Tests for SchemaScanner. +- **Tests:** Extracts tables, columns, relations, indexes from Drizzle schema +- **Usage Patterns:** Unit tests for scanner module. + +#### [`test/context-generator.test.ts`](packages/cli/test/context-generator.test.ts) +**Purpose:** Tests for ContextGenerator. +- **Tests:** Creates context from schema/routes, handles missing routes, empty schema, missing schema +- **Usage Patterns:** Unit tests for context generation. + +#### [`test/route-scanner.test.ts`](packages/cli/test/route-scanner.test.ts) +**Purpose:** Tests for RouteScanner. +- **Tests:** Extracts Hono routes with auth detection and schema usage +- **Usage Patterns:** Unit tests for route scanning. + +--- + +## packages/shared + +`@betterbase/shared` - Shared utilities and types used across all packages. + +### Shared Modules + +#### [`src/types.ts`](packages/shared/src/types.ts) +**Purpose:** Common TypeScript type definitions. +- **Exports:** `BetterBaseResponse`, `ProviderType`, `DatabaseCredentials`, `StorageConfig`, etc. +- **Key Types:** + - `BetterBaseResponse`: Standard response wrapper { data, error } + - `ProviderType`: "postgres" | "mysql" | "sqlite" | "neon" | "turso" | "planetscale" | "supabase" + - `DatabaseCredentials`: Connection configuration interface + - `StorageConfig`: Storage provider configuration + +#### [`src/errors.ts`](packages/shared/src/errors.ts) +**Purpose:** Shared error classes across all packages. +- **Exports:** `BetterBaseError`, `ValidationError`, `DatabaseError`, `AuthError`, `StorageError` +- **Key Features:** All errors extend base Error class with code, status, and details + +#### [`src/constants.ts`](packages/shared/src/constants.ts) +**Purpose:** Shared constants used across the platform. +- **Exports:** `VERSION`, `DEFAULT_PORT`, `DEFAULT_POOL_SIZE`, `DEFAULT_TIMEOUT`, etc. + +#### [`src/utils.ts`](packages/shared/src/utils.ts) +**Purpose:** Utility functions for validation, formatting, etc. +- **Exports:** `validateEmail()`, `formatDate()`, `slugify()`, `generateId()`, etc. + +--- + +## Templates + +BetterBase provides project templates for quick project initialization. + +### templates/base + +The base template with essential project structure. + +- **Path:** `templates/base/` +- **Purpose:** Starting point for new BetterBase projects +- **Includes:** + - Basic Hono server setup + - Database schema with users example + - Authentication middleware + - Storage routes + - Health check endpoint + +### templates/auth + +The authentication template with full BetterAuth integration. + +- **Path:** `templates/auth/` +- **Purpose:** Projects requiring authentication out of the box +- **Includes:** + - Pre-configured BetterAuth setup (`src/auth/index.ts`, `src/auth/types.ts`) + - Email/password authentication + - Social OAuth providers (configurable) + - Session management + - Auth middleware (`src/middleware/auth.ts`) + - Auth routes (`src/routes/auth.ts`, `src/routes/auth-example.ts`) + - Auth schema (`src/db/auth-schema.ts`, `src/db/schema.ts`) + - Drizzle adapter integration (`src/db/index.ts`) + - `betterbase.config.ts`, `drizzle.config.ts`, `package.json` + +### templates/iac + +The IaC project template with Convex-inspired patterns. + +- **Path:** `templates/iac/` +- **Purpose:** Projects using the Infrastructure as Code approach +- **Includes:** + - `betterbase/schema.ts` - Schema definition + - `betterbase/queries/` - Query functions + - `betterbase/mutations/` - Mutation functions + - `betterbase/actions/` - Action functions + - `betterbase/cron.ts` - Scheduled functions + - `betterbase.config.ts` - Project configuration + +--- + +## Usage Examples + +### Client SDK + +```typescript +import { createClient } from '@betterbase/client'; + +const client = createClient({ + url: 'http://localhost:3000', + key: 'your-api-key', +}); + +// Authenticate user +const { data, error } = await client.auth.signIn('user@example.com', 'password123'); + +if (error) { + console.error('Sign in failed:', error); +} else { + console.log('User signed in:', data?.user); +} + +// Query data +const users = await client.from('users').select('*').execute(); +console.log('Users:', users); + +// Upload file +const file = new File(['hello world'], 'test.txt', { type: 'text/plain' }); +const uploadResult = await client.storage.from('bucket').upload('test.txt', file); +console.log('Upload result:', uploadResult); + +// Subscribe to realtime updates +const subscription = client.realtime.from('posts').on('INSERT', (payload) => { + console.log('New post:', payload); +}).subscribe(); + +// Cleanup subscription +subscription.unsubscribe(); +``` + +### Server-Side with Hono + +```typescript +import { Hono } from 'hono'; +import { eq } from 'drizzle-orm'; +import { auth } from './auth'; +import { db } from './db'; +import { users } from './db/schema'; + +const app = new Hono(); + +// Protected route +app.get('/api/protected', async (c) => { + const session = await auth.api.getSession({ headers: c.req.raw.headers }); + if (!session) { + return c.json({ error: 'Unauthorized' }, 401); + } + + const userPosts = await db.select().from(users) + .where(eq(users.id, session.user.id)); + + return c.json(userPosts); +}); + +export default app; +``` + +### RLS Policy Definition + +```typescript +// src/db/policies/users.policy.ts +import { definePolicy } from '@betterbase/core/rls'; + +export default definePolicy('users', { + select: 'auth.uid() = id', + update: 'auth.uid() = id', + delete: 'auth.uid() = id', + insert: 'auth.uid() = id', +}); +``` + +--- + +## Architecture Decisions + +### Authentication +- **Choice:** BetterAuth for password and social authentication +- **Rationale:** Lightweight, extensible, and compatible with Drizzle ORM + +### Database +- **Choice:** Drizzle ORM for database abstraction +- **Rationale:** Type-safe, composable, and supports multiple dialects + +### Storage +- **Choice:** S3-compatible storage with AWS SDK v3 +- **Rationale:** Wide support, compatibility with multiple providers + +### Realtime +- **Choice:** WebSocket-based realtime using Bun's WebSocket API +- **Rationale:** Fast, lightweight, and built into Bun runtime + +### GraphQL +- **Choice:** graphql-yoga for server, GraphQL.js for schema +- **Rationale:** Simple setup, good integration with Hono + +### Validation +- **Choice:** Zod for schema validation +- **Rationale:** Type-safe, easy to use, and integrates well with TypeScript + +### CLI +- **Choice:** Commander.js for CLI framework +- **Rationale:** Mature, lightweight, and well-documented + +--- + +## Development Workflow + +### Creating a New Project + +```bash +# Initialize new project +bb init + +# Answer prompts for project name, database provider, etc. + +# Navigate to project directory +cd my-project + +# Install dependencies +bun install + +# Start development server +bun run dev +``` + +### Adding Authentication + +```bash +# Add BetterAuth integration +bb auth setup + +# Run database migrations +bun run db:push + +# Set auth secret in .env +echo "AUTH_SECRET=your-secret-key" >> .env +``` + +### Generating CRUD Routes + +```bash +# Generate CRUD routes for a table +bb generate crud posts + +# Run GraphQL schema generation +bb graphql generate +``` + +### Migrating Database + +```bash +# Generate and apply migrations +bb migrate + +# Preview migration without applying +bb migrate preview + +# Apply migrations to production +bb migrate production +``` + +--- + +## API Reference + +### Client SDK + +#### `createClient(config)` +```typescript +const client = createClient({ + url: string; + key?: string; + schema?: string; + fetch?: typeof fetch; + storage?: StorageAdapter; +}); +``` + +#### `client.auth` +```typescript +client.auth.signUp(email: string, password: string, name: string): Promise>; +client.auth.signIn(email: string, password: string): Promise>; +client.auth.signOut(): Promise>; +client.auth.getSession(): Promise>; +client.auth.getToken(): string | null; +client.auth.setToken(token: string | null): void; +``` + +#### `client.from(table)` +```typescript +const query = client.from('users'); +query.select(fields?: string): this; +query.eq(column: string, value: unknown): this; +query.in(column: string, values: unknown[]): this; +query.limit(count: number): this; +query.offset(count: number): this; +query.order(column: string, direction?: 'asc' | 'desc'): this; +query.execute(): Promise>; +query.single(id: string): Promise>; +query.insert(data: Partial): Promise>; +query.update(id: string, data: Partial): Promise>; +query.delete(id: string): Promise>; +``` + +#### `client.realtime` +```typescript +client.realtime.from(table: string): { + on: ( + event: RealtimeEvent, + callback: RealtimeCallback, + ) => { + subscribe: (filter?: Record) => RealtimeSubscription; + }; +}; +``` + +#### `client.storage` +```typescript +client.storage.from(bucket: string): StorageBucketClient; +``` + +#### `StorageBucketClient` +```typescript +bucket.upload(path: string, file: File | Blob | ArrayBuffer, options?: UploadOptions): Promise>; +bucket.download(path: string): Promise>; +bucket.getPublicUrl(path: string): Promise>; +bucket.createSignedUrl(path: string, options?: SignedUrlOptions): Promise>; +bucket.remove(paths: string[]): Promise>; +bucket.list(prefix?: string): Promise>; +``` + +--- + +## Server-Side API + +### Hono App + +```typescript +import { Hono } from 'hono'; + +const app = new Hono(); + +// Health check +app.get('/health', (c) => { + return c.json({ status: 'ok' }); +}); + +// Protected route +app.get('/protected', requireAuth, (c) => { + const user = c.get('user'); + return c.json({ user }); +}); + +// Error handler +app.onError((err, c) => { + return c.json({ error: err.message }, 500); +}); + +export default app; +``` + +### Auth Middleware + +```typescript +import { requireAuth, optionalAuth } from './middleware/auth'; + +// Example 1: Require authentication for all routes +app.use('*', requireAuth); + +// Example 2: Optional authentication (mutually exclusive - use one or the other) +// app.use('*', optionalAuth); + +// Get user from context +const user = c.get('user'); +``` + +> **Note:** `requireAuth` and `optionalAuth` are mutually exclusive choices for route protection. Use `app.use('*', requireAuth)` for mandatory authentication, or `app.use('*', optionalAuth)` for optional authentication. + +### Realtime Broadcast + +```typescript +import { realtime } from './lib/realtime'; + +// Broadcast event +realtime.broadcast('posts', 'INSERT', { + id: '1', + title: 'New Post', + content: 'Hello World', + createdAt: new Date(), +}); +``` + +--- + +## Configuration + +### Project Configuration (`betterbase.config.ts`) + +```typescript +import { defineConfig } from '@betterbase/core'; + +export default defineConfig({ + project: { + name: 'my-project', + }, + provider: { + type: 'neon', + connectionString: process.env.DATABASE_URL, + }, + storage: { + provider: 's3', + bucket: 'my-bucket', + region: 'us-east-1', + accessKeyId: process.env.STORAGE_ACCESS_KEY_ID, + secretAccessKey: process.env.STORAGE_SECRET_ACCESS_KEY, + }, + webhooks: [ + { + id: 'new-post', + table: 'posts', + events: ['INSERT'], + url: process.env.WEBHOOK_URL, + secret: process.env.WEBHOOK_SECRET, + enabled: true, + }, + ], + graphql: { + enabled: true, + }, +}); +``` + +### Environment Variables + +```bash +# Database +DATABASE_URL="postgres://user:password@localhost:5432/mydb" +TURSO_URL="https://mydb.turso.io" +TURSO_AUTH_TOKEN="my-turso-token" + +# Auth +AUTH_SECRET="your-auth-secret" +AUTH_URL="http://localhost:3000" + +# Storage +STORAGE_PROVIDER="s3" +STORAGE_BUCKET="my-bucket" +STORAGE_REGION="us-east-1" +STORAGE_ACCESS_KEY_ID="my-access-key" +STORAGE_SECRET_ACCESS_KEY="my-secret-key" + +# Webhooks +WEBHOOK_URL="https://example.com/webhook" +WEBHOOK_SECRET="my-webhook-secret" + +# Server +PORT=3000 +NODE_ENV="development" +``` + +--- + +## Testing + +### Running Tests + +```bash +# Run all tests +bun test + +# Run tests in watch mode +bun test --watch + +# Run specific test file +bun test packages/cli/test/smoke.test.ts +``` + +### Test Structure + +Tests are located in the `test/` directory of each package. The test files follow the pattern `*.test.ts`. + +--- + +## Contributing + +### Development Setup + +```bash +# Clone repository +git clone +cd betterbase + +# Install dependencies +bun install + +# Build packages +bun run build + +# Run tests +bun test +``` + +### Development Workflow + +1. Create a new branch +2. Make changes to the codebase +3. Run tests +4. Commit changes +5. Push to remote repository +6. Create a pull request + +--- + +## Changelog + +All notable changes to BetterBase will be documented in this file. + +### Recent Updates (2026-03-30) + +#### Documentation Updates +- **CODEBASE_MAP.md**: Updated with new IaC error classes, generators, docs directory, and IaC template + +#### New Modules Added +- **IaC Errors**: Added `iac/errors.ts` with error classes (IaCError, ValidationError, DatabaseError, AuthError, NotFoundError) and formatError utility +- **IaC Generators**: Added code generators (drizzle-schema-gen.ts, migration-gen.ts, api-typegen.ts) +- **Documentation**: Added docs/ directory with comprehensive documentation structure +- **IaC Template**: Added `templates/iac/` for Convex-inspired IaC pattern projects + +#### Package Updates +- **packages/cli**: 30+ test files, 9 utility modules +- **packages/client**: IaC client with hooks, vanilla client, paginated queries, embeddings +- **packages/server**: Enhanced admin routes with roles, notifications, SMTP, API keys, CLI sessions, Inngest integration +- **apps/test-project**: Example project demonstrating all features + +--- + +## License + +BetterBase is released under the MIT license. + diff --git a/CONTRIBUTING.md b/specs/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to specs/CONTRIBUTING.md diff --git a/CodeRabbit_Full_Codebase_review.md b/specs/CodeRabbit_Full_Codebase_review.md similarity index 100% rename from CodeRabbit_Full_Codebase_review.md rename to specs/CodeRabbit_Full_Codebase_review.md diff --git a/NOTICE.md b/specs/NOTICE.md similarity index 100% rename from NOTICE.md rename to specs/NOTICE.md diff --git a/specs/README.md b/specs/README.md new file mode 100644 index 0000000..9dab5f2 --- /dev/null +++ b/specs/README.md @@ -0,0 +1,647 @@ +
+ +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/weroperking/Betterbase) + +``` +β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— +β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β•β•β• β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β• +β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ•— β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— +β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• +β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— +β•šβ•β• β•šβ•β•β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β•β•β•β•β• β•šβ•β•β•β•β•β•β•β•šβ•β•β•β•β•β•β• β•šβ•β•β•β•β•β• β•šβ•β•β• β•šβ•β•β• β•šβ•β•β•β•β•β•β• +``` + +**The AI-Native Backend-as-a-Service Platform** + +Blazing-fast backend development with Sub-100ms Local Dev β€” Built on Bun + SQLite, deploy anywhere with PostgreSQL support. + +*Database β€’ Authentication β€’ Realtime Subscriptions β€’ Storage β€’ Serverless Functions β€’ Vector Search* + +**Last Updated: 2026-03-30** + +
+ +--- + +## Why Choose Betterbase? + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ ✦ BETTERBASE ARCHITECTURE ✦ β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Frontend β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ (React, Vue, │──────────▢│ BETTERBASE CORE │──────────▢│ DB β”‚ β”‚ +β”‚ β”‚ Mobile, β”‚ β”‚ β”‚ β”‚ SQLite/ β”‚ β”‚ +β”‚ β”‚ Svelte) β”‚ β”‚ Auth β”‚ Realtime β”‚ Storage β”‚ β”‚ Postgresβ”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ RLS β”‚ Vector β”‚ Fns β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ β”‚ IaC │◀──── (Infrastructure as Code) β”‚ β”‚ +β”‚ β”‚ Layer β”‚ β”‚ β”‚ +β”‚ β”‚ Convex-ish β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”β”‚ +β”‚ β”‚ Inngest β”‚β”‚ +β”‚ β”‚ Workflows β”‚β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +| | TraditionalBAAS | Betterbase | +|--|------------------|------------| +| ⚑ | Slow local dev | **Sub-100ms dev** with Bun + SQLite | +| πŸ—„οΈ | Black box DB | **Full PostgreSQL** with raw SQL access | +| πŸ” | Basic search | **Full-text + Vector search** built-in | +| πŸš€ | Cloud lock-in | **Self-host anywhere** with Docker | +| πŸ“Š | Limited analytics | **Full observability** out of the box | +| πŸ” | Closed source | **100% open source** - deploy anywhere | + +--- + +## Quick Start + +```bash +# Install the CLI +bun install -g @betterbase/cli + +# Create a new project (IaC mode - recommended) +bb init my-app +cd my-app +bun install +bb dev +``` + +Your project structure: + +``` +my-app/ +β”œβ”€β”€ betterbase/ +β”‚ β”œβ”€β”€ schema.ts # Define tables (Convex-style) +β”‚ β”œβ”€β”€ queries/ # Read functions (auto-subscribe) +β”‚ β”œβ”€β”€ mutations/ # Write functions (transactions) +β”‚ └── actions/ # Side-effects (scheduled, HTTP) +β”œβ”€β”€ betterbase.config.ts # Optional config +└── package.json +``` + +### Define Your Schema + +Edit `betterbase/schema.ts`: + +```typescript +import { defineSchema, defineTable, v } from "@betterbase/core/iac" + +export const schema = defineSchema({ + users: defineTable({ + name: v.string(), + email: v.string(), + }).uniqueIndex("by_email", ["email"]), + + posts: defineTable({ + title: v.string(), + content: v.string(), + published: v.boolean(), + authorId: v.id("users"), + }).index("by_author", ["authorId"]), +}) +``` + +### Write Functions + +```typescript +// betterbase/queries/posts.ts +import { query } from "@betterbase/core/iac" + +export const listPosts = query({ + args: { published: v.optional(v.boolean()) }, + handler: async (ctx, args) => { + return ctx.db.query("posts") + .filter("published", "eq", args.published ?? true) + .order("desc") + .take(50) + }, +}) +``` + +```typescript +// betterbase/mutations/posts.ts +import { mutation } from "@betterbase/core/iac" + +export const createPost = mutation({ + args: { + title: v.string(), + content: v.string(), + authorId: v.id("users"), + }, + handler: async (ctx, args) => { + return ctx.db.insert("posts", { + ...args, + published: false, + }) + }, +}) +``` + +### Run + +```bash +bb dev +``` + +Your backend runs at `http://localhost:3000`. The dashboard is at `http://localhost:3001`. + +--- + +## Key Features + +| Feature | Description | +|---------|-------------| +| **IaC Layer** | Convex-inspired: define schema + functions in TypeScript | +| **Auto-Realtime** | Queries auto-subscribe to changes | +| **Type Safety** | Full TypeScript inference, no code generation needed | +| **Migrations** | Automatic diff + apply on `bb dev` | +| **Raw SQL** | `ctx.db.execute()` for complex queries | +| **Full-Text Search** | PostgreSQL GIN indexes via `ctx.db.search()` | +| **Vector Search** | pgvector + HNSW for embeddings | +| **Serverless Functions** | Deploy custom API functions | +| **Storage** | S3-compatible object storage | +| **Webhooks** | Event-driven with signed payloads | +| **Background Jobs** | Durable workflows via Inngest | +| **RLS** | Row-level security policies | +| **Branching** | Preview environments per branch | + +--- + +## Betterbase vs Convex + +| Feature | Convex | Betterbase | +|---------|--------|------------| +| Database | Black box | Full PostgreSQL | +| Raw SQL | Not available | `ctx.db.execute()` | +| Full-Text Search | Not built-in | PostgreSQL FTS | +| Vector Search | Limited | pgvector + HNSW | +| Self-Hosting | Not supported | Docker to your infra | +| Migration | β€” | `bb migrate from-convex` | + +**Betterbase gives you Convex simplicity with full SQL power.** + +--- + +## Inngest Integration + +Betterbase uses [Inngest](https://www.inngest.com/) for durable workflows and background jobs. + +### Deployment Modes + +| Mode | Inngest Backend | Used By | +|------|----------------|---------| +| Cloud | `https://api.inngest.com` | BetterBase Cloud offering | +| Self-Hosted | `http://inngest:8288` | Docker deployment | +| Local Dev | `http://localhost:8288` | Development and testing | + +### Environment Variables + +```bash +# For local development +INNGEST_BASE_URL=http://localhost:8288 + +# For self-hosted production +INNGEST_BASE_URL=http://inngest:8288 +INNGEST_SIGNING_KEY=your-signing-key +INNGEST_EVENT_KEY=your-event-key +``` + +### Features + +- **Webhook Delivery**: Retryable, observable webhook delivery with automatic backoff +- **Notification Rules**: Cron-based metric polling with fan-out notifications +- **Background Exports**: Async CSV export with progress tracking + +--- + +## Project Structure + +Betterbase supports two patterns: + +### 1. IaC Pattern (Recommended) + +``` +my-app/ +β”œβ”€β”€ betterbase/ +β”‚ β”œβ”€β”€ schema.ts # defineSchema() + defineTable() +β”‚ β”œβ”€β”€ queries/ # query() functions +β”‚ β”œβ”€β”€ mutations/ # mutation() functions +β”‚ β”œβ”€β”€ actions/ # action() functions +β”‚ └── cron.ts # scheduled functions +β”œβ”€β”€ betterbase.config.ts # Optional config +└── package.json +``` + +### 2. Original Pattern (Advanced) + +``` +my-app/ +β”œβ”€β”€ src/ +β”‚ β”œβ”€β”€ db/ +β”‚ β”‚ β”œβ”€β”€ schema.ts # Drizzle schema +β”‚ β”‚ └── migrate.ts # Migration runner +β”‚ β”œβ”€β”€ routes/ # Hono routes +β”‚ └── functions/ # Serverless functions +β”œβ”€β”€ betterbase.config.ts +└── package.json +``` + +Both patterns work together. Add `betterbase/` to any existing project. + +--- + +## CLI Reference + +| Command | Description | +|---------|-------------| +| `bb init [name]` | Create new project | +| `bb dev` | Start dev server | +| `bb iac sync` | Sync IaC schema | +| `bb iac analyze` | Analyze query performance | +| `bb migrate` | Run migrations | +| `bb generate types` | Generate TypeScript types | + +--- + +## Client SDK + +```bash +bun add @betterbase/client +``` + +```typescript +import { createClient } from '@betterbase/client' + +const client = createClient({ + baseUrl: 'http://localhost:3000', +}) + +// Use IaC functions +const { data: posts } = await client.bff.queries.posts.listPosts({}) + +// Mutations +await client.bff.mutations.posts.createPost({ + title: 'Hello', + content: 'World', + authorId: 'user-123', +}) +``` + +--- + +## Deployment + +### Local + +```bash +bb dev +``` + +### Docker + +```bash +docker-compose up -d +``` + +### Self-Hosted + +See [SELF_HOSTED.md](SELF_HOSTED.md) for full documentation. + +```typescript +database: { + provider: 'neon', + connectionString: process.env.NEON_CONNECTION_STRING +} +``` + +### Turso (libSQL) + +Best for edge deployments and distributed databases. + +```typescript +database: { + provider: 'turso', + connectionString: process.env.TURSO_DATABASE_URL, + authToken: process.env.TURSO_AUTH_TOKEN +} +``` + +### MySQL + +Best for legacy applications or MySQL preference. + +```typescript +database: { + provider: 'mysql', + connectionString: process.env.MYSQL_URL +} +``` + +### PlanetScale (MySQL-compatible) + +Best for serverless MySQL with branch-based schema changes. + +```typescript +database: { + provider: 'planetscale', + connectionString: process.env.PLANETSCALE_URL +} +``` + +--- + +## Authentication + +### Setup BetterAuth + +Initialize authentication in your project: + +```bash +bb auth setup +``` + +This creates `src/auth/` with default configuration. + +### Configure Providers + +Edit `src/auth/index.ts`: + +```typescript +import { betterAuth } from 'better-auth' +import { drizzleAdapter } from 'better-auth/adapters/drizzle' +import { db } from '../db' + +export const auth = betterAuth({ + database: drizzleAdapter(db, { + provider: 'sqlite' // or 'postgres', 'mysql' + }), + emailAndPassword: { + enabled: true, + requireEmailVerification: false + }, + socialProviders: { + github: { + clientId: process.env.GITHUB_CLIENT_ID, + clientSecret: process.env.GITHUB_CLIENT_SECRET + }, + google: { + clientId: process.env.GOOGLE_CLIENT_ID, + clientSecret: process.env.GOOGLE_CLIENT_SECRET + } + }, + session: { + expiresIn: 60 * 60 * 24 * 7, // 7 days + updateAge: 60 * 60 * 24 // 1 day + } +}) +``` + +### Row Level Security + +Betterbase integrates with database RLS for secure data access: + +```typescript +// In your schema or via CLI +bb rls add \ + --table posts \ + --name users_own_posts \ + --command SELECT \ + --check "user_id = auth.uid()" +``` + +This ensures users can only access their own data. + +--- + +## Ask Deepwiki + +> *Your AI-powered development assistant, integrated directly into Betterbase.* + +Ask Deepwiki provides intelligent context for AI-assisted development: + +- **Smart Code Context**: Automatic `.betterbase-context.json` generation +- **IaC Analysis**: Understand your schema, queries, and mutations +- **Query Optimization**: Get recommendations for better performance +- **Documentation Generation**: Auto-generate docs from your code + +**Deepwiki Badge**: The badge at the top of this README links to [Ask Deepwiki](https://deepwiki.com/weroperking/Betterbase), where you can chat with an AI that understands your entire Betterbase project. + +### Using Ask Deepwiki + +1. **Development**: Get instant answers about your IaC layer +2. **Debugging**: Understand query behavior and optimization +3. **Onboarding**: New team members can ask about your architecture +4. **Refactoring**: Get AI suggestions for improving your code + +--- + +## Contributing + +We welcome contributions! Please follow these steps: + +### Getting Started + +1. **Fork** the repository +2. **Clone** your fork: `git clone https://github.com/your-username/betterbase.git` +3. **Install** dependencies: `bun install` +4. **Create** a branch: `git checkout -b feature/my-feature` + +### Development Setup + +```bash +# Install dependencies +bun install + +# Build all packages +bun run build + +# Run tests +bun test + +# Run linting +bun run lint +``` + +### Project Structure + +``` +betterbase/ +β”œβ”€β”€ apps/ +β”‚ └── test-project/ # Example/test project +β”œβ”€β”€ packages/ +β”‚ β”œβ”€β”€ cli/ # @betterbase/cli +β”‚ β”œβ”€β”€ client/ # @betterbase/client +β”‚ └── core/ # @betterbase/core +β”œβ”€β”€ templates/ # Project templates +└── turbo.json # Turborepo configuration +``` + +### Code Style + +We use Biome for code formatting and linting: + +```bash +# Format code +bun run format + +# Lint code +bun run lint + +# Fix auto-fixable issues +bun run lint:fix +``` + +### Testing + +```bash +# Run all tests +bun test + +# Run tests for specific package +bun test --filter=@betterbase/cli + +# Run tests in watch mode +bun test --watch +``` + +### Commit Messages + +Follow Conventional Commits: + +``` +feat: add new feature +fix: resolve bug +docs: update documentation +refactor: restructure code +test: add tests +chore: maintenance +``` + +### Submitting Changes + +1. Push your branch: `git push origin feature/my-feature` +2. Open a **Pull Request** +3. Fill out the PR template +4. Wait for review + +--- + +## Code of Conduct + +### Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +### Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +### Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +### Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at conduct@betterbase.io. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. + +--- + +## Changelog + +All notable changes to this project will be documented in this section. + +### [1.0.0] - 2026-03-19 + +#### Added + +- **AI Context Generation**: Automatic `.betterbase-context.json` generation for AI-assisted development +- **Branch Management**: New `bb branch` command for creating isolated preview environments +- **Vector Search**: pgvector-powered similarity search with embeddings support +- **Auto-REST**: Automatic CRUD route generation from Drizzle schema +- **Enhanced CLI**: Added 12 commands including branch, webhook management, and storage operations + +#### Updated + +- Updated copyright year to 2026 +- Improved documentation with Last Updated timestamp +- Verified all features against current codebase structure +- Removed deprecated @betterbase/shared package references + +#### Security + +- Improved webhook signature verification +- Enhanced RLS policy engine + +--- + +## License + +Betterbase is open source under the [MIT License](LICENSE). + +``` +MIT License + +Copyright (c) 2026 Betterbase + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +--- + +## Community & Support + +### Get Help + +| Resource | Link | +|----------|------| +| **Discord** | [Discord](https://discord.gg/R6Dm6Cgy2E) | +| **GitHub Issues** | [GitHub Issues](https://github.com/weroperking/Betterbase/issues) | + +### Contribute + +| Resource | Link | +|----------|------| +| **GitHub** | [GitHub](https://github.com/weroperking/Betterbase) | +| **Contributing Guide** | [CONTRIBUTING.md](CONTRIBUTING.md) | +| **Good First Issues** | [Good First Issues](https://github.com/weroperking/Betterbase/labels/good-first-issue) | + +--- + +
+ +**Built with ❀️ by Weroperking** + +[Website](#) β€’ [Documentation](docs/README.md) β€’ [Discord](https://discord.gg/R6Dm6Cgy2E) β€’ [GitHub](https://github.com/weroperking/Betterbase) β€’ [Twitter](#) + +
\ No newline at end of file diff --git a/SELF_HOSTED.md b/specs/SELF_HOSTED.md similarity index 100% rename from SELF_HOSTED.md rename to specs/SELF_HOSTED.md From 26c7ffd48e207cee6638e3546fbb9fcf3d11004f Mon Sep 17 00:00:00 2001 From: BroUnion Date: Tue, 31 Mar 2026 16:47:34 +0000 Subject: [PATCH 4/6] fix: address code review findings - cleanup API fallbacks, fix WebSocket state, remove leaked content, fix broken links, sync Docker versions --- Dockerfile.project | 2 +- apps/dashboard/src/components/auth/SetupGuard.tsx | 15 ++++----------- apps/dashboard/src/lib/api.ts | 11 ++++++++++- docker-compose.yml | 1 + packages/cli/src/index.ts | 5 ++--- packages/client/src/iac/provider.tsx | 4 +++- specs/CodeRabbit_Full_Codebase_review.md | 11 +---------- specs/README.md | 6 +++--- 8 files changed, 25 insertions(+), 30 deletions(-) diff --git a/Dockerfile.project b/Dockerfile.project index 6196aff..efec61a 100644 --- a/Dockerfile.project +++ b/Dockerfile.project @@ -15,7 +15,7 @@ # ---------------------------------------------------------------------------- # Stage 1: Base # ---------------------------------------------------------------------------- -FROM oven/bun:1.3.9-debian AS base +FROM oven/bun:1.3.10-debian AS base LABEL maintainer="Betterbase Team" LABEL description="Betterbase Project - AI-Native Backend Platform" diff --git a/apps/dashboard/src/components/auth/SetupGuard.tsx b/apps/dashboard/src/components/auth/SetupGuard.tsx index 0496bd9..3959cfd 100644 --- a/apps/dashboard/src/components/auth/SetupGuard.tsx +++ b/apps/dashboard/src/components/auth/SetupGuard.tsx @@ -1,22 +1,15 @@ import { useEffect, useState } from "react"; import { useNavigate } from "react-router"; +import { checkSetup } from "../lib/api"; export function SetupGuard({ children }: { children: React.ReactNode }) { const navigate = useNavigate(); const [checking, setChecking] = useState(true); useEffect(() => { - // Try hitting /admin/auth/setup without a token. - // If setup is complete, login page is appropriate. - // If setup is not done, /admin/auth/setup returns 201, not 410. - fetch(`${import.meta.env.VITE_API_URL ?? "http://localhost:3001"}/admin/auth/setup`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ _check: true }), // Will fail validation but we only care about 410 - }) - .then((res) => { - if (res.status === 410) { - // Setup complete β€” redirect to login + checkSetup() + .then((isSetup) => { + if (isSetup) { navigate("/login", { replace: true }); } setChecking(false); diff --git a/apps/dashboard/src/lib/api.ts b/apps/dashboard/src/lib/api.ts index b1e95be..2223a34 100644 --- a/apps/dashboard/src/lib/api.ts +++ b/apps/dashboard/src/lib/api.ts @@ -1,4 +1,4 @@ -const API_BASE = import.meta.env.VITE_API_URL ?? "http://localhost:3001"; +const API_BASE = import.meta.env.VITE_API_URL; export class ApiError extends Error { constructor( @@ -94,3 +94,12 @@ export const api = { return res.blob(); }, }; + +export async function checkSetup(): Promise { + const res = await fetch(`${API_BASE}/admin/auth/setup`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ _check: true }), + }); + return res.status !== 410; +} diff --git a/docker-compose.yml b/docker-compose.yml index 6679b84..25f28c2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -38,6 +38,7 @@ services: environment: MINIO_ROOT_USER: ${MINIO_ROOT_USER:-betterbase} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-betterbase_dev_password} + MINIO_DATA_DIR: /data ports: - "9000:9000" - "9001:9001" diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index df0a8dc..91fb73b 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -553,10 +553,9 @@ export function createProgram(): Command { }); branch - .argument("[project-root]", "project root directory") .option("-p, --project-root ", "project root directory", process.cwd()) - .action(async (projectRootArg: string | undefined, options: { projectRoot?: string }) => { - const projectRoot = projectRootArg || options.projectRoot || process.cwd(); + .action(async (options: { projectRoot?: string }) => { + const projectRoot = options.projectRoot || process.cwd(); await runBranchCommand([], projectRoot); }); diff --git a/packages/client/src/iac/provider.tsx b/packages/client/src/iac/provider.tsx index b2ee5ed..79b8e88 100644 --- a/packages/client/src/iac/provider.tsx +++ b/packages/client/src/iac/provider.tsx @@ -32,6 +32,7 @@ export function BetterbaseProvider({ function connect() { if (isCleaned) return; + setWsReady(false); const wsUrl = `${config.url.replace(/^http/, "ws")}/betterbase/ws?project=${config.projectSlug ?? "default"}`; const ws = new WebSocket(wsUrl); wsRef.current = ws; @@ -48,7 +49,7 @@ export function BetterbaseProvider({ }; ws.onclose = () => { if (isCleaned) return; - setWsReady(false); + wsRef.current = null; timeoutId = setTimeout(connect, reconnectDelayMs); reconnectDelayMs = Math.min(reconnectDelayMs * 2, maxReconnectDelayMs); }; @@ -67,6 +68,7 @@ export function BetterbaseProvider({ return () => { isCleaned = true; + setWsReady(false); if (timeoutId !== null) clearTimeout(timeoutId); wsRef.current?.close(); }; diff --git a/specs/CodeRabbit_Full_Codebase_review.md b/specs/CodeRabbit_Full_Codebase_review.md index 935d064..b868856 100644 --- a/specs/CodeRabbit_Full_Codebase_review.md +++ b/specs/CodeRabbit_Full_Codebase_review.md @@ -777,16 +777,7 @@ Thanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=gi Comment `@coderabbitai help` to get the list of available commands and usage tips. - - - - - - - - - - +w9TR9X4poxAIY/gMxeE5AbfQkPDAjesSASxSYorSfIXDVeY50arQ2WQ1Y5rdYrfLrXfTYkjAbajXY9fTDE3Qws3SbeXINObG3dje3S41/DbG4z/N3MTQZJwnaaTf/Z433RTDw94rw0ApvH416fYeogYigAEqdIEuPKI0ExPOIxFJEw0RI6E6DZI0g2EsveE5E0lVE5ldE3U6o/UpsQ07E/4rnXg5Yl0WQZgaCIoEqWQakFrNrDrdOJVMQ2k36EBNgGDODfwGQmfSDBkrFDkjbLk3XHk4/Pktk6jU3Cba/Uw63UNc42jCbOQq3ObeQCsrAFXBMjaJ3KwmU1NZ3Owr/BwpUh45w1Up4l45/R3a4gTD/ew93Ecw7cc2TdUwAv3N4yopg74/I4kHEl7QE8IxAy0lA608E201PB0v7J07PRzV08vU9T0ugtEj4jEsAsPMcXc4M3E9vSHNyBhAsdon8ToofAfPMACkY3sKk0QwXMrOkyQ/M6QhjQ1RspfZsq1PIdsvqbXLQvXXk0xPQ/YgwlC4wi3E4us1dSw6U9bfsuU+cxU7ZT3Mc47CcjU3cDcn0k+OrFLYiWJVvOAg80FCI7mEEk85dNAxFK8/dEg3RPFXPTXIcSyfYIseLEAzi5fbixcMkRqbLI5MHH8QvF9YvO890lozvNyMlKwAAeVyAiByBLQBAAH15QcgbA6VkMv0r8eKtLcZ+TN9Fx6IJoPBHI5dpiaS1VRjwRcMsCUg99sKtjcKyz8K9iBTBsqyl8ayyLxT6y7dwQqhLFmTTDMKIqnd00pyri39ZzByFSf9RyVTmKVyXigCtTVK3QuKnlwN/BYD9yzTDzgSF0YjTyEVzyUUsjrzpKwQyCpx89TLSUyVBh8w8A/AQhpFaAS0Z86UN9EBSFocgK4dGcWqw51MOqSFssGxLFVCX5R9edqSUzYLjrMy0ADYEMF9hS0KWN5BOy1dOsKwNC4ruSDjyz9DBTiLzdRTmMsr0DziMj8qwy3rWz4yvrqK+NSqX8ka6BaKhyFyGLlTMl6qfc1zNT2KXyHlWr1KnliQlBGJTS3sjzPtojDlBrk8CDHSpKcVbzS97z88ii7SUSnzvTia1L1MKamIdKZrEdGIkAolHpkN/RODyUNBvi8Sr9ML6Ib9Pr2sNp6IoLDZQrbr0hiTfqddtj9ckrfKDi0qSKwaWy78JTKKyq+y+MMbqr7ily8a3CmqiadTBaUss5qQuro9eqLS6arSxKbTITrNJLdYOb3TubwcO9SVALdY9rulxbhTyUrAfRiQx4M6UZvyFCKAlCrQVKOLSb1Nfbt4QybQr9y6EaNbpB6I2BIzKAwZDY0yCJbhdbdgp98t5VirDaD8ErnUCKUrKyhTqzjixSbbsrLDqgLLM62sX4c72o86L4C7dFa6Ot8hoROSmgqKbDKrbjhzsbarcbXDVzXiA8Dr3RMT+1qbzTIjg7RLNd4jmbI7iC2aJrjKQc3MUFOw2BEYGRcD46odfNdr16U7z0wzyUZ4xMThZBRwqg5BRwuBccm6KAKd7A4RSx+96csBi6BbS63zS5K6e7x9O7ZiJDZ8ELFiXrqy18HA16rQNd+6cLSyh7kq/Lj8LbQab9yL2MQaRTeH4aiqDbOS96Zytt5S7jFzHiGrWLLs758FO0kZe1ugLgKArgbhAp34MA77A6H6RKBrQ6zzBhuBaAoUyVFGAkKoVG6g1GUpNGO6dGdLpk9LBxDRRqfxnNprebyjSUKEtoAhcNkhBwXVeB4NIBAAcAjy28YjWMi9ICcAFwCSVCgP4M6rMUCy68SKC8h8QuCqh3VefJk2GlkvIPWR6iJ1AFh+Kthk/M2oio40iye8wyUp/VG/erbQUOimq12s+xq9cgPKxhkGxy0Ox84Bx64W4bCMkYxOoAgSSf2sIwS2mwxhm4xhFUWMlISBkaARQfAMleiSxx7LwXZySA5oeFHLYc4EgU5/ZgRwkAQsjYkgIGBmXHsG0VaIIFQHlQ/BeomYW0SMeKXWa7Eb8gyclKrQYxBakU1XqeiLRaaI4dCaKcEUKDM7EKWbuIIgM0HZ8SFvgXJ0DXJcES5rwGgW5ulaZoFpQBhecBnIoooD5sxqFXsMgBh/CP8wMMookjKDxvdQcWJ3KoJpBUJtyXgDJYEZAaJm0QVj0hJwMZJ4iIxF+YObZHauzDKF1NFklgAXyaDrDpUfnYTruLONrwtJSKu4cEdrIhv4ZXWDAKpOMwq4C2fpHJb2fOaObCBOY9f0mfFJeuYpbQjSVJW1YpPaiDHBapbzHQCBkxdwDqHxYeqet7LRud25CLUFG5G6ZdtkfxvEYqoLQzbACzZzZkaYr6fka1KGdHDqFsfscuEmbqG6hCE7Xmd8z0eWb6oT3WdDSRSEFukXNeYmB/HixreUdGYbY0abZbd6jmb2e4KOXhVHedHYCBaclkGmhnlmtGlwFubBf9ZxCDZbdhXGTSg7EdUdCqGdPXpedAxowpSBgPeR3xdpxSDVianBbcd/jci7C2vYn5bNFlfib5oCa/XteJKUulU9KgZPfDcIlnt3f3c9aPY9b0n9kKXJVBcNYNOuEFigzDAtBRmub4F3ZbrMvBBzGRHPB/C2dwCptoGWBPaqA0FY9i0gDqHJTEAY+WBxBY7Y42pQiwqNsHrqctbHvSonvBqnoWxyvjCqDJSQ4XbpXBeC3OCDaRffC1Yjfua8swASHzEIn0jjeGXsFw7BnBaTfVsTNgQLdlIE2FCLWFDLePt6bVP6cJsGbwWsbrcnfGcba0exeEjQE7aMyDtWbMx+1tIHYaSlzEyiQgZrygaC/2BdZpazX6JxbpRknSFWkShUDAABfo2wgdQ+QMSNbEE3u3a73be/LJEDVP1pZjfBbPkSSzWRw0GFsYn49gUE8eDBlYRIFnBozCZjElfNEtFNDeQ7Fa1M43a8GJfJVq7pXq63fff1FnpoBfU9dWDwjvE9fgg4n8ByFoDJVVdJS6/Qya8GwU7kAcsO6WrpQZwpUWuO9O8Obu927JboCe6wC2Y/m+9O4E3JQcu2bvBBVpRaCCdYi3u63tXpkMRGsA4pPILiYSHlYW80JqYBtNvE/uak+tpadadTY6d6CLV6Bc6sTc5YoJrYtaXaTKJo0O42XC4UHXFEi4D2X6sOWmTAhOXmRaUWQgDZ+0gcuQUQAcsojoAcoxwSHOQZ+FAAFZhQ0AahhRugagSBBQ/RJQ0BhRhReh2QGgGBegBBNfuQagBAWReg0BuRzHhQ/QBB9feh+h5elkRf1Axf9RJfnDpeL0FkGfxWHK2AgYSBQeQZI4ZeyRTVdBBeDAABvAwVoBuJAWwE4WVL0OgNcYZKwXzO8BuLgJQooNaJPyAFPxASy8LEIZFDAAv/0XTEv5PtOLBogKIcLMKaBFKK+AEGZkgOvxP1oZPq+tqv08dfv0vwfsvggMSAAMUycXDr8FFogn8H4bgusXAAHUFh5Q84ycmFF+V+dXl/J+G5NyvjH1eV2V2Ax+uAB/J+p+YIdg5/cHEBF/j/7+1/5/x4t+OId+GA9/XEdfYTJPyP4r9T+PpKotuXSw6ZiGr/W/iv2T7T8n+X/OAZACX4ICy+6/b/tv134t9UBwAwfqAJP5n8b6YeDnF+XH738H+s/FAW/wwGf8X+P/WAH/wAH4DD+7/IfhAK3KPoYCN/SAHfxP5ICPAz/H9KgPQFUCGBogpgSwLwEH8QBHAsviQKIYVQICyCKArwJ0iUCP+QgkQcYjoESCsBiAaQbgJCD78uAXTeQWAKUF6kdyfxE0poPgESCdBtA8wQoOT6GDjB//WQVwAIGtAdWpfIgWXwAi8R1AG/d+DQCsAUB3AkUPvoXwb7v8G4roYEGrHT55wDgtgOvkXxuAJDEYtAGwDmD/499TBriCIBHzr5sJG+ZfXIfkIwDRCvApQt6OUN+iVCG41QgoUqBVB5gGhXoTIfENL4NwI4dAHIFtUchFC6+AKBIdsCcjdCDgVKBwA6lQEABtFfgINX6vQvQ7QCNuMMVBBFOhjKGYQ3DcFl9v4FIJoTeCOENxjUpeMeOMJmH2Ap4G6egFAHAQhDcAgATAJkACAIgLADABeApA18emvIFQBkAMEjSQ4fQKiqxCy+FkCgGxFLDgiJB/gUIFnQ8AzDNhbAcYUoF2HwBVQL8KdJYMn6rDk+6wg4OiKhENw6hO+RoRcJOGoCshlQk/lcMwA3CuAFIwOIwG2CdhnUVScIDNFeFLl4ECQS0O1nApxFY24UOSp4wBzjUZwc4NQsqF8zIBWQZULwDyxajnUv+AFA7FFXwzIQNiCIj/htAJgFB1Q4wt4DOCCDsjCE6ABgJLAGSoQsRyoeAN0EShtE0Cx0dhLAHQDIAAA5L4DthcAwcb9FHlNWsK9hsgeQblEPDUo8B0SPohjBIi7DuoNABok/pCPGEwi4RRAVMavwCAFJWYAQXocXwuFIj5gV8NEVsNZF34vA+IwgQoKJFBCI+ZI7YSYJ/Bt9lApAHMcn1pFFjsh9ApkQvVNGsiZBxQhQO3zbDIBJQNQDQIbwACktkasF6NQAPxsAmsasPyk0DCI0I14QpLXA2iqx6AqAXoCyA0AsgWQs4lMRcKNFBU8w4wsId2FdStimEwLF1IYMJCbwUW90XsKFHECIA/Qn4Nol5A7Ect9xBMMERcPTGsjMxpoLsWX1LEoiKxGI1kc32KGv9D+pfAALqTDrguAWwDsKdG4ihxZfBoKoCG628reJAXoIr16CChaAkoSULQHZB68GA3QUQOyBt5+hhQtAP0PyG5AkBuQfEhgExIaC0BTetABoLb25AMA/QvQP0HRLQCm9uQ7IGoAaIGE4TbAlI8YdyEFANBegFvNAGgEV6iTJQgoFkAIHZB8TFeLE7kIryG5mTGOJklkNRIEDa92JJAYUIKAMm0AwQ0kkgOyGt4sgGAEkpQMrxqAiSp0/goXlACD4h9SA4fN6BL396C8GezPByitBuBR8GQmUmPucnj5qSnIGdVaLQAh5UodoWfIZOoCiA5gB4HAFkJFJSkIQ0p8EDKUIQcpJS9AQAA --> ``` ----- diff --git a/specs/README.md b/specs/README.md index 9dab5f2..b91b4f6 100644 --- a/specs/README.md +++ b/specs/README.md @@ -591,7 +591,7 @@ All notable changes to this project will be documented in this section. ## License -Betterbase is open source under the [MIT License](LICENSE). +Betterbase is open source under the [MIT License](../LICENSE). ``` MIT License @@ -633,7 +633,7 @@ SOFTWARE. | Resource | Link | |----------|------| | **GitHub** | [GitHub](https://github.com/weroperking/Betterbase) | -| **Contributing Guide** | [CONTRIBUTING.md](CONTRIBUTING.md) | +| **Contributing Guide** | [CONTRIBUTING.md](../CONTRIBUTING.md) | | **Good First Issues** | [Good First Issues](https://github.com/weroperking/Betterbase/labels/good-first-issue) | --- @@ -642,6 +642,6 @@ SOFTWARE. **Built with ❀️ by Weroperking** -[Website](#) β€’ [Documentation](docs/README.md) β€’ [Discord](https://discord.gg/R6Dm6Cgy2E) β€’ [GitHub](https://github.com/weroperking/Betterbase) β€’ [Twitter](#) +Website β€’ [Documentation](../docs/README.md) β€’ [Discord](https://discord.gg/R6Dm6Cgy2E) β€’ [GitHub](https://github.com/weroperking/Betterbase) β€’ Twitter \ No newline at end of file From 13a308648b3ab3b6d7fdb42e2152f5dcef238359 Mon Sep 17 00:00:00 2001 From: BroUnion Date: Wed, 1 Apr 2026 00:57:27 +0000 Subject: [PATCH 5/6] feat: enhance project initialization by prompting for project name and confirming directory overwrite --- .../src/components/auth/SetupGuard.tsx | 4 +-- packages/cli/src/commands/init.ts | 27 ++++++++++++++----- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/apps/dashboard/src/components/auth/SetupGuard.tsx b/apps/dashboard/src/components/auth/SetupGuard.tsx index 3959cfd..147b13e 100644 --- a/apps/dashboard/src/components/auth/SetupGuard.tsx +++ b/apps/dashboard/src/components/auth/SetupGuard.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { useNavigate } from "react-router"; -import { checkSetup } from "../lib/api"; +import { checkSetup } from "../../lib/api"; export function SetupGuard({ children }: { children: React.ReactNode }) { const navigate = useNavigate(); @@ -8,7 +8,7 @@ export function SetupGuard({ children }: { children: React.ReactNode }) { useEffect(() => { checkSetup() - .then((isSetup) => { + .then((isSetup: boolean) => { if (isSetup) { navigate("/login", { replace: true }); } diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 5a0d59b..e367002 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -1310,13 +1310,30 @@ export async function runInitCommand(rawOptions: InitCommandOptions): Promise Date: Wed, 1 Apr 2026 19:37:58 +0000 Subject: [PATCH 6/6] feat: implement metrics page and project layout with navigation tabs --- apps/dashboard/src/layouts/AppLayout.tsx | 3 +- apps/dashboard/src/lib/utils.ts | 10 +- apps/dashboard/src/pages/MetricsPage.tsx | 273 ++++++++++++++++++ .../src/pages/projects/ProjectDetailPage.tsx | 240 +++++---------- .../src/pages/projects/ProjectLayout.tsx | 86 ++++++ apps/dashboard/src/routes.tsx | 39 ++- packages/cli/src/commands/login.ts | 69 ++++- packages/cli/src/index.ts | 9 +- packages/server/src/lib/env.ts | 1 + packages/server/src/routes/admin/metrics.ts | 4 +- 10 files changed, 544 insertions(+), 190 deletions(-) create mode 100644 apps/dashboard/src/pages/MetricsPage.tsx create mode 100644 apps/dashboard/src/pages/projects/ProjectLayout.tsx diff --git a/apps/dashboard/src/layouts/AppLayout.tsx b/apps/dashboard/src/layouts/AppLayout.tsx index 07d3c7e..8559ff1 100644 --- a/apps/dashboard/src/layouts/AppLayout.tsx +++ b/apps/dashboard/src/layouts/AppLayout.tsx @@ -33,7 +33,8 @@ const nav = [ { label: "Projects", href: "/projects", icon: FolderOpen }, { label: "Storage", href: "/storage", icon: HardDrive }, { label: "Logs", href: "/logs", icon: ScrollText }, - { label: "Observability", href: "/observability", icon: BarChart2 }, + { label: "Observability", href: "/observability", icon: Activity }, + { label: "Metrics", href: "/metrics", icon: BarChart2 }, { label: "Audit Log", href: "/audit", icon: Shield }, { label: "Team", href: "/team", icon: Users }, { diff --git a/apps/dashboard/src/lib/utils.ts b/apps/dashboard/src/lib/utils.ts index ab9c255..cfe5bed 100644 --- a/apps/dashboard/src/lib/utils.ts +++ b/apps/dashboard/src/lib/utils.ts @@ -5,7 +5,13 @@ export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } -export function formatDate(date: string | Date, opts?: Intl.DateTimeFormatOptions) { +export function formatDate( + date: string | Date | null | undefined, + opts?: Intl.DateTimeFormatOptions, +) { + if (!date) return "N/A"; + const d = new Date(date); + if (isNaN(d.getTime())) return "N/A"; return new Intl.DateTimeFormat("en-US", { year: "numeric", month: "short", @@ -13,7 +19,7 @@ export function formatDate(date: string | Date, opts?: Intl.DateTimeFormatOption hour: "2-digit", minute: "2-digit", ...opts, - }).format(new Date(date)); + }).format(d); } export function formatRelative(date: string | Date): string { diff --git a/apps/dashboard/src/pages/MetricsPage.tsx b/apps/dashboard/src/pages/MetricsPage.tsx new file mode 100644 index 0000000..1a07b5f --- /dev/null +++ b/apps/dashboard/src/pages/MetricsPage.tsx @@ -0,0 +1,273 @@ +import { PageHeader } from "@/components/ui/PageHeader"; +import { PageSkeleton } from "@/components/ui/PageSkeleton"; +import { StatCard } from "@/components/ui/StatCard"; +import { api } from "@/lib/api"; +import { QK } from "@/lib/query-keys"; +import { useQuery } from "@tanstack/react-query"; +import { BarChart2, Clock, FolderOpen, TrendingUp, Users, Zap } from "lucide-react"; +import { useState } from "react"; +import { Area, AreaChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts"; + +type Period = "24h" | "7d" | "30d"; + +export default function MetricsPage() { + const [period, setPeriod] = useState("24h"); + + const { data: metrics, isLoading } = useQuery({ + queryKey: QK.metricsOverview(), + queryFn: () => api.get<{ metrics: any }>("/admin/metrics/overview"), + refetchInterval: 30_000, + }); + + const { data: latency } = useQuery({ + queryKey: QK.metricsLatency(period), + queryFn: () => api.get<{ latency: any }>(`/admin/metrics/latency?period=${period}`), + refetchInterval: 30_000, + }); + + const { data: timeseries } = useQuery({ + queryKey: QK.metricsTimeseries("requests", period), + queryFn: () => api.get<{ timeseries: any[] }>(`/admin/metrics/timeseries?period=${period}`), + refetchInterval: 30_000, + }); + + const { data: topEndpoints } = useQuery({ + queryKey: QK.metricsTopEndpoints(period), + queryFn: () => + api.get<{ endpoints: any[] }>(`/admin/metrics/top-endpoints?period=${period}&limit=15`), + refetchInterval: 30_000, + }); + + if (isLoading) return ; + + const m = metrics?.metrics; + const l = latency?.latency; + const ts = timeseries?.timeseries ?? []; + const endpoints = topEndpoints?.endpoints ?? []; + + return ( +
+ + +
+ {/* Period selector */} +
+ {(["24h", "7d", "30d"] as Period[]).map((p) => ( + + ))} +
+ + {/* Key Metrics Cards */} +
+ + + + +
+ + {/* Latency Distribution */} +
+ {[ + { label: "P50 Latency", value: l?.p50 ?? 0, icon: TrendingUp }, + { label: "P95 Latency", value: l?.p95 ?? 0, icon: TrendingUp }, + { label: "P99 Latency", value: l?.p99 ?? 0, icon: TrendingUp }, + { label: "Avg Latency", value: l?.avg ?? 0, icon: BarChart2 }, + ].map(({ label, value, icon: Icon }) => ( +
+
+ + + {label} + +
+
+ {value}ms +
+
+ ))} +
+ + {/* Request Trends */} +
+

+ Request Trends β€” {period} +

+
+ + + new Date(v).toLocaleTimeString([], { hour: "2-digit" })} + stroke="var(--color-text-muted)" + fontSize={11} + /> + + + + + + +
+
+ + {/* Top Endpoints Performance */} +
+

+ Top Endpoints by Requests β€” {period} +

+
+ + + + + + + + + + + + + {endpoints.length === 0 ? ( + + + + ) : ( + endpoints.map((ep: any, i: number) => ( + + + + + + + + + )) + )} + +
RankMethodEndpointRequestsAvg LatencyErrors
+ No endpoint data available +
+ #{i + 1} + + + {ep.method} + + + {ep.path} + + {ep.requests?.toLocaleString() ?? ep.count?.toLocaleString() ?? 0} + + {ep.avg_ms}ms + 0 ? "var(--color-danger)" : "var(--color-text-muted)", + }} + > + {ep.errors} +
+
+
+
+
+ ); +} diff --git a/apps/dashboard/src/pages/projects/ProjectDetailPage.tsx b/apps/dashboard/src/pages/projects/ProjectDetailPage.tsx index d895b06..0594b67 100644 --- a/apps/dashboard/src/pages/projects/ProjectDetailPage.tsx +++ b/apps/dashboard/src/pages/projects/ProjectDetailPage.tsx @@ -1,30 +1,15 @@ import { ConfirmDialog } from "@/components/ui/ConfirmDialog"; -import { PageHeader } from "@/components/ui/PageHeader"; import { PageSkeleton } from "@/components/ui/PageSkeleton"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { api } from "@/lib/api"; import { QK } from "@/lib/query-keys"; import { formatDate } from "@/lib/utils"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { - Activity, - Clock, - Database, - FolderOpen, - Globe, - Key, - Pause, - Play, - Trash2, - Users, - Webhook, - Zap, -} from "lucide-react"; +import { Pause, Play, Trash2 } from "lucide-react"; import { useState } from "react"; -import { Link, useNavigate, useParams } from "react-router"; +import { useNavigate, useParams } from "react-router"; import { toast } from "sonner"; export default function ProjectDetailPage() { @@ -69,160 +54,81 @@ export default function ProjectDetailPage() { if (isLoading) return ; const project = data?.project; - const tabs = [ - { value: "overview", label: "Overview", icon: FolderOpen }, - { - value: "observability", - label: "Observability", - icon: Activity, - href: `/projects/${projectId}/observability`, - }, - { value: "users", label: "Users", icon: Users, href: `/projects/${projectId}/users` }, - { value: "auth", label: "Auth", icon: Key, href: `/projects/${projectId}/auth` }, - { - value: "database", - label: "Database", - icon: Database, - href: `/projects/${projectId}/database`, - }, - { value: "env", label: "Environment", icon: Globe, href: `/projects/${projectId}/env` }, - { - value: "webhooks", - label: "Webhooks", - icon: Webhook, - href: `/projects/${projectId}/webhooks`, - }, - { value: "functions", label: "Functions", icon: Zap, href: `/projects/${projectId}/functions` }, - { value: "realtime", label: "Realtime", icon: Clock, href: `/projects/${projectId}/realtime` }, - ]; - return ( -
- - - -
- } - /> - -
- - - {tabs.map((tab) => - tab.href ? ( - - - {tab.label} - - - ) : ( - - {tab.label} - - ), - )} - - - -
- - - Project Details - - -
- Status - - {project?.status} - -
-
- Slug - {project?.slug} -
-
- Created - - {formatDate(project?.created_at)} - -
-
-
+
+
+ + + Project Details + + +
+ Status + + {project?.status} + +
+
+ Slug + {project?.slug} +
+
+ Created + + {formatDate(project?.created_at)} + +
+
+
- - - Admin Key - - Use this key to authenticate as this project's admin. - - - - - - + + + Admin Key + Use this key to authenticate as this project's admin. + + + + + - - - Danger Zone - Irreversible and destructive actions. - - -
-
-

Suspend Project

-

- Prevents all user authentication. -

-
- -
-
-
-

Delete Project

-

- Permanently remove this project and all data. -

-
- -
-
-
+ + + Danger Zone + Irreversible and destructive actions. + + +
+
+

Suspend Project

+

+ Prevents all user authentication. +

+
+ +
+
+
+

Delete Project

+

+ Permanently remove this project and all data. +

+
+
- - +
+
api.get<{ project: any }>(`/admin/projects/${projectId}`), + }); + + if (isLoading) return ; + + const project = data?.project; + + const tabs = [ + { value: "overview", label: "Overview", icon: FolderOpen, href: `/projects/${projectId}` }, + { + value: "observability", + label: "Observability", + icon: Activity, + href: `/projects/${projectId}/observability`, + }, + { value: "users", label: "Users", icon: Users, href: `/projects/${projectId}/users` }, + { value: "auth", label: "Auth", icon: Key, href: `/projects/${projectId}/auth` }, + { + value: "database", + label: "Database", + icon: Database, + href: `/projects/${projectId}/database`, + }, + { value: "env", label: "Environment", icon: Globe, href: `/projects/${projectId}/env` }, + { + value: "webhooks", + label: "Webhooks", + icon: Webhook, + href: `/projects/${projectId}/webhooks`, + }, + { value: "functions", label: "Functions", icon: Zap, href: `/projects/${projectId}/functions` }, + { value: "iac", label: "IaC", icon: Code, href: `/projects/${projectId}/iac/schema` }, + { value: "realtime", label: "Realtime", icon: Clock, href: `/projects/${projectId}/realtime` }, + ]; + + const currentPath = location.pathname; + const activeTab = tabs.find((tab) => tab.href === currentPath)?.value ?? "overview"; + + return ( +
+ + +
+ + + {tabs.map((tab) => ( + + + {tab.label} + + + ))} + + +
+ + +
+ ); +} diff --git a/apps/dashboard/src/routes.tsx b/apps/dashboard/src/routes.tsx index 749050d..1377ff8 100644 --- a/apps/dashboard/src/routes.tsx +++ b/apps/dashboard/src/routes.tsx @@ -18,8 +18,10 @@ const SetupPage = lazy(() => import("@/pages/SetupPage")); const LoginPage = lazy(() => import("@/pages/LoginPage")); const OverviewPage = lazy(() => import("@/pages/OverviewPage")); const ObservabilityPage = lazy(() => import("@/pages/ObservabilityPage")); +const MetricsPage = lazy(() => import("@/pages/MetricsPage")); const ProjectsPage = lazy(() => import("@/pages/projects/ProjectsPage")); const ProjectDetailPage = lazy(() => import("@/pages/projects/ProjectDetailPage")); +const ProjectLayout = lazy(() => import("@/pages/projects/ProjectLayout")); const ProjectObservabilityPage = lazy(() => import("@/pages/projects/ProjectObservabilityPage")); const ProjectUsersPage = lazy(() => import("@/pages/projects/users/ProjectUsersPage")); const ProjectUserPage = lazy(() => import("@/pages/projects/users/ProjectUserPage")); @@ -66,22 +68,29 @@ export const routes: RouteObject[] = [ children: [ { index: true, element: wrap(OverviewPage) }, { path: "observability", element: wrap(ObservabilityPage) }, + { path: "metrics", element: wrap(MetricsPage) }, { path: "projects", element: wrap(ProjectsPage) }, - { path: "projects/:projectId", element: wrap(ProjectDetailPage) }, - { path: "projects/:projectId/observability", element: wrap(ProjectObservabilityPage) }, - { path: "projects/:projectId/users", element: wrap(ProjectUsersPage) }, - { path: "projects/:projectId/users/:userId", element: wrap(ProjectUserPage) }, - { path: "projects/:projectId/auth", element: wrap(ProjectAuthPage) }, - { path: "projects/:projectId/database", element: wrap(ProjectDatabasePage) }, - { path: "projects/:projectId/realtime", element: wrap(ProjectRealtimePage) }, - { path: "projects/:projectId/env", element: wrap(ProjectEnvPage) }, - { path: "projects/:projectId/webhooks", element: wrap(ProjectWebhooksPage) }, - { path: "projects/:projectId/functions", element: wrap(ProjectFunctionsPage) }, - { path: "projects/:projectId/iac/schema", element: wrap(ProjectIaCSchemaPage) }, - { path: "projects/:projectId/iac/functions", element: wrap(ProjectIaCFunctionsPage) }, - { path: "projects/:projectId/iac/jobs", element: wrap(ProjectIaCJobsPage) }, - { path: "projects/:projectId/iac/realtime", element: wrap(ProjectIaCRealtimePage) }, - { path: "projects/:projectId/iac/query", element: wrap(ProjectIaCQueryPage) }, + { + path: "projects/:projectId", + element: wrap(ProjectLayout), + children: [ + { index: true, element: wrap(ProjectDetailPage) }, + { path: "observability", element: wrap(ProjectObservabilityPage) }, + { path: "users", element: wrap(ProjectUsersPage) }, + { path: "users/:userId", element: wrap(ProjectUserPage) }, + { path: "auth", element: wrap(ProjectAuthPage) }, + { path: "database", element: wrap(ProjectDatabasePage) }, + { path: "realtime", element: wrap(ProjectRealtimePage) }, + { path: "env", element: wrap(ProjectEnvPage) }, + { path: "webhooks", element: wrap(ProjectWebhooksPage) }, + { path: "functions", element: wrap(ProjectFunctionsPage) }, + { path: "iac/schema", element: wrap(ProjectIaCSchemaPage) }, + { path: "iac/functions", element: wrap(ProjectIaCFunctionsPage) }, + { path: "iac/jobs", element: wrap(ProjectIaCJobsPage) }, + { path: "iac/realtime", element: wrap(ProjectIaCRealtimePage) }, + { path: "iac/query", element: wrap(ProjectIaCQueryPage) }, + ], + }, { path: "webhooks/:webhookId/deliveries", element: wrap(WebhookDeliveriesPage) }, { path: "functions/:functionId/invocations", element: wrap(FunctionInvocationsPage) }, { path: "storage", element: wrap(StoragePage) }, diff --git a/packages/cli/src/commands/login.ts b/packages/cli/src/commands/login.ts index 22e270e..bdb648d 100644 --- a/packages/cli/src/commands/login.ts +++ b/packages/cli/src/commands/login.ts @@ -13,8 +13,14 @@ export function registerLoginCommand(program: Command) { .command("login") .description("Authenticate with a Betterbase instance") .option("--url ", "Self-hosted Betterbase server URL", DEFAULT_SERVER_URL) + .option("--email ", "Admin email (for API key login)") + .option("--password ", "Admin password (for API key login)") .action(async (opts) => { - await runLoginCommand({ serverUrl: opts.url }); + if (opts.email && opts.password) { + await runApiKeyLogin({ serverUrl: opts.url, email: opts.email, password: opts.password }); + } else { + await runLoginCommand({ serverUrl: opts.url }); + } }); program @@ -49,7 +55,16 @@ export async function runLoginCommand(opts: { serverUrl?: string } = {}) { userCode = data.user_code; verificationUri = data.verification_uri; } catch (err: any) { - error(`Could not reach server: ${err.message}`); + const msg = err.message || ""; + if (msg.includes("connect") || msg.includes("ECONNREFUSED") || msg.includes("fetch")) { + error(`Could not connect to ${serverUrl}. Is the server running?`); + console.log(chalk.dim(`\n To start a local server:`)); + console.log(chalk.dim(` cd packages/server && bun run dev`)); + console.log(chalk.dim(`\n Or specify a different URL:`)); + console.log(chalk.dim(` bb login --url http://localhost:3001`)); + } else { + error(`Could not reach server: ${msg}`); + } process.exit(1); } @@ -116,6 +131,56 @@ export async function runLoginCommand(opts: { serverUrl?: string } = {}) { process.exit(1); } +export async function runApiKeyLogin(opts: { + serverUrl?: string; + email: string; + password: string; +}): Promise { + const serverUrl = (opts.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/$/, ""); + + blank(); + section("API Key Login"); + + try { + const res = await fetch(`${serverUrl}/admin/auth`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email: opts.email, password: opts.password }), + }); + + if (!res.ok) { + const err = (await res.json()) as { error?: string }; + error(`Login failed: ${err.error || res.statusText}`); + process.exit(1); + } + + const { token, admin } = (await res.json()) as { token: string; admin: { email: string } }; + + saveCredentials({ + token, + admin_email: admin.email, + server_url: serverUrl, + created_at: new Date().toISOString(), + }); + + box("Logged in", [ + { label: "Instance", value: serverUrl }, + { label: "Account", value: admin.email }, + ]); + success(`Logged in as ${chalk.cyan(admin.email)}`); + } catch (err: any) { + const msg = err.message || ""; + if (msg.includes("connect") || msg.includes("ECONNREFUSED") || msg.includes("fetch")) { + error(`Could not connect to ${serverUrl}. Is the server running?`); + console.log(chalk.dim(`\n To start a local server:`)); + console.log(chalk.dim(` cd packages/server && bun run dev`)); + } else { + error(`Login failed: ${msg}`); + } + process.exit(1); + } +} + // Legacy exports for compatibility export async function runLoginCommandLegacy(): Promise { await runLoginCommand({}); diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 91fb73b..f7cbf13 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -563,8 +563,15 @@ export function createProgram(): Command { .command("login") .description("Authenticate with a Betterbase instance") .option("--url ", "Self-hosted Betterbase server URL", "https://api.betterbase.io") + .option("--email ", "Admin email (for headless/server login)") + .option("--password ", "Admin password (for headless/server login)") .action(async (opts) => { - await runLoginCommand({ serverUrl: opts.url }); + if (opts.email && opts.password) { + const { runApiKeyLogin } = await import("./commands/login"); + await runApiKeyLogin({ serverUrl: opts.url, email: opts.email, password: opts.password }); + } else { + await runLoginCommand({ serverUrl: opts.url }); + } }); program.command("logout").description("Sign out of Betterbase").action(runLogoutCommand); diff --git a/packages/server/src/lib/env.ts b/packages/server/src/lib/env.ts index a40e890..4edab8a 100644 --- a/packages/server/src/lib/env.ts +++ b/packages/server/src/lib/env.ts @@ -16,6 +16,7 @@ const EnvSchema = z.object({ INNGEST_BASE_URL: z.string().url().optional(), INNGEST_SIGNING_KEY: z.string().optional(), INNGEST_EVENT_KEY: z.string().optional(), + PORT: z.string().default("3000"), }); export type Env = z.infer; diff --git a/packages/server/src/routes/admin/metrics.ts b/packages/server/src/routes/admin/metrics.ts index d7e8a67..190b5ea 100644 --- a/packages/server/src/routes/admin/metrics.ts +++ b/packages/server/src/routes/admin/metrics.ts @@ -101,13 +101,13 @@ metricsRoutes.get("/top-endpoints", async (c) => { const { rows } = await pool.query( ` SELECT path, method, - COUNT(*)::int AS count, + COUNT(*)::int AS requests, AVG(duration_ms)::int AS avg_ms, COUNT(*) FILTER (WHERE status >= 500)::int AS errors FROM betterbase_meta.request_logs WHERE created_at > NOW() - INTERVAL '${range}' GROUP BY path, method - ORDER BY count DESC + ORDER BY requests DESC LIMIT $1 `, [limit],